Skip to content

Commit 45500c8

Browse files
committed
core: set zend_pass_function as default constructor
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
1 parent 3447c58 commit 45500c8

101 files changed

Lines changed: 797 additions & 475 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

UPGRADING

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ PHP 8.6 UPGRADE NOTES
4343
. MessageFormatter::parse() and parseMessage() now return PHP_INT_MIN as
4444
int, rather than float, on 64-bit platforms when parsing integer values.
4545

46+
- MySQLi:
47+
. mysqli_fetch_object() no longer accepts classes with non-public
48+
constructors.
49+
4650
- PCNTL:
4751
. pcntl_alarm() now raises a ValueError if the seconds argument is
4852
lower than zero or greater than platform's UINT_MAX.
@@ -54,6 +58,10 @@ PHP 8.6 UPGRADE NOTES
5458
execution error occurs (e.g. malformed UTF-8 input with the /u modifier).
5559
This is consistent with other preg_* functions.
5660

61+
- PDO:
62+
. PDOStatement::fetchObject() no longer accepts classes with non-public
63+
constructors.
64+
5765
- Phar:
5866
. Phar::mungServer() now raises a ValueError when an invalid
5967
argument value is passed instead of being silently ignored.
@@ -69,13 +77,19 @@ PHP 8.6 UPGRADE NOTES
6977
$constructor_args argument instead of $class. Errors raised when
7078
the requested class is not instantiable (abstract, interface, enum)
7179
now surface before the row is fetched.
80+
. pg_fetch_object() no longer accepts classes with non-public
81+
constructors.
7282

7383
- Posix:
7484
. posix_access() now raises a ValueError when an invalid $flags
7585
argument value is passed.
7686
. posix_mkfifo() now raises a ValueError when an invalid $permissions
7787
argument value is passed.
7888

89+
- Reflection:
90+
. ReflectionClass::newInstanceWithoutConstructor() no longer accepts
91+
classes with non-public constructors.
92+
7993
- Session:
8094
. Setting session.cookie_path, session.cookie_domain, or session.cache_limiter
8195
to a value containing null bytes now emits a warning and leaves the setting

Zend/Optimizer/escape_analysis.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ static bool is_allocation_def(zend_op_array *op_array, zend_ssa *ssa, int def, i
167167
&& !ce->create_object
168168
&& ce->default_object_handlers->get_constructor == zend_std_get_constructor
169169
&& ce->default_object_handlers->dtor_obj == zend_objects_destroy_object
170-
&& !ce->constructor
170+
&& (!ce->constructor || zend_is_pass_function(ce->constructor))
171171
&& !ce->destructor
172172
&& !ce->__get
173173
&& !ce->__set
@@ -236,7 +236,7 @@ static bool is_local_def(zend_op_array *op_array, zend_ssa *ssa, int def, int va
236236
&& !ce->create_object
237237
&& ce->default_object_handlers->get_constructor == zend_std_get_constructor
238238
&& ce->default_object_handlers->dtor_obj == zend_objects_destroy_object
239-
&& !ce->constructor
239+
&& (!ce->constructor || zend_is_pass_function(ce->constructor))
240240
&& !ce->destructor
241241
&& !ce->__get
242242
&& !ce->__set

Zend/Optimizer/zend_inference.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3393,7 +3393,7 @@ static zend_always_inline zend_result _zend_update_type_info(
33933393
}
33943394
/* New objects without constructors cannot escape. */
33953395
if (ce
3396-
&& !ce->constructor
3396+
&& (!ce->constructor || zend_is_pass_function(ce->constructor))
33973397
&& !ce->create_object
33983398
&& ce->default_object_handlers->get_constructor == zend_std_get_constructor) {
33993399
tmp &= ~MAY_BE_RCN;

Zend/tests/enum/no-new-through-reflection.phpt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ enum Foo {}
77

88
try {
99
(new \ReflectionClass(Foo::class))->newInstanceWithoutConstructor();
10-
} catch (Error $e) {
11-
echo $e->getMessage() . "\n";
10+
} catch (Throwable $e) {
11+
echo $e::class, ': ', $e->getMessage() . "\n";
1212
}
1313

1414
?>
1515
--EXPECT--
16-
Cannot instantiate enum Foo
16+
ReflectionException: Class Foo cannot be instantiated manually

Zend/tests/traits/bug60173.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ $rc = new ReflectionClass('foo');
99
$rc->newInstance();
1010
?>
1111
--EXPECTF--
12-
Fatal error: Uncaught Error: Cannot instantiate trait foo in %s:%d
12+
Fatal error: Uncaught ReflectionException: Class foo cannot be instantiated manually in %s:%d
1313
Stack trace:
1414
#0 %s(%d): ReflectionClass->newInstance()
1515
#1 {main}

Zend/zend_API.c

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "zend_hash.h"
2626
#include "zend_modules.h"
2727
#include "zend_extensions.h"
28+
#include "zend_attributes.h"
2829
#include "zend_constants.h"
2930
#include "zend_interfaces.h"
3031
#include "zend_exceptions.h"
@@ -1845,27 +1846,38 @@ ZEND_API zend_result object_init_ex(zval *arg, zend_class_entry *class_type) /*
18451846

18461847
ZEND_API zend_result object_init_with_constructor(zval *arg, zend_class_entry *class_type, uint32_t param_count, zval *params, HashTable *named_params) /* {{{ */
18471848
{
1849+
zend_function *constructor = class_type->constructor;
1850+
if (UNEXPECTED(constructor == NULL)) {
1851+
const zend_attribute *non_instantiable_class = zend_get_attribute_str(class_type->attributes, ZEND_STRL("noninstantiableclass"));
1852+
ZEND_ASSERT(non_instantiable_class);
1853+
zend_string *msg = Z_STR(non_instantiable_class->args[0].value);
1854+
zend_throw_error(NULL, "%s", ZSTR_VAL(msg));
1855+
ZVAL_UNDEF(arg);
1856+
return FAILURE;
1857+
}
1858+
1859+
if (UNEXPECTED(!(constructor->common.fn_flags & ZEND_ACC_PUBLIC))) {
1860+
/* Use zend_bad_constructor_call() somehow? */
1861+
zend_throw_error(
1862+
NULL,
1863+
"Call to %s %s::%s() from global scope",
1864+
zend_visibility_string(class_type->constructor->common.fn_flags),
1865+
ZSTR_VAL(class_type->constructor->common.scope->name),
1866+
ZSTR_VAL(class_type->constructor->common.function_name)
1867+
);
1868+
ZVAL_UNDEF(arg);
1869+
return FAILURE;
1870+
}
1871+
18481872
zend_result status = _object_and_properties_init(arg, class_type, NULL);
18491873
if (UNEXPECTED(status == FAILURE)) {
18501874
ZVAL_UNDEF(arg);
18511875
return FAILURE;
18521876
}
18531877
zend_object *obj = Z_OBJ_P(arg);
1854-
zend_function *constructor = obj->handlers->get_constructor(obj);
1855-
if (constructor == NULL) {
1856-
/* The constructor can be NULL for 2 different reasons:
1857-
* - It is not defined
1858-
* - We are not allowed to call the constructor (e.g. private, or internal opaque class)
1859-
* and an exception has been thrown
1860-
* in the former case, we are (mostly) done and the object is initialized,
1861-
* in the latter we need to destroy the object as initialization failed
1862-
*/
1863-
if (UNEXPECTED(EG(exception))) {
1864-
zval_ptr_dtor(arg);
1865-
ZVAL_UNDEF(arg);
1866-
return FAILURE;
1867-
}
18681878

1879+
/* Fake constructor means we don't need to call the constructor actually */
1880+
if (zend_is_pass_function(constructor)) {
18691881
/* Surprisingly, this is the only case where internal classes will allow to pass extra arguments
18701882
* However, if there are named arguments (and it is not empty),
18711883
* an Error must be thrown to be consistent with new ClassName() */
@@ -1884,6 +1896,7 @@ ZEND_API zend_result object_init_with_constructor(zval *arg, zend_class_entry *c
18841896
return SUCCESS;
18851897
}
18861898
}
1899+
18871900
/* A constructor should not return a value, however if an exception is thrown
18881901
* zend_call_known_function() will set the retval to IS_UNDEF */
18891902
zval retval;
@@ -3510,6 +3523,11 @@ static zend_class_entry *do_register_internal_class(const zend_class_entry *orig
35103523
if (class_entry->info.internal.builtin_functions) {
35113524
zend_register_functions(class_entry, class_entry->info.internal.builtin_functions, &class_entry->function_table, EG(current_module)->type);
35123525
}
3526+
3527+
/* Assign the pass function as a default constructor */
3528+
if (class_entry->constructor == NULL) {
3529+
class_entry->constructor = (zend_function *) &zend_pass_function;
3530+
}
35133531

35143532
lowercase_name = zend_string_tolower_ex(orig_class_entry->name, EG(current_module)->type == MODULE_PERSISTENT);
35153533
lowercase_name = zend_new_interned_string(lowercase_name);

Zend/zend_attributes.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ ZEND_API zend_class_entry *zend_ce_override;
3232
ZEND_API zend_class_entry *zend_ce_deprecated;
3333
ZEND_API zend_class_entry *zend_ce_nodiscard;
3434
ZEND_API zend_class_entry *zend_ce_delayed_target_validation;
35+
ZEND_API zend_class_entry *zend_ce_non_instantiable_class;
3536

3637
static zend_object_handlers attributes_object_handlers_sensitive_parameter_value;
3738

@@ -264,6 +265,24 @@ ZEND_METHOD(NoDiscard, __construct)
264265
}
265266
}
266267

268+
ZEND_METHOD(NonInstantiableClass, __construct)
269+
{
270+
zend_string *message = NULL;
271+
zval value;
272+
273+
ZEND_PARSE_PARAMETERS_START(1, 1)
274+
Z_PARAM_STR(message)
275+
ZEND_PARSE_PARAMETERS_END();
276+
277+
ZVAL_STR(&value, message);
278+
zend_update_property_ex(zend_ce_non_instantiable_class, Z_OBJ_P(ZEND_THIS), ZSTR_KNOWN(ZEND_STR_MESSAGE), &value);
279+
280+
/* The assignment might fail due to 'readonly'. */
281+
if (UNEXPECTED(EG(exception))) {
282+
RETURN_THROWS();
283+
}
284+
}
285+
267286
static zend_attribute *get_attribute(const HashTable *attributes, const zend_string *lcname, uint32_t offset)
268287
{
269288
if (attributes) {
@@ -605,6 +624,9 @@ void zend_register_attribute_ce(void)
605624

606625
zend_ce_delayed_target_validation = register_class_DelayedTargetValidation();
607626
attr = zend_mark_internal_attribute(zend_ce_delayed_target_validation);
627+
628+
zend_ce_non_instantiable_class = register_class_NonInstantiableClass();
629+
attr = zend_mark_internal_attribute(zend_ce_delayed_target_validation);
608630
}
609631

610632
void zend_attributes_shutdown(void)

Zend/zend_attributes.stub.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,13 @@ public function __construct(?string $message = null) {}
103103
*/
104104
#[Attribute(Attribute::TARGET_ALL)]
105105
final class DelayedTargetValidation {}
106+
107+
/**
108+
* @strict-properties
109+
*/
110+
#[Attribute(Attribute::TARGET_CLASS)]
111+
final class NonInstantiableClass {
112+
public readonly string $message;
113+
114+
public function __construct(string $message) {}
115+
}

Zend/zend_attributes_arginfo.h

Lines changed: 30 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Zend/zend_closures.c

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "zend.h"
2121
#include "zend_API.h"
2222
#include "zend_closures.h"
23+
#include "zend_attributes.h"
2324
#include "zend_exceptions.h"
2425
#include "zend_interfaces.h"
2526
#include "zend_objects.h"
@@ -448,13 +449,6 @@ ZEND_METHOD(Closure, getCurrent)
448449
RETURN_OBJ_COPY(obj);
449450
}
450451

451-
static ZEND_COLD zend_function *zend_closure_get_constructor(zend_object *object) /* {{{ */
452-
{
453-
zend_throw_error(NULL, "Instantiation of class Closure is not allowed");
454-
return NULL;
455-
}
456-
/* }}} */
457-
458452
/* int return due to Object Handler API */
459453
static int zend_closure_compare(zval *o1, zval *o2) /* {{{ */
460454
{
@@ -736,7 +730,6 @@ void zend_register_closure_ce(void) /* {{{ */
736730

737731
memcpy(&closure_handlers, &std_object_handlers, sizeof(zend_object_handlers));
738732
closure_handlers.free_obj = zend_closure_free_storage;
739-
closure_handlers.get_constructor = zend_closure_get_constructor;
740733
closure_handlers.get_method = zend_closure_get_method;
741734
closure_handlers.compare = zend_closure_compare;
742735
closure_handlers.clone_obj = zend_closure_clone;

0 commit comments

Comments
 (0)