Browse Source

Fixed bug #62210 (Exceptions can leak temporary variables. As a part of the fix serious refactoring was done. op_array->brk_cont_array was removed, and replaced with more general and speed efficient op_array->T_liveliness. ZEND_GOTO opcode is always replaced by ZEND_JMP at compile time). (Bob, Dmitry, Laruence)

Squashed commit of the following:

commit 38e22106d4
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Tue Jul 7 15:12:19 2015 +0300

    Added NEWS entry

commit 0a355935bf
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Tue Jul 7 15:06:32 2015 +0300

    Inline function, to eliminate repeatable checks

commit d937584f3a
Merge: 0341626 32677f5
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Tue Jul 7 14:35:49 2015 +0300

    Merge branch 'master' into temporary_cleaning

    * master:
      Fixed bug #70006 (cli - function with default arg = STDOUT crash output).
      Fix x86 build
      Fixed use after free on closure_call_leak_with_exception.phpt
      Fixed test

commit 0341626ea9
Merge: 74869fa dec35de
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Tue Jul 7 12:00:53 2015 +0300

    Merge branch 'temporary_cleaning' of https://github.com/laruence/php-src into temporary_cleaning

    * 'temporary_cleaning' of https://github.com/laruence/php-src:
      Fixed checkpoint get
      Fixed crash of invalid pointer derefer
      cleanup

commit 74869fa673
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Tue Jul 7 11:45:34 2015 +0300

    Fixed wrong GOTO resolution

commit dec35ded32
Author: Xinchen Hui <laruence@gmail.com>
Date:   Tue Jul 7 15:58:49 2015 +0800

    Fixed checkpoint get

commit b0f419540a
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Tue Jul 7 10:47:11 2015 +0300

    Fixed crash of invalid pointer derefer (laruence)

commit 7a428d98ca
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Tue Jul 7 10:35:47 2015 +0300

    Fixed identation

commit 9c3a4dce9c
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Tue Jul 7 10:33:52 2015 +0300

    Fixed invalid size

commit 653abc670b
Author: Xinchen Hui <laruence@gmail.com>
Date:   Tue Jul 7 11:29:14 2015 +0800

    Fixed crash of invalid pointer derefer

commit e04500ceda
Author: Xinchen Hui <laruence@gmail.com>
Date:   Tue Jul 7 11:28:26 2015 +0800

    cleanup

commit 34183e1687
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Tue Jul 7 04:49:46 2015 +0300

    op_array->T_liveliness compression

commit 2f6ad84579
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Tue Jul 7 04:44:44 2015 +0300

    White spaces

commit be83f115a3
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Tue Jul 7 04:42:26 2015 +0300

    Identation

commit 1f5084b990
Merge: 91b620d 1adf3df
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Tue Jul 7 04:41:54 2015 +0300

    Merge branch 'master' into temporary_cleaning

    * master:
      Throw TypeError for invalid callback
      Fix crash when exception occurs during nested rope
      Fix crash when exception is thrown during ROPE_END
      Small cleanup in ternary compilation
      move the define to the right place
      fix ext/ldap build
      Rectify information about invalid shift warning being now ArithmeticError

commit 91b620d684
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Tue Jul 7 04:32:04 2015 +0300

    Replace GOTO by FREE/FE_FREE and JMP at compile time

commit 7052e56979
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Tue Jul 7 02:25:08 2015 +0300

    Use zend_regenerate_var_liveliness_info() to regenerate information after pass two.

commit ae72b0dc67
Merge: a81c4bb a919fe8
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Mon Jul 6 21:02:34 2015 +0300

    Merge branch 'master' into temporary_cleaning

    * master:
      Do not display EXT_TYPE_UNUSED in phpdbg opcodes
      Run debug build with opcache on travis

commit a81c4bb8c6
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Mon Jul 6 20:59:34 2015 +0300

    Improved algorithm. It's actually the same algorithm with second loop removed and simpler temporary data structures. The only difference may be in "re-defined" vatriable handling. Now live-range in that case started from the seconnd definition (this must be more safe).

commit 9a16810f7a
Merge: bbfbe47 001ecd3
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Mon Jul 6 17:57:45 2015 +0300

    Merge branch 'master' into temporary_cleaning

    * master:
      Simplify TMP var number decoding (without HashTable)

commit bbfbe470c8
Merge: 0bda4ab 436b01e
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Mon Jul 6 17:02:01 2015 +0300

    Merge branch 'master' into temporary_cleaning

    * master:
      Avoid dangerous optimization
      Fixed JMPZNZ instruction printing
      Attempt at falling back on ldap_find_control for Mac OS

commit 0bda4abea7
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Mon Jul 6 16:05:33 2015 +0300

    Fixed live-range construction for OP_DATA opcode
    Added comments about algorithm assumtions

commit 521ad9df98
Merge: 4398dab a09dcb0
Author: Bob Weinand <bobwei9@hotmail.com>
Date:   Mon Jul 6 14:54:15 2015 +0200

    Merge branch 'master' of https://github.com/php/php-src into temporary_cleaning

commit 4398dab82f
Author: Bob Weinand <bobwei9@hotmail.com>
Date:   Mon Jul 6 13:51:27 2015 +0200

    Add a few phpt tests related to temporary cleaning

commit 739656f83f
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Mon Jul 6 14:28:49 2015 +0300

    Fixed Zend/tests/foreach_004.phpt failure (FE_FETCH shouldn't be included into TMP vatriablr live range)

commit 3df462a2bc
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Mon Jul 6 13:41:02 2015 +0300

    Improve data layout (reduce the op_array structure size on 64-bit systems)

commit 883b73c56e
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Mon Jul 6 13:28:45 2015 +0300

    Removed op_array->brk_cont_array

commit ae5e58b598
Author: Bob Weinand <bobwei9@hotmail.com>
Date:   Mon Jul 6 04:22:58 2015 +0200

    Fix bug with brk_cont variable free / free loop vars via temporary liveliness info

commit b4223ca627
Author: Bob Weinand <bobwei9@hotmail.com>
Date:   Mon Jul 6 04:07:07 2015 +0200

    Fix bugs / cleanup fixes

commit ea33189d22
Author: Xinchen Hui <laruence@gmail.com>
Date:   Sun Jul 5 20:58:38 2015 +0800

    Removed useless TsTop

commit 1dbb007e4a
Merge: 550bbf8 3a8af24
Author: Xinchen Hui <laruence@gmail.com>
Date:   Sat Jul 4 15:06:44 2015 +0800

    Merge branch 'temporary_cleaning' of https://github.com/dstogov/php-src into temporary_cleaning

commit 3a8af24529
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Fri Jul 3 16:15:36 2015 +0300

    More exceptions from regular liveliness analyses (with explanation in comments).
    Mark old "unexplained" exceptions with ???.

commit ba721efa2c
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Fri Jul 3 14:16:09 2015 +0300

    Print list of live temp variables (at least for internal debugging)

commit 8d1f88fe91
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Fri Jul 3 13:31:56 2015 +0300

    Use op_array->T_liveliness to free incomplete ropes and restore error_reporting level on exception

commit 80c1d0d779
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Fri Jul 3 11:05:39 2015 +0300

    Don't keep empty T_liveliness

commit 501ae8aaac
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Thu Jul 2 22:31:48 2015 +0300

    Reverted changes to Zend/zend_arena.h.
    Reuse CG(arena) instead of creating a new one.

commit a4fce36907
Merge: 6ff7246 fd0fcce
Author: Dmitry Stogov <dmitry@zend.com>
Date:   Thu Jul 2 22:01:42 2015 +0300

    Merge branch 'temporary_cleaning' of github.com:bwoebi/php-src into temporary_cleaning

    * 'temporary_cleaning' of github.com:bwoebi/php-src:
      Fix remaining issues with compacted temporaries
      Fix regression from last commit (+1 ?!)
      Fix off-by-one (opcache may remove last ZEND_RETURN)
      Speed algorithm up, more fail safety when reusing temporaries
      Dumb bug in opcode.c (forgot to update Ts[i])
      Fix opcache support
      Exempt ROPE temporaries from freeing
      Hmm, we need temporary info for all the opcodes
      Add opcache support for cleaning in optimization step (Opcache seems to have a few unrelated issues which blow up together with that patch)
      Add proper temporary cleaning upon frame abortion
      Fix arena on small sizes (size < sizeof(zend_arena))

commit fd0fcce811
Author: Bob Weinand <bobwei9@hotmail.com>
Date:   Thu Jul 2 20:00:33 2015 +0200

    Fix remaining issues with compacted temporaries

commit 427dc58bbb
Author: Bob Weinand <bobwei9@hotmail.com>
Date:   Wed Jul 1 22:49:12 2015 +0200

    Fix regression from last commit (+1 ?!)

commit 1adcf56a6e
Author: Bob Weinand <bobwei9@hotmail.com>
Date:   Wed Jul 1 22:17:07 2015 +0200

    Fix off-by-one (opcache may remove last ZEND_RETURN)

commit 25b231b784
Author: Bob Weinand <bobwei9@hotmail.com>
Date:   Wed Jul 1 20:59:24 2015 +0200

    Speed algorithm up, more fail safety when reusing temporaries

commit 22d9d05350
Author: Bob Weinand <bobwei9@hotmail.com>
Date:   Wed Jul 1 16:48:46 2015 +0200

    Dumb bug in opcode.c (forgot to update Ts[i])

commit 6538269bfa
Author: Bob Weinand <bobwei9@hotmail.com>
Date:   Wed Jul 1 13:05:52 2015 +0200

    Fix opcache support

commit 333a7c4a88
Author: Bob Weinand <bobwei9@hotmail.com>
Date:   Sat Jun 27 22:40:21 2015 +0200

    Exempt ROPE temporaries from freeing

commit 02585f7708
Author: Bob Weinand <bobwei9@hotmail.com>
Date:   Fri Jun 26 16:20:55 2015 +0200

    Hmm, we need temporary info for all the opcodes

commit cbcaedbd78
Author: Bob Weinand <bobwei9@hotmail.com>
Date:   Fri Jun 26 01:04:09 2015 +0200

    Add opcache support for cleaning in optimization step
    (Opcache seems to have a few unrelated issues which blow up together with that patch)

commit fef649f406
Author: Bob Weinand <bobwei9@hotmail.com>
Date:   Fri Jun 26 01:02:27 2015 +0200

    Add proper temporary cleaning upon frame abortion

commit 1cec2e7271
Author: Bob Weinand <bobwei9@hotmail.com>
Date:   Thu Jun 25 23:33:21 2015 +0200

    Fix arena on small sizes (size < sizeof(zend_arena))
pull/1393/merge
Dmitry Stogov 11 years ago
parent
commit
5ee8413259
  1. 5
      NEWS
  2. 29
      Zend/tests/jump15.phpt
  3. 23
      Zend/tests/temporary_cleaning_001.phpt
  4. 32
      Zend/tests/temporary_cleaning_002.phpt
  5. 19
      Zend/tests/temporary_cleaning_003.phpt
  6. 44
      Zend/tests/temporary_cleaning_004.phpt
  7. 48
      Zend/tests/temporary_cleaning_005.phpt
  8. 203
      Zend/zend_compile.c
  9. 62
      Zend/zend_compile.h
  10. 131
      Zend/zend_execute.c
  11. 237
      Zend/zend_opcode.c
  12. 25
      Zend/zend_vm_def.h
  13. 35
      Zend/zend_vm_execute.h
  14. 2
      Zend/zend_vm_opcodes.c
  15. 1
      Zend/zend_vm_opcodes.h
  16. 83
      ext/opcache/Optimizer/block_pass.c
  17. 16
      ext/opcache/Optimizer/nop_removal.c
  18. 1
      ext/opcache/Optimizer/pass1_5.c
  19. 40
      ext/opcache/Optimizer/zend_optimizer.c
  20. 3
      ext/opcache/Optimizer/zend_optimizer_internal.h
  21. 6
      ext/opcache/zend_file_cache.c
  22. 9
      ext/opcache/zend_persist.c
  23. 8
      ext/opcache/zend_persist_calc.c
  24. 40
      sapi/phpdbg/phpdbg_opcode.c

5
NEWS

@ -12,6 +12,11 @@ PHP NEWS
ArrayAccess object). (Laruence)
. Fixed bug #69957 (Different ways of handling div/mod/intdiv). (Bob)
. Fixed bug #69900 (Too long timeout on pipes). (Anatol)
. Fixed bug #62210 (Exceptions can leak temporary variables. As a part of
the fix serious refactoring was done. op_array->brk_cont_array was removed,
and replaced with more general and speed efficient op_array->T_liveliness.
ZEND_GOTO opcode is always replaced by ZEND_JMP at compile time).
(Bob, Dmitry, Laruence)
- CLI server:
. Fixed bug #69655 (php -S changes MKCALENDAR request method to MKCOL). (cmb)

29
Zend/tests/jump15.phpt

@ -0,0 +1,29 @@
--TEST--
jump 15: goto from loop (forward)
--FILE--
<?php
$ar = array("1","2","3");
foreach ($ar as $val) {
switch ($val) {
case "1":
echo "1: ok\n";
break;
case "2":
echo "2: ok\n";
goto L1;
case "3":
echo "bug\n";
break;
}
}
echo "bug\n";
L1:
try {
echo "3: ok\n";
} finally {
}
?>
--EXPECT--
1: ok
2: ok
3: ok

23
Zend/tests/temporary_cleaning_001.phpt

@ -0,0 +1,23 @@
--TEST--
Temporary leak on exception
--FILE--
<?php
function ops() {
throw new Exception();
}
try {
$x = 2;
$y = new stdClass;
while ($x-- && new stdClass) {
$r = [$x] + ($y ? ((array) $x) + [2] : ops());
$y = (array) $y;
}
} catch (Exception $e) {
}
?>
==DONE==
--EXPECT--
==DONE==

32
Zend/tests/temporary_cleaning_002.phpt

@ -0,0 +1,32 @@
--TEST--
Temporary leak on rope (encapsed string)
--FILE--
<?php
class Obj {
function __get($x) {
throw new Exception();
}
}
$x = new Obj;
$y = 0;
try {
$r = "$y|$x->x|";
} catch (Exception $e) {
}
try {
$r = "$x->x|$y|";
} catch (Exception $e) {
}
try {
$r = "$y|$y|$x->x";
} catch (Exception $e) {
}
?>
==DONE==
--EXPECT--
==DONE==

19
Zend/tests/temporary_cleaning_003.phpt

@ -0,0 +1,19 @@
--TEST--
Fundamental memory leak test on temporaries
--FILE--
<?php
function ops() {
throw new Exception();
}
try{
$x = 1;
$r = [$x] + ops();
} catch (Exception $e) {
}
?>
==DONE==
--EXPECT--
==DONE==

44
Zend/tests/temporary_cleaning_004.phpt

@ -0,0 +1,44 @@
--TEST--
Temporary leak with switch
--FILE--
<?php
function ops() {
throw new Exception();
}
$a = [new stdClass, new stdClass];
switch ($a[0]) {
case false:
break;
default:
try {
$x = 2;
$y = new stdClass;
while ($x-- && new stdClass) {
$r = [$x] + ($y ? ((array) $x) + [2] : ops());
$y = (array) $y;
}
} catch (Exception $e) {
}
}
try {
switch ($a[0]) {
case false:
break;
default:
$x = 2;
$y = new stdClass;
while ($x-- && new stdClass) {
$r = [$x] + ($y ? ((array) $x) + [2] : ops());
$y = (array) $y;
}
}
} catch (Exception $e) {
}
?>
==DONE==
--EXPECT--
==DONE==

48
Zend/tests/temporary_cleaning_005.phpt

@ -0,0 +1,48 @@
--TEST--
Temporary leak with foreach
--FILE--
<?php
function ops() {
throw new Exception();
}
$a = [new stdClass, new stdClass];
foreach ([$a, [new stdClass]] as $b) {
switch ($b[0]) {
case false:
break;
default:
try {
$x = 2;
$y = new stdClass;
while ($x-- && new stdClass) {
$r = [$x] + ($y ? ((array) $x) + [2] : ops());
$y = (array) $y;
}
} catch (Exception $e) {
}
}
}
foreach ([$a, [new stdClass]] as $b) {
try {
switch ($b[0]) {
case false:
break;
default:
$x = 2;
$y = new stdClass;
while ($x-- && new stdClass) {
$r = [$x] + ($y ? ((array) $x) + [2] : ops());
$y = (array) $y;
}
}
} catch (Exception $e) {
}
}
?>
==DONE==
--EXPECT--
==DONE==

203
Zend/zend_compile.c

@ -32,6 +32,7 @@
#include "zend_multibyte.h"
#include "zend_language_scanner.h"
#include "zend_inheritance.h"
#include "zend_vm.h"
#define SET_NODE(target, src) do { \
target ## _type = (src)->op_type; \
@ -210,16 +211,21 @@ void zend_oparray_context_begin(zend_oparray_context *prev_context) /* {{{ */
CG(context).opcodes_size = INITIAL_OP_ARRAY_SIZE;
CG(context).vars_size = 0;
CG(context).literals_size = 0;
CG(context).current_brk_cont = -1;
CG(context).backpatch_count = 0;
CG(context).in_finally = 0;
CG(context).fast_call_var = -1;
CG(context).current_brk_cont = -1;
CG(context).last_brk_cont = 0;
CG(context).brk_cont_array = NULL;
CG(context).labels = NULL;
}
/* }}} */
void zend_oparray_context_end(zend_oparray_context *prev_context) /* {{{ */
{
if (CG(context).brk_cont_array) {
efree(CG(context).brk_cont_array);
}
if (CG(context).labels) {
zend_hash_destroy(CG(context).labels);
FREE_HASHTABLE(CG(context).labels);
@ -562,7 +568,7 @@ static inline void zend_begin_loop(const znode *loop_var) /* {{{ */
zend_brk_cont_element *brk_cont_element;
int parent = CG(context).current_brk_cont;
CG(context).current_brk_cont = CG(active_op_array)->last_brk_cont;
CG(context).current_brk_cont = CG(context).last_brk_cont;
brk_cont_element = get_next_brk_cont_element(CG(active_op_array));
brk_cont_element->parent = parent;
@ -580,7 +586,7 @@ static inline void zend_begin_loop(const znode *loop_var) /* {{{ */
static inline void zend_end_loop(int cont_addr) /* {{{ */
{
zend_brk_cont_element *brk_cont_element
= &CG(active_op_array)->brk_cont_array[CG(context).current_brk_cont];
= &CG(context).brk_cont_array[CG(context).current_brk_cont];
brk_cont_element->cont = cont_addr;
brk_cont_element->brk = get_next_op_number(CG(active_op_array));
CG(context).current_brk_cont = brk_cont_element->parent;
@ -874,61 +880,6 @@ static void str_dtor(zval *zv) /* {{{ */ {
}
/* }}} */
void zend_resolve_goto_label(zend_op_array *op_array, zend_op *opline, int pass2) /* {{{ */
{
zend_label *dest;
int current, distance;
zval *label;
if (pass2) {
label = RT_CONSTANT(op_array, opline->op2);
} else {
label = CT_CONSTANT_EX(op_array, opline->op2.constant);
}
if (CG(context).labels == NULL ||
(dest = zend_hash_find_ptr(CG(context).labels, Z_STR_P(label))) == NULL) {
if (pass2) {
CG(in_compilation) = 1;
CG(active_op_array) = op_array;
CG(zend_lineno) = opline->lineno;
zend_error_noreturn(E_COMPILE_ERROR, "'goto' to undefined label '%s'", Z_STRVAL_P(label));
} else {
/* Label is not defined. Delay to pass 2. */
return;
}
}
opline->op1.opline_num = dest->opline_num;
zval_dtor(label);
ZVAL_NULL(label);
/* Check that we are not moving into loop or switch */
current = opline->extended_value;
for (distance = 0; current != dest->brk_cont; distance++) {
if (current == -1) {
if (pass2) {
CG(in_compilation) = 1;
CG(active_op_array) = op_array;
CG(zend_lineno) = opline->lineno;
}
zend_error_noreturn(E_COMPILE_ERROR, "'goto' into loop or switch statement is disallowed");
}
current = op_array->brk_cont_array[current].parent;
}
if (distance == 0) {
/* Nothing to break out of, optimize to ZEND_JMP */
opline->opcode = ZEND_JMP;
opline->extended_value = 0;
SET_UNUSED(opline->op2);
} else {
/* Set real break distance */
ZVAL_LONG(label, distance);
}
}
/* }}} */
static zend_bool zend_is_call(zend_ast *ast);
static int generate_free_loop_var(znode *var) /* {{{ */
@ -3615,12 +3566,12 @@ void zend_compile_break_continue(zend_ast *ast) /* {{{ */
depth, depth == 1 ? "" : "s");
}
if (nest_level > 1 && CG(active_op_array)->brk_cont_array[array_offset].start >= 0) {
if (nest_level > 1 && CG(context).brk_cont_array[array_offset].start >= 0) {
generate_free_loop_var(loop_var);
loop_var--;
}
array_offset = CG(active_op_array)->brk_cont_array[array_offset].parent;
array_offset = CG(context).brk_cont_array[array_offset].parent;
} while (--nest_level > 0);
}
opline = zend_emit_op(NULL, ast->kind == ZEND_AST_BREAK ? ZEND_BRK : ZEND_CONT, NULL, NULL);
@ -3629,16 +3580,125 @@ void zend_compile_break_continue(zend_ast *ast) /* {{{ */
}
/* }}} */
void zend_resolve_goto_label(zend_op_array *op_array, znode *label_node, zend_op *pass2_opline) /* {{{ */
{
zend_label *dest;
int current, distance, free_vars;
zval *label;
znode *loop_var = NULL;
if (pass2_opline) {
label = RT_CONSTANT(op_array, pass2_opline->op2);
} else {
label = &label_node->u.constant;
}
if (CG(context).labels == NULL ||
(dest = zend_hash_find_ptr(CG(context).labels, Z_STR_P(label))) == NULL) {
if (pass2_opline) {
CG(in_compilation) = 1;
CG(active_op_array) = op_array;
CG(zend_lineno) = pass2_opline->lineno;
zend_error_noreturn(E_COMPILE_ERROR, "'goto' to undefined label '%s'", Z_STRVAL_P(label));
} else {
/* Label is not defined. Delay to pass 2. */
zend_op *opline;
current = CG(context).current_brk_cont;
while (current != -1) {
if (CG(context).brk_cont_array[current].start >= 0) {
zend_emit_op(NULL, ZEND_NOP, NULL, NULL);
}
current = CG(context).brk_cont_array[current].parent;
}
opline = zend_emit_op(NULL, ZEND_GOTO, NULL, label_node);
opline->extended_value = CG(context).current_brk_cont;
return;
}
}
zval_dtor(label);
ZVAL_NULL(label);
/* Check that we are not moving into loop or switch */
if (pass2_opline) {
current = pass2_opline->extended_value;
} else {
current = CG(context).current_brk_cont;
}
if (!pass2_opline) {
loop_var = zend_stack_top(&CG(loop_var_stack));
}
for (distance = 0, free_vars = 0; current != dest->brk_cont; distance++) {
if (current == -1) {
if (pass2_opline) {
CG(in_compilation) = 1;
CG(active_op_array) = op_array;
CG(zend_lineno) = pass2_opline->lineno;
}
zend_error_noreturn(E_COMPILE_ERROR, "'goto' into loop or switch statement is disallowed");
}
if (CG(context).brk_cont_array[current].start >= 0) {
if (pass2_opline) {
free_vars++;
} else {
generate_free_loop_var(loop_var);
loop_var--;
}
}
current = CG(context).brk_cont_array[current].parent;
}
if (pass2_opline) {
if (free_vars) {
current = pass2_opline->extended_value;
while (current != dest->brk_cont) {
if (CG(context).brk_cont_array[current].start >= 0) {
zend_op *brk_opline = &op_array->opcodes[CG(context).brk_cont_array[current].brk];
if (brk_opline->opcode == ZEND_FREE) {
(pass2_opline - free_vars)->opcode = ZEND_FREE;
(pass2_opline - free_vars)->op1_type = brk_opline->op1_type;
if (op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK) {
(pass2_opline - free_vars)->op1.var = brk_opline->op1.var;
} else {
(pass2_opline - free_vars)->op1.var = (uint32_t)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, op_array->last_var + brk_opline->op1.var);
ZEND_VM_SET_OPCODE_HANDLER(pass2_opline - free_vars);
}
free_vars--;
} else if (brk_opline->opcode == ZEND_FE_FREE) {
(pass2_opline - free_vars)->opcode = ZEND_FE_FREE;
(pass2_opline - free_vars)->op1_type = brk_opline->op1_type;
if (op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK) {
(pass2_opline - free_vars)->op1.var = brk_opline->op1.var;
} else {
(pass2_opline - free_vars)->op1.var = (uint32_t)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, op_array->last_var + brk_opline->op1.var);
ZEND_VM_SET_OPCODE_HANDLER(pass2_opline - free_vars);
}
free_vars--;
}
}
current = CG(context).brk_cont_array[current].parent;
}
}
pass2_opline->opcode = ZEND_JMP;
pass2_opline->op1.opline_num = dest->opline_num;
SET_UNUSED(pass2_opline->op2);
pass2_opline->extended_value = 0;
} else {
zend_op *opline = zend_emit_op(NULL, ZEND_JMP, NULL, NULL);
opline->op1.opline_num = dest->opline_num;
}
}
/* }}} */
void zend_compile_goto(zend_ast *ast) /* {{{ */
{
zend_ast *label_ast = ast->child[0];
znode label_node;
zend_op *opline;
zend_compile_expr(&label_node, label_ast);
opline = zend_emit_op(NULL, ZEND_GOTO, NULL, &label_node);
opline->extended_value = CG(context).current_brk_cont;
zend_resolve_goto_label(CG(active_op_array), opline, 0);
zend_resolve_goto_label(CG(active_op_array), &label_node, NULL);
}
/* }}} */
@ -6278,10 +6338,7 @@ void zend_compile_silence(znode *result, zend_ast *ast) /* {{{ */
{
zend_ast *expr_ast = ast->child[0];
znode silence_node;
uint32_t begin_opline_num, end_opline_num;
zend_brk_cont_element *brk_cont_element;
begin_opline_num = get_next_op_number(CG(active_op_array));
zend_emit_op_tmp(&silence_node, ZEND_BEGIN_SILENCE, NULL, NULL);
if (expr_ast->kind == ZEND_AST_VAR) {
@ -6292,15 +6349,7 @@ void zend_compile_silence(znode *result, zend_ast *ast) /* {{{ */
zend_compile_expr(result, expr_ast);
}
end_opline_num = get_next_op_number(CG(active_op_array));
zend_emit_op(NULL, ZEND_END_SILENCE, &silence_node, NULL);
/* Store BEGIN_SILENCE/END_SILENCE pair to restore previous
* EG(error_reporting) value on exception */
brk_cont_element = get_next_brk_cont_element(CG(active_op_array));
brk_cont_element->start = begin_opline_num;
brk_cont_element->cont = brk_cont_element->brk = end_opline_num;
brk_cont_element->parent = -1;
}
/* }}} */
@ -6628,10 +6677,6 @@ static void zend_compile_encaps_list(znode *result, zend_ast *ast) /* {{{ */
GET_NODE(result, opline->result);
} else {
uint32_t var;
zend_brk_cont_element *info = get_next_brk_cont_element(CG(active_op_array));
info->start = rope_init_lineno;
info->parent = CG(context).current_brk_cont;
info->cont = info->brk = opline - CG(active_op_array)->opcodes;
init_opline->extended_value = j;
opline->opcode = ZEND_ROPE_END;

62
Zend/zend_compile.h

@ -109,15 +109,36 @@ typedef struct _zend_declarables {
zend_long ticks;
} zend_declarables;
typedef struct _zend_brk_cont_element {
int start;
int cont;
int brk;
int parent;
} zend_brk_cont_element;
typedef struct _zend_label {
int brk_cont;
uint32_t opline_num;
} zend_label;
typedef struct _zend_try_catch_element {
uint32_t try_op;
uint32_t catch_op; /* ketchup! */
uint32_t finally_op;
uint32_t finally_end;
} zend_try_catch_element;
/* Compilation context that is different for each op array. */
typedef struct _zend_oparray_context {
uint32_t opcodes_size;
int vars_size;
int literals_size;
int current_brk_cont;
int backpatch_count;
int in_finally;
uint32_t fast_call_var;
int current_brk_cont;
int last_brk_cont;
zend_brk_cont_element *brk_cont_array;
HashTable *labels;
} zend_oparray_context;
@ -163,26 +184,6 @@ struct _zend_op {
zend_uchar result_type;
};
typedef struct _zend_brk_cont_element {
int start;
int cont;
int brk;
int parent;
} zend_brk_cont_element;
typedef struct _zend_label {
int brk_cont;
uint32_t opline_num;
} zend_label;
typedef struct _zend_try_catch_element {
uint32_t try_op;
uint32_t catch_op; /* ketchup! */
uint32_t finally_op;
uint32_t finally_end;
} zend_try_catch_element;
/* method flags (types) */
#define ZEND_ACC_STATIC 0x01
#define ZEND_ACC_ABSTRACT 0x02
@ -328,6 +329,11 @@ typedef struct _zend_internal_function_info {
zend_bool _is_variadic;
} zend_internal_function_info;
#define ZEND_LIVE_ROPE 1
#define ZEND_LIVE_SILENCE 2
#define ZEND_LIVE_LOOP 3
#define ZEND_LIVE_MASK 3
struct _zend_op_array {
/* Common elements */
zend_uchar type;
@ -351,10 +357,12 @@ struct _zend_op_array {
int last_var;
uint32_t T;
zend_string **vars;
uint32_t *T_liveliness;
void **run_time_cache;
int cache_size;
int last_brk_cont;
int last_try_catch;
zend_brk_cont_element *brk_cont_array;
zend_try_catch_element *try_catch_array;
/* static variables support */
@ -369,9 +377,6 @@ struct _zend_op_array {
int last_literal;
zval *literals;
int cache_size;
void **run_time_cache;
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
};
@ -712,7 +717,7 @@ void zend_do_extended_fcall_end(void);
void zend_verify_namespace(void);
void zend_resolve_goto_label(zend_op_array *op_array, zend_op *opline, int pass2);
void zend_resolve_goto_label(zend_op_array *op_array, znode *label_node, zend_op *pass2_opline);
ZEND_API void function_add_ref(zend_function *function);
@ -727,6 +732,8 @@ ZEND_API int zend_execute_scripts(int type, zval *retval, int file_count, ...);
ZEND_API int open_file_for_scanning(zend_file_handle *file_handle);
ZEND_API void init_op_array(zend_op_array *op_array, zend_uchar type, int initial_ops_size);
ZEND_API void destroy_op_array(zend_op_array *op_array);
ZEND_API void zend_generate_var_liveliness_info(zend_op_array *op_array);
ZEND_API void zend_regenerate_var_liveliness_info(zend_op_array *op_array);
ZEND_API void zend_destroy_file_handle(zend_file_handle *file_handle);
ZEND_API void zend_cleanup_user_class_data(zend_class_entry *ce);
ZEND_API void zend_cleanup_internal_class_data(zend_class_entry *ce);
@ -951,6 +958,7 @@ static zend_always_inline int zend_check_arg_send_type(const zend_function *zf,
#define ZEND_ARRAY_SIZE_SHIFT 2
/* Pseudo-opcodes that are used only temporarily during compilation */
#define ZEND_GOTO 253
#define ZEND_BRK 254
#define ZEND_CONT 255

131
Zend/zend_execute.c

@ -1939,31 +1939,6 @@ static zend_always_inline void zend_fetch_property_address(zval *result, zval *c
}
}
static inline zend_brk_cont_element* zend_brk_cont(int nest_levels, int array_offset, const zend_op_array *op_array, const zend_execute_data *execute_data)
{
zend_brk_cont_element *jmp_to;
do {
ZEND_ASSERT(array_offset != -1);
jmp_to = &op_array->brk_cont_array[array_offset];
if (nest_levels > 1 && jmp_to->start >= 0) {
zend_op *brk_opline = &op_array->opcodes[jmp_to->brk];
if (brk_opline->opcode == ZEND_FREE) {
zval_ptr_dtor_nogc(EX_VAR(brk_opline->op1.var));
} else if (brk_opline->opcode == ZEND_FE_FREE) {
zval *var = EX_VAR(brk_opline->op1.var);
if (Z_TYPE_P(var) != IS_ARRAY && Z_FE_ITER_P(var) != (uint32_t)-1) {
zend_hash_iterator_del(Z_FE_ITER_P(var));
}
zval_ptr_dtor_nogc(var);
}
}
array_offset = jmp_to->parent;
} while (--nest_levels > 0);
return jmp_to;
}
#if ZEND_INTENSIVE_DEBUGGING
#define CHECK_SYMBOL_TABLES() \
@ -2384,7 +2359,66 @@ static zend_always_inline zend_generator *zend_get_running_generator(zend_execut
static zend_always_inline void i_cleanup_unfinished_execution(zend_execute_data *execute_data, uint32_t op_num, uint32_t catch_op_num) /* {{{ */
{
int i;
if (EX(func)->op_array.T_liveliness
&& op_num < EX(func)->op_array.last
&& EX(func)->op_array.T_liveliness[op_num] != (uint32_t)-1) {
uint32_t *off = EX(func)->op_array.T_liveliness + EX(func)->op_array.T_liveliness[op_num];
uint32_t *catch_off = NULL;
uint32_t var = *off;
if (catch_op_num && EX(func)->op_array.T_liveliness[catch_op_num] != (uint32_t)-1) {
catch_off = EX(func)->op_array.T_liveliness + EX(func)->op_array.T_liveliness[catch_op_num];
}
do {
/* we should be safe to assume that all temporaries at catch_op_num will be present at op_num too, in same order */
if (catch_off && *catch_off == var) {
catch_off++;
var = *(++off);
continue;
}
if ((var & ZEND_LIVE_MASK) == ZEND_LIVE_ROPE) {
/* free incomplete rope */
zend_string **rope;
zend_op *last;
var = var & ~ZEND_LIVE_ROPE;
rope = (zend_string **) EX_VAR(var);
last = EX(func)->op_array.opcodes + op_num;
while ((last->opcode != ZEND_ROPE_ADD && last->opcode != ZEND_ROPE_INIT)
|| last->result.var != var) {
ZEND_ASSERT(last >= EX(func)->op_array.opcodes);
last--;
}
if (last->opcode == ZEND_ROPE_INIT) {
zend_string_release(*rope);
} else {
int j = last->extended_value;
do {
zend_string_release(rope[j]);
} while (j--);
}
} else if ((var & ZEND_LIVE_MASK) == ZEND_LIVE_SILENCE) {
/* restore previous error_reporting value */
var = var & ~ZEND_LIVE_SILENCE;
if (!EG(error_reporting) && Z_LVAL_P(EX_VAR(var)) != 0) {
EG(error_reporting) = Z_LVAL_P(EX_VAR(var));
}
} else if ((var & ZEND_LIVE_MASK) == ZEND_LIVE_LOOP) {
/* free loop variables */
var = var & ~ZEND_LIVE_LOOP;
if (Z_TYPE_P(EX_VAR(var)) != IS_ARRAY && Z_FE_ITER_P(EX_VAR(var)) != (uint32_t) -1) {
zend_hash_iterator_del(Z_FE_ITER_P(EX_VAR(var)));
}
zval_ptr_dtor_nogc(EX_VAR(var));
} else {
zval_ptr_dtor_nogc(EX_VAR(var));
}
var = *(++off);
} while (var != (uint32_t)-1);
}
if (UNEXPECTED(EX(call))) {
zend_execute_data *call = EX(call);
zend_op *opline = EX(func)->op_array.opcodes + op_num;
@ -2498,51 +2532,6 @@ static zend_always_inline void i_cleanup_unfinished_execution(zend_execute_data
call = EX(call);
} while (call);
}
for (i = 0; i < EX(func)->op_array.last_brk_cont; i++) {
const zend_brk_cont_element *brk_cont = &EX(func)->op_array.brk_cont_array[i];
if (brk_cont->start < 0) {
continue;
} else if (brk_cont->start > op_num) {
/* further blocks will not be relevant... */
break;
} else if (op_num < brk_cont->brk) {
if (!catch_op_num || catch_op_num >= brk_cont->brk) {
zend_op *brk_opline = &EX(func)->op_array.opcodes[brk_cont->brk];
if (brk_opline->opcode == ZEND_FREE) {
zval_ptr_dtor_nogc(EX_VAR(brk_opline->op1.var));
} else if (brk_opline->opcode == ZEND_FE_FREE) {
zval *var = EX_VAR(brk_opline->op1.var);
if (Z_TYPE_P(var) != IS_ARRAY && Z_FE_ITER_P(var) != (uint32_t)-1) {
zend_hash_iterator_del(Z_FE_ITER_P(var));
}
zval_ptr_dtor_nogc(var);
} else if (brk_opline->opcode == ZEND_ROPE_END) {
zend_string **rope = (zend_string **) EX_VAR(brk_opline->op1.var);
zend_op *last = EX(func)->op_array.opcodes + op_num;
while ((last->opcode != ZEND_ROPE_ADD && last->opcode != ZEND_ROPE_INIT)
|| last->result.var != brk_opline->op1.var) {
ZEND_ASSERT(last >= EX(func)->op_array.opcodes);
last--;
}
if (last->opcode == ZEND_ROPE_INIT) {
zend_string_release(*rope);
} else {
int j = last->extended_value;
do {
zend_string_release(rope[j]);
} while (j--);
}
} else if (brk_opline->opcode == ZEND_END_SILENCE) {
/* restore previous error_reporting value */
if (!EG(error_reporting) && Z_LVAL_P(EX_VAR(brk_opline->op1.var)) != 0) {
EG(error_reporting) = Z_LVAL_P(EX_VAR(brk_opline->op1.var));
}
}
}
}
}
}
/* }}} */

237
Zend/zend_opcode.c

@ -65,6 +65,7 @@ void init_op_array(zend_op_array *op_array, zend_uchar type, int initial_ops_siz
op_array->vars = NULL;
op_array->T = 0;
op_array->T_liveliness = NULL;
op_array->function_name = NULL;
op_array->filename = zend_get_compiled_filename();
@ -77,9 +78,7 @@ void init_op_array(zend_op_array *op_array, zend_uchar type, int initial_ops_siz
op_array->scope = NULL;
op_array->prototype = NULL;
op_array->brk_cont_array = NULL;
op_array->try_catch_array = NULL;
op_array->last_brk_cont = 0;
op_array->static_variables = NULL;
op_array->last_try_catch = 0;
@ -383,12 +382,12 @@ ZEND_API void destroy_op_array(zend_op_array *op_array)
if (op_array->doc_comment) {
zend_string_release(op_array->doc_comment);
}
if (op_array->brk_cont_array) {
efree(op_array->brk_cont_array);
}
if (op_array->try_catch_array) {
efree(op_array->try_catch_array);
}
if (op_array->T_liveliness) {
efree(op_array->T_liveliness);
}
if (op_array->fn_flags & ZEND_ACC_DONE_PASS_TWO) {
zend_llist_apply_with_argument(&zend_extensions, (llist_apply_with_arg_func_t) zend_extension_op_array_dtor_handler, op_array);
}
@ -447,9 +446,9 @@ int get_next_op_number(zend_op_array *op_array)
zend_brk_cont_element *get_next_brk_cont_element(zend_op_array *op_array)
{
op_array->last_brk_cont++;
op_array->brk_cont_array = erealloc(op_array->brk_cont_array, sizeof(zend_brk_cont_element)*op_array->last_brk_cont);
return &op_array->brk_cont_array[op_array->last_brk_cont-1];
CG(context).last_brk_cont++;
CG(context).brk_cont_array = erealloc(CG(context).brk_cont_array, sizeof(zend_brk_cont_element)*CG(context).last_brk_cont);
return &CG(context).brk_cont_array[CG(context).last_brk_cont-1];
}
static void zend_update_extended_info(zend_op_array *op_array)
@ -576,7 +575,7 @@ static void zend_resolve_finally_call(zend_op_array *op_array, uint32_t op_num,
fast_call_var = op_array->opcodes[op_array->try_catch_array[i].finally_end].op1.var;
/* generate a FAST_CALL to finally block */
start_op = get_next_op_number(op_array);
start_op = get_next_op_number(op_array);
opline = get_next_op(op_array);
opline->opcode = ZEND_FAST_CALL;
@ -672,7 +671,7 @@ static uint32_t zend_get_brk_cont_target(const zend_op_array *op_array, const ze
int array_offset = opline->op1.num;
zend_brk_cont_element *jmp_to;
do {
jmp_to = &op_array->brk_cont_array[array_offset];
jmp_to = &CG(context).brk_cont_array[array_offset];
if (nest_levels > 1) {
array_offset = jmp_to->parent;
}
@ -700,11 +699,8 @@ static void zend_resolve_finally_calls(zend_op_array *op_array)
break;
case ZEND_GOTO:
if (Z_TYPE_P(CT_CONSTANT_EX(op_array, opline->op2.constant)) != IS_LONG) {
uint32_t num = opline->op2.constant;
ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline->op2);
zend_resolve_goto_label(op_array, opline, 1);
opline->op2.constant = num;
zend_resolve_goto_label(op_array, NULL, opline);
}
/* break omitted intentionally */
case ZEND_JMP:
@ -751,6 +747,9 @@ ZEND_API int pass_two(zend_op_array *op_array)
op_array->literals = (zval*)erealloc(op_array->literals, sizeof(zval) * op_array->last_literal);
CG(context).literals_size = op_array->last_literal;
}
zend_generate_var_liveliness_info(op_array);
opline = op_array->opcodes;
end = opline + op_array->last;
while (opline < end) {
@ -787,7 +786,7 @@ ZEND_API int pass_two(zend_op_array *op_array)
break;
case ZEND_GOTO:
if (Z_TYPE_P(RT_CONSTANT(op_array, opline->op2)) != IS_LONG) {
zend_resolve_goto_label(op_array, opline, 1);
zend_resolve_goto_label(op_array, NULL, opline);
}
/* break omitted intentionally */
case ZEND_JMP:
@ -840,6 +839,214 @@ int pass_two_wrapper(zval *el)
return pass_two((zend_op_array *) Z_PTR_P(el));
}
/* The following liveliness analyzing algorithm assumes that
* 1) temporary variables are defined before use
* 2) they have linear live-ranges without "holes"
* 3) Opcodes never use and define the same temorary variables
*/
typedef struct _op_var_info {
struct _op_var_info *next;
uint32_t var;
} op_var_info;
static zend_always_inline uint32_t liveliness_kill_var(zend_op_array *op_array, zend_op *cur_op, uint32_t var, uint32_t *Tstart, op_var_info **opTs)
{
uint32_t start = Tstart[var];
uint32_t end = cur_op - op_array->opcodes;
uint32_t count = 0;
uint32_t var_offset, j;
Tstart[var] = -1;
if (cur_op->opcode == ZEND_OP_DATA) {
end--;
}
start++;
if (op_array->opcodes[start].opcode == ZEND_OP_DATA
|| op_array->opcodes[start].opcode == ZEND_FE_FETCH_R
|| op_array->opcodes[start].opcode == ZEND_FE_FETCH_RW) {
start++;
}
if (start < end) {
op_var_info *new_opTs;
var_offset = (uint32_t)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, op_array->last_var + var);
if (op_array->opcodes[end].opcode == ZEND_ROPE_END) {
var_offset |= ZEND_LIVE_ROPE;
} else if (op_array->opcodes[end].opcode == ZEND_END_SILENCE) {
var_offset |= ZEND_LIVE_SILENCE;
} else if (op_array->opcodes[end].opcode == ZEND_FE_FREE) {
var_offset |= ZEND_LIVE_LOOP;
}
if (opTs[start]) {
if (start > 0 && opTs[start-1] == opTs[start]) {
op_var_info *opT = opTs[start];
do {
count++;
opT = opT->next;
} while (opT);
count += 2;
} else {
count++;
}
} else {
count += 2;
}
new_opTs = zend_arena_alloc(&CG(arena), sizeof(op_var_info));
new_opTs->next = opTs[start];
new_opTs->var = var_offset;
opTs[start] = new_opTs;
for (j = start + 1; j < end; j++) {
if (opTs[j-1]->next == opTs[j]) {
opTs[j] = opTs[j-1];
} else {
if (opTs[j]) {
count++;
} else {
count += 2;
}
new_opTs = zend_arena_alloc(&CG(arena), sizeof(op_var_info));
new_opTs->next = opTs[j];
new_opTs->var = var_offset;
opTs[j] = new_opTs;
}
}
}
return count;
}
static zend_always_inline uint32_t *generate_var_liveliness_info_ex(zend_op_array *op_array, zend_bool done_pass_two)
{
zend_op *opline, *end;
uint32_t var, i, op_live_total = 0;
uint32_t *info, info_off = op_array->last + 1;
void *checkpoint = zend_arena_checkpoint(CG(arena));
uint32_t *Tstart = zend_arena_alloc(&CG(arena), sizeof(uint32_t) * op_array->T);
op_var_info **opTs = zend_arena_alloc(&CG(arena), sizeof(op_var_info *) * op_array->last);
memset(Tstart, -1, sizeof(uint32_t) * op_array->T);
memset(opTs, 0, sizeof(op_var_info *) * op_array->last);
opline = op_array->opcodes;
end = opline + op_array->last;
do {
if ((opline->result_type & (IS_VAR|IS_TMP_VAR))
&& !((opline)->result_type & EXT_TYPE_UNUSED)
/* the following opcodes are used in inline branching
* (and anyway always bool, so no need to free) and may
* not be defined depending on the taken branch */
&& opline->opcode != ZEND_BOOL
&& opline->opcode != ZEND_JMPZ_EX
&& opline->opcode != ZEND_JMPNZ_EX
/* these two consecutive ops appear on ternary,
* the result of true branch is undefined for false branch */
&& (opline->opcode != ZEND_QM_ASSIGN || (opline + 1)->opcode != ZEND_JMP)
/* exception for opcache, it might nowhere use the temporary
* (anyway bool, so no need to free) */
&& opline->opcode != ZEND_CASE
/* the following opcodes reuse TMP created before */
&& opline->opcode != ZEND_ROPE_ADD
&& opline->opcode != ZEND_ADD_ARRAY_ELEMENT
/* passes fast_call */
&& opline->opcode != ZEND_FAST_CALL
/* the following opcodes pass class_entry */
&& opline->opcode != ZEND_FETCH_CLASS
&& opline->opcode != ZEND_DECLARE_CLASS
&& opline->opcode != ZEND_DECLARE_INHERITED_CLASS
&& opline->opcode != ZEND_DECLARE_INHERITED_CLASS_DELAYED
&& opline->opcode != ZEND_DECLARE_ANON_CLASS
&& opline->opcode != ZEND_DECLARE_ANON_INHERITED_CLASS) {
if (done_pass_two) {
var = EX_VAR_TO_NUM(opline->result.var) - op_array->last_var;
} else {
var = opline->result.var;
}
/* Objects created via ZEND_NEW are only fully initialized after the DO_FCALL (constructor call) */
if (opline->opcode == ZEND_NEW) {
Tstart[var] = opline->op2.opline_num - 1;
} else {
Tstart[var] = opline - op_array->opcodes;
}
}
if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) {
if (done_pass_two) {
var = EX_VAR_TO_NUM(opline->op1.var) - op_array->last_var;
} else {
var = opline->op1.var;
}
if (Tstart[var] != (uint32_t)-1
/* the following opcodes don't free TMP */
&& opline->opcode != ZEND_ROPE_ADD
&& opline->opcode != ZEND_FETCH_LIST
&& opline->opcode != ZEND_CASE
&& opline->opcode != ZEND_FE_FETCH_R
&& opline->opcode != ZEND_FE_FETCH_RW) {
op_live_total += liveliness_kill_var(op_array, opline, var, Tstart, opTs);
}
}
if (opline->op2_type & (IS_VAR|IS_TMP_VAR)) {
if (done_pass_two) {
var = EX_VAR_TO_NUM(opline->op2.var) - op_array->last_var;
} else {
var = opline->op2.var;
}
if (Tstart[var] != (uint32_t)-1) {
op_live_total += liveliness_kill_var(op_array, opline, var, Tstart, opTs);
}
}
} while (++opline != end);
#if ZEND_DEBUG
/* Check that all TMP variable live-ranges are closed */
for (i = 0; i < op_array->T; i++) {
ZEND_ASSERT(Tstart[i] == (uint32_t)-1);
}
#endif
if (!op_live_total) {
info = NULL;
} else {
info = emalloc((op_array->last + 1 + op_live_total) * sizeof(uint32_t));
for (i = 0; i < op_array->last; i++) {
if (!opTs[i]) {
info[i] = (uint32_t)-1;
} else if (i > 0 && opTs[i-1] == opTs[i]) {
info[i] = info[i-1];
} else {
op_var_info *opT = opTs[i];
info[i] = info_off;
while (opT) {
info[info_off++] = opT->var;
opT = opT->next;
}
info[info_off++] = (uint32_t)-1;
}
}
info[op_array->last] = info_off;
ZEND_ASSERT(info_off == op_array->last + 1 + op_live_total);
}
zend_arena_release(&CG(arena), checkpoint);
return info;
}
ZEND_API void zend_generate_var_liveliness_info(zend_op_array *op_array)
{
op_array->T_liveliness = generate_var_liveliness_info_ex(op_array, 0);
}
ZEND_API void zend_regenerate_var_liveliness_info(zend_op_array *op_array)
{
if (op_array->T_liveliness) {
efree(op_array->T_liveliness);
}
op_array->T_liveliness = generate_var_liveliness_info_ex(op_array, 1);
}
int print_class(zend_class_entry *class_entry)
{
printf("Class %s:\n", ZSTR_VAL(class_entry->name));

25
Zend/zend_vm_def.h

@ -4866,31 +4866,6 @@ ZEND_VM_HANDLER(52, ZEND_BOOL, CONST|TMPVAR|CV, ANY)
ZEND_VM_NEXT_OPCODE();
}
ZEND_VM_HANDLER(100, ZEND_GOTO, ANY, CONST)
{
USE_OPLINE
zend_brk_cont_element *el;
SAVE_OPLINE();
el = zend_brk_cont(Z_LVAL_P(EX_CONSTANT(opline->op2)), opline->extended_value,
&EX(func)->op_array, execute_data);
if (el->start >= 0) {
zend_op *brk_opline = EX(func)->op_array.opcodes + el->brk;
if (brk_opline->opcode == ZEND_FREE) {
zval_ptr_dtor_nogc(EX_VAR(brk_opline->op1.var));
} else if (brk_opline->opcode == ZEND_FE_FREE) {
zval *var = EX_VAR(brk_opline->op1.var);
if (Z_TYPE_P(var) != IS_ARRAY && Z_FE_ITER_P(var) != (uint32_t)-1) {
zend_hash_iterator_del(Z_FE_ITER_P(var));
}
zval_ptr_dtor_nogc(var);
}
}
ZEND_VM_JMP(OP_JMP_ADDR(opline, opline->op1));
}
ZEND_VM_HANDLER(48, ZEND_CASE, CONST|TMPVAR|CV, CONST|TMPVAR|CV)
{
USE_OPLINE

35
Zend/zend_vm_execute.h

@ -2225,31 +2225,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_RECV_INIT_SPEC_CONST_HANDLER(Z
ZEND_VM_NEXT_OPCODE();
}
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_GOTO_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zend_brk_cont_element *el;
SAVE_OPLINE();
el = zend_brk_cont(Z_LVAL_P(EX_CONSTANT(opline->op2)), opline->extended_value,
&EX(func)->op_array, execute_data);
if (el->start >= 0) {
zend_op *brk_opline = EX(func)->op_array.opcodes + el->brk;
if (brk_opline->opcode == ZEND_FREE) {
zval_ptr_dtor_nogc(EX_VAR(brk_opline->op1.var));
} else if (brk_opline->opcode == ZEND_FE_FREE) {
zval *var = EX_VAR(brk_opline->op1.var);
if (Z_TYPE_P(var) != IS_ARRAY && Z_FE_ITER_P(var) != (uint32_t)-1) {
zend_hash_iterator_del(Z_FE_ITER_P(var));
}
zval_ptr_dtor_nogc(var);
}
}
ZEND_VM_JMP(OP_JMP_ADDR(opline, opline->op1));
}
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ADD_INTERFACE_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
@ -48422,27 +48397,27 @@ void zend_init_opcodes_handlers(void)
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_GOTO_SPEC_CONST_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_GOTO_SPEC_CONST_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_GOTO_SPEC_CONST_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_GOTO_SPEC_CONST_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_GOTO_SPEC_CONST_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,

2
Zend/zend_vm_opcodes.c

@ -122,7 +122,7 @@ const char *zend_vm_opcodes_map[173] = {
"ZEND_FETCH_OBJ_UNSET",
"ZEND_FETCH_LIST",
"ZEND_FETCH_CONSTANT",
"ZEND_GOTO",
NULL,
"ZEND_EXT_STMT",
"ZEND_EXT_FCALL_BEGIN",
"ZEND_EXT_FCALL_END",

1
Zend/zend_vm_opcodes.h

@ -130,7 +130,6 @@ END_EXTERN_C()
#define ZEND_FETCH_OBJ_UNSET 97
#define ZEND_FETCH_LIST 98
#define ZEND_FETCH_CONSTANT 99
#define ZEND_GOTO 100
#define ZEND_EXT_STMT 101
#define ZEND_EXT_FCALL_BEGIN 102
#define ZEND_EXT_FCALL_END 103

83
ext/opcache/Optimizer/block_pass.c

@ -123,10 +123,6 @@ static int find_code_blocks(zend_op_array *op_array, zend_cfg *cfg, zend_optimiz
blocks[0].start_opline_no = 0;
while (opline < end) {
switch((unsigned)opline->opcode) {
case ZEND_GOTO:
/* would not optimize GOTOs - we cannot really know where it jumps,
* so these optimizations are too dangerous */
return 0;
case ZEND_FAST_CALL:
START_BLOCK_OP(ZEND_OP1(opline).opline_num);
if (opline->extended_value) {
@ -197,65 +193,6 @@ static int find_code_blocks(zend_op_array *op_array, zend_cfg *cfg, zend_optimiz
blocks[op_array->try_catch_array[i].try_op].protected = 1;
}
}
/* Currently, we don't optimize op_arrays with BRK/CONT/GOTO opcodes,
* but, we have to keep brk_cont_array to avoid memory leaks during
* exception handling */
if (op_array->last_brk_cont) {
int i, j;
j = 0;
for (i = 0; i< op_array->last_brk_cont; i++) {
if (op_array->brk_cont_array[i].start >= 0 &&
(op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_FREE ||
op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_FE_FREE ||
op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_ROPE_END ||
op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_END_SILENCE)) {
int parent = op_array->brk_cont_array[i].parent;
while (parent >= 0 &&
op_array->brk_cont_array[parent].start < 0 &&
(op_array->opcodes[op_array->brk_cont_array[parent].brk].opcode != ZEND_FREE ||
op_array->opcodes[op_array->brk_cont_array[parent].brk].opcode != ZEND_FE_FREE ||
op_array->opcodes[op_array->brk_cont_array[i].brk].opcode != ZEND_ROPE_END ||
op_array->opcodes[op_array->brk_cont_array[parent].brk].opcode != ZEND_END_SILENCE)) {
parent = op_array->brk_cont_array[parent].parent;
}
op_array->brk_cont_array[i].parent = parent;
j++;
}
}
if (j) {
cfg->loop_start = zend_arena_calloc(&ctx->arena, op_array->last_brk_cont, sizeof(zend_code_block *));
cfg->loop_cont = zend_arena_calloc(&ctx->arena, op_array->last_brk_cont, sizeof(zend_code_block *));
cfg->loop_brk = zend_arena_calloc(&ctx->arena, op_array->last_brk_cont, sizeof(zend_code_block *));
j = 0;
for (i = 0; i< op_array->last_brk_cont; i++) {
if (op_array->brk_cont_array[i].start >= 0 &&
(op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_FREE ||
op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_FE_FREE ||
op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_ROPE_END ||
op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_END_SILENCE)) {
if (i != j) {
op_array->brk_cont_array[j] = op_array->brk_cont_array[i];
}
cfg->loop_start[j] = &blocks[op_array->brk_cont_array[j].start];
cfg->loop_cont[j] = &blocks[op_array->brk_cont_array[j].cont];
cfg->loop_brk[j] = &blocks[op_array->brk_cont_array[j].brk];
START_BLOCK_OP(op_array->brk_cont_array[j].start);
START_BLOCK_OP(op_array->brk_cont_array[j].cont);
START_BLOCK_OP(op_array->brk_cont_array[j].brk);
blocks[op_array->brk_cont_array[j].start].protected = 1;
blocks[op_array->brk_cont_array[j].brk].protected = 1;
j++;
}
}
op_array->last_brk_cont = j;
} else {
efree(op_array->brk_cont_array);
op_array->brk_cont_array = NULL;
op_array->last_brk_cont = 0;
}
}
/* Build CFG (Control Flow Graph) */
cur_block = blocks;
@ -523,16 +460,6 @@ static void zend_rebuild_access_path(zend_cfg *cfg, zend_op_array *op_array, int
/* Walk thorough all paths */
zend_access_path(start, ctx);
/* Add brk/cont paths */
if (op_array->last_brk_cont) {
int i;
for (i=0; i< op_array->last_brk_cont; i++) {
zend_access_path(cfg->loop_start[i], ctx);
zend_access_path(cfg->loop_cont[i], ctx);
zend_access_path(cfg->loop_brk[i], ctx);
}
}
/* Add exception paths */
if (op_array->last_try_catch) {
int i;
@ -1196,16 +1123,6 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array)
op_array->last_try_catch = j;
}
/* adjust loop jump targets */
if (op_array->last_brk_cont) {
int i;
for (i = 0; i< op_array->last_brk_cont; i++) {
op_array->brk_cont_array[i].start = cfg->loop_start[i]->start_opline - new_opcodes;
op_array->brk_cont_array[i].cont = cfg->loop_cont[i]->start_opline - new_opcodes;
op_array->brk_cont_array[i].brk = cfg->loop_brk[i]->start_opline - new_opcodes;
}
}
/* adjust jump targets */
for (cur_block = blocks; cur_block; cur_block = cur_block->next) {
if (!cur_block->access) {

16
ext/opcache/Optimizer/nop_removal.c

@ -44,14 +44,6 @@ void zend_optimizer_nop_removal(zend_op_array *op_array)
end = op_array->opcodes + op_array->last;
for (opline = op_array->opcodes; opline < end; opline++) {
/* GOTO target is unresolved yet. We can't optimize. */
if (opline->opcode == ZEND_GOTO &&
Z_TYPE(ZEND_OP2_LITERAL(opline)) != IS_LONG) {
/* TODO: in general we can avoid this restriction */
FREE_ALLOCA(shiftlist);
return;
}
/* Kill JMP-over-NOP-s */
if (opline->opcode == ZEND_JMP && ZEND_OP1(opline).opline_num > i) {
/* check if there are only NOPs under the branch */
@ -85,7 +77,6 @@ void zend_optimizer_nop_removal(zend_op_array *op_array)
for (opline = op_array->opcodes; opline<end; opline++) {
switch (opline->opcode) {
case ZEND_JMP:
case ZEND_GOTO:
case ZEND_FAST_CALL:
case ZEND_DECLARE_ANON_CLASS:
case ZEND_DECLARE_ANON_INHERITED_CLASS:
@ -117,13 +108,6 @@ void zend_optimizer_nop_removal(zend_op_array *op_array)
}
}
/* update brk/cont array */
for (j = 0; j < op_array->last_brk_cont; j++) {
op_array->brk_cont_array[j].brk -= shiftlist[op_array->brk_cont_array[j].brk];
op_array->brk_cont_array[j].cont -= shiftlist[op_array->brk_cont_array[j].cont];
op_array->brk_cont_array[j].start -= shiftlist[op_array->brk_cont_array[j].start];
}
/* update try/catch array */
for (j = 0; j < op_array->last_try_catch; j++) {
op_array->try_catch_array[j].try_op -= shiftlist[op_array->try_catch_array[j].try_op];

1
ext/opcache/Optimizer/pass1_5.c

@ -624,7 +624,6 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx)
case ZEND_EXIT:
case ZEND_THROW:
case ZEND_CATCH:
case ZEND_GOTO:
case ZEND_FAST_CALL:
case ZEND_FAST_RET:
case ZEND_JMP:

40
ext/opcache/Optimizer/zend_optimizer.c

@ -340,29 +340,11 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array,
*/
case ZEND_FREE:
case ZEND_CASE: {
zend_op *m, *n;
int brk = op_array->last_brk_cont;
zend_bool in_switch = 0;
while (brk--) {
if (op_array->brk_cont_array[brk].start <= (opline - op_array->opcodes) &&
op_array->brk_cont_array[brk].brk > (opline - op_array->opcodes)) {
in_switch = 1;
break;
}
}
zend_op *m = opline;
zend_op *end = op_array->opcodes + op_array->last;
if (!in_switch) {
ZEND_ASSERT(opline->opcode == ZEND_FREE);
MAKE_NOP(opline);
zval_dtor(val);
return 1;
}
m = opline;
n = op_array->opcodes + op_array->brk_cont_array[brk].brk + 1;
while (m < n) {
if (ZEND_OP1_TYPE(m) == type &&
ZEND_OP1(m).var == var) {
while (m < end) {
if (ZEND_OP1_TYPE(m) == type && ZEND_OP1(m).var == var) {
if (m->opcode == ZEND_CASE) {
zval old_val;
ZVAL_COPY_VALUE(&old_val, val);
@ -371,6 +353,7 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array,
ZVAL_COPY_VALUE(val, &old_val);
} else if (m->opcode == ZEND_FREE) {
MAKE_NOP(m);
break;
} else {
ZEND_ASSERT(0);
}
@ -475,6 +458,17 @@ static void zend_optimize(zend_op_array *op_array,
if (ZEND_OPTIMIZER_PASS_11 & OPTIMIZATION_LEVEL) {
zend_optimizer_compact_literals(op_array, ctx);
}
if ((ZEND_OPTIMIZER_PASS_1
|ZEND_OPTIMIZER_PASS_2
|ZEND_OPTIMIZER_PASS_3
|ZEND_OPTIMIZER_PASS_4
|ZEND_OPTIMIZER_PASS_5
|ZEND_OPTIMIZER_PASS_9
|ZEND_OPTIMIZER_PASS_10
|ZEND_OPTIMIZER_PASS_11) & OPTIMIZATION_LEVEL) {
zend_regenerate_var_liveliness_info(op_array);
}
}
static void zend_accel_optimize(zend_op_array *op_array,
@ -494,7 +488,6 @@ static void zend_accel_optimize(zend_op_array *op_array,
}
switch (opline->opcode) {
case ZEND_JMP:
case ZEND_GOTO:
case ZEND_FAST_CALL:
case ZEND_DECLARE_ANON_CLASS:
case ZEND_DECLARE_ANON_INHERITED_CLASS:
@ -539,7 +532,6 @@ static void zend_accel_optimize(zend_op_array *op_array,
}
switch (opline->opcode) {
case ZEND_JMP:
case ZEND_GOTO:
case ZEND_FAST_CALL:
case ZEND_DECLARE_ANON_CLASS:
case ZEND_DECLARE_ANON_INHERITED_CLASS:

3
ext/opcache/Optimizer/zend_optimizer_internal.h

@ -73,9 +73,6 @@ typedef struct _zend_cfg {
zend_code_block *blocks;
zend_code_block **try;
zend_code_block **catch;
zend_code_block **loop_start;
zend_code_block **loop_cont;
zend_code_block **loop_brk;
zend_op **Tsource;
char *same_t;
} zend_cfg;

6
ext/opcache/zend_file_cache.c

@ -386,7 +386,6 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra
# if ZEND_USE_ABS_JMP_ADDR
switch (opline->opcode) {
case ZEND_JMP:
case ZEND_GOTO:
case ZEND_FAST_CALL:
case ZEND_DECLARE_ANON_CLASS:
case ZEND_DECLARE_ANON_INHERITED_CLASS:
@ -459,11 +458,11 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra
SERIALIZE_STR(op_array->function_name);
SERIALIZE_STR(op_array->filename);
SERIALIZE_PTR(op_array->brk_cont_array);
SERIALIZE_PTR(op_array->scope);
SERIALIZE_STR(op_array->doc_comment);
SERIALIZE_PTR(op_array->try_catch_array);
SERIALIZE_PTR(op_array->prototype);
SERIALIZE_PTR(op_array->T_liveliness);
}
}
@ -913,7 +912,6 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr
# if ZEND_USE_ABS_JMP_ADDR
switch (opline->opcode) {
case ZEND_JMP:
case ZEND_GOTO:
case ZEND_FAST_CALL:
case ZEND_DECLARE_ANON_CLASS:
case ZEND_DECLARE_ANON_INHERITED_CLASS:
@ -982,11 +980,11 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr
UNSERIALIZE_STR(op_array->function_name);
UNSERIALIZE_STR(op_array->filename);
UNSERIALIZE_PTR(op_array->brk_cont_array);
UNSERIALIZE_PTR(op_array->scope);
UNSERIALIZE_STR(op_array->doc_comment);
UNSERIALIZE_PTR(op_array->try_catch_array);
UNSERIALIZE_PTR(op_array->prototype);
UNSERIALIZE_PTR(op_array->T_liveliness);
}
}

9
ext/opcache/zend_persist.c

@ -501,7 +501,6 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc
/* fix jumps to point to new array */
switch (opline->opcode) {
case ZEND_JMP:
case ZEND_GOTO:
case ZEND_FAST_CALL:
case ZEND_DECLARE_ANON_CLASS:
case ZEND_DECLARE_ANON_INHERITED_CLASS:
@ -590,10 +589,6 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc
op_array->arg_info = arg_info;
}
if (op_array->brk_cont_array) {
zend_accel_store(op_array->brk_cont_array, sizeof(zend_brk_cont_element) * op_array->last_brk_cont);
}
if (op_array->scope) {
op_array->scope = zend_shared_alloc_get_xlat_entry(op_array->scope);
}
@ -618,6 +613,10 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc
zend_accel_store(op_array->try_catch_array, sizeof(zend_try_catch_element) * op_array->last_try_catch);
}
if (op_array->T_liveliness) {
zend_accel_store(op_array->T_liveliness, sizeof(uint32_t) * op_array->T_liveliness[op_array->last]);
}
if (op_array->vars) {
if (already_stored) {
persist_ptr = zend_shared_alloc_get_xlat_entry(op_array->vars);

8
ext/opcache/zend_persist_calc.c

@ -229,10 +229,6 @@ static void zend_persist_op_array_calc_ex(zend_op_array *op_array)
}
}
if (op_array->brk_cont_array) {
ADD_DUP_SIZE(op_array->brk_cont_array, sizeof(zend_brk_cont_element) * op_array->last_brk_cont);
}
if (ZCG(accel_directives).save_comments && op_array->doc_comment) {
ADD_STRING(op_array->doc_comment);
}
@ -241,6 +237,10 @@ static void zend_persist_op_array_calc_ex(zend_op_array *op_array)
ADD_DUP_SIZE(op_array->try_catch_array, sizeof(zend_try_catch_element) * op_array->last_try_catch);
}
if (op_array->T_liveliness) {
ADD_DUP_SIZE(op_array->T_liveliness, sizeof(uint32_t) * op_array->T_liveliness[op_array->last]);
}
if (op_array->vars) {
int i;

40
sapi/phpdbg/phpdbg_opcode.c

@ -24,6 +24,7 @@
#include "phpdbg_opcode.h"
#include "phpdbg_utils.h"
#include "ext/standard/php_string.h"
#include "zend_smart_str.h"
ZEND_EXTERN_MODULE_GLOBALS(phpdbg);
@ -56,7 +57,6 @@ char *phpdbg_decode_opline(zend_op_array *ops, zend_op *op) /*{{{ */
/* OP1 */
switch (op->opcode) {
case ZEND_JMP:
case ZEND_GOTO:
case ZEND_FAST_CALL:
asprintf(&decode[1], "J%ld", OP_JMP_ADDR(op, op->op1) - ops->opcodes);
break;
@ -114,6 +114,44 @@ char *phpdbg_decode_opline(zend_op_array *ops, zend_op *op) /*{{{ */
break;
}
#if 1
if (ops->T_liveliness) {
uint32_t *var = ops->T_liveliness + (op - ops->opcodes);
if (*var != (uint32_t)-1) {
smart_str str = {0};
var = ops->T_liveliness + (*var);
smart_str_appends(&str, "; [@");
smart_str_append_long(&str, EX_VAR_TO_NUM(((*var) & ~0x3)) - ops->last_var);
while (*(++var) != (uint32_t)-1) {
smart_str_appends(&str, ", @");
smart_str_append_long(&str, EX_VAR_TO_NUM(((*var) & ~0x3)) - ops->last_var);
}
smart_str_appendc(&str, ']');
smart_str_0(&str);
asprintf(&decode[0],
"%-20s %-20s %-20s%-20s",
decode[1] ? decode[1] : "",
decode[2] ? decode[2] : "",
decode[3] ? decode[3] : "",
ZSTR_VAL(str.s));
smart_str_free(&str);
if (decode[1])
free(decode[1]);
if (decode[2])
free(decode[2]);
if (decode[3])
free(decode[3]);
return decode[0];
}
}
#endif
asprintf(&decode[0],
"%-20s %-20s %-20s",
decode[1] ? decode[1] : "",

Loading…
Cancel
Save