1
0
Fork 0
XCache is a fast, stable PHP opcode cacher that has been proven and is now running on production servers under high load. https://xcache.lighttpd.net/
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.

2617 lines
64 KiB

<?php
define('INDENT', "\t");
ini_set('error_reporting', E_ALL);
function color($str, $color = 33)
{
return "\x1B[{$color}m$str\x1B[0m";
}
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 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) // {{{
{
$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);
}
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 // {{{
{
var $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 // {{{
{
var $src;
function Decompiler_Code($src)
{
assert('isset($src)');
$this->src = $src;
}
function toCode($indent)
{
return $this->src;
}
}
// }}}
class Decompiler_Binop extends Decompiler_Code // {{{
{
var $opc;
var $op1;
var $op2;
var $parent;
var $indent;
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];
$op1 = foldToCode($this->op1, $indent);
if (is_a($this->op1, 'Decompiler_Binop') && $this->op1->opc != $this->opc) {
$op1 = "(" . str($op1, $indent) . ")";
}
$op2 = foldToCode($this->op2, $indent);
if (is_a($this->op2, 'Decompiler_Binop') && $this->op2->opc != $this->opc && substr($opstr, -1) != '=') {
$op2 = "(" . str($op2, $indent) . ")";
}
if (str($op1) == '0' && ($this->opc == XC_ADD || $this->opc == XC_SUB)) {
return $opstr . str($op2, $indent);
}
return str($op1) . ' ' . $opstr . ' ' . str($op2);
}
}
// }}}
class Decompiler_Fetch extends Decompiler_Code // {{{
{
var $src;
var $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 // {{{
{
var $obj;
function Decompiler_Box(&$obj)
{
$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 = 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 // {{{
{
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 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)
{
$elements = array();
foreach ($array as $key => $value) {
$elements[] = array(value($key), value($value));
}
$this->value = $elements;
}
}
// }}}
class Decompiler_ForeachBox extends Decompiler_Box // {{{
{
var $iskey;
function toCode($indent)
{
return 'foreach (' . '';
}
}
// }}}
class Decompiler
{
var $namespace;
var $namespaceDecided;
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",
);
// }}}
$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) // {{{
{
$len = strlen($this->namespace) + 1;
if (substr($name, 0, $len) == $this->namespace . '\\') {
return substr($name, $len);
}
else {
return $name;
}
}
// }}}
function outputPhp(&$EX, $opline, $last, $indent) // {{{
{
$needBlankline = isset($EX['lastBlock']);
$origindent = $indent;
$curticks = 0;
for ($i = $opline; $i <= $last; $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 $origindent, "}\n\n";
$indent = $origindent;
}
else {
if ($oldticks) {
echo $origindent, "}\n\n";
}
else if (!$oldticks) {
$indent .= INDENT;
}
if ($needBlankline) {
$needBlankline = false;
echo PHP_EOL;
}
echo $origindent, "declare (ticks=$curticks) {\n";
}
}
if ($needBlankline) {
$needBlankline = false;
echo PHP_EOL;
}
echo $indent, str($op['php'], $indent), ";\n";
$EX['lastBlock'] = 'basic';
}
}
if ($curticks) {
echo $origindent, "}\n";
}
}
// }}}
function getOpVal($op, &$EX, $tostr = true, $free = false) // {{{
{
switch ($op['op_type']) {
case XC_IS_CONST:
return foldToCode(value($op['constant']), $EX);
case XC_IS_VAR:
case XC_IS_TMP_VAR:
$T = &$EX['Ts'];
$ret = $T[$op['var']];
if ($tostr) {
$ret = foldToCode($ret, $EX);
}
if ($free) {
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) // {{{
{
for ($i = 0, $cnt = count($opcodes); $i < $cnt; $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(),
'op3' => 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, $first, $last, $indent) // {{{
{
$this->dasmBasicBlock($EX, $first, $last);
// $this->dumpRange($EX, $first, $last, $indent);
$this->outputPhp($EX, $first, $last, $indent);
}
// }}}
function removeJmpInfo(&$EX, $line) // {{{
{
$opcodes = &$EX['opcodes'];
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 beginComplexBlock(&$EX) // {{{
{
if (isset($EX['lastBlock'])) {
echo PHP_EOL;
$EX['lastBlock'] = null;
}
}
// }}}
function endComplexBlock(&$EX) // {{{
{
$EX['lastBlock'] = 'complex';
}
// }}}
function decompileComplexBlock(&$EX, $first, $last, $indent) // {{{
{
$T = &$EX['Ts'];
$opcodes = &$EX['opcodes'];
$firstOp = &$opcodes[$first];
$lastOp = &$opcodes[$last];
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
)
&& !empty($lastOp['jmpouts'])
) {
$cases = array();
$caseNext = null;
$caseDefault = null;
$caseOp = null;
for ($i = $first; $i <= $last; ++$i) {
$op = $opcodes[$i];
if ($op['opcode'] == XC_CASE) {
if (!isset($caseOp)) {
$caseOp = $op;
}
$jmpz = $opcodes[$i + 1];
assert('$jmpz["opcode"] == XC_JMPZ');
$caseNext = $jmpz['jmpouts'][0];
$i = $cases[$i] = $caseNext - 1;
}
else if ($op['opcode'] == XC_JMP) {
// default
if ($op['jmpouts'][0] >= $i) {
$caseNext = $op['jmpouts'][0];
$caseDefault = $i;
$i = $cases[$i] = $caseNext - 1;
}
}
}
$this->beginComplexBlock($EX);
echo $indent, 'switch (', str($this->getOpVal($caseOp['op1'], $EX, true, true), $EX), ') {', PHP_EOL;
$caseIsOut = false;
foreach ($cases as $caseFirst => $caseLast) {
if ($caseIsOut && !empty($EX['lastBlock']) && empty($lastCaseFall)) {
echo PHP_EOL;
}
unset($EX['lastBlock']);
$caseOp = $opcodes[$caseFirst];
echo $indent;
if ($caseOp['opcode'] == XC_CASE) {
echo 'case ';
echo str($this->getOpVal($caseOp['op2'], $EX), $EX);
echo ':', PHP_EOL;
$this->removeJmpInfo($EX, $caseFirst);
++$caseFirst;
assert('$opcodes[$caseFirst]["opcode"] == XC_JMPZ');
$this->removeJmpInfo($EX, $caseFirst);
++$caseFirst;
}
else {
echo 'default';
echo ':', PHP_EOL;
assert('$opcodes[$caseFirst]["opcode"] == XC_JMP');
$this->removeJmpInfo($EX, $caseFirst);
++$caseFirst;
}
assert('$opcodes[$caseLast]["opcode"] == XC_JMP');
$this->removeJmpInfo($EX, $caseLast);
--$caseLast;
switch ($opcodes[$caseLast]['opcode']) {
case XC_BRK:
case XC_CONT:
case XC_GOTO:
$lastCaseFall = false;
break;
default:
$lastCaseFall = true;
}
$this->recognizeAndDecompileClosedBlocks($EX, $caseFirst, $caseLast, $indent . INDENT);
$caseIsOut = true;
}
echo $indent, '}', PHP_EOL;
$this->endComplexBlock($EX);
return;
}
if ($lastOp['opcode'] == XC_JMPNZ && !empty($lastOp['jmpouts'])
&& $lastOp['jmpouts'][0] == $first) {
$this->removeJmpInfo($EX, $last);
$this->beginComplexBlock($EX);
echo $indent, 'do {', PHP_EOL;
$this->recognizeAndDecompileClosedBlocks($EX, $first, $last, $indent . INDENT);
echo $indent, '} while (', str($this->getOpVal($lastOp['op1'], $EX)), ');', PHP_EOL;
$this->endComplexBlock($EX);
return;
}
$firstJmp = null;
$firstJmpOp = null;
for ($i = $first; $i <= $last; ++$i) {
if (!empty($opcodes[$i]['jmpouts'])) {
$firstJmp = $i;
$firstJmpOp = &$opcodes[$firstJmp];
break;
}
}
if (isset($firstJmpOp)
&& $firstJmpOp['opcode'] == XC_JMPZ
&& $firstJmpOp['jmpouts'][0] > $last
&& $lastOp['opcode'] == XC_JMP && !empty($lastOp['jmpouts'])
&& $lastOp['jmpouts'][0] == $first) {
$this->removeJmpInfo($EX, $firstJmp);
$this->removeJmpInfo($EX, $last);
$this->beginComplexBlock($EX);
ob_start();
$this->recognizeAndDecompileClosedBlocks($EX, $first, $last, $indent . INDENT);
$body = ob_get_clean();
echo $indent, 'while (', str($this->getOpVal($firstJmpOp['op1'], $EX)), ') {', PHP_EOL;
echo $body;
echo $indent, '}', PHP_EOL;
$this->endComplexBlock($EX);
return;
}
if (isset($firstJmpOp)
&& $firstJmpOp['opcode'] == XC_FE_FETCH
&& $firstJmpOp['jmpouts'][0] > $last
&& $lastOp['opcode'] == XC_JMP && !empty($lastOp['jmpouts'])
&& $lastOp['jmpouts'][0] == $firstJmp) {
$this->removeJmpInfo($EX, $firstJmp);
$this->removeJmpInfo($EX, $last);
$this->beginComplexBlock($EX);
ob_start();
$this->recognizeAndDecompileClosedBlocks($EX, $first, $last, $indent . INDENT);
$body = ob_get_clean();
$as = foldToCode($firstJmpOp['fe_as'], $EX);
if (isset($firstJmpOp['fe_key'])) {
$as = str($firstJmpOp['fe_key'], $EX) . ' => ' . str($as);
}
echo $indent, 'foreach (', str($firstJmpOp['fe_src'], $EX), " as $as) {", PHP_EOL;
echo $body;
echo $indent, '}', PHP_EOL;
$this->endComplexBlock($EX);
return;
}
$this->decompileBasicBlock($EX, $first, $last, $indent);
}
// }}}
function recognizeAndDecompileClosedBlocks(&$EX, $first, $last, $indent) // {{{ decompile in a tree way
{
$opcodes = &$EX['opcodes'];
$i = $starti = $first;
while ($i <= $last) {
$op = $opcodes[$i];
if (!empty($op['jmpins']) || !empty($op['jmpouts'])) {
$blockFirst = $i;
$blockLast = -1;
$i = $blockFirst;
// $this->dumpRange($EX, $i, $last, $indent);
do {
$op = $opcodes[$i];
if (!empty($op['jmpins'])) {
// care about jumping from blocks behind, not before
foreach ($op['jmpins'] as $oplineNumber) {
if ($oplineNumber <= $last && $blockLast < $oplineNumber) {
$blockLast = $oplineNumber;
}
}
}
if (!empty($op['jmpouts'])) {
$blockLast = max($blockLast, max($op['jmpouts']) - 1);
}
++$i;
} while ($i <= $blockLast);
assert('$blockLast <= $last');
if ($blockLast >= $blockFirst) {
if ($blockFirst > $starti) {
$this->decompileBasicBlock($EX, $starti, $blockFirst - 1, $indent);
}
$this->decompileComplexBlock($EX, $blockFirst, $blockLast, $indent);
$i = $starti = $blockLast + 1;
continue;
}
}
++$i;
}
if ($starti <= $last) {
$this->decompileBasicBlock($EX, $starti, $last, $indent);
}
}
// }}}
function &dop_array($op_array, $indent = '') // {{{
{
$op_array['opcodes'] = $this->fixOpcode($op_array['opcodes'], true, $indent == '' ? 1 : null);
$opcodes = &$op_array['opcodes'];
// {{{ build jmpins/jmpouts to op_array
for ($i = 0, $cnt = count($opcodes); $i < $cnt; $i ++) {
$op = &$opcodes[$i];
$op['line'] = $i;
switch ($op['opcode']) {
case XC_CONT:
case XC_BRK:
$op['jmpouts'] = array();
break;
case XC_GOTO:
$target = $op['op1']['var'];
$op['goto'] = $target;
$opcodes[$target]['gofrom'][] = $i;
break;
case XC_JMP:
$target = $op['op1']['var'];
$op['jmpouts'] = array($target);
$opcodes[$target]['jmpins'][] = $i;
break;
case XC_JMPZNZ: