Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,8 @@ PHP NEWS
- PGSQL:
. Enabled 64 bits support for pg_lo_truncate()/pg_lo_tell()
if the server supports it. (KentarouTakeda)
. 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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
. pg_fetch_object() now surfaces non-instantiable class errorsv before
. pg_fetch_object() now surfaces non-instantiable class errors before

fetching, and reports the empty-constructor ValueError on the
$constructor_args argument. (David Carlier)

- Phar:
Expand Down
14 changes: 14 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ PHP 8.6 UPGRADE NOTES
. MessageFormatter::parse() and parseMessage() now return PHP_INT_MIN as
int, rather than float, on 64-bit platforms when parsing integer values.

- MySQLi:
. mysqli_fetch_object() no longer accepts classes with non-public
constructors.

- PCNTL:
. pcntl_alarm() now raises a ValueError if the seconds argument is
lower than zero or greater than platform's UINT_MAX.
Expand All @@ -54,6 +58,10 @@ PHP 8.6 UPGRADE NOTES
execution error occurs (e.g. malformed UTF-8 input with the /u modifier).
This is consistent with other preg_* functions.

- PDO:
. PDOStatement::fetchObject() no longer accepts classes with non-public
constructors.

- Phar:
. Phar::mungServer() now raises a ValueError when an invalid
argument value is passed instead of being silently ignored.
Expand All @@ -69,13 +77,19 @@ PHP 8.6 UPGRADE NOTES
$constructor_args argument instead of $class. Errors raised when
the requested class is not instantiable (abstract, interface, enum)
now surface before the row is fetched.
. pg_fetch_object() no longer accepts classes with non-public
constructors.

- Posix:
. posix_access() now raises a ValueError when an invalid $flags
argument value is passed.
. posix_mkfifo() now raises a ValueError when an invalid $permissions
argument value is passed.

- Reflection:
. ReflectionClass::newInstanceWithoutConstructor() no longer accepts
classes with non-public constructors.
Comment on lines +89 to +91
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for this? This provides no extra safety to internal classes as sub-classing does not inherit NonInstantiableClass.


- Session:
. Setting session.cookie_path, session.cookie_domain, or session.cache_limiter
to a value containing null bytes now emits a warning and leaves the setting
Expand Down
4 changes: 4 additions & 0 deletions UPGRADING.INTERNALS
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ PHP 8.6 INTERNALS UPGRADE NOTES
. Added ZEND_CONTAINER_OF().
. The OPENBASEDIR_CHECKPATH() compatibility macro has been removed, instead
use php_check_open_basedir() directly.
. The get_constructor object handler has been removed.
Instead to mark an internal class as not instantiable the new
#[\NonInstantiableClass("Reason")] attribute should be attached to the
class definition in the stubs.

========================
2. Build system changes
Expand Down
2 changes: 0 additions & 2 deletions Zend/Optimizer/escape_analysis.c
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ static bool is_allocation_def(zend_op_array *op_array, zend_ssa *ssa, int def, i
if (ce
&& !ce->parent
&& !ce->create_object
&& ce->default_object_handlers->get_constructor == zend_std_get_constructor
&& ce->default_object_handlers->dtor_obj == zend_objects_destroy_object
&& !ce->constructor
&& !ce->destructor
Expand Down Expand Up @@ -234,7 +233,6 @@ static bool is_local_def(zend_op_array *op_array, zend_ssa *ssa, int def, int va
script, op_array, opline);
if (ce
&& !ce->create_object
&& ce->default_object_handlers->get_constructor == zend_std_get_constructor
&& ce->default_object_handlers->dtor_obj == zend_objects_destroy_object
&& !ce->constructor
&& !ce->destructor
Expand Down
3 changes: 1 addition & 2 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -3394,8 +3394,7 @@ static zend_always_inline zend_result _zend_update_type_info(
/* New objects without constructors cannot escape. */
if (ce
&& !ce->constructor
&& !ce->create_object
&& ce->default_object_handlers->get_constructor == zend_std_get_constructor) {
&& !ce->create_object) {
tmp &= ~MAY_BE_RCN;
}
UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
Expand Down
8 changes: 2 additions & 6 deletions Zend/tests/closures/closure_instantiate.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,10 @@ Mark Baker mark@lange.demon.co.uk at the PHPNW2017 Conference for PHP Testfest 2
try {
// Closures should be instantiatable using new
$x = new Closure();
} catch (Exception $e) {
// Instantiating a closure is an error, not an exception, so we shouldn't see this
echo 'EXCEPTION: ', $e->getMessage();
} catch (Throwable $e) {
// This is the message that we should see for a caught error
echo 'ERROR: ', $e->getMessage();
echo $e::class, ': ', $e->getMessage();
}

?>
--EXPECT--
ERROR: Instantiation of class Closure is not allowed
Error: Instantiation of class Closure is not allowed
6 changes: 3 additions & 3 deletions Zend/tests/enum/no-new-through-reflection.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ enum Foo {}

try {
(new \ReflectionClass(Foo::class))->newInstanceWithoutConstructor();
} catch (Error $e) {
echo $e->getMessage() . "\n";
} catch (Throwable $e) {
echo $e::class, ': ', $e->getMessage() . "\n";
}

?>
--EXPECT--
Cannot instantiate enum Foo
ReflectionException: Cannot instantiate enum Foo
2 changes: 1 addition & 1 deletion Zend/tests/traits/bug60173.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ $rc = new ReflectionClass('foo');
$rc->newInstance();
?>
--EXPECTF--
Fatal error: Uncaught Error: Cannot instantiate trait foo in %s:%d
Fatal error: Uncaught ReflectionException: Cannot instantiate trait foo in %s:%d
Stack trace:
#0 %s(%d): ReflectionClass->newInstance()
#1 {main}
Expand Down
35 changes: 11 additions & 24 deletions Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "zend_hash.h"
#include "zend_modules.h"
#include "zend_extensions.h"
#include "zend_attributes.h"
#include "zend_constants.h"
#include "zend_interfaces.h"
#include "zend_exceptions.h"
Expand Down Expand Up @@ -1792,16 +1793,7 @@ ZEND_API void object_properties_load(zend_object *object, const HashTable *prope
static zend_always_inline zend_result _object_and_properties_init(zval *arg, zend_class_entry *class_type, HashTable *properties) /* {{{ */
{
if (UNEXPECTED(class_type->ce_flags & ZEND_ACC_UNINSTANTIABLE)) {
if (class_type->ce_flags & ZEND_ACC_INTERFACE) {
zend_throw_error(NULL, "Cannot instantiate interface %s", ZSTR_VAL(class_type->name));
} else if (class_type->ce_flags & ZEND_ACC_TRAIT) {
zend_throw_error(NULL, "Cannot instantiate trait %s", ZSTR_VAL(class_type->name));
} else if (class_type->ce_flags & ZEND_ACC_ENUM) {
zend_throw_error(NULL, "Cannot instantiate enum %s", ZSTR_VAL(class_type->name));
} else {
ZEND_ASSERT(class_type->ce_flags & (ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS));
zend_throw_error(NULL, "Cannot instantiate abstract class %s", ZSTR_VAL(class_type->name));
}
zend_cannot_instantiate_class(class_type, NULL);
ZVAL_NULL(arg);
Z_OBJ_P(arg) = NULL;
return FAILURE;
Expand Down Expand Up @@ -1845,27 +1837,21 @@ ZEND_API zend_result object_init_ex(zval *arg, zend_class_entry *class_type) /*

ZEND_API zend_result object_init_with_constructor(zval *arg, zend_class_entry *class_type, uint32_t param_count, zval *params, HashTable *named_params) /* {{{ */
{
if (UNEXPECTED(!zend_check_class_is_instantiable_or_throw(class_type, zend_get_executed_scope()))) {
ZVAL_UNDEF(arg);
return FAILURE;
}

zend_result status = _object_and_properties_init(arg, class_type, NULL);
if (UNEXPECTED(status == FAILURE)) {
ZVAL_UNDEF(arg);
return FAILURE;
}
zend_object *obj = Z_OBJ_P(arg);
zend_function *constructor = obj->handlers->get_constructor(obj);
if (constructor == NULL) {
/* The constructor can be NULL for 2 different reasons:
* - It is not defined
* - We are not allowed to call the constructor (e.g. private, or internal opaque class)
* and an exception has been thrown
* in the former case, we are (mostly) done and the object is initialized,
* in the latter we need to destroy the object as initialization failed
*/
if (UNEXPECTED(EG(exception))) {
zval_ptr_dtor(arg);
ZVAL_UNDEF(arg);
return FAILURE;
}

zend_function *constructor = class_type->constructor;
/* No constructor, so no need to call it */
if (constructor == NULL) {
/* Surprisingly, this is the only case where internal classes will allow to pass extra arguments
* However, if there are named arguments (and it is not empty),
* an Error must be thrown to be consistent with new ClassName() */
Expand All @@ -1884,6 +1870,7 @@ ZEND_API zend_result object_init_with_constructor(zval *arg, zend_class_entry *c
return SUCCESS;
}
}

/* A constructor should not return a value, however if an exception is thrown
* zend_call_known_function() will set the retval to IS_UNDEF */
zval retval;
Expand Down
14 changes: 8 additions & 6 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,10 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
return FAILURE;
}

if (!zend_is_class_instantiable(ce)) {
return FAILURE;
}

if (object_init_ex(result, ce) != SUCCESS) {
return FAILURE;
}
Expand Down Expand Up @@ -1096,10 +1100,9 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
}
}

zend_function *ctor = Z_OBJ_HT_P(result)->get_constructor(Z_OBJ_P(result));
if (ctor) {
if (ce->constructor) {
zend_call_known_function(
ctor, Z_OBJ_P(result), Z_OBJCE_P(result), NULL, 0, NULL, args);
ce->constructor, Z_OBJ_P(result), Z_OBJCE_P(result), NULL, 0, NULL, args);
}

zend_array_destroy(args);
Expand All @@ -1117,10 +1120,9 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
}
}

zend_function *ctor = Z_OBJ_HT_P(result)->get_constructor(Z_OBJ_P(result));
if (ctor) {
if (ce->constructor) {
zend_call_known_instance_method(
ctor, Z_OBJ_P(result), NULL, args_ast->children, args);
ce->constructor, Z_OBJ_P(result), NULL, args_ast->children, args);
}

for (uint32_t i = 0; i < args_ast->children; i++) {
Expand Down
22 changes: 22 additions & 0 deletions Zend/zend_attributes.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ ZEND_API zend_class_entry *zend_ce_override;
ZEND_API zend_class_entry *zend_ce_deprecated;
ZEND_API zend_class_entry *zend_ce_nodiscard;
ZEND_API zend_class_entry *zend_ce_delayed_target_validation;
ZEND_API zend_class_entry *zend_ce_non_instantiable_class;

static zend_object_handlers attributes_object_handlers_sensitive_parameter_value;

Expand Down Expand Up @@ -264,6 +265,24 @@ ZEND_METHOD(NoDiscard, __construct)
}
}

ZEND_METHOD(NonInstantiableClass, __construct)
{
zend_string *message = NULL;
zval value;

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STR(message)
ZEND_PARSE_PARAMETERS_END();

ZVAL_STR(&value, message);
zend_update_property_ex(zend_ce_non_instantiable_class, Z_OBJ_P(ZEND_THIS), ZSTR_KNOWN(ZEND_STR_MESSAGE), &value);

/* The assignment might fail due to 'readonly'. */
if (UNEXPECTED(EG(exception))) {
RETURN_THROWS();
}
}

static zend_attribute *get_attribute(const HashTable *attributes, const zend_string *lcname, uint32_t offset)
{
if (attributes) {
Expand Down Expand Up @@ -605,6 +624,9 @@ void zend_register_attribute_ce(void)

zend_ce_delayed_target_validation = register_class_DelayedTargetValidation();
attr = zend_mark_internal_attribute(zend_ce_delayed_target_validation);

zend_ce_non_instantiable_class = register_class_NonInstantiableClass();
attr = zend_mark_internal_attribute(zend_ce_delayed_target_validation);
}

void zend_attributes_shutdown(void)
Expand Down
10 changes: 10 additions & 0 deletions Zend/zend_attributes.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,13 @@ public function __construct(?string $message = null) {}
*/
#[Attribute(Attribute::TARGET_ALL)]
final class DelayedTargetValidation {}

/**
* @strict-properties
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class NonInstantiableClass {
public readonly string $message;

public function __construct(string $message) {}
}
31 changes: 30 additions & 1 deletion Zend/zend_attributes_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 1 addition & 8 deletions Zend/zend_closures.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "zend.h"
#include "zend_API.h"
#include "zend_closures.h"
#include "zend_attributes.h"
#include "zend_exceptions.h"
#include "zend_interfaces.h"
#include "zend_objects.h"
Expand Down Expand Up @@ -448,13 +449,6 @@ ZEND_METHOD(Closure, getCurrent)
RETURN_OBJ_COPY(obj);
}

static ZEND_COLD zend_function *zend_closure_get_constructor(zend_object *object) /* {{{ */
{
zend_throw_error(NULL, "Instantiation of class Closure is not allowed");
return NULL;
}
/* }}} */

/* int return due to Object Handler API */
static int zend_closure_compare(zval *o1, zval *o2) /* {{{ */
{
Expand Down Expand Up @@ -736,7 +730,6 @@ void zend_register_closure_ce(void) /* {{{ */

memcpy(&closure_handlers, &std_object_handlers, sizeof(zend_object_handlers));
closure_handlers.free_obj = zend_closure_free_storage;
closure_handlers.get_constructor = zend_closure_get_constructor;
closure_handlers.get_method = zend_closure_get_method;
closure_handlers.compare = zend_closure_compare;
closure_handlers.clone_obj = zend_closure_clone;
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_closures.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* @strict-properties
* @not-serializable
*/
#[\NonInstantiableClass("Instantiation of class Closure is not allowed")]
final class Closure
{
private function __construct() {}
Expand Down
Loading
Loading