root/trunk/Decompiler.class.php

Revision 522, 43.6 kB (checked in by moo, 8 months ago)

slide hits per second and hour

  • Property svn:eol-style set to native
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, false, $indent);
32        return $src->__toString();
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()
168    {
169        return $this->obj->__toString();
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    var $indent = '';
261
262    function Decompiler_Array($value = array(), $needexport = false, $indent = '')
263    {
264        $this->value = $value;
265        $this->needExport = $needexport;
266        $this->indent = $indent;
267    }
268
269    function __toString()
270    {
271        $exp = "array(";
272        $indent = $this->indent . INDENT;
273        $assoclen = 0;
274        $multiline = 0;
275        $i = 0;
276        foreach ($this->value as $k => $v) {
277            if ($i !== $k) {
278                $len = strlen($k);
279                if ($assoclen < $len) {
280                    $assoclen = $len;
281                }
282            }
283            if (is_array($v)) {
284                $multiline ++;
285            }
286            ++ $i;
287        }
288        if ($assoclen && $this->needExport) {
289            $assoclen += 2;
290        }
291
292        $i = 0;
293        $subindent = $indent . INDENT;
294        foreach ($this->value as $k => $v) {
295            if ($multiline) {
296                if ($i) {
297                    $exp .= ",";
298                }
299                $exp .= "\n";
300                $exp .= $indent;
301            }
302            else {
303                if ($i) {
304                    $exp .= ", ";
305                }
306            }
307
308            if ($this->needExport) {
309                $k = var_export($k, true);
310            }
311            if ($multiline) {
312                $exp .= sprintf("%{$assoclen}s => ", $k);
313            }
314            else if ($assoclen) {
315                $exp .= $k . ' => ';
316            }
317
318            if (is_array($v)) {
319                $v = new Decompiler_Array($v, $this->needExport);
320            }
321            $exp .= str($v, $subindent);
322
323            $i ++;
324        }
325        if ($multiline) {
326            $exp .= "$indent);";
327        }
328        else {
329            $exp .= ")";
330        }
331        return $exp;
332    }
333}
334// }}}
335class Decompiler_ForeachBox extends Decompiler_Box // {{{
336{
337    var $iskey;
338
339    function __toString()
340    {
341        return 'foreach (' . '';
342    }
343}
344// }}}
345
346class Decompiler
347{
348    var $rName = '!^[\\w_][\\w\\d_]*$!';
349    var $rQuotedName = "!^'[\\w_][\\w\\d_]*'\$!";
350
351    function Decompiler()
352    {
353        // {{{ opinfo
354        $this->unaryops = array(
355                XC_BW_NOT   => '~',
356                XC_BOOL_NOT => '!',
357                );
358        $this->binops = array(
359                XC_ADD                 => "+",
360                XC_ASSIGN_ADD          => "+=",
361                XC_SUB                 => "-",
362                XC_ASSIGN_SUB          => "-=",
363                XC_MUL                 => "*",
364                XC_ASSIGN_MUL          => "*=",
365                XC_DIV                 => "/",
366                XC_ASSIGN_DIV          => "/=",
367                XC_MOD                 => "%",
368                XC_ASSIGN_MOD          => "%=",
369                XC_SL                  => "<<",
370                XC_ASSIGN_SL           => "<<=",
371                XC_SR                  => ">>",
372                XC_ASSIGN_SR           => ">>=",
373                XC_CONCAT              => ".",
374                XC_ASSIGN_CONCAT       => ".=",
375                XC_IS_IDENTICAL        => "===",
376                XC_IS_NOT_IDENTICAL    => "!==",
377                XC_IS_EQUAL            => "==",
378                XC_IS_NOT_EQUAL        => "!=",
379                XC_IS_SMALLER          => "<",
380                XC_IS_SMALLER_OR_EQUAL => "<=",
381                XC_BW_OR               => "|",
382                XC_ASSIGN_BW_OR        => "|=",
383                XC_BW_AND              => "&",
384                XC_ASSIGN_BW_AND       => "&=",
385                XC_BW_XOR              => "^",
386                XC_ASSIGN_BW_XOR       => "^=",
387                XC_BOOL_XOR            => "xor",
388                );
389        // }}}
390        $this->includeTypes = array( // {{{
391                ZEND_EVAL         => 'eval',
392                ZEND_INCLUDE      => 'include',
393                ZEND_INCLUDE_ONCE => 'include_once',
394                ZEND_REQUIRE      => 'require',
395                ZEND_REQUIRE_ONCE => 'require_once',
396                );
397                // }}}
398    }
399    function outputPhp(&$opcodes, $opline, $last, $indent) // {{{
400    {
401        $origindent = $indent;
402        $curticks = 0;
403        for ($i = $opline; $i <= $last; $i ++) {
404            $op = $opcodes[$i];
405            if (isset($op['php'])) {
406                $toticks = isset($op['ticks']) ? $op['ticks'] : 0;
407                if ($curticks != $toticks) {
408                    if (!$toticks) {
409                        echo $origindent, "}\n";
410                        $indent = $origindent;
411                    }
412                    else {
413                        if ($curticks) {
414                            echo $origindent, "}\n";
415                        }
416                        else if (!$curticks) {
417                            $indent .= INDENT;
418                        }
419                        echo $origindent, "declare(ticks=$curticks) {\n";
420                    }
421                    $curticks = $toticks;
422                }
423                echo $indent, str($op['php']), ";\n";
424            }
425        }
426        if ($curticks) {
427            echo $origindent, "}\n";
428        }
429    }
430    // }}}
431    function getOpVal($op, &$EX, $tostr = true, $free = false) // {{{
432    {
433        switch ($op['op_type']) {
434        case XC_IS_CONST:
435            return str(value($op['u.constant']));
436
437        case XC_IS_VAR:
438        case XC_IS_TMP_VAR:
439            $T = &$EX['Ts'];
440            $ret = $T[$op['u.var']];
441            if ($tostr) {
442                $ret = str($ret, $EX);
443            }
444            if ($free) {
445                unset($T[$op['u.var']]);
446            }
447            return $ret;
448
449        case XC_IS_CV:
450            $var = $op['u.var'];
451            $var = $EX['op_array']['vars'][$var];
452            return '$' . $var['name'];
453
454        case XC_IS_UNUSED:
455            return null;
456        }
457    }
458    // }}}
459    function &dop_array($op_array, $indent = '') // {{{
460    {
461        $opcodes = &$op_array['opcodes'];
462        $last = count($opcodes) - 1;
463        if ($opcodes[$last]['opcode'] == XC_HANDLE_EXCEPTION) {
464            unset($opcodes[$last]);
465        }
466        $EX['indent'] = '';
467        //for ($i = 0, $cnt = count($opcodes); $i < $cnt; $i ++) {
468        //  $opcodes[$i]['opcode'] = xcache_get_fixed_opcode($opcodes[$i]['opcode'], $i);
469        //}
470        // {{{ build jmp array
471        for ($i = 0, $cnt = count($opcodes); $i < $cnt; $i ++) {
472            $op = &$opcodes[$i];
473            /*
474            if ($op['opcode'] == XC_JMPZ) {
475                $this->dumpop($op, $EX);
476                var_dump($op);
477            }
478            continue;
479            */
480            $op['line'] = $i;
481            switch ($op['opcode']) {
482            case XC_JMP:
483                $target = $op['op1']['u.var'];
484                $op['jmpouts'] = array($target);
485                $opcodes[$target]['jmpins'][] = $i;
486                break;
487
488            case XC_JMPZNZ:
489                $jmpz = $op['op2']['u.opline_num'];
490                $jmpnz = $op['extended_value'];
491                $op['jmpouts'] = array($jmpz, $jmpnz);
492                $opcodes[$jmpz]['jmpins'][] = $i;
493                $opcodes[$jmpnz]['jmpins'][] = $i;
494                break;
495
496            case XC_JMPZ:
497            case XC_JMPNZ:
498            case XC_JMPZ_EX:
499            case XC_JMPNZ_EX:
500            // case XC_FE_RESET:
501            case XC_FE_FETCH:
502            // case XC_JMP_NO_CTOR:
503                $target = $op['op2']['u.opline_num'];
504                //if (!isset($target)) {
505                //  $this->dumpop($op, $EX);
506                //  var_dump($op); exit;
507                //}
508                $op['jmpouts'] = array($target);
509                $opcodes[$target]['jmpins'][] = $i;
510                break;
511
512            /*
513            case XC_RETURN:
514                $op['jmpouts'] = array();
515                break;
516            */
517            }
518        }
519        unset($op);
520        // }}}
521        // build semi-basic blocks
522        $nextbbs = array();
523        $starti = 0;
524        for ($i = 1, $cnt = count($opcodes); $i < $cnt; $i ++) {
525            if (isset($opcodes[$i]['jmpins'])
526             || isset($opcodes[$i - 1]['jmpouts'])) {
527                $nextbbs[$starti] = $i;
528                $starti = $i;
529            }
530        }
531        $nextbbs[$starti] = $cnt;
532
533        $EX = array();
534        $EX['Ts'] = array();
535        $EX['indent'] = $indent;
536        $EX['nextbbs'] = $nextbbs;
537        $EX['op_array'] = &$op_array;
538        $EX['opcodes'] = &$opcodes;
539        // func call
540        $EX['object'] = null;
541        $EX['fbc'] = null;
542        $EX['argstack'] = array();
543        $EX['arg_types_stack'] = array();
544        $EX['last'] = count($opcodes) - 1;
545        $EX['silence'] = 0;
546
547        for ($next = 0, $last = $EX['last'];
548                $loop = $this->outputCode($EX, $next, $last, $indent, true);
549                list($next, $last) = $loop) {
550            // empty
551        }
552        return $EX;
553    }
554    // }}}
555    function outputCode(&$EX, $opline, $last, $indent, $loop = false) // {{{
556    {
557        $op = &$EX['opcodes'][$opline];
558        $next = $EX['nextbbs'][$opline];
559
560        $end = $next - 1;
561        if ($end > $last) {
562            $end = $last;
563        }
564
565        if (isset($op['jmpins'])) {
566            echo "\nline", $op['line'], ":\n";
567        }
568        else {
569            // echo ";;;\n";
570        }
571        $this->dasmBasicBlock($EX, $opline, $end);
572        $this->outputPhp($EX['opcodes'], $opline, $end, $indent);
573        // jmpout op
574        $op = &$EX['opcodes'][$end];
575        $op1 = $op['op1'];
576        $op2 = $op['op2'];
577        $ext = $op['extended_value'];
578        $line = $op['line'];
579
580        if (isset($EX['opcodes'][$next])) {
581            if (isset($last) && $next > $last) {
582                $next = null;
583            }
584        }
585        else {
586            $next = null;
587        }
588        if ($op['opcode'] == XC_FE_FETCH) {
589            $opline = $next;
590            $next = $op['op2']['u.opline_num'];
591            $end = $next - 1;
592
593            ob_start();
594            $this->outputCode($EX, $opline, $end /* - 1 skip last jmp */, $indent . INDENT);
595            $body = ob_get_clean();
596
597            $as = str($op['fe_as']);
598            if (isset($op['fe_key'])) {
599                $as = str($op['fe_key']) . ' => ' . $as;
600            }
601            echo "{$indent}foreach (" . str($op['fe_src']) . " as $as) {\n";
602            echo $body;
603            echo "{$indent}}";
604            // $this->outputCode($EX, $next, $last, $indent);
605            // return;
606        }
607        /*
608        if ($op['opcode'] == XC_JMPZ) {
609            $target = $op2['u.opline_num'];
610            if ($line + 1) {
611                $nextblock = $EX['nextbbs'][$next];
612                $jmpop = end($nextblock);
613                if ($jmpop['opcode'] == XC_JMP) {
614                    $ifendline = $op2['u.opline_num'];
615                    if ($ifendline >= $line) {
616                        $cond = $op['cond'];
617                        echo "{$indent}if ($cond) {\n";
618                        $this->outputCode($EX, $next, $last, INDENT . $indent);
619                        echo "$indent}\n";
620                        $this->outputCode($EX, $target, $last, $indent);
621                        return;
622                    }
623                }
624            }
625        }
626        */
627        if (!isset($next)) {
628            return;
629        }
630        if (!empty($op['jmpouts']) && isset($op['isjmp'])) {
631            if (isset($op['cond'])) {
632                echo "{$indent}check ($op[cond]) {\n";
633                echo INDENT;
634            }
635            echo $indent;
636            echo xcache_get_opcode($op['opcode']), ' line', $op['jmpouts'][0];
637            if (isset($op['jmpouts'][1])) {
638                echo ', line', $op['jmpouts'][1];
639            }
640            echo ";";
641            // echo ' // <- line', $op['line'];
642            echo "\n";
643            if (isset($op['cond'])) echo "$indent}\n";
644        }
645
646        // proces JMPZ_EX/JMPNZ_EX for AND,OR
647        $op = &$EX['opcodes'][$next];
648        /*
649        if (isset($op['jmpins'])) {
650            foreach (array_reverse($op['jmpins']) as $fromline) {
651                $fromop = $EX['opcodes'][$fromline];
652<