diff --git a/Zend/tests/array_unpack/classes.phpt b/Zend/tests/array_unpack/classes.phpt index fa4b0f8e536f4..dfc111ca5c0c1 100644 --- a/Zend/tests/array_unpack/classes.phpt +++ b/Zend/tests/array_unpack/classes.phpt @@ -9,21 +9,21 @@ class C { public static $bar = [...self::ARR]; } -class D { - public const A = [...self::B]; - public const B = [...self::A]; +try { + class D { + public const A = [...self::B]; + public const B = [...self::A]; + } +} catch (Error $e) { + echo $e->getMessage(), "\n"; } var_dump(C::FOO); var_dump(C::$bar); -try { - var_dump(D::A); -} catch (Error $ex) { - echo "Exception: " . $ex->getMessage() . "\n"; -} ?> --EXPECT-- +Cannot declare self-referencing constant self::B array(5) { [0]=> int(0) @@ -44,4 +44,3 @@ array(3) { [2]=> int(3) } -Exception: Cannot declare self-referencing constant self::B diff --git a/Zend/tests/bug41633_2.phpt b/Zend/tests/bug41633_2.phpt index 975d2218b55e3..ff4875d5661af 100644 --- a/Zend/tests/bug41633_2.phpt +++ b/Zend/tests/bug41633_2.phpt @@ -11,4 +11,4 @@ echo Foo::A."\n"; Fatal error: Uncaught Error: Undefined constant self::B in %s:%d Stack trace: #0 {main} - thrown in %sbug41633_2.php on line 5 + thrown in %sbug41633_2.php on line 2 diff --git a/Zend/tests/bug69832.phpt b/Zend/tests/bug69832.phpt index 503de16422ea2..2247d3af0041b 100644 --- a/Zend/tests/bug69832.phpt +++ b/Zend/tests/bug69832.phpt @@ -3,6 +3,12 @@ Bug #69832 (Assertion failed in zend_compile_const_expr_magic_const) --FILE-- foo); var_dump($t->bar); diff --git a/Zend/tests/bug78868.phpt b/Zend/tests/bug78868.phpt index 7502662c9e739..0c087d5e35bce 100644 --- a/Zend/tests/bug78868.phpt +++ b/Zend/tests/bug78868.phpt @@ -2,6 +2,15 @@ Bug #78868: Calling __autoload() with incorrect EG(fake_scope) value --FILE-- foo(); + //doesn't affect the error + eval("class B {const foo = 1;}"); +} +spl_autoload_register('main_autoload'); + class C { private $private = 1; @@ -14,15 +23,6 @@ class A { static $foo = B::foo; //not resolved on include() } -function main_autoload($class_name) { - $c = new C; - $c->foo(); - //doesn't affect the error - eval("class B {const foo = 1;}"); -} - -spl_autoload_register('main_autoload'); - $classA = new ReflectionClass("A"); $props = $classA->getProperties(); $props[0]->setValue(2); //causes constant resolving, which runs autoload, all with EG(fake_scope) == "A" diff --git a/Zend/tests/constexpr/evaluation_error.phpt b/Zend/tests/constexpr/evaluation_error.phpt new file mode 100644 index 0000000000000..7cce5cde36e91 --- /dev/null +++ b/Zend/tests/constexpr/evaluation_error.phpt @@ -0,0 +1,43 @@ +--TEST-- +Error during evaluation of static/constants +--FILE-- +getMessage(), "\n"; +} +try { + var_dump(A::$s2); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(new A); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(new A); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +try { + // TODO: Should this fail? + var_dump(A::C); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Undefined constant "UNKNOWN" +Trying to use class for which initializer evaluation has previously failed +Trying to use class for which initializer evaluation has previously failed +Trying to use class for which initializer evaluation has previously failed +int(42) diff --git a/Zend/tests/constexpr/evaluation_order.phpt b/Zend/tests/constexpr/evaluation_order.phpt new file mode 100644 index 0000000000000..7b7a2c5db8218 --- /dev/null +++ b/Zend/tests/constexpr/evaluation_order.phpt @@ -0,0 +1,45 @@ +--TEST-- +Evaluation order of initializers in class declaration +--FILE-- + +--EXPECT-- +Creating 1 +Creating 2 +Creating 3 +Creating 4 +Creating 5 +Creating 6 + +Creating 10 +Creating 11 + +Creating 10 +Creating 11 +Creating 12 diff --git a/Zend/tests/constexpr/new.phpt b/Zend/tests/constexpr/new.phpt new file mode 100644 index 0000000000000..3a7747c970dc3 --- /dev/null +++ b/Zend/tests/constexpr/new.phpt @@ -0,0 +1,62 @@ +--TEST-- +new in constant expressions +--FILE-- +getMessage(), "\n"; +} + +const B = new stdClass; +var_dump(B); + +try { + eval('const C = new stdClass([] + 0);'); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +class Test { + public function __construct(public $a, public $b) {} +} + +try { + eval('const D = new Test(new stdClass, [] + 0);'); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +const E = new Test(new stdClass, 42); +var_dump(E); + +class Test2 { + public function __construct() { + echo "Side-effect\n"; + throw new Exception("Failed to construct"); + } +} + +try { + eval('const F = new Test2();'); +} catch (Exception $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Class "DoesNotExist" not found +object(stdClass)#2 (0) { +} +Unsupported operand types: array + int +Unsupported operand types: array + int +object(Test)#4 (2) { + ["a"]=> + object(stdClass)#1 (0) { + } + ["b"]=> + int(42) +} +Side-effect +Failed to construct diff --git a/Zend/tests/constexpr/new_anon_class.phpt b/Zend/tests/constexpr/new_anon_class.phpt new file mode 100644 index 0000000000000..b3b79e82a19c2 --- /dev/null +++ b/Zend/tests/constexpr/new_anon_class.phpt @@ -0,0 +1,10 @@ +--TEST-- +New with anonymous class is not supported in constant expressions +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use anonymous class in constant expression in %s on line %d diff --git a/Zend/tests/constexpr/new_arg_eval.phpt b/Zend/tests/constexpr/new_arg_eval.phpt new file mode 100644 index 0000000000000..d4d1f92899522 --- /dev/null +++ b/Zend/tests/constexpr/new_arg_eval.phpt @@ -0,0 +1,44 @@ +--TEST-- +Check that const exprs are pre-evaluated in new arguments +--FILE-- + +--EXPECTF-- +object(C)#1 (1) { + ["x"]=> + string(0) "" +} +object(C)#2 (1) { + ["x"]=> + string(4) "test" +} +object(C)#3 (1) { + ["x"]=> + string(%d) "%snew_arg_eval.php" +} +object(C)#3 (1) { + ["x"]=> + object(C)#2 (1) { + ["x"]=> + string(5) "test2" + } +} diff --git a/Zend/tests/constexpr/new_arg_unpack.phpt b/Zend/tests/constexpr/new_arg_unpack.phpt new file mode 100644 index 0000000000000..62dc82b392dc7 --- /dev/null +++ b/Zend/tests/constexpr/new_arg_unpack.phpt @@ -0,0 +1,10 @@ +--TEST-- +Argument unpacking in new arguments in const expr (not yet supported) +--FILE-- + +--EXPECTF-- +Fatal error: Argument unpacking in constant expressions is not supported in %s on line %d diff --git a/Zend/tests/constexpr/new_dynamic_class_name.phpt b/Zend/tests/constexpr/new_dynamic_class_name.phpt new file mode 100644 index 0000000000000..2a14c41b56591 --- /dev/null +++ b/Zend/tests/constexpr/new_dynamic_class_name.phpt @@ -0,0 +1,10 @@ +--TEST-- +Dynamic class name in new is not supported +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use dynamic class name in constant expression in %s on line %d diff --git a/Zend/tests/constexpr/new_in_property.phpt b/Zend/tests/constexpr/new_in_property.phpt new file mode 100644 index 0000000000000..113438c1d7404 --- /dev/null +++ b/Zend/tests/constexpr/new_in_property.phpt @@ -0,0 +1,69 @@ +--TEST-- +Behavior of new in non-static property +--FILE-- +prop === $o2->prop); +echo "\n"; + +class Test3 { + public function __construct() { + throw new Exception("Failed to construct"); + } + + public function __destruct() { + echo "Destructor\n"; + } +} + +class Test4 { + public $prop = new Test3(); + public $prop2 = new Test3(); +} + +try { + var_dump(new Test4); +} catch (Exception $e) { + echo $e, "\n"; +} + +class Test5 extends DateTime { + public $prop = new Test3(); + public $prop2 = new Test3(); +} + +try { + var_dump(new Test5); +} catch (Exception $e) { + echo $e, "\n"; +} + +?> +--EXPECTF-- +Instantiating object 1 +Instantiating object 2 +bool(false) + +Exception: Failed to construct in %s:%d +Stack trace: +#0 %s(%d): Test3->__construct() +#1 {main} +Exception: Failed to construct in %s:%d +Stack trace: +#0 %s(%d): Test3->__construct() +#1 {main} diff --git a/Zend/tests/constexpr/new_in_typed_context.phpt b/Zend/tests/constexpr/new_in_typed_context.phpt new file mode 100644 index 0000000000000..2ca81b4fc1d2c --- /dev/null +++ b/Zend/tests/constexpr/new_in_typed_context.phpt @@ -0,0 +1,44 @@ +--TEST-- +new as typed property/parameter default +--FILE-- +getMessage(), "\n"; +} + +function test(stdClass $arg = new stdClass) { + var_dump($arg); +} +test(); + +function test2(stdClass $arg = new Test) { + var_dump($arg); +} +try { + test2(); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +object(Test)#1 (1) { + ["prop"]=> + object(stdClass)#2 (0) { + } +} +Cannot assign Test to property Test2::$prop of type stdClass +object(stdClass)#1 (0) { +} +test2(): Argument #1 ($arg) must be of type stdClass, Test given, called in %s on line %d diff --git a/Zend/tests/constexpr/new_invalid_operation_in_arg.phpt b/Zend/tests/constexpr/new_invalid_operation_in_arg.phpt new file mode 100644 index 0000000000000..89a1fc9baffa4 --- /dev/null +++ b/Zend/tests/constexpr/new_invalid_operation_in_arg.phpt @@ -0,0 +1,10 @@ +--TEST-- +Invalid operation in new arg in const expr +--FILE-- + +--EXPECTF-- +Fatal error: Constant expression contains invalid operations in %s on line %d diff --git a/Zend/tests/constexpr/new_named_params.phpt b/Zend/tests/constexpr/new_named_params.phpt new file mode 100644 index 0000000000000..761ce97576bd3 --- /dev/null +++ b/Zend/tests/constexpr/new_named_params.phpt @@ -0,0 +1,51 @@ +--TEST-- +Named params in new in const expr (not supported yet) +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +object(Vec)#1 (3) { + ["x"]=> + float(0) + ["y"]=> + float(1) + ["z"]=> + float(2) +} +object(Vec)#2 (3) { + ["x"]=> + float(2) + ["y"]=> + float(1) + ["z"]=> + float(0) +} +object(Vec)#3 (3) { + ["x"]=> + float(0) + ["y"]=> + float(2) + ["z"]=> + float(1) +} +Named parameter $x overwrites previous argument diff --git a/Zend/tests/constexpr/new_positional_after_named.phpt b/Zend/tests/constexpr/new_positional_after_named.phpt new file mode 100644 index 0000000000000..45f7fd6f37bf2 --- /dev/null +++ b/Zend/tests/constexpr/new_positional_after_named.phpt @@ -0,0 +1,10 @@ +--TEST-- +Positional argument after named argument in new arguments +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use positional argument after named argument in %s on line %d diff --git a/Zend/tests/constexpr/new_recursion.phpt b/Zend/tests/constexpr/new_recursion.phpt new file mode 100644 index 0000000000000..13a061ab915c7 --- /dev/null +++ b/Zend/tests/constexpr/new_recursion.phpt @@ -0,0 +1,74 @@ +--TEST-- +new recursion in property default value +--FILE-- +getMessage(), "\n"; +} + +class Test2 { + // Check property name unmangling. + private $test = new Test2; +} + +try { + new Test2; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +// Check mutual recursion. +class Test3 { + public $test4 = new Test4; +} +class Test4 { + public $test3 = new Test3; +} + +try { + new Test3; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +try { + new Test4; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +// Mutual recursion through constructor. +class Test5 { + public $test6 = new Test6; +} +class Test6 { + public function __construct() { + new Test5; + } +} + +try { + new Test5; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +try { + new Test6; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Trying to recursively instantiate Test while evaluating default value for Test::$test +Trying to recursively instantiate Test2 while evaluating default value for Test2::$test +Trying to recursively instantiate Test3 while evaluating default value for Test3::$test4 +Trying to recursively instantiate Test4 while evaluating default value for Test4::$test3 +Trying to recursively instantiate Test5 while evaluating default value for Test5::$test6 +Trying to recursively instantiate Test5 while evaluating default value for Test5::$test6 diff --git a/Zend/tests/constexpr/new_static.phpt b/Zend/tests/constexpr/new_static.phpt new file mode 100644 index 0000000000000..26d868c57f5c3 --- /dev/null +++ b/Zend/tests/constexpr/new_static.phpt @@ -0,0 +1,10 @@ +--TEST-- +Static in new is not supported +--FILE-- + +--EXPECTF-- +Fatal error: "static" is not allowed in compile-time constants in %s on line %d diff --git a/Zend/tests/constexpr/new_trait_prop_compat.phpt b/Zend/tests/constexpr/new_trait_prop_compat.phpt new file mode 100644 index 0000000000000..d7ca8d4023a62 --- /dev/null +++ b/Zend/tests/constexpr/new_trait_prop_compat.phpt @@ -0,0 +1,26 @@ +--TEST-- +Compatibility of trait properties with new default +--FILE-- + +--EXPECTF-- +Fatal error: T1 and T2 define the same property ($prop) in the composition of B. However, the definition differs and is considered incompatible. Class was composed in %s on line %d diff --git a/Zend/tests/constexpr/objects.phpt b/Zend/tests/constexpr/objects.phpt new file mode 100644 index 0000000000000..08e075a0af796 --- /dev/null +++ b/Zend/tests/constexpr/objects.phpt @@ -0,0 +1,49 @@ +--TEST-- +Objects in constant expressions +--FILE-- +prop); +var_dump(CONSTANT); +var_dump(test()); + +?> +--EXPECT-- +object(Foo)#1 (1) { + ["prop"]=> + int(0) +} +object(Foo)#2 (1) { + ["prop"]=> + int(1) +} +object(Foo)#5 (1) { + ["prop"]=> + int(2) +} +object(Foo)#3 (1) { + ["prop"]=> + int(3) +} +object(Foo)#5 (1) { + ["prop"]=> + int(4) +} diff --git a/Zend/tests/constexpr/trait_evaluation.phpt b/Zend/tests/constexpr/trait_evaluation.phpt new file mode 100644 index 0000000000000..45601de2963ac --- /dev/null +++ b/Zend/tests/constexpr/trait_evaluation.phpt @@ -0,0 +1,29 @@ +--TEST-- +Initializers in traits should not be eagerly evaluated +--FILE-- + +--EXPECT-- +Creating C(1) +Creating C(2) +Creating T(1) diff --git a/Zend/tests/enum/case-attributes.phpt b/Zend/tests/enum/case-attributes.phpt index a5ec8600cdd3b..6a607c7a29b44 100644 --- a/Zend/tests/enum/case-attributes.phpt +++ b/Zend/tests/enum/case-attributes.phpt @@ -19,7 +19,7 @@ var_dump((new \ReflectionClassConstant(Foo::class, 'Bar'))->getAttributes(EnumCa ?> --EXPECT-- -object(EnumCaseAttribute)#1 (1) { +object(EnumCaseAttribute)#2 (1) { ["value"]=> string(3) "Bar" } diff --git a/Zend/tests/enum/name-property.phpt b/Zend/tests/enum/name-property.phpt index 2197f235bbb2d..0d4d5534e9ad5 100644 --- a/Zend/tests/enum/name-property.phpt +++ b/Zend/tests/enum/name-property.phpt @@ -23,7 +23,7 @@ var_dump(IntFoo::Bar->name); --EXPECT-- array(1) { [0]=> - object(ReflectionProperty)#2 (2) { + object(ReflectionProperty)#6 (2) { ["name"]=> string(4) "name" ["class"]=> @@ -33,14 +33,14 @@ array(1) { string(3) "Bar" array(2) { [0]=> - object(ReflectionProperty)#3 (2) { + object(ReflectionProperty)#5 (2) { ["name"]=> string(4) "name" ["class"]=> string(6) "IntFoo" } [1]=> - object(ReflectionProperty)#4 (2) { + object(ReflectionProperty)#7 (2) { ["name"]=> string(5) "value" ["class"]=> diff --git a/Zend/tests/invalid_parent_const_ref_leak.phpt b/Zend/tests/invalid_parent_const_ref_leak.phpt index ed728f7d45e47..9a185a232b3e1 100644 --- a/Zend/tests/invalid_parent_const_ref_leak.phpt +++ b/Zend/tests/invalid_parent_const_ref_leak.phpt @@ -3,12 +3,10 @@ Leak when using an invalid parent:: reference in a constant definition --FILE-- getMessage(), "\n"; } diff --git a/Zend/tests/type_declarations/typed_properties_021.phpt b/Zend/tests/type_declarations/typed_properties_021.phpt index d4d4d0ed19d2f..25d424f3bf0d9 100644 --- a/Zend/tests/type_declarations/typed_properties_021.phpt +++ b/Zend/tests/type_declarations/typed_properties_021.phpt @@ -2,12 +2,10 @@ Test typed properties delay type check on constant --FILE-- getMessage(), "\n"; } diff --git a/Zend/tests/type_declarations/typed_properties_058.phpt b/Zend/tests/type_declarations/typed_properties_058.phpt index 47c75037d1631..22b41b2dc1830 100644 --- a/Zend/tests/type_declarations/typed_properties_058.phpt +++ b/Zend/tests/type_declarations/typed_properties_058.phpt @@ -10,8 +10,12 @@ class A { public int $foo = FOO; } -class B { - public string $foo = FOO; +try { + class B { + public string $foo = FOO; + } +} catch (TypeError $e) { + echo $e->getMessage() . "\n"; } $o = new A(); @@ -27,6 +31,7 @@ for ($i = 0; $i < 2; $i++) { } ?> --EXPECT-- -int(5) -Cannot assign int to property B::$foo of type string Cannot assign int to property B::$foo of type string +int(5) +Trying to use class for which initializer evaluation has previously failed +Trying to use class for which initializer evaluation has previously failed diff --git a/Zend/zend_API.c b/Zend/zend_API.c index ca6214ea1fa03..a03078f29719a 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -1349,9 +1349,14 @@ ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type) / } } + if (ce_flags & ZEND_ACC_CONSTANTS_UPDATE_FAILED) { + zend_throw_error(NULL, "Trying to use class for which initializer evaluation has previously failed"); + return FAILURE; + } + if (class_type->parent) { if (UNEXPECTED(zend_update_class_constants(class_type->parent) != SUCCESS)) { - return FAILURE; + goto failure; } } @@ -1370,7 +1375,7 @@ ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type) / if (Z_TYPE(c->value) == IS_CONSTANT_AST) { val = &c->value; if (UNEXPECTED(zval_update_constant_ex(val, c->ce) != SUCCESS)) { - return FAILURE; + goto failure; } } } ZEND_HASH_FOREACH_END(); @@ -1414,23 +1419,27 @@ ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type) / val = (zval*)((char*)default_properties_table + prop_info->offset - OBJ_PROP_TO_OFFSET(0)); } if (Z_TYPE_P(val) == IS_CONSTANT_AST) { + if (!(prop_info->flags & ZEND_ACC_STATIC) + && GC_FLAGS(Z_AST_P(val)) & IS_AST_DYNAMIC) { + continue; + } if (ZEND_TYPE_IS_SET(prop_info->type)) { zval tmp; ZVAL_COPY(&tmp, val); if (UNEXPECTED(zval_update_constant_ex(&tmp, prop_info->ce) != SUCCESS)) { zval_ptr_dtor(&tmp); - return FAILURE; + goto failure; } /* property initializers must always be evaluated with strict types */; if (UNEXPECTED(!zend_verify_property_type(prop_info, &tmp, /* strict */ 1))) { zval_ptr_dtor(&tmp); - return FAILURE; + goto failure; } zval_ptr_dtor(val); ZVAL_COPY_VALUE(val, &tmp); } else if (UNEXPECTED(zval_update_constant_ex(val, prop_info->ce) != SUCCESS)) { - return FAILURE; + goto failure; } } } ZEND_HASH_FOREACH_END(); @@ -1452,10 +1461,18 @@ ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type) / } return SUCCESS; + +failure: + if (mutable_data) { + mutable_data->ce_flags |= ZEND_ACC_CONSTANTS_UPDATE_FAILED; + } else { + class_type->ce_flags |= ZEND_ACC_CONSTANTS_UPDATE_FAILED; + } + return FAILURE; } /* }}} */ -static zend_always_inline void _object_properties_init(zend_object *object, zend_class_entry *class_type) /* {{{ */ +static zend_always_inline zend_result _object_properties_init(zend_object *object, zend_class_entry *class_type) /* {{{ */ { if (class_type->default_properties_count) { zval *src = CE_DEFAULT_PROPERTIES_TABLE(class_type); @@ -1468,14 +1485,50 @@ static zend_always_inline void _object_properties_init(zend_object *object, zend src++; dst++; } while (src != end); + } else if (UNEXPECTED(class_type->ce_flags & ZEND_ACC_HAS_DYNAMIC_AST_PROPERTIES)) { + do { + ZVAL_COPY_PROP(dst, src); + if (Z_TYPE_P(dst) == IS_CONSTANT_AST) { + zend_property_info *prop_info = zend_get_property_info_for_slot(object, dst); + if (Z_EXTRA_P(src)) { + zend_throw_error(NULL, + "Trying to recursively instantiate %s " + "while evaluating default value for %s::$%s", + ZSTR_VAL(class_type->name), ZSTR_VAL(prop_info->ce->name), + zend_get_unmangled_property_name(prop_info->name)); + goto failure; + } + + Z_EXTRA_P(src) = 1; + zend_result result = zval_update_constant_ex(dst, prop_info->ce); + Z_EXTRA_P(src) = 0; + + if (result == FAILURE || (ZEND_TYPE_IS_SET(prop_info->type) + && !zend_verify_property_type(prop_info, dst, /* strict */ 1))) { +failure: + /* On failure, initialize the remaining properties with UNDEF. */ + zval_ptr_dtor_nogc(dst); + do { + ZVAL_UNDEF(dst); + src++; + dst++; + } while (src != end); + return FAILURE; + } + } + src++; + dst++; + } while (src != end); } else { do { ZVAL_COPY_PROP(dst, src); + ZEND_ASSERT(Z_TYPE_P(dst) != IS_CONSTANT_AST); src++; dst++; } while (src != end); } } + return SUCCESS; } /* }}} */ @@ -1612,13 +1665,28 @@ static zend_always_inline zend_result _object_and_properties_init(zval *arg, zen ZVAL_OBJ(arg, obj); if (properties) { object_properties_init_ex(obj, properties); - } else { - _object_properties_init(obj, class_type); + return SUCCESS; + } + + if (EXPECTED(_object_properties_init(obj, class_type) == SUCCESS)) { + return SUCCESS; } } else { + /* Report failure if an exception is thrown during create_object(). However, don't + * report a failure if an exception already exists, as this causes problems with + * extension code that does not handle exceptions early enough. */ + bool had_exception = EG(exception) != NULL; ZVAL_OBJ(arg, class_type->create_object(class_type)); + if (EXPECTED(!EG(exception) || had_exception)) { + return SUCCESS; + } } - return SUCCESS; + + zend_object_store_ctor_failed(Z_OBJ_P(arg)); + zval_ptr_dtor(arg); + ZVAL_NULL(arg); + Z_OBJ_P(arg) = NULL; + return FAILURE; } /* }}} */ @@ -3994,6 +4062,9 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z ce->ce_flags |= ZEND_ACC_HAS_AST_STATICS; } else { ce->ce_flags |= ZEND_ACC_HAS_AST_PROPERTIES; + if (GC_FLAGS(Z_AST_P(property)) & IS_AST_DYNAMIC) { + ce->ce_flags |= ZEND_ACC_HAS_DYNAMIC_AST_PROPERTIES; + } } } } diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index b8d35151559d6..99702a446473e 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -504,6 +504,14 @@ static zend_result zend_ast_add_unpacked_element(zval *result, zval *expr) { return FAILURE; } +zend_class_entry *zend_ast_fetch_class(zend_ast *ast, zend_class_entry *scope) +{ + zend_string *class_name = zend_ast_get_str(ast); + switch (ast->attr) { + } + return zend_fetch_class_by_name(class_name, NULL, ZEND_FETCH_CLASS_EXCEPTION); +} + ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast, zend_class_entry *scope) { zval op1, op2; @@ -784,6 +792,88 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast zend_enum_new(result, ce, case_name, case_value_zv); break; } + case ZEND_AST_NEW: + { + zend_class_entry *ce = zend_ast_fetch_class(ast->child[0], scope); + if (!ce) { + return FAILURE; + } + + if (object_init_ex(result, ce) != SUCCESS) { + return FAILURE; + } + + zend_ast_list *args_ast = zend_ast_get_list(ast->child[1]); + if (args_ast->attr) { + /* Has named arguments. */ + HashTable *args = zend_new_array(args_ast->children); + for (uint32_t i = 0; i < args_ast->children; i++) { + zend_ast *arg_ast = args_ast->child[i]; + zend_string *name = NULL; + zval arg; + if (arg_ast->kind == ZEND_AST_NAMED_ARG) { + name = zend_ast_get_str(arg_ast->child[0]); + arg_ast = arg_ast->child[1]; + } + if (zend_ast_evaluate(&arg, arg_ast, scope) == FAILURE) { + zend_array_destroy(args); + zval_ptr_dtor(result); + return FAILURE; + } + if (name) { + if (!zend_hash_add(args, name, &arg)) { + zend_throw_error(NULL, + "Named parameter $%s overwrites previous argument", + ZSTR_VAL(name)); + zend_array_destroy(args); + zval_ptr_dtor(result); + return FAILURE; + } + } else { + zend_hash_next_index_insert(args, &arg); + } + } + + zend_function *ctor = Z_OBJ_HT_P(result)->get_constructor(Z_OBJ_P(result)); + if (ctor) { + zend_call_known_function( + ctor, Z_OBJ_P(result), Z_OBJCE_P(result), NULL, 0, NULL, args); + } + + zend_array_destroy(args); + } else { + ALLOCA_FLAG(use_heap) + zval *args = do_alloca(sizeof(zval) * args_ast->children, use_heap); + for (uint32_t i = 0; i < args_ast->children; i++) { + if (zend_ast_evaluate(&args[i], args_ast->child[i], scope) == FAILURE) { + for (uint32_t j = 0; j < i; j++) { + zval_ptr_dtor(&args[j]); + } + free_alloca(args, use_heap); + zval_ptr_dtor(result); + return FAILURE; + } + } + + zend_function *ctor = Z_OBJ_HT_P(result)->get_constructor(Z_OBJ_P(result)); + if (ctor) { + zend_call_known_instance_method( + ctor, Z_OBJ_P(result), NULL, args_ast->children, args); + } + + for (uint32_t i = 0; i < args_ast->children; i++) { + zval_ptr_dtor(&args[i]); + } + free_alloca(args, use_heap); + } + + if (EG(exception)) { + zend_object_store_ctor_failed(Z_OBJ_P(result)); + zval_ptr_dtor(result); + return FAILURE; + } + return SUCCESS; + } default: zend_throw_error(NULL, "Unsupported constant expression"); ret = FAILURE; @@ -936,17 +1026,17 @@ ZEND_API void ZEND_FASTCALL zend_ast_ref_destroy(zend_ast_ref *ast) efree(ast); } -ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn) { +ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn, void *context) { if (zend_ast_is_list(ast)) { zend_ast_list *list = zend_ast_get_list(ast); uint32_t i; for (i = 0; i < list->children; ++i) { - fn(&list->child[i]); + fn(&list->child[i], context); } } else { uint32_t i, children = zend_ast_get_num_children(ast); for (i = 0; i < children; ++i) { - fn(&ast->child[i]); + fn(&ast->child[i], context); } } } diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index fb6587b48cd31..7889b83f37af7 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -302,8 +302,8 @@ ZEND_API zend_ast_ref * ZEND_FASTCALL zend_ast_copy(zend_ast *ast); ZEND_API void ZEND_FASTCALL zend_ast_destroy(zend_ast *ast); ZEND_API void ZEND_FASTCALL zend_ast_ref_destroy(zend_ast_ref *ast); -typedef void (*zend_ast_apply_func)(zend_ast **ast_ptr); -ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn); +typedef void (*zend_ast_apply_func)(zend_ast **ast_ptr, void *context); +ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn, void *context); static zend_always_inline bool zend_ast_is_special(zend_ast *ast) { return (ast->kind >> ZEND_AST_SPECIAL_SHIFT) & 1; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index f7e283d76d18e..b42d6af0402ab 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7563,7 +7563,7 @@ void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ } /* We currently don't early-bind classes that implement interfaces or use traits */ - if (!ce->num_interfaces && !ce->num_traits + if (!ce->num_interfaces && !ce->num_traits && (ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED) && !(CG(compiler_options) & ZEND_COMPILE_WITHOUT_EXECUTION)) { if (toplevel) { if (extends_ast) { @@ -9450,7 +9450,9 @@ bool zend_is_allowed_in_const_expr(zend_ast_kind kind) /* {{{ */ || kind == ZEND_AST_CONST || kind == ZEND_AST_CLASS_CONST || kind == ZEND_AST_CLASS_NAME || kind == ZEND_AST_MAGIC_CONST || kind == ZEND_AST_COALESCE - || kind == ZEND_AST_CONST_ENUM_INIT; + || kind == ZEND_AST_CONST_ENUM_INIT + || kind == ZEND_AST_NEW || kind == ZEND_AST_ARG_LIST + || kind == ZEND_AST_NAMED_ARG; } /* }}} */ @@ -9557,8 +9559,61 @@ void zend_compile_const_expr_magic_const(zend_ast **ast_ptr) /* {{{ */ } /* }}} */ -void zend_compile_const_expr(zend_ast **ast_ptr) /* {{{ */ +static void zend_compile_const_expr_new(zend_ast **ast_ptr) { + zend_ast *class_ast = (*ast_ptr)->child[0]; + if (class_ast->kind == ZEND_AST_CLASS) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use anonymous class in constant expression"); + } + if (class_ast->kind != ZEND_AST_ZVAL) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use dynamic class name in constant expression"); + } + + zend_string *class_name = zend_resolve_class_name_ast(class_ast); + int fetch_type = zend_get_class_fetch_type(class_name); + if (ZEND_FETCH_CLASS_STATIC == fetch_type) { + zend_error_noreturn(E_COMPILE_ERROR, + "\"static\" is not allowed in compile-time constants"); + } + + zval *class_ast_zv = zend_ast_get_zval(class_ast); + zval_ptr_dtor_nogc(class_ast_zv); + ZVAL_STR(class_ast_zv, class_name); + class_ast->attr = fetch_type; +} + +static void zend_compile_const_expr_args(zend_ast **ast_ptr) +{ + zend_ast_list *list = zend_ast_get_list(*ast_ptr); + bool uses_named_args = false; + for (uint32_t i = 0; i < list->children; i++) { + zend_ast *arg = list->child[i]; + if (arg->kind == ZEND_AST_UNPACK) { + zend_error_noreturn(E_COMPILE_ERROR, + "Argument unpacking in constant expressions is not supported"); + } + if (arg->kind == ZEND_AST_NAMED_ARG) { + uses_named_args = true; + } else if (uses_named_args) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use positional argument after named argument"); + } + } + if (uses_named_args) { + list->attr = 1; + } +} + +typedef struct { + /* Whether the value of this expression may differ on each evaluation. */ + bool is_dynamic; +} const_expr_context; + +void zend_compile_const_expr(zend_ast **ast_ptr, void *context) /* {{{ */ +{ + const_expr_context *ctx = (const_expr_context *) context; zend_ast *ast = *ast_ptr; if (ast == NULL || ast->kind == ZEND_AST_ZVAL) { return; @@ -9581,21 +9636,34 @@ void zend_compile_const_expr(zend_ast **ast_ptr) /* {{{ */ case ZEND_AST_MAGIC_CONST: zend_compile_const_expr_magic_const(ast_ptr); break; - default: - zend_ast_apply(ast, zend_compile_const_expr); + case ZEND_AST_NEW: + zend_compile_const_expr_new(ast_ptr); + ctx->is_dynamic = true; + break; + case ZEND_AST_ARG_LIST: + zend_compile_const_expr_args(ast_ptr); break; } + + zend_ast_apply(ast, zend_compile_const_expr, context); } /* }}} */ void zend_const_expr_to_zval(zval *result, zend_ast **ast_ptr) /* {{{ */ { + const_expr_context context; + context.is_dynamic = false; + zend_eval_const_expr(ast_ptr); - zend_compile_const_expr(ast_ptr); + zend_compile_const_expr(ast_ptr, &context); if ((*ast_ptr)->kind != ZEND_AST_ZVAL) { /* Replace with compiled AST zval representation. */ zval ast_zv; ZVAL_AST(&ast_zv, zend_ast_copy(*ast_ptr)); + if (context.is_dynamic) { + GC_ADD_FLAGS(Z_AST(ast_zv), IS_AST_DYNAMIC); + } + zend_ast_destroy(*ast_ptr); *ast_ptr = zend_ast_create_zval(&ast_zv); } @@ -10229,6 +10297,25 @@ void zend_eval_const_expr(zend_ast **ast_ptr) /* {{{ */ } break; } + // TODO: We should probably use zend_ast_apply to recursively walk nodes without + // special handling. It is required that all nodes that are part of a const expr + // are visited. Probably we should be distinguishing evaluation of const expr and + // normal exprs here. + case ZEND_AST_ARG_LIST: + { + zend_ast_list *list = zend_ast_get_list(ast); + for (uint32_t i = 0; i < list->children; i++) { + zend_eval_const_expr(&list->child[i]); + } + return; + } + case ZEND_AST_NEW: + zend_eval_const_expr(&ast->child[0]); + zend_eval_const_expr(&ast->child[1]); + return; + case ZEND_AST_NAMED_ARG: + zend_eval_const_expr(&ast->child[1]); + return; default: return; } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 9e22837d61582..2252ea2e4c07c 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -238,7 +238,7 @@ typedef struct _zend_oparray_context { /* ZEND_ACC_ visibility flags or IS_CONSTANT_VISITED_MARK | | | */ #define ZEND_CLASS_CONST_IS_CASE (1 << 6) /* | | | X */ /* | | | */ -/* Class Flags (unused: 29...) | | | */ +/* Class Flags (unused: 30...) | | | */ /* =========== | | | */ /* | | | */ /* Special class types | | | */ @@ -297,6 +297,10 @@ typedef struct _zend_oparray_context { #define ZEND_ACC_HAS_AST_CONSTANTS (1 << 24) /* X | | | */ #define ZEND_ACC_HAS_AST_PROPERTIES (1 << 25) /* X | | | */ #define ZEND_ACC_HAS_AST_STATICS (1 << 26) /* X | | | */ +#define ZEND_ACC_HAS_DYNAMIC_AST_PROPERTIES (1 << 29) + /* X | | | */ +/* An error was thrown during constant updating | | | */ +#define ZEND_ACC_CONSTANTS_UPDATE_FAILED (1 << 30) /* X | | | */ /* | | | */ /* loaded from file cache to process memory | | | */ #define ZEND_ACC_FILE_CACHED (1 << 27) /* X | | | */ diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c index af71ac7a6589b..eff9c06b3b170 100644 --- a/Zend/zend_exceptions.c +++ b/Zend/zend_exceptions.c @@ -819,7 +819,11 @@ static zend_object *zend_throw_exception_zstr(zend_class_entry *exception_ce, ze ZEND_ASSERT(instanceof_function(exception_ce, zend_ce_throwable) && "Exceptions must implement Throwable"); + zend_object *orig_exception = EG(exception); + EG(exception) = NULL; object_init_ex(&ex, exception_ce); + ZEND_ASSERT(!EG(exception)); + EG(exception) = orig_exception; if (message) { ZVAL_STR(&tmp, message); diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index ba2610e6abf61..e28cb0d4d8749 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1294,6 +1294,7 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par if (Z_OPT_TYPE_P(dst) == IS_CONSTANT_AST) { ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; ce->ce_flags |= ZEND_ACC_HAS_AST_PROPERTIES; + ZEND_ASSERT(!(GC_FLAGS(Z_AST_P(dst)) & IS_AST_DYNAMIC)); } continue; } while (dst != end); @@ -1305,6 +1306,9 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par if (Z_OPT_TYPE_P(dst) == IS_CONSTANT_AST) { ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; ce->ce_flags |= ZEND_ACC_HAS_AST_PROPERTIES; + if (GC_FLAGS(Z_AST_P(dst)) & IS_AST_DYNAMIC) { + ce->ce_flags |= ZEND_ACC_HAS_DYNAMIC_AST_PROPERTIES; + } } continue; } while (dst != end); @@ -2066,13 +2070,17 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent op2 = &traits[i]->default_properties_table[OBJ_PROP_TO_NUM(property_info->offset)]; } - /* if any of the values is a constant, we try to resolve it */ - if (UNEXPECTED(Z_TYPE_P(op1) == IS_CONSTANT_AST)) { + /* If any of the values is a constant, we try to resolve it. Don't resolve + * "dynamic" ASTs, which may have side-effects. For these we assume + * incompatibility. */ + if (UNEXPECTED(Z_TYPE_P(op1) == IS_CONSTANT_AST) + && !(GC_FLAGS(Z_AST_P(op1)) & IS_AST_DYNAMIC)) { ZVAL_COPY_OR_DUP(&op1_tmp, op1); zval_update_constant_ex(&op1_tmp, ce); op1 = &op1_tmp; } - if (UNEXPECTED(Z_TYPE_P(op2) == IS_CONSTANT_AST)) { + if (UNEXPECTED(Z_TYPE_P(op2) == IS_CONSTANT_AST) + && !(GC_FLAGS(Z_AST_P(op2)) & IS_AST_DYNAMIC)) { ZVAL_COPY_OR_DUP(&op2_tmp, op2); zval_update_constant_ex(&op2_tmp, ce); op2 = &op2_tmp; @@ -2702,6 +2710,9 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string } zv = zend_hash_find_ex(CG(class_table), key, 1); Z_CE_P(zv) = ret; + if (!(ce->ce_flags & ZEND_ACC_TRAIT)) { + zend_update_class_constants(ret); + } return ret; } } else { @@ -2823,6 +2834,9 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string free_alloca(traits_and_interfaces, use_heap); } + if (!(ce->ce_flags & ZEND_ACC_TRAIT)) { + zend_update_class_constants(ce); + } return ce; } /* }}} */ diff --git a/Zend/zend_types.h b/Zend/zend_types.h index dbe2df23deedc..82e0fb9e2940c 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -721,6 +721,9 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) { #define IS_OBJ_DESTRUCTOR_CALLED (1<<8) #define IS_OBJ_FREE_CALLED (1<<9) +/* ast flags */ +#define IS_AST_DYNAMIC (1<<8) + #define OBJ_FLAGS(obj) GC_FLAGS(obj) /* Recursion protection macros must be used only for arrays and objects */ diff --git a/ext/reflection/tests/ReflectionClassConstant_toString_error.phpt b/ext/reflection/tests/ReflectionClassConstant_toString_error.phpt index 54290e97aa30a..2cee746e3a094 100644 --- a/ext/reflection/tests/ReflectionClassConstant_toString_error.phpt +++ b/ext/reflection/tests/ReflectionClassConstant_toString_error.phpt @@ -3,8 +3,12 @@ Exception thrown while converting ReflectionClassConstant to string --FILE-- getMessage(), "\n"; } try { @@ -16,3 +20,4 @@ try { ?> --EXPECT-- Undefined constant self::UNKNOWN +Undefined constant self::UNKNOWN diff --git a/ext/reflection/tests/ReflectionClass_toString_004.phpt b/ext/reflection/tests/ReflectionClass_toString_004.phpt index 0eecb3c8a3460..b1f26f12edd28 100644 --- a/ext/reflection/tests/ReflectionClass_toString_004.phpt +++ b/ext/reflection/tests/ReflectionClass_toString_004.phpt @@ -3,9 +3,14 @@ Constant evaluation exception during ReflectionClass::__toString() --FILE-- getMessage(), "\n"; } + try { echo new ReflectionClass(A::class); } catch (Error $e) { @@ -15,3 +20,4 @@ try { ?> --EXPECT-- Undefined constant self::UNKNOWN +Undefined constant self::UNKNOWN diff --git a/ext/reflection/tests/ReflectionEnum_getCase.phpt b/ext/reflection/tests/ReflectionEnum_getCase.phpt index f2b53cf657920..0d65da0dcf1b7 100644 --- a/ext/reflection/tests/ReflectionEnum_getCase.phpt +++ b/ext/reflection/tests/ReflectionEnum_getCase.phpt @@ -32,7 +32,7 @@ test(IntEnum::class, 'Baz'); ?> --EXPECT-- -object(ReflectionEnumUnitCase)#2 (2) { +object(ReflectionEnumUnitCase)#4 (2) { ["name"]=> string(3) "Foo" ["class"]=> @@ -40,7 +40,7 @@ object(ReflectionEnumUnitCase)#2 (2) { } ReflectionException: Enum_::Bar is not a case ReflectionException: Case Enum_::Baz does not exist -object(ReflectionEnumBackedCase)#2 (2) { +object(ReflectionEnumBackedCase)#4 (2) { ["name"]=> string(3) "Foo" ["class"]=> diff --git a/ext/reflection/tests/ReflectionEnum_getCases.phpt b/ext/reflection/tests/ReflectionEnum_getCases.phpt index 936500877426c..db40f0e9d23ae 100644 --- a/ext/reflection/tests/ReflectionEnum_getCases.phpt +++ b/ext/reflection/tests/ReflectionEnum_getCases.phpt @@ -22,14 +22,14 @@ var_dump((new ReflectionEnum(IntEnum::class))->getCases()); --EXPECT-- array(2) { [0]=> - object(ReflectionEnumUnitCase)#2 (2) { + object(ReflectionEnumUnitCase)#6 (2) { ["name"]=> string(3) "Foo" ["class"]=> string(5) "Enum_" } [1]=> - object(ReflectionEnumUnitCase)#3 (2) { + object(ReflectionEnumUnitCase)#7 (2) { ["name"]=> string(3) "Bar" ["class"]=> @@ -38,14 +38,14 @@ array(2) { } array(2) { [0]=> - object(ReflectionEnumBackedCase)#2 (2) { + object(ReflectionEnumBackedCase)#6 (2) { ["name"]=> string(3) "Foo" ["class"]=> string(7) "IntEnum" } [1]=> - object(ReflectionEnumBackedCase)#1 (2) { + object(ReflectionEnumBackedCase)#5 (2) { ["name"]=> string(3) "Bar" ["class"]=> diff --git a/ext/reflection/tests/bug76536.phpt b/ext/reflection/tests/bug76536.phpt index d6b22ada21868..1d7da3efc894a 100644 --- a/ext/reflection/tests/bug76536.phpt +++ b/ext/reflection/tests/bug76536.phpt @@ -2,13 +2,14 @@ Bug #76536 (PHP crashes with core dump when throwing exception in error handler) --FILE-- getConstants(); ?> diff --git a/ext/reflection/tests/new_in_attributes.phpt b/ext/reflection/tests/new_in_attributes.phpt new file mode 100644 index 0000000000000..4c6d4476f3dee --- /dev/null +++ b/ext/reflection/tests/new_in_attributes.phpt @@ -0,0 +1,69 @@ +--TEST-- +new in attribute arguments +--FILE-- +getAttributes()[0]; +$args1 = $ra->getArguments(); +$obj1 = $ra->newInstance(); +var_dump($args1, $obj1); + +// Check that we get fresh instances each time: +$args2 = $ra->getArguments(); +$obj2 = $ra->newInstance(); +var_dump($args1[1] !== $args2[1]); +var_dump($obj1->y !== $obj2->y); + +// Check that named args work: +#[MyAttribute(y: new stdClass, x: null)] +class Test2 { +} + +$rc = new ReflectionClass(Test2::class); +$ra = $rc->getAttributes()[0]; +$args = $ra->getArguments(); +$obj = $ra->newInstance(); +var_dump($args, $obj); + +?> +--EXPECT-- +array(2) { + [0]=> + NULL + [1]=> + object(stdClass)#3 (0) { + } +} +object(MyAttribute)#4 (2) { + ["x"]=> + NULL + ["y"]=> + object(stdClass)#5 (0) { + } +} +bool(true) +bool(true) +array(2) { + ["y"]=> + object(stdClass)#2 (0) { + } + ["x"]=> + NULL +} +object(MyAttribute)#10 (2) { + ["x"]=> + NULL + ["y"]=> + object(stdClass)#11 (0) { + } +} diff --git a/ext/reflection/tests/new_in_constexpr.phpt b/ext/reflection/tests/new_in_constexpr.phpt new file mode 100644 index 0000000000000..268b70025d1ff --- /dev/null +++ b/ext/reflection/tests/new_in_constexpr.phpt @@ -0,0 +1,57 @@ +--TEST-- +Handling of new in constant expressions in reflection +--FILE-- +getReflectionConstant('A'); +var_dump($rcc->getValue() === Test1::A); + +function test1() { + static $x = new stdClass; + return $x; +} + +$rf = new ReflectionFunction('test1'); +$s = $rf->getStaticVariables(); +var_dump($s['x'] === test1()); + +function test2($x = new stdClass) { + return $x; +} + +$rf = new ReflectionFunction('test2'); +$rp = $rf->getParameters()[0]; +// Parameter default should *not* be the same. +var_dump($rp->getDefaultValue() !== test2()); + +class Test2 { + public static $prop1 = new stdClass; + public $prop2 = new stdClass; +} + +$rc = new ReflectionClass(Test2::class); +$rp1 = $rc->getProperty('prop1'); +$rp2 = $rc->getProperty('prop2'); +// For static properties, the value should be the same, +// but the default value shouldn't be. +var_dump($rp1->getValue() === Test2::$prop1); +// TODO: There is a pre-existing bug where the static property +// default value is actually the current value without opcache. +// var_dump($rp1->getDefaultValue() !== Test2::$prop1); +$o = new Test2; +var_dump($rp2->getValue($o) === $o->prop2); +var_dump($rp2->getDefaultValue() !== $o->prop2); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/ext/standard/tests/filters/object_init_failure.phpt b/ext/standard/tests/filters/object_init_failure.phpt index 3a88d87ccab06..2fc0d5fe20b8d 100644 --- a/ext/standard/tests/filters/object_init_failure.phpt +++ b/ext/standard/tests/filters/object_init_failure.phpt @@ -2,8 +2,12 @@ Creating the stream filter object may fail --FILE-- getMessage(), "\n"; } stream_filter_register('sample.filter', SampleFilter::class); try { @@ -13,7 +17,9 @@ try { } ?> --EXPECTF-- +Undefined constant "FOO" + Warning: file_get_contents(): Unable to create or locate filter "sample.filter" in %s on line %d Warning: file_get_contents(): Unable to create filter (sample.filter) in %s on line %d -Undefined constant "FOO" +Trying to use class for which initializer evaluation has previously failed diff --git a/ext/standard/tests/filters/object_init_failure_2.phpt b/ext/standard/tests/filters/object_init_failure_2.phpt index 32473bc83127d..af4303ba39e81 100644 --- a/ext/standard/tests/filters/object_init_failure_2.phpt +++ b/ext/standard/tests/filters/object_init_failure_2.phpt @@ -2,8 +2,12 @@ Creating the stream filter object may fail (include variation) --FILE-- getMessage(), "\n"; } stream_filter_register('sample.filter', SampleFilter::class); try { @@ -13,7 +17,9 @@ try { } ?> --EXPECTF-- +Undefined constant "FOO" + Warning: main(): Unable to create or locate filter "sample.filter" in %s on line %d Warning: main(): Unable to create filter (sample.filter) in %s on line %d -Undefined constant "FOO" +Trying to use class for which initializer evaluation has previously failed diff --git a/ext/standard/tests/streams/bug77664.phpt b/ext/standard/tests/streams/bug77664.phpt index 4d9a2e0cb22a5..1516f86e43720 100644 --- a/ext/standard/tests/streams/bug77664.phpt +++ b/ext/standard/tests/streams/bug77664.phpt @@ -2,15 +2,21 @@ BUG #77664 (Segmentation fault when using undefined constant in custom wrapper) --FILE-- getMessage(), "\n"; } stream_wrapper_register('error',ErrorWrapper::class); file_get_contents('error://test'); ?> --EXPECTF-- -Fatal error: Uncaught Error: Undefined constant self::INVALID in %s:%d +Undefined constant self::INVALID + +Fatal error: Uncaught Error: Trying to use class for which initializer evaluation has previously failed in %s:%d Stack trace: #0 %sbug77664.php(%d): file_get_contents('error://test') #1 {main} diff --git a/ext/tokenizer/tests/PhpToken_extension_errors.phpt b/ext/tokenizer/tests/PhpToken_extension_errors.phpt index 72164f398cb1b..8875fe3a660ef 100644 --- a/ext/tokenizer/tests/PhpToken_extension_errors.phpt +++ b/ext/tokenizer/tests/PhpToken_extension_errors.phpt @@ -5,8 +5,12 @@ PhpToken extensions that throw during construction --FILE-- getMessage(), "\n"; } try { @@ -27,4 +31,5 @@ try { ?> --EXPECT-- Undefined constant "UNKNOWN" +Trying to use class for which initializer evaluation has previously failed Cannot instantiate abstract class MyPhpToken2 diff --git a/tests/classes/constants_basic_006.phpt b/tests/classes/constants_basic_006.phpt deleted file mode 100644 index a293c0bede108..0000000000000 --- a/tests/classes/constants_basic_006.phpt +++ /dev/null @@ -1,43 +0,0 @@ ---TEST-- -Ensure class constants are not evaluated when a class is looked up to resolve inheritance during runtime. ---FILE-- - D::V, E::A => K); - } - - eval('class D extends C { const V = \'test\'; }'); - - class E extends D - { - const A = "hello"; - } - - define('K', "nasty"); - - var_dump(C::X, C::$a, D::X, D::$a, E::X, E::$a); -?> ---EXPECT-- -string(5) "hello" -array(2) { - ["nasty"]=> - string(4) "test" - ["hello"]=> - string(5) "nasty" -} -string(5) "hello" -array(2) { - ["nasty"]=> - string(4) "test" - ["hello"]=> - string(5) "nasty" -} -string(5) "hello" -array(2) { - ["nasty"]=> - string(4) "test" - ["hello"]=> - string(5) "nasty" -}