Skip to content

Commit 1097ea7

Browse files
committed
Promote undeclared variables to errors
1 parent 10b2754 commit 1097ea7

File tree

8 files changed

+124
-25
lines changed

8 files changed

+124
-25
lines changed

Zend/Optimizer/zend_inference.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5007,7 +5007,6 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
50075007
case ZEND_NOP:
50085008
case ZEND_QM_ASSIGN:
50095009
case ZEND_JMP:
5010-
case ZEND_CHECK_VAR:
50115010
case ZEND_MAKE_REF:
50125011
case ZEND_BEGIN_SILENCE:
50135012
case ZEND_END_SILENCE:
@@ -5029,6 +5028,8 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
50295028
case ZEND_JMP_NULL:
50305029
case ZEND_JMP_FRAMELESS:
50315030
return 0;
5031+
case ZEND_CHECK_VAR:
5032+
return t1 & MAY_BE_UNDEF;
50325033
case ZEND_IS_IDENTICAL:
50335034
case ZEND_IS_NOT_IDENTICAL:
50345035
case ZEND_CASE_STRICT:

Zend/zend.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1807,6 +1807,34 @@ ZEND_API ZEND_COLD void zend_throw_error(zend_class_entry *exception_ce, const c
18071807
}
18081808
/* }}} */
18091809

1810+
ZEND_API ZEND_COLD void zend_throw_error_unchecked(zend_class_entry *exception_ce, const char *format, ...)
1811+
{
1812+
va_list va;
1813+
char *message = NULL;
1814+
1815+
if (!exception_ce) {
1816+
exception_ce = zend_ce_error;
1817+
}
1818+
1819+
/* Marker used to disable exception generation during preloading. */
1820+
if (EG(exception) == (void*)(uintptr_t)-1) {
1821+
return;
1822+
}
1823+
1824+
va_start(va, format);
1825+
zend_vspprintf(&message, 0, format, va);
1826+
1827+
//TODO: we can't convert compile-time errors to exceptions yet???
1828+
if (EG(current_execute_data) && !CG(in_compilation)) {
1829+
zend_throw_exception(exception_ce, message, 0);
1830+
} else {
1831+
zend_error_noreturn(E_ERROR, "%s", message);
1832+
}
1833+
1834+
efree(message);
1835+
va_end(va);
1836+
}
1837+
18101838
/* type should be one of the BP_VAR_* constants, only special messages happen for isset/empty and unset */
18111839
ZEND_API ZEND_COLD void zend_illegal_container_offset(const zend_string *container, const zval *offset, int type)
18121840
{

Zend/zend.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ ZEND_API ZEND_COLD void zend_error_zstr(int type, zend_string *message);
391391
ZEND_API ZEND_COLD void zend_error_zstr_at(int type, zend_string *filename, uint32_t lineno, zend_string *message);
392392

393393
ZEND_API ZEND_COLD void zend_throw_error(zend_class_entry *exception_ce, const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3);
394+
ZEND_API ZEND_COLD void zend_throw_error_unchecked(zend_class_entry *exception_ce, const char *format, ...);
394395
ZEND_API ZEND_COLD void zend_type_error(const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 1, 2);
395396
ZEND_API ZEND_COLD void zend_argument_count_error(const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 1, 2);
396397
ZEND_API ZEND_COLD void zend_value_error(const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 1, 2);

Zend/zend_compile.c

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2870,7 +2870,7 @@ static void zend_compile_class_ref(znode *result, zend_ast *name_ast, uint32_t f
28702870
}
28712871
/* }}} */
28722872

2873-
static zend_result zend_try_compile_cv(znode *result, zend_ast *ast) /* {{{ */
2873+
static zend_result zend_try_compile_cv(znode *result, zend_ast *ast, uint32_t type) /* {{{ */
28742874
{
28752875
zend_ast *name_ast = ast->child[0];
28762876
if (name_ast->kind == ZEND_AST_ZVAL) {
@@ -2890,6 +2890,11 @@ static zend_result zend_try_compile_cv(znode *result, zend_ast *ast) /* {{{ */
28902890
result->op_type = IS_CV;
28912891
result->u.op.var = lookup_cv(name);
28922892

2893+
// FIXME: Check whether the CV is guaranteed to be declared
2894+
if (type == BP_VAR_R || type == BP_VAR_RW) {
2895+
zend_emit_op(NULL, ZEND_CHECK_VAR, result, NULL);
2896+
}
2897+
28932898
if (UNEXPECTED(Z_TYPE_P(zv) != IS_STRING)) {
28942899
zend_string_release_ex(name, 0);
28952900
}
@@ -2994,7 +2999,7 @@ static zend_op *zend_compile_simple_var(znode *result, zend_ast *ast, uint32_t t
29942999
result->op_type = IS_TMP_VAR;
29953000
}
29963001
return opline;
2997-
} else if (zend_try_compile_cv(result, ast) == FAILURE) {
3002+
} else if (zend_try_compile_cv(result, ast, type) == FAILURE) {
29983003
return zend_compile_simple_var_no_cv(result, ast, type, delayed);
29993004
}
30003005
return NULL;
@@ -3420,7 +3425,7 @@ static void zend_compile_expr_with_potential_assign_to_self(
34203425
/* $a[0] = $a should evaluate the right $a first */
34213426
znode cv_node;
34223427

3423-
if (zend_try_compile_cv(&cv_node, expr_ast) == FAILURE) {
3428+
if (zend_try_compile_cv(&cv_node, expr_ast, BP_VAR_R) == FAILURE) {
34243429
zend_compile_simple_var_no_cv(expr_node, expr_ast, BP_VAR_R, 0);
34253430
} else {
34263431
zend_emit_op_tmp(expr_node, ZEND_QM_ASSIGN, &cv_node, NULL);
@@ -3510,7 +3515,7 @@ static void zend_compile_assign(znode *result, zend_ast *ast) /* {{{ */
35103515
/* list($a, $b) = $a should evaluate the right $a first */
35113516
znode cv_node;
35123517

3513-
if (zend_try_compile_cv(&cv_node, expr_ast) == FAILURE) {
3518+
if (zend_try_compile_cv(&cv_node, expr_ast, BP_VAR_R) == FAILURE) {
35143519
zend_compile_simple_var_no_cv(&expr_node, expr_ast, BP_VAR_R, 0);
35153520
} else {
35163521
zend_emit_op_tmp(&expr_node, ZEND_QM_ASSIGN, &cv_node, NULL);
@@ -3825,7 +3830,7 @@ static uint32_t zend_compile_args(
38253830
opcode = ZEND_SEND_VAR_EX;
38263831
CG(active_op_array)->fn_flags |= ZEND_ACC_USES_THIS;
38273832
break;
3828-
} else if (zend_try_compile_cv(&arg_node, arg) == SUCCESS) {
3833+
} else if (zend_try_compile_cv(&arg_node, arg, BP_VAR_FUNC_ARG) == SUCCESS) {
38293834
opcode = ZEND_SEND_VAR_EX;
38303835
break;
38313836
}
@@ -5423,7 +5428,7 @@ static void zend_compile_global_var(zend_ast *ast) /* {{{ */
54235428
// TODO(GLOBALS) Forbid "global $GLOBALS"?
54245429
if (is_this_fetch(var_ast)) {
54255430
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use $this as global variable");
5426-
} else if (zend_try_compile_cv(&result, var_ast) == SUCCESS) {
5431+
} else if (zend_try_compile_cv(&result, var_ast, BP_VAR_W) == SUCCESS) {
54275432
zend_op *opline = zend_emit_op(NULL, ZEND_BIND_GLOBAL, &result, &name_node);
54285433
opline->extended_value = zend_alloc_cache_slot();
54295434
} else {
@@ -5549,7 +5554,7 @@ static void zend_compile_unset(zend_ast *ast) /* {{{ */
55495554
case ZEND_AST_VAR:
55505555
if (is_this_fetch(var_ast)) {
55515556
zend_error_noreturn(E_COMPILE_ERROR, "Cannot unset $this");
5552-
} else if (zend_try_compile_cv(&var_node, var_ast) == SUCCESS) {
5557+
} else if (zend_try_compile_cv(&var_node, var_ast, BP_VAR_UNSET) == SUCCESS) {
55535558
opline = zend_emit_op(NULL, ZEND_UNSET_CV, &var_node, NULL);
55545559
} else {
55555560
opline = zend_compile_simple_var_no_cv(NULL, var_ast, BP_VAR_UNSET, 0);
@@ -6074,7 +6079,7 @@ static void zend_compile_foreach(zend_ast *ast) /* {{{ */
60746079
if (is_this_fetch(value_ast)) {
60756080
zend_error_noreturn(E_COMPILE_ERROR, "Cannot re-assign $this");
60766081
} else if (value_ast->kind == ZEND_AST_VAR &&
6077-
zend_try_compile_cv(&value_node, value_ast) == SUCCESS) {
6082+
zend_try_compile_cv(&value_node, value_ast, BP_VAR_W) == SUCCESS) {
60786083
SET_NODE(opline->op2, &value_node);
60796084
} else {
60806085
opline->op2_type = IS_VAR;
@@ -10664,7 +10669,7 @@ static void zend_compile_isset_or_empty(znode *result, zend_ast *ast) /* {{{ */
1066410669
if (is_this_fetch(var_ast)) {
1066510670
opline = zend_emit_op(result, ZEND_ISSET_ISEMPTY_THIS, NULL, NULL);
1066610671
CG(active_op_array)->fn_flags |= ZEND_ACC_USES_THIS;
10667-
} else if (zend_try_compile_cv(&var_node, var_ast) == SUCCESS) {
10672+
} else if (zend_try_compile_cv(&var_node, var_ast, BP_VAR_IS) == SUCCESS) {
1066810673
opline = zend_emit_op(result, ZEND_ISSET_ISEMPTY_CV, &var_node, NULL);
1066910674
} else {
1067010675
opline = zend_compile_simple_var_no_cv(result, var_ast, BP_VAR_IS, 0);

Zend/zend_execute.c

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -348,19 +348,15 @@ static zend_always_inline zval *_get_zval_ptr_cv_BP_VAR_R(uint32_t var EXECUTE_D
348348
{
349349
zval *ret = EX_VAR(var);
350350

351-
if (UNEXPECTED(Z_TYPE_P(ret) == IS_UNDEF)) {
352-
return zval_undefined_cv(var EXECUTE_DATA_CC);
353-
}
351+
ZEND_ASSERT(Z_TYPE_P(ret) != IS_UNDEF);
354352
return ret;
355353
}
356354

357355
static zend_always_inline zval *_get_zval_ptr_cv_deref_BP_VAR_R(uint32_t var EXECUTE_DATA_DC)
358356
{
359357
zval *ret = EX_VAR(var);
360358

361-
if (UNEXPECTED(Z_TYPE_P(ret) == IS_UNDEF)) {
362-
return zval_undefined_cv(var EXECUTE_DATA_CC);
363-
}
359+
ZEND_ASSERT(Z_TYPE_P(ret) != IS_UNDEF);
364360
ZVAL_DEREF(ret);
365361
return ret;
366362
}
@@ -376,11 +372,7 @@ static zend_always_inline zval *_get_zval_ptr_cv_BP_VAR_RW(uint32_t var EXECUTE_
376372
{
377373
zval *ret = EX_VAR(var);
378374

379-
if (UNEXPECTED(Z_TYPE_P(ret) == IS_UNDEF)) {
380-
zval_undefined_cv(var EXECUTE_DATA_CC);
381-
ZVAL_NULL(ret);
382-
return ret;
383-
}
375+
ZEND_ASSERT(Z_TYPE_P(ret) != IS_UNDEF);
384376
return ret;
385377
}
386378

@@ -2045,6 +2037,7 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim,
20452037
/* The string may be destroyed while throwing the notice.
20462038
* Temporarily increase the refcount to detect this situation. */
20472039
GC_ADDREF(s);
2040+
// FIXME: Still needed?
20482041
if (UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) {
20492042
zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC);
20502043
}
@@ -3641,6 +3634,7 @@ static zend_never_inline zval* zend_fetch_static_property_address_ex(zend_proper
36413634
name = Z_STR_P(varname);
36423635
tmp_name = NULL;
36433636
} else {
3637+
// FIXME: Still needed?
36443638
if (op1_type == IS_CV && UNEXPECTED(Z_TYPE_P(varname) == IS_UNDEF)) {
36453639
zval_undefined_cv(opline->op1.var EXECUTE_DATA_CC);
36463640
}

Zend/zend_types.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1488,6 +1488,8 @@ static zend_always_inline uint32_t zval_delref_p(zval* pz) {
14881488
#define ZVAL_DEREF(z) do { \
14891489
if (UNEXPECTED(Z_ISREF_P(z))) { \
14901490
(z) = Z_REFVAL_P(z); \
1491+
/* FIXME: Verify this is beneficial. */ \
1492+
ZEND_ASSERT(Z_TYPE_P(z) != IS_UNDEF && Z_TYPE_P(z) != IS_REFERENCE); \
14911493
} \
14921494
} while (0)
14931495

Zend/zend_vm_def.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2669,6 +2669,7 @@ ZEND_VM_C_LABEL(try_assign_dim_array):
26692669
SEPARATE_ARRAY(object_ptr);
26702670
if (OP2_TYPE == IS_UNUSED) {
26712671
value = GET_OP_DATA_ZVAL_PTR_UNDEF(BP_VAR_R);
2672+
// FIXME: Still needed?
26722673
if (OP_DATA_TYPE == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) {
26732674
HashTable *ht = Z_ARRVAL_P(object_ptr);
26742675
if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) {
@@ -2742,6 +2743,7 @@ ZEND_VM_C_LABEL(try_assign_dim_array):
27422743
}
27432744

27442745
value = GET_OP_DATA_ZVAL_PTR_UNDEF(BP_VAR_R);
2746+
// FIXME: Still needed?
27452747
if (OP_DATA_TYPE == IS_CV && UNEXPECTED(Z_ISUNDEF_P(value))) {
27462748
value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC);
27472749
} else if (OP_DATA_TYPE & (IS_CV|IS_VAR)) {
@@ -9214,8 +9216,9 @@ ZEND_VM_HANDLER(49, ZEND_CHECK_VAR, CV, UNUSED)
92149216

92159217
if (UNEXPECTED(Z_TYPE_INFO_P(op1) == IS_UNDEF)) {
92169218
SAVE_OPLINE();
9217-
ZVAL_UNDEFINED_OP1();
9218-
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
9219+
zend_string *cv = CV_DEF_OF(EX_VAR_TO_NUM(opline->op1.var));
9220+
zend_throw_error_unchecked(NULL, "Undefined variable $%S", cv);
9221+
HANDLE_EXCEPTION();
92199222
}
92209223
ZEND_VM_NEXT_OPCODE();
92219224
}

0 commit comments

Comments
 (0)