for more details. (Dmitry)
- Removed support for "continue" and "break" operators with non-constant
operands. (Dmitry)
+- Implemented "jump label" operator (limited "goto"). (Dmitry, Sara)
- Changed __toString() behavior to call it in all necessary places
(Marcus, Dmitry)
- Changed "instanceof" and "catch" operators, is_a() and is_subclass_of()
--- /dev/null
+--TEST--
+jump 01: jump backward
+--FILE--
+<?php
+$n = 1;
+L1:
+echo "$n: ok\n";
+$n++;
+if ($n <= 3) jump L1;
+?>
+--EXPECT--
+1: ok
+2: ok
+3: ok
--- /dev/null
+--TEST--
+jump 02: jump forward
+--FILE--
+<?php
+$n = 1;
+L1:
+if ($n > 3) jump L2;
+echo "$n: ok\n";
+$n++;
+jump L1;
+L2:
+?>
+--EXPECT--
+1: ok
+2: ok
+3: ok
--- /dev/null
+--TEST--
+jump 03: jump inside control structures
+--FILE--
+<?php
+do {
+ if (1) {
+ echo "1: ok\n";
+ jump L1;
+ } else {
+ echo "bug\n";
+L1:
+ echo "2: ok\n";
+ }
+} while (0);
+?>
+--EXPECT--
+1: ok
+2: ok
--- /dev/null
+--TEST--
+jump 04: jump from loop (backward)
+--FILE--
+<?php
+$s = "X";
+echo "1: ok\n";
+L1: if ($s != "X") {
+ echo "4: ok\n";
+} else {
+ echo "2: ok\n";
+ while ($s != "XXX") {
+ echo "3: ok\n";
+ $s .= "X";
+ jump L1;
+ echo "bug\n";
+ }
+ echo "bug\n";
+}
+?>
+--EXPECT--
+1: ok
+2: ok
+3: ok
+4: ok
--- /dev/null
+--TEST--
+jump 05: jump 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";
+ jump L1;
+ case "3":
+ echo "bug\n";
+ break;
+ }
+}
+echo "bug\n";
+L1:
+echo "3: ok\n";
+?>
+--EXPECT--
+1: ok
+2: ok
+3: ok
--- /dev/null
+--TEST--
+jump 06: jump to undefined label
+--FILE--
+<?php
+jump L1;
+?>
+--EXPECTF--
+Fatal error: 'jump' to undefined label 'L1' in %sjump06.php on line 2
--- /dev/null
+--TEST--
+jump 07: jump into loop (backward)
+--FILE--
+<?php
+while (0) {
+ L1: echo "bug\n";
+}
+jump L1;
+?>
+--EXPECTF--
+Fatal error: 'jump' into loop or switch statement is disallowed in %sjump07.php on line 5
--- /dev/null
+--TEST--
+jump 08: jump into loop (forward)
+--FILE--
+<?php
+jump L1;
+while (0) {
+ L1: echo "bug\n";
+}
+?>
+--EXPECTF--
+Fatal error: 'jump' into loop or switch statement is disallowed in %sjump08.php on line 2
--- /dev/null
+--TEST--
+jump 09: jump into switch (backward)
+--FILE--
+<?php
+switch (0) {
+ case 1:
+ L1: echo "bug\n";
+ break;
+}
+jump L1;
+?>
+--EXPECTF--
+Fatal error: 'jump' into loop or switch statement is disallowed in %sjump09.php on line 7
--- /dev/null
+--TEST--
+jump 10: jump into switch (forward)
+--FILE--
+<?php
+jump L1;
+switch (0) {
+ case 1:
+ L1: echo "bug\n";
+ break;
+}
+?>
+--EXPECTF--
+Fatal error: 'jump' into loop or switch statement is disallowed in %sjump10.php on line 2
CG(start_lineno) = 0;
init_compiler_declarables(TSRMLS_C);
zend_hash_apply(CG(auto_globals), (apply_func_t) zend_auto_global_arm TSRMLS_CC);
+ zend_stack_init(&CG(labels_stack));
+ CG(labels) = NULL;
}
zend_hash_destroy(&CG(script_encodings_table));
zend_hash_destroy(&CG(filenames_table));
zend_llist_destroy(&CG(open_files));
+ zend_stack_destroy(&CG(labels_stack));
}
CG(doc_comment) = NULL;
CG(doc_comment_len) = 0;
}
+
+ zend_stack_push(&CG(labels_stack), (void *) &CG(labels), sizeof(HashTable*));
+ CG(labels) = NULL;
}
void zend_do_handle_exception(TSRMLS_D)
pass_two(CG(active_op_array) TSRMLS_CC);
+ zend_release_labels(TSRMLS_C);
+
if (CG(active_class_entry)) {
zend_check_magic_method_implementation(CG(active_class_entry), (zend_function*)CG(active_op_array), E_COMPILE_ERROR TSRMLS_CC);
} else {
*result = opline->result;
}
+void zend_do_label(znode *label TSRMLS_DC)
+{
+ zend_op_array *oparray = CG(active_op_array);
+ zend_label dest;
+
+ if (!CG(labels)) {
+ ALLOC_HASHTABLE(CG(labels));
+ zend_hash_init(CG(labels), 4, NULL, NULL, 0);
+ }
+
+ dest.brk_cont = oparray->current_brk_cont;
+ dest.opline_num = get_next_op_number(oparray);
+
+ if (zend_u_hash_add(CG(labels), Z_TYPE(label->u.constant), Z_UNIVAL(label->u.constant),
+ Z_UNILEN(label->u.constant) + 1, (void**)&dest, sizeof(zend_label), NULL) == FAILURE) {
+ zend_error(E_COMPILE_ERROR, "Label '%R' already defined", Z_TYPE(label->u.constant), Z_UNIVAL(label->u.constant));
+ }
+
+ /* Done with label now */
+ zval_dtor(&label->u.constant);
+}
+
+void zend_resolve_goto_label(zend_op_array *op_array, zend_op *opline, int pass2 TSRMLS_DC)
+{
+ zend_label *dest;
+ long current, distance;
+
+ if (CG(labels) == NULL ||
+ zend_u_hash_find(CG(labels), Z_TYPE(opline->op2.u.constant), Z_UNIVAL(opline->op2.u.constant), Z_UNILEN(opline->op2.u.constant)+1, (void**)&dest) == FAILURE) {
+
+ if (pass2) {
+ CG(in_compilation) = 1;
+ CG(active_op_array) = op_array;
+ CG(zend_lineno) = opline->lineno;
+ zend_error(E_COMPILE_ERROR, "'jump' to undefined label '%R'", Z_TYPE(opline->op2.u.constant), Z_UNIVAL(opline->op2.u.constant));
+ } else {
+ /* Label is not defined. Delay to pass 2. */
+ INC_BPC(op_array);
+ return;
+ }
+ }
+
+ opline->op1.u.opline_num = dest->opline_num;
+ zval_dtor(&opline->op2.u.constant);
+
+ /* 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(E_COMPILE_ERROR, "'jump' 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(&opline->op2.u.constant, distance);
+ }
+
+ if (pass2) {
+ DEC_BPC(op_array);
+ }
+}
+
+void zend_do_goto(znode *label TSRMLS_DC)
+{
+ zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);
+
+ opline->opcode = ZEND_GOTO;
+ opline->extended_value = CG(active_op_array)->current_brk_cont;
+ SET_UNUSED(opline->op1);
+ opline->op2 = *label;
+ zend_resolve_goto_label(CG(active_op_array), opline, 0 TSRMLS_CC);
+}
+
+void zend_release_labels(TSRMLS_D)
+{
+ if (CG(labels)) {
+ zend_hash_destroy(CG(labels));
+ FREE_HASHTABLE(CG(labels));
+ }
+ if (!zend_stack_is_empty(&CG(labels_stack))) {
+ HashTable **pht;
+
+ zend_stack_top(&CG(labels_stack), (void**)&pht);
+ CG(labels) = *pht;
+ zend_stack_del_top(&CG(labels_stack));
+ } else {
+ CG(labels) = NULL;
+ }
+}
+
/*
* Local variables:
* tab-width: 4
int parent;
} zend_brk_cont_element;
+typedef struct _zend_label {
+ int brk_cont;
+ zend_uint opline_num;
+} zend_label;
typedef struct _zend_try_catch_element {
zend_uint try_op;
void zend_do_normalization(znode *result, znode *str TSRMLS_DC);
+void zend_do_label(znode *label TSRMLS_DC);
+void zend_do_goto(znode *label TSRMLS_DC);
+void zend_resolve_goto_label(zend_op_array *op_array, zend_op *opline, int pass2 TSRMLS_DC);
+void zend_release_labels(TSRMLS_D);
+
#define INITIAL_OP_ARRAY_SIZE 64
opline->op2.u.constant.refcount = 2;
}
switch (opline->opcode) {
+ case ZEND_GOTO:
+ if (Z_TYPE(opline->op2.u.constant) != IS_LONG) {
+ zend_resolve_goto_label(CG(active_op_array), opline, 1 TSRMLS_CC);
+ }
+ /* break omitted intentionally */
case ZEND_JMP:
opline->op1.u.jmp_addr = &CG(active_op_array)->opcodes[opline->op1.u.opline_num];
break;
opline++;
}
+ zend_release_labels(TSRMLS_C);
+
EG(return_value_ptr_ptr) = &local_retval;
EG(active_op_array) = CG(active_op_array);
zend_execute(CG(active_op_array) TSRMLS_CC);
HashTable script_encodings_table;
char *script_encoding;
+ HashTable *labels;
+ zend_stack labels_stack;
+
#ifdef ZTS
HashTable **static_members;
int last_static_member;
%token T_DEFAULT
%token T_BREAK
%token T_CONTINUE
+%token T_GOTO
%token T_FUNCTION
%token T_CONST
%token T_RETURN
statement:
unticked_statement { zend_do_ticks(TSRMLS_C); }
+ | T_STRING ':' { zend_do_label(&$1 TSRMLS_CC); }
;
unticked_statement:
'{' inner_statement_list '}' { zend_do_end_catch(&$1 TSRMLS_CC); }
additional_catches { zend_do_mark_last_catch(&$7, &$18 TSRMLS_CC); }
| T_THROW expr ';' { zend_do_throw(&$2 TSRMLS_CC); }
+ | T_GOTO T_STRING ';' { zend_do_goto(&$2 TSRMLS_CC); }
;
CG(active_op_array) = original_active_op_array;
if (compilation_successful) {
pass_two(op_array TSRMLS_CC);
+ zend_release_labels(TSRMLS_C);
} else {
efree(op_array);
retval = NULL;
zend_do_handle_exception(TSRMLS_C);
CG(active_op_array) = original_active_op_array;
pass_two(op_array TSRMLS_CC);
+ zend_release_labels(TSRMLS_C);
retval = op_array;
}
zend_restore_lexical_state(&original_lex_state TSRMLS_CC);
return T_CONTINUE;
}
+<ST_IN_SCRIPTING>"jump" {
+ return T_GOTO;
+}
+
<ST_IN_SCRIPTING>"echo" {
return T_ECHO;
}
opline->op2.u.constant.refcount = 2;
}
switch (opline->opcode) {
+ case ZEND_GOTO:
+ if (Z_TYPE(opline->op2.u.constant) != IS_LONG) {
+ zend_resolve_goto_label(op_array, opline, 1 TSRMLS_CC);
+ }
+ /* break omitted intentionally */
case ZEND_JMP:
opline->op1.u.jmp_addr = &op_array->opcodes[opline->op1.u.opline_num];
break;
ZEND_VM_JMP(EX(op_array)->opcodes + el->cont);
}
+ZEND_VM_HANDLER(69, ZEND_GOTO, ANY, CONST)
+{
+ zend_op *opline = EX(opline);
+ zend_brk_cont_element *el;
+
+ el = zend_brk_cont(Z_LVAL(opline->op2.u.constant), opline->extended_value,
+ EX(op_array), EX(Ts) TSRMLS_CC);
+
+ zend_op *brk_opline = EX(op_array)->opcodes + el->brk;
+
+ switch (brk_opline->opcode) {
+ case ZEND_SWITCH_FREE:
+ zend_switch_free(brk_opline, EX(Ts) TSRMLS_CC);
+ break;
+ case ZEND_FREE:
+ zendi_zval_dtor(EX_T(brk_opline->op1.u.var).tmp_var);
+ break;
+ }
+ ZEND_VM_JMP(opline->op1.u.jmp_addr);
+}
+
ZEND_VM_HANDLER(48, ZEND_CASE, CONST|TMP|VAR|CV, CONST|TMP|VAR|CV)
{
zend_op *opline = EX(opline);
ZEND_VM_JMP(EX(op_array)->opcodes + el->cont);
}
+static int ZEND_GOTO_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
+{
+ zend_op *opline = EX(opline);
+ zend_brk_cont_element *el;
+
+ el = zend_brk_cont(Z_LVAL(opline->op2.u.constant), opline->extended_value,
+ EX(op_array), EX(Ts) TSRMLS_CC);
+
+ zend_op *brk_opline = EX(op_array)->opcodes + el->brk;
+
+ switch (brk_opline->opcode) {
+ case ZEND_SWITCH_FREE:
+ zend_switch_free(brk_opline, EX(Ts) TSRMLS_CC);
+ break;
+ case ZEND_FREE:
+ zendi_zval_dtor(EX_T(brk_opline->op1.u.var).tmp_var);
+ break;
+ }
+ ZEND_VM_JMP(opline->op1.u.jmp_addr);
+}
+
static int ZEND_FETCH_CLASS_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
zend_op *opline = EX(opline);
ZEND_NEW_SPEC_HANDLER,
ZEND_NEW_SPEC_HANDLER,
ZEND_NEW_SPEC_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,
#define ZEND_SEND_VAR 66
#define ZEND_SEND_REF 67
#define ZEND_NEW 68
+#define ZEND_GOTO 69
#define ZEND_FREE 70
#define ZEND_INIT_ARRAY 71
#define ZEND_ADD_ARRAY_ELEMENT 72
echo "Done\n";
?>
---EXPECT--
+--EXPECT--
array(49) {
[0]=>
array(2) {
[0]=>
- int(368)
+ int(369)
[1]=>
string(2) "<?"
}
[1]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[3]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[6]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[8]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[10]=>
array(2) {
[0]=>
- int(350)
+ int(351)
[1]=>
string(5) "isset"
}
[15]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[17]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[22]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[26]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[30]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[32]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[34]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[37]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[39]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[41]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[44]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[47]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[48]=>
array(2) {
[0]=>
- int(370)
+ int(371)
[1]=>
string(2) "?>"
}
[0]=>
array(2) {
[0]=>
- int(368)
+ int(369)
[1]=>
string(6) "<?php "
}
[5]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[7]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[9]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[12]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[15]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[18]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[21]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[23]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[28]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[30]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[33]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[35]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[36]=>
array(2) {
[0]=>
- int(370)
+ int(371)
[1]=>
string(2) "?>"
}
[0]=>
array(2) {
[0]=>
- int(368)
+ int(369)
[1]=>
string(2) "<?"
}
[1]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[2]=>
array(2) {
[0]=>
- int(366)
+ int(367)
[1]=>
string(13) "/* comment */"
}
[3]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[5]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[8]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[10]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[13]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[15]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[17]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[19]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[21]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[23]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[25]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[28]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[30]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[32]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[37]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[39]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[41]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[46]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) " "
}
[47]=>
array(2) {
[0]=>
- int(370)
+ int(371)
[1]=>
string(2) "?>"
}
[0]=>
array(2) {
[0]=>
- int(368)
+ int(369)
[1]=>
string(6) "<?php
"
[3]=>
array(2) {
[0]=>
- int(372)
+ int(373)
[1]=>
string(6) "<<<DD
"
[5]=>
array(2) {
[0]=>
- int(373)
+ int(374)
[1]=>
string(2) "DD"
}
[6]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) "
"
[10]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) "
"
[13]=>
array(2) {
[0]=>
- int(372)
+ int(373)
[1]=>
string(8) "<<<DDDD
"
[15]=>
array(2) {
[0]=>
- int(373)
+ int(374)
[1]=>
string(4) "DDDD"
}
[17]=>
array(2) {
[0]=>
- int(371)
+ int(372)
[1]=>
string(1) "
"
[18]=>
array(2) {
[0]=>
- int(370)
+ int(371)
[1]=>
string(2) "?>"
}