From 57026f7af5384c71759ec235941de1718158fb76 Mon Sep 17 00:00:00 2001 From: Khaled Alam Date: Mon, 19 Jan 2026 21:15:01 +0400 Subject: [PATCH 01/11] feat: Add zend_class_use_traits() for internal classes. --- Zend/zend_API.h | 1 + Zend/zend_inheritance.c | 55 +++++++++++++++++++ build/gen_stub.php | 35 +++++++++++- ext/zend_test/test.c | 11 ++++ ext/zend_test/test.stub.php | 18 ++++++ ext/zend_test/test_arginfo.h | 52 +++++++++++++++++- ext/zend_test/tests/internal_class_trait.phpt | 31 +++++++++++ 7 files changed, 199 insertions(+), 4 deletions(-) create mode 100644 ext/zend_test/tests/internal_class_trait.phpt diff --git a/Zend/zend_API.h b/Zend/zend_API.h index c1ccbf13666a5..9e81a69803707 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -392,6 +392,7 @@ ZEND_API zend_class_entry *zend_register_internal_class_ex(const zend_class_entr ZEND_API zend_class_entry *zend_register_internal_class_with_flags(const zend_class_entry *class_entry, zend_class_entry *parent_ce, uint32_t flags); ZEND_API zend_class_entry *zend_register_internal_interface(const zend_class_entry *orig_class_entry); ZEND_API void zend_class_implements(zend_class_entry *class_entry, int num_interfaces, ...); +ZEND_API void zend_class_use_traits(zend_class_entry *class_entry, int num_traits, ...); ZEND_API zend_result zend_register_class_alias_ex(const char *name, size_t name_len, zend_class_entry *ce, bool persistent); diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 3c3931cdca164..6b245ed41cd5b 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2248,6 +2248,61 @@ ZEND_API void zend_do_implement_interface(zend_class_entry *ce, zend_class_entry } /* }}} */ +static void zend_do_traits_method_binding(zend_class_entry *ce, zend_class_entry **traits, HashTable **exclude_tables, zend_class_entry **aliases, bool verify_abstract, bool *contains_abstract_methods); +static void zend_do_traits_constant_binding(zend_class_entry *ce, zend_class_entry **traits); +static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_entry **traits); +static void zend_fixup_trait_method(zend_function *fn, zend_class_entry *ce); + +ZEND_API void zend_class_use_traits(zend_class_entry *class_entry, int num_traits, ...) /* {{{ */ +{ + zend_class_entry *trait_entry; + va_list trait_list; + zend_class_entry **traits; + zend_function *fn; + bool contains_abstract_methods = false; + + ZEND_ASSERT(class_entry->ce_flags & ZEND_ACC_LINKED); + + if (num_traits == 0) { + return; + } + + traits = safe_emalloc(num_traits, sizeof(zend_class_entry *), 0); + + va_start(trait_list, num_traits); + for (int i = 0; i < num_traits; i++) { + trait_entry = va_arg(trait_list, zend_class_entry *); + if (UNEXPECTED(!(trait_entry->ce_flags & ZEND_ACC_TRAIT))) { + efree(traits); + zend_error_noreturn(E_ERROR, "Class %s cannot use %s - it is not a trait", + ZSTR_VAL(class_entry->name), ZSTR_VAL(trait_entry->name)); + return; + } + traits[i] = trait_entry; + } + va_end(trait_list); + + zend_do_traits_method_binding(class_entry, traits, NULL, NULL, false, &contains_abstract_methods); + + zend_do_traits_constant_binding(class_entry, traits); + + zend_do_traits_property_binding(class_entry, traits); + + ZEND_HASH_MAP_FOREACH_PTR(&class_entry->function_table, fn) { + zend_fixup_trait_method(fn, class_entry); + } ZEND_HASH_FOREACH_END(); + + if (contains_abstract_methods) { + zend_do_traits_method_binding(class_entry, traits, NULL, NULL, true, &contains_abstract_methods); + ZEND_HASH_MAP_FOREACH_PTR(&class_entry->function_table, fn) { + zend_fixup_trait_method(fn, class_entry); + } ZEND_HASH_FOREACH_END(); + } + + efree(traits); +} +/* }}} */ + static void zend_do_implement_interfaces(zend_class_entry *ce, zend_class_entry **interfaces) /* {{{ */ { uint32_t num_parent_interfaces = ce->parent ? ce->parent->num_interfaces : 0; diff --git a/build/gen_stub.php b/build/gen_stub.php index e5fbe4867525c..94c9894026ab1 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -13,6 +13,7 @@ use PhpParser\Node\Stmt\Enum_; use PhpParser\Node\Stmt\Interface_; use PhpParser\Node\Stmt\Trait_; +use PhpParser\Node\Stmt\TraitUse; use PhpParser\PrettyPrinter\Standard; use PhpParser\PrettyPrinterAbstract; @@ -3414,6 +3415,8 @@ class ClassInfo { private /* readonly */ array $extends; /** @var Name[] */ private /* readonly */ array $implements; + /** @var Name[] */ + private /* readonly */ array $uses; /** @var ConstInfo[] */ public /* readonly */ array $constInfos; /** @var PropertyInfo[] */ @@ -3430,6 +3433,7 @@ class ClassInfo { * @param AttributeInfo[] $attributes * @param Name[] $extends * @param Name[] $implements + * @param Name[] $uses * @param ConstInfo[] $constInfos * @param PropertyInfo[] $propertyInfos * @param FuncInfo[] $funcInfos @@ -3448,6 +3452,7 @@ public function __construct( bool $isNotSerializable, array $extends, array $implements, + array $uses, array $constInfos, array $propertyInfos, array $funcInfos, @@ -3468,6 +3473,7 @@ public function __construct( $this->isNotSerializable = $isNotSerializable; $this->extends = $extends; $this->implements = $implements; + $this->uses = $uses; $this->constInfos = $constInfos; $this->propertyInfos = $propertyInfos; $this->funcInfos = $funcInfos; @@ -3487,6 +3493,9 @@ public function getRegistration(array $allConstInfos): string foreach ($this->implements as $implements) { $params[] = "zend_class_entry *class_entry_" . implode("_", $implements->getParts()); } + foreach ($this->uses as $use) { + $params[] = "zend_class_entry *class_entry_" . implode("_", $use->getParts()); + } $escapedName = implode("_", $this->name->getParts()); @@ -3584,6 +3593,17 @@ function (Name $item) { $code .= "\tzend_class_implements(class_entry, " . count($implements) . ", " . implode(", ", $implements) . ");\n"; } + $traits = array_map( + function (Name $item) { + return "class_entry_" . implode("_", $item->getParts()); + }, + $this->uses + ); + + if (!empty($traits)) { + $code .= "\tzend_class_use_traits(class_entry, " . count($traits) . ", " . implode(", ", $traits) . ");\n"; + } + if ($this->alias) { $code .= "\tzend_register_class_alias(\"" . str_replace("\\", "\\\\", $this->alias) . "\", class_entry);\n"; } @@ -4385,15 +4405,16 @@ private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPri $propertyInfos = []; $methodInfos = []; $enumCaseInfos = []; + $traitUses = []; foreach ($stmt->stmts as $classStmt) { $cond = self::handlePreprocessorConditions($conds, $classStmt); if ($classStmt instanceof Stmt\Nop) { continue; } - + $classFlags = $stmt instanceof Class_ ? $stmt->flags : 0; $abstractFlag = $stmt instanceof Stmt\Interface_ ? Modifiers::ABSTRACT : 0; - + if ($classStmt instanceof Stmt\ClassConst) { foreach ($classStmt->consts as $const) { $constInfos[] = parseConstLike( @@ -4443,11 +4464,15 @@ private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPri } else if ($classStmt instanceof Stmt\EnumCase) { $enumCaseInfos[] = new EnumCaseInfo( $classStmt->name->toString(), $classStmt->expr); + } else if ($classStmt instanceof TraitUse) { + foreach ($classStmt->traits as $trait) { + $traitUses[] = $trait; + } } else { throw new Exception("Not implemented {$classStmt->getType()}"); } } - + $this->classInfos[] = parseClass( $className, $stmt, @@ -4455,6 +4480,7 @@ private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPri $propertyInfos, $methodInfos, $enumCaseInfos, + $traitUses, $cond, $this->getMinimumPhpVersionIdCompatibility(), $this->isUndocumentable @@ -5000,6 +5026,7 @@ function parseProperty( * @param PropertyInfo[] $properties * @param FuncInfo[] $methods * @param EnumCaseInfo[] $enumCases + * @param Name[] $traitUses */ function parseClass( Name $name, @@ -5008,6 +5035,7 @@ function parseClass( array $properties, array $methods, array $enumCases, + array $traitUses, ?string $cond, ?int $minimumPhpVersionIdCompatibility, bool $isUndocumentable @@ -5083,6 +5111,7 @@ function parseClass( $isNotSerializable, $extends, $implements, + $traitUses, $consts, $properties, $methods, diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index fd1a51c5776c3..5f09a66e6aa88 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -57,6 +57,8 @@ static zend_class_entry *zend_test_child_class; static zend_class_entry *zend_test_gen_stub_flag_compatibility_test; static zend_class_entry *zend_attribute_test_class; static zend_class_entry *zend_test_trait; +static zend_class_entry *zend_test_trait_for_internal_class; +static zend_class_entry *zend_test_class_with_trait; static zend_class_entry *zend_test_attribute; static zend_class_entry *zend_test_repeatable_attribute; static zend_class_entry *zend_test_parameter_attribute; @@ -1234,6 +1236,12 @@ static ZEND_METHOD(_ZendTestTrait, testMethod) RETURN_TRUE; } +static ZEND_METHOD(_ZendTestTraitForInternalClass, traitMethod) +{ + ZEND_PARSE_PARAMETERS_NONE(); + RETURN_LONG(789); +} + static ZEND_METHOD(ZendTestNS_Foo, method) { ZEND_PARSE_PARAMETERS_NONE(); @@ -1536,6 +1544,9 @@ PHP_MINIT_FUNCTION(zend_test) zend_test_trait = register_class__ZendTestTrait(); + zend_test_trait_for_internal_class = register_class__ZendTestTraitForInternalClass(); + zend_test_class_with_trait = register_class__ZendTestClassWithTrait(zend_test_trait_for_internal_class); + register_test_symbols(module_number); zend_test_attribute = register_class_ZendTestAttribute(); diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index d0c0c64b8b1d0..6b580c3b8579d 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -34,6 +34,24 @@ interface _ZendTestInterface public const DUMMY = 0; } + trait _ZendTestTraitForInternalClass + { + /** @var int */ + public const ZEND_TRAIT_CONST = 123; + + public int $traitProp = 456; + + public function traitMethod(): int + { + return 789; + } + } + + class _ZendTestClassWithTrait + { + use _ZendTestTraitForInternalClass; + } + /** @alias _ZendTestClassAlias */ class _ZendTestClass implements _ZendTestInterface { public const mixed TYPED_CLASS_CONST1 = []; diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index 683b3b38648b6..9c3501a2e0f7d 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: a8dae89983ccbcd5dd36d1cdee736d40af4fd33c */ + * Stub hash: 9e1fda9f4ac6fd742869cb0553326ffac3356321 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_trigger_bailout, 0, 0, IS_NEVER, 0) ZEND_END_ARG_INFO() @@ -215,6 +215,8 @@ ZEND_END_ARG_INFO() #define arginfo_ZendTestNS2_ZendSubNS_namespaced_deprecated_aliased_func arginfo_zend_test_void_return +#define arginfo_class__ZendTestTraitForInternalClass_traitMethod arginfo_zend_test_nodiscard + #define arginfo_class__ZendTestClass_is_object arginfo_zend_test_nodiscard #define arginfo_class__ZendTestClass___toString arginfo_zend_get_current_func_name @@ -348,6 +350,7 @@ static ZEND_FUNCTION(ZendTestNS2_namespaced_func); static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func); static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func); static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_deprecated_func); +static ZEND_METHOD(_ZendTestTraitForInternalClass, traitMethod); static ZEND_METHOD(_ZendTestClass, is_object); static ZEND_METHOD(_ZendTestClass, __toString); static ZEND_METHOD(_ZendTestClass, returnsStatic); @@ -527,6 +530,11 @@ static const zend_function_entry ext_functions[] = { ZEND_FE_END }; +static const zend_function_entry class__ZendTestTraitForInternalClass_methods[] = { + ZEND_ME(_ZendTestTraitForInternalClass, traitMethod, arginfo_class__ZendTestTraitForInternalClass_traitMethod, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + static const zend_function_entry class__ZendTestClass_methods[] = { ZEND_ME(_ZendTestClass, is_object, arginfo_class__ZendTestClass_is_object, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(_ZendTestClass, __toString, arginfo_class__ZendTestClass___toString, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED) @@ -697,6 +705,48 @@ static zend_class_entry *register_class__ZendTestInterface(void) return class_entry; } +static zend_class_entry *register_class__ZendTestTraitForInternalClass(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "_ZendTestTraitForInternalClass", class__ZendTestTraitForInternalClass_methods); +#if (PHP_VERSION_ID >= 80400) + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_TRAIT); +#else + class_entry = zend_register_internal_class_ex(&ce, NULL); + class_entry->ce_flags |= ZEND_ACC_TRAIT; +#endif + + zval const_ZEND_TRAIT_CONST_value; + ZVAL_LONG(&const_ZEND_TRAIT_CONST_value, 123); + zend_string *const_ZEND_TRAIT_CONST_name = zend_string_init_interned("ZEND_TRAIT_CONST", sizeof("ZEND_TRAIT_CONST") - 1, true); + zend_declare_class_constant_ex(class_entry, const_ZEND_TRAIT_CONST_name, &const_ZEND_TRAIT_CONST_value, ZEND_ACC_PUBLIC, NULL); + zend_string_release_ex(const_ZEND_TRAIT_CONST_name, true); + + zval property_traitProp_default_value; + ZVAL_LONG(&property_traitProp_default_value, 456); + zend_string *property_traitProp_name = zend_string_init("traitProp", sizeof("traitProp") - 1, true); + zend_declare_typed_property(class_entry, property_traitProp_name, &property_traitProp_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(property_traitProp_name, true); + + return class_entry; +} + +static zend_class_entry *register_class__ZendTestClassWithTrait(zend_class_entry *class_entry__ZendTestTraitForInternalClass) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "_ZendTestClassWithTrait", NULL); +#if (PHP_VERSION_ID >= 80400) + class_entry = zend_register_internal_class_with_flags(&ce, NULL, 0); +#else + class_entry = zend_register_internal_class_ex(&ce, NULL); +#endif + zend_class_use_traits(class_entry, 1, class_entry__ZendTestTraitForInternalClass); + + return class_entry; +} + static zend_class_entry *register_class__ZendTestClass(zend_class_entry *class_entry__ZendTestInterface) { zend_class_entry ce, *class_entry; diff --git a/ext/zend_test/tests/internal_class_trait.phpt b/ext/zend_test/tests/internal_class_trait.phpt new file mode 100644 index 0000000000000..995e2cf05de30 --- /dev/null +++ b/ext/zend_test/tests/internal_class_trait.phpt @@ -0,0 +1,31 @@ +--TEST-- +Test internal class using trait via zend_class_use_traits +--EXTENSIONS-- +zend_test +--FILE-- +traitProp); + +var_dump($obj->traitMethod()); + +var_dump(method_exists(_ZendTestClassWithTrait::class, 'traitMethod')); + +$rc = new ReflectionClass(_ZendTestClassWithTrait::class); +$traits = $rc->getTraitNames(); +var_dump(count($traits)); +var_dump(in_array('_ZendTestTraitForInternalClass', $traits)); + +echo "Done\n"; +?> +--EXPECT-- +int(123) +int(456) +int(789) +bool(true) +int(1) +bool(true) +Done From 27304b6928cb441cdb1f31e8a1b086eea03f154b Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 20 Jan 2026 02:15:30 +0100 Subject: [PATCH 02/11] Fixes --- Zend/zend_inheritance.c | 17 +++++++++++++++-- Zend/zend_opcode.c | 9 ++++++++- ext/zend_test/test.stub.php | 5 +---- ext/zend_test/test_arginfo.h | 2 +- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index c0be97b2dc563..6aa654bfda83e 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2267,10 +2267,15 @@ ZEND_API void zend_class_use_traits(zend_class_entry *class_entry, int num_trait } traits = safe_emalloc(num_traits, sizeof(zend_class_entry *), 0); + class_entry->trait_names = safe_pemalloc(num_traits, sizeof(zend_class_name), 0, 1); + class_entry->num_traits = num_traits; va_start(trait_list, num_traits); for (int i = 0; i < num_traits; i++) { trait_entry = va_arg(trait_list, zend_class_entry *); + class_entry->trait_names[i].name = zend_string_copy(trait_entry->name); + class_entry->trait_names[i].lc_name = zend_string_tolower_ex(zend_string_copy(trait_entry->name), 1); + if (UNEXPECTED(!(trait_entry->ce_flags & ZEND_ACC_TRAIT))) { efree(traits); zend_error_noreturn(E_ERROR, "Class %s cannot use %s - it is not a trait", @@ -2457,7 +2462,11 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_ } } - if (UNEXPECTED(fn->type == ZEND_INTERNAL_FUNCTION)) { + if (ce->type == ZEND_INTERNAL_CLASS) { + ZEND_ASSERT(fn->type == ZEND_INTERNAL_FUNCTION); + new_fn = (zend_function*)(uintptr_t)malloc(sizeof(zend_internal_function)); + memcpy(new_fn, fn, sizeof(zend_internal_function)); + } else if (UNEXPECTED(fn->type == ZEND_INTERNAL_FUNCTION)) { new_fn = zend_arena_alloc(&CG(arena), sizeof(zend_internal_function)); memcpy(new_fn, fn, sizeof(zend_internal_function)); new_fn->common.fn_flags |= ZEND_ACC_ARENA_ALLOCATED; @@ -2878,7 +2887,11 @@ static void zend_do_traits_constant_binding(zend_class_entry *ce, zend_class_ent if (do_trait_constant_check(ce, constant, constant_name, traits, i)) { zend_class_constant *ct = NULL; - ct = zend_arena_alloc(&CG(arena),sizeof(zend_class_constant)); + if (ce->type == ZEND_INTERNAL_CLASS) { + ct = malloc(sizeof(zend_class_constant)); + } else { + ct = zend_arena_alloc(&CG(arena),sizeof(zend_class_constant)); + } memcpy(ct, constant, sizeof(zend_class_constant)); constant = ct; diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index d874f566dc87d..065016351737c 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -486,7 +486,7 @@ ZEND_API void destroy_zend_class(zval *zv) zend_string_release_ex(ce->name, 1); ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, fn) { - if (fn->common.scope == ce) { + if (fn->common.scope == ce && !(fn->common.fn_flags & ZEND_ACC_TRAIT_CLONE)) { zend_free_internal_arg_info(&fn->internal_function, true); if (fn->common.attributes) { @@ -536,6 +536,13 @@ ZEND_API void destroy_zend_class(zval *zv) if (ce->attributes) { zend_hash_release(ce->attributes); } + if (ce->num_traits > 0) { + for (uint32_t i = 0; i < ce->num_traits; i++) { + zend_string_release(ce->trait_names[i].name); + zend_string_release(ce->trait_names[i].lc_name); + } + free(ce->trait_names); + } free(ce); break; } diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 4df773087fce1..3e681742ca910 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -41,10 +41,7 @@ trait _ZendTestTraitForInternalClass public int $traitProp = 456; - public function traitMethod(): int - { - return 789; - } + public function traitMethod(): int {} } class _ZendTestClassWithTrait diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index 39abe8771119d..d19eb80b6cf32 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 9e1fda9f4ac6fd742869cb0553326ffac3356321 */ + * Stub hash: 96a1797dd5d481752d5502e0e50fc9cf31636e13 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_trigger_bailout, 0, 0, IS_NEVER, 0) ZEND_END_ARG_INFO() From b1caa2209b5f7bdd55f84e842622b6f3b64396ef Mon Sep 17 00:00:00 2001 From: Khaled Alam Date: Tue, 20 Jan 2026 14:07:01 +0400 Subject: [PATCH 03/11] feat: impove zend_class_use_internal_traits naming. --- Zend/zend_API.h | 2 +- Zend/zend_inheritance.c | 115 +++++++++--------- build/gen_stub.php | 2 +- ext/zend_test/test_arginfo.h | 2 +- ext/zend_test/tests/internal_class_trait.phpt | 2 +- 5 files changed, 59 insertions(+), 64 deletions(-) diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 9e81a69803707..3aa1a19f399ba 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -392,7 +392,7 @@ ZEND_API zend_class_entry *zend_register_internal_class_ex(const zend_class_entr ZEND_API zend_class_entry *zend_register_internal_class_with_flags(const zend_class_entry *class_entry, zend_class_entry *parent_ce, uint32_t flags); ZEND_API zend_class_entry *zend_register_internal_interface(const zend_class_entry *orig_class_entry); ZEND_API void zend_class_implements(zend_class_entry *class_entry, int num_interfaces, ...); -ZEND_API void zend_class_use_traits(zend_class_entry *class_entry, int num_traits, ...); +ZEND_API void zend_class_use_internal_traits(zend_class_entry *class_entry, int num_traits, ...); ZEND_API zend_result zend_register_class_alias_ex(const char *name, size_t name_len, zend_class_entry *ce, bool persistent); diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 6aa654bfda83e..99301a77a6a0d 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2247,66 +2247,6 @@ ZEND_API void zend_do_implement_interface(zend_class_entry *ce, zend_class_entry } /* }}} */ -static void zend_do_traits_method_binding(zend_class_entry *ce, zend_class_entry **traits, HashTable **exclude_tables, zend_class_entry **aliases, bool verify_abstract, bool *contains_abstract_methods); -static void zend_do_traits_constant_binding(zend_class_entry *ce, zend_class_entry **traits); -static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_entry **traits); -static void zend_fixup_trait_method(zend_function *fn, zend_class_entry *ce); - -ZEND_API void zend_class_use_traits(zend_class_entry *class_entry, int num_traits, ...) /* {{{ */ -{ - zend_class_entry *trait_entry; - va_list trait_list; - zend_class_entry **traits; - zend_function *fn; - bool contains_abstract_methods = false; - - ZEND_ASSERT(class_entry->ce_flags & ZEND_ACC_LINKED); - - if (num_traits == 0) { - return; - } - - traits = safe_emalloc(num_traits, sizeof(zend_class_entry *), 0); - class_entry->trait_names = safe_pemalloc(num_traits, sizeof(zend_class_name), 0, 1); - class_entry->num_traits = num_traits; - - va_start(trait_list, num_traits); - for (int i = 0; i < num_traits; i++) { - trait_entry = va_arg(trait_list, zend_class_entry *); - class_entry->trait_names[i].name = zend_string_copy(trait_entry->name); - class_entry->trait_names[i].lc_name = zend_string_tolower_ex(zend_string_copy(trait_entry->name), 1); - - if (UNEXPECTED(!(trait_entry->ce_flags & ZEND_ACC_TRAIT))) { - efree(traits); - zend_error_noreturn(E_ERROR, "Class %s cannot use %s - it is not a trait", - ZSTR_VAL(class_entry->name), ZSTR_VAL(trait_entry->name)); - return; - } - traits[i] = trait_entry; - } - va_end(trait_list); - - zend_do_traits_method_binding(class_entry, traits, NULL, NULL, false, &contains_abstract_methods); - - zend_do_traits_constant_binding(class_entry, traits); - - zend_do_traits_property_binding(class_entry, traits); - - ZEND_HASH_MAP_FOREACH_PTR(&class_entry->function_table, fn) { - zend_fixup_trait_method(fn, class_entry); - } ZEND_HASH_FOREACH_END(); - - if (contains_abstract_methods) { - zend_do_traits_method_binding(class_entry, traits, NULL, NULL, true, &contains_abstract_methods); - ZEND_HASH_MAP_FOREACH_PTR(&class_entry->function_table, fn) { - zend_fixup_trait_method(fn, class_entry); - } ZEND_HASH_FOREACH_END(); - } - - efree(traits); -} -/* }}} */ - static void zend_do_implement_interfaces(zend_class_entry *ce, zend_class_entry **interfaces) /* {{{ */ { uint32_t num_parent_interfaces = ce->parent ? ce->parent->num_interfaces : 0; @@ -3072,6 +3012,61 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent } /* }}} */ +ZEND_API void zend_class_use_internal_traits(zend_class_entry *class_entry, int num_traits, ...) /* {{{ */ +{ + zend_class_entry *trait_entry; + va_list trait_list; + zend_class_entry **traits; + zend_function *fn; + bool contains_abstract_methods = false; + + ZEND_ASSERT(class_entry->ce_flags & ZEND_ACC_LINKED); + + if (num_traits == 0) { + return; + } + + traits = safe_emalloc(num_traits, sizeof(zend_class_entry *), 0); + class_entry->trait_names = safe_pemalloc(num_traits, sizeof(zend_class_name), 0, 1); + class_entry->num_traits = num_traits; + + va_start(trait_list, num_traits); + for (int i = 0; i < num_traits; i++) { + trait_entry = va_arg(trait_list, zend_class_entry *); + class_entry->trait_names[i].name = zend_string_copy(trait_entry->name); + class_entry->trait_names[i].lc_name = zend_string_tolower_ex(zend_string_copy(trait_entry->name), 1); + + if (UNEXPECTED(!(trait_entry->ce_flags & ZEND_ACC_TRAIT))) { + efree(traits); + zend_error_noreturn(E_ERROR, "Class %s cannot use %s - it is not a trait", + ZSTR_VAL(class_entry->name), ZSTR_VAL(trait_entry->name)); + return; + } + traits[i] = trait_entry; + } + va_end(trait_list); + + zend_do_traits_method_binding(class_entry, traits, NULL, NULL, false, &contains_abstract_methods); + + zend_do_traits_constant_binding(class_entry, traits); + + zend_do_traits_property_binding(class_entry, traits); + + ZEND_HASH_MAP_FOREACH_PTR(&class_entry->function_table, fn) { + zend_fixup_trait_method(fn, class_entry); + } ZEND_HASH_FOREACH_END(); + + if (contains_abstract_methods) { + zend_do_traits_method_binding(class_entry, traits, NULL, NULL, true, &contains_abstract_methods); + ZEND_HASH_MAP_FOREACH_PTR(&class_entry->function_table, fn) { + zend_fixup_trait_method(fn, class_entry); + } ZEND_HASH_FOREACH_END(); + } + + efree(traits); +} +/* }}} */ + #define MAX_ABSTRACT_INFO_CNT 3 #define MAX_ABSTRACT_INFO_FMT "%s%s%s%s" #define DISPLAY_ABSTRACT_FN(idx) \ diff --git a/build/gen_stub.php b/build/gen_stub.php index d0330ff677c22..adddad3a9ef71 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -3601,7 +3601,7 @@ function (Name $item) { ); if (!empty($traits)) { - $code .= "\tzend_class_use_traits(class_entry, " . count($traits) . ", " . implode(", ", $traits) . ");\n"; + $code .= "\tzend_class_use_internal_traits(class_entry, " . count($traits) . ", " . implode(", ", $traits) . ");\n"; } if ($this->alias) { diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index d19eb80b6cf32..8963a71a17cc3 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -742,7 +742,7 @@ static zend_class_entry *register_class__ZendTestClassWithTrait(zend_class_entry #else class_entry = zend_register_internal_class_ex(&ce, NULL); #endif - zend_class_use_traits(class_entry, 1, class_entry__ZendTestTraitForInternalClass); + zend_class_use_internal_traits(class_entry, 1, class_entry__ZendTestTraitForInternalClass); return class_entry; } diff --git a/ext/zend_test/tests/internal_class_trait.phpt b/ext/zend_test/tests/internal_class_trait.phpt index 995e2cf05de30..471b4d2a5a7af 100644 --- a/ext/zend_test/tests/internal_class_trait.phpt +++ b/ext/zend_test/tests/internal_class_trait.phpt @@ -1,5 +1,5 @@ --TEST-- -Test internal class using trait via zend_class_use_traits +Test internal class using trait via zend_class_use_internal_traits --EXTENSIONS-- zend_test --FILE-- From 1c817953ce547aef918c42bbfaf26a77aa5ab9a7 Mon Sep 17 00:00:00 2001 From: Khaled Alam Date: Tue, 20 Jan 2026 15:55:54 +0400 Subject: [PATCH 04/11] feat: remove supporting abstract trait methods, assert num_traits, add more test cases. --- Zend/zend_inheritance.c | 17 +-- ext/zend_test/test.c | 28 ++++- ext/zend_test/test.stub.php | 13 +++ ext/zend_test/test_arginfo.h | 106 ++++++++++++++---- .../tests/internal_class_trait_abstract.phpt | 14 +++ .../tests/internal_class_trait_not_trait.phpt | 14 +++ .../internal_class_trait_zero_traits.phpt | 19 ++++ 7 files changed, 182 insertions(+), 29 deletions(-) create mode 100644 ext/zend_test/tests/internal_class_trait_abstract.phpt create mode 100644 ext/zend_test/tests/internal_class_trait_not_trait.phpt create mode 100644 ext/zend_test/tests/internal_class_trait_zero_traits.phpt diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 99301a77a6a0d..d1254b9dc6a5d 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -3021,6 +3021,7 @@ ZEND_API void zend_class_use_internal_traits(zend_class_entry *class_entry, int bool contains_abstract_methods = false; ZEND_ASSERT(class_entry->ce_flags & ZEND_ACC_LINKED); + ZEND_ASSERT(num_traits != 0); if (num_traits == 0) { return; @@ -3038,9 +3039,8 @@ ZEND_API void zend_class_use_internal_traits(zend_class_entry *class_entry, int if (UNEXPECTED(!(trait_entry->ce_flags & ZEND_ACC_TRAIT))) { efree(traits); - zend_error_noreturn(E_ERROR, "Class %s cannot use %s - it is not a trait", + zend_error_noreturn(E_COMPILE_ERROR, "Class %s cannot use %s - it is not a trait", ZSTR_VAL(class_entry->name), ZSTR_VAL(trait_entry->name)); - return; } traits[i] = trait_entry; } @@ -3056,14 +3056,15 @@ ZEND_API void zend_class_use_internal_traits(zend_class_entry *class_entry, int zend_fixup_trait_method(fn, class_entry); } ZEND_HASH_FOREACH_END(); + efree(traits); + + /* TODO: Add second binding pass for abstract trait methods if needed in the future. */ + ZEND_ASSERT(!contains_abstract_methods); if (contains_abstract_methods) { - zend_do_traits_method_binding(class_entry, traits, NULL, NULL, true, &contains_abstract_methods); - ZEND_HASH_MAP_FOREACH_PTR(&class_entry->function_table, fn) { - zend_fixup_trait_method(fn, class_entry); - } ZEND_HASH_FOREACH_END(); + zend_error_noreturn(E_COMPILE_ERROR, + "Internal trait binding does not support abstract trait methods in %s", + ZSTR_VAL(class_entry->name)); } - - efree(traits); } /* }}} */ diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 63398a7902ce5..9e366318df745 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -59,6 +59,9 @@ static zend_class_entry *zend_attribute_test_class; static zend_class_entry *zend_test_trait; static zend_class_entry *zend_test_trait_for_internal_class; static zend_class_entry *zend_test_class_with_trait; +static zend_class_entry *zend_test_abstract_trait_for_internal_traits; +static zend_class_entry *zend_test_not_a_trait_for_internal_traits; +static zend_class_entry *zend_test_internal_traits_driver; static zend_class_entry *zend_test_attribute; static zend_class_entry *zend_test_repeatable_attribute; static zend_class_entry *zend_test_parameter_attribute; @@ -1243,6 +1246,27 @@ static ZEND_METHOD(_ZendTestTraitForInternalClass, traitMethod) RETURN_LONG(789); } +static ZEND_FUNCTION(zend_test_use_internal_traits_zero) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + zend_class_use_internal_traits(zend_test_internal_traits_driver, 0); +} + +static ZEND_FUNCTION(zend_test_use_internal_traits_not_trait) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + zend_class_use_internal_traits(zend_test_internal_traits_driver, 1, zend_test_not_a_trait_for_internal_traits); +} + +static ZEND_FUNCTION(zend_test_use_internal_traits_abstract_trait) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + zend_class_use_internal_traits(zend_test_internal_traits_driver, 1, zend_test_abstract_trait_for_internal_traits); +} + static ZEND_METHOD(ZendTestNS_Foo, method) { ZEND_PARSE_PARAMETERS_NONE(); @@ -1544,9 +1568,11 @@ PHP_MINIT_FUNCTION(zend_test) zend_attribute_test_class = register_class_ZendAttributeTest(); zend_test_trait = register_class__ZendTestTrait(); - zend_test_trait_for_internal_class = register_class__ZendTestTraitForInternalClass(); zend_test_class_with_trait = register_class__ZendTestClassWithTrait(zend_test_trait_for_internal_class); + zend_test_abstract_trait_for_internal_traits = register_class__ZendTestAbstractTraitForInternalTraits(); + zend_test_not_a_trait_for_internal_traits = register_class__ZendTestNotATraitForInternalTraits(); + zend_test_internal_traits_driver = register_class__ZendTestInternalTraitsDriver(); register_test_symbols(module_number); diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 3e681742ca910..ac769c73d20e2 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -49,6 +49,19 @@ class _ZendTestClassWithTrait use _ZendTestTraitForInternalClass; } + trait _ZendTestAbstractTraitForInternalTraits + { + abstract public function abstractMethod(): void; + } + + class _ZendTestNotATraitForInternalTraits {} + + class _ZendTestInternalTraitsDriver {} + + function zend_test_use_internal_traits_zero(): void {} + function zend_test_use_internal_traits_not_trait(): void {} + function zend_test_use_internal_traits_abstract_trait(): void {} + /** @alias _ZendTestClassAlias */ class _ZendTestClass implements _ZendTestInterface { public const mixed TYPED_CLASS_CONST1 = []; diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index 8963a71a17cc3..db6224bb2b87e 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,12 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 96a1797dd5d481752d5502e0e50fc9cf31636e13 */ + * Stub hash: 8e7a71a91bffaf61361d183be14f42205b7359da */ + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_use_internal_traits_zero, 0, 0, IS_VOID, 0) +ZEND_END_ARG_INFO() + +#define arginfo_zend_test_use_internal_traits_not_trait arginfo_zend_test_use_internal_traits_zero + +#define arginfo_zend_test_use_internal_traits_abstract_trait arginfo_zend_test_use_internal_traits_zero ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_trigger_bailout, 0, 0, IS_NEVER, 0) ZEND_END_ARG_INFO() @@ -10,8 +17,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_nullable_array_return, 0, 0, IS_ARRAY, 1) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_void_return, 0, 0, IS_VOID, 0) -ZEND_END_ARG_INFO() +#define arginfo_zend_test_void_return arginfo_zend_test_use_internal_traits_zero ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_compile_string, 0, 3, IS_VOID, 0) ZEND_ARG_TYPE_INFO(0, source_string, IS_STRING, 0) @@ -23,16 +29,16 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_deprecated, 0, 0, IS_V ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, arg, IS_MIXED, 0, "null") ZEND_END_ARG_INFO() -#define arginfo_zend_test_deprecated_attr arginfo_zend_test_void_return +#define arginfo_zend_test_deprecated_attr arginfo_zend_test_use_internal_traits_zero ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_nodiscard, 0, 0, IS_LONG, 0) ZEND_END_ARG_INFO() #define arginfo_zend_test_deprecated_nodiscard arginfo_zend_test_nodiscard -#define arginfo_zend_test_aliased arginfo_zend_test_void_return +#define arginfo_zend_test_aliased arginfo_zend_test_use_internal_traits_zero -#define arginfo_zend_test_deprecated_aliased arginfo_zend_test_void_return +#define arginfo_zend_test_deprecated_aliased arginfo_zend_test_use_internal_traits_zero ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_create_unterminated_string, 0, 1, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, str, IS_STRING, 0) @@ -99,7 +105,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_parameter_with_attribu ZEND_ARG_TYPE_INFO(0, parameter, IS_STRING, 0) ZEND_END_ARG_INFO() -#define arginfo_zend_test_attribute_with_named_argument arginfo_zend_test_void_return +#define arginfo_zend_test_attribute_with_named_argument arginfo_zend_test_use_internal_traits_zero ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_get_current_func_name, 0, 0, IS_STRING, 0) ZEND_END_ARG_INFO() @@ -188,7 +194,7 @@ ZEND_END_ARG_INFO() #define arginfo_zend_test_compile_to_ast arginfo_zend_create_unterminated_string -#define arginfo_zend_test_gh18756 arginfo_zend_test_void_return +#define arginfo_zend_test_gh18756 arginfo_zend_test_use_internal_traits_zero #define arginfo_zend_test_opcache_preloading arginfo_zend_test_is_pcre_bundled @@ -197,26 +203,28 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_uri_parser, 0, 2, IS_A ZEND_ARG_TYPE_INFO(0, parser, IS_STRING, 0) ZEND_END_ARG_INFO() -#define arginfo_zend_test_gh19792 arginfo_zend_test_void_return +#define arginfo_zend_test_gh19792 arginfo_zend_test_use_internal_traits_zero #define arginfo_ZendTestNS2_namespaced_func arginfo_zend_test_is_pcre_bundled -#define arginfo_ZendTestNS2_namespaced_deprecated_func arginfo_zend_test_void_return +#define arginfo_ZendTestNS2_namespaced_deprecated_func arginfo_zend_test_use_internal_traits_zero -#define arginfo_ZendTestNS2_namespaced_aliased_func arginfo_zend_test_void_return +#define arginfo_ZendTestNS2_namespaced_aliased_func arginfo_zend_test_use_internal_traits_zero -#define arginfo_ZendTestNS2_namespaced_deprecated_aliased_func arginfo_zend_test_void_return +#define arginfo_ZendTestNS2_namespaced_deprecated_aliased_func arginfo_zend_test_use_internal_traits_zero #define arginfo_ZendTestNS2_ZendSubNS_namespaced_func arginfo_zend_test_is_pcre_bundled -#define arginfo_ZendTestNS2_ZendSubNS_namespaced_deprecated_func arginfo_zend_test_void_return +#define arginfo_ZendTestNS2_ZendSubNS_namespaced_deprecated_func arginfo_zend_test_use_internal_traits_zero -#define arginfo_ZendTestNS2_ZendSubNS_namespaced_aliased_func arginfo_zend_test_void_return +#define arginfo_ZendTestNS2_ZendSubNS_namespaced_aliased_func arginfo_zend_test_use_internal_traits_zero -#define arginfo_ZendTestNS2_ZendSubNS_namespaced_deprecated_aliased_func arginfo_zend_test_void_return +#define arginfo_ZendTestNS2_ZendSubNS_namespaced_deprecated_aliased_func arginfo_zend_test_use_internal_traits_zero #define arginfo_class__ZendTestTraitForInternalClass_traitMethod arginfo_zend_test_nodiscard +#define arginfo_class__ZendTestAbstractTraitForInternalTraits_abstractMethod arginfo_zend_test_use_internal_traits_zero + #define arginfo_class__ZendTestClass_is_object arginfo_zend_test_nodiscard #define arginfo_class__ZendTestClass___toString arginfo_zend_get_current_func_name @@ -265,9 +273,9 @@ ZEND_END_ARG_INFO() #define arginfo_class_ZendTestChildClassWithMethodWithParameterAttribute_override arginfo_zend_test_parameter_with_attribute -#define arginfo_class_ZendTestForbidDynamicCall_call arginfo_zend_test_void_return +#define arginfo_class_ZendTestForbidDynamicCall_call arginfo_zend_test_use_internal_traits_zero -#define arginfo_class_ZendTestForbidDynamicCall_callStatic arginfo_zend_test_void_return +#define arginfo_class_ZendTestForbidDynamicCall_callStatic arginfo_zend_test_use_internal_traits_zero #if (PHP_VERSION_ID >= 80100) ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ZendTestNS_Foo_method, 0, 0, IS_LONG, 0) @@ -282,10 +290,13 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ZendTestNS_NotUnlikelyCompileError_method, 0, 0, ZendTestNS\\\116otUnlikelyCompileError, 1) ZEND_END_ARG_INFO() -#define arginfo_class_ZendTestNS2_Foo_method arginfo_zend_test_void_return +#define arginfo_class_ZendTestNS2_Foo_method arginfo_zend_test_use_internal_traits_zero -#define arginfo_class_ZendTestNS2_ZendSubNS_Foo_method arginfo_zend_test_void_return +#define arginfo_class_ZendTestNS2_ZendSubNS_Foo_method arginfo_zend_test_use_internal_traits_zero +static ZEND_FUNCTION(zend_test_use_internal_traits_zero); +static ZEND_FUNCTION(zend_test_use_internal_traits_not_trait); +static ZEND_FUNCTION(zend_test_use_internal_traits_abstract_trait); static ZEND_FUNCTION(zend_trigger_bailout); static ZEND_FUNCTION(zend_test_array_return); static ZEND_FUNCTION(zend_test_nullable_array_return); @@ -351,7 +362,7 @@ static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func); static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func); static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_deprecated_func); static ZEND_METHOD(_ZendTestTraitForInternalClass, traitMethod); -static ZEND_METHOD(_ZendTestClass, is_object); +static static ZEND_METHOD(_ZendTestClass, is_object); static ZEND_METHOD(_ZendTestClass, __toString); static ZEND_METHOD(_ZendTestClass, returnsStatic); static ZEND_METHOD(_ZendTestClass, returnsThrowable); @@ -377,6 +388,9 @@ static ZEND_METHOD(ZendTestNS2_Foo, method); static ZEND_METHOD(ZendTestNS2_ZendSubNS_Foo, method); static const zend_function_entry ext_functions[] = { + ZEND_FE(zend_test_use_internal_traits_zero, arginfo_zend_test_use_internal_traits_zero) + ZEND_FE(zend_test_use_internal_traits_not_trait, arginfo_zend_test_use_internal_traits_not_trait) + ZEND_FE(zend_test_use_internal_traits_abstract_trait, arginfo_zend_test_use_internal_traits_abstract_trait) ZEND_FE(zend_trigger_bailout, arginfo_zend_trigger_bailout) ZEND_FE(zend_test_array_return, arginfo_zend_test_array_return) #if (PHP_VERSION_ID >= 80400) @@ -535,6 +549,15 @@ static const zend_function_entry class__ZendTestTraitForInternalClass_methods[] ZEND_FE_END }; +static const zend_function_entry class__ZendTestAbstractTraitForInternalTraits_methods[] = { +#if (PHP_VERSION_ID >= 80400) + ZEND_RAW_FENTRY("abstractMethod", NULL, arginfo_class__ZendTestAbstractTraitForInternalTraits_abstractMethod, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT, NULL, NULL) +#else + ZEND_RAW_FENTRY("abstractMethod", NULL, arginfo_class__ZendTestAbstractTraitForInternalTraits_abstractMethod, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) +#endif + ZEND_FE_END +}; + static const zend_function_entry class__ZendTestClass_methods[] = { ZEND_ME(_ZendTestClass, is_object, arginfo_class__ZendTestClass_is_object, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(_ZendTestClass, __toString, arginfo_class__ZendTestClass___toString, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED) @@ -747,6 +770,49 @@ static zend_class_entry *register_class__ZendTestClassWithTrait(zend_class_entry return class_entry; } +static zend_class_entry *register_class__ZendTestAbstractTraitForInternalTraits(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "_ZendTestAbstractTraitForInternalTraits", class__ZendTestAbstractTraitForInternalTraits_methods); +#if (PHP_VERSION_ID >= 80400) + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_TRAIT); +#else + class_entry = zend_register_internal_class_ex(&ce, NULL); + class_entry->ce_flags |= ZEND_ACC_TRAIT; +#endif + + return class_entry; +} + +static zend_class_entry *register_class__ZendTestNotATraitForInternalTraits(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "_ZendTestNotATraitForInternalTraits", NULL); +#if (PHP_VERSION_ID >= 80400) + class_entry = zend_register_internal_class_with_flags(&ce, NULL, 0); +#else + class_entry = zend_register_internal_class_ex(&ce, NULL); +#endif + + return class_entry; +} + +static zend_class_entry *register_class__ZendTestInternalTraitsDriver(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "_ZendTestInternalTraitsDriver", NULL); +#if (PHP_VERSION_ID >= 80400) + class_entry = zend_register_internal_class_with_flags(&ce, NULL, 0); +#else + class_entry = zend_register_internal_class_ex(&ce, NULL); +#endif + + return class_entry; +} + static zend_class_entry *register_class__ZendTestClass(zend_class_entry *class_entry__ZendTestInterface) { zend_class_entry ce, *class_entry; diff --git a/ext/zend_test/tests/internal_class_trait_abstract.phpt b/ext/zend_test/tests/internal_class_trait_abstract.phpt new file mode 100644 index 0000000000000..29754cea44868 --- /dev/null +++ b/ext/zend_test/tests/internal_class_trait_abstract.phpt @@ -0,0 +1,14 @@ +--TEST-- +zend_class_use_internal_traits with abstract trait method produces E_COMPILE_ERROR +--EXTENSIONS-- +zend_test +--FILE-- + +--EXPECTF-- +Fatal error: Internal trait binding does not support abstract trait methods in %s in %s on line %d diff --git a/ext/zend_test/tests/internal_class_trait_not_trait.phpt b/ext/zend_test/tests/internal_class_trait_not_trait.phpt new file mode 100644 index 0000000000000..5ed5579da0d1a --- /dev/null +++ b/ext/zend_test/tests/internal_class_trait_not_trait.phpt @@ -0,0 +1,14 @@ +--TEST-- +zend_class_use_internal_traits with non-trait class produces E_COMPILE_ERROR +--EXTENSIONS-- +zend_test +--FILE-- + +--EXPECTF-- +Fatal error: Class %s cannot use %s - it is not a trait in %s on line %d diff --git a/ext/zend_test/tests/internal_class_trait_zero_traits.phpt b/ext/zend_test/tests/internal_class_trait_zero_traits.phpt new file mode 100644 index 0000000000000..d44d7a446e225 --- /dev/null +++ b/ext/zend_test/tests/internal_class_trait_zero_traits.phpt @@ -0,0 +1,19 @@ +--TEST-- +zend_class_use_internal_traits with num_traits=0 (no-op behavior) +--EXTENSIONS-- +zend_test +--FILE-- +getTraitNames())); + +echo "Done\n"; +?> +--EXPECT-- +int(0) +Done From 2b8be5195b70e3b90d424380255b026baba915a9 Mon Sep 17 00:00:00 2001 From: Khaled Alam Date: Tue, 20 Jan 2026 16:01:29 +0400 Subject: [PATCH 05/11] feat: remove contains_abstract_methods false assumption. --- Zend/zend_inheritance.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index d1254b9dc6a5d..33bbdec24c1ca 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -3059,7 +3059,6 @@ ZEND_API void zend_class_use_internal_traits(zend_class_entry *class_entry, int efree(traits); /* TODO: Add second binding pass for abstract trait methods if needed in the future. */ - ZEND_ASSERT(!contains_abstract_methods); if (contains_abstract_methods) { zend_error_noreturn(E_COMPILE_ERROR, "Internal trait binding does not support abstract trait methods in %s", From 757ec9dcb15dfb826b9a7d06423295ece6e7dcf6 Mon Sep 17 00:00:00 2001 From: Khaled Alam Date: Tue, 20 Jan 2026 16:25:30 +0400 Subject: [PATCH 06/11] feat: fix static static. --- ext/zend_test/test_arginfo.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index db6224bb2b87e..e035cc7dfe23b 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -362,7 +362,7 @@ static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func); static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func); static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_deprecated_func); static ZEND_METHOD(_ZendTestTraitForInternalClass, traitMethod); -static static ZEND_METHOD(_ZendTestClass, is_object); +static ZEND_METHOD(_ZendTestClass, is_object); static ZEND_METHOD(_ZendTestClass, __toString); static ZEND_METHOD(_ZendTestClass, returnsStatic); static ZEND_METHOD(_ZendTestClass, returnsThrowable); From 0745a68e410934c546375a3e3b1df0dc6cfebf05 Mon Sep 17 00:00:00 2001 From: Khaled Alam Date: Wed, 21 Jan 2026 00:17:36 +0400 Subject: [PATCH 07/11] feat: Remove abstract method logic and its testing related code. --- Zend/zend_inheritance.c | 13 +++----- ext/zend_test/test.c | 9 ------ ext/zend_test/test.stub.php | 6 ---- ext/zend_test/test_arginfo.h | 32 +------------------ .../tests/internal_class_trait_abstract.phpt | 14 -------- 5 files changed, 5 insertions(+), 69 deletions(-) delete mode 100644 ext/zend_test/tests/internal_class_trait_abstract.phpt diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 33bbdec24c1ca..b3cc35396bd38 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -3018,12 +3018,11 @@ ZEND_API void zend_class_use_internal_traits(zend_class_entry *class_entry, int va_list trait_list; zend_class_entry **traits; zend_function *fn; - bool contains_abstract_methods = false; ZEND_ASSERT(class_entry->ce_flags & ZEND_ACC_LINKED); - ZEND_ASSERT(num_traits != 0); + ZEND_ASSERT(num_traits >= 0); - if (num_traits == 0) { + if (UNEXPECTED(num_traits == 0)) { return; } @@ -3046,6 +3045,7 @@ ZEND_API void zend_class_use_internal_traits(zend_class_entry *class_entry, int } va_end(trait_list); + bool contains_abstract_methods = false; zend_do_traits_method_binding(class_entry, traits, NULL, NULL, false, &contains_abstract_methods); zend_do_traits_constant_binding(class_entry, traits); @@ -3058,12 +3058,7 @@ ZEND_API void zend_class_use_internal_traits(zend_class_entry *class_entry, int efree(traits); - /* TODO: Add second binding pass for abstract trait methods if needed in the future. */ - if (contains_abstract_methods) { - zend_error_noreturn(E_COMPILE_ERROR, - "Internal trait binding does not support abstract trait methods in %s", - ZSTR_VAL(class_entry->name)); - } + /* TODO: Verify abstract trait method implementation requirements are enforced. */ } /* }}} */ diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 9e366318df745..6a240780873d7 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -59,7 +59,6 @@ static zend_class_entry *zend_attribute_test_class; static zend_class_entry *zend_test_trait; static zend_class_entry *zend_test_trait_for_internal_class; static zend_class_entry *zend_test_class_with_trait; -static zend_class_entry *zend_test_abstract_trait_for_internal_traits; static zend_class_entry *zend_test_not_a_trait_for_internal_traits; static zend_class_entry *zend_test_internal_traits_driver; static zend_class_entry *zend_test_attribute; @@ -1260,13 +1259,6 @@ static ZEND_FUNCTION(zend_test_use_internal_traits_not_trait) zend_class_use_internal_traits(zend_test_internal_traits_driver, 1, zend_test_not_a_trait_for_internal_traits); } -static ZEND_FUNCTION(zend_test_use_internal_traits_abstract_trait) -{ - ZEND_PARSE_PARAMETERS_NONE(); - - zend_class_use_internal_traits(zend_test_internal_traits_driver, 1, zend_test_abstract_trait_for_internal_traits); -} - static ZEND_METHOD(ZendTestNS_Foo, method) { ZEND_PARSE_PARAMETERS_NONE(); @@ -1570,7 +1562,6 @@ PHP_MINIT_FUNCTION(zend_test) zend_test_trait = register_class__ZendTestTrait(); zend_test_trait_for_internal_class = register_class__ZendTestTraitForInternalClass(); zend_test_class_with_trait = register_class__ZendTestClassWithTrait(zend_test_trait_for_internal_class); - zend_test_abstract_trait_for_internal_traits = register_class__ZendTestAbstractTraitForInternalTraits(); zend_test_not_a_trait_for_internal_traits = register_class__ZendTestNotATraitForInternalTraits(); zend_test_internal_traits_driver = register_class__ZendTestInternalTraitsDriver(); diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index ac769c73d20e2..19e283ff51421 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -49,18 +49,12 @@ class _ZendTestClassWithTrait use _ZendTestTraitForInternalClass; } - trait _ZendTestAbstractTraitForInternalTraits - { - abstract public function abstractMethod(): void; - } - class _ZendTestNotATraitForInternalTraits {} class _ZendTestInternalTraitsDriver {} function zend_test_use_internal_traits_zero(): void {} function zend_test_use_internal_traits_not_trait(): void {} - function zend_test_use_internal_traits_abstract_trait(): void {} /** @alias _ZendTestClassAlias */ class _ZendTestClass implements _ZendTestInterface { diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index e035cc7dfe23b..b20b9b509430f 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,13 +1,11 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 8e7a71a91bffaf61361d183be14f42205b7359da */ + * Stub hash: f98ede068cef2b4a2fe4a423d18fed1748c22b29 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_use_internal_traits_zero, 0, 0, IS_VOID, 0) ZEND_END_ARG_INFO() #define arginfo_zend_test_use_internal_traits_not_trait arginfo_zend_test_use_internal_traits_zero -#define arginfo_zend_test_use_internal_traits_abstract_trait arginfo_zend_test_use_internal_traits_zero - ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_trigger_bailout, 0, 0, IS_NEVER, 0) ZEND_END_ARG_INFO() @@ -223,8 +221,6 @@ ZEND_END_ARG_INFO() #define arginfo_class__ZendTestTraitForInternalClass_traitMethod arginfo_zend_test_nodiscard -#define arginfo_class__ZendTestAbstractTraitForInternalTraits_abstractMethod arginfo_zend_test_use_internal_traits_zero - #define arginfo_class__ZendTestClass_is_object arginfo_zend_test_nodiscard #define arginfo_class__ZendTestClass___toString arginfo_zend_get_current_func_name @@ -296,7 +292,6 @@ ZEND_END_ARG_INFO() static ZEND_FUNCTION(zend_test_use_internal_traits_zero); static ZEND_FUNCTION(zend_test_use_internal_traits_not_trait); -static ZEND_FUNCTION(zend_test_use_internal_traits_abstract_trait); static ZEND_FUNCTION(zend_trigger_bailout); static ZEND_FUNCTION(zend_test_array_return); static ZEND_FUNCTION(zend_test_nullable_array_return); @@ -390,7 +385,6 @@ static ZEND_METHOD(ZendTestNS2_ZendSubNS_Foo, method); static const zend_function_entry ext_functions[] = { ZEND_FE(zend_test_use_internal_traits_zero, arginfo_zend_test_use_internal_traits_zero) ZEND_FE(zend_test_use_internal_traits_not_trait, arginfo_zend_test_use_internal_traits_not_trait) - ZEND_FE(zend_test_use_internal_traits_abstract_trait, arginfo_zend_test_use_internal_traits_abstract_trait) ZEND_FE(zend_trigger_bailout, arginfo_zend_trigger_bailout) ZEND_FE(zend_test_array_return, arginfo_zend_test_array_return) #if (PHP_VERSION_ID >= 80400) @@ -549,15 +543,6 @@ static const zend_function_entry class__ZendTestTraitForInternalClass_methods[] ZEND_FE_END }; -static const zend_function_entry class__ZendTestAbstractTraitForInternalTraits_methods[] = { -#if (PHP_VERSION_ID >= 80400) - ZEND_RAW_FENTRY("abstractMethod", NULL, arginfo_class__ZendTestAbstractTraitForInternalTraits_abstractMethod, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT, NULL, NULL) -#else - ZEND_RAW_FENTRY("abstractMethod", NULL, arginfo_class__ZendTestAbstractTraitForInternalTraits_abstractMethod, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) -#endif - ZEND_FE_END -}; - static const zend_function_entry class__ZendTestClass_methods[] = { ZEND_ME(_ZendTestClass, is_object, arginfo_class__ZendTestClass_is_object, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(_ZendTestClass, __toString, arginfo_class__ZendTestClass___toString, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED) @@ -770,21 +755,6 @@ static zend_class_entry *register_class__ZendTestClassWithTrait(zend_class_entry return class_entry; } -static zend_class_entry *register_class__ZendTestAbstractTraitForInternalTraits(void) -{ - zend_class_entry ce, *class_entry; - - INIT_CLASS_ENTRY(ce, "_ZendTestAbstractTraitForInternalTraits", class__ZendTestAbstractTraitForInternalTraits_methods); -#if (PHP_VERSION_ID >= 80400) - class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_TRAIT); -#else - class_entry = zend_register_internal_class_ex(&ce, NULL); - class_entry->ce_flags |= ZEND_ACC_TRAIT; -#endif - - return class_entry; -} - static zend_class_entry *register_class__ZendTestNotATraitForInternalTraits(void) { zend_class_entry ce, *class_entry; diff --git a/ext/zend_test/tests/internal_class_trait_abstract.phpt b/ext/zend_test/tests/internal_class_trait_abstract.phpt deleted file mode 100644 index 29754cea44868..0000000000000 --- a/ext/zend_test/tests/internal_class_trait_abstract.phpt +++ /dev/null @@ -1,14 +0,0 @@ ---TEST-- -zend_class_use_internal_traits with abstract trait method produces E_COMPILE_ERROR ---EXTENSIONS-- -zend_test ---FILE-- - ---EXPECTF-- -Fatal error: Internal trait binding does not support abstract trait methods in %s in %s on line %d From 2f76222aae0ec8253f19642912c21e1a9a7473af Mon Sep 17 00:00:00 2001 From: Khaled Alam Date: Wed, 21 Jan 2026 23:33:12 +0400 Subject: [PATCH 08/11] feat: add zend_test_use_internal_traits_not_trait to arginfo test list (sweep). --- Zend/tests/arginfo_zpp_mismatch.inc | 1 + ext/zend_test/tests/internal_class_trait_not_trait.phpt | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/tests/arginfo_zpp_mismatch.inc b/Zend/tests/arginfo_zpp_mismatch.inc index 2eb8905f5d4a1..aa362a2fac4ca 100644 --- a/Zend/tests/arginfo_zpp_mismatch.inc +++ b/Zend/tests/arginfo_zpp_mismatch.inc @@ -15,6 +15,7 @@ function skipFunction($function): bool { || $function === 'zend_test_array_return' || $function === 'zend_test_crash' || $function === 'zend_leak_bytes' + || $function === 'zend_test_use_internal_traits_not_trait' /* mess with output */ || (is_string($function) && str_starts_with($function, 'ob_')) || $function === 'output_add_rewrite_var' diff --git a/ext/zend_test/tests/internal_class_trait_not_trait.phpt b/ext/zend_test/tests/internal_class_trait_not_trait.phpt index 5ed5579da0d1a..c5350bd499988 100644 --- a/ext/zend_test/tests/internal_class_trait_not_trait.phpt +++ b/ext/zend_test/tests/internal_class_trait_not_trait.phpt @@ -5,7 +5,6 @@ zend_test --FILE-- Date: Thu, 22 Jan 2026 00:55:21 +0400 Subject: [PATCH 09/11] Generate fresh test_arginfo.h. --- ext/zend_test/test_arginfo.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index 65bce277d6d49..ee7c99ca19d54 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,10 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: 25b63d5be5822cf0b717150dde07625cdd503c24 */ + * Stub hash: f98ede068cef2b4a2fe4a423d18fed1748c22b29 */ + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_use_internal_traits_zero, 0, 0, IS_VOID, 0) +ZEND_END_ARG_INFO() + +#define arginfo_zend_test_use_internal_traits_not_trait arginfo_zend_test_use_internal_traits_zero ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_trigger_bailout, 0, 0, IS_NEVER, 0) ZEND_END_ARG_INFO() From 7f0101b839cf32114cf6371cf9e4306414014f6b Mon Sep 17 00:00:00 2001 From: Khaled Alam Date: Thu, 22 Jan 2026 10:31:52 +0400 Subject: [PATCH 10/11] ci/cd: re trigger ci/cd. --- ext/date/tests/gh13857.phpt | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 ext/date/tests/gh13857.phpt diff --git a/ext/date/tests/gh13857.phpt b/ext/date/tests/gh13857.phpt new file mode 100644 index 0000000000000..8164866616bda --- /dev/null +++ b/ext/date/tests/gh13857.phpt @@ -0,0 +1,30 @@ +--TEST-- +Bug GH-13857 (Unexpected Results with date_diff for Different Timezones) wall-clock dates are inverted. +--FILE-- +setTimeZone(new DateTimeZone('Asia/Tokyo')); +$endDate = (new DateTime('2024-04-01 08:48:22'))->setTimeZone(new DateTimeZone('UTC')); + +echo "nowDate: ", $nowDate->format('Y-m-d H:i:s T'), "\n"; +echo "endDate: ", $endDate->format('Y-m-d H:i:s T'), "\n"; + +$diff = date_diff($nowDate, $endDate); +echo "diff: ", $diff->format('%R %Y-%M-%D %H:%I:%S'), "\n"; +echo "days: ", $diff->days, "\n"; + +$diff2 = date_diff($endDate, $nowDate); +echo "reversed: ", $diff2->format('%R %Y-%M-%D %H:%I:%S'), "\n"; + +$a = new DateTime('2024-04-01 00:49:22', new DateTimeZone('+09:00')); +$b = new DateTime('2024-03-31 23:48:22', new DateTimeZone('+00:00')); +$diff3 = $a->diff($b); +echo "offset TZ: ", $diff3->format('%R %Y-%M-%D %H:%I:%S'), "\n"; +?> +--EXPECT-- +nowDate: 2024-04-01 00:49:22 JST +endDate: 2024-03-31 23:48:22 UTC +diff: + 00-00-00 07:59:00 +days: 0 +reversed: - 00-00-00 07:59:00 +offset TZ: + 00-00-00 07:59:00 From 9783420756a3cdb0b3ccfa0741167f93b56834f4 Mon Sep 17 00:00:00 2001 From: Khaled Alam Date: Thu, 22 Jan 2026 10:32:46 +0400 Subject: [PATCH 11/11] Revert "ci/cd: re trigger ci/cd." This reverts commit 7f0101b839cf32114cf6371cf9e4306414014f6b. --- ext/date/tests/gh13857.phpt | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 ext/date/tests/gh13857.phpt diff --git a/ext/date/tests/gh13857.phpt b/ext/date/tests/gh13857.phpt deleted file mode 100644 index 8164866616bda..0000000000000 --- a/ext/date/tests/gh13857.phpt +++ /dev/null @@ -1,30 +0,0 @@ ---TEST-- -Bug GH-13857 (Unexpected Results with date_diff for Different Timezones) wall-clock dates are inverted. ---FILE-- -setTimeZone(new DateTimeZone('Asia/Tokyo')); -$endDate = (new DateTime('2024-04-01 08:48:22'))->setTimeZone(new DateTimeZone('UTC')); - -echo "nowDate: ", $nowDate->format('Y-m-d H:i:s T'), "\n"; -echo "endDate: ", $endDate->format('Y-m-d H:i:s T'), "\n"; - -$diff = date_diff($nowDate, $endDate); -echo "diff: ", $diff->format('%R %Y-%M-%D %H:%I:%S'), "\n"; -echo "days: ", $diff->days, "\n"; - -$diff2 = date_diff($endDate, $nowDate); -echo "reversed: ", $diff2->format('%R %Y-%M-%D %H:%I:%S'), "\n"; - -$a = new DateTime('2024-04-01 00:49:22', new DateTimeZone('+09:00')); -$b = new DateTime('2024-03-31 23:48:22', new DateTimeZone('+00:00')); -$diff3 = $a->diff($b); -echo "offset TZ: ", $diff3->format('%R %Y-%M-%D %H:%I:%S'), "\n"; -?> ---EXPECT-- -nowDate: 2024-04-01 00:49:22 JST -endDate: 2024-03-31 23:48:22 UTC -diff: + 00-00-00 07:59:00 -days: 0 -reversed: - 00-00-00 07:59:00 -offset TZ: + 00-00-00 07:59:00