diff --git a/Zend/tests/attributes/NonpublicConstructor/Generator.phpt b/Zend/tests/attributes/NonpublicConstructor/Generator.phpt new file mode 100644 index 0000000000000..2101de37edc37 --- /dev/null +++ b/Zend/tests/attributes/NonpublicConstructor/Generator.phpt @@ -0,0 +1,13 @@ +--TEST-- +#[\NonpublicConstructor]: works as a replacement for get_constructor +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Call to private Generator::__construct() from global scope, the "Generator" class is reserved for internal use and cannot be manually instantiated, in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/attributes/NonpublicConstructor/Reflection_newInstance.phpt b/Zend/tests/attributes/NonpublicConstructor/Reflection_newInstance.phpt new file mode 100644 index 0000000000000..f251e5116e072 --- /dev/null +++ b/Zend/tests/attributes/NonpublicConstructor/Reflection_newInstance.phpt @@ -0,0 +1,20 @@ +--TEST-- +#[\NonpublicConstructor]: affects message from ReflectionClass::newInstance() +--FILE-- +newInstance( true ); + +?> +--EXPECTF-- +Fatal error: Uncaught ReflectionException: Access to non-public constructor of class Demo, use ::getInstance() instead, in %s:%d +Stack trace: +#0 %s(%d): ReflectionClass->newInstance(true) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/attributes/NonpublicConstructor/Reflection_newInstanceArgs.phpt b/Zend/tests/attributes/NonpublicConstructor/Reflection_newInstanceArgs.phpt new file mode 100644 index 0000000000000..9724d90f9f134 --- /dev/null +++ b/Zend/tests/attributes/NonpublicConstructor/Reflection_newInstanceArgs.phpt @@ -0,0 +1,20 @@ +--TEST-- +#[\NonpublicConstructor]: affects message from ReflectionClass::newInstanceArgs() +--FILE-- +newInstanceArgs( [ true ] ); + +?> +--EXPECTF-- +Fatal error: Uncaught ReflectionException: Access to non-public constructor of class Demo, use ::getInstance() instead, in %s:%d +Stack trace: +#0 %s(%d): ReflectionClass->newInstanceArgs(Array) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/attributes/NonpublicConstructor/as_object.phpt b/Zend/tests/attributes/NonpublicConstructor/as_object.phpt new file mode 100644 index 0000000000000..8d6ad0d0e5251 --- /dev/null +++ b/Zend/tests/attributes/NonpublicConstructor/as_object.phpt @@ -0,0 +1,14 @@ +--TEST-- +#[\NonpublicConstructor]: using the attribute itself as an object +--FILE-- + +--EXPECTF-- +object(NonpublicConstructor)#%d (1) { + ["message"]=> + string(7) "example" +} diff --git a/Zend/tests/attributes/NonpublicConstructor/global_private.phpt b/Zend/tests/attributes/NonpublicConstructor/global_private.phpt new file mode 100644 index 0000000000000..30a73f98adeb2 --- /dev/null +++ b/Zend/tests/attributes/NonpublicConstructor/global_private.phpt @@ -0,0 +1,18 @@ +--TEST-- +#[\NonpublicConstructor]: affects error message (private constructor, global scope) +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Call to private Demo::__construct() from global scope, use ::getInstance() instead, in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %s diff --git a/Zend/tests/attributes/NonpublicConstructor/global_protected.phpt b/Zend/tests/attributes/NonpublicConstructor/global_protected.phpt new file mode 100644 index 0000000000000..e7b2a29d7896c --- /dev/null +++ b/Zend/tests/attributes/NonpublicConstructor/global_protected.phpt @@ -0,0 +1,18 @@ +--TEST-- +#[\NonpublicConstructor]: affects error message (protected constructor, global scope) +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Call to protected Demo::__construct() from global scope, use ::getInstance() instead, in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %s diff --git a/Zend/tests/attributes/NonpublicConstructor/must_be_constructor.phpt b/Zend/tests/attributes/NonpublicConstructor/must_be_constructor.phpt new file mode 100644 index 0000000000000..b866704ddcdeb --- /dev/null +++ b/Zend/tests/attributes/NonpublicConstructor/must_be_constructor.phpt @@ -0,0 +1,13 @@ +--TEST-- +#[\NonpublicConstructor]: On a private non-constructor +--FILE-- + +--EXPECTF-- +Fatal error: #[NonpublicConstructor] can only be applied to constructors in %s on line %d diff --git a/Zend/tests/attributes/NonpublicConstructor/must_be_nonpublic.phpt b/Zend/tests/attributes/NonpublicConstructor/must_be_nonpublic.phpt new file mode 100644 index 0000000000000..5bf9381fb80da --- /dev/null +++ b/Zend/tests/attributes/NonpublicConstructor/must_be_nonpublic.phpt @@ -0,0 +1,13 @@ +--TEST-- +#[\NonpublicConstructor]: On a public constructor +--FILE-- + +--EXPECTF-- +Fatal error: #[NonpublicConstructor] can only be applied to protected or private constructors in %s on line %d diff --git a/Zend/tests/attributes/NonpublicConstructor/non_string.phpt b/Zend/tests/attributes/NonpublicConstructor/non_string.phpt new file mode 100644 index 0000000000000..5f5c4943173b6 --- /dev/null +++ b/Zend/tests/attributes/NonpublicConstructor/non_string.phpt @@ -0,0 +1,13 @@ +--TEST-- +#[\NonpublicConstructor]: Message argument must be a string +--FILE-- + +--EXPECTF-- +Fatal error: NonpublicConstructor::__construct(): Argument #1 ($message) must be of type string, true given in %s on line %d diff --git a/Zend/tests/attributes/NonpublicConstructor/subclass_private.phpt b/Zend/tests/attributes/NonpublicConstructor/subclass_private.phpt new file mode 100644 index 0000000000000..a2e1e1708898d --- /dev/null +++ b/Zend/tests/attributes/NonpublicConstructor/subclass_private.phpt @@ -0,0 +1,25 @@ +--TEST-- +#[\NonpublicConstructor]: affects error message (private constructor, protected scope) +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Call to private Demo::__construct() from scope Subclass, use ::getInstance() instead, in %s:%d +Stack trace: +#0 %s(%d): Subclass::create() +#1 {main} + thrown in %s on line %s diff --git a/Zend/tests/attributes/NonpublicConstructor/subclass_without_new.phpt b/Zend/tests/attributes/NonpublicConstructor/subclass_without_new.phpt new file mode 100644 index 0000000000000..1c62d2be9f666 --- /dev/null +++ b/Zend/tests/attributes/NonpublicConstructor/subclass_without_new.phpt @@ -0,0 +1,25 @@ +--TEST-- +#[\NonpublicConstructor]: affects error message (private constructor, protected scope, called via `parent::__construct()`) +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Cannot call private Demo::__construct(), use ::getInstance() instead, in %s:%d +Stack trace: +#0 %s(%d): Subclass->__construct() +#1 {main} + thrown in %s on line %s diff --git a/Zend/tests/attributes/NonpublicConstructor/without_message.phpt b/Zend/tests/attributes/NonpublicConstructor/without_message.phpt new file mode 100644 index 0000000000000..85579c29fac81 --- /dev/null +++ b/Zend/tests/attributes/NonpublicConstructor/without_message.phpt @@ -0,0 +1,13 @@ +--TEST-- +#[\NonpublicConstructor]: Message argument is required +--FILE-- + +--EXPECTF-- +Fatal error: #[NonpublicConstructor] takes 1 parameter, 0 given in %s on line %d diff --git a/Zend/tests/generators/errors/generator_instantiate_error.phpt b/Zend/tests/generators/errors/generator_instantiate_error.phpt index 7e55fe357db34..03be10c2daf4a 100644 --- a/Zend/tests/generators/errors/generator_instantiate_error.phpt +++ b/Zend/tests/generators/errors/generator_instantiate_error.phpt @@ -7,7 +7,7 @@ new Generator; ?> --EXPECTF-- -Fatal error: Uncaught Error: The "Generator" class is reserved for internal use and cannot be manually instantiated in %s:%d +Fatal error: Uncaught Error: Call to private Generator::__construct() from global scope, the "Generator" class is reserved for internal use and cannot be manually instantiated, in %s:%d Stack trace: #0 {main} thrown in %s on line %d diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index d7bcb1f54e889..adcb4ab91f7da 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -31,6 +31,7 @@ ZEND_API zend_class_entry *zend_ce_sensitive_parameter; ZEND_API zend_class_entry *zend_ce_sensitive_parameter_value; ZEND_API zend_class_entry *zend_ce_override; ZEND_API zend_class_entry *zend_ce_deprecated; +ZEND_API zend_class_entry *zend_ce_nonpublic_constructor; static zend_object_handlers attributes_object_handlers_sensitive_parameter_value; @@ -94,6 +95,110 @@ static void validate_allow_dynamic_properties( scope->ce_flags |= ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES; } +static void validate_nonpublic_constructor( + zend_attribute *attr, + uint32_t target, + zend_class_entry *scope +) { + zend_op_array *op_array = CG(active_op_array); + ZEND_ASSERT(op_array); + + if (!(op_array->fn_flags & ZEND_ACC_CTOR)) { + zend_error_noreturn( + E_ERROR, + "#[NonpublicConstructor] can only be applied to constructors" + ); + } + if (!(op_array->fn_flags & (ZEND_ACC_PROTECTED|ZEND_ACC_PRIVATE))) { + zend_error_noreturn( + E_ERROR, + "#[NonpublicConstructor] can only be applied to protected or private constructors" + ); + } + if (attr->argc != 1) { + zend_error_noreturn( + E_ERROR, + "#[NonpublicConstructor] takes 1 parameter, %d given", + attr->argc + ); + } + zval message; + if (FAILURE == zend_get_attribute_value(&message, attr, 0, NULL)) { + ZEND_ASSERT(EG(exception)); + return; + } + + if (Z_TYPE(message) != IS_STRING) { + zend_error_noreturn( + E_ERROR, + "NonpublicConstructor::__construct(): Argument #1 ($message) must be of type string, %s given", + zend_zval_value_name(&message) + ); + } + zval_ptr_dtor(&message); +} + +ZEND_API ZEND_COLD zend_result ZEND_FASTCALL zend_attribute_get_nonpublic_suffix(HashTable *attributes, zend_string **message_suffix) +{ + *message_suffix = ZSTR_EMPTY_ALLOC(); + + if (!attributes) { + return SUCCESS; + } + + zend_attribute *nonpublicConstructor = zend_get_attribute_str( + attributes, + "nonpublicconstructor", + sizeof("nonpublicconstructor")-1 + ); + + if (!nonpublicConstructor) { + return SUCCESS; + } + + ZEND_ASSERT(nonpublicConstructor->argc == 1); + + zend_result result = FAILURE; + zend_string *message_prop = ZSTR_EMPTY_ALLOC(); + + zval obj; + ZVAL_UNDEF(&obj); + zval *z; + + /* Construct the NonpublicConstructor object to correctly handle parameter processing. */ + if (FAILURE == zend_get_attribute_object( + &obj, + zend_ce_nonpublic_constructor, + nonpublicConstructor, + NULL, + NULL + )) { + goto out; + } + + /* Extract the $message property. */ + z = zend_read_property_ex(zend_ce_nonpublic_constructor, Z_OBJ_P(&obj), ZSTR_KNOWN(ZEND_STR_MESSAGE), false, NULL); + ZEND_ASSERT(z != &EG(uninitialized_zval)); + ZEND_ASSERT(Z_TYPE_P(z) == IS_STRING); + message_prop = zend_string_copy(Z_STR_P(z)); + + /* Construct the suffix. */ + *message_suffix = zend_strpprintf_unchecked( + 0, + ", %S,", + message_prop + ); + + result = SUCCESS; + + out: + + zend_string_release(message_prop); + zval_ptr_dtor(&obj); + + return result; +} + ZEND_METHOD(Attribute, __construct) { zend_long flags = ZEND_ATTRIBUTE_TARGET_ALL; @@ -193,6 +298,30 @@ ZEND_METHOD(Deprecated, __construct) } } +ZEND_METHOD(NonpublicConstructor, __construct) +{ + zend_string *message = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(message) + ZEND_PARSE_PARAMETERS_END(); + + zval messageValue; + ZVAL_STR(&messageValue, message); + + zend_update_property_ex( + zend_ce_nonpublic_constructor, + Z_OBJ_P(ZEND_THIS), + ZSTR_KNOWN(ZEND_STR_MESSAGE), + &messageValue + ); + + /* The assignment might fail due to 'readonly'. */ + if (UNEXPECTED(EG(exception))) { + RETURN_THROWS(); + } +} + static zend_attribute *get_attribute(HashTable *attributes, zend_string *lcname, uint32_t offset) { if (attributes) { @@ -520,6 +649,10 @@ void zend_register_attribute_ce(void) zend_ce_deprecated = register_class_Deprecated(); attr = zend_mark_internal_attribute(zend_ce_deprecated); + + zend_ce_nonpublic_constructor = register_class_NonpublicConstructor(); + attr = zend_mark_internal_attribute(zend_ce_nonpublic_constructor); + attr->validator = validate_nonpublic_constructor; } void zend_attributes_shutdown(void) diff --git a/Zend/zend_attributes.h b/Zend/zend_attributes.h index 8a825247c00f8..0f817e1253680 100644 --- a/Zend/zend_attributes.h +++ b/Zend/zend_attributes.h @@ -47,6 +47,7 @@ extern ZEND_API zend_class_entry *zend_ce_sensitive_parameter; extern ZEND_API zend_class_entry *zend_ce_sensitive_parameter_value; extern ZEND_API zend_class_entry *zend_ce_override; extern ZEND_API zend_class_entry *zend_ce_deprecated; +extern ZEND_API zend_class_entry *zend_ce_nonpublic_constructor; typedef struct { zend_string *name; @@ -92,6 +93,8 @@ ZEND_API zend_attribute *zend_add_attribute( uint32_t zend_attribute_attribute_get_flags(zend_attribute *attr, zend_class_entry *scope); +ZEND_API ZEND_COLD zend_result ZEND_FASTCALL zend_attribute_get_nonpublic_suffix(HashTable *attributes, zend_string **message_suffix); + END_EXTERN_C() static zend_always_inline zend_attribute *zend_add_class_attribute(zend_class_entry *ce, zend_string *name, uint32_t argc) diff --git a/Zend/zend_attributes.stub.php b/Zend/zend_attributes.stub.php index 0a35b0c57cb44..a7f9020c7c553 100644 --- a/Zend/zend_attributes.stub.php +++ b/Zend/zend_attributes.stub.php @@ -84,3 +84,14 @@ final class Deprecated public function __construct(?string $message = null, ?string $since = null) {} } + +/** + * @strict-properties + */ +#[Attribute(Attribute::TARGET_METHOD)] +final class NonpublicConstructor +{ + public readonly string $message; + + public function __construct(string $message) {} +} diff --git a/Zend/zend_attributes_arginfo.h b/Zend/zend_attributes_arginfo.h index 018caa47d0ac5..8c27f593bb5ea 100644 --- a/Zend/zend_attributes_arginfo.h +++ b/Zend/zend_attributes_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 2358a0d820edd06a1702c84104bfd545af08311c */ + * Stub hash: 9d32931d7b2781cdc520ebfa4538f0b19a51097a */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Attribute___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "Attribute::TARGET_ALL") @@ -29,6 +29,10 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Deprecated___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, since, IS_STRING, 1, "null") ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_NonpublicConstructor___construct, 0, 0, 1) + ZEND_ARG_TYPE_INFO(0, message, IS_STRING, 0) +ZEND_END_ARG_INFO() + ZEND_METHOD(Attribute, __construct); ZEND_METHOD(ReturnTypeWillChange, __construct); ZEND_METHOD(AllowDynamicProperties, __construct); @@ -38,6 +42,7 @@ ZEND_METHOD(SensitiveParameterValue, getValue); ZEND_METHOD(SensitiveParameterValue, __debugInfo); ZEND_METHOD(Override, __construct); ZEND_METHOD(Deprecated, __construct); +ZEND_METHOD(NonpublicConstructor, __construct); static const zend_function_entry class_Attribute_methods[] = { ZEND_ME(Attribute, __construct, arginfo_class_Attribute___construct, ZEND_ACC_PUBLIC) @@ -76,6 +81,11 @@ static const zend_function_entry class_Deprecated_methods[] = { ZEND_FE_END }; +static const zend_function_entry class_NonpublicConstructor_methods[] = { + ZEND_ME(NonpublicConstructor, __construct, arginfo_class_NonpublicConstructor___construct, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + static zend_class_entry *register_class_Attribute(void) { zend_class_entry ce, *class_entry; @@ -253,3 +263,24 @@ static zend_class_entry *register_class_Deprecated(void) return class_entry; } + +static zend_class_entry *register_class_NonpublicConstructor(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "NonpublicConstructor", class_NonpublicConstructor_methods); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES); + + zval property_message_default_value; + ZVAL_UNDEF(&property_message_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_MESSAGE), &property_message_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + + zend_string *attribute_name_Attribute_class_NonpublicConstructor_0 = zend_string_init_interned("Attribute", sizeof("Attribute") - 1, 1); + zend_attribute *attribute_Attribute_class_NonpublicConstructor_0 = zend_add_class_attribute(class_entry, attribute_name_Attribute_class_NonpublicConstructor_0, 1); + zend_string_release(attribute_name_Attribute_class_NonpublicConstructor_0); + zval attribute_Attribute_class_NonpublicConstructor_0_arg0; + ZVAL_LONG(&attribute_Attribute_class_NonpublicConstructor_0_arg0, ZEND_ATTRIBUTE_TARGET_METHOD); + ZVAL_COPY_VALUE(&attribute_Attribute_class_NonpublicConstructor_0->args[0].value, &attribute_Attribute_class_NonpublicConstructor_0_arg0); + + return class_entry; +} diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index a6ea91a7425b9..7cbe731e01cf6 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -24,6 +24,7 @@ #include "zend_exceptions.h" #include "zend_generators.h" #include "zend_closures.h" +#include "zend_attributes.h" #include "zend_generators_arginfo.h" #include "zend_observer.h" #include "zend_vm_opcodes.h" @@ -471,14 +472,6 @@ static zend_object *zend_generator_create(zend_class_entry *class_type) /* {{{ * } /* }}} */ -static ZEND_COLD zend_function *zend_generator_get_constructor(zend_object *object) /* {{{ */ -{ - zend_throw_error(NULL, "The \"Generator\" class is reserved for internal use and cannot be manually instantiated"); - - return NULL; -} -/* }}} */ - ZEND_API zend_execute_data *zend_generator_check_placeholder_frame(zend_execute_data *ptr) { if (!ptr->func && Z_TYPE(ptr->This) == IS_OBJECT) { @@ -891,6 +884,13 @@ static inline void zend_generator_rewind(zend_generator *generator) /* {{{ */ } /* }}} */ +ZEND_METHOD(Generator, __construct) +{ + // Reachable via reflection + zend_throw_error(NULL, "The \"Generator\" class is reserved for internal use and cannot be manually instantiated"); + RETURN_THROWS(); +} + /* {{{ Rewind the generator */ ZEND_METHOD(Generator, rewind) { @@ -1221,7 +1221,6 @@ void zend_register_generator_ce(void) /* {{{ */ zend_generator_handlers.dtor_obj = zend_generator_dtor_storage; zend_generator_handlers.get_gc = zend_generator_get_gc; zend_generator_handlers.clone_obj = NULL; - zend_generator_handlers.get_constructor = zend_generator_get_constructor; zend_ce_ClosedGeneratorException = register_class_ClosedGeneratorException(zend_ce_exception); } diff --git a/Zend/zend_generators.stub.php b/Zend/zend_generators.stub.php index c081d8e35e26d..428a6360dfa32 100644 --- a/Zend/zend_generators.stub.php +++ b/Zend/zend_generators.stub.php @@ -8,6 +8,9 @@ */ final class Generator implements Iterator { + #[\NonpublicConstructor("the \"Generator\" class is reserved for internal use and cannot be manually instantiated")] + private function __construct() {} + public function rewind(): void {} public function valid(): bool {} diff --git a/Zend/zend_generators_arginfo.h b/Zend/zend_generators_arginfo.h index 54a6744af1b64..5519afc408027 100644 --- a/Zend/zend_generators_arginfo.h +++ b/Zend/zend_generators_arginfo.h @@ -1,5 +1,8 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: d376e984db0db6ccd9356f632f9d7e1382b2afb7 */ + * Stub hash: f15f37a5f93befeddb75e34c89bc0d84879a511b */ + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Generator___construct, 0, 0, 0) +ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Generator_rewind, 0, 0, IS_VOID, 0) ZEND_END_ARG_INFO() @@ -27,6 +30,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Generator___debugInfo, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() +ZEND_METHOD(Generator, __construct); ZEND_METHOD(Generator, rewind); ZEND_METHOD(Generator, valid); ZEND_METHOD(Generator, current); @@ -38,6 +42,7 @@ ZEND_METHOD(Generator, getReturn); ZEND_METHOD(Generator, __debugInfo); static const zend_function_entry class_Generator_methods[] = { + ZEND_ME(Generator, __construct, arginfo_class_Generator___construct, ZEND_ACC_PRIVATE) ZEND_ME(Generator, rewind, arginfo_class_Generator_rewind, ZEND_ACC_PUBLIC) ZEND_ME(Generator, valid, arginfo_class_Generator_valid, ZEND_ACC_PUBLIC) ZEND_ME(Generator, current, arginfo_class_Generator_current, ZEND_ACC_PUBLIC) @@ -58,6 +63,15 @@ static zend_class_entry *register_class_Generator(zend_class_entry *class_entry_ class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE); zend_class_implements(class_entry, 1, class_entry_Iterator); + + zend_string *attribute_name_NonpublicConstructor_func___construct_0 = zend_string_init_interned("NonpublicConstructor", sizeof("NonpublicConstructor") - 1, 1); + zend_attribute *attribute_NonpublicConstructor_func___construct_0 = zend_add_function_attribute(zend_hash_str_find_ptr(&class_entry->function_table, "__construct", sizeof("__construct") - 1), attribute_name_NonpublicConstructor_func___construct_0, 1); + zend_string_release(attribute_name_NonpublicConstructor_func___construct_0); + zval attribute_NonpublicConstructor_func___construct_0_arg0; + zend_string *attribute_NonpublicConstructor_func___construct_0_arg0_str = zend_string_init("the \"Generator\" class is reserved for internal use and cannot be manually instantiated", strlen("the \"Generator\" class is reserved for internal use and cannot be manually instantiated"), 1); + ZVAL_STR(&attribute_NonpublicConstructor_func___construct_0_arg0, attribute_NonpublicConstructor_func___construct_0_arg0_str); + ZVAL_COPY_VALUE(&attribute_NonpublicConstructor_func___construct_0->args[0].value, &attribute_NonpublicConstructor_func___construct_0_arg0); + return class_entry; } diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index cdcdd1622746f..c932db7788fa9 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -19,6 +19,7 @@ */ #include "zend.h" +#include "zend_attributes.h" #include "zend_globals.h" #include "zend_lazy_objects.h" #include "zend_variables.h" @@ -2024,14 +2025,29 @@ ZEND_API ZEND_COLD bool zend_std_unset_static_property(zend_class_entry *ce, zen static ZEND_COLD zend_never_inline void zend_bad_constructor_call(zend_function *constructor, zend_class_entry *scope) /* {{{ */ { + zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); + + if (zend_attribute_get_nonpublic_suffix(constructor->common.attributes, &message_suffix) == FAILURE) { + return; + } + if (scope) { - zend_throw_error(NULL, "Call to %s %s::%s() from scope %s", + zend_throw_error(NULL, "Call to %s %s::%s() from scope %s%s", zend_visibility_string(constructor->common.fn_flags), ZSTR_VAL(constructor->common.scope->name), - ZSTR_VAL(constructor->common.function_name), ZSTR_VAL(scope->name) + ZSTR_VAL(constructor->common.function_name), ZSTR_VAL(scope->name), + ZSTR_VAL(message_suffix) ); } else { - zend_throw_error(NULL, "Call to %s %s::%s() from global scope", zend_visibility_string(constructor->common.fn_flags), ZSTR_VAL(constructor->common.scope->name), ZSTR_VAL(constructor->common.function_name)); + zend_throw_error( + NULL, + "Call to %s %s::%s() from global scope%s", + zend_visibility_string(constructor->common.fn_flags), + ZSTR_VAL(constructor->common.scope->name), + ZSTR_VAL(constructor->common.function_name), + ZSTR_VAL(message_suffix) + ); } + zend_string_release(message_suffix); } /* }}} */ diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 7e471b5acd8b6..6b7147e7f1029 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3783,7 +3783,19 @@ ZEND_VM_HANDLER(113, ZEND_INIT_STATIC_METHOD_CALL, UNUSED|CLASS_FETCH|CONST|VAR, HANDLE_EXCEPTION(); } if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { - zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); + zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); + + if (zend_attribute_get_nonpublic_suffix(ce->constructor->common.attributes, &message_suffix) == FAILURE) { + ZEND_ASSERT(EG(exception)); + } else { + zend_throw_error( + NULL, + "Cannot call private %s::__construct()%s", + ZSTR_VAL(ce->name), + ZSTR_VAL(message_suffix) + ); + zend_string_release(message_suffix); + } HANDLE_EXCEPTION(); } fbc = ce->constructor; diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 9209399a5cdbf..12827535da90f 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -7369,7 +7369,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_STATIC_METHOD_CALL_SPEC_C HANDLE_EXCEPTION(); } if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { - zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); + zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); + + if (zend_attribute_get_nonpublic_suffix(ce->constructor->common.attributes, &message_suffix) == FAILURE) { + ZEND_ASSERT(EG(exception)); + } else { + zend_throw_error( + NULL, + "Cannot call private %s::__construct()%s", + ZSTR_VAL(ce->name), + ZSTR_VAL(message_suffix) + ); + zend_string_release(message_suffix); + } HANDLE_EXCEPTION(); } fbc = ce->constructor; @@ -9943,7 +9955,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_STATIC_METHOD_CALL_SPEC_C HANDLE_EXCEPTION(); } if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { - zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); + zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); + + if (zend_attribute_get_nonpublic_suffix(ce->constructor->common.attributes, &message_suffix) == FAILURE) { + ZEND_ASSERT(EG(exception)); + } else { + zend_throw_error( + NULL, + "Cannot call private %s::__construct()%s", + ZSTR_VAL(ce->name), + ZSTR_VAL(message_suffix) + ); + zend_string_release(message_suffix); + } HANDLE_EXCEPTION(); } fbc = ce->constructor; @@ -10701,7 +10725,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_STATIC_METHOD_CALL_SPEC_C HANDLE_EXCEPTION(); } if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { - zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); + zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); + + if (zend_attribute_get_nonpublic_suffix(ce->constructor->common.attributes, &message_suffix) == FAILURE) { + ZEND_ASSERT(EG(exception)); + } else { + zend_throw_error( + NULL, + "Cannot call private %s::__construct()%s", + ZSTR_VAL(ce->name), + ZSTR_VAL(message_suffix) + ); + zend_string_release(message_suffix); + } HANDLE_EXCEPTION(); } fbc = ce->constructor; @@ -12435,7 +12471,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_STATIC_METHOD_CALL_SPEC_C HANDLE_EXCEPTION(); } if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { - zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); + zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); + + if (zend_attribute_get_nonpublic_suffix(ce->constructor->common.attributes, &message_suffix) == FAILURE) { + ZEND_ASSERT(EG(exception)); + } else { + zend_throw_error( + NULL, + "Cannot call private %s::__construct()%s", + ZSTR_VAL(ce->name), + ZSTR_VAL(message_suffix) + ); + zend_string_release(message_suffix); + } HANDLE_EXCEPTION(); } fbc = ce->constructor; @@ -25471,7 +25519,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_STATIC_METHOD_CALL_SPEC_V HANDLE_EXCEPTION(); } if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { - zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); + zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); + + if (zend_attribute_get_nonpublic_suffix(ce->constructor->common.attributes, &message_suffix) == FAILURE) { + ZEND_ASSERT(EG(exception)); + } else { + zend_throw_error( + NULL, + "Cannot call private %s::__construct()%s", + ZSTR_VAL(ce->name), + ZSTR_VAL(message_suffix) + ); + zend_string_release(message_suffix); + } HANDLE_EXCEPTION(); } fbc = ce->constructor; @@ -28404,7 +28464,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_STATIC_METHOD_CALL_SPEC_V HANDLE_EXCEPTION(); } if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { - zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); + zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); + + if (zend_attribute_get_nonpublic_suffix(ce->constructor->common.attributes, &message_suffix) == FAILURE) { + ZEND_ASSERT(EG(exception)); + } else { + zend_throw_error( + NULL, + "Cannot call private %s::__construct()%s", + ZSTR_VAL(ce->name), + ZSTR_VAL(message_suffix) + ); + zend_string_release(message_suffix); + } HANDLE_EXCEPTION(); } fbc = ce->constructor; @@ -29914,7 +29986,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_STATIC_METHOD_CALL_SPEC_V HANDLE_EXCEPTION(); } if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { - zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); + zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); + + if (zend_attribute_get_nonpublic_suffix(ce->constructor->common.attributes, &message_suffix) == FAILURE) { + ZEND_ASSERT(EG(exception)); + } else { + zend_throw_error( + NULL, + "Cannot call private %s::__construct()%s", + ZSTR_VAL(ce->name), + ZSTR_VAL(message_suffix) + ); + zend_string_release(message_suffix); + } HANDLE_EXCEPTION(); } fbc = ce->constructor; @@ -32853,7 +32937,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_STATIC_METHOD_CALL_SPEC_V HANDLE_EXCEPTION(); } if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { - zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); + zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); + + if (zend_attribute_get_nonpublic_suffix(ce->constructor->common.attributes, &message_suffix) == FAILURE) { + ZEND_ASSERT(EG(exception)); + } else { + zend_throw_error( + NULL, + "Cannot call private %s::__construct()%s", + ZSTR_VAL(ce->name), + ZSTR_VAL(message_suffix) + ); + zend_string_release(message_suffix); + } HANDLE_EXCEPTION(); } fbc = ce->constructor; @@ -35124,7 +35220,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_STATIC_METHOD_CALL_SPEC_U HANDLE_EXCEPTION(); } if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { - zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); + zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); + + if (zend_attribute_get_nonpublic_suffix(ce->constructor->common.attributes, &message_suffix) == FAILURE) { + ZEND_ASSERT(EG(exception)); + } else { + zend_throw_error( + NULL, + "Cannot call private %s::__construct()%s", + ZSTR_VAL(ce->name), + ZSTR_VAL(message_suffix) + ); + zend_string_release(message_suffix); + } HANDLE_EXCEPTION(); } fbc = ce->constructor; @@ -37293,7 +37401,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_STATIC_METHOD_CALL_SPEC_U HANDLE_EXCEPTION(); } if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { - zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); + zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); + + if (zend_attribute_get_nonpublic_suffix(ce->constructor->common.attributes, &message_suffix) == FAILURE) { + ZEND_ASSERT(EG(exception)); + } else { + zend_throw_error( + NULL, + "Cannot call private %s::__construct()%s", + ZSTR_VAL(ce->name), + ZSTR_VAL(message_suffix) + ); + zend_string_release(message_suffix); + } HANDLE_EXCEPTION(); } fbc = ce->constructor; @@ -37704,7 +37824,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_STATIC_METHOD_CALL_SPEC_U HANDLE_EXCEPTION(); } if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { - zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); + zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); + + if (zend_attribute_get_nonpublic_suffix(ce->constructor->common.attributes, &message_suffix) == FAILURE) { + ZEND_ASSERT(EG(exception)); + } else { + zend_throw_error( + NULL, + "Cannot call private %s::__construct()%s", + ZSTR_VAL(ce->name), + ZSTR_VAL(message_suffix) + ); + zend_string_release(message_suffix); + } HANDLE_EXCEPTION(); } fbc = ce->constructor; @@ -39938,7 +40070,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_STATIC_METHOD_CALL_SPEC_U HANDLE_EXCEPTION(); } if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { - zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); + zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); + + if (zend_attribute_get_nonpublic_suffix(ce->constructor->common.attributes, &message_suffix) == FAILURE) { + ZEND_ASSERT(EG(exception)); + } else { + zend_throw_error( + NULL, + "Cannot call private %s::__construct()%s", + ZSTR_VAL(ce->name), + ZSTR_VAL(message_suffix) + ); + zend_string_release(message_suffix); + } HANDLE_EXCEPTION(); } fbc = ce->constructor; diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 6d5b9d71c27ae..3bcbbe8c8de31 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -4938,7 +4938,25 @@ ZEND_METHOD(ReflectionClass, newInstance) HashTable *named_params; if (!(constructor->common.fn_flags & ZEND_ACC_PUBLIC)) { - zend_throw_exception_ex(reflection_exception_ptr, 0, "Access to non-public constructor of class %s", ZSTR_VAL(ce->name)); + zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); + + if (zend_attribute_get_nonpublic_suffix( + constructor->common.attributes, + &message_suffix) == FAILURE + ) { + ZEND_ASSERT(EG(exception)); + zval_ptr_dtor(return_value); + RETURN_THROWS(); + } + + zend_throw_exception_ex( + reflection_exception_ptr, + 0, + "Access to non-public constructor of class %s%s", + ZSTR_VAL(ce->name), + ZSTR_VAL(message_suffix) + ); + zend_string_release(message_suffix); zval_ptr_dtor(return_value); RETURN_NULL(); } @@ -5011,7 +5029,25 @@ ZEND_METHOD(ReflectionClass, newInstanceArgs) /* Run the constructor if there is one */ if (constructor) { if (!(constructor->common.fn_flags & ZEND_ACC_PUBLIC)) { - zend_throw_exception_ex(reflection_exception_ptr, 0, "Access to non-public constructor of class %s", ZSTR_VAL(ce->name)); + zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); + + if (zend_attribute_get_nonpublic_suffix( + constructor->common.attributes, + &message_suffix) == FAILURE + ) { + ZEND_ASSERT(EG(exception)); + zval_ptr_dtor(return_value); + RETURN_THROWS(); + } + + zend_throw_exception_ex( + reflection_exception_ptr, + 0, + "Access to non-public constructor of class %s%s", + ZSTR_VAL(ce->name), + ZSTR_VAL(message_suffix) + ); + zend_string_release(message_suffix); zval_ptr_dtor(return_value); RETURN_NULL(); } diff --git a/ext/reflection/tests/bug64007.phpt b/ext/reflection/tests/bug64007.phpt index a25beb6360d72..5dc6ff95fc083 100644 --- a/ext/reflection/tests/bug64007.phpt +++ b/ext/reflection/tests/bug64007.phpt @@ -16,7 +16,7 @@ var_dump($generator); --EXPECTF-- string(%d) "Class Generator is an internal class marked as final that cannot be instantiated without invoking its constructor" -Fatal error: Uncaught Error: The "Generator" class is reserved for internal use and cannot be manually instantiated in %sbug64007.php:%d +Fatal error: Uncaught ReflectionException: Access to non-public constructor of class Generator, the "Generator" class is reserved for internal use and cannot be manually instantiated, in %sbug64007.php:%d Stack trace: #0 %s(%d): ReflectionClass->newInstance() #1 {main}