From d8f2306542e0e8a9429df600e4fe7cb952e2f1af Mon Sep 17 00:00:00 2001 From: Jorg Sowa Date: Wed, 10 Jun 2026 20:02:12 +0200 Subject: [PATCH] Fix canonical case of magic-method name macros --- .../debug_info/debug_info-error-0.0.phpt | 2 +- Zend/tests/debug_info/debug_info-error-0.phpt | 2 +- .../debug_info/debug_info-error-1.0.phpt | 2 +- Zend/tests/debug_info/debug_info-error-1.phpt | 2 +- .../debug_info-error-case-insensitive.phpt | 20 ++++++++++++++ .../debug_info-error-empty_str.phpt | 2 +- .../debug_info/debug_info-error-false.phpt | 2 +- .../debug_info/debug_info-error-object.phpt | 2 +- .../debug_info/debug_info-error-resource.phpt | 2 +- .../debug_info/debug_info-error-str.phpt | 2 +- .../debug_info/debug_info-error-true.phpt | 2 +- ...matic_implementation_case_insensitive.phpt | 23 ++++++++++++++++ Zend/zend_API.c | 24 ++++++++--------- Zend/zend_compile.c | 4 +-- Zend/zend_compile.h | 16 +++++++++--- Zend/zend_enum.c | 26 +++++++++---------- 16 files changed, 92 insertions(+), 41 deletions(-) create mode 100644 Zend/tests/debug_info/debug_info-error-case-insensitive.phpt create mode 100644 Zend/tests/magic_methods/stringable_automatic_implementation_case_insensitive.phpt diff --git a/Zend/tests/debug_info/debug_info-error-0.0.phpt b/Zend/tests/debug_info/debug_info-error-0.0.phpt index ab41b440fc88..4df25c4c95af 100644 --- a/Zend/tests/debug_info/debug_info-error-0.0.phpt +++ b/Zend/tests/debug_info/debug_info-error-0.0.phpt @@ -17,4 +17,4 @@ $c = new C(0.0); var_dump($c); ?> --EXPECTF-- -Fatal error: __debuginfo() must return an array in %s on line %d +Fatal error: __debugInfo() must return an array in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-0.phpt b/Zend/tests/debug_info/debug_info-error-0.phpt index 37289c6468ff..c0cedf3b88e5 100644 --- a/Zend/tests/debug_info/debug_info-error-0.phpt +++ b/Zend/tests/debug_info/debug_info-error-0.phpt @@ -17,4 +17,4 @@ $c = new C(0); var_dump($c); ?> --EXPECTF-- -Fatal error: __debuginfo() must return an array in %s on line %d +Fatal error: __debugInfo() must return an array in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-1.0.phpt b/Zend/tests/debug_info/debug_info-error-1.0.phpt index 9b168621b5ef..6af945cb0e37 100644 --- a/Zend/tests/debug_info/debug_info-error-1.0.phpt +++ b/Zend/tests/debug_info/debug_info-error-1.0.phpt @@ -17,4 +17,4 @@ $c = new C(1.0); var_dump($c); ?> --EXPECTF-- -Fatal error: __debuginfo() must return an array in %s on line %d +Fatal error: __debugInfo() must return an array in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-1.phpt b/Zend/tests/debug_info/debug_info-error-1.phpt index ae01a6055c00..7cadd485118d 100644 --- a/Zend/tests/debug_info/debug_info-error-1.phpt +++ b/Zend/tests/debug_info/debug_info-error-1.phpt @@ -17,4 +17,4 @@ $c = new C(1); var_dump($c); ?> --EXPECTF-- -Fatal error: __debuginfo() must return an array in %s on line %d +Fatal error: __debugInfo() must return an array in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-case-insensitive.phpt b/Zend/tests/debug_info/debug_info-error-case-insensitive.phpt new file mode 100644 index 000000000000..c2a6f4357a5c --- /dev/null +++ b/Zend/tests/debug_info/debug_info-error-case-insensitive.phpt @@ -0,0 +1,20 @@ +--TEST-- +Testing __debugInfo() magic method declared with non-canonical case +--FILE-- +val; + } + public function __construct($val) { + $this->val = $val; + } +} + +$c = new C(1); +var_dump($c); +?> +--EXPECTF-- +Fatal error: __debugInfo() must return an array in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-empty_str.phpt b/Zend/tests/debug_info/debug_info-error-empty_str.phpt index bbab78cd8202..21611cc9bde5 100644 --- a/Zend/tests/debug_info/debug_info-error-empty_str.phpt +++ b/Zend/tests/debug_info/debug_info-error-empty_str.phpt @@ -17,4 +17,4 @@ $c = new C(""); var_dump($c); ?> --EXPECTF-- -Fatal error: __debuginfo() must return an array in %s on line %d +Fatal error: __debugInfo() must return an array in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-false.phpt b/Zend/tests/debug_info/debug_info-error-false.phpt index 3e48372c420c..fb5a35c2d1e3 100644 --- a/Zend/tests/debug_info/debug_info-error-false.phpt +++ b/Zend/tests/debug_info/debug_info-error-false.phpt @@ -17,4 +17,4 @@ $c = new C(false); var_dump($c); ?> --EXPECTF-- -Fatal error: __debuginfo() must return an array in %s on line %d +Fatal error: __debugInfo() must return an array in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-object.phpt b/Zend/tests/debug_info/debug_info-error-object.phpt index 42e073999908..5cdc6b289e62 100644 --- a/Zend/tests/debug_info/debug_info-error-object.phpt +++ b/Zend/tests/debug_info/debug_info-error-object.phpt @@ -17,4 +17,4 @@ $c = new C(new stdClass); var_dump($c); ?> --EXPECTF-- -Fatal error: __debuginfo() must return an array in %s on line %d +Fatal error: __debugInfo() must return an array in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-resource.phpt b/Zend/tests/debug_info/debug_info-error-resource.phpt index ccacce7a74b4..1fc84fe83d87 100644 --- a/Zend/tests/debug_info/debug_info-error-resource.phpt +++ b/Zend/tests/debug_info/debug_info-error-resource.phpt @@ -19,4 +19,4 @@ $c = new C(fopen("data:text/plain,Foo", 'r')); var_dump($c); ?> --EXPECTF-- -Fatal error: __debuginfo() must return an array in %s on line %d +Fatal error: __debugInfo() must return an array in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-str.phpt b/Zend/tests/debug_info/debug_info-error-str.phpt index 85d3f63b97e0..fe4201de1c9f 100644 --- a/Zend/tests/debug_info/debug_info-error-str.phpt +++ b/Zend/tests/debug_info/debug_info-error-str.phpt @@ -17,4 +17,4 @@ $c = new C("foo"); var_dump($c); ?> --EXPECTF-- -Fatal error: __debuginfo() must return an array in %s on line %d +Fatal error: __debugInfo() must return an array in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-true.phpt b/Zend/tests/debug_info/debug_info-error-true.phpt index 3843c6a7a14a..12bb7329a856 100644 --- a/Zend/tests/debug_info/debug_info-error-true.phpt +++ b/Zend/tests/debug_info/debug_info-error-true.phpt @@ -17,4 +17,4 @@ $c = new C(true); var_dump($c); ?> --EXPECTF-- -Fatal error: __debuginfo() must return an array in %s on line %d +Fatal error: __debugInfo() must return an array in %s on line %d diff --git a/Zend/tests/magic_methods/stringable_automatic_implementation_case_insensitive.phpt b/Zend/tests/magic_methods/stringable_automatic_implementation_case_insensitive.phpt new file mode 100644 index 000000000000..7f4564794452 --- /dev/null +++ b/Zend/tests/magic_methods/stringable_automatic_implementation_case_insensitive.phpt @@ -0,0 +1,23 @@ +--TEST-- +Stringable is automatically implemented for __toString() declared with non-canonical case +--FILE-- +getInterfaceNames()); +var_dump((string) new Test); + +?> +--EXPECT-- +bool(true) +array(1) { + [0]=> + string(10) "Stringable" +} +string(3) "foo" diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 65834adbafff..d1b8b959dde1 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2809,18 +2809,18 @@ ZEND_API void zend_check_magic_method_implementation(const zend_class_entry *ce, zend_check_magic_method_public(ce, fptr); zend_check_magic_method_arg_type(0, ce, fptr, error_type, MAY_BE_STRING); zend_check_magic_method_arg_type(1, ce, fptr, error_type, MAY_BE_ARRAY); - } else if (zend_string_equals_literal(lcname, ZEND_CALLSTATIC_FUNC_NAME)) { + } else if (zend_string_equals_literal(lcname, ZEND_CALLSTATIC_FUNC_LCNAME)) { zend_check_magic_method_args(2, ce, fptr, error_type); zend_check_magic_method_static(ce, fptr, error_type); zend_check_magic_method_public(ce, fptr); zend_check_magic_method_arg_type(0, ce, fptr, error_type, MAY_BE_STRING); zend_check_magic_method_arg_type(1, ce, fptr, error_type, MAY_BE_ARRAY); - } else if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_NAME)) { + } else if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_LCNAME)) { zend_check_magic_method_args(0, ce, fptr, error_type); zend_check_magic_method_non_static(ce, fptr, error_type); zend_check_magic_method_public(ce, fptr); zend_check_magic_method_return_type(ce, fptr, error_type, MAY_BE_STRING); - } else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_NAME)) { + } else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_LCNAME)) { zend_check_magic_method_args(0, ce, fptr, error_type); zend_check_magic_method_non_static(ce, fptr, error_type); zend_check_magic_method_public(ce, fptr); @@ -2829,18 +2829,18 @@ ZEND_API void zend_check_magic_method_implementation(const zend_class_entry *ce, zend_error(E_DEPRECATED, "Returning null from %s::__debugInfo() is deprecated, make the return type non-nullable and return an empty array instead", ZSTR_VAL(ce->name)); } - } else if (zend_string_equals_literal(lcname, "__serialize")) { + } else if (zend_string_equals_literal(lcname, ZEND_SERIALIZE_FUNC_NAME)) { zend_check_magic_method_args(0, ce, fptr, error_type); zend_check_magic_method_non_static(ce, fptr, error_type); zend_check_magic_method_public(ce, fptr); zend_check_magic_method_return_type(ce, fptr, error_type, MAY_BE_ARRAY); - } else if (zend_string_equals_literal(lcname, "__unserialize")) { + } else if (zend_string_equals_literal(lcname, ZEND_UNSERIALIZE_FUNC_NAME)) { zend_check_magic_method_args(1, ce, fptr, error_type); zend_check_magic_method_non_static(ce, fptr, error_type); zend_check_magic_method_public(ce, fptr); zend_check_magic_method_arg_type(0, ce, fptr, error_type, MAY_BE_ARRAY); zend_check_magic_method_return_type(ce, fptr, error_type, MAY_BE_VOID); - } else if (zend_string_equals_literal(lcname, "__set_state")) { + } else if (zend_string_equals_literal(lcname, ZEND_SET_STATE_FUNC_NAME)) { zend_check_magic_method_args(1, ce, fptr, error_type); zend_check_magic_method_static(ce, fptr, error_type); zend_check_magic_method_public(ce, fptr); @@ -2888,16 +2888,16 @@ ZEND_API void zend_add_magic_method(zend_class_entry *ce, zend_function *fptr, c } else if (zend_string_equals_literal(lcname, ZEND_ISSET_FUNC_NAME)) { ce->__isset = fptr; ce->ce_flags |= ZEND_ACC_USE_GUARDS; - } else if (zend_string_equals_literal(lcname, ZEND_CALLSTATIC_FUNC_NAME)) { + } else if (zend_string_equals_literal(lcname, ZEND_CALLSTATIC_FUNC_LCNAME)) { ce->__callstatic = fptr; - } else if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_NAME)) { + } else if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_LCNAME)) { ce->__tostring = fptr; - } else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_NAME)) { + } else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_LCNAME)) { ce->__debugInfo = fptr; ce->ce_flags |= ZEND_ACC_USE_GUARDS; - } else if (zend_string_equals_literal(lcname, "__serialize")) { + } else if (zend_string_equals_literal(lcname, ZEND_SERIALIZE_FUNC_NAME)) { ce->__serialize = fptr; - } else if (zend_string_equals_literal(lcname, "__unserialize")) { + } else if (zend_string_equals_literal(lcname, ZEND_UNSERIALIZE_FUNC_NAME)) { ce->__unserialize = fptr; } } @@ -3111,7 +3111,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend /* If not specified, add __toString() return type for compatibility with Stringable * interface. */ - if (scope && zend_string_equals_literal_ci(internal_function->function_name, "__tostring") && + if (scope && zend_string_equals_literal_ci(internal_function->function_name, ZEND_TOSTRING_FUNC_LCNAME) && !(internal_function->fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) { zend_error(E_CORE_WARNING, "%s::__toString() implemented without string return type", ZSTR_VAL(scope->name)); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 105f99d24171..7e9f7ceac8db 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8610,7 +8610,7 @@ static zend_string *zend_begin_method_decl(zend_op_array *op_array, zend_string } zend_add_magic_method(ce, (zend_function *) op_array, lcname); - if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_NAME) + if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_LCNAME) && !(ce->ce_flags & ZEND_ACC_TRAIT)) { add_stringable_interface(ce); } @@ -8841,7 +8841,7 @@ static zend_op_array *zend_compile_func_decl_ex( } zend_compile_params(params_ast, return_type_ast, - is_method && zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_NAME) ? IS_STRING : 0); + is_method && zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_LCNAME) ? IS_STRING : 0); if (CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR) { zend_mark_function_as_generator(); zend_emit_op(NULL, ZEND_GENERATOR_CREATE, NULL, NULL); diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 0e31332c97f0..2351882a560d 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -368,7 +368,7 @@ typedef struct _zend_oparray_context { #define ZEND_ACC_USES_THIS (1 << 17) /* | X | | */ /* | | | */ /* call through user function trampoline. e.g. | | | */ -/* __call, __callstatic | | | */ +/* __call, __callStatic | | | */ #define ZEND_ACC_CALL_VIA_TRAMPOLINE (1 << 18) /* | X | | */ /* | | | */ /* disable inline caching | | | */ @@ -1249,10 +1249,18 @@ END_EXTERN_C() #define ZEND_UNSET_FUNC_NAME "__unset" #define ZEND_ISSET_FUNC_NAME "__isset" #define ZEND_CALL_FUNC_NAME "__call" -#define ZEND_CALLSTATIC_FUNC_NAME "__callstatic" -#define ZEND_TOSTRING_FUNC_NAME "__tostring" +#define ZEND_CALLSTATIC_FUNC_NAME "__callStatic" +#define ZEND_CALLSTATIC_FUNC_LCNAME "__callstatic" +#define ZEND_TOSTRING_FUNC_NAME "__toString" +#define ZEND_TOSTRING_FUNC_LCNAME "__tostring" #define ZEND_INVOKE_FUNC_NAME "__invoke" -#define ZEND_DEBUGINFO_FUNC_NAME "__debuginfo" +#define ZEND_DEBUGINFO_FUNC_NAME "__debugInfo" +#define ZEND_DEBUGINFO_FUNC_LCNAME "__debuginfo" +#define ZEND_SLEEP_FUNC_NAME "__sleep" +#define ZEND_WAKEUP_FUNC_NAME "__wakeup" +#define ZEND_SERIALIZE_FUNC_NAME "__serialize" +#define ZEND_UNSERIALIZE_FUNC_NAME "__unserialize" +#define ZEND_SET_STATE_FUNC_NAME "__set_state" /* The following constants may be combined in CG(compiler_options) * to change the default compiler behavior */ diff --git a/Zend/zend_enum.c b/Zend/zend_enum.c index a5091f6c1b6f..7f0b58575387 100644 --- a/Zend/zend_enum.c +++ b/Zend/zend_enum.c @@ -92,21 +92,21 @@ static void zend_verify_enum_magic_methods(const zend_class_entry *ce) { // Only __get, __call, __debugInfo and __invoke are allowed - ZEND_ENUM_DISALLOW_MAGIC_METHOD(constructor, "__construct"); - ZEND_ENUM_DISALLOW_MAGIC_METHOD(destructor, "__destruct"); - ZEND_ENUM_DISALLOW_MAGIC_METHOD(clone, "__clone"); - ZEND_ENUM_DISALLOW_MAGIC_METHOD(__get, "__get"); - ZEND_ENUM_DISALLOW_MAGIC_METHOD(__set, "__set"); - ZEND_ENUM_DISALLOW_MAGIC_METHOD(__unset, "__unset"); - ZEND_ENUM_DISALLOW_MAGIC_METHOD(__isset, "__isset"); - ZEND_ENUM_DISALLOW_MAGIC_METHOD(__tostring, "__toString"); - ZEND_ENUM_DISALLOW_MAGIC_METHOD(__serialize, "__serialize"); - ZEND_ENUM_DISALLOW_MAGIC_METHOD(__unserialize, "__unserialize"); + ZEND_ENUM_DISALLOW_MAGIC_METHOD(constructor, ZEND_CONSTRUCTOR_FUNC_NAME); + ZEND_ENUM_DISALLOW_MAGIC_METHOD(destructor, ZEND_DESTRUCTOR_FUNC_NAME); + ZEND_ENUM_DISALLOW_MAGIC_METHOD(clone, ZEND_CLONE_FUNC_NAME); + ZEND_ENUM_DISALLOW_MAGIC_METHOD(__get, ZEND_GET_FUNC_NAME); + ZEND_ENUM_DISALLOW_MAGIC_METHOD(__set, ZEND_SET_FUNC_NAME); + ZEND_ENUM_DISALLOW_MAGIC_METHOD(__unset, ZEND_UNSET_FUNC_NAME); + ZEND_ENUM_DISALLOW_MAGIC_METHOD(__isset, ZEND_ISSET_FUNC_NAME); + ZEND_ENUM_DISALLOW_MAGIC_METHOD(__tostring, ZEND_TOSTRING_FUNC_NAME); + ZEND_ENUM_DISALLOW_MAGIC_METHOD(__serialize, ZEND_SERIALIZE_FUNC_NAME); + ZEND_ENUM_DISALLOW_MAGIC_METHOD(__unserialize, ZEND_UNSERIALIZE_FUNC_NAME); static const char *const forbidden_methods[] = { - "__sleep", - "__wakeup", - "__set_state", + ZEND_SLEEP_FUNC_NAME, + ZEND_WAKEUP_FUNC_NAME, + ZEND_SET_STATE_FUNC_NAME, }; uint32_t forbidden_methods_length = sizeof(forbidden_methods) / sizeof(forbidden_methods[0]);