core: set zend_pass_function as default constructor#19797
Conversation
bf13ae2 to
e89c0d3
Compare
e89c0d3 to
4490fee
Compare
5508388 to
dfbe9aa
Compare
dfbe9aa to
6724dd0
Compare
383258a to
77b6bc7
Compare
2a0b003 to
36c1328
Compare
|
+1 for eventually removing I'm not a fan of using An alternative would be to add a private constructor that throws on these classes (as suggested in #17796 (comment)). This can be done by gen_stubs when a class is annotated with This way the Reflection |
I'm happy to make the attribute add a private constructor, but my initial understanding as to why nobody wanted to pursue this is because it wouldn't allow a custom message like we currently have. But I guess this is just changing how we handle errors. And just to clarify, when you say gen_stub can generate the implementation do you want it to generate individually constructor methods, or that it points to a single implementation that is aliased?
This probably could work as most Might make sense to add a helper function |
This is to make the semantics of when a class cannot be instiantiated obvious rather than needed to remember to call the get_constructor() handler. For this we create a new NonInstantiableClass attribute that can be set to mark classes which shouldn't be instantiable with a message and set the constructor pointer to NULL
Add some APIs so that we can stop relying on the get_constructor object handler
| static ZEND_FUNCTION(non_instantiable_constructor) | ||
| { | ||
| } | ||
|
|
||
| static zend_arg_info zend_non_instantiable_constructor_arg_info[1] = {0}; | ||
| ZEND_API const zend_internal_function zend_non_instantiable_constructor = { | ||
| ZEND_INTERNAL_FUNCTION, /* type */ | ||
| {0, 0, 0}, /* arg_flags */ | ||
| ZEND_ACC_PRIVATE, /* fn_flags */ | ||
| NULL, /* name */ | ||
| NULL, /* scope */ | ||
| NULL, /* prototype */ | ||
| 0, /* num_args */ | ||
| 0, /* required_num_args */ | ||
| zend_non_instantiable_constructor_arg_info + 1, /* arg_info */ | ||
| NULL, /* attributes */ | ||
| NULL, /* run_time_cache */ | ||
| NULL, /* doc_comment */ | ||
| 0, /* T */ | ||
| 0, /* fn_flags2 */ | ||
| NULL, /* prop_info */ | ||
| ZEND_FN(non_instantiable_constructor), /* handler */ | ||
| NULL, /* module */ | ||
| NULL, /* frameless_function_infos */ | ||
| {NULL,NULL,NULL,NULL} /* reserved */ | ||
| }; |
There was a problem hiding this comment.
Possibly this can be generated with a @generate-function-entries stub (like ext/zend_test/tmp_methods.stub.php).
| /* Add zend_non_instantiable_constructor constructor if class cannot be manually instantiated */ | ||
| if (ce->constructor == NULL && (ce->ce_flags & (ZEND_ACC_ENUM|ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT)) == 0) { |
There was a problem hiding this comment.
Should you error if there is already a constructor? Otherwise we can do this:
#[NonInstantiableClass("test")]
class C {
public function __construct() {}
}
new C(); // works
| . pg_fetch_object() now surfaces non-instantiable class errors | ||
| before fetching, resolves the constructor via the get_constructor | ||
| handler, and reports the empty-constructor ValueError on the | ||
| . pg_fetch_object() now surfaces non-instantiable class errorsv before |
There was a problem hiding this comment.
| . pg_fetch_object() now surfaces non-instantiable class errorsv before | |
| . pg_fetch_object() now surfaces non-instantiable class errors before |
| } | ||
| } | ||
|
|
||
| ZEND_API bool zend_check_class_is_instantiable_or_throw(const zend_class_entry *ce, const zend_class_entry *scope) { |
There was a problem hiding this comment.
From the context of ZEND_NEW there is some redundancy as some of these checks are also performed by object_init_ex. We could rename this method to "check_constructor_visibility_or_throw", remove the ZEND_ACC_UNINSTANTIABLE check, and rely on the non_instantiable_constructor to throw when it's called.
| - Reflection: | ||
| . ReflectionClass::newInstanceWithoutConstructor() no longer accepts | ||
| classes with non-public constructors. |
There was a problem hiding this comment.
What is the reason for this? This provides no extra safety to internal classes as sub-classing does not inherit NonInstantiableClass.
Currently, it's basically impossible to determine reliably if an internal class is instantiable.
The current way is to overload the
get_constructorobject handler to throw an exception and returnNULL.This is problematic for two reasons:
ce->constructorget_constructorcan returnNULLif no explicit constructor was setThis complexity in semantics was one of the motivating factors for introducing the
object_init_with_constructor()API. However, this doesn't cleanly apply to many cases due to the "unorthodox" behaviour of DB drivers prepopulating object properties and being able to modify them in the constructor.These issues are mainly problematic as it means it's possible to create broken instances of internal objects (e.g.
CurlHandler).One other issue with the current design, is that to be able to call the
get_constructorobject handler one must first create the object just to immediately discard it, this is wasteful but moreover in those cases the destructor of the object shouldn't run and one must remember to call thezend_object_store_ctor_failed(obj);API instead of the usualOBJ_RELEASE().Therefore, we propose to use the "magic" internal
zend_pass_function, which is already used as a dummy constructor call, as the default value force->constructor. And rather than overriding theget_constructorobject handler to mark a class as instantiable we add a\#[NonInstantiableClass("reason")]attribute that replaces thece->constructorwithNULL.This design has a few advantages:
ce->constructor != NULLget_constructorobject handler (as it is only overridden to throw a custom exception)This would also fix certain Reflection bugs such as #17796, and should make it finally possible to grab a closure using the FCC syntax of
new MyClass(...).I'm quite happy to not offer this to userland yet while we iron out the kinks (and I don't have the energy to submit an RFC for this at the moment) by making the attribute internal use only.
Remaining planned items:
get_constructorobject handler.