From 3588d8af129489eda3e3fdb9612b09a4da16dcfd Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sat, 23 Jun 2018 20:51:49 +0200 Subject: [PATCH] Deprecate case-insensitive constants RFC: https://wiki.php.net/rfc/case_insensitive_constant_deprecation --- NEWS | 2 + UPGRADING | 7 + Zend/tests/bug46304.phpt | 15 +++ ...case_insensitive_constant_deprecation.phpt | 127 ++++++++++++++++++ Zend/zend_builtin_functions.c | 9 +- Zend/zend_constants.c | 92 +++++++++++-- Zend/zend_constants.h | 6 +- Zend/zend_vm_def.h | 13 +- Zend/zend_vm_execute.h | 13 +- 9 files changed, 263 insertions(+), 21 deletions(-) create mode 100644 Zend/tests/case_insensitive_constant_deprecation.phpt diff --git a/NEWS b/NEWS index 3fc05e06e3..665b7f9c05 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,8 @@ PHP NEWS arguments). (cmb) . Fixed bug #76392 (Error relocating sapi/cli/php: unsupported relocation type 37). (Peter Kokot) + . The declaration and use of case-insensitive constants has been deprecated. + (Nikita) - Filter: . Added the 'add_slashes' sanitization mode (FILTER_SANITIZE_ADD_SLASHES). diff --git a/UPGRADING b/UPGRADING index 160a1d3549..abd2ca7f20 100644 --- a/UPGRADING +++ b/UPGRADING @@ -258,6 +258,13 @@ FPM: 4. Deprecated Functionality ======================================== +Core: + . The declaration of case-insensitive constants has been deprecate. Passing + true as the third argument to define() will now generate a deprecation + warning. The use of case-insensitive constants with a case that differs from + the declaration is also deprecated. + (RFC: https://wiki.php.net/rfc/case_insensitive_constant_deprecation) + GD: . image2wbmp() has been deprecated. diff --git a/Zend/tests/bug46304.phpt b/Zend/tests/bug46304.phpt index e2e031116d..9898838907 100644 --- a/Zend/tests/bug46304.phpt +++ b/Zend/tests/bug46304.phpt @@ -40,6 +40,11 @@ print ns1\ns2\coNSt6 . "\n"; print NS1\ns2\coNSt1 . "\n"; ?> --EXPECTF-- +Deprecated: define(): Declaration of case-insensitive constants is deprecated in %s on line 6 + +Deprecated: define(): Declaration of case-insensitive constants is deprecated in %s on line 7 + +Deprecated: define(): Declaration of case-insensitive constants is deprecated in %s on line 8 value1 value1 value1 @@ -52,13 +57,23 @@ value3 value4 value4 value4 + +Deprecated: Case-insensitive constants are deprecated. The correct casing for this constant is "NS1\ns2\const4" in %s on line 25 value4 value5 value5 value5 + +Deprecated: Case-insensitive constants are deprecated. The correct casing for this constant is "ns1\ns2\const5" in %s on line 30 value5 + +Deprecated: Case-insensitive constants are deprecated. The correct casing for this constant is "ns1\NS2\coNSt6" in %s on line 32 value6 + +Deprecated: Case-insensitive constants are deprecated. The correct casing for this constant is "ns1\NS2\coNSt6" in %s on line 33 value6 + +Deprecated: Case-insensitive constants are deprecated. The correct casing for this constant is "ns1\NS2\coNSt6" in %s on line 34 value6 value6 diff --git a/Zend/tests/case_insensitive_constant_deprecation.phpt b/Zend/tests/case_insensitive_constant_deprecation.phpt new file mode 100644 index 0000000000..e7a92c8c4c --- /dev/null +++ b/Zend/tests/case_insensitive_constant_deprecation.phpt @@ -0,0 +1,127 @@ +--TEST-- +Case-insensitive constants are deprecated +--FILE-- + +--EXPECTF-- +Deprecated: define(): Declaration of case-insensitive constants is deprecated in %s on line 4 + +Deprecated: define(): Declaration of case-insensitive constants is deprecated in %s on line 5 +int(42) + +Deprecated: Case-insensitive constants are deprecated. The correct casing for this constant is "FOO" in %s on line 8 +int(42) +int(24) +int(24) + +Deprecated: Case-insensitive constants are deprecated. The correct casing for this constant is "NS\FOO" in %s on line 12 +int(24) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +int(42) + +Deprecated: Case-insensitive constants are deprecated. The correct casing for this constant is "FOO" in %s on line 21 +int(42) +int(24) +int(24) + +Deprecated: Case-insensitive constants are deprecated. The correct casing for this constant is "NS\FOO" in %s on line 24 +int(24) +int(24) + +Deprecated: Case-insensitive constants are deprecated. The correct casing for this constant is "NS\FOO" in %s on line 29 +int(24) +int(24) + +Deprecated: Case-insensitive constants are deprecated. The correct casing for this constant is "NS\FOO" in %s on line 34 +int(24) +int(42) + +Deprecated: Case-insensitive constants are deprecated. The correct casing for this constant is "FOO" in %s on line 39 +int(42) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +int(42) + +Deprecated: Case-insensitive constants are deprecated. The correct casing for this constant is "FOO" in %s on line 48 +int(42) +int(24) +int(24) + +Deprecated: Case-insensitive constants are deprecated. The correct casing for this constant is "NS\FOO" in %s on line 51 +int(24) +int(42) + +Deprecated: Case-insensitive constants are deprecated. The correct casing for this constant is "FOO" in %s on line 55 +int(42) +int(43) + +Deprecated: Case-insensitive constants are deprecated. The correct casing for this constant is "FOO" in %s on line 59 +int(43) diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index 57e5d77380..7b6cebff5e 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -854,7 +854,6 @@ ZEND_FUNCTION(define) case_sensitive = 0; } - /* class constant, check if there is name and make sure class is valid & exists */ if (zend_memnstr(ZSTR_VAL(name), "::", sizeof("::") - 1, ZSTR_VAL(name) + ZSTR_LEN(name))) { zend_error(E_WARNING, "Class constants cannot be defined or redefined"); RETURN_FALSE; @@ -905,7 +904,13 @@ repeat: ZVAL_COPY(&c.value, val); zval_ptr_dtor(&val_free); + register_constant: + if (non_cs) { + zend_error(E_DEPRECATED, + "define(): Declaration of case-insensitive constants is deprecated"); + } + c.flags = case_sensitive; /* non persistent */ c.name = zend_string_copy(name); c.module_number = PHP_USER_CONSTANT; @@ -928,7 +933,7 @@ ZEND_FUNCTION(defined) Z_PARAM_STR(name) ZEND_PARSE_PARAMETERS_END(); - if (zend_get_constant_ex(name, zend_get_executed_scope(), ZEND_FETCH_CLASS_SILENT)) { + if (zend_get_constant_ex(name, zend_get_executed_scope(), ZEND_FETCH_CLASS_SILENT | ZEND_GET_CONSTANT_NO_DEPRECATION_CHECK)) { RETURN_TRUE; } else { RETURN_FALSE; diff --git a/Zend/zend_constants.c b/Zend/zend_constants.c index fe1d9e235b..8b68858ad4 100644 --- a/Zend/zend_constants.c +++ b/Zend/zend_constants.c @@ -250,7 +250,7 @@ ZEND_API int zend_verify_const_access(zend_class_constant *c, zend_class_entry * } /* }}} */ -ZEND_API zval *zend_get_constant_str(const char *name, size_t name_len) +static inline zend_constant *zend_get_constant_str_impl(const char *name, size_t name_len) { zend_constant *c; ALLOCA_FLAG(use_heap) @@ -268,10 +268,16 @@ ZEND_API zval *zend_get_constant_str(const char *name, size_t name_len) free_alloca(lcname, use_heap); } + return c; +} + +ZEND_API zval *zend_get_constant_str(const char *name, size_t name_len) +{ + zend_constant *c = zend_get_constant_str_impl(name, name_len); return c ? &c->value : NULL; } -ZEND_API zval *zend_get_constant(zend_string *name) +static inline zend_constant *zend_get_constant_impl(zend_string *name) { zval *zv; zend_constant *c; @@ -291,9 +297,32 @@ ZEND_API zval *zend_get_constant(zend_string *name) c = zend_get_special_constant(ZSTR_VAL(name), ZSTR_LEN(name)); } free_alloca(lcname, use_heap); - return c ? &c->value : NULL; + return c; + } else { + return (zend_constant *) Z_PTR_P(zv); + } +} + +ZEND_API zval *zend_get_constant(zend_string *name) +{ + zend_constant *c = zend_get_constant_impl(name); + return c ? &c->value : NULL; +} + +static zend_bool is_access_deprecated(const zend_constant *c, const char *access_name) { + const char *ns_sep = zend_memrchr(ZSTR_VAL(c->name), '\\', ZSTR_LEN(c->name)); + if (ns_sep) { + /* Namespaces are always case-insensitive. Only compare shortname. */ + size_t shortname_offset = ns_sep - ZSTR_VAL(c->name) + 1; + size_t shortname_len = ZSTR_LEN(c->name) - shortname_offset; + return memcmp( + access_name + shortname_offset, + ZSTR_VAL(c->name) + shortname_offset, + shortname_len + ) != 0; } else { - return &((zend_constant*)Z_PTR_P(zv))->value; + /* No namespace, compare whole name */ + return memcmp(access_name, ZSTR_VAL(c->name), ZSTR_LEN(c->name)) != 0; } } @@ -415,26 +444,45 @@ failure: } } free_alloca(lcname, use_heap); - if (c) { - return &c->value; + + if (!c) { + if (!(flags & IS_CONSTANT_UNQUALIFIED)) { + return NULL; + } + + /* name requires runtime resolution, need to check non-namespaced name */ + c = zend_get_constant_str_impl(constant_name, const_name_len); + name = constant_name; } - /* name requires runtime resolution, need to check non-namespaced name */ - if ((flags & IS_CONSTANT_UNQUALIFIED) != 0) { - return zend_get_constant_str(constant_name, const_name_len); + } else { + if (cname) { + c = zend_get_constant_impl(cname); + } else { + c = zend_get_constant_str_impl(name, name_len); } + } + + if (!c) { return NULL; } - if (cname) { - return zend_get_constant(cname); - } else { - return zend_get_constant_str(name, name_len); + if (!(flags & ZEND_GET_CONSTANT_NO_DEPRECATION_CHECK)) { + if (!(c->flags & (CONST_CS|CONST_CT_SUBST)) && is_access_deprecated(c, name)) { + zend_error(E_DEPRECATED, + "Case-insensitive constants are deprecated. " + "The correct casing for this constant is \"%s\"", + ZSTR_VAL(c->name)); + } } + + return &c->value; } -ZEND_API zend_constant* ZEND_FASTCALL zend_quick_get_constant(const zval *key, uint32_t flags) +ZEND_API zend_constant* ZEND_FASTCALL zend_quick_get_constant( + const zval *key, uint32_t flags, zend_bool *is_deprecated) { zval *zv; + const zval *orig_key = key; zend_constant *c = NULL; zv = zend_hash_find_ex(EG(zend_constants), Z_STR_P(key), 1); @@ -461,6 +509,22 @@ ZEND_API zend_constant* ZEND_FASTCALL zend_quick_get_constant(const zval *key, u } } } + + if (!c) { + return NULL; + } + + if (is_deprecated) { + if (c->flags & (CONST_CS|CONST_CT_SUBST)) { + /* Constant is case-sensitive or true/false/null */ + *is_deprecated = 0; + } else { + zend_bool ns_fallback = key >= orig_key + 2; + const zval *access_key = ns_fallback ? orig_key + 2 : orig_key - 1; + *is_deprecated = is_access_deprecated(c, Z_STRVAL_P(access_key)); + } + } + return c; } diff --git a/Zend/zend_constants.h b/Zend/zend_constants.h index 98484debe2..e06d91ee14 100644 --- a/Zend/zend_constants.h +++ b/Zend/zend_constants.h @@ -31,6 +31,9 @@ #define PHP_USER_CONSTANT INT_MAX /* a constant defined in user space */ +/* Flag for zend_get_constant_ex(). Must not class with ZEND_FETCH_CLASS_* flags. */ +#define ZEND_GET_CONSTANT_NO_DEPRECATION_CHECK 0x1000 + typedef struct _zend_constant { zval value; zend_string *name; @@ -79,7 +82,8 @@ ZEND_API int zend_register_constant(zend_constant *c); #ifdef ZTS void zend_copy_constants(HashTable *target, HashTable *sourc); #endif -ZEND_API zend_constant* ZEND_FASTCALL zend_quick_get_constant(const zval *key, uint32_t flags); +ZEND_API zend_constant* ZEND_FASTCALL zend_quick_get_constant( + const zval *key, uint32_t flags, zend_bool *is_deprecated); END_EXTERN_C() #define ZEND_CONSTANT_DTOR free_zend_constant diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 898bb1229a..3d0fdc0182 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -5045,11 +5045,12 @@ ZEND_VM_HANDLER(99, ZEND_FETCH_CONSTANT, UNUSED|CONST_FETCH, CONST, CACHE_SLOT) { USE_OPLINE zend_constant *c; + zend_bool is_deprecated; c = CACHED_PTR(opline->extended_value); if (EXPECTED(c != NULL) && EXPECTED(!IS_SPECIAL_CACHE_VAL(c))) { /* pass */ - } else if (UNEXPECTED((c = zend_quick_get_constant(RT_CONSTANT(opline, opline->op2) + 1, opline->op1.num)) == NULL)) { + } else if (UNEXPECTED((c = zend_quick_get_constant(RT_CONSTANT(opline, opline->op2) + 1, opline->op1.num, &is_deprecated)) == NULL)) { SAVE_OPLINE(); if ((opline->op1.num & IS_CONSTANT_UNQUALIFIED) != 0) { @@ -5070,6 +5071,14 @@ ZEND_VM_HANDLER(99, ZEND_FETCH_CONSTANT, UNUSED|CONST_FETCH, CONST, CACHE_SLOT) ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } + } else if (is_deprecated) { + SAVE_OPLINE(); + zend_error(E_DEPRECATED, + "Case-insensitive constants are deprecated. " + "The correct casing for this constant is \"%s\"", + ZSTR_VAL(c->name)); + ZVAL_COPY_OR_DUP(EX_VAR(opline->result.var), &c->value); + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } else { CACHE_PTR(opline->extended_value, c); } @@ -7611,7 +7620,7 @@ ZEND_VM_HANDLER(122, ZEND_DEFINED, CONST, ANY, CACHE_SLOT) break; } } - if ((c = zend_quick_get_constant(RT_CONSTANT(opline, opline->op1), 0)) == NULL) { + if ((c = zend_quick_get_constant(RT_CONSTANT(opline, opline->op1), 0, NULL)) == NULL) { CACHE_PTR(opline->extended_value, ENCODE_SPECIAL_CACHE_NUM(zend_hash_num_elements(EG(zend_constants)))); result = 0; } else { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 499838f1cc..c5598d6da7 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3883,7 +3883,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DEFINED_SPEC_CONST_HANDLER(ZEN break; } } - if ((c = zend_quick_get_constant(RT_CONSTANT(opline, opline->op1), 0)) == NULL) { + if ((c = zend_quick_get_constant(RT_CONSTANT(opline, opline->op1), 0, NULL)) == NULL) { CACHE_PTR(opline->extended_value, ENCODE_SPECIAL_CACHE_NUM(zend_hash_num_elements(EG(zend_constants)))); result = 0; } else { @@ -31965,11 +31965,12 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CONSTANT_SPEC_UNUSED_CON { USE_OPLINE zend_constant *c; + zend_bool is_deprecated; c = CACHED_PTR(opline->extended_value); if (EXPECTED(c != NULL) && EXPECTED(!IS_SPECIAL_CACHE_VAL(c))) { /* pass */ - } else if (UNEXPECTED((c = zend_quick_get_constant(RT_CONSTANT(opline, opline->op2) + 1, opline->op1.num)) == NULL)) { + } else if (UNEXPECTED((c = zend_quick_get_constant(RT_CONSTANT(opline, opline->op2) + 1, opline->op1.num, &is_deprecated)) == NULL)) { SAVE_OPLINE(); if ((opline->op1.num & IS_CONSTANT_UNQUALIFIED) != 0) { @@ -31990,6 +31991,14 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CONSTANT_SPEC_UNUSED_CON ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } + } else if (is_deprecated) { + SAVE_OPLINE(); + zend_error(E_DEPRECATED, + "Case-insensitive constants are deprecated. " + "The correct casing for this constant is \"%s\"", + ZSTR_VAL(c->name)); + ZVAL_COPY_OR_DUP(EX_VAR(opline->result.var), &c->value); + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } else { CACHE_PTR(opline->extended_value, c); } -- 2.40.0