|
|
|
<?php
|
|
|
|
|
|
|
|
define('INDENT', "\t");
|
|
|
|
ini_set('error_reporting', E_ALL);
|
|
|
|
|
|
|
|
function color($str, $color = 33)
|
|
|
|
{
|
|
|
|
return "\x1B[{$color}m$str\x1B[0m";
|
|
|
|
}
|
|
|
|
|
|
|
|
function printBacktrace() // {{{
|
|
|
|
{
|
|
|
|
$backtrace = debug_backtrace();
|
|
|
|
foreach ($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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
printf("%d: %s::%s(%s)" . PHP_EOL
|
|
|
|
, $stack['line']
|
|
|
|
, isset($stack['class']) ? $stack['class'] : ''
|
|
|
|
, $stack['function']
|
|
|
|
, implode(', ', $args)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
|
|
|
|
function str($code, $indent = '') // {{{
|
|
|
|
{
|
|
|
|
if (is_array($code)) {
|
|
|
|
$array = array();
|
|
|
|
foreach ($code as $key => $value) {
|
|
|
|
$array[$key] = str($value, $indent);
|
|
|
|
}
|
|
|
|
return $array;
|
|
|
|
}
|
|
|
|
if (is_object($code)) {
|
|
|
|
$code = foldToCode($code, $indent);
|
|
|
|
return $code->toCode($indent);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (string) $code;
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
function unsetArray(&$array, $name) // {{{
|
|
|
|
{
|
|
|
|
unset($array[$name]);
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
|
|
|
|
function foldToCode($src, $indent = '') // {{{ wrap or rewrap anything to Decompiler_Code
|
|
|
|
{
|
|
|
|
if (is_array($indent)) {
|
|
|
|
$indent = $indent['indent'];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!is_object($src)) {
|
|
|
|
return new Decompiler_Code($src);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!method_exists($src, 'toCode')) {
|
|
|
|
var_dump($src);
|
|
|
|
exit('no toCode');
|
|
|
|
}
|
|
|
|
if (get_class($src) != 'Decompiler_Code') {
|
|
|
|
// rewrap it
|
|
|
|
$src = new Decompiler_Code($src->toCode($indent));
|
|
|
|
}
|
|
|
|
|
|
|
|
return $src;
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
function value($value, &$EX) // {{{
|
|
|
|
{
|
|
|
|
$spec = xcache_get_special_value($value);
|
|
|
|
if (isset($spec)) {
|
|
|
|
$value = $spec;
|
|
|
|
if (!is_array($value)) {
|
|
|
|
// constant
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_a($value, 'Decompiler_Object')) {
|
|
|
|
// use as is
|
|
|
|
}
|
|
|
|
else if (is_array($value)) {
|
|
|
|
$value = new Decompiler_ConstArray($value, $EX);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (isset($EX['value2constant'][$value])) {
|
|
|
|
$value = new Decompiler_Code($EX['value2constant'][$value]);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$value = new Decompiler_Value($value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
function unquoteName_($str, $asVariableName, $indent = '') // {{{
|
|
|
|
{
|
|
|
|
$str = str($str, $indent);
|
|
|
|
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 // {{{
|
|
|
|
{
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
class Decompiler_Value extends Decompiler_Object // {{{
|
|
|
|
{
|
|
|
|
public $value;
|
|
|
|
|
|
|
|
function Decompiler_Value($value = null)
|
|
|
|
{
|
|
|
|
$this->value = $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
function toCode($indent)
|
|
|
|
{
|
|
|
|
$code = var_export($this->value, true);
|
|
|
|
if (gettype($this->value) == 'string') {
|
|
|
|
switch ($this->value) {
|
|
|
|
case "\r":
|
|
|
|
return '"\\r"';
|
|
|
|
case "\n":
|
|
|
|
return '"\\n"';
|
|
|
|
case "\r\n":
|
|
|
|
return '"\\r\\n"';
|
|
|
|
}
|
|
|
|
$code = str_replace("\r\n", '\' . "\\r\\n" . \'', $code);
|
|
|
|
$code = str_replace("\r", '\' . "\\r" . \'', $code);
|
|
|
|
$code = str_replace("\n", '\' . "\\n" . \'', $code);
|
|
|
|
}
|
|
|
|
return $code;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
class Decompiler_Code extends Decompiler_Object // {{{
|
|
|
|
{
|
|
|
|
public $src;
|
|
|
|
|
|
|
|
function Decompiler_Code($src)
|
|
|
|
{
|
|
|
|
assert('isset($src)');
|
|
|
|
$this->src = $src;
|
|
|
|
}
|
|
|
|
|
|
|
|
function toCode($indent)
|
|
|
|
{
|
|
|
|
return $this->src;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
class Decompiler_Binop extends Decompiler_Code // {{{
|
|
|
|
{
|
|
|
|
public $opc;
|
|
|
|
public $op1;
|
|
|
|
public $op2;
|
|
|
|
public $parent;
|
|
|
|
|
|
|
|
function Decompiler_Binop($parent, $op1, $opc, $op2)
|
|
|
|
{
|
|
|
|
$this->parent = &$parent;
|
|
|
|
$this->opc = $opc;
|
|
|
|
$this->op1 = $op1;
|
|
|
|
$this->op2 = $op2;
|
|
|
|
}
|
|
|
|
|
|
|
|
function toCode($indent)
|
|
|
|
{
|
|
|
|
$opstr = $this->parent->binops[$this->opc];
|
|
|
|
|
|
|
|
if (is_a($this->op1, 'Decompiler_TriOp') || is_a($this->op1, 'Decompiler_Binop') && $this->op1->opc != $this->opc) {
|
|
|
|
$op1 = "(" . str($this->op1, $indent) . ")";
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$op1 = $this->op1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_a($this->op2, 'Decompiler_TriOp') || is_a($this->op2, 'Decompiler_Binop') && $this->op2->opc != $this->opc && substr($opstr, -1) != '=') {
|
|
|
|
$op2 = "(" . str($this->op2, $indent) . ")";
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$op2 = $this->op2;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (str($op1) == '0' && ($this->opc == XC_ADD || $this->opc == XC_SUB)) {
|
|
|
|
return $opstr . str($op2, $indent);
|
|
|
|
}
|
|
|
|
|
|
|
|
return str($op1, $indent) . ' ' . $opstr . ($this->opc == XC_ASSIGN_REF ? '' : ' ') . str($op2, $indent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
class Decompiler_TriOp extends Decompiler_Code // {{{
|
|
|
|
{
|
|
|
|
public $condition;
|
|
|
|
public $trueValue;
|
|
|
|
public $falseValue;
|
|
|
|
|
|
|
|
function Decompiler_TriOp($condition, $trueValue, $falseValue)
|
|
|
|
{
|
|
|
|
$this->condition = $condition;
|
|
|
|
$this->trueValue = $trueValue;
|
|
|
|
$this->falseValue = $falseValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
function toCode($indent)
|
|
|
|
{
|
|
|
|
$trueValue = $this->trueValue;
|
|
|
|
if (is_a($this->trueValue, 'Decompiler_TriOp')) {
|
|
|
|
$trueValue = "(" . str($trueValue, $indent) . ")";
|
|
|
|
}
|
|
|
|
$falseValue = $this->falseValue;
|
|
|
|
if (is_a($this->falseValue, 'Decompiler_TriOp')) {
|
|
|
|
$falseValue = "(" . str($falseValue, $indent) . ")";
|
|
|
|
}
|
|
|
|
|
|
|
|
return str($this->condition) . ' ? ' . str($trueValue) . ' : ' . str($falseValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
class Decompiler_Fetch extends Decompiler_Code // {{{
|
|
|
|
{
|
|
|
|
public $src;
|
|
|
|
public $fetchType;
|
|
|
|
|
|
|
|
function Decompiler_Fetch($src, $type, $globalsrc)
|
|
|
|
{
|
|
|
|
$this->src = $src;
|
|
|
|
$this->fetchType = $type;
|
|
|
|
$this->globalsrc = $globalsrc;
|
|
|
|
}
|
|
|
|
|
|
|
|
function toCode($indent)
|
|
|
|
{
|
|
|
|
switch ($this->fetchType) {
|
|
|
|
case ZEND_FETCH_LOCAL:
|
|
|
|
return '$' . substr($this->src, 1, -1);
|
|
|
|
case ZEND_FETCH_STATIC:
|
|
|
|
if (ZEND_ENGINE_2_3) {
|
|
|
|
// closure local variable?
|
|
|
|
return str($this->src);
|
|
|
|
}
|
|
|
|
die('static fetch cant to string');
|
|
|
|
case ZEND_FETCH_GLOBAL:
|
|
|
|
case ZEND_FETCH_GLOBAL_LOCK:
|
|
|
|
return $this->globalsrc;
|
|
|
|
default:
|
|
|
|
var_dump($this->fetchType);
|
|
|
|
assert(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
class Decompiler_Box // {{{
|
|
|
|
{
|
|
|
|
public $obj;
|
|
|
|
|
|
|
|
function Decompiler_Box(&$obj)
|
|
|
|
{
|
|
|
|
$this->obj = &$obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
function toCode($indent)
|
|
|
|
{
|
|
|
|
return $this->obj->toCode($indent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
class Decompiler_Dim extends Decompiler_Value // {{{
|
|
|
|
{
|
|
|
|
public $offsets = array();
|
|
|
|
public $isLast = false;
|
|
|
|
public $isObject = false;
|
|
|
|
public $assign = null;
|
|
|
|
|
|
|
|
function toCode($indent)
|
|
|
|
{
|
|
|
|
if (is_a($this->value, 'Decompiler_ListBox')) {
|
|
|
|
$exp = str($this->value->obj->src, $indent);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$exp = str($this->value, $indent);
|
|
|
|
}
|
|
|
|
$last = count($this->offsets) - 1;
|
|
|
|
foreach ($this->offsets as $i => $dim) {
|
|
|
|
if ($this->isObject && $i == $last) {
|
|
|
|
$exp .= '->' . unquoteVariableName($dim, $indent);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$exp .= '[' . str($dim, $indent) . ']';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $exp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
class Decompiler_DimBox extends Decompiler_Box // {{{
|
|
|
|
{
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
class Decompiler_List extends Decompiler_Code // {{{
|
|
|
|
{
|
|
|
|
public $src;
|
|
|
|
public $dims = array();
|
|
|
|
public $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 str($dim, $indent);
|
|
|
|
}
|
|
|
|
return str($this->dims[0]->assign, $indent) . ' = ' . str($dim, $indent);
|
|
|
|
}
|
|
|
|
/* flatten dims */
|
|
|
|
$assigns = array();
|
|
|
|
foreach ($this->dims as $dim) {
|
|
|
|
$assign = &$assigns;
|
|
|
|
foreach ($dim->offsets as $offset) {
|
|
|
|
$assign = &$assign[$offset];
|
|
|
|
}
|
|
|
|
$assign = foldToCode($dim->assign, $indent);
|
|
|
|
}
|
|
|
|
return str($this->toList($assigns)) . ' = ' . str($this->src, $indent);
|
|
|
|
}
|
|
|
|
|
|
|
|
function toList($assigns)
|
|
|
|
{
|
|
|
|
$keys = array_keys($assigns);
|
|
|
|
if (count($keys) < 2) {
|
|
|
|
$keys[] = 0;
|
|
|
|
}
|
|
|
|
$max = call_user_func_array('max', $keys);
|
|
|
|
$list = 'list(';
|
|
|
|
for ($i = 0; $i <= $max; $i ++) {
|
|
|
|
if ($i) {
|
|
|
|
$list .= ', ';
|
|
|
|
}
|
|
|
|
if (!isset($assigns[$i])) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (is_array($assigns[$i])) {
|
|
|
|
$list .= $this->toList($assigns[$i]);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$list .= $assigns[$i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $list . ')';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
class Decompiler_ListBox extends Decompiler_Box // {{{
|
|
|
|
{
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
class Decompiler_Array extends Decompiler_Value // {{{
|
|
|
|
{
|
|
|
|
// emenets
|
|
|
|
function Decompiler_Array()
|
|
|
|
{
|
|
|
|
$this->value = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
function toCode($indent)
|
|
|
|
{
|
|
|
|
$subindent = $indent . INDENT;
|
|
|
|
|
|
|
|
$elementsCode = array();
|
|
|
|
$index = 0;
|
|
|
|
foreach ($this->value as $element) {
|
|
|
|
list($key, $value) = $element;
|
|
|
|
if (!isset($key)) {
|
|
|
|
$key = $index++;
|
|
|
|
}
|
|
|
|
$elementsCode[] = array(str($key, $subindent), str($value, $subindent), $key, $value);
|
|
|
|
}
|
|
|
|
|
|
|
|
$exp = "array(";
|
|
|
|
$indent = $indent . INDENT;
|
|
|
|
$assocWidth = 0;
|
|
|
|
$multiline = 0;
|
|
|
|
$i = 0;
|
|
|
|
foreach ($elementsCode as $element) {
|
|
|
|
list($keyCode, $valueCode) = $element;
|
|
|
|
if ((string) $i !== $keyCode) {
|
|
|
|
$assocWidth = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
++$i;
|
|
|
|
}
|
|
|
|
foreach ($elementsCode as $element) {
|
|
|
|
list($keyCode, $valueCode, $key, $value) = $element;
|
|
|
|
if ($assocWidth) {
|
|
|
|
$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) = $element;
|
|
|
|
if ($multiline) {
|
|
|
|
if ($i) {
|
|
|
|
$exp .= ",";
|
|
|
|
}
|
|
|
|
$exp .= "\n";
|
|
|
|
$exp .= $indent;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if ($i) {
|
|
|
|
$exp .= ", ";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($assocWidth) {
|
|
|
|
if ($multiline) {
|
|
|
|
$exp .= sprintf("%-{$assocWidth}s => ", $keyCode);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$exp .= $keyCode . ' => ';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$exp .= $value;
|
|
|
|
|
|
|
|
$i ++;
|
|
|
|
}
|
|
|
|
if ($multiline) {
|
|
|
|
$exp .= "\n$indent)";
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$exp .= ")";
|
|
|
|
}
|
|
|
|
return $exp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
class Decompiler_ConstArray extends Decompiler_Array // {{{
|
|
|
|
{
|
|
|
|
function Decompiler_ConstArray($array, &$EX)
|
|
|
|
{
|
|
|
|
$elements = array();
|
|
|
|
foreach ($array as $key => $value) {
|
|
|
|
$elements[] = array(value($key, $EX), value($value, $EX));
|
|
|
|
}
|
|
|
|
$this->value = $elements;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
class Decompiler_ForeachBox extends Decompiler_Box // {{{
|
|
|
|
{
|
|
|
|
public $iskey;
|
|
|
|
|
|
|
|
function toCode($indent)
|
|
|
|
{
|
|
|
|
return 'foreach (' . '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
|
|
|
|
class Decompiler
|
|
|
|
{
|
|
|
|
public $namespace;
|
|
|
|
public $namespaceDecided;
|
|
|
|
public $activeFile;
|
|
|
|
public $activeClass;
|
|
|
|
public $activeMethod;
|
|
|
|
public $activeFunction;
|
|
|
|
|
|
|
|
function Decompiler()
|
|
|
|
{
|
|
|
|
// {{{ 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, "\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
// {{{ opinfo
|
|
|
|
$this->unaryops = array(
|
|
|
|
XC_BW_NOT => '~',
|
|
|
|
XC_BOOL_NOT => '!',
|
|
|
|
);
|
|
|
|
$this->binops = 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_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_REF => "= &",
|
|
|
|
XC_JMP_SET => "?:",
|
|
|
|
XC_JMP_SET_VAR => "?:",
|
|
|
|
XC_JMPZ_EX => "&&",
|
|
|
|
XC_JMPNZ_EX => "||",
|
|
|
|
);
|
|
|
|
// }}}
|
|
|
|
$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) {
|
|
|
|
$this->namespace = strtok($name, '\\');
|
|
|
|
echo 'namespace ', $this->namespace, ";\n\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->namespaceDecided = true;
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
function stripNamespace($name) // {{{
|
|
|
|
{
|
|
|
|
$name = str($name);
|
|
|
|
$len = strlen($this->namespace) + 1;
|
|
|
|
if (substr($name, 0, $len) == $this->namespace . '\\') {
|
|
|
|
return substr($name, $len);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return $name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
function outputPhp(&$EX, $range) // {{{
|
|
|
|
{
|
|
|
|
$needBlankline = isset($EX['lastBlock']);
|
|
|
|
$indent = $EX['indent'];
|
|
|
|
$curticks = 0;
|
|
|
|
for ($i = $range[0]; $i <= $range[1]; $i ++) {
|
|
|
|
$op = $EX['opcodes'][$i];
|
|
|
|
if (isset($op['gofrom'])) {
|
|
|
|
if ($needBlankline) {
|
|
|
|
$needBlankline = false;
|
|
|
|
echo PHP_EOL;
|
|
|
|
}
|
|
|
|
echo 'label' . $i, ":\n";
|
|
|
|
}
|
|
|
|
if (isset($op['php'])) {
|
|
|
|
$toticks = isset($op['ticks']) ? (int) str($op['ticks']) : 0;
|
|
|
|
if ($curticks != $toticks) {
|
|
|
|
$oldticks = $curticks;
|
|
|
|
$curticks = $toticks;
|
|
|
|
if (!$curticks) {
|
|
|
|
echo $EX['indent'], "}\n\n";
|
|
|
|
$indent = $EX['indent'];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if ($oldticks) {
|
|
|
|
echo $EX['indent'], "}\n\n";
|
|
|
|
}
|
|
|
|
else if (!$oldticks) {
|
|
|
|
$indent .= INDENT;
|
|
|
|
}
|
|
|
|
if ($needBlankline) {
|
|
|
|
$needBlankline = false;
|
|
|
|
echo PHP_EOL;
|
|
|
|
}
|
|
|
|
echo $EX['indent'], "declare (ticks=$curticks) {\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($needBlankline) {
|
|
|
|
$needBlankline = false;
|
|
|
|
echo PHP_EOL;
|
|
|
|
}
|
|
|
|
echo $indent, str($op['php'], $indent), ";\n";
|
|
|
|
$EX['lastBlock'] = 'basic';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($curticks) {
|
|
|
|
echo $EX['indent'], "}\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
function getOpVal($op, &$EX, $free = false) // {{{
|
|
|
|
{
|
|
|
|
switch ($op['op_type']) {
|
|
|
|
case XC_IS_CONST:
|
|
|
|
return value($op['constant'], $EX);
|
|
|
|
|
|
|
|
case XC_IS_VAR:
|
|
|
|
case XC_IS_TMP_VAR:
|
|
|
|
$T = &$EX['Ts'];
|
|
|
|
if (!isset($T[$op['var']])) {
|
|
|
|
printBacktrace();
|
|
|
|
}
|
|
|
|
$ret = $T[$op['var']];
|
|
|
|
if ($free && empty($this->keepTs)) {
|
|
|
|
unset($T[$op['var']]);
|
|
|
|
}
|
|
|
|
return $ret;
|
|
|
|
|
|
|
|
case XC_IS_CV:
|
|
|
|
$var = $op['var'];
|
|
|
|
$var = $EX['op_array']['vars'][$var];
|
|
|
|
return '$' . $var['name'];
|
|
|
|
|
|
|
|
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) {
|
|
|
|
$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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $opcodes;
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
function decompileBasicBlock(&$EX, $range, $unhandled = false) // {{{
|
|
|
|
{
|
|
|
|
$this->dasmBasicBlock($EX, $range);
|
|
|
|
if ($unhandled) {
|
|
|
|
$this->dumpRange($EX, $range);
|
|
|
|
}
|
|
|
|
$this->outputPhp($EX, $range);
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
function isIfCondition(&$EX, $range) // {{{
|
|
|
|
{
|
|
|
|
$opcodes = &$EX['opcodes'];
|
|
|
|
$firstOp = &$opcodes[$range[0]];
|
|
|
|
return $firstOp['opcode'] == XC_JMPZ && !empty($firstOp['jmpouts']) && $opcodes[$firstOp['jmpouts'][0] - 1]['opcode'] == XC_JMP
|
|
|
|
&& !empty($opcodes[$firstOp['jmpouts'][0] - 1]['jmpouts'])
|
|
|
|
&& $opcodes[$firstOp['jmpouts'][0] - 1]['jmpouts'][0] == $range[1] + 1;
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
function removeJmpInfo(&$EX, $line) // {{{
|
|
|
|
{
|
|
|
|
$opcodes = &$EX['opcodes'];
|
|
|
|
if (!isset($opcodes[$line]['jmpouts'])) {
|
|
|
|
printBacktrace();
|
|
|
|
}
|
|
|
|
foreach ($opcodes[$line]['jmpouts'] as $jmpTo) {
|
|
|
|
$jmpins = &$opcodes[$jmpTo]['jmpins'];
|
|
|
|
$jmpins = array_flip($jmpins);
|
|
|
|
unset($jmpins[$line]);
|
|
|
|
$jmpins = array_keys($jmpins);
|
|
|
|
}
|
|
|
|
// $opcodes[$line]['opcode'] = XC_NOP;
|
|
|
|
unset($opcodes[$line]['jmpouts']);
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
function beginScope(&$EX, $doIndent = true) // {{{
|
|
|
|
{
|
|
|
|
array_push($EX['scopeStack'], array($EX['lastBlock'], $EX['indent']));
|
|
|
|
if ($doIndent) {
|
|
|
|
$EX['indent'] .= INDENT;
|
|
|
|
}
|
|
|
|
$EX['lastBlock'] = null;
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
function endScope(&$EX) // {{{
|
|
|
|
{
|
|
|
|
list($EX['lastBlock'], $EX['indent']) = array_pop($EX['scopeStack']);
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
function beginComplexBlock(&$EX) // {{{
|
|
|
|
{
|
|
|
|
if (isset($EX['lastBlock'])) {
|
|
|
|
echo PHP_EOL;
|
|
|
|
$EX['lastBlock'] = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
function endComplexBlock(&$EX) // {{{
|
|
|
|
{
|
|
|
|
$EX['lastBlock'] = 'complex';
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
function decompileComplexBlock(&$EX, $range) // {{{
|
|
|
|
{
|
|
|
|
$T = &$EX['Ts'];
|
|
|
|
$opcodes = &$EX['opcodes'];
|
|
|
|
$indent = $EX['indent'];
|
|
|
|
|
|
|
|
$firstOp = &$opcodes[$range[0]];
|
|
|
|
$lastOp = &$opcodes[$range[1]];
|
|
|
|
|
|
|
|
// {{{ && || and or
|
|
|
|
if (($firstOp['opcode'] == XC_JMPZ_EX || $firstOp['opcode'] == XC_JMPNZ_EX) && !empty($firstOp['jmpouts'])
|
|
|
|
&& $firstOp['jmpouts'][0] == $range[1] + 1
|
|
|
|
&& $lastOp['opcode'] == XC_BOOL
|
|
|
|
&& $firstOp['opcode']['result']['var'] == $lastOp['opcode']['result']['var']
|
|
|
|
) {
|
|
|
|
$this->removeJmpInfo($EX, $range[0]);
|
|
|
|
|
|
|
|
$this->recognizeAndDecompileClosedBlocks($EX, array($range[0], $range[0]));
|
|
|
|
$op1 = $this->getOpVal($firstOp['result'], $EX, true);
|
|
|
|
|
|
|
|
$this->recognizeAndDecompileClosedBlocks($EX, array($range[0] + 1, $range[1]));
|
|
|
|
$op2 = $this->getOpVal($lastOp['result'], $EX, true);
|
|
|
|
|
|
|
|
$T[$firstOp['result']['var']] = new Decompiler_Binop($this, $op1, $firstOp['opcode'], $op2);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
// {{{ ?: excluding JMP_SET/JMP_SET_VAR
|
|
|
|
if ($firstOp['opcode'] == XC_JMPZ && !empty($firstOp['jmpouts'])
|
|
|
|
&& $range[1] >= $range[0] + 3
|
|
|
|
&& ($opcodes[$firstOp['jmpouts'][0] - 2]['opcode'] == XC_QM_ASSIGN || $opcodes[$firstOp['jmpouts'][0] - 2]['opcode'] == XC_QM_ASSIGN_VAR)
|
|
|
|
&& $opcodes[$firstOp['jmpouts'][0] - 1]['opcode'] == XC_JMP && $opcodes[$firstOp['jmpouts'][0] - 1]['jmpouts'][0] == $range[1] + 1
|
|
|
|
&& ($lastOp['opcode'] == XC_QM_ASSIGN || $lastOp['opcode'] == XC_QM_ASSIGN_VAR)
|
|
|
|
) {
|
|
|
|
$trueRange = array($range[0] + 1, $firstOp['jmpouts'][0] - 2);
|
|
|
|
$falseRange = array($firstOp['jmpouts'][0], $range[1]);
|
|
|
|
$this->removeJmpInfo($EX, $range[0]);
|
|
|
|
|
|
|
|
$condition = $this->getOpVal($firstOp['op1'], $EX);
|
|
|
|
$this->recognizeAndDecompileClosedBlocks($EX, $trueRange);
|
|
|
|
$trueValue = $this->getOpVal($opcodes[$trueRange[1]]['result'], $EX, true);
|
|
|
|
$this->recognizeAndDecompileClosedBlocks($EX, $falseRange);
|
|
|
|
$falseValue = $this->getOpVal($opcodes[$falseRange[1]]['result'], $EX, true);
|
|
|
|
$T[$opcodes[$trueRange[1]]['result']['var']] = new Decompiler_TriOp($condition, $trueValue, $falseValue);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
// {{{ goto (TODO: recognize XC_BRK)
|
|
|
|
if ($firstOp['opcode'] == XC_JMP && !empty($firstOp['jmpouts']) && $firstOp['jmpouts'][0] == $range[1] + 1) {
|
|
|
|
$this->removeJmpInfo($EX, $range[0]);
|
|
|
|
assert(XC_GOTO != -1);
|
|
|
|
$firstOp['opcode'] = XC_GOTO;
|
|
|
|
$target = $firstOp['op1']['var'];
|
|
|
|
$firstOp['goto'] = $target;
|
|
|
|
$opcodes[$target]['gofrom'][] = $range[0];
|
|
|
|
|
|
|
|
$this->recognizeAndDecompileClosedBlocks($EX, $range);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
// {{{ for
|
|
|
|
if (!empty($firstOp['jmpins']) && $opcodes[$firstOp['jmpins'][0]]['opcode'] == XC_JMP
|
|
|
|
&& $lastOp['opcode'] == XC_JMP && !empty($lastOp['jmpouts']) && $lastOp['jmpouts'][0] <= $firstOp['jmpins'][0]
|
|
|
|
&& !empty($opcodes[$range[1] + 1]['jmpins']) && $opcodes[$opcodes[$range[1] + 1]['jmpins'][0]]['opcode'] == XC_JMPZNZ
|
|
|
|
) {
|
|
|
|
$nextRange = array($lastOp['jmpouts'][0], $firstOp['jmpins'][0]);
|
|
|
|
$conditionRange = array($range[0], $nextRange[0] - 1);
|
|
|
|
$this->removeJmpInfo($EX, $conditionRange[1]);
|
|
|
|
$bodyRange = array($nextRange[1], $range[1]);
|
|
|
|
$this->removeJmpInfo($EX, $bodyRange[1]);
|
|
|
|
|
|
|
|
$initial = '';
|
|
|
|
$this->beginScope($EX);
|
|
|
|
$this->dasmBasicBlock($EX, $conditionRange);
|
|
|
|
$conditionCodes = array();
|
|
|
|
for ($i = $conditionRange[0]; $i <= $conditionRange[1]; ++$i) {
|
|
|
|
if (isset($opcodes[$i]['php'])) {
|
|
|
|
$conditionCodes[] = str($opcodes[$i]['php'], $EX);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$conditionCodes[] = str($this->getOpVal($opcodes[$conditionRange[1]]['op1'], $EX), $EX);
|
|
|
|
if (implode(',', $conditionCodes) == 'true') {
|
|
|
|
$conditionCodes = array();
|
|
|
|
}
|
|
|
|
$this->endScope($EX);
|
|
|
|
|
|
|
|
$this->beginScope($EX);
|
|
|
|
$this->dasmBasicBlock($EX, $nextRange);
|
|
|
|
$nextCodes = array();
|
|
|
|
for ($i = $nextRange[0]; $i <= $nextRange[1]; ++$i) {
|
|
|
|
if (isset($opcodes[$i]['php'])) {
|
|
|
|
$nextCodes[] = str($opcodes[$i]['php'], $EX);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$this->endScope($EX);
|
|
|
|
|
|
|
|
$this->beginComplexBlock($EX);
|
|
|
|
echo $indent, 'for (', str($initial, $EX), '; ', implode(', ', $conditionCodes), '; ', implode(', ', $nextCodes), ') ', '{', PHP_EOL;
|
|
|
|
$this->beginScope($EX);
|
|
|
|
$this->recognizeAndDecompileClosedBlocks($EX, $bodyRange);
|
|
|
|
$this->endScope($EX);
|
|
|
|
echo $indent, '}', PHP_EOL;
|
|
|
|
$this->endComplexBlock($EX);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
// {{{ if/elseif/else
|
|
|
|
if ($this->isIfCondition($EX, $range)) {
|
|
|
|
$this->beginComplexBlock($EX);
|
|
|
|
$isElseIf = false;
|
|
|
|
do {
|
|
|
|
$ifRange = array($range[0], $opcodes[$range[0]]['jmpouts'][0] - 1);
|
|
|
|
$this->removeJmpInfo($EX, $ifRange[0]);
|
|
|
|
$this->removeJmpInfo($EX, $ifRange[1]);
|
|
|
|
$condition = $this->getOpVal($opcodes[$ifRange[0]]['op1'], $EX);
|
|
|
|
|
|
|
|
echo $indent, $isElseIf ? 'else if' : 'if', ' (', str($condition, $EX), ') ', '{', PHP_EOL;
|
|
|
|
$this->beginScope($EX);
|
|
|
|
$this->recognizeAndDecompileClosedBlocks($EX, $ifRange);
|
|
|
|
$this->endScope($EX);
|
|
|
|
$EX['lastBlock'] = null;
|
|
|
|
echo $indent, '}', PHP_EOL;
|
|
|
|
|
|
|
|
$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]['jmpouts'])) {
|
|
|
|
if ($this->isIfCondition($EX, array($i, $range[1]))) {
|
|
|
|
$this->dasmBasicBlock($EX, array($range[0], $i));
|
|
|
|
$range[0] = $i;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while ($this->isIfCondition($EX, $range));
|
|
|
|
if ($ifRange[1] < $range[1]) {
|
|
|
|
$elseRange = array($ifRange[1], $range[1]);
|
|
|
|
echo $indent, 'else ', '{', PHP_EOL;
|
|
|
|
$this->beginScope($EX);
|
|
|
|
$this->recognizeAndDecompileClosedBlocks($EX, $elseRange);
|
|
|
|
$this->endScope($EX);
|
|
|
|
$EX['lastBlock'] = null;
|
|
|
|
echo $indent, '}', PHP_EOL;
|
|
|
|
}
|
|
|
|
$this->endComplexBlock($EX);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ($firstOp['opcode'] == XC_JMPZ && !empty($firstOp['jmpouts'])
|
|
|
|
&& $firstOp['jmpouts'][0] - 1 == $range[1] && $opcodes[$firstOp['jmpouts'][0] - 1]['opcode'] == XC_RETURN) {
|
|
|
|
$this->beginComplexBlock($EX);
|
|
|
|
$this->removeJmpInfo($EX, $range[0]);
|
|
|
|
$condition = $this->getOpVal($opcodes[$range[0]]['op1'], $EX);
|
|
|
|
|
|
|
|
echo $indent, 'if (', str($condition, $EX), ') ', '{', PHP_EOL;
|
|
|
|
$this->beginScope($EX);
|
|
|
|
$this->recognizeAndDecompileClosedBlocks($EX, $range);
|
|
|
|
$this->endScope($EX);
|
|
|
|
echo $indent, '}', PHP_EOL;
|
|
|
|
$this->endComplexBlock($EX);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
// {{{ try/catch
|
|
|
|
if (!empty($firstOp['jmpins']) && !empty($opcodes[$firstOp['jmpins'][0]]['isCatchBegin'])) {
|
|
|
|
$catchBlocks = array();
|
|
|
|
$catchFirst = $firstOp['jmpins'][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($EX, $catchOpLine - 1);
|
|
|
|
|
|
|
|
$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->beginComplexBlock($EX);
|
|
|
|
echo $indent, "try {", PHP_EOL;
|
|
|
|
$this->beginScope($EX);
|
|
|
|
$this->recognizeAndDecompileClosedBlocks($EX, $tryRange);
|
|
|
|
$this->endScope($EX);
|
|
|
|
echo $indent, '}', PHP_EOL;
|
|
|
|
if (!$catchBlocks) {
|
|
|
|
printBacktrace();
|
|
|
|
assert($catchBlocks);
|
|
|
|
}
|
|
|
|
foreach ($catchBlocks as $catchFirst => $catchInfo) {
|
|
|
|
list($catchOpLine, $catchBodyLast) = $catchInfo;
|
|
|
|
$catchBodyFirst = $catchOpLine + 1;
|
|
|
|
$this->dasmBasicBlock($EX, array($catchFirst, $catchOpLine));
|
|
|
|
$catchOp = &$opcodes[$catchOpLine];
|
|
|
|
echo $indent, 'catch ('
|
|
|
|
, $this->stripNamespace(isset($catchOp['op1']['constant']) ? $catchOp['op1']['constant'] : str($this->getOpVal($catchOp['op1'], $EX)))
|
|
|
|
, ' '
|
|
|
|
, isset($catchOp['op2']['constant']) ? '$' . $catchOp['op2']['constant'] : str($this->getOpVal($catchOp['op2'], $EX))
|
|
|
|
, ") {", PHP_EOL;
|
|
|
|
unset($catchOp);
|
|
|
|
|
|
|
|
$EX['lastBlock'] = null;
|
|
|
|
$this->beginScope($EX);
|
|
|
|
$this->recognizeAndDecompileClosedBlocks($EX, array($catchBodyFirst, $catchBodyLast));
|
|
|
|
$this->endScope($EX);
|
|
|
|
echo $indent, '}', PHP_EOL;
|
|
|
|
}
|
|
|
|
$this->endComplexBlock($EX);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
// {{{ switch/case
|
|
|
|
if ($firstOp['opcode'] == XC_SWITCH_FREE && isset($T[$firstOp['op1']['var']])) {
|
|
|
|
// TODO: merge this code to CASE code. use SWITCH_FREE to detect begin of switch by using $Ts if possible
|
|
|
|
$this->beginComplexBlock($EX);
|
|
|
|
echo $indent, 'switch (', str($this->getOpVal($firstOp['op1'], $EX)), ") {", PHP_EOL;
|
|
|
|
echo $indent, '}', PHP_EOL;
|
|
|
|
$this->endComplexBlock($EX);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
($firstOp['opcode'] == XC_CASE
|
|
|
|
|| $firstOp['opcode'] == XC_JMP && !empty($firstOp['jmpouts']) && $opcodes[$firstOp['jmpouts'][0]]['opcode'] == XC_CASE
|
|