diff --git a/Zend/tests/strict_namespace_bad_value.phpt b/Zend/tests/strict_namespace_bad_value.phpt new file mode 100644 index 000000000000..a95fd90b2279 --- /dev/null +++ b/Zend/tests/strict_namespace_bad_value.phpt @@ -0,0 +1,8 @@ +--TEST-- +declare(strict_namespace=1) value must be 0 or 1 +--FILE-- + +--EXPECTF-- +Fatal error: strict_namespace declaration must have 0 or 1 as its value in %s on line %d diff --git a/Zend/tests/strict_namespace_block_mode.phpt b/Zend/tests/strict_namespace_block_mode.phpt new file mode 100644 index 000000000000..ed1b81329f86 --- /dev/null +++ b/Zend/tests/strict_namespace_block_mode.phpt @@ -0,0 +1,9 @@ +--TEST-- +declare(strict_namespace=1) must not use block mode +--FILE-- + +--EXPECTF-- +Fatal error: strict_namespace declaration must not use block mode in %s on line %d diff --git a/Zend/tests/strict_namespace_constants.phpt b/Zend/tests/strict_namespace_constants.phpt new file mode 100644 index 000000000000..534977f6c7ed --- /dev/null +++ b/Zend/tests/strict_namespace_constants.phpt @@ -0,0 +1,50 @@ +--TEST-- +declare(strict_namespace=1) disables global fallback for unqualified constants +--FILE-- +getMessage(), "\n"; +} + +// PHP_INT_MAX resolves to Foo\PHP_INT_MAX; fully qualified reaches the global +try { + \var_dump(PHP_INT_MAX); +} catch (\Error $e) { + echo $e->getMessage(), "\n"; +} +\var_dump(\is_int(\PHP_INT_MAX)); + +// constant expressions follow the same rule +const PREFIX = 'p-'; +const COMBINED = PREFIX . 'x'; +\var_dump(COMBINED); + +// true/false/null are keywords, never namespace-resolved +\var_dump(null, true, false); +?> +--EXPECT-- +string(12) "foo-greeting" +string(15) "global-bareword" +Undefined constant "Foo\BAREWORD" +Undefined constant "Foo\PHP_INT_MAX" +bool(true) +string(3) "p-x" +NULL +bool(true) +bool(false) diff --git a/Zend/tests/strict_namespace_not_first.phpt b/Zend/tests/strict_namespace_not_first.phpt new file mode 100644 index 000000000000..66ca516f3527 --- /dev/null +++ b/Zend/tests/strict_namespace_not_first.phpt @@ -0,0 +1,9 @@ +--TEST-- +declare(strict_namespace=1) must be the first statement in the script +--FILE-- + +--EXPECTF-- +Fatal error: strict_namespace declaration must be the very first statement in the script in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index ac5a9d71a6ea..8a536eb9f4fd 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -407,6 +407,7 @@ void zend_file_context_begin(zend_file_context *prev_context) /* {{{ */ FC(in_namespace) = 0; FC(has_bracketed_namespaces) = 0; FC(declarables).ticks = 0; + FC(declarables).strict_namespace = 0; zend_hash_init(&FC(seen_symbols), 8, NULL, NULL, 0); } /* }}} */ @@ -4091,6 +4092,11 @@ static bool zend_compile_function_name(znode *name_node, zend_ast *name_ast) /* ZVAL_STR(&name_node->u.constant, zend_resolve_function_name( orig_name, name_ast->attr, &is_fully_qualified)); + /* Name is already namespace-prefixed; resolve as qualified, no fallback */ + if (FC(declarables).strict_namespace) { + return false; + } + return !is_fully_qualified && FC(current_namespace); } /* }}} */ @@ -7312,6 +7318,27 @@ static void zend_compile_declare(const zend_ast *ast) /* {{{ */ CG(active_op_array)->fn_flags |= ZEND_ACC_STRICT_TYPES; } + } else if (zend_string_equals_literal_ci(name, "strict_namespace")) { + zval value_zv; + + if (FAILURE == zend_is_first_statement(ast, /* allow_nop */ true)) { + zend_error_noreturn(E_COMPILE_ERROR, "strict_namespace declaration must be " + "the very first statement in the script"); + } + + if (ast->child[1] != NULL) { + zend_error_noreturn(E_COMPILE_ERROR, "strict_namespace declaration must not " + "use block mode"); + } + + zend_const_expr_to_zval(&value_zv, value_ast_ptr, /* allow_dynamic */ false); + + if (Z_TYPE(value_zv) != IS_LONG || (Z_LVAL(value_zv) != 0 && Z_LVAL(value_zv) != 1)) { + zend_error_noreturn(E_COMPILE_ERROR, "strict_namespace declaration must have 0 or 1 as its value"); + } + + FC(declarables).strict_namespace = Z_LVAL(value_zv) == 1; + } else { zend_error(E_COMPILE_WARNING, "Unsupported declare '%s'", ZSTR_VAL(name)); } @@ -11370,7 +11397,9 @@ static void zend_compile_const(znode *result, const zend_ast *ast) /* {{{ */ opline = zend_emit_op_tmp(result, ZEND_FETCH_CONSTANT, NULL, NULL); opline->op2_type = IS_CONST; - if (is_fully_qualified || !FC(current_namespace)) { + /* strict_namespace disables the fallback; true/false/null were already + * resolved by zend_try_ct_eval_const() above */ + if (is_fully_qualified || FC(declarables).strict_namespace || !FC(current_namespace)) { opline->op1.num = 0; opline->op2.constant = zend_add_const_name_literal( resolved_name, false); @@ -11747,8 +11776,10 @@ static void zend_compile_const_expr_const(zend_ast **ast_ptr) /* {{{ */ } zend_ast_destroy(ast); + /* strict_namespace also suppresses the fallback in constant expressions + * (const X = FOO;, default values, attribute arguments) */ *ast_ptr = zend_ast_create_constant(resolved_name, - !is_fully_qualified && FC(current_namespace) ? IS_CONSTANT_UNQUALIFIED_IN_NAMESPACE : 0); + !is_fully_qualified && !FC(declarables).strict_namespace && FC(current_namespace) ? IS_CONSTANT_UNQUALIFIED_IN_NAMESPACE : 0); } /* }}} */ diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 2351882a560d..93defbe9c85c 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -104,6 +104,8 @@ static zend_always_inline znode *zend_ast_get_znode(zend_ast *ast) { typedef struct _zend_declarables { zend_long ticks; + /* No global fallback for unqualified function/constant names in a namespace */ + bool strict_namespace; } zend_declarables; /* Compilation context that is different for each file, but shared between op arrays. */