You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3597 lines
94 KiB
3597 lines
94 KiB
<?php
|
|
|
|
define('INDENT', "\t");
|
|
ini_set('error_reporting', E_ALL);
|
|
assert_options(ASSERT_ACTIVE, 0);
|
|
if (function_exists("gc_disable")) {
|
|
gc_collect_cycles();
|
|
gc_disable();
|
|
}
|
|
|
|
function color($str, $color = 33)
|
|
{
|
|
return "\x1B[{$color}m$str\x1B[0m";
|
|
}
|
|
|
|
class Decompiler_Output
|
|
{
|
|
// {{{
|
|
var $errorToOutput = true;
|
|
var $target;
|
|
|
|
function Decompiler_Output($target)
|
|
{
|
|
$this->target = $target;
|
|
}
|
|
// }}}
|
|
|
|
var $indent = "";
|
|
function indent() // {{{
|
|
{
|
|
$this->indent .= INDENT;
|
|
}
|
|
// }}}
|
|
function outdent() // {{{
|
|
{
|
|
$this->indent = substr($this->indent, 0, -strlen(INDENT));
|
|
}
|
|
// }}}
|
|
|
|
function writeOne_($code) // {{{
|
|
{
|
|
if ($this->target == STDOUT) {
|
|
echo $code;
|
|
}
|
|
else {
|
|
fwrite($this->target, $code);
|
|
}
|
|
}
|
|
// }}}
|
|
var $writes = 0;
|
|
function write() // {{{
|
|
{
|
|
++$this->writes;
|
|
foreach (func_get_args() as $code) {
|
|
if (is_object($code)) {
|
|
$s = $code;
|
|
$code = $code->toCode($this->indent);
|
|
}
|
|
if (is_array($code)) {
|
|
call_user_func_array(array(&$this, 'write'), $code);
|
|
}
|
|
else {
|
|
$this->writeOne_((string) $code);
|
|
}
|
|
}
|
|
}
|
|
// }}}
|
|
function writeln() // {{{
|
|
{
|
|
if ($this->codeTypeCurrent != $this->codeTypeNext) {
|
|
if ($this->codeTypeCurrent && $this->codeTypeNext == 'line') {
|
|
$this->writeOne_(PHP_EOL);
|
|
}
|
|
$this->codeTypeCurrent = $this->codeTypeNext;
|
|
}
|
|
$this->writeOne_($this->indent);
|
|
|
|
$args = func_get_args();
|
|
call_user_func_array(array(&$this, 'write'), $args);
|
|
|
|
$this->writeOne_(PHP_EOL);
|
|
}
|
|
// }}}
|
|
function printfError($format) // {{{
|
|
{
|
|
$args = func_get_args();
|
|
unset($args[0]);
|
|
|
|
foreach ($args as $i => $arg) {
|
|
unset($arg);
|
|
|
|
if (is_object($args[$i])) {
|
|
$args[$i] = $args[$i]->toCode($this->indent);
|
|
}
|
|
}
|
|
|
|
$error = implode("", $args);
|
|
trigger_error($error);
|
|
if ($this->errorToOutput) {
|
|
$this->beginComment();
|
|
fwrite($this->target, $error);
|
|
$this->endComment();
|
|
}
|
|
}
|
|
// }}}
|
|
|
|
var $inComment = 0;
|
|
function beginComment() // {{{
|
|
{
|
|
if (!$this->inComment++) {
|
|
$this->writeln("/*");
|
|
}
|
|
}
|
|
// }}}
|
|
function endComment() // {{{
|
|
{
|
|
if (!--$this->inComment) {
|
|
$this->writeln("*/");
|
|
}
|
|
}
|
|
// }}}
|
|
|
|
var $scopeStack = array();
|
|
var $codeTypeCurrent = null;
|
|
var $codeTypeNext = 'line';
|
|
/*
|
|
line <- line
|
|
|
|
something { <- complexblock
|
|
line <- line
|
|
} <- complexblock
|
|
|
|
sdf <- line
|
|
*/
|
|
function beginBlock_($isComplex) // {{{
|
|
{
|
|
// array_push($this->scopeStack, array($this->codeTypeCurrent, $this->codeTypeNext));
|
|
array_push($this->scopeStack, $this->codeTypeNext);
|
|
$this->codeTypeCurrent = null;
|
|
$this->codeTypeNext = $isComplex ? 'complexblock' : 'line';
|
|
}
|
|
// }}}
|
|
function endBlock_() // {{{
|
|
{
|
|
// list($this->codeTypeCurrent, $this->codeTypeNext) = array_pop($this->scopeStack);
|
|
$this->codeTypeNext = array_pop($this->scopeStack);
|
|
}
|
|
// }}}
|
|
function beginScope() // {{{
|
|
{
|
|
$this->beginBlock_(false);
|
|
$this->indent();
|
|
}
|
|
// }}}
|
|
function endScope() // {{{
|
|
{
|
|
$this->outdent();
|
|
$this->endBlock_();
|
|
}
|
|
// }}}
|
|
function beginComplexBlock() // {{{
|
|
{
|
|
if (isset($this->codeTypeCurrent)) {
|
|
$this->writeOne_(PHP_EOL);
|
|
}
|
|
|
|
$this->beginBlock_(true);
|
|
}
|
|
// }}}
|
|
function endComplexBlock() // {{{
|
|
{
|
|
$this->endBlock_();
|
|
}
|
|
// }}}
|
|
}
|
|
|
|
class Decompiler_Backtrace // {{{
|
|
{
|
|
function Decompiler_Backtrace()
|
|
{
|
|
$this->backtrace = debug_backtrace();
|
|
array_shift($this->backtrace);
|
|
}
|
|
|
|
function toCode($indent)
|
|
{
|
|
$code = array();
|
|
foreach ($this->backtrace as $stack) {
|
|
$args = array();
|
|
foreach ($stack['args'] as $arg) {
|
|
if (is_scalar($arg)) {
|
|
$args[] = var_export($arg, true);
|
|
}
|
|
else if (is_array($arg)) {
|
|
$array = array();
|
|
foreach ($arg as $key => $value) {
|
|
$array[] = var_export($key, true) . " => " . (is_scalar($value) ? var_export($value, true) : gettype($value));
|
|
if (count($array) >= 5) {
|
|
$array[] = '...';
|
|
break;
|
|
}
|
|
}
|
|
$args[] = "array(" . implode(', ', $array) . ')';
|
|
}
|
|
else {
|
|
$args[] = gettype($arg);
|
|
}
|
|
}
|
|
$code[] = sprintf("%s%d: %s::%s(%s)" . PHP_EOL
|
|
, $indent
|
|
, $stack['line']
|
|
, isset($stack['class']) ? $stack['class'] : ''
|
|
, $stack['function']
|
|
, implode(', ', $args)
|
|
);
|
|
}
|
|
return implode("", $code);
|
|
}
|
|
}
|
|
// }}}
|
|
function printBacktrace() // {{{
|
|
{
|
|
$decompiler = &$GLOBALS['__xcache_decompiler'];
|
|
$decompiler->output->beginComment();
|
|
$decompiler->output->write(new Decompiler_Backtrace());
|
|
$decompiler->output->endComment();
|
|
}
|
|
// }}}
|
|
|
|
function decompileAst($ast) // {{{
|
|
{
|
|
$kind = $ast['kind'];
|
|
$children = $ast['children'];
|
|
unset($ast['kind']);
|
|
unset($ast['children']);
|
|
$decompiler = &$GLOBALS['__xcache_decompiler'];
|
|
|
|
switch ($kind) {
|
|
case ZEND_CONST:
|
|
return value($ast[0]);
|
|
|
|
case XC_INIT_ARRAY:
|
|
$array = new Decompiler_Array($decompiler);
|
|
for ($i = 0; $i < $children; $i += 2) {
|
|
if (isset($ast[$i + 1])) {
|
|
$key = decompileAst($ast[$i]);
|
|
$value = decompileAst($ast[$i + 1]);
|
|
$array->value[] = array($key, $value, '');
|
|
}
|
|
else {
|
|
$array->value[] = array(null, decompileAst($ast[$i]), '');
|
|
}
|
|
}
|
|
return $array;
|
|
|
|
// ZEND_BOOL_AND: handled in binop
|
|
// ZEND_BOOL_OR: handled in binop
|
|
|
|
case ZEND_SELECT:
|
|
return new Decompiler_TernaryOp($decompiler
|
|
, decompileAst($ast[0])
|
|
, decompileAst($ast[1])
|
|
, decompileAst($ast[2])
|
|
);
|
|
|
|
case ZEND_UNARY_PLUS:
|
|
return new Decompiler_UnaryOp($decompiler, XC_ADD, decompileAst($ast[0]));
|
|
|
|
case ZEND_UNARY_MINUS:
|
|
return new Decompiler_UnaryOp($decompiler, XC_SUB, decompileAst($ast[0]));
|
|
|
|
default:
|
|
if (isset($decompiler->binaryOp[$kind])) {
|
|
return new Decompiler_BinaryOp($decompiler
|
|
, decompileAst($ast[0])
|
|
, $kind
|
|
, decompileAst($ast[1])
|
|
);
|
|
}
|
|
|
|
return "un-handled kind $kind in zend_ast";
|
|
}
|
|
}
|
|
// }}}
|
|
function value($value) // {{{
|
|
{
|
|
if (ZEND_ENGINE_2_6 && (xcache_get_type($value) & IS_CONSTANT_TYPE_MASK) == IS_CONSTANT_AST) {
|
|
return decompileAst(xcache_dasm_ast($value));
|
|
}
|
|
|
|
$decompiler = &$GLOBALS['__xcache_decompiler'];
|
|
|
|
$originalValue = xcache_get_special_value($value);
|
|
if (isset($originalValue)) {
|
|
if ((xcache_get_type($value) & IS_CONSTANT_TYPE_MASK) == IS_CONSTANT) {
|
|
// constant
|
|
return new Decompiler_Code($decompiler, $decompiler->stripNamespace($originalValue));
|
|
}
|
|
|
|
$value = $originalValue;
|
|
}
|
|
|
|
if (is_a($value, 'Decompiler_Object')) {
|
|
// use as is
|
|
}
|
|
else if (is_array($value)) {
|
|
$value = new Decompiler_ConstArray($decompiler, $value);
|
|
}
|
|
else {
|
|
if (is_scalar($value) && ($constant = $decompiler->value2constant($value)) && isset($constant)) {
|
|
$value = new Decompiler_Code($decompiler, $constant);
|
|
}
|
|
else {
|
|
$value = new Decompiler_Value($decompiler, $value);
|
|
}
|
|
}
|
|
return $value;
|
|
}
|
|
// }}}
|
|
function unquoteName_($str, $asVariableName, $indent = '') // {{{
|
|
{
|
|
$str = is_object($str) ? $str->toCode($indent) : $str;
|
|
if (preg_match("!^'[\\w_][\\w\\d_\\\\]*'\$!", $str)) {
|
|
return str_replace('\\\\', '\\', substr($str, 1, -1));
|
|
}
|
|
else if ($asVariableName) {
|
|
return "{" . $str . "}";
|
|
}
|
|
else {
|
|
return $str;
|
|
}
|
|
}
|
|
// }}}
|
|
function unquoteVariableName($str, $indent = '') // {{{
|
|
{
|
|
return unquoteName_($str, true, $indent);
|
|
}
|
|
// }}}
|
|
function unquoteName($str, $indent = '') // {{{
|
|
{
|
|
return unquoteName_($str, false, $indent);
|
|
}
|
|
// }}}
|
|
class Decompiler_Object // {{{
|
|
{
|
|
function toCode($indent)
|
|
{
|
|
return "";
|
|
}
|
|
}
|
|
// }}}
|
|
class Decompiler_Value extends Decompiler_Object // {{{
|
|
{
|
|
var $value;
|
|
|
|
function Decompiler_Value(&$decompiler, $value = null)
|
|
{
|
|
$this->decompiler = &$decompiler;
|
|
$this->value = $value;
|
|
}
|
|
|
|
function toCode($indent)
|
|
{
|
|
$code = var_export($this->value, true);
|
|
if (gettype($this->value) == 'string') {
|
|
$code = preg_replace_callback("![\t\r\n]+!", array(&$this, 'convertNewline'), $code);
|
|
$code = preg_replace_callback("![\\x01-\\x1f]+!", array(&$this, 'escapeString'), $code);
|
|
$code = preg_replace_callback("![\\x7f-\\xff]+!", array(&$this, 'escape8BitString'), $code);
|
|
$code = preg_replace("!^'' \\. \"|\" \\. ''\$!", '"', $code);
|
|
}
|
|
return $code;
|
|
}
|
|
|
|
function convertNewline($m)
|
|
{
|
|
return "' . \"" . strtr($m[0], array("\t" => "\\t", "\r" => "\\r", "\n" => "\\n")) . "\" . '";
|
|
}
|
|
|
|
function escape8BitString($m)
|
|
{
|
|
if (function_exists("iconv") && @iconv("UTF-8", "UTF-8//IGNORE", $m[0]) == $m[0]) {
|
|
return $m[0];
|
|
}
|
|
return $this->escapeString($m);
|
|
}
|
|
|
|
function escapeString($m)
|
|
{
|
|
$s = $m[0];
|
|
$escaped = '';
|
|
for ($i = 0, $c = strlen($s); $i < $c; ++$i) {
|
|
$escaped .= "\\x" . dechex(ord($s[$i]));
|
|
}
|
|
return "' . \"" . $escaped . "\" . '";
|
|
}
|
|
}
|
|
// }}}
|
|
class Decompiler_Code extends Decompiler_Object // {{{
|
|
{
|
|
var $src;
|
|
|
|
function Decompiler_Code(&$decompiler, $src)
|
|
{
|
|
$this->decompiler = &$decompiler;
|
|
if (!assert('isset($src)')) {
|
|
printBacktrace();
|
|
}
|
|
$this->src = $src;
|
|
}
|
|
|
|
function toCode($indent)
|
|
{
|
|
return $this->src;
|
|
}
|
|
}
|
|
// }}}
|
|
class Decompiler_Statements extends Decompiler_Code // {{{
|
|
{
|
|
var $src;
|
|
|
|
function toCode($indent)
|
|
{
|
|
$code = array();
|
|
foreach ($this->src as $i => $src) {
|
|
if ($i) {
|
|
$code[] = ', ';
|
|
}
|
|
$code[] = $src;
|
|
}
|
|
return $code;
|
|
}
|
|
}
|
|
// }}}
|
|
class Decompiler_UnaryOp extends Decompiler_Code // {{{
|
|
{
|
|
var $opc;
|
|
var $op;
|
|
|
|
function Decompiler_UnaryOp(&$decompiler, $opc, $op)
|
|
{
|
|
$this->decompiler = &$decompiler;
|
|
$this->opc = $opc;
|
|
$this->op = $op;
|
|
}
|
|
|
|
function toCode($indent)
|
|
{
|
|
$opstr = $this->decompiler->unaryOp[$this->opc];
|
|
|
|
if (is_a($this->op, 'Decompiler_TernaryOp') || is_a($this->op, 'Decompiler_BinaryOp') && $this->op->opc != $this->opc) {
|
|
$op = array("(", $this->op, ")");
|
|
}
|
|
else {
|
|
$op = $this->op;
|
|
}
|
|
return array($opstr, $op);
|
|
}
|
|
}
|
|
// }}}
|
|
class Decompiler_BinaryOp extends Decompiler_Code // {{{
|
|
{
|
|
var $opc;
|
|
var $op1;
|
|
var $op2;
|
|
|
|
function Decompiler_BinaryOp(&$decompiler, $op1, $opc, $op2)
|
|
{
|
|
$this->decompiler = &$decompiler;
|
|
$this->opc = $opc;
|
|
$this->op1 = $op1;
|
|
$this->op2 = $op2;
|
|
}
|
|
|
|
function toCode($indent)
|
|
{
|
|
$opstr = $this->decompiler->binaryOp[$this->opc];
|
|
|
|
if (is_a($this->op1, 'Decompiler_TernaryOp') || is_a($this->op1, 'Decompiler_BinaryOp') && $this->op1->opc != $this->opc) {
|
|
$op1 = array("(", $this->op1, ")");
|
|
}
|
|
else {
|
|
$op1 = $this->op1;
|
|
}
|
|
|
|
if (is_a($this->op2, 'Decompiler_TernaryOp') || is_a($this->op2, 'Decompiler_BinaryOp') && $this->op2->opc != $this->opc && substr($opstr, -1) != '=') {
|
|
$op2 = array("(", $this->op2, ")");
|
|
}
|
|
else {
|
|
$op2 = $this->op2;
|
|
}
|
|
|
|
if (is_a($op1, 'Decompiler_Value') && $op1->value == '0' && ($this->opc == XC_ADD || $this->opc == XC_SUB)) {
|
|
$unaryOp = new Decompiler_UnaryOp($this->decompiler, $this->opc, $op2);
|
|
return $unaryOp->toCode($indent);
|
|
}
|
|
|
|
return array($op1, ' ', $opstr, ($this->opc == XC_ASSIGN_REF ? '' : ' '), $op2);
|
|
}
|
|
}
|
|
// }}}
|
|
class Decompiler_TernaryOp extends Decompiler_Code // {{{
|
|
{
|
|
var $condition;
|
|
var $trueValue;
|
|
var $falseValue;
|
|
|
|
function Decompiler_TernaryOp(&$decompiler, $condition, $trueValue, $falseValue)
|
|
{
|
|
$this->decompiler = &$decompiler;
|
|
$this->condition = $condition;
|
|
$this->trueValue = $trueValue;
|
|
$this->falseValue = $falseValue;
|
|
}
|
|
|
|
function toCode($indent)
|
|
{
|
|
$trueValue = $this->trueValue;
|
|
if (is_a($this->trueValue, 'Decompiler_TernaryOp')) {
|
|
$trueValue = array("(", $trueValue, ")");
|
|
}
|
|
$falseValue = $this->falseValue;
|
|
if (is_a($this->falseValue, 'Decompiler_TernaryOp')) {
|
|
$falseValue = array("(", $falseValue, ")");
|
|
}
|
|
|
|
return array($this->condition, ' ? ', $trueValue, ' : ', $falseValue);
|
|
}
|
|
}
|
|
// }}}
|
|
class Decompiler_Fetch extends Decompiler_Code // {{{
|
|
{
|
|
var $fetchType;
|
|
var $name;
|
|
var $scope;
|
|
|
|
function Decompiler_Fetch(&$decompiler, $scope, $name, $type)
|
|
{
|
|
$this->decompiler = &$decompiler;
|
|
$this->scope = $scope;
|
|
$this->name = $name;
|
|
unset($this->src);
|
|
$this->fetchType = $type;
|
|
}
|
|
|
|
function toCode($indent)
|
|
{
|
|
switch ($this->fetchType) {
|
|
case XC_FETCH_PROPERTY:
|
|
return array($this->scope, '->', $this->name);
|
|
|
|
case ZEND_FETCH_STATIC_MEMBER:
|
|
return array($this->scope, '::$', $this->name);
|
|
|
|
case ZEND_FETCH_LOCAL:
|
|
return array('$', $this->name);
|
|
|
|
case ZEND_FETCH_STATIC:
|
|
if (ZEND_ENGINE_2_3) {
|
|
// closure local variable?
|
|
return 'STR' . $this->name;
|
|
}
|
|
else {
|
|
return value($this->name);
|
|
}
|
|
die('static fetch cant to string');
|
|
|
|
case ZEND_FETCH_GLOBAL:
|
|
case ZEND_FETCH_GLOBAL_LOCK:
|
|
return xcache_is_autoglobal($this->name) ? array('$', $this->name) : array("\$GLOBALS[", value($this->name), "]");
|
|
|
|
default:
|
|
var_dump($this->fetchType);
|
|
assert(0);
|
|
}
|
|
}
|
|
}
|
|
// }}}
|
|
class Decompiler_Box extends Decompiler_Object // {{{
|
|
{
|
|
var $obj;
|
|
|
|
function Decompiler_Box(&$decompiler, &$obj)
|
|
{
|
|
$this->decompiler = &$decompiler;
|
|
$this->obj = &$obj;
|
|
}
|
|
|
|
function toCode($indent)
|
|
{
|
|
return $this->obj->toCode($indent);
|
|
}
|
|
}
|
|
// }}}
|
|
class Decompiler_Dim extends Decompiler_Value // {{{
|
|
{
|
|
var $offsets = array();
|
|
var $isLast = false;
|
|
var $isObject = false;
|
|
var $assign = null;
|
|
|
|
function toCode($indent)
|
|
{
|
|
if (is_a($this->value, 'Decompiler_ListBox')) {
|
|
$exp = array($this->value->obj->src);
|
|
}
|
|
else {
|
|
$exp = array($this->value);
|
|
}
|
|
$last = count($this->offsets) - 1;
|
|
foreach ($this->offsets as $i => $dim) {
|
|
if ($this->isObject && $i == $last) {
|
|
$exp[] = '->';
|
|
$exp[] = unquoteVariableName($dim, $indent);
|
|
}
|
|
else {
|
|
$exp[] = '[';
|
|
$exp[] = $dim;
|
|
$exp[] = ']';
|
|
}
|
|
}
|
|
return $exp;
|
|
}
|
|
}
|
|
// }}}
|
|
class Decompiler_DimBox extends Decompiler_Box // {{{
|
|
{
|
|
}
|
|
// }}}
|
|
class Decompiler_List extends Decompiler_Code // {{{
|
|
{
|
|
var $src;
|
|
var $dims = array();
|
|
var $everLocked = false;
|
|
|
|
function toCode($indent)
|
|
{
|
|
if (count($this->dims) == 1 && !$this->everLocked) {
|
|
$dim = $this->dims[0];
|
|
unset($dim->value);
|
|
$dim->value = $this->src;
|
|
if (!isset($dim->assign)) {
|
|
return $dim;
|
|
}
|
|
return array($this->dims[0]->assign, ' = ', $dim);
|
|
}
|
|
/* flatten dims */
|
|
$assigns = array();
|
|
foreach ($this->dims as $dim) {
|
|
$assign = &$assigns;
|
|
foreach ($dim->offsets as $offset) {
|
|
$assign = &$assign[$offset];
|
|
}
|
|
$assign = $dim->assign;
|
|
}
|
|
return array($this->toList($assigns), ' = ', $this->src);
|
|
}
|
|
|
|
function toList($assigns)
|
|
{
|
|
$keys = array_keys($assigns);
|
|
if (count($keys) < 2) {
|
|
$keys[] = 0;
|
|
}
|
|
$max = call_user_func_array('max', $keys);
|
|
$code = array("list(");
|
|
for ($i = 0; $i <= $max; $i++) {
|
|
if ($i) {
|
|
$code[] = ', ';
|
|
}
|
|
if (!isset($assigns[$i])) {
|
|
continue;
|
|
}
|
|
if (is_array($assigns[$i])) {
|
|
$code[] = $this->toList($assigns[$i]);
|
|
}
|
|
else {
|
|
$code[] = $assigns[$i];
|
|
}
|
|
}
|
|
$code[] = ')';
|
|
return $code;
|
|
}
|
|
}
|
|
// }}}
|
|
class Decompiler_ListBox extends Decompiler_Box // {{{
|
|
{
|
|
}
|
|
// }}}
|
|
class Decompiler_Array extends Decompiler_Value // {{{
|
|
{
|
|
// elements
|
|
function Decompiler_Array(&$decompiler)
|
|
{
|
|
$this->decompiler = &$decompiler;
|
|
$this->value = array();
|
|
}
|
|
|
|
function toCode($indent)
|
|
{
|
|
$subindent = $indent . INDENT;
|
|
|
|
$elementsCode = array();
|
|
$index = 0;
|
|
foreach ($this->value as $element) {
|
|
$key = $element[0];
|
|
if (isset($key)) {
|
|
$key = value($key);
|
|
$keyCode = $key->toCode($subindent);
|
|
}
|
|
else {
|
|
$keyCode = $index++;
|
|
}
|
|
$element[0] = $keyCode;
|
|
$elementsCode[] = $element;
|
|
}
|
|
|
|
$code = array("array(");
|
|
$indent = $indent . INDENT;
|
|
$assocWidth = 0;
|
|
$multiline = 0;
|
|
$i = 0;
|
|
foreach ($elementsCode as $element) {
|
|
$keyCode = $element[0];
|
|
if (is_array($keyCode) || (string) $i !== (string) $keyCode) {
|
|
$assocWidth = 1;
|
|
break;
|
|
}
|
|
++$i;
|
|
}
|
|
foreach ($elementsCode as $element) {
|
|
list($keyCode, $value) = $element;
|
|
if ($assocWidth && !is_array($keyCode)) {
|
|
$len = strlen($keyCode);
|
|
if ($assocWidth < $len) {
|
|
$assocWidth = $len;
|
|
}
|
|
}
|
|
if (is_array($value) || is_a($value, 'Decompiler_Array')) {
|
|
$multiline++;
|
|
}
|
|
}
|
|
|
|
$i = 0;
|
|
foreach ($elementsCode as $element) {
|
|
list($keyCode, $value, $ref) = $element;
|
|
if ($multiline) {
|
|
if ($i) {
|
|
$code[] = ",";
|
|
}
|
|
$code[] = PHP_EOL;
|
|
$code[] = $indent;
|
|
}
|
|
else {
|
|
if ($i) {
|
|
$code[] = ", ";
|
|
}
|
|
}
|
|
|
|
if ($assocWidth) {
|
|
if ($multiline && !is_array($keyCode)) {
|
|
$code[] = sprintf("%-{$assocWidth}s => ", $keyCode);
|
|
}
|
|
else {
|
|
$code[] = $keyCode;
|
|
$code[] = ' => ';
|
|
}
|
|
}
|
|
|
|
$code[] = $ref;
|
|
$value = value($value);
|
|
$code[] = $value->toCode($subindent);
|
|
|
|
$i++;
|
|
}
|
|
if ($multiline) {
|
|
$code[] = PHP_EOL . "$indent)";
|
|
}
|
|
else {
|
|
$code[] = ")";
|
|
}
|
|
return $code;
|
|
}
|
|
}
|
|
// }}}
|
|
class Decompiler_ConstArray extends Decompiler_Array // {{{
|
|
{
|
|
function Decompiler_ConstArray(&$decompiler, $array)
|
|
{
|
|
$this->decompiler = &$decompiler;
|
|
$elements = array();
|
|
foreach ($array as $key => $value) {
|
|
if ((xcache_get_type($value) & IS_CONSTANT_INDEX)) {
|
|
$keyCode = new Decompiler_Code($this, $GLOBALS['__xcache_decompiler']->stripNamespace(
|
|
ZEND_ENGINE_2_3
|
|
? substr($key, 0, -2)
|
|
: $key
|
|
));
|
|
}
|
|
else {
|
|
$keyCode = $key;
|
|
}
|
|
$elements[] = array($keyCode, value($value), '');
|
|
}
|
|
$this->value = $elements;
|
|
}
|
|
}
|
|
// }}}
|
|
class Decompiler_ForeachBox extends Decompiler_Box // {{{
|
|
{
|
|
var $iskey;
|
|
|
|
function toCode($indent)
|
|
{
|
|
return '#foreachBox#';
|
|
}
|
|
}
|
|
// }}}
|
|
|
|
class Decompiler
|
|
{
|
|
var $namespace;
|
|
var $namespaceDecided;
|
|
var $activeFile;
|
|
var $activeDir;
|
|
var $activeClass;
|
|
var $activeMethod;
|
|
var $activeFunction;
|
|
var $outputPhp;
|
|
var $outputOpcode;
|
|
var $value2constant = array();
|
|
var $output;
|
|
|
|
function Decompiler($outputTypes)
|
|
{
|
|
$this->outputPhp = in_array('php', $outputTypes);
|
|
$this->outputOpcode = in_array('opcode', $outputTypes);
|
|
$this->output = new Decompiler_Output(STDOUT);
|
|
$GLOBALS['__xcache_decompiler'] = $this;
|
|
// {{{ testing
|
|
// XC_UNDEF XC_OP_DATA
|
|
$this->test = !empty($_ENV['XCACHE_DECOMPILER_TEST']);
|
|
$this->usedOps = array();
|
|
|
|
if ($this->test) {
|
|
$content = file_get_contents(__FILE__);
|
|
for ($i = 0; $opname = xcache_get_opcode($i); $i++) {
|
|
if (!preg_match("/\\bXC_" . $opname . "\\b(?!')/", $content)) {
|
|
echo "not recognized opcode ", $opname, PHP_EOL;
|
|
}
|
|
}
|
|
}
|
|
// }}}
|
|
// {{{ opinfo
|
|
$this->unaryOp = array(
|
|
XC_BW_NOT => '~',
|
|
XC_BOOL_NOT => '!',
|
|
XC_ADD => "+",
|
|
XC_SUB => "-",
|
|
XC_NEW => "new ",
|
|
XC_THROW => "throw ",
|
|
XC_CLONE => "clone ",
|
|
);
|
|
$this->binaryOp = array(
|
|
XC_ADD => "+",
|
|
XC_ASSIGN_ADD => "+=",
|
|
XC_SUB => "-",
|
|
XC_ASSIGN_SUB => "-=",
|
|
XC_MUL => "*",
|
|
XC_ASSIGN_MUL => "*=",
|
|
XC_DIV => "/",
|
|
XC_ASSIGN_DIV => "/=",
|
|
XC_MOD => "%",
|
|
XC_ASSIGN_MOD => "%=",
|
|
XC_SL => "<<",
|
|
XC_ASSIGN_SL => "<<=",
|
|
XC_SR => ">>",
|
|
XC_ASSIGN_SR => ">>=",
|
|
XC_CONCAT => ".",
|
|
XC_ASSIGN_CONCAT => ".=",
|
|
XC_POW => "**",
|
|
XC_ASSIGN_POW => "*=",
|
|
XC_IS_IDENTICAL => "===",
|
|
XC_IS_NOT_IDENTICAL => "!==",
|
|
XC_IS_EQUAL => "==",
|
|
XC_IS_NOT_EQUAL => "!=",
|
|
XC_IS_SMALLER => "<",
|
|
XC_IS_SMALLER_OR_EQUAL => "<=",
|
|
XC_BW_OR => "|",
|
|
XC_ASSIGN_BW_OR => "|=",
|
|
XC_BW_AND => "&",
|
|
XC_ASSIGN_BW_AND => "&=",
|
|
XC_BW_XOR => "^",
|
|
XC_ASSIGN_BW_XOR => "^=",
|
|
XC_BOOL_XOR => "xor",
|
|
XC_ASSIGN => "=",
|
|
XC_ASSIGN_DIM => "=",
|
|
XC_ASSIGN_OBJ => "=",
|
|
XC_ASSIGN_REF => "= &",
|
|
XC_JMP_SET => "?:",
|
|
XC_JMP_SET_VAR => "?:",
|
|
XC_JMPZ_EX => "&&",
|
|
XC_JMPNZ_EX => "||",
|
|
XC_INSTANCEOF => "instanceof",
|
|
);
|
|
if (defined('IS_CONSTANT_AST')) {
|
|
$this->binaryOp[ZEND_BOOL_AND] = '&&';
|
|
$this->binaryOp[ZEND_BOOL_OR] = '||';
|
|
}
|
|
// }}}
|
|
$this->includeTypes = array( // {{{
|
|
ZEND_EVAL => 'eval',
|
|
ZEND_INCLUDE => 'include',
|
|
ZEND_INCLUDE_ONCE => 'include_once',
|
|
ZEND_REQUIRE => 'require',
|
|
ZEND_REQUIRE_ONCE => 'require_once',
|
|
);
|
|
// }}}
|
|
}
|
|
function detectNamespace($name) // {{{
|
|
{
|
|
if ($this->namespaceDecided) {
|
|
return;
|
|
}
|
|
|
|
if (strpos($name, '\\') !== false) {
|
|
$namespace = strtok($name, '\\');
|
|
if ($namespace == $this->namespace) {
|
|
return;
|
|
}
|
|
|
|
$this->namespace = $namespace;
|
|
$this->output->beginComplexBlock();
|
|
$this->output->writeln('namespace ', $this->namespace, ";");
|
|
$this->output->endComplexBlock();
|
|
}
|
|
|
|
$this->namespaceDecided = true;
|
|
}
|
|
// }}}
|
|
function stripNamespace($name) // {{{
|
|
{
|
|
if (!isset($name)) {
|
|
return $name;
|
|
}
|
|
|
|
$len = strlen($this->namespace) + 1;
|
|
if (!is_string($name)) {
|
|
printBacktrace();
|
|
exit;
|
|
}
|
|
if (strncasecmp($name, $this->namespace . '\\', $len) == 0) {
|
|
return substr($name, $len);
|
|
}
|
|
else {
|
|
return $name;
|
|
}
|
|
}
|
|
// }}}
|
|
function value2constant($value) // {{{
|
|
{
|
|
if (isset($this->EX) && isset($this->EX['value2constant'][$value])) {
|
|
return $this->EX['value2constant'][$value];
|
|
}
|
|
else if (isset($this->value2constant[$value])) {
|
|
return $this->value2constant[$value];
|
|
}
|
|
}
|
|
// }}}
|
|
function outputPhp($range) // {{{
|
|
{
|
|
$curticks = 0;
|
|
for ($i = $range[0]; $i <= $range[1]; $i++) {
|
|
$op = $this->EX['opcodes'][$i];
|
|
if (isset($op['gofrom'])) {
|
|
$this->output->beginComplexBlock();
|
|
echo 'label' . $i, ":", PHP_EOL;
|
|
$this->output->endComplexBlock();
|
|
}
|
|
if (isset($op['php'])) {
|
|
$toticks = isset($op['ticks']) ? (int) $op['ticks'] : 0;
|
|
if ($curticks != $toticks) {
|
|
if ($curticks) {
|
|
$this->output->endScope();
|
|
$this->output->writeln("}");
|
|
$this->output->endComplexBlock();
|
|
}
|
|
$curticks = $toticks;
|
|
if ($curticks) {
|
|
$this->output->beginComplexBlock();
|
|
$this->output->writeln("declare (ticks=$curticks) {");
|
|
$this->output->beginScope();
|
|
}
|
|
}
|
|
$this->output->writeln($op['php'], ';');
|
|
unset($op['php']);
|
|
}
|
|
}
|
|
if ($curticks) {
|
|
$this->output->endScope();
|
|
$this->output->writeln("}");
|
|
$this->output->endComplexBlock();
|
|
}
|
|
}
|
|
// }}}
|
|
function getOpVal($op, $free = false, $namespaced = false) // {{{
|
|
{
|
|
switch ($op['op_type']) {
|
|
case XC_IS_CONST:
|
|
if ($namespaced && isset($op['constant'])) {
|
|
return $this->stripNamespace($op['constant']);
|
|
}
|
|
return value($op['constant']);
|
|
|
|
case XC_IS_VAR:
|
|
case XC_IS_TMP_VAR:
|
|
$T = &$this->EX['Ts'];
|
|
if (!isset($T[$op['var']])) {
|
|
if ($this->outputPhp && isset($free)) {
|
|
printBacktrace();
|
|
}
|
|
return null;
|
|
}
|
|
$ret = $T[$op['var']];
|
|
if ($free && empty($this->keepTs)) {
|
|
unset($T[$op['var']]);
|
|
}
|
|
return $ret;
|
|
|
|
case XC_IS_CV:
|
|
$vars = &$this->EX['op_array']['vars'];
|
|
$var = $op['var'];
|
|
return new Decompiler_Fetch($this, null, isset($vars[$var]) ? $vars[$var]['name'] : '?', ZEND_FETCH_LOCAL);
|
|
|
|
case XC_IS_UNUSED:
|
|
return null;
|
|
}
|
|
}
|
|
// }}}
|
|
function removeKeyPrefix($array, $prefix) // {{{
|
|
{
|
|
$prefixLen = strlen($prefix);
|
|
$ret = array();
|
|
foreach ($array as $key => $value) {
|
|
if (substr($key, 0, $prefixLen) == $prefix) {
|
|
$key = substr($key, $prefixLen);
|
|
}
|
|
$ret[$key] = $value;
|
|
}
|
|
return $ret;
|
|
}
|
|
// }}}
|
|
function fixOpCode(&$opcodes, $removeTailing = false, $defaultReturnValue = null) // {{{
|
|
{
|
|
$last = count($opcodes) - 1;
|
|
for ($i = 0; $i <= $last; $i++) {
|
|
if (function_exists('xcache_get_fixed_opcode')) {
|
|
$opcodes[$i]['opcode'] = xcache_get_fixed_opcode($opcodes[$i]['opcode'], $i);
|
|
}
|
|
if (isset($opcodes[$i]['op1'])) {
|
|
$opcodes[$i]['op1'] = $this->removeKeyPrefix($opcodes[$i]['op1'], 'u.');
|
|
$opcodes[$i]['op2'] = $this->removeKeyPrefix($opcodes[$i]['op2'], 'u.');
|
|
$opcodes[$i]['result'] = $this->removeKeyPrefix($opcodes[$i]['result'], 'u.');
|
|
}
|
|
else {
|
|
$op = array(
|
|
'op1' => array(),
|
|
'op2' => array(),
|
|
'result' => array(),
|
|
);
|
|
foreach ($opcodes[$i] as $name => $value) {
|
|
if (preg_match('!^(op1|op2|result)\\.(.*)!', $name, $m)) {
|
|
list(, $which, $field) = $m;
|
|
$op[$which][$field] = $value;
|
|
}
|
|
else if (preg_match('!^(op1|op2|result)_type$!', $name, $m)) {
|
|
list(, $which) = $m;
|
|
$op[$which]['op_type'] = $value;
|
|
}
|
|
else {
|
|
$op[$name] = $value;
|
|
}
|
|
}
|
|
$opcodes[$i] = $op;
|
|
}
|
|
}
|
|
|
|
if ($removeTailing) {
|
|
$last = count($opcodes) - 1;
|
|
if ($opcodes[$last]['opcode'] == XC_HANDLE_EXCEPTION) {
|
|
$this->usedOps[XC_HANDLE_EXCEPTION] = true;
|
|
$opcodes[$last]['opcode'] = XC_NOP;
|
|
--$last;
|
|
}
|
|
if ($opcodes[$last]['opcode'] == XC_RETURN
|
|
|| $opcodes[$last]['opcode'] == XC_GENERATOR_RETURN) {
|
|
$op1 = $opcodes[$last]['op1'];
|
|
if ($op1['op_type'] == XC_IS_CONST && array_key_exists('constant', $op1) && $op1['constant'] === $defaultReturnValue) {
|
|
$opcodes[$last]['opcode'] = XC_NOP;
|
|
--$last;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// }}}
|
|
function decompileBasicBlock($range, $unhandled = false) // {{{
|
|
{
|
|
$this->dasmBasicBlock($range);
|
|
if ($unhandled) {
|
|
$this->dumpRange($range);
|
|
}
|
|
$this->outputPhp($range);
|
|
}
|
|
// }}}
|
|
function isIfCondition($range) // {{{
|
|
{
|
|
$opcodes = &$this->EX['opcodes'];
|
|
$firstOp = &$opcodes[$range[0]];
|
|
return $firstOp['opcode'] == XC_JMPZ && !empty($firstOp['jmptos']) && $opcodes[$firstOp['jmptos'][0] - 1]['opcode'] == XC_JMP
|
|
&& !empty($opcodes[$firstOp['jmptos'][0] - 1]['jmptos'])
|
|
&& $opcodes[$firstOp['jmptos'][0] - 1]['jmptos'][0] == $range[1] + 1;
|
|
}
|
|
// }}}
|
|
function removeJmpInfo($line) // {{{
|
|
{
|
|
$opcodes = &$this->EX['opcodes'];
|
|
if (!isset($opcodes[$line]['jmptos'])) {
|
|
printBacktrace();
|
|
}
|
|
foreach ($opcodes[$line]['jmptos'] as $jmpTo) {
|
|
$jmpfroms = &$opcodes[$jmpTo]['jmpfroms'];
|
|
$jmpfroms = array_flip($jmpfroms);
|
|
unset($jmpfroms[$line]);
|
|
$jmpfroms = array_keys($jmpfroms);
|
|
}
|
|
// $opcodes[$line]['opcode'] = XC_NOP;
|
|
unset($opcodes[$line]['jmptos']);
|
|
}
|
|
// }}}
|
|
function op($range, $offset) // {{{
|
|
{
|
|
$opcodes = &$this->EX['opcodes'];
|
|
if ($offset > 0) {
|
|
for ($i = $offset; $i <= $range[1]; ++$i) {
|
|
if ($opcodes[$i]['opcode'] != XC_NOP) {
|
|
return $i;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for ($i = -$offset; $i >= $range[0]; --$i) {
|
|
if ($opcodes[$i]['opcode'] != XC_NOP) {
|
|
return $i;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
// }}}
|
|
function decompileComplexBlock($range) // {{{
|
|
{
|
|
$opcodes = &$this->EX['opcodes'];
|
|
|
|
$firstOp = &$opcodes[$this->op($range, $range[0])];
|
|
$lastOp = &$opcodes[$this->op($range, -$range[1])];
|
|
|
|
// {{{ && || and or
|
|
if (($firstOp['opcode'] == XC_JMPZ_EX || $firstOp['opcode'] == XC_JMPNZ_EX) && !empty($firstOp['jmptos'])
|
|
&& $firstOp['jmptos'][0] == $range[1] + 1
|
|
&& $lastOp['opcode'] == XC_BOOL
|
|
&& $firstOp['opcode']['result']['var'] == $lastOp['opcode']['result']['var']
|
|
) {
|
|
$this->removeJmpInfo($range[0]);
|
|
|
|
$this->recognizeAndDecompileClosedBlocks(array($range[0], $range[0]));
|
|
$op1 = $this->getOpVal($firstOp['result'], true);
|
|
|
|
$this->recognizeAndDecompileClosedBlocks(array($range[0] + 1, $range[1]));
|
|
$op2 = $this->getOpVal($lastOp['result'], true);
|
|
|
|
$this->EX['Ts'][$firstOp['result']['var']] = new Decompiler_BinaryOp($this, $op1, $firstOp['opcode'], $op2);
|
|
return false;
|
|
}
|
|
// }}}
|
|
// {{{ ?: excluding JMP_SET/JMP_SET_VAR
|
|
if ($firstOp['opcode'] == XC_JMPZ && !empty($firstOp['jmptos'])
|
|
&& $range[1] >= $range[0] + 3
|
|
&& ($opcodes[$firstOp['jmptos'][0] - 2]['opcode'] == XC_QM_ASSIGN || $opcodes[$firstOp['jmptos'][0] - 2]['opcode'] == XC_QM_ASSIGN_VAR)
|
|
&& $opcodes[$firstOp['jmptos'][0] - 1]['opcode'] == XC_JMP && $opcodes[$firstOp['jmptos'][0] - 1]['jmptos'][0] == $range[1] + 1
|
|
&& ($lastOp['opcode'] == XC_QM_ASSIGN || $lastOp['opcode'] == XC_QM_ASSIGN_VAR)
|
|
) {
|
|
$trueRange = array($range[0] + 1, $firstOp['jmptos'][0] - 2);
|
|
$falseRange = array($firstOp['jmptos'][0], $range[1]);
|
|
$this->removeJmpInfo($range[0]);
|
|
|
|
$condition = $this->getOpVal($firstOp['op1']);
|
|
$this->recognizeAndDecompileClosedBlocks($trueRange);
|
|
$trueValue = $this->getOpVal($opcodes[$trueRange[1]]['result'], true);
|
|
$this->recognizeAndDecompileClosedBlocks($falseRange);
|
|
$falseValue = $this->getOpVal($opcodes[$falseRange[1]]['result'], true);
|
|
$this->EX['Ts'][$opcodes[$trueRange[1]]['result']['var']] = new Decompiler_TernaryOp($this, $condition, $trueValue, $falseValue);
|
|
return false;
|
|
}
|
|
// }}}
|
|
// {{{ goto (TODO: recognize BRK which is translated to JMP by optimizer)
|
|
if ($firstOp['opcode'] == XC_JMP && !empty($firstOp['jmptos']) && $firstOp['jmptos'][0] == $range[1] + 1) {
|
|
$this->removeJmpInfo($range[0]);
|
|
assert(XC_GOTO != -1);
|
|
$firstOp['opcode'] = XC_GOTO;
|
|
$target = $firstOp['op1']['var'];
|
|
$firstOp['goto'] = $target;
|
|
$opcodes[$target]['gofrom'][] = $range[0];
|
|
|
|
$this->recognizeAndDecompileClosedBlocks($range);
|
|
return false;
|
|
}
|
|
// }}}
|
|
|
|
// {{{ search firstJmpOp
|
|
$firstJmpOp = null;
|
|
for ($i = $range[0]; $i <= $range[1]; ++$i) {
|
|
if (!empty($opcodes[$i]['jmptos'])) {
|
|
$firstJmpOp = &$opcodes[$i];
|
|
break;
|
|
}
|
|
}
|
|
// }}}
|
|
if (!isset($firstJmpOp)) {
|
|
return;
|
|
}
|
|
// {{{ search lastJmpOp
|
|
$lastJmpOp = null;
|
|
for ($i = $range[1]; $i > $firstJmpOp['line']; --$i) {
|
|
if (!empty($opcodes[$i]['jmptos'])) {
|
|
$lastJmpOp = &$opcodes[$i];
|
|
break;
|
|
}
|
|
}
|
|
// }}}
|
|
if ($this->decompile_foreach($range, $opcodes, $firstOp, $lastOp, $firstJmpOp, $lastJmpOp)) {
|
|
return true;
|
|
}
|
|
if ($this->decompile_while($range, $opcodes, $firstOp, $lastOp, $firstJmpOp)) {
|
|
return true;
|
|
}
|
|
if ($this->decompile_for($range, $opcodes, $firstOp, $lastOp)) {
|
|
return true;
|
|
}
|
|
if ($this->decompile_if($range, $opcodes, $firstOp, $lastOp)) {
|
|
return true;
|
|
}
|
|
if ($this->decompile_switch($range, $opcodes, $firstOp, $lastOp)) {
|
|
return true;
|
|
}
|
|
if ($this->decompile_tryCatch($range, $opcodes, $firstOp, $lastOp)) {
|
|
return true;
|
|
}
|
|
if ($this->decompile_doWhile($range, $opcodes, $firstOp, $lastOp)) {
|
|
return true;
|
|
}
|
|
|
|
$this->decompileBasicBlock($range, true);
|
|
}
|
|
// }}}
|
|
function decompile_for($range, &$opcodes, &$firstOp, &$lastOp) // {{{
|
|
{
|
|
if (!empty($firstOp['jmpfroms']) && $opcodes[$firstOp['jmpfroms'][0]]['opcode'] == XC_JMP
|
|
&& $lastOp['opcode'] == XC_JMP && !empty($lastOp['jmptos']) && $lastOp['jmptos'][0] <= $firstOp['jmpfroms'][0]
|
|
&& !empty($opcodes[$range[1] + 1]['jmpfroms']) && $opcodes[$opcodes[$range[1] + 1]['jmpfroms'][0]]['opcode'] == XC_JMPZNZ
|
|
) {
|
|
$nextRange = array($lastOp['jmptos'][0], $firstOp['jmpfroms'][0]);
|
|
$conditionRange = array($range[0], $nextRange[0] - 1);
|
|
$this->removeJmpInfo($conditionRange[1]);
|
|
$bodyRange = array($nextRange[1], $range[1]);
|
|
$this->removeJmpInfo($bodyRange[1]);
|
|
|
|
$this->output->beginComplexBlock();
|
|
$initial = '';
|
|
$this->output->beginScope();
|
|
$this->dasmBasicBlock($conditionRange);
|
|
$conditionCodes = array();
|
|
for ($i = $conditionRange[0]; $i <= $conditionRange[1]; ++$i) {
|
|
if (isset($opcodes[$i]['php'])) {
|
|
$conditionCodes[] = $opcodes[$i]['php'];
|
|
}
|
|
}
|
|
$conditionCodes[] = $this->getOpVal($opcodes[$conditionRange[1]]['op1']);
|
|
if (count($conditionCodes) == 1 && $conditionCodes[0] == 'true') {
|
|
$conditionCodes = array();
|
|
}
|
|
$this->output->endScope();
|
|
|
|
$this->output->beginScope();
|
|
$this->dasmBasicBlock($nextRange);
|
|
$nextCodes = array();
|
|
for ($i = $nextRange[0]; $i <= $nextRange[1]; ++$i) {
|
|
if (isset($opcodes[$i]['php'])) {
|
|
$nextCodes[] = $opcodes[$i]['php'];
|
|
}
|
|
}
|
|
$this->output->endScope();
|
|
|
|
$this->output->writeln('for (', $initial, '; ', new Decompiler_Statements($this, $conditionCodes), '; ', new Decompiler_Statements($this, $nextCodes), ') ', '{');
|
|
$this->clearJmpInfo_brk_cont($bodyRange);
|
|
$this->output->beginScope();
|
|
$this->recognizeAndDecompileClosedBlocks($bodyRange);
|
|
$this->output->endScope();
|
|
$this->output->writeln('}');
|
|
$this->output->endComplexBlock();
|
|
return true;
|
|
}
|
|
}
|
|
// }}}
|
|
function decompile_if($range, &$opcodes, &$firstOp, &$lastOp) // {{{
|
|
{
|
|
if ($this->isIfCondition($range)) {
|
|
$this->output->beginComplexBlock();
|
|
$isElseIf = false;
|
|
do {
|
|
$ifRange = array($range[0], $opcodes[$range[0]]['jmptos'][0] - 1);
|
|
$this->removeJmpInfo($ifRange[0]);
|
|
$this->removeJmpInfo($ifRange[1]);
|
|
$condition = $this->getOpVal($opcodes[$ifRange[0]]['op1']);
|
|
|
|
$this->output->writeln($isElseIf ? 'else if' : 'if', ' (', $condition, ') ', '{');
|
|
$this->output->beginScope();
|
|
$this->recognizeAndDecompileClosedBlocks($ifRange);
|
|
$this->output->endScope();
|
|
$this->output->writeln("}");
|
|
|
|
$isElseIf = true;
|
|
// search for else if
|
|
$range[0] = $ifRange[1] + 1;
|
|
for ($i = $ifRange[1] + 1; $i <= $range[1]; ++$i) {
|
|
// find first jmpout
|
|
if (!empty($opcodes[$i]['jmptos'])) {
|
|
if ($this->isIfCondition(array($i, $range[1]))) {
|
|
$this->dasmBasicBlock(array($range[0], $i));
|
|
$range[0] = $i;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} while ($this->isIfCondition($range));
|
|
if ($ifRange[1] <= $range[1]) {
|
|
$elseRange = array($ifRange[1], $range[1]);
|
|
$this->output->writeln('else ', '{');
|
|
$this->output->beginScope();
|
|
$this->recognizeAndDecompileClosedBlocks($elseRange);
|
|
$this->output->endScope();
|
|
$this->output->writeln("}");
|
|
}
|
|
$this->output->endComplexBlock();
|
|
return true;
|
|
}
|
|
|
|
if ($firstOp['opcode'] == XC_JMPZ && !empty($firstOp['jmptos'])
|
|
&& $firstOp['jmptos'][0] - 1 == $range[1]
|
|
&& ($opcodes[$firstOp['jmptos'][0] - 1]['opcode'] == XC_RETURN || $opcodes[$firstOp['jmptos'][0] - 1]['opcode'] == XC_GENERATOR_RETURN)) {
|
|
$this->output->beginComplexBlock();
|
|
$this->removeJmpInfo($range[0]);
|
|
$condition = $this->getOpVal($opcodes[$range[0]]['op1']);
|
|
|
|
$this->output->writeln('if (', $condition, ') ', '{');
|
|
$this->output->beginScope();
|
|
$this->recognizeAndDecompileClosedBlocks($range);
|
|
$this->output->endScope();
|
|
$this->output->writeln('}');
|
|
$this->output->endComplexBlock();
|
|
return true;
|
|
}
|
|
}
|
|
// }}}
|
|
function decompile_tryCatch($range, &$opcodes, &$firstOp, &$lastOp) // {{{
|
|
{
|
|
if (!empty($firstOp['jmpfroms']) && !empty($opcodes[$firstOp['jmpfroms'][0]]['isCatchBegin'])) {
|
|
$catchBlocks = array();
|
|
$catchFirst = $firstOp['jmpfroms'][0];
|
|
|
|
$tryRange = array($range[0], $catchFirst - 1);
|
|
|
|
// search for XC_CATCH
|
|
for ($i = $catchFirst; $i <= $range[1]; ) {
|
|
if ($opcodes[$i]['opcode'] == XC_CATCH) {
|
|
$catchOpLine = $i;
|
|
$this->removeJmpInfo($catchFirst);
|
|
|
|
$catchNext = $opcodes[$catchOpLine]['extended_value'];
|
|
$catchBodyLast = $catchNext - 1;
|
|
if ($opcodes[$catchBodyLast]['opcode'] == XC_JMP) {
|
|
--$catchBodyLast;
|
|
}
|
|
|
|
$catchBlocks[$catchFirst] = array($catchOpLine, $catchBodyLast);
|
|
|
|
$i = $catchFirst = $catchNext;
|
|
}
|
|
else {
|
|
++$i;
|
|
}
|
|
}
|
|
|
|
if ($opcodes[$tryRange[1]]['opcode'] == XC_JMP) {
|
|
--$tryRange[1];
|
|
}
|
|
|
|
$this->output->beginComplexBlock();
|
|
$this->output->writeln("try {");
|
|
$this->output->beginScope();
|
|
$this->recognizeAndDecompileClosedBlocks($tryRange);
|
|
$this->output->endScope();
|
|
$this->output->writeln("}");
|
|
if (!$catchBlocks) {
|
|
printBacktrace();
|
|
assert($catchBlocks);
|
|
}
|
|
foreach ($catchBlocks as $catchFirst => $catchInfo) {
|
|
list($catchOpLine, $catchBodyLast) = $catchInfo;
|
|
$catchBodyFirst = $catchOpLine + 1;
|
|
$this->dasmBasicBlock(array($catchFirst, $catchOpLine));
|
|
$catchOp = &$opcodes[$catchOpLine];
|
|
$this->output->writeln("catch ("
|
|
, $this->stripNamespace(isset($catchOp['op1']['constant']) ? $catchOp['op1']['constant'] : $this->getOpVal($catchOp['op1']))
|
|
, ' '
|
|
, isset($catchOp['op2']['constant']) ? '$' . $catchOp['op2']['constant'] : $this->getOpVal($catchOp['op2'])
|
|
, ") {"
|
|
);
|
|
unset($catchOp);
|
|
|
|
$this->output->beginScope();
|
|
$this->recognizeAndDecompileClosedBlocks(array($catchBodyFirst, $catchBodyLast));
|
|
$this->output->endScope();
|
|
$this->output->writeln("}");
|
|
}
|
|
$this->output->endComplexBlock();
|
|
return true;
|
|
}
|
|
}
|
|
// }}}
|
|
function decompile_switch($range, &$opcodes, &$firstOp, &$lastOp) // {{{
|
|
{
|
|
if ($firstOp['opcode'] == XC_CASE && !empty($lastOp['jmptos'])
|
|
|| $firstOp['opcode'] == XC_JMP && !empty($firstOp['jmptos']) && $opcodes[$firstOp['jmptos'][0]]['opcode'] == XC_CASE && !empty($lastOp['jmptos'])
|
|
) {
|
|
$this->clearJmpInfo_brk_cont($range);
|
|
$cases = array();
|
|
$caseDefault = null;
|
|
$caseOp = null;
|
|
for ($i = $range[0]; $i <= $range[1]; ) {
|
|
$op = $opcodes[$i];
|
|
if ($op['opcode'] == XC_CASE) {
|
|
if (!isset($caseOp)) {
|
|
$caseOp = $op;
|
|
}
|
|
$jmpz = $opcodes[$i + 1];
|
|
assert('$jmpz["opcode"] == XC_JMPZ');
|
|
$caseNext = $jmpz['jmptos'][0];
|
|
$cases[$i] = $caseNext - 1;
|
|
$i = $caseNext;
|
|
}
|
|
else if ($op['opcode'] == XC_JMP && $op['jmptos'][0] >= $i) {
|
|
// default
|
|
$caseNext = $op['jmptos'][0];
|
|
$caseDefault = $i;
|
|
$cases[$i] = $caseNext - 1;
|
|
$i = $caseNext;
|
|
}
|
|
else {
|
|
++$i;
|
|
}
|
|
}
|
|
|
|
$this->output->beginComplexBlock();
|
|
|
|
$this->output->writeln('switch (', $this->getOpVal($caseOp['op1'], true), ") {");
|
|
$caseIsOut = false;
|
|
$caseExpressionBegin = $range[0];
|
|
foreach ($cases as $caseFirst => $caseLast) {
|
|
if ($caseExpressionBegin < $caseFirst) {
|
|
$this->recognizeAndDecompileClosedBlocks(array($caseExpressionBegin, $caseFirst - 1));
|
|
}
|
|
$caseExpressionBegin = $caseLast + 1;
|
|
|
|
if ($caseIsOut && empty($lastCaseFall)) {
|
|
echo PHP_EOL;
|
|
}
|
|
|
|
$caseOp = $opcodes[$caseFirst];
|
|
|
|
if ($caseOp['opcode'] == XC_CASE) {
|
|
$this->output->writeln('case ', $this->getOpVal($caseOp['op2']), ':');
|
|
|
|
$this->removeJmpInfo($caseFirst);
|
|
++$caseFirst;
|
|
|
|
assert('$opcodes[$caseFirst]["opcode"] == XC_JMPZ');
|
|
$this->removeJmpInfo($caseFirst);
|
|
++$caseFirst;
|
|
}
|
|
else {
|
|
$this->output->writeln('default:');
|
|
|
|
assert('$opcodes[$caseFirst]["opcode"] == XC_JMP');
|
|
$this->removeJmpInfo($caseFirst);
|
|
++$caseFirst;
|
|
}
|
|
|
|
assert('$opcodes[$caseLast]["opcode"] == XC_JMP');
|
|
$this->removeJmpInfo($caseLast);
|
|
--$caseLast;
|
|
switch ($opcodes[$caseLast]['opcode']) {
|
|
case XC_BRK:
|
|
case XC_CONT:
|
|
case XC_GOTO:
|
|
$lastCaseFall = false;
|
|
break;
|
|
|
|
default:
|
|
$lastCaseFall = true;
|
|
}
|
|
|
|
$this->output->beginScope();
|
|
$this->recognizeAndDecompileClosedBlocks(array($caseFirst, $caseLast));
|
|
$this->output->endScope();
|
|
$caseIsOut = true;
|
|
}
|
|
$this->output->writeln('}');
|
|
|
|
$this->output->endComplexBlock();
|
|
return true;
|
|
}
|
|
}
|
|
// }}}
|
|
function decompile_doWhile($range, &$opcodes, &$firstOp, &$lastOp) // {{{
|
|
{
|
|
if ($lastOp['opcode'] == XC_JMPNZ && !empty($lastOp['jmptos'])
|
|
&& $lastOp['jmptos'][0] == $range[0]) {
|
|
$this->removeJmpInfo($range[1]);
|
|
$this->clearJmpInfo_brk_cont($range);
|
|
$this->output->beginComplexBlock();
|
|
|
|
$this->output->writeln("do {");
|
|
$this->output->beginScope();
|
|
$this->recognizeAndDecompileClosedBlocks($range);
|
|
$this->output->endScope();
|
|
$this->output->writeln("} while (", $this->getOpVal($lastOp['op1']), ');');
|
|
|
|
$this->output->endComplexBlock();
|
|
return true;
|
|
}
|
|
}
|
|
// }}}
|
|
function decompile_while($range, &$opcodes, &$firstOp, &$lastOp, &$firstJmpOp) // {{{
|
|
{
|
|
if ($firstJmpOp['opcode'] == XC_JMPZ
|
|
&& $firstJmpOp['jmptos'][0] > $range[1]
|
|
&& $lastOp['opcode'] == XC_JMP
|
|
&& !empty($lastOp['jmptos']) && $lastOp['jmptos'][0] == $range[0]) {
|
|
$this->removeJmpInfo($firstJmpOp['line']);
|
|
$this->removeJmpInfo($range[1]);
|
|
$this->output->beginComplexBlock();
|
|
|
|
ob_start();
|
|
$this->output->beginScope();
|
|
$this->recognizeAndDecompileClosedBlocks($range);
|
|
$this->output->endScope();
|
|
$body = ob_get_clean();
|
|
|
|
$this->output->writeln("while (", $this->getOpVal($firstJmpOp['op1']), ") {");
|
|
echo $body;
|
|
$this->output->writeln('}');
|
|
|
|
$this->output->endComplexBlock();
|
|
return true;
|
|
}
|
|
}
|
|
// }}}
|
|
function decompile_foreach($range, &$opcodes, &$firstOp, &$lastOp, &$firstJmpOp, &$lastJmpOp) // {{{
|
|
{
|
|
if ($firstJmpOp['opcode'] == XC_FE_FETCH
|
|
&& !empty($firstJmpOp['jmptos']) && $firstJmpOp['jmptos'][0] > $lastJmpOp['line']
|
|
&& isset($lastJmpOp)
|
|
&& $lastJmpOp['opcode'] == XC_JMP
|
|
&& !empty($lastJmpOp['jmptos']) && $lastJmpOp['jmptos'][0] == $firstJmpOp['line']) {
|
|
$this->removeJmpInfo($firstJmpOp['line']);
|
|
$this->removeJmpInfo($lastJmpOp['line']);
|
|
$this->clearJmpInfo_brk_cont($range);
|
|
$this->output->beginComplexBlock();
|
|
|
|
ob_start();
|
|
$this->output->beginScope();
|
|
$this->recognizeAndDecompileClosedBlocks($range);
|
|
$this->output->endScope();
|
|
$body = ob_get_clean();
|
|
|
|
$as = $firstJmpOp['fe_as'];
|
|
if (isset($firstJmpOp['fe_key'])) {
|
|
$as = array($firstJmpOp['fe_key'], ' => ', $as);
|
|
}
|
|
|
|
$this->output->writeln("foreach (", $firstJmpOp['fe_src'], " as ", $as, ") {");
|
|
echo $body;
|
|
$this->output->writeln('}');
|
|
|
|
$this->output->endComplexBlock();
|
|
return true;
|
|
}
|
|
}
|
|
// }}}
|
|
function recognizeAndDecompileClosedBlocks($range) // {{{ decompile in a tree way
|
|
{
|
|
$opcodes = &$this->EX['opcodes'];
|
|
if (count($opcodes) > 1000) {
|
|
$total = 70;
|
|
|
|
static $spinChars = array(
|
|
'-', '\\', '|', '/'
|
|
);
|
|
if (!isset($this->EX['bar'])) {
|
|
$this->EX['bar'] = str_repeat(' ', $total);
|
|
$this->EX['barX'] = 0;
|
|
}
|
|
|
|
$left = $total;
|
|
$bar = '';
|
|
|
|
$width = floor($total * $range[0] / count($opcodes));
|
|
$left -= $width;
|
|
$bar .= str_repeat('>', $width);
|
|
|
|
$width = ceil($total * ($range[1] - $range[0]) / count($opcodes));
|
|
if ($left && !$width) {
|
|
$width = 1;
|
|
}
|
|
$left -= $width;
|
|
$bar .= str_repeat($spinChars[$this->EX['barX']++ % count($spinChars)], $width);
|
|
|
|
$bar .= substr($this->EX['bar'], strlen($bar));
|
|
$this->EX['bar'] = $bar;
|
|
|
|
fwrite(STDERR, "\r[$bar]");
|
|
}
|
|
|
|
$ranges = array();
|
|
$starti = $range[0];
|
|
for ($i = $starti; $i <= $range[1]; ) {
|
|
if (!empty($opcodes[$i]['jmpfroms']) || !empty($opcodes[$i]['jmptos'])) {
|
|
$blockFirst = $i;
|
|
$blockLast = -1;
|
|
$j = $blockFirst;
|
|
do {
|
|
$op = $opcodes[$j];
|
|
if (!empty($op['jmpfroms'])) {
|
|
// care about jumping from blocks behind, not before
|
|
foreach ($op['jmpfroms'] as $oplineNumber) {
|
|
if ($oplineNumber <= $range[1] && $blockLast < $oplineNumber) {
|
|
$blockLast = $oplineNumber;
|
|
}
|
|
}
|
|
}
|
|
if (!empty($op['jmptos'])) {
|
|
$blockLast = max($blockLast, max($op['jmptos']) - 1);
|
|
}
|
|
++$j;
|
|
} while ($j <= $blockLast);
|
|
|
|
if ($blockLast > $range[1]) {
|
|
fprintf(STDERR, "%d: \$blockLast(%d) > \$range[1](%d)\n", __LINE__, $blockLast, $range[1]);
|
|
assert('$blockLast <= $range[1]');
|
|
printBacktrace();
|
|
$this->dumpRange($range);
|
|
}
|
|
|
|
if ($blockLast >= $blockFirst) {
|
|
if ($blockFirst > $starti) {
|
|
$this->decompileBasicBlock(array($starti, $blockFirst - 1));
|
|
}
|
|
$this->decompileComplexBlock(array($blockFirst, $blockLast));
|
|
$starti = $blockLast + 1;
|
|
$i = $starti;
|
|
}
|
|
else {
|
|
++$i;
|
|
}
|
|
}
|
|
else {
|
|
++$i;
|
|
}
|
|
}
|
|
if ($starti <= $range[1]) {
|
|
$this->decompileBasicBlock(array($starti, $range[1]));
|
|
}
|
|
}
|
|
// }}}
|
|
function buildJmpInfo($range) // {{{ build jmpfroms/jmptos to op_array
|
|
{
|
|
$op_array = &$this->EX['op_array'];
|
|
$opcodes = &$this->EX['opcodes'];
|
|
for ($i = $range[0]; $i <= $range[1]; $i++) {
|
|
$op = &$opcodes[$i];
|
|
switch ($op['opcode']) {
|
|
case XC_CONT:
|
|
case XC_BRK:
|
|
$jmpTo = null;
|
|
if ($op['op2']['op_type'] == XC_IS_CONST && is_int($op['op2']['constant'])) {
|
|
$nestedLevel = $op['op2']['constant'];
|
|
$arrayOffset = $op['op1']['opline_num'];
|
|
// zend_brk_cont
|
|
while ($nestedLevel-- > 0) {
|
|
if ($arrayOffset == -1) {
|
|
$jmpTo = null;
|
|
break;
|
|
}
|
|
if (!isset($op_array['brk_cont_array'][$arrayOffset])) {
|
|
fprintf(STDERR, "%d: brk/cont not found at #$i\n", __LINE__);
|
|
break;
|
|
}
|
|
$jmpTo = $op_array['brk_cont_array'][$arrayOffset];
|
|
$arrayOffset = $jmpTo['parent'];
|
|
}
|
|
}
|
|
|
|
$op['jmptos'] = array();
|
|
if (isset($jmpTo)) {
|
|
$jmpTo = $jmpTo[$op['opcode'] == XC_CONT ? 'cont' : 'brk'];
|
|
$op['jmptos'][] = $jmpTo;
|
|
$opcodes[$jmpTo]['jmpfroms'][] = $i;
|
|
}
|
|
break;
|
|
|
|
case XC_GOTO:
|
|
$target = $op['op1']['var'];
|
|
if (!isset($opcodes[$target])) {
|
|
fprintf(STDERR, "%d: missing jump target at #$i" . PHP_EOL, __LINE__);
|
|
break;
|
|
}
|
|
$op['goto'] = $target;
|
|
$opcodes[$target]['gofrom'][] = $i;
|
|
break;
|
|
|
|
case XC_JMP:
|
|
$target = $op['op1']['var'];
|
|
if (!isset($opcodes[$target])) {
|
|
fprintf(STDERR, "%d: missing jump target at #$i" . PHP_EOL, __LINE__);
|
|
break;
|
|
}
|
|
$op['jmptos'] = array($target);
|
|
$opcodes[$target]['jmpfroms'][] = $i;
|
|
break;
|
|
|
|
case XC_JMPZNZ:
|
|
$jmpz = $op['op2']['opline_num'];
|
|
$jmpnz = $op['extended_value'];
|
|
if (!isset($opcodes[$jmpz])) {
|
|
fprintf(STDERR, "%d: missing jump target at #$i" . PHP_EOL, __LINE__);
|
|
break;
|
|
}
|
|
if (!isset($opcodes[$jmpnz])) {
|
|
fprintf(STDERR, "%d: missing jump target at #$i" . PHP_EOL, __LINE__);
|
|
break;
|
|
}
|
|
$op['jmptos'] = array($jmpz, $jmpnz);
|
|
$opcodes[$jmpz]['jmpfroms'][] = $i;
|
|
$opcodes[$jmpnz]['jmpfroms'][] = $i;
|
|
break;
|
|
|
|
case XC_JMPZ:
|
|
case XC_JMPNZ:
|
|
case XC_JMPZ_EX:
|
|
case XC_JMPNZ_EX:
|
|
// case XC_JMP_SET:
|
|
// case XC_JMP_SET_VAR:
|
|
// case XC_FE_RESET:
|
|
case XC_FE_FETCH:
|
|
// case XC_JMP_NO_CTOR:
|
|
$target = $op['op2']['opline_num'];
|
|
if (!isset($opcodes[$target])) {
|
|
fprintf(STDERR, "%d: missing jump target at #$i" . PHP_EOL, __LINE__);
|
|
break;
|
|
}
|
|
$op['jmptos'] = array($target);
|
|
$opcodes[$target]['jmpfroms'][] = $i;
|
|
break;
|
|
|
|
/*
|
|
case XC_RETURN:
|
|
$op['jmptos'] = array();
|
|
break;
|
|
*/
|
|
|
|
case XC_CASE:
|
|
// just to link together
|
|
$op['jmptos'] = array($i + 2);
|
|
$opcodes[$i + 2]['jmpfroms'][] = $i;
|
|
break;
|
|
|
|
case XC_CATCH:
|
|
$catchNext = $op['extended_value'];
|
|
$catchBegin = $opcodes[$i - 1]['opcode'] == XC_FETCH_CLASS ? $i - 1 : $i;
|
|
$opcodes[$catchBegin]['jmptos'] = array($catchNext);
|
|
$opcodes[$catchNext]['jmpfroms'][] = $catchBegin;
|
|
break;
|
|
}
|
|
/*
|
|
if (!empty($op['jmptos']) || !empty($op['jmpfroms'])) {
|
|
echo $i, "\t", xcache_get_opcode($op['opcode']), PHP_EOL;
|
|
}
|
|
// */
|
|
}
|
|
unset($op);
|
|
if (isset($op_array['try_catch_array'])) {
|
|
foreach ($op_array['try_catch_array'] as $try_catch_element) {
|
|
$catch_op = $try_catch_element['catch_op'];
|
|
$opcodes[$catch_op]['isCatchBegin'] = true;
|
|
}
|
|
foreach ($op_array['try_catch_array'] as $try_catch_element) {
|
|
$catch_op = $try_catch_element['catch_op'];
|
|
$try_op = $try_catch_element['try_op'];
|
|
do {
|
|
$opcodes[$try_op]['jmpfroms'][] = $catch_op;
|
|
$opcodes[$catch_op]['jmptos'][] = $try_op;
|
|
if ($opcodes[$catch_op]['opcode'] == XC_CATCH) {
|
|
$catch_op = $opcodes[$catch_op]['extended_value'];
|
|
}
|
|
else if ($catch_op + 1 <= $range[1] && $opcodes[$catch_op + 1]['opcode'] == XC_CATCH) {
|
|
$catch_op = $opcodes[$catch_op + 1]['extended_value'];
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
} while ($catch_op <= $range[1] && empty($opcodes[$catch_op]['isCatchBegin']));
|
|
}
|
|
}
|
|
}
|
|
// }}}
|
|
function clearJmpInfo_brk_cont($range) // {{{ clear jmpfroms/jmptos for BRK/CONT relative to this range only
|
|
{
|
|
$opcodes = &$this->EX['opcodes'];
|
|
for ($i = $range[0]; $i <= $range[1]; $i++) {
|
|
$op = &$opcodes[$i];
|
|
switch ($op['opcode']) {
|
|
case XC_CONT:
|
|
case XC_BRK:
|
|
if (!empty($op['jmptos'])) {
|
|
if ($op['jmptos'][0] == $range[0]
|
|
|| $op['jmptos'][0] == $range[1] + 1) {
|
|
$this->removeJmpInfo($i);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
unset($op);
|
|
}
|
|
// }}}
|
|
function &dop_array($op_array, $isFunction = false) // {{{
|
|
{
|
|
$this->fixOpCode($op_array['opcodes'], true, $isFunction ? null : 1);
|
|
|
|
$opcodes = &$op_array['opcodes'];
|
|
|
|
$EX = array();
|
|
$this->EX = &$EX;
|
|
$EX['Ts'] = $this->outputPhp ? array() : null;
|
|
$EX['op_array'] = &$op_array;
|
|
$EX['opcodes'] = &$opcodes;
|
|
// func call
|
|
$EX['object'] = null;
|
|
$EX['called_scope'] = null;
|
|
$EX['fbc'] = null;
|
|
$EX['argstack'] = array();
|
|
$EX['arg_types_stack'] = array();
|
|
$EX['silence'] = 0;
|
|
$EX['recvs'] = array();
|
|
$EX['uses'] = array();
|
|
$EX['value2constant'] = array();
|
|
if (isset($this->activeMethod)) {
|
|
$EX['value2constant'][$this->activeMethod] = '__METHOD__';
|
|
}
|
|
if (isset($this->activeFunction)) {
|
|
$EX['value2constant'][$this->activeFunction] = '__FUNCTION__';
|
|
}
|
|
|
|
$range = array(0, count($opcodes) - 1);
|
|
for ($i = $range[0]; $i <= $range[1]; $i++) {
|
|
$opcodes[$i]['line'] = $i;
|
|
}
|
|
$this->buildJmpInfo($range);
|
|
|
|
if ($this->outputOpcode) {
|
|
$this->keepTs = true;
|
|
$this->dumpRange($range);
|
|
$this->keepTs = false;
|
|
}
|
|
if ($this->outputPhp) {
|
|
// decompile in a tree way
|
|
$this->recognizeAndDecompileClosedBlocks($range);
|
|
}
|
|
unset($this->EX);
|
|
return $EX;
|
|
}
|
|
// }}}
|
|
function dasmBasicBlock($range) // {{{
|
|
{
|
|
$T = &$this->EX['Ts'];
|
|
$opcodes = &$this->EX['opcodes'];
|
|
$lastphpop = null;
|
|
|
|
for ($i = $range[0]; $i <= $range[1]; $i++) {
|
|
// {{{ prepair
|
|
$op = &$opcodes[$i];
|
|
$opc = $op['opcode'];
|
|
if ($opc == XC_NOP) {
|
|
$this->usedOps[$opc] = true;
|
|
continue;
|
|
}
|
|
|
|
$op1 = $op['op1'];
|
|
$op2 = $op['op2'];
|
|
$res = $op['result'];
|
|
$ext = $op['extended_value'];
|
|
|
|
$opname = xcache_get_opcode($opc);
|
|
|
|
if ($opname == 'UNDEF' || !isset($opname)) {
|
|
echo '// UNDEF OP:';
|
|
$this->dumpOp($op);
|
|
continue;
|
|
}
|
|
// echo $i, ' '; $this->dumpOp($op); //var_dump($op);
|
|
|
|
$resvar = null;
|
|
unset($curResVar);
|
|
if (array_key_exists($res['var'], $T)) {
|
|
$curResVar = &$T[$res['var']];
|
|
}
|
|
if ((ZEND_ENGINE_2_4 ? ($res['op_type'] & EXT_TYPE_UNUSED) : ($res['EA.type'] & EXT_TYPE_UNUSED)) || $res['op_type'] == XC_IS_UNUSED) {
|
|
$istmpres = false;
|
|
}
|
|
else {
|
|
$istmpres = true;
|
|
}
|
|
// }}}
|
|
// echo $opname, PHP_EOL;
|
|
|
|
$notHandled = false;
|
|
switch ($opc) {
|
|
case XC_NEW: // {{{
|
|
array_push($this->EX['arg_types_stack'], array($this->EX['fbc'], $this->EX['object'], $this->EX['called_scope']));
|
|
$this->EX['object'] = $istmpres ? (int) $res['var'] : null;
|
|
$this->EX['called_scope'] = null;
|
|
$this->EX['fbc'] = new Decompiler_UnaryOp($this, $opc, $this->getOpVal($op1, false, true));
|
|
break;
|
|
// }}}
|
|
case XC_CATCH: // {{{
|
|
break;
|
|
// }}}
|
|
case XC_INSTANCEOF: // {{{
|
|
$resvar = new Decompiler_BinaryOp($this, $this->getOpVal($op1), $opc, $this->stripNamespace($this->getOpVal($op2)));
|
|
break;
|
|
// }}}
|
|
case XC_FETCH_CLASS: // {{{
|
|
if ($op2['op_type'] == XC_IS_UNUSED) {
|
|
switch (($ext & (defined('ZEND_FETCH_CLASS_MASK') ? ZEND_FETCH_CLASS_MASK : 0xFF))) {
|
|
case ZEND_FETCH_CLASS_SELF:
|
|
$class = 'self';
|
|
break;
|
|
case ZEND_FETCH_CLASS_PARENT:
|
|
$class = 'parent';
|
|
break;
|
|
case ZEND_FETCH_CLASS_STATIC:
|
|
$class = 'static';
|
|
break;
|
|
}
|
|
$istmpres = true;
|
|
}
|
|
else {
|
|
$class = $this->getOpVal($op2, true, true);
|
|
}
|
|
$resvar = $class;
|
|
break;
|
|
// }}}
|
|
case XC_FETCH_CONSTANT: // {{{
|
|
if ($op1['op_type'] == XC_IS_UNUSED) {
|
|
$resvar = $this->stripNamespace($op2['constant']);
|
|
break;
|
|
}
|
|
|
|
if ($op1['op_type'] == XC_IS_CONST) {
|
|
if (!ZEND_ENGINE_2) {
|
|
$resvar = $op1['constant'];
|
|
break;
|
|
}
|
|
$resvar = $this->stripNamespace($op1['constant']);
|
|
}
|
|
else {
|
|
$resvar = $this->getOpVal($op1);
|
|
}
|
|
|
|
$resvar = new Decompiler_Code($this, array($resvar, '::', unquoteName($this->getOpVal($op2))));
|
|
break;
|
|
// }}}
|
|
// {{{ case FETCH_*
|
|
case XC_FETCH_R:
|
|
case XC_FETCH_W:
|
|
case XC_FETCH_RW:
|
|
case XC_FETCH_FUNC_ARG:
|
|
case XC_FETCH_UNSET:
|
|
case XC_FETCH_IS:
|
|
$fetchType = defined('ZEND_FETCH_TYPE_MASK') ? ($ext & ZEND_FETCH_TYPE_MASK) : $op2[!ZEND_ENGINE_2 ? 'fetch_type' : 'EA.type'];
|
|
$name = isset($op1['constant']) ? $op1['constant'] : $this->getOpVal($op1);
|
|
if ($fetchType == ZEND_FETCH_STATIC_MEMBER) {
|
|
$class = $this->getOpVal($op2, false, true);
|
|
}
|
|
else {
|
|
$class = null;
|
|
}
|
|
$rvalue = new Decompiler_Fetch($this, $class, $name, $fetchType);
|
|
|
|
if ($res['op_type'] != XC_IS_UNUSED) {
|
|
$resvar = $rvalue;
|
|
}
|
|
break;
|
|
// }}}
|
|
case XC_UNSET_VAR: // {{{
|
|
$fetchType = defined('ZEND_FETCH_TYPE_MASK') ? ($ext & ZEND_FETCH_TYPE_MASK) : $op2['EA.type'];
|
|
if ($fetchType == ZEND_FETCH_STATIC_MEMBER) {
|
|
$class = isset($op2['constant']) ? $op2['constant'] /* PHP5.3- */ : $this->getOpVal($op2);
|
|
$rvalue = $this->stripNamespace($class) . '::$' . $op1['constant'];
|
|
}
|
|
else {
|
|
$rvalue = isset($op1['constant']) ? '$' . $op1['constant'] /* PHP5.1- */ : $this->getOpVal($op1);
|
|
}
|
|
|
|
$op['php'] = array("unset(", $rvalue, ")");
|
|
$lastphpop = &$op;
|
|
break;
|
|
// }}}
|
|
// {{{ case FETCH_DIM_*
|
|
case XC_FETCH_DIM_TMP_VAR:
|
|
case XC_FETCH_DIM_R:
|
|
case XC_FETCH_DIM_W:
|
|
case XC_FETCH_DIM_RW:
|
|
case XC_FETCH_DIM_FUNC_ARG:
|
|
case XC_FETCH_DIM_UNSET:
|
|
case XC_FETCH_DIM_IS:
|
|
case XC_ASSIGN_DIM:
|
|
case XC_UNSET_DIM:
|
|
case XC_UNSET_DIM_OBJ:
|
|
case XC_UNSET_OBJ:
|
|
$src = $this->getOpVal($op1);
|
|
if (is_a($src, "Decompiler_ForeachBox")) {
|
|
assert($opc == XC_FETCH_DIM_TMP_VAR);
|
|
if (ZEND_ENGINE_2) {
|
|
$src = clone($src);
|
|
}
|
|
else {
|
|
$src = new Decompiler_ForeachBox($this, $src->obj);
|
|
}
|
|
$src->iskey = $op2['constant'];
|
|
$resvar = $src;
|
|
break;
|
|
}
|
|
|
|
if (is_a($src, "Decompiler_DimBox")) {
|
|
$dimbox = $src;
|
|
}
|
|
else {
|
|
if (!is_a($src, "Decompiler_ListBox")) {
|
|
$op1val = $this->getOpVal($op1);
|
|
$list = new Decompiler_List($this, isset($op1val) ? $op1val : '$this');
|
|
|
|
$src = new Decompiler_ListBox($this, $list);
|
|
if (!isset($op1['var'])) {
|
|
$this->dumpOp($op);
|
|
var_dump($op);
|
|
die('missing var');
|
|
}
|
|
$T[$op1['var']] = $src;
|
|
unset($list);
|
|
}
|
|
$dim = new Decompiler_Dim($this, $src);
|
|
$src->obj->dims[] = &$dim;
|
|
|
|
$dimbox = new Decompiler_DimBox($this, $dim);
|
|
}
|
|
$dim = &$dimbox->obj;
|
|
$dim->offsets[] = $this->getOpVal($op2);
|
|
/* TODO: use type mask */
|
|
if ($ext == ZEND_FETCH_ADD_LOCK) {
|
|
$src->obj->everLocked = true;
|
|
}
|
|
else if ($ext == ZEND_FETCH_STANDARD) {
|
|
$dim->isLast = true;
|
|
}
|
|
if ($opc == XC_UNSET_OBJ) {
|
|
$dim->isObject = true;
|
|
}
|
|
else if ($opc == XC_UNSET_DIM_OBJ) {
|
|
$dim->isObject = ZEND_ENGINE_2 ? $ext == ZEND_UNSET_OBJ : false /* cannot distingue */;
|
|
}
|
|
unset($dim);
|
|
$rvalue = $dimbox;
|
|
unset($dimbox);
|
|
|
|
if ($opc == XC_ASSIGN_DIM) {
|
|
$lvalue = $rvalue;
|
|
++ $i;
|
|
$rvalue = $this->getOpVal($opcodes[$i]['op1']);
|
|
$resvar = new Decompiler_BinaryOp($this, $lvalue, $opc, $rvalue);
|
|
}
|
|
else if ($opc == XC_UNSET_DIM || $opc == XC_UNSET_OBJ || $opc == XC_UNSET_DIM_OBJ) {
|
|
$op['php'] = array("unset(", $rvalue, ")");
|
|
$lastphpop = &$op;
|
|
}
|
|
else if ($res['op_type'] != XC_IS_UNUSED) {
|
|
$resvar = $rvalue;
|
|
}
|
|
break;
|
|
// }}}
|
|
case XC_ASSIGN: // {{{
|
|
$lvalue = $this->getOpVal($op1);
|
|
$rvalue = $this->getOpVal($op2);
|
|
if (is_a($rvalue, 'Decompiler_ForeachBox')) {
|
|
$type = $rvalue->iskey ? 'fe_key' : 'fe_as';
|
|
$rvalue->obj[$type] = $lvalue;
|
|
unset($T[$op2['var']]);
|
|
break;
|
|
}
|
|
if (is_a($rvalue, "Decompiler_DimBox")) {
|
|
$dim = &$rvalue->obj;
|
|
$dim->assign = $lvalue;
|
|
if ($dim->isLast) {
|
|
$resvar = $dim->value;
|
|
}
|
|
unset($dim);
|
|
break;
|
|
}
|
|
if (is_a($lvalue, 'Decompiler_Fetch') && is_a($rvalue, 'Decompiler_Fetch')) {
|
|
if ($lvalue->name == $rvalue->name) {
|
|
switch ($rvalue->fetchType) {
|
|
case ZEND_FETCH_STATIC:
|
|
$statics = &$this->EX['op_array']['static_variables'];
|
|
if ((xcache_get_type($statics[$rvalue->name]) & IS_LEXICAL_VAR)) {
|
|
$this->EX['uses'][] = $lvalue;
|
|
unset($statics);
|
|
break 2;
|
|
}
|
|
unset($statics);
|
|
}
|
|
}
|
|
}
|
|
$resvar = new Decompiler_BinaryOp($this, $lvalue, XC_ASSIGN, $rvalue);
|
|
break;
|
|
// }}}
|
|
case XC_ASSIGN_REF: // {{{
|
|
$lvalue = $this->getOpVal($op1);
|
|
$rvalue = $this->getOpVal($op2);
|
|
if (is_a($lvalue, 'Decompiler_Fetch') && is_a($rvalue, 'Decompiler_Fetch')) {
|
|
if ($lvalue->name == $rvalue->name) {
|
|
switch ($rvalue->fetchType) {
|
|
case ZEND_FETCH_GLOBAL:
|
|
case ZEND_FETCH_GLOBAL_LOCK:
|
|
$resvar = new Decompiler_Code($this, array('global ', $lvalue));
|
|
break 2;
|
|
case ZEND_FETCH_STATIC:
|
|
$statics = &$this->EX['op_array']['static_variables'];
|
|
if ((xcache_get_type($statics[$rvalue->name]) & IS_LEXICAL_REF)) {
|
|
$this->EX['uses'][] = array('&', $lvalue);
|
|
unset($statics);
|
|
break 2;
|
|
}
|
|
|
|
$resvar = array();
|
|
$resvar[] = 'static ';
|
|
$resvar[] = $lvalue;
|
|
if (isset($statics[$rvalue->name])) {
|
|
$var = $statics[$rvalue->name];
|
|
$resvar[] = ' = ';
|
|
$resvar[] = value($var);
|
|
}
|
|
$resvar = new Decompiler_Code($this, $resvar);
|
|
unset($statics);
|
|
break 2;
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
// TODO: PHP_6 global
|
|
$resvar = new Decompiler_BinaryOp($this, $lvalue, XC_ASSIGN_REF, $rvalue);
|
|
break;
|
|
// }}}
|
|
// {{{ case FETCH_OBJ_*
|
|
case XC_FETCH_OBJ_R:
|
|
case XC_FETCH_OBJ_W:
|
|
case XC_FETCH_OBJ_RW:
|
|
case XC_FETCH_OBJ_FUNC_ARG:
|
|
case XC_FETCH_OBJ_UNSET:
|
|
case XC_FETCH_OBJ_IS:
|
|
case XC_ASSIGN_OBJ:
|
|
$obj = $this->getOpVal($op1);
|
|
if (!isset($obj)) {
|
|
$obj = '$this';
|
|
}
|
|
$name = isset($op2['constant']) ? new Decompiler_Value($this, $op2['constant']) : $this->getOpVal($op2);
|
|
if ($res['op_type'] != XC_IS_UNUSED) {
|
|
$resvar = new Decompiler_Fetch($this, $obj, $name->value, XC_FETCH_PROPERTY);
|
|
}
|
|
if ($opc == XC_ASSIGN_OBJ) {
|
|
++ $i;
|
|
$lvalue = $rvalue;
|
|
$rvalue = $this->getOpVal($opcodes[$i]['op1']);
|
|
$resvar = new Decompiler_BinaryOp($this, $lvalue, $opc, $rvalue);
|
|
}
|
|
break;
|
|
// }}}
|
|
case XC_ISSET_ISEMPTY_DIM_OBJ:
|
|
case XC_ISSET_ISEMPTY_PROP_OBJ:
|
|
case XC_ISSET_ISEMPTY:
|
|
case XC_ISSET_ISEMPTY_VAR: // {{{
|
|
if ($opc == XC_ISSET_ISEMPTY_VAR) {
|
|
$rvalue = $this->getOpVal($op1);
|
|
// for < PHP_5_3
|
|
if ($op1['op_type'] == XC_IS_CONST) {
|
|
$rvalue = '$' . unquoteVariableName($this->getOpVal($op1));
|
|
}
|
|
$fetchtype = defined('ZEND_FETCH_TYPE_MASK') ? ($ext & ZEND_FETCH_TYPE_MASK) : $op2['EA.type'];
|
|
if ($fetchtype == ZEND_FETCH_STATIC_MEMBER) {
|
|
$class = isset($op2['constant']) ? $op2['constant'] : $this->getOpVal($op2);
|
|
$rvalue = $this->stripNamespace($class) . '::' . unquoteName($rvalue, $this->EX);
|
|
}
|
|
}
|
|
else if ($opc == XC_ISSET_ISEMPTY) {
|
|
$rvalue = $this->getOpVal($op1);
|
|
}
|
|
else {
|
|
$container = $this->getOpVal($op1);
|
|
$dim = $this->getOpVal($op2);
|
|
if ($opc == XC_ISSET_ISEMPTY_PROP_OBJ) {
|
|
if (!isset($container)) {
|
|
$container = '$this';
|
|
}
|
|
$rvalue = array($container, "->", unquoteVariableName($dim));
|
|
}
|
|
else {
|
|
$rvalue = array($container, '[', $dim, ']');
|
|
}
|
|
}
|
|
|
|
switch (((!ZEND_ENGINE_2 ? $op['op2']['var'] /* constant */ : $ext) & ZEND_ISSET_ISEMPTY_MASK)) {
|
|
case ZEND_ISSET:
|
|
$rvalue = array("isset(", $rvalue, ")");
|
|
break;
|
|
case ZEND_ISEMPTY:
|
|
$rvalue = array("empty(", $rvalue, ")");
|
|
break;
|
|
}
|
|
$resvar = new Decompiler_Code($this, $rvalue);
|
|
break;
|
|
// }}}
|
|
case XC_SEND_VAR_NO_REF:
|
|
case XC_SEND_VAL:
|
|
case XC_SEND_REF:
|
|
case XC_SEND_VAR: // {{{
|
|
$ref = (!ZEND_ENGINE_2_4 && $opc == XC_SEND_REF ? '&' : '');
|
|
$this->EX['argstack'][] = array($ref, $this->getOpVal($op1));
|
|
break;
|
|
// }}}
|
|
case XC_INIT_STATIC_METHOD_CALL:
|
|
case XC_INIT_METHOD_CALL: // {{{
|
|
array_push($this->EX['arg_types_stack'], array($this->EX['fbc'], $this->EX['object'], $this->EX['called_scope']));
|
|
if ($opc == XC_INIT_STATIC_METHOD_CALL) {
|
|
$this->EX['object'] = null;
|
|
$this->EX['called_scope'] = $this->getOpVal($op1, false, true);
|
|
}
|
|
else {
|
|
$obj = $this->getOpVal($op1);
|
|
if (!isset($obj)) {
|
|
$obj = '$this';
|
|
}
|
|
$this->EX['object'] = $obj;
|
|
$this->EX['called_scope'] = null;
|
|
}
|
|
if ($res['op_type'] != XC_IS_UNUSED) {
|
|
$resvar = '$obj call$';
|
|
}
|
|
|
|
$this->EX['fbc'] = isset($op2['constant']) ? $op2['constant'] : $this->getOpVal($op2);
|
|
if (!isset($this->EX['fbc'])) {
|
|
$this->EX['fbc'] = '__construct';
|
|
}
|
|
break;
|
|
// }}}
|
|
case XC_INIT_NS_FCALL_BY_NAME:
|
|
case XC_INIT_FCALL_BY_NAME: // {{{
|
|
if (!ZEND_ENGINE_2 && ($ext & ZEND_CTOR_CALL)) {
|
|
break;
|
|
}
|
|
array_push($this->EX['arg_types_stack'], array($this->EX['fbc'], $this->EX['object'], $this->EX['called_scope']));
|
|
if (!ZEND_ENGINE_2 && ($ext & ZEND_MEMBER_FUNC_CALL)) {
|
|
if (isset($op1['constant'])) {
|
|
$this->EX['object'] = null;
|
|
$this->EX['called_scope'] = $this->stripNamespace($op1['constant']);
|
|
}
|
|
else {
|
|
$this->EX['object'] = $this->getOpVal($op1);
|
|
$this->EX['called_scope'] = null;
|
|
}
|
|
}
|
|
else {
|
|
$this->EX['object'] = null;
|
|
$this->EX['called_scope'] = null;
|
|
}
|
|
$this->EX['fbc'] = $this->getOpVal($op2, true, true);
|
|
break;
|
|
// }}}
|
|
case XC_INIT_FCALL_BY_FUNC: // {{{ deprecated even in PHP 4?
|
|
$this->EX['object'] = null;
|
|
$this->EX['called_scope'] = null;
|
|
$which = $op1['var'];
|
|
$this->EX['fbc'] = $this->EX['op_array']['funcs'][$which]['name'];
|
|
break;
|
|
// }}}
|
|
case XC_DO_FCALL_BY_FUNC:
|
|
$which = $op1['var'];
|
|
$fname = $this->EX['op_array']['funcs'][$which]['name'];
|
|
$args = $this->popargs($ext);
|
|
$resvar = new Decompiler_Code($this, array($fname, "(", $args, ")"));
|
|
break;
|
|
case XC_DO_FCALL:
|
|
$fname = unquoteName($this->getOpVal($op1), $this->EX);
|
|
$args = $this->popargs($ext);
|
|
$resvar = new Decompiler_Code($this, array($fname, "(", $args, ")"));
|
|
break;
|
|
case XC_DO_FCALL_BY_NAME: // {{{
|
|
$object = null;
|
|
|
|
if (!is_int($this->EX['object'])) {
|
|
$object = $this->EX['object'];
|
|
}
|
|
|
|
$code = array();
|
|
if (isset($object)) {
|
|
$code[] = $object;
|
|
$code[] = '->';
|
|
}
|
|
if (isset($this->EX['called_scope'])) {
|
|
$code[] = $this->EX['called_scope'];
|
|
$code[] = '::';
|
|
}
|
|
if (isset($this->EX['fbc'])) {
|
|
$code[] = $this->EX['fbc'];
|
|
}
|
|
$code[] = '(';
|
|
$code[] = $this->popargs($ext);
|
|
$code[] = ')';
|
|
$resvar = new Decompiler_Code($this, $code);
|
|
unset($code);
|
|
|
|
if (is_int($this->EX['object'])) {
|
|
$T[$this->EX['object']] = $resvar;
|
|
$resvar = null;
|
|
}
|
|
list($this->EX['fbc'], $this->EX['object'], $this->EX['called_scope']) = array_pop($this->EX['arg_types_stack']);
|
|
break;
|
|
// }}}
|
|
case XC_VERIFY_ABSTRACT_CLASS: // {{{
|
|
//unset($T[$op1['var']]);
|
|
break;
|
|
// }}}
|
|
case XC_DECLARE_CLASS:
|
|
case XC_DECLARE_INHERITED_CLASS:
|
|
case XC_DECLARE_INHERITED_CLASS_DELAYED: // {{{
|
|
$key = $op1['constant'];
|
|
// possible missing tailing \0 (outside of the string)
|
|
$key = substr($key . ".", 0, strlen($key));
|
|
if (!isset($this->dc['class_table'][$key])) {
|
|
echo $this->EX['indent'], "/* class not found: ", $key, ", existing classes are:", PHP_EOL;
|
|
var_dump(array_keys($this->dc['class_table']));
|
|
echo "*/", PHP_EOL;
|
|
break;
|
|
}
|
|
$class = &$this->dc['class_table'][$key];
|
|
$this->detectNamespace($class['name']);
|
|
|
|
if (!isset($class['name'])) {
|
|
$class['name'] = unquoteName($this->getOpVal($op2), $this->EX);
|
|
}
|
|
if ($opc == XC_DECLARE_INHERITED_CLASS || $opc == XC_DECLARE_INHERITED_CLASS_DELAYED) {
|
|
if (ZEND_ENGINE_2_5) {
|
|
$ext = (0xffffffff - $ext + 1) / XC_SIZEOF_TEMP_VARIABLE - 1;
|
|
}
|
|
else {
|
|
$ext /= XC_SIZEOF_TEMP_VARIABLE;
|
|
}
|
|
$class['parent'] = $T[$ext];
|
|
unset($T[$ext]);
|
|
}
|
|
else {
|
|
$class['parent'] = null;
|
|
}
|
|
|
|
for (;;) {
|
|
if ($i + 1 <= $range[1]
|
|
&& $opcodes[$i + 1]['opcode'] == XC_ADD_INTERFACE
|
|
&& $opcodes[$i + 1]['op1']['var'] == $res['var']) {
|
|
// continue
|
|
}
|
|
else if ($i + 2 <= $range[1]
|
|
&& $opcodes[$i + 2]['opcode'] == XC_ADD_INTERFACE
|
|
&& $opcodes[$i + 2]['op1']['var'] == $res['var']
|
|
&& $opcodes[$i + 1]['opcode'] == XC_FETCH_CLASS) {
|
|
// continue
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
$this->usedOps[XC_ADD_INTERFACE] = true;
|
|
|
|
$fetchop = &$opcodes[$i + 1];
|
|
$interface = $this->stripNamespace(unquoteName($this->getOpVal($fetchop['op2']), $this->EX));
|
|
$addop = &$opcodes[$i + 2];
|
|
$class['interfaces'][$addop['extended_value']] = $interface;
|
|
unset($fetchop, $addop);
|
|
$i += 2;
|
|
}
|
|
$this->activeClass = $class['name'];
|
|
|
|
$oldEX = &$this->EX;
|
|
unset($this->EX);
|
|
$this->dclass($class);
|
|
$this->EX = &$oldEX;
|
|
unset($oldEX);
|
|
|
|
$this->activeClass = null;
|
|
unset($class);
|
|
break;
|
|
// }}}
|
|
case XC_INIT_STRING: // {{{
|
|
$resvar = "''";
|
|
break;
|
|
// }}}
|
|
case XC_ADD_CHAR:
|
|
case XC_ADD_STRING:
|
|
case XC_ADD_VAR: // {{{
|
|
$op1val = $this->getOpVal($op1);
|
|
$op2val = $this->getOpVal($op2);
|
|
switch ($opc) {
|
|
case XC_ADD_CHAR:
|
|
$op2val = value(chr($op2val->value));
|
|
break;
|
|
case XC_ADD_STRING:
|
|
break;
|
|
case XC_ADD_VAR:
|
|
break;
|
|
}
|
|
if (!isset($op1val) == "''") {
|
|
$rvalue = $op2val;
|
|
}
|
|
else if (!isset($op2val) == "''") {
|
|
$rvalue = $op1val;
|
|
}
|
|
else {
|
|
$rvalue = new Decompiler_BinaryOp($this, $op1val, XC_CONCAT, $op2val);
|
|
}
|
|
$resvar = $rvalue;
|
|
// }}}
|
|
break;
|
|
case XC_PRINT: // {{{
|
|
$op1val = $this->getOpVal($op1);
|
|
$resvar = new Decompiler_Code($this, array("print(", $op1val, ")"));
|
|
break;
|
|
// }}}
|
|
case XC_ECHO: // {{{
|
|
$op1val = $this->getOpVal($op1);
|
|
$resvar = new Decompiler_Code($this, array("echo ", $op1val));
|
|
break;
|
|
// }}}
|
|
case XC_EXIT: // {{{
|
|
$op1val = $this->getOpVal($op1);
|
|
$resvar = new Decompiler_Code($this, array("exit(", $op1val, ")"));
|
|
break;
|
|
// }}}
|
|
case XC_INIT_ARRAY:
|
|
case XC_ADD_ARRAY_ELEMENT: // {{{
|
|
$rvalue = $this->getOpVal($op1, true);
|
|
$assoc = $this->getOpVal($op2);
|
|
$element = array($assoc, $rvalue, empty($ext) ? '' : '&');
|
|
|
|
if ($opc == XC_INIT_ARRAY) {
|
|
$resvar = new Decompiler_Array($this);
|
|
|
|
if (isset($rvalue)) {
|
|
$resvar->value[] = $element;
|
|
}
|
|
}
|
|
else {
|
|
$curResVar->value[] = $element;
|
|
}
|
|
unset($element);
|
|
break;
|
|
// }}}
|
|
case XC_QM_ASSIGN:
|
|
case XC_QM_ASSIGN_VAR: // {{{
|
|
if (isset($curResVar) && is_a($curResVar, 'Decompiler_BinaryOp')) {
|
|
$curResVar->op2 = $this->getOpVal($op1);
|
|
}
|
|
else {
|
|
$resvar = $this->getOpVal($op1);
|
|
}
|
|
break;
|
|
// }}}
|
|
case XC_BOOL: // {{{
|
|
$resvar = /*'(bool) ' .*/ $this->getOpVal($op1);
|
|
break;
|
|
// }}}
|
|
case XC_GENERATOR_RETURN:
|
|
case XC_RETURN_BY_REF:
|
|
case XC_RETURN: // {{{
|
|
$resvar = new Decompiler_Code($this, array("return ", $this->getOpVal($op1)));
|
|
break;
|
|
// }}}
|
|
case XC_INCLUDE_OR_EVAL: // {{{
|
|
$type = ZEND_ENGINE_2_4 ? $ext : $op2['var']; // hack
|
|
$keyword = $this->includeTypes[$type];
|
|
$rvalue = $this->getOpVal($op1);
|
|
if ($type == ZEND_EVAL) {
|
|
$resvar = new Decompiler_Code($this, array($keyword, "(", $rvalue, ")"));
|
|
}
|
|
else {
|
|
$resvar = new Decompiler_Code($this, array($keyword, " ", $rvalue));
|
|
}
|
|
break;
|
|
// }}}
|
|
case XC_FE_RESET: // {{{
|
|
$resvar = $this->getOpVal($op1);
|
|
break;
|
|
// }}}
|
|
case XC_FE_FETCH: // {{{
|
|
$op['fe_src'] = $this->getOpVal($op1, true);
|
|
$fe = new Decompiler_ForeachBox($this, $op);
|
|
$fe->iskey = false;
|
|
|
|
if (ZEND_ENGINE_2_1) {
|
|
// save current first
|
|
$T[$res['var']] = $fe;
|
|
|
|
// move to next opcode
|
|
++ $i;
|
|
assert($opcodes[$i]['opcode'] == XC_OP_DATA);
|
|
$fe = new Decompiler_ForeachBox($this, $op);
|
|
$fe->iskey = true;
|
|
|
|
$res = $opcodes[$i]['result'];
|
|
}
|
|
|
|
$resvar = $fe;
|
|
break;
|
|
// }}}
|
|
case XC_YIELD: // {{{
|
|
$resvar = new Decompiler_Code($this, array('yield ', $this->getOpVal($op1)));
|
|
break;
|
|
// }}}
|
|
case XC_SWITCH_FREE: // {{{
|
|
if (isset($T[$op1['var']])) {
|
|
$this->output->beginComplexBlock();
|
|
$this->output->writeln('switch (', $this->getOpVal($op1), ") {");
|
|
$this->output->writeln('}');
|
|
$this->output->endComplexBlock();
|
|
}
|
|
break;
|
|
// }}}
|
|
case XC_FREE: // {{{
|
|
$free = $T[$op1['var']];
|
|
if (!is_a($free, 'Decompiler_Box')) {
|
|
$op['php'] = is_object($free) || is_array($free) ? $free : $this->unquote($free, '(', ')');
|
|
$lastphpop = &$op;
|
|
}
|
|
unset($T[$op1['var']], $free);
|
|
break;
|
|
// }}}
|
|
case XC_JMP_NO_CTOR:
|
|
break;
|
|
case XC_JMPZ_EX: // and
|
|
case XC_JMPNZ_EX: // or
|
|
$resvar = $this->getOpVal($op1);
|
|
break;
|
|
|
|
case XC_JMPNZ: // while
|
|
case XC_JMPZNZ: // for
|
|
case XC_JMPZ: // {{{
|
|
break;
|
|
// }}}
|
|
case XC_CONT:
|
|
case XC_BRK:
|
|
$resvar = $opc == XC_CONT ? 'continue' : 'break';
|
|
$count = $this->getOpVal($op2);
|
|
if ($count->value != 1) {
|
|
$resvar .= ' ' . $count->value;
|
|
}
|
|
break;
|
|
case XC_GOTO:
|
|
$resvar = 'goto label' . $op['op1']['var'];
|
|
$istmpres = false;
|
|
break;
|
|
|
|
case XC_JMP: // {{{
|
|
break;
|
|
// }}}
|
|
case XC_CASE:
|
|
// $switchValue = $this->getOpVal($op1);
|
|
$caseValue = $this->getOpVal($op2);
|
|
$resvar = $caseValue;
|
|
break;
|
|
case XC_RECV_INIT:
|
|
case XC_RECV:
|
|
$offset = isset($op1['var']) ? $op1['var'] : $op1['constant'];
|
|
$lvalue = $this->getOpVal($op['result']);
|
|
if ($opc == XC_RECV_INIT) {
|
|
$default = value($op['op2']['constant']);
|
|
}
|
|
else {
|
|
$default = null;
|
|
}
|
|
$this->EX['recvs'][$offset] = array($lvalue, $default);
|
|
break;
|
|
case XC_POST_DEC:
|
|
case XC_POST_INC:
|
|
case XC_POST_DEC_OBJ:
|
|
case XC_POST_INC_OBJ:
|
|
case XC_PRE_DEC:
|
|
case XC_PRE_INC:
|
|
case XC_PRE_DEC_OBJ:
|
|
case XC_PRE_INC_OBJ: // {{{
|
|
$flags = array_flip(explode('_', $opname));
|
|
if (isset($flags['OBJ'])) {
|
|
$code = array($this->getOpVal($op1), '->', $op2['constant']);
|
|
}
|
|
else {
|
|
$code = array($this->getOpVal($op1));
|
|
}
|
|
$opstr = isset($flags['DEC']) ? '--' : '++';
|
|
if (isset($flags['POST'])) {
|
|
$code[] = $opstr;
|
|
}
|
|
else {
|
|
array_unshift($code, $opstr);
|
|
}
|
|
$resvar = new Decompiler_Code($this, $code);
|
|
break;
|