Skip to content

Commit

Permalink
Implement acyclic object tracking
Browse files Browse the repository at this point in the history
Acyclic objects don't need to be added to the GC. We know an object is acyclic
if all properties are typed as acyclic, and the object does not have any dynamic
properties. This is possible to track at runtime with minimal overhead.

Fixes GH-17127
  • Loading branch information
iluuu1994 committed Dec 12, 2024
1 parent 07cd468 commit 72c581b
Show file tree
Hide file tree
Showing 29 changed files with 112 additions and 2 deletions.
6 changes: 6 additions & 0 deletions Zend/tests/gc/gc_045.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ class GlobalData

class Value
{
/* Force object to be added to GC, even though it is acyclic. */
public $dummy;

public function __destruct()
{
new Bar();
Expand All @@ -19,6 +22,9 @@ class Value

class Bar
{
/* Force object to be added to GC, even though it is acyclic. */
public $dummy;

public function __construct()
{
GlobalData::$bar = $this;
Expand Down
27 changes: 27 additions & 0 deletions Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -4494,6 +4494,27 @@ static zend_always_inline bool is_persistent_class(zend_class_entry *ce) {
&& ce->info.internal.module->type == MODULE_PERSISTENT;
}

static bool zend_type_may_be_cyclic(zend_type type)
{
if (!ZEND_TYPE_IS_SET(type)) {
return true;
}

if (!ZEND_TYPE_IS_COMPLEX(type)) {
return ZEND_TYPE_PURE_MASK(type) & (MAY_BE_OBJECT|MAY_BE_ARRAY);
} else if (ZEND_TYPE_IS_UNION(type)) {
zend_type *list_type;
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), list_type) {
if (zend_type_may_be_cyclic(*list_type)) {
return true;
}
} ZEND_TYPE_LIST_FOREACH_END();
return false;
}

return true;
}

ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment, zend_type type) /* {{{ */
{
zend_property_info *property_info, *property_info_ptr;
Expand All @@ -4506,6 +4527,12 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z
}
}

if (!(access_type & ZEND_ACC_STATIC)
&& !(ce->ce_flags & ZEND_ACC_MAY_BE_CYCLIC)
&& zend_type_may_be_cyclic(type)) {
ce->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;
}

if (ce->type == ZEND_INTERNAL_CLASS) {
property_info = pemalloc(sizeof(zend_property_info), 1);
} else {
Expand Down
3 changes: 3 additions & 0 deletions Zend/zend_closures.c
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,9 @@ void zend_register_closure_ce(void) /* {{{ */
zend_ce_closure = register_class_Closure();
zend_ce_closure->create_object = zend_closure_new;
zend_ce_closure->default_object_handlers = &closure_handlers;
/* FIXME: Potentially improve during construction of closure? static closures
* not binding by references can't be cyclic. */
zend_ce_closure->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

memcpy(&closure_handlers, &std_object_handlers, sizeof(zend_object_handlers));
closure_handlers.free_obj = zend_closure_free_storage;
Expand Down
5 changes: 4 additions & 1 deletion Zend/zend_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ typedef struct _zend_oparray_context {
#define ZEND_ACC_PROTECTED_SET (1 << 11) /* | | X | */
#define ZEND_ACC_PRIVATE_SET (1 << 12) /* | | X | */
/* | | | */
/* Class Flags (unused: 30,31) | | | */
/* Class Flags (unused: 31) | | | */
/* =========== | | | */
/* | | | */
/* Special class types | | | */
Expand Down Expand Up @@ -333,6 +333,9 @@ typedef struct _zend_oparray_context {
/* Class cannot be serialized or unserialized | | | */
#define ZEND_ACC_NOT_SERIALIZABLE (1 << 29) /* X | | | */
/* | | | */
/* Object may be the root of a cycle | | | */
#define ZEND_ACC_MAY_BE_CYCLIC (1 << 30) /* X | | | */
/* | | | */
/* Function Flags (unused: 29-30) | | | */
/* ============== | | | */
/* | | | */
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_fibers.c
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,7 @@ void zend_register_fiber_ce(void)
zend_ce_fiber = register_class_Fiber();
zend_ce_fiber->create_object = zend_fiber_object_create;
zend_ce_fiber->default_object_handlers = &zend_fiber_handlers;
zend_ce_fiber->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

zend_fiber_handlers = std_object_handlers;
zend_fiber_handlers.dtor_obj = zend_fiber_object_destroy;
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_generators.c
Original file line number Diff line number Diff line change
Expand Up @@ -1215,6 +1215,7 @@ void zend_register_generator_ce(void) /* {{{ */
/* get_iterator has to be assigned *after* implementing the interface */
zend_ce_generator->get_iterator = zend_generator_get_iterator;
zend_ce_generator->default_object_handlers = &zend_generator_handlers;
zend_ce_generator->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

memcpy(&zend_generator_handlers, &std_object_handlers, sizeof(zend_object_handlers));
zend_generator_handlers.free_obj = zend_generator_free_storage;
Expand Down
4 changes: 4 additions & 0 deletions Zend/zend_inheritance.c
Original file line number Diff line number Diff line change
Expand Up @@ -1810,6 +1810,7 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par
ce->parent = parent_ce;
ce->default_object_handlers = parent_ce->default_object_handlers;
ce->ce_flags |= ZEND_ACC_RESOLVED_PARENT;
ce->ce_flags |= (parent_ce->ce_flags & ZEND_ACC_MAY_BE_CYCLIC);

/* Inherit properties */
if (parent_ce->default_properties_count) {
Expand Down Expand Up @@ -2832,6 +2833,9 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent
if (!traits[i]) {
continue;
}

ce->ce_flags |= (traits[i]->ce_flags & ZEND_ACC_MAY_BE_CYCLIC);

ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&traits[i]->properties_info, prop_name, property_info) {
uint32_t flags = property_info->flags;

Expand Down
2 changes: 2 additions & 0 deletions Zend/zend_object_handlers.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ ZEND_API HashTable *rebuild_object_properties_internal(zend_object *zobj) /* {{{
zend_class_entry *ce = zobj->ce;
int i;

GC_TYPE_INFO(zobj) &= ~(GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT);

zobj->properties = zend_new_array(ce->default_properties_count);
if (ce->default_properties_count) {
zend_hash_real_init_mixed(zobj->properties);
Expand Down
3 changes: 3 additions & 0 deletions Zend/zend_objects.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ static zend_always_inline void _zend_object_std_init(zend_object *object, zend_c
{
GC_SET_REFCOUNT(object, 1);
GC_TYPE_INFO(object) = GC_OBJECT;
if (!(ce->ce_flags & ZEND_ACC_MAY_BE_CYCLIC)) {
GC_TYPE_INFO(object) |= (GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT);
}
object->ce = ce;
object->extra_flags = 0;
object->handlers = ce->default_object_handlers;
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_weakrefs.c
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,7 @@ void zend_register_weakref_ce(void) /* {{{ */
zend_ce_weakmap->create_object = zend_weakmap_create_object;
zend_ce_weakmap->get_iterator = zend_weakmap_get_iterator;
zend_ce_weakmap->default_object_handlers = &zend_weakmap_handlers;
zend_ce_weakmap->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

memcpy(&zend_weakmap_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
zend_weakmap_handlers.offset = XtOffsetOf(zend_weakmap, std);
Expand Down
1 change: 1 addition & 0 deletions ext/curl/interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ PHP_MINIT_FUNCTION(curl)
curl_ce = register_class_CurlHandle();
curl_ce->create_object = curl_create_object;
curl_ce->default_object_handlers = &curl_object_handlers;
curl_ce->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

memcpy(&curl_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
curl_object_handlers.offset = XtOffsetOf(php_curl, std);
Expand Down
1 change: 1 addition & 0 deletions ext/curl/multi.c
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,7 @@ static zend_object_handlers curl_multi_handlers;
void curl_multi_register_handlers(void) {
curl_multi_ce->create_object = curl_multi_create_object;
curl_multi_ce->default_object_handlers = &curl_multi_handlers;
curl_multi_ce->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

memcpy(&curl_multi_handlers, &std_object_handlers, sizeof(zend_object_handlers));
curl_multi_handlers.offset = XtOffsetOf(php_curlm, std);
Expand Down
2 changes: 2 additions & 0 deletions ext/dom/php_dom.c
Original file line number Diff line number Diff line change
Expand Up @@ -1292,6 +1292,7 @@ PHP_MINIT_FUNCTION(dom)
dom_xpath_class_entry = register_class_DOMXPath();
dom_xpath_class_entry->create_object = dom_xpath_objects_new;
dom_xpath_class_entry->default_object_handlers = &dom_xpath_object_handlers;
dom_xpath_class_entry->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

zend_hash_init(&dom_xpath_prop_handlers, 0, NULL, NULL, true);
DOM_REGISTER_PROP_HANDLER(&dom_xpath_prop_handlers, "document", dom_xpath_document_read, NULL);
Expand All @@ -1301,6 +1302,7 @@ PHP_MINIT_FUNCTION(dom)
dom_modern_xpath_class_entry = register_class_Dom_XPath();
dom_modern_xpath_class_entry->create_object = dom_xpath_objects_new;
dom_modern_xpath_class_entry->default_object_handlers = &dom_xpath_object_handlers;
dom_modern_xpath_class_entry->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

zend_hash_add_new_ptr(&classes, dom_modern_xpath_class_entry->name, &dom_xpath_prop_handlers);
#endif
Expand Down
1 change: 1 addition & 0 deletions ext/pdo/pdo_dbh.c
Original file line number Diff line number Diff line change
Expand Up @@ -1449,6 +1449,7 @@ void pdo_dbh_init(int module_number)
pdo_dbh_ce = register_class_PDO();
pdo_dbh_ce->create_object = pdo_dbh_new;
pdo_dbh_ce->default_object_handlers = &pdo_dbh_object_handlers;
pdo_dbh_ce->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

memcpy(&pdo_dbh_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
pdo_dbh_object_handlers.offset = XtOffsetOf(pdo_dbh_object_t, std);
Expand Down
1 change: 1 addition & 0 deletions ext/pdo/pdo_stmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -2491,6 +2491,7 @@ void pdo_stmt_init(void)
pdo_dbstmt_ce->get_iterator = pdo_stmt_iter_get;
pdo_dbstmt_ce->create_object = pdo_dbstmt_new;
pdo_dbstmt_ce->default_object_handlers = &pdo_dbstmt_object_handlers;
pdo_dbstmt_ce->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

memcpy(&pdo_dbstmt_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
pdo_dbstmt_object_handlers.offset = XtOffsetOf(pdo_stmt_t, std);
Expand Down
32 changes: 32 additions & 0 deletions ext/reflection/php_reflection.c
Original file line number Diff line number Diff line change
Expand Up @@ -4343,6 +4343,16 @@ ZEND_METHOD(ReflectionClass, getAttributes)
}
/* }}} */

ZEND_METHOD(ReflectionClass, mayBeCyclic)
{
reflection_object *intern;
zend_class_entry *ce;

GET_REFLECTION_OBJECT_PTR(ce);

RETURN_BOOL(ce->ce_flags & ZEND_ACC_MAY_BE_CYCLIC);
}

/* {{{ Returns the class' constructor if there is one, NULL otherwise */
ZEND_METHOD(ReflectionClass, getConstructor)
{
Expand Down Expand Up @@ -7682,91 +7692,113 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */

reflection_function_abstract_ptr = register_class_ReflectionFunctionAbstract(reflector_ptr);
reflection_function_abstract_ptr->default_object_handlers = &reflection_object_handlers;
reflection_function_abstract_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;
reflection_function_abstract_ptr->create_object = reflection_objects_new;

reflection_function_ptr = register_class_ReflectionFunction(reflection_function_abstract_ptr);
reflection_function_ptr->create_object = reflection_objects_new;
reflection_function_ptr->default_object_handlers = &reflection_object_handlers;
reflection_function_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

reflection_generator_ptr = register_class_ReflectionGenerator();
reflection_generator_ptr->create_object = reflection_objects_new;
reflection_generator_ptr->default_object_handlers = &reflection_object_handlers;
reflection_generator_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

reflection_parameter_ptr = register_class_ReflectionParameter(reflector_ptr);
reflection_parameter_ptr->create_object = reflection_objects_new;
reflection_parameter_ptr->default_object_handlers = &reflection_object_handlers;
reflection_parameter_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

reflection_type_ptr = register_class_ReflectionType(zend_ce_stringable);
reflection_type_ptr->create_object = reflection_objects_new;
reflection_type_ptr->default_object_handlers = &reflection_object_handlers;
reflection_type_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

reflection_named_type_ptr = register_class_ReflectionNamedType(reflection_type_ptr);
reflection_named_type_ptr->create_object = reflection_objects_new;
reflection_named_type_ptr->default_object_handlers = &reflection_object_handlers;
reflection_named_type_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

reflection_union_type_ptr = register_class_ReflectionUnionType(reflection_type_ptr);
reflection_union_type_ptr->create_object = reflection_objects_new;
reflection_union_type_ptr->default_object_handlers = &reflection_object_handlers;
reflection_union_type_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

reflection_intersection_type_ptr = register_class_ReflectionIntersectionType(reflection_type_ptr);
reflection_intersection_type_ptr->create_object = reflection_objects_new;
reflection_intersection_type_ptr->default_object_handlers = &reflection_object_handlers;
reflection_intersection_type_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

reflection_method_ptr = register_class_ReflectionMethod(reflection_function_abstract_ptr);
reflection_method_ptr->create_object = reflection_objects_new;
reflection_method_ptr->default_object_handlers = &reflection_object_handlers;
reflection_method_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

reflection_class_ptr = register_class_ReflectionClass(reflector_ptr);
reflection_class_ptr->create_object = reflection_objects_new;
reflection_class_ptr->default_object_handlers = &reflection_object_handlers;
reflection_class_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

reflection_object_ptr = register_class_ReflectionObject(reflection_class_ptr);
reflection_object_ptr->create_object = reflection_objects_new;
reflection_object_ptr->default_object_handlers = &reflection_object_handlers;
reflection_object_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

reflection_property_ptr = register_class_ReflectionProperty(reflector_ptr);
reflection_property_ptr->create_object = reflection_objects_new;
reflection_property_ptr->default_object_handlers = &reflection_object_handlers;
reflection_property_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

reflection_class_constant_ptr = register_class_ReflectionClassConstant(reflector_ptr);
reflection_class_constant_ptr->create_object = reflection_objects_new;
reflection_class_constant_ptr->default_object_handlers = &reflection_object_handlers;
reflection_class_constant_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

reflection_extension_ptr = register_class_ReflectionExtension(reflector_ptr);
reflection_extension_ptr->create_object = reflection_objects_new;
reflection_extension_ptr->default_object_handlers = &reflection_object_handlers;
reflection_extension_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

reflection_zend_extension_ptr = register_class_ReflectionZendExtension(reflector_ptr);
reflection_zend_extension_ptr->create_object = reflection_objects_new;
reflection_zend_extension_ptr->default_object_handlers = &reflection_object_handlers;
reflection_zend_extension_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

reflection_reference_ptr = register_class_ReflectionReference();
reflection_reference_ptr->create_object = reflection_objects_new;
reflection_reference_ptr->default_object_handlers = &reflection_object_handlers;
reflection_reference_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

reflection_attribute_ptr = register_class_ReflectionAttribute(reflector_ptr);
reflection_attribute_ptr->create_object = reflection_objects_new;
reflection_attribute_ptr->default_object_handlers = &reflection_object_handlers;
reflection_attribute_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

reflection_enum_ptr = register_class_ReflectionEnum(reflection_class_ptr);
reflection_enum_ptr->create_object = reflection_objects_new;
reflection_enum_ptr->default_object_handlers = &reflection_object_handlers;
reflection_enum_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

reflection_enum_unit_case_ptr = register_class_ReflectionEnumUnitCase(reflection_class_constant_ptr);
reflection_enum_unit_case_ptr->create_object = reflection_objects_new;
reflection_enum_unit_case_ptr->default_object_handlers = &reflection_object_handlers;
reflection_enum_unit_case_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

reflection_enum_backed_case_ptr = register_class_ReflectionEnumBackedCase(reflection_enum_unit_case_ptr);
reflection_enum_backed_case_ptr->create_object = reflection_objects_new;
reflection_enum_backed_case_ptr->default_object_handlers = &reflection_object_handlers;
reflection_enum_backed_case_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

reflection_fiber_ptr = register_class_ReflectionFiber();
reflection_fiber_ptr->create_object = reflection_objects_new;
reflection_fiber_ptr->default_object_handlers = &reflection_object_handlers;
reflection_fiber_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

reflection_constant_ptr = register_class_ReflectionConstant(reflector_ptr);
reflection_constant_ptr->create_object = reflection_objects_new;
reflection_constant_ptr->default_object_handlers = &reflection_object_handlers;
reflection_constant_ptr->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

reflection_property_hook_type_ptr = register_class_PropertyHookType();

Expand Down
2 changes: 2 additions & 0 deletions ext/reflection/php_reflection.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,8 @@ public function getNamespaceName(): string {}
public function getShortName(): string {}

public function getAttributes(?string $name = null, int $flags = 0): array {}

public function mayBeCyclic(): bool {}
}

class ReflectionObject extends ReflectionClass
Expand Down
6 changes: 5 additions & 1 deletion ext/reflection/php_reflection_arginfo.h

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

1 change: 1 addition & 0 deletions ext/simplexml/simplexml.c
Original file line number Diff line number Diff line change
Expand Up @@ -2654,6 +2654,7 @@ PHP_MINIT_FUNCTION(simplexml)
ce_SimpleXMLElement = register_class_SimpleXMLElement(zend_ce_stringable, zend_ce_countable, spl_ce_RecursiveIterator);
ce_SimpleXMLElement->create_object = sxe_object_new;
ce_SimpleXMLElement->default_object_handlers = &sxe_object_handlers;
ce_SimpleXMLElement->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;
ce_SimpleXMLElement->get_iterator = php_sxe_get_iterator;

memcpy(&sxe_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
Expand Down
1 change: 1 addition & 0 deletions ext/sockets/sockets.c
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ static PHP_MINIT_FUNCTION(sockets)
socket_ce = register_class_Socket();
socket_ce->create_object = socket_create_object;
socket_ce->default_object_handlers = &socket_object_handlers;
socket_ce->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;

memcpy(&socket_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
socket_object_handlers.offset = XtOffsetOf(php_socket, std);
Expand Down
Loading

0 comments on commit 72c581b

Please sign in to comment.