root/tags/1.2.0/Decompiler.class.php

Revision 1, 43.5 kB (checked in by moo, 3 years ago)

initial import to online

Line 
1<?php
2
3define('INDENT', "\t");
4ini_set('error_reporting', E_ALL);
5
6function color($str, $color = 33)
7{
8    return "\x1B[{$color}m$str\x1B[0m";
9}
10
11function str($src, $indent = '') // {{{
12{
13    if (is_array($indent)) {
14        $indent = $indent['indent'];
15    }
16
17    /*
18    $e = xcache_get_special_value($src);
19    if (isset($e)) {
20        if (is_array($e)) {
21            $src = $e;
22        }
23        else {
24            return $e;
25        }
26    }
27    */
28
29    if (is_array($src)) {
30        die_error('array str');
31        $src = new Decompiler_Array($src);
32        return $src->__toString($indent);
33    }
34
35    if (is_object($src)) {
36        if (!method_exists($src, '__toString')) {
37            var_dump($src);
38            die_error('no __toString');
39        }
40        return $src->__toString($indent);
41    }
42
43    return $src;
44}
45// }}}
46function value($value) // {{{
47{
48    $spec = xcache_get_special_value($value);
49    if (isset($spec)) {
50        $value = $spec;
51        if (!is_array($value)) {
52            // constant
53            return $value;
54        }
55    }
56
57    if (is_array($value)) {
58        $value = new Decompiler_Array($value, true);
59    }
60    else {
61        $value = new Decompiler_Value($value, true);
62    }
63    return $value;
64}
65// }}}
66class Decompiler_Object // {{{
67{
68}
69// }}}
70class Decompiler_Value extends Decompiler_Object // {{{
71{
72    var $value;
73
74    function Decompiler_Value($value = null)
75    {
76        $this->value = $value;
77    }
78
79    function __toString()
80    {
81        return var_export($this->value, true);
82    }
83}
84// }}}
85class Decompiler_Code extends Decompiler_Object // {{{
86{
87    var $src;
88
89    function Decompiler_Code($src)
90    {
91        $this->src = $src;
92    }
93
94    function __toString()
95    {
96        return $this->src;
97    }
98}
99// }}}
100class Decompiler_Binop extends Decompiler_Code // {{{
101{
102    var $opc;
103    var $op1;
104    var $op2;
105    var $parent;
106
107    function Decompiler_Binop($parent, $op1, $opc, $op2)
108    {
109        $this->parent = &$parent;
110        $this->opc = $opc;
111        $this->op1 = $op1;
112        $this->op2 = $op2;
113    }
114
115    function __toString()
116    {
117        $op1 = str($this->op1);
118        if (is_a($this->op1, 'Decompiler_Binop') && $this->op1->opc != $this->opc) {
119            $op1 = "($op1)";
120        }
121        $opstr = $this->parent->binops[$this->opc];
122        if ($op1 == '0' && $this->opc == XC_SUB) {
123            return $opstr . str($this->op2);
124        }
125        return $op1 . ' ' . $opstr . ' ' . str($this->op2);
126    }
127}
128// }}}
129class Decompiler_Fetch extends Decompiler_Code // {{{
130{
131    var $src;
132    var $fetchType;
133
134    function Decompiler_Fetch($src, $type, $globalsrc)
135    {
136        $this->src = $src;
137        $this->fetchType = $type;
138        $this->globalsrc = $globalsrc;
139    }
140
141    function __toString()
142    {
143        switch ($this->fetchType) {
144        case ZEND_FETCH_LOCAL:
145            return '$' . substr($this->src, 1, -1);
146        case ZEND_FETCH_STATIC:
147            die('static fetch cant to string');
148        case ZEND_FETCH_GLOBAL:
149        case ZEND_FETCH_GLOBAL_LOCK:
150            return $this->globalsrc;
151        default:
152            var_dump($this->fetchType);
153            assert(0);
154        }
155    }
156}
157// }}}
158class Decompiler_Box // {{{
159{
160    var $obj;
161
162    function Decompiler_Box(&$obj)
163    {
164        $this->obj = &$obj;
165    }
166
167    function __toString($indent)
168    {
169        return $this->obj->__toString($indent);
170    }
171}
172// }}}
173class Decompiler_Dim extends Decompiler_Value // {{{
174{
175    var $offsets = array();
176    var $isLast = false;
177    var $assign = null;
178
179    function __toString()
180    {
181        if (is_a($this->value, 'Decompiler_ListBox')) {
182            $exp = str($this->value->obj->src);
183        }
184        else {
185            $exp = str($this->value);
186        }
187        foreach ($this->offsets as $dim) {
188            $exp .= '[' . str($dim) . ']';
189        }
190        return $exp;
191    }
192}
193// }}}
194class Decompiler_DimBox extends Decompiler_Box // {{{
195{
196}
197// }}}
198class Decompiler_List extends Decompiler_Code // {{{
199{
200    var $src;
201    var $dims = array();
202    var $everLocked = false;
203
204    function __toString()
205    {
206        if (count($this->dims) == 1 && !$this->everLocked) {
207            $dim = $this->dims[0];
208            unset($dim->value);
209            $dim->value = $this->src;
210            if (!isset($dim->assign)) {
211                return str($dim);
212            }
213            return str($this->dims[0]->assign) . ' = ' . str($dim);
214        }
215        /* flatten dims */
216        $assigns = array();
217        foreach ($this->dims as $dim) {
218            $assign = &$assigns;
219            foreach ($dim->offsets as $offset) {
220                $assign = &$assign[$offset];
221            }
222            $assign = str($dim->assign);
223        }
224        return $this->toList($assigns) . ' = ' . str($this->src);
225    }
226
227    function toList($assigns)
228    {
229        $keys = array_keys($assigns);
230        if (count($keys) < 2) {
231            $keys[] = 0;
232        }
233        $max = call_user_func_array('max', $keys);
234        $list = 'list(';
235        for ($i = 0; $i <= $max; $i ++) {
236            if ($i) {
237                $list .= ', ';
238            }
239            if (!isset($assigns[$i])) {
240                continue;
241            }
242            if (is_array($assigns[$i])) {
243                $list .= $this->toList($assigns[$i]);
244            }
245            else {
246                $list .= $assigns[$i];
247            }
248        }
249        return $list . ')';
250    }
251}
252// }}}
253class Decompiler_ListBox extends Decompiler_Box // {{{
254{
255}
256// }}}
257class Decompiler_Array extends Decompiler_Value // {{{
258{
259    var $needExport = false;
260
261    function Decompiler_Array($value = array(), $needexport = false)
262    {
263        $this->value = $value;
264        $this->needExport = $needexport;
265    }
266
267    function __toString($indent)
268    {
269        $exp = "array(";
270        $indent .= INDENT;
271        $assoclen = 0;
272        $multiline = 0;
273        $i = 0;
274        foreach ($this->value as $k => $v) {
275            if ($i !== $k) {
276                $len = strlen($k);
277                if ($assoclen < $len) {
278                    $assoclen = $len;
279                }
280            }
281            if (is_array($v)) {
282                $multiline ++;
283            }
284            ++ $i;
285        }
286        if ($assoclen && $this->needExport) {
287            $assoclen += 2;
288        }
289
290        $i = 0;
291        $subindent = $indent . INDENT;
292        foreach ($this->value as $k => $v) {
293            if ($multiline) {
294                if ($i) {
295                    $exp .= ",";
296                }
297                $exp .= "\n";
298                $exp .= $indent;
299            }
300            else {
301                if ($i) {
302                    $exp .= ", ";
303                }
304            }
305
306            if ($this->needExport) {
307                $k = var_export($k, true);
308            }
309            if ($multiline) {
310                $exp .= sprintf("%{$assoclen}s => ", $k);
311            }
312            else if ($assoclen) {
313                $exp .= $k . ' => ';
314            }
315
316            if (is_array($v)) {
317                $v = new Decompiler_Array($v, $this->needExport);
318            }
319            $exp .= str($v, $subindent);
320
321            $i ++;
322        }
323        if ($multiline) {
324            $exp .= "$indent);";
325        }
326        else {
327            $exp .= ")";
328        }
329        return $exp;
330    }
331}
332// }}}
333class Decompiler_ForeachBox extends Decompiler_Box // {{{
334{
335    var $iskey;
336
337    function __toString($indent)
338    {
339        return 'foreach (' . '';
340    }
341}
342// }}}
343
344class Decompiler
345{
346    var $rName = '!^[\\w_][\\w\\d_]*$!';
347    var $rQuotedName = "!^'[\\w_][\\w\\d_]*'\$!";
348
349    function Decompiler()
350    {
351        // {{{ opinfo
352        $this->unaryops = array(
353                XC_BW_NOT   => '~',
354                XC_BOOL_NOT => '!',
355                );
356        $this->binops = array(
357                XC_ADD                 => "+",
358                XC_ASSIGN_ADD          => "+=",
359                XC_SUB                 => "-",
360                XC_ASSIGN_SUB          => "-=",
361                XC_MUL                 => "*",
362                XC_ASSIGN_MUL          => "*=",
363                XC_DIV                 => "/",
364                XC_ASSIGN_DIV          => "/=",
365                XC_MOD                 => "%",
366                XC_ASSIGN_MOD          => "%=",
367                XC_SL                  => "<<",
368                XC_ASSIGN_SL           => "<<=",
369                XC_SR                  => ">>",
370                XC_ASSIGN_SR           => ">>=",
371                XC_CONCAT              => ".",
372                XC_ASSIGN_CONCAT       => ".=",
373                XC_IS_IDENTICAL        => "===",
374                XC_IS_NOT_IDENTICAL    => "!==",
375                XC_IS_EQUAL            => "==",
376                XC_IS_NOT_EQUAL        => "!=",
377                XC_IS_SMALLER          => "<",
378                XC_IS_SMALLER_OR_EQUAL => "<=",
379                XC_BW_OR               => "|",
380                XC_ASSIGN_BW_OR        => "|=",
381                XC_BW_AND              => "&",
382                XC_ASSIGN_BW_AND       => "&=",
383                XC_BW_XOR              => "^",
384                XC_ASSIGN_BW_XOR       => "^=",
385                XC_BOOL_XOR            => "xor",
386                );
387        // }}}
388        $this->includeTypes = array( // {{{
389                ZEND_EVAL         => 'eval',
390                ZEND_INCLUDE      => 'include',
391                ZEND_INCLUDE_ONCE => 'include_once',
392                ZEND_REQUIRE      => 'require',
393                ZEND_REQUIRE_ONCE => 'require_once',
394                );
395                // }}}
396    }
397    function outputPhp(&$opcodes, $opline, $last, $indent) // {{{
398    {
399        $origindent = $indent;
400        $curticks = 0;
401        for ($i = $opline; $i <= $last; $i ++) {
402            $op = $opcodes[$i];
403            if (isset($op['php'])) {
404                $toticks = isset($op['ticks']) ? $op['ticks'] : 0;
405                if ($curticks != $toticks) {
406                    if (!$toticks) {
407                        echo $origindent, "}\n";
408                        $indent = $origindent;
409                    }
410                    else {
411                        if ($curticks) {
412                            echo $origindent, "}\n";
413                        }
414                        else if (!$curticks) {
415                            $indent .= INDENT;
416                        }
417                        echo $origindent, "declare(ticks=$curticks) {\n";
418                    }
419                    $curticks = $toticks;
420                }
421                echo $indent, str($op['php']), ";\n";
422            }
423        }
424        if ($curticks) {
425            echo $origindent, "}\n";
426        }
427    }
428    // }}}
429    function getOpVal($op, &$EX, $tostr = true, $free = false) // {{{
430    {
431        switch ($op['op_type']) {
432        case XC_IS_CONST:
433            return str(value($op['u.constant']));
434
435        case XC_IS_VAR:
436        case XC_IS_TMP_VAR:
437            $T = &$EX['Ts'];
438            $ret = $T[$op['u.var']];
439            if ($tostr) {
440                $ret = str($ret, $EX);
441            }
442            if ($free) {
443                unset($T[$op['u.var']]);
444            }
445            return $ret;
446
447        case XC_IS_CV:
448            $var = $op['u.var'];
449            $var = $EX['op_array']['vars'][$var];
450            return '$' . $var['name'];
451
452        case XC_IS_UNUSED:
453            return null;
454        }
455    }
456    // }}}
457    function &dop_array($op_array, $indent = '') // {{{
458    {
459        $opcodes = &$op_array['opcodes'];
460        $last = count($opcodes) - 1;
461        if ($opcodes[$last]['opcode'] == XC_HANDLE_EXCEPTION) {
462            unset($opcodes[$last]);
463        }
464        $EX['indent'] = '';
465        //for ($i = 0, $cnt = count($opcodes); $i < $cnt; $i ++) {
466        //  $opcodes[$i]['opcode'] = xcache_get_fixed_opcode($opcodes[$i]['opcode'], $i);
467        //}
468        // {{{ build jmp array
469        for ($i = 0, $cnt = count($opcodes); $i < $cnt; $i ++) {
470            $op = &$opcodes[$i];
471            /*
472            if ($op['opcode'] == XC_JMPZ) {
473                $this->dumpop($op, $EX);
474                var_dump($op);
475            }
476            continue;
477            */
478            $op['line'] = $i;
479            switch ($op['opcode']) {
480            case XC_JMP:
481                $target = $op['op1']['u.var'];
482                $op['jmpouts'] = array($target);
483                $opcodes[$target]['jmpins'][] = $i;
484                break;
485
486            case XC_JMPZNZ:
487                $jmpz = $op['op2']['u.opline_num'];
488                $jmpnz = $op['extended_value'];
489                $op['jmpouts'] = array($jmpz, $jmpnz);
490                $opcodes[$jmpz]['jmpins'][] = $i;
491                $opcodes[$jmpnz]['jmpins'][] = $i;
492                break;
493
494            case XC_JMPZ:
495            case XC_JMPNZ:
496            case XC_JMPZ_EX:
497            case XC_JMPNZ_EX:
498            // case XC_FE_RESET:
499            case XC_FE_FETCH:
500            // case XC_JMP_NO_CTOR:
501                $target = $op['op2']['u.opline_num'];
502                //if (!isset($target)) {
503                //  $this->dumpop($op, $EX);
504                //  var_dump($op); exit;
505                //}
506                $op['jmpouts'] = array($target);
507                $opcodes[$target]['jmpins'][] = $i;
508                break;
509
510            /*
511            case XC_RETURN:
512                $op['jmpouts'] = array();
513                break;
514            */
515            }
516        }
517        unset($op);
518        // }}}
519        // build semi-basic blocks
520        $nextbbs = array();
521        $starti = 0;
522        for ($i = 1, $cnt = count($opcodes); $i < $cnt; $i ++) {
523            if (isset($opcodes[$i]['jmpins'])
524             || isset($opcodes[$i - 1]['jmpouts'])) {
525                $nextbbs[$starti] = $i;
526                $starti = $i;
527            }
528        }
529        $nextbbs[$starti] = $cnt;
530
531        $EX = array();
532        $EX['Ts'] = array();
533        $EX['indent'] = $indent;
534        $EX['nextbbs'] = $nextbbs;
535        $EX['op_array'] = &$op_array;
536        $EX['opcodes'] = &$opcodes;
537        // func call
538        $EX['object'] = null;
539        $EX['fbc'] = null;
540        $EX['argstack'] = array();
541        $EX['arg_types_stack'] = array();
542        $EX['last'] = count($opcodes) - 1;
543        $EX['silence'] = 0;
544
545        for ($next = 0, $last = $EX['last'];
546                $loop = $this->outputCode($EX, $next, $last, $indent, true);
547                list($next, $last) = $loop) {
548            // empty
549        }
550        return $EX;
551    }
552    // }}}
553    function outputCode(&$EX, $opline, $last, $indent, $loop = false) // {{{
554    {
555        $op = &$EX['opcodes'][$opline];
556        $next = $EX['nextbbs'][$opline];
557
558        $end = $next - 1;
559        if ($end > $last) {
560            $end = $last;
561        }
562
563        if (isset($op['jmpins'])) {
564            echo "\nline", $op['line'], ":\n";
565        }
566        else {
567            // echo ";;;\n";
568        }
569        $this->dasmBasicBlock($EX, $opline, $end);
570        $this->outputPhp($EX['opcodes'], $opline, $end, $indent);
571        // jmpout op
572        $op = &$EX['opcodes'][$end];
573        $op1 = $op['op1'];
574        $op2 = $op['op2'];
575        $ext = $op['extended_value'];
576        $line = $op['line'];
577
578        if (isset($EX['opcodes'][$next])) {
579            if (isset($last) && $next > $last) {
580                $next = null;
581            }
582        }
583        else {
584            $next = null;
585        }
586        if ($op['opcode'] == XC_FE_FETCH) {
587            $opline = $next;
588            $next = $op['op2']['u.opline_num'];
589            $end = $next - 1;
590
591            ob_start();
592            $this->outputCode($EX, $opline, $end /* - 1 skip last jmp */, $indent . INDENT);
593            $body = ob_get_clean();
594
595            $as = str($op['fe_as']);
596            if (isset($op['fe_key'])) {
597                $as = str($op['fe_key']) . ' => ' . $as;
598            }
599            echo "{$indent}foreach (" . str($op['fe_src']) . " as $as) {\n";
600            echo $body;
601            echo "{$indent}}";
602            // $this->outputCode($EX, $next, $last, $indent);
603            // return;
604        }
605        /*
606        if ($op['opcode'] == XC_JMPZ) {
607            $target = $op2['u.opline_num'];
608            if ($line + 1) {
609                $nextblock = $EX['nextbbs'][$next];
610                $jmpop = end($nextblock);
611                if ($jmpop['opcode'] == XC_JMP) {
612                    $ifendline = $op2['u.opline_num'];
613                    if ($ifendline >= $line) {
614                        $cond = $op['cond'];
615                        echo "{$indent}if ($cond) {\n";
616                        $this->outputCode($EX, $next, $last, INDENT . $indent);
617                        echo "$indent}\n";
618                        $this->outputCode($EX, $target, $last, $indent);
619                        return;
620                    }
621                }
622            }
623        }
624        */
625        if (!isset($next)) {
626            return;
627        }
628        if (!empty($op['jmpouts']) && isset($op['isjmp'])) {
629            if (isset($op['cond'])) {
630                echo "{$indent}check ($op[cond]) {\n";
631                echo INDENT;
632            }
633            echo $indent;
634            echo xcache_get_opcode($op['opcode']), ' line', $op['jmpouts'][0];
635            if (isset($op['jmpouts'][1])) {
636                echo ', line', $op['jmpouts'][1];
637            }
638            echo ";";
639            // echo ' // <- line', $op['line'];
640            echo "\n";
641            if (isset($op['cond'])) echo "$indent}\n";
642        }
643
644        // proces JMPZ_EX/JMPNZ_EX for AND,OR
645        $op = &$EX['opcodes'][$next];
646        /*
647        if (isset($op['jmpins'])) {
648            foreach (array_reverse($op['jmpins']) as $fromline) {
649                $fromop = $EX['opcodes'][$fromline];
650                switch ($fromop['opcode']) {
651                case XC_JMPZ_EX: $opstr = 'and'; break;
652