diff --git a/Decompiler.class.php b/Decompiler.class.php index 98e2598..cd64e56 100644 --- a/Decompiler.class.php +++ b/Decompiler.class.php @@ -563,6 +563,7 @@ class Decompiler echo PHP_EOL; } echo $indent, str($op['php'], $indent), ";\n"; + $EX['lastBlock'] = 'basic'; } } if ($curticks) { @@ -666,7 +667,7 @@ class Decompiler function decompileBasicBlock(&$EX, $first, $last, $indent) // {{{ { $this->dasmBasicBlock($EX, $first, $last); - // $this->dumpRange($EX, $first, $last); + // $this->dumpRange($EX, $first, $last, $indent); $this->outputPhp($EX, $first, $last, $indent); } // }}} @@ -698,11 +699,109 @@ class Decompiler // }}} 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); @@ -787,7 +886,9 @@ class Decompiler $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) { @@ -809,8 +910,8 @@ class Decompiler } $this->decompileComplexBlock($EX, $blockFirst, $blockLast, $indent); $i = $starti = $blockLast + 1; + continue; } - continue; } ++$i; } @@ -875,6 +976,17 @@ class Decompiler $op['jmpouts'] = array(); break; */ + + case XC_SWITCH_FREE: + $op['jmpouts'] = array($i + 1); + $opcodes[$i + 1]['jmpins'][] = $i; + break; + + case XC_CASE: + // just to link together + $op['jmpouts'] = array($i + 2); + $opcodes[$i + 2]['jmpins'][] = $i; + break; } /* if (!empty($op['jmpouts']) || !empty($op['jmpins'])) { @@ -913,8 +1025,17 @@ class Decompiler $EX['recvs'] = array(); $EX['uses'] = array(); + $first = 0; + $last = count($opcodes) - 1; + + /* dump whole array + $this->dasmBasicBlock($EX, $first, $last); + for ($i = $first; $i <= $last; ++$i) { + echo $i, "\t", $this->dumpop($opcodes[$i], $EX); + } + // */ // decompile in a tree way - $this->recognizeAndDecompileClosedBlocks($EX, 0, count($opcodes) - 1, $EX['indent']); + $this->recognizeAndDecompileClosedBlocks($EX, $first, $last, $EX['indent']); return $EX; } // }}} @@ -1672,7 +1793,6 @@ class Decompiler break; // }}} case XC_SWITCH_FREE: // {{{ - // unset($T[$op1['var']]); break; // }}} case XC_FREE: // {{{ @@ -1749,9 +1869,9 @@ class Decompiler break; // }}} case XC_CASE: - $switchValue = $this->getOpVal($op1, $EX); + // $switchValue = $this->getOpVal($op1, $EX); $caseValue = $this->getOpVal($op2, $EX); - $resvar = str($switchValue) . ' == ' . str($caseValue); + $resvar = $caseValue; break; case XC_RECV_INIT: case XC_RECV: @@ -1948,12 +2068,12 @@ class Decompiler echo PHP_EOL; } // }}} - function dumpRange(&$EX, $first, $last) // {{{ + function dumpRange(&$EX, $first, $last, $indent = '') // {{{ { for ($i = $first; $i <= $last; ++$i) { - echo $i, "\t"; $this->dumpop($EX['opcodes'][$i], $EX); + echo $indent, $i, "\t"; $this->dumpop($EX['opcodes'][$i], $EX); } - echo "==", PHP_EOL; + echo $indent, "==", PHP_EOL; } // }}} function dargs(&$EX, $indent) // {{{ diff --git a/decompilesample.php b/decompilesample.php index e661856..8800a9f 100644 --- a/decompilesample.php +++ b/decompilesample.php @@ -301,18 +301,72 @@ foreach ($array as $key => $value) { } } -switch ($switch) { +switch ($normalSwitch) { case 'case1': echo 'case1'; + + switch ($nestedSwitch) { + case 1: + } + break; case 'case2': echo 'case2'; break; +default: + switch ($nestedSwitch) { + case 1: + } + + echo 'default'; + break; +} + +switch ($switchWithoutDefault) { +case 'case1': + echo 'case1'; + break; + +case 'case2': + echo 'case2'; + break; +} + +switch ($switchWithMiddleDefault) { +case 'case1': + echo 'case1'; + break; + default: echo 'default'; break; + +case 'case2': + echo 'case2'; + break; +} + +switch ($switchWithInitialDefault) { +default: + echo 'default'; + break; + +case 'case1': + echo 'case1'; + break; + +case 'case2': + echo 'case2'; + break; +} + +switch (emptySwitch()) { +} + +switch (emptySwitch()) { +default: } declare (ticks=1) {