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)
+++ /dev/null
---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
+++ /dev/null
---TEST--
-Return types must not double free loop variables
---FILE--
-<?php
-
-function foo(): string {
- foreach ([new stdClass] as $class) {
- try {
- return $class; // invalid return type
- } catch (TypeError $e) {
- return "BAG!";
- }
- }
-}
-try {
- print foo();
-} catch (TypeError $e) {
- print "no leak or segfault";
-}
-?>
---EXPECT--
-no leak or segfault
+++ /dev/null
---TEST--
-Return types must not double free loop variables
---FILE--
-<?php
-
-function foo(): string {
- $a = [];
- try {
- return $a; // invalid return type
- } catch (TypeError $e) {
- echo "BAG!\n";
- return "ops!\n";
- }
-}
-try {
- echo foo();
-} catch (TypeError $e) {
- echo "OK\n";
-}
-
-?>
---EXPECT--
-OK
+++ /dev/null
---TEST--
-Excepton on return types mismutch shouldn't execute "catch" code but should execute "finally" code.
---FILE--
-<?php
-
-function foo(): string {
- $a = "OK\n";
- try {
- return $a; // invalid return type
- } catch (TypeError $e) {
- echo "BAG!\n";
- return "ops!\n";
- } finally {
- echo "finally1\n";
- }
-}
-function bar(): string {
- $a = [];
- try {
- return $a; // invalid return type
- } catch (TypeError $e) {
- echo "BAG!\n";
- return "ops!\n";
- } finally {
- echo "finally2\n";
- }
-}
-try {
- echo foo();
-} catch (TypeError $e) {
- echo "BAG\n";
-}
-try {
- echo bar();
-} catch (TypeError $e) {
- echo "OK\n";
-}
-?>
---EXPECT--
-finally1
-OK
-finally2
-OK
+++ /dev/null
---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==
+++ /dev/null
---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==
+++ /dev/null
---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==
+++ /dev/null
---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==
+++ /dev/null
---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==
+++ /dev/null
---TEST--
-Exception after separation during indirect write to fcall result
---FILE--
-<?php
-
-function throwing() { throw new Exception; }
-
-function getArray() { return [0]; }
-
-try {
- getArray()[throwing()] = 1;
-} catch (Exception $e) {
- echo "Exception\n";
-}
-
-?>
---EXPECT--
-Exception
+++ /dev/null
---TEST--
-Exception inside a foreach loop with return
---FILE--
-<?php
-class saboteurTestController {
- public function isConsistent() { throw new \Exception(); }
-}
-
-$controllers = array(new saboteurTestController(),new saboteurTestController());
-foreach ($controllers as $controller) {
- try {
- if ($controller->isConsistent()) {
- return $controller;
- }
- } catch (\Exception $e) {
- echo "Exception\n";
- }
-}
-?>
---EXPECT--
-Exception
-Exception
+++ /dev/null
---TEST--
-Exception inside a foreach loop with on an object with destructor
---FILE--
-<?php
-class bar {
- public $foo = 1;
- public $bar = 2;
- function __destruct() {
- throw new Exception("test");
- }
-}
-function foo(): string {
- foreach (new bar() as $foo) {
- try {
- $foo = new Exception;
- return;
- } catch (Exception $e) {
- echo "Exception1: " . $e->getMessage() . "\n";
- } catch (Error $e) {
- echo "Error1: " . $e->getMessage() . "\n";
- }
- }
- echo "bag!\n";
-}
-try {
- foo();
-} catch (Throwable $e) {
- echo (($e instanceof Exception) ? "Exception2: " : "Error2: ") .
- $e->getMessage() . "\n";
- $e = $e->getPrevious();
- while ($e instanceof Throwable) {
- echo "\tPrev " . (($e instanceof Exception) ? "Exception2: " : "Error2: ") .
- $e->getMessage() . "\n";
- $e = $e->getPrevious();
- }
-}
-echo "ok\n";
-?>
---EXPECTF--
-Exception2: test
- Prev Error2: Return value of foo() must be of the type string, none returned in %stemporary_cleaning_008.php on line %d
-ok
+++ /dev/null
---TEST--
-Exception inside a foreach loop with on an object with destructor
---FILE--
-<?php
-class bar {
- public $foo = 1;
- function __destruct() {
- throw new Exception;
- }
-}
-
-function foo() {
- foreach (new bar() as &$foo) {
- try {
- $foo = new Exception;
- return;
- } catch (Exception $e) {
- echo "Exception1\n";
- }
- }
-}
-try {
- foo();
-} catch (Exception $e) {
- echo "Exception2\n";
-}
-?>
---EXPECT--
-Exception2
zval orig_user_error_handler;
zend_bool in_compilation;
zend_class_entry *saved_class_entry;
+ zend_stack loop_var_stack;
zend_stack delayed_oplines_stack;
zend_array *symbol_table;
if (in_compilation) {
saved_class_entry = CG(active_class_entry);
CG(active_class_entry) = NULL;
+ SAVE_STACK(loop_var_stack);
SAVE_STACK(delayed_oplines_stack);
CG(in_compilation) = 0;
}
if (in_compilation) {
CG(active_class_entry) = saved_class_entry;
+ RESTORE_STACK(loop_var_stack);
RESTORE_STACK(delayed_oplines_stack);
CG(in_compilation) = 1;
}
#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; \
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);
void zend_init_compiler_data_structures(void) /* {{{ */
{
+ zend_stack_init(&CG(loop_var_stack), sizeof(znode));
zend_stack_init(&CG(delayed_oplines_stack), sizeof(zend_op));
CG(active_class_entry) = NULL;
CG(in_compilation) = 0;
void shutdown_compiler(void) /* {{{ */
{
+ zend_stack_destroy(&CG(loop_var_stack));
zend_stack_destroy(&CG(delayed_oplines_stack));
zend_hash_destroy(&CG(filenames_table));
zend_hash_destroy(&CG(const_filenames));
zend_brk_cont_element *brk_cont_element;
int parent = CG(context).current_brk_cont;
- CG(context).current_brk_cont = CG(context).last_brk_cont;
+ CG(context).current_brk_cont = CG(active_op_array)->last_brk_cont;
brk_cont_element = get_next_brk_cont_element(CG(active_op_array));
brk_cont_element->parent = parent;
- if (loop_var && (loop_var->op_type & (IS_TMP_VAR|IS_VAR))) {
- brk_cont_element->loop_var = *loop_var;
+
+ if (loop_var) {
+ zend_stack_push(&CG(loop_var_stack), loop_var);
+ brk_cont_element->start = get_next_op_number(CG(active_op_array));
} else {
- brk_cont_element->loop_var.op_type = IS_UNUSED;
+ /* The start field is used to free temporary variables in case of exceptions.
+ * We won't try to free something of we don't have loop variable. */
+ brk_cont_element->start = -1;
}
}
/* }}} */
static inline void zend_end_loop(int cont_addr) /* {{{ */
{
zend_brk_cont_element *brk_cont_element
- = &CG(context).brk_cont_array[CG(context).current_brk_cont];
+ = &CG(active_op_array)->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;
+
+ if (brk_cont_element->start >= 0) {
+ zend_stack_del_top(&CG(loop_var_stack));
+ }
}
/* }}} */
}
/* }}} */
-static zend_bool zend_is_call(zend_ast *ast);
-
-static void generate_free_loop_var_ex(znode *var, uint32_t flags) /* {{{ */
+void zend_resolve_goto_label(zend_op_array *op_array, zend_op *opline, int pass2) /* {{{ */
{
- if (var->op_type != IS_UNUSED) {
- zend_op *opline = get_next_op(CG(active_op_array));
+ 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) {
- opline->opcode = var->flag ? ZEND_FE_FREE : ZEND_FREE;
- SET_NODE(opline->op1, var);
+ 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);
- opline->extended_value = flags;
+ } else {
+ /* Set real break distance */
+ ZVAL_LONG(label, distance);
}
}
/* }}} */
-static void generate_free_loop_var(znode *var) /* {{{ */
+static zend_bool zend_is_call(zend_ast *ast);
+
+static int generate_free_loop_var(znode *var) /* {{{ */
{
- generate_free_loop_var_ex(var, 0);
+ switch (var->op_type) {
+ case IS_UNUSED:
+ /* Stack separator on function boundary, stop applying */
+ return 1;
+ case IS_VAR:
+ case IS_TMP_VAR:
+ {
+ zend_op *opline = get_next_op(CG(active_op_array));
+
+ opline->opcode = var->flag ? ZEND_FE_FREE : ZEND_FREE;
+ SET_NODE(opline->op1, var);
+ SET_UNUSED(opline->op2);
+ }
+ }
+
+ return 0;
}
+/* }}} */
static uint32_t zend_add_try_element(uint32_t try_op) /* {{{ */
{
}
/* }}} */
-static void zend_free_foreach_and_switch_variables(uint32_t flags) /* {{{ */
+static void zend_free_foreach_and_switch_variables(void) /* {{{ */
{
- int array_offset = CG(context).current_brk_cont;
- while (array_offset != -1) {
- zend_brk_cont_element *brk_cont = &CG(context).brk_cont_array[array_offset];
- generate_free_loop_var_ex(&brk_cont->loop_var, flags);
- array_offset = brk_cont->parent;
- }
+ zend_stack_apply(&CG(loop_var_stack), ZEND_STACK_APPLY_TOPDOWN, (int (*)(void *element)) generate_free_loop_var);
}
/* }}} */
zend_compile_expr(&expr_node, expr_ast);
}
- /* Generator return types are handled separately */
- if (!(CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR) && CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
- zend_emit_return_type_check(expr_ast ? &expr_node : NULL, CG(active_op_array)->arg_info - 1);
- }
-
- zend_free_foreach_and_switch_variables(ZEND_FREE_ON_RETURN);
+ zend_free_foreach_and_switch_variables();
if (CG(context).in_finally) {
opline = zend_emit_op(NULL, ZEND_DISCARD_EXCEPTION, NULL, NULL);
opline->op1.var = CG(context).fast_call_var;
}
+ /* Generator return types are handled separately */
+ if (!(CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR) && CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
+ zend_emit_return_type_check(expr_ast ? &expr_node : NULL, CG(active_op_array)->arg_info - 1);
+ }
opline = zend_emit_op(NULL, by_ref ? ZEND_RETURN_BY_REF : ZEND_RETURN,
&expr_node, NULL);
} else {
int array_offset = CG(context).current_brk_cont;
zend_long nest_level = depth;
+ znode *loop_var = zend_stack_top(&CG(loop_var_stack));
do {
if (array_offset == -1) {
depth, depth == 1 ? "" : "s");
}
- if (nest_level > 1) {
- generate_free_loop_var_ex(&CG(context).brk_cont_array[array_offset].loop_var, ZEND_FREE_ON_BREAK);
+ if (nest_level > 1 && CG(active_op_array)->brk_cont_array[array_offset].start >= 0) {
+ generate_free_loop_var(loop_var);
+ loop_var--;
}
- array_offset = CG(context).brk_cont_array[array_offset].parent;
+ array_offset = CG(active_op_array)->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);
}
/* }}} */
-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;
-
- 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].loop_var.op_type != IS_UNUSED) {
- 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;
- }
- 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].loop_var.op_type != IS_UNUSED) {
- if (pass2_opline) {
- free_vars++;
- } else {
- generate_free_loop_var_ex(&CG(context).brk_cont_array[current].loop_var, ZEND_FREE_ON_BREAK);
- }
- }
- 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].loop_var.op_type != IS_UNUSED) {
- zend_op *brk_opline = &op_array->opcodes[CG(context).brk_cont_array[current].brk];
-
- (pass2_opline - free_vars)->opcode = brk_opline->opcode;
- (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);
- zend_resolve_goto_label(CG(active_op_array), &label_node, NULL);
+ 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);
}
/* }}} */
opline_ext->lineno = decl->start_lineno;
}
+ {
+ /* Push a separator to the loop variable stack */
+ znode dummy_var;
+ dummy_var.op_type = IS_UNUSED;
+
+ zend_stack_push(&CG(loop_var_stack), (void *) &dummy_var);
+ }
+
zend_compile_params(params_ast, return_type_ast);
if (uses_ast) {
zend_compile_closure_uses(uses_ast);
pass_two(CG(active_op_array));
zend_oparray_context_end(&orig_oparray_context);
+ /* Pop the loop variable stack separator */
+ zend_stack_del_top(&CG(loop_var_stack));
+
CG(active_op_array) = orig_op_array;
}
/* }}} */
{
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) {
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;
}
/* }}} */
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;
zend_long ticks;
} zend_declarables;
-typedef struct _zend_brk_cont_element {
- int cont;
- int brk;
- int parent;
- znode loop_var;
-} 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;
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
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;
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 */
int last_literal;
zval *literals;
+ int cache_size;
+ void **run_time_cache;
+
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
};
void zend_verify_namespace(void);
-void zend_resolve_goto_label(zend_op_array *op_array, znode *label_node, zend_op *pass2_opline);
+void zend_resolve_goto_label(zend_op_array *op_array, zend_op *opline, int pass2);
ZEND_API void function_add_ref(zend_function *function);
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);
#define ZEND_FETCH_ARG_MASK 0x000fffff
-#define ZEND_FREE_ON_RETURN (1<<0)
-#define ZEND_FREE_ON_BREAK (1<<1)
-
-#define ZEND_MEMBER_FUNC_CALL (1<<0)
+#define ZEND_MEMBER_FUNC_CALL 1<<0
#define ZEND_ARG_SEND_BY_REF (1<<0)
#define ZEND_ARG_COMPILE_TIME_BOUND (1<<1)
#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
}
}
+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() \
static zend_always_inline void i_cleanup_unfinished_execution(zend_execute_data *execute_data, uint32_t op_num, uint32_t catch_op_num) /* {{{ */
{
- 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);
- }
-
+ int i;
if (UNEXPECTED(EX(call))) {
zend_execute_data *call = EX(call);
zend_op *opline = EX(func)->op_array.opcodes + op_num;
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));
+ }
+ }
+ }
+ }
+ }
}
/* }}} */
struct _zend_compiler_globals {
+ zend_stack loop_var_stack;
+
zend_class_entry *active_class_entry;
zend_string *compiled_filename;
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();
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;
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);
}
zend_brk_cont_element *get_next_brk_cont_element(zend_op_array *op_array)
{
- 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];
+ 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];
}
static void zend_update_extended_info(zend_op_array *op_array)
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;
int array_offset = opline->op1.num;
zend_brk_cont_element *jmp_to;
do {
- jmp_to = &CG(context).brk_cont_array[array_offset];
+ jmp_to = &op_array->brk_cont_array[array_offset];
if (nest_levels > 1) {
array_offset = jmp_to->parent;
}
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, NULL, opline);
+ zend_resolve_goto_label(op_array, opline, 1);
+ opline->op2.constant = num;
}
/* break omitted intentionally */
case ZEND_JMP:
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) {
break;
case ZEND_GOTO:
if (Z_TYPE_P(RT_CONSTANT(op_array, opline->op2)) != IS_LONG) {
- zend_resolve_goto_label(op_array, NULL, opline);
+ zend_resolve_goto_label(op_array, opline, 1);
}
/* break omitted intentionally */
case ZEND_JMP:
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 opcodes write the result of the true branch of a ternary, short
- * ternary or coalesce and are immediately followed by the instructions
- * for the false branch (where this result is not live) */
- && (opline->opcode != ZEND_QM_ASSIGN || (opline + 1)->opcode != ZEND_JMP)
- && opline->opcode != ZEND_JMP_SET
- && opline->opcode != ZEND_COALESCE
- /* 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
- && opline->opcode != ZEND_SEPARATE
- /* 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;
- }
- ZEND_ASSERT(Tstart[var] == (unsigned) -1);
- if (opline->opcode == ZEND_NEW) {
- /* Objects created via ZEND_NEW are only fully initialized
- * after the DO_FCALL (constructor call) */
- 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_SEPARATE
- && opline->opcode != ZEND_FETCH_LIST
- && opline->opcode != ZEND_CASE
- && opline->opcode != ZEND_FE_FETCH_R
- && opline->opcode != ZEND_FE_FETCH_RW
- /* the following opcodes are parts of "return" statement */
- && opline->opcode != ZEND_VERIFY_RETURN_TYPE
- && (opline->opcode != ZEND_FREE || !(opline->extended_value & (ZEND_FREE_ON_RETURN|ZEND_FREE_ON_BREAK)))
- && (opline->opcode != ZEND_FE_FREE || !(opline->extended_value & (ZEND_FREE_ON_RETURN|ZEND_FREE_ON_BREAK)))
- ) {
- 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 0
- /* 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));
ZEND_VM_HANDLER(70, ZEND_FREE, TMPVAR, ANY)
{
- zval *var;
USE_OPLINE
SAVE_OPLINE();
- var = EX_VAR(opline->op1.var);
- zval_ptr_dtor_nogc(var);
- ZVAL_NULL(var);
+ zval_ptr_dtor_nogc(EX_VAR(opline->op1.var));
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
var = EX_VAR(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));
- Z_FE_ITER_P(var) = (uint32_t)-1;
}
zval_ptr_dtor_nogc(var);
- ZVAL_NULL(var);
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
if (UNEXPECTED(EG(exception) != NULL)) {
FREE_OP1();
- if (OP1_TYPE == IS_TMP_VAR || OP1_TYPE == IS_VAR) {
- ZVAL_NULL(retval_ref);
- }
}
#endif
}
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
}
}
- if (catch_op_num) {
- if (EX(func)->op_array.opcodes[op_num].opcode == ZEND_VERIFY_RETURN_TYPE
- || (EX(func)->op_array.opcodes[op_num].opcode == ZEND_FREE && (EX(func)->op_array.opcodes[op_num].extended_value & ZEND_FREE_ON_RETURN))
- || (EX(func)->op_array.opcodes[op_num].opcode == ZEND_FE_FREE && (EX(func)->op_array.opcodes[op_num].extended_value & ZEND_FREE_ON_RETURN))
- ) {
- catch_op_num = 0;
- }
- }
-
i_cleanup_unfinished_execution(execute_data, op_num, catch_op_num);
if (finally_op_num && (!catch_op_num || catch_op_num >= finally_op_num)) {
}
}
- if (catch_op_num) {
- if (EX(func)->op_array.opcodes[op_num].opcode == ZEND_VERIFY_RETURN_TYPE
- || (EX(func)->op_array.opcodes[op_num].opcode == ZEND_FREE && (EX(func)->op_array.opcodes[op_num].extended_value & ZEND_FREE_ON_RETURN))
- || (EX(func)->op_array.opcodes[op_num].opcode == ZEND_FE_FREE && (EX(func)->op_array.opcodes[op_num].extended_value & ZEND_FREE_ON_RETURN))
- ) {
- catch_op_num = 0;
- }
- }
-
i_cleanup_unfinished_execution(execute_data, op_num, catch_op_num);
if (finally_op_num && (!catch_op_num || catch_op_num >= finally_op_num)) {
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
if (UNEXPECTED(EG(exception) != NULL)) {
- if (IS_CONST == IS_TMP_VAR || IS_CONST == IS_VAR) {
- ZVAL_NULL(retval_ref);
- }
}
#endif
}
if (UNEXPECTED(EG(exception) != NULL)) {
zval_ptr_dtor_nogc(free_op1);
- if (IS_TMP_VAR == IS_TMP_VAR || IS_TMP_VAR == IS_VAR) {
- ZVAL_NULL(retval_ref);
- }
}
#endif
}
if (UNEXPECTED(EG(exception) != NULL)) {
zval_ptr_dtor_nogc(free_op1);
- if (IS_VAR == IS_TMP_VAR || IS_VAR == IS_VAR) {
- ZVAL_NULL(retval_ref);
- }
}
#endif
}
if (UNEXPECTED(EG(exception) != NULL)) {
- if (IS_UNUSED == IS_TMP_VAR || IS_UNUSED == IS_VAR) {
- ZVAL_NULL(retval_ref);
- }
}
#endif
}
if (UNEXPECTED(EG(exception) != NULL)) {
- if (IS_CV == IS_TMP_VAR || IS_CV == IS_VAR) {
- ZVAL_NULL(retval_ref);
- }
}
#endif
}
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FREE_SPEC_TMPVAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
- zval *var;
USE_OPLINE
SAVE_OPLINE();
- var = EX_VAR(opline->op1.var);
- zval_ptr_dtor_nogc(var);
- ZVAL_NULL(var);
+ zval_ptr_dtor_nogc(EX_VAR(opline->op1.var));
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
var = EX_VAR(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));
- Z_FE_ITER_P(var) = (uint32_t)-1;
}
zval_ptr_dtor_nogc(var);
- ZVAL_NULL(var);
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
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,
- ZEND_NULL_HANDLER,
+ ZEND_GOTO_SPEC_CONST_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
"ZEND_FETCH_OBJ_UNSET",
"ZEND_FETCH_LIST",
"ZEND_FETCH_CONSTANT",
- NULL,
+ "ZEND_GOTO",
"ZEND_EXT_STMT",
"ZEND_EXT_FCALL_BEGIN",
"ZEND_EXT_FCALL_END",
#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
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) {
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;
/* 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;
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) {
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 */
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:
}
}
+ /* 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];
case ZEND_EXIT:
case ZEND_THROW:
case ZEND_CATCH:
+ case ZEND_GOTO:
case ZEND_FAST_CALL:
case ZEND_FAST_RET:
case ZEND_JMP:
*/
case ZEND_FREE:
case ZEND_CASE: {
- zend_op *m = opline;
- zend_op *end = op_array->opcodes + op_array->last;
+ 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;
+ }
+ }
- while (m < end) {
- if (ZEND_OP1_TYPE(m) == type && ZEND_OP1(m).var == var) {
+ 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) {
if (m->opcode == ZEND_CASE) {
zval old_val;
ZVAL_COPY_VALUE(&old_val, val);
ZVAL_COPY_VALUE(val, &old_val);
} else if (m->opcode == ZEND_FREE) {
MAKE_NOP(m);
- break;
} else {
ZEND_ASSERT(0);
}
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,
}
switch (opline->opcode) {
case ZEND_JMP:
+ case ZEND_GOTO:
case ZEND_FAST_CALL:
case ZEND_DECLARE_ANON_CLASS:
case ZEND_DECLARE_ANON_INHERITED_CLASS:
}
switch (opline->opcode) {
case ZEND_JMP:
+ case ZEND_GOTO:
case ZEND_FAST_CALL:
case ZEND_DECLARE_ANON_CLASS:
case ZEND_DECLARE_ANON_INHERITED_CLASS:
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;
# 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:
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);
}
}
# 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:
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);
}
}
/* 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:
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);
}
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);
}
}
+ 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);
}
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;
#include "phpdbg_opcode.h"
#include "phpdbg_utils.h"
#include "ext/standard/php_string.h"
-#include "zend_smart_str.h"
ZEND_EXTERN_MODULE_GLOBALS(phpdbg);
/* 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;
break;
}
-#if 0
- 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] : "",