Skip to content

Commit 009b5e5

Browse files
committed
Store class names in canonical case in the class table
1 parent de9472d commit 009b5e5

1,148 files changed

Lines changed: 4546 additions & 4395 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

NEWS

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@ PHP NEWS
33
?? ??? ????, PHP 8.6.0alpha1
44

55
- Core:
6+
. PHP is now case-sensitive for function and class names. Calling a function
7+
using incorrect casing (e.g. STRLEN() instead of strlen()) now raises a
8+
fatal Error. (jorgsowa)
9+
. Class, interface, trait, and enum names are now stored and looked up using
10+
their canonical casing. Referencing them with incorrect casing now raises a
11+
fatal Error across all language constructs and APIs that accept a class
12+
name. (jorgsowa)
13+
. Namespace segments in references and use imports are now matched
14+
case-sensitively. Using incorrect casing raises a fatal Error. (jorgsowa)
615
. Added first-class callable cache to share instances for the duration of the
716
request. (ilutov)
817
. It is now possible to use reference assign on WeakMap without the key

UPGRADING

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,58 @@ PHP 8.6 UPGRADE NOTES
1919
1. Backward Incompatible Changes
2020
========================================
2121

22+
- Core:
23+
. PHP is now case-sensitive for function and class names. The global function
24+
table and class table are now keyed by the canonical (declared) name rather
25+
than its lowercase equivalent. As a result, calling a function with
26+
incorrect casing (e.g. STRLEN() instead of strlen()) now raises a fatal
27+
Error ("Call to undefined function STRLEN()"), and referencing a class,
28+
interface, trait, or enum with incorrect casing (e.g. new FOO() when the
29+
class is declared as Foo) now raises a fatal Error ("Class 'FOO' not
30+
found"). This applies to all language constructs and APIs that perform a
31+
name lookup: new, instanceof, catch, extends, implements, use (trait), type
32+
declarations, callable strings/arrays, class_exists() and related
33+
introspection functions, Closure::bind()/bindTo(), Reflection constructors
34+
and methods, and the SOAP classmap option.
35+
RFC: https://wiki.php.net/rfc/case_sensitive_php
36+
. Namespace segments in use imports are now matched case-sensitively. A use
37+
import whose path does not match the canonical casing of the target
38+
namespace segment will produce a fatal Error when the imported name is
39+
first resolved.
40+
RFC: https://wiki.php.net/rfc/case_sensitive_php
41+
. Declaring a magic method with incorrect casing (e.g. __tostring() instead
42+
of __toString()) is now a compile-time fatal Error. Previously magic
43+
method names were matched case-insensitively; under case-sensitive method
44+
names a wrong-cased declaration would otherwise silently lose its magic
45+
behavior.
46+
RFC: https://wiki.php.net/rfc/case_sensitive_php
47+
. Attribute names are now matched case-sensitively, consistent with class
48+
names. Internal attributes such as #[\Deprecated], #[\Override] or
49+
#[\AllowDynamicProperties] are only recognized with their exact casing,
50+
and the name filter of ReflectionFunctionAbstract::getAttributes() and
51+
related methods compares case-sensitively. The "self", "parent" and
52+
"static" keywords in callable strings remain case-insensitive.
53+
RFC: https://wiki.php.net/rfc/case_sensitive_php
54+
. The namespace portion of constant names is now case-sensitive, matching
55+
the constant base name. Referencing a namespaced constant with incorrect
56+
namespace casing (e.g. myapp\FOO when defined as MyApp\FOO) now raises
57+
an "Undefined constant" Error, including via constant() and defined().
58+
RFC: https://wiki.php.net/rfc/case_sensitive_php
59+
60+
- SOAP:
61+
. WSDL operation names are now matched case-sensitively when dispatching
62+
requests and when calling operations through SoapClient. This matches
63+
XML, where element names are case-sensitive. SoapServer::addFunction()
64+
also requires the exact case of the registered PHP function.
65+
RFC: https://wiki.php.net/rfc/case_sensitive_php
66+
67+
- SPL:
68+
. The default spl_autoload() implementation now builds the include file
69+
name from the case-preserved class name instead of its lowercased form.
70+
On case-sensitive file systems the file name must match the class name
71+
casing (e.g. MyClass.php instead of myclass.php).
72+
RFC: https://wiki.php.net/rfc/case_sensitive_php
73+
2274
- DOM:
2375
. Properties previously documented as @readonly (e.g. DOMNode::$nodeType,
2476
DOMDocument::$xmlEncoding, DOMEntity::$actualEncoding, ::$encoding,

UPGRADING.INTERNALS

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,24 @@ PHP 8.6 INTERNALS UPGRADE NOTES
1414
1. Internal API changes
1515
========================
1616

17+
. Function, class, and method tables (EG(function_table), EG(class_table),
18+
zend_class_entry.function_table) are now keyed by the canonical (declared)
19+
name instead of its lowercase form, and lookups are case-sensitive.
20+
Extensions performing lowercased lookups in these tables must pass the
21+
exact name instead. Related changes:
22+
- zend_class_name lost its lc_name field and zend_attribute lost its
23+
lcname field.
24+
- zend_get_attribute_str() and zend_get_parameter_attribute_str() now
25+
match attribute names case-sensitively; pass the exact attribute name
26+
instead of a lowercased one.
27+
- The "key" argument of the get_method and get_static_method object
28+
handlers is no longer a pre-lowercased copy of the method name; when
29+
non-NULL it holds the same string as the method name itself (with a
30+
precomputed hash).
31+
- zend_perform_class_autoload() lost its lowercase-name parameter;
32+
autoloaders receive the case-preserved class name.
33+
- do_bind_class() and zend_bind_class_in_slot() changed signatures
34+
(the separate lowercase-name literal no longer exists).
1735
. ZSTR_INIT_LITERAL(), zend_string_starts_with_literal(), and
1836
zend_string_starts_with_literal_ci() now support strings containing NUL
1937
bytes. Passing non-literal char* is no longer supported.

Zend/Optimizer/compact_literals.c

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -134,43 +134,43 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
134134
LITERAL_INFO(opline->op1.constant, 1);
135135
break;
136136
case ZEND_INIT_FCALL_BY_NAME:
137-
LITERAL_INFO(opline->op2.constant, 2);
137+
LITERAL_INFO(opline->op2.constant, 1);
138138
break;
139139
case ZEND_INIT_NS_FCALL_BY_NAME:
140-
LITERAL_INFO(opline->op2.constant, 3);
140+
LITERAL_INFO(opline->op2.constant, 2);
141141
break;
142142
case ZEND_INIT_METHOD_CALL:
143143
if (opline->op1_type == IS_CONST) {
144144
LITERAL_INFO(opline->op1.constant, 1);
145145
}
146146
if (opline->op2_type == IS_CONST) {
147-
LITERAL_INFO(opline->op2.constant, 2);
147+
LITERAL_INFO(opline->op2.constant, 1);
148148
}
149149
break;
150150
case ZEND_INIT_STATIC_METHOD_CALL:
151151
if (opline->op1_type == IS_CONST) {
152-
LITERAL_INFO(opline->op1.constant, 2);
152+
LITERAL_INFO(opline->op1.constant, 1);
153153
}
154154
if (opline->op2_type == IS_CONST) {
155-
LITERAL_INFO(opline->op2.constant, 2);
155+
LITERAL_INFO(opline->op2.constant, 1);
156156
}
157157
break;
158158
case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL:
159159
LITERAL_INFO(opline->op1.constant, 1);
160160
break;
161161
case ZEND_CATCH:
162-
LITERAL_INFO(opline->op1.constant, 2);
162+
LITERAL_INFO(opline->op1.constant, 1);
163163
break;
164164
case ZEND_FETCH_CONSTANT:
165165
if (opline->op1.num & IS_CONSTANT_UNQUALIFIED_IN_NAMESPACE) {
166-
LITERAL_INFO(opline->op2.constant, 3);
167-
} else {
168166
LITERAL_INFO(opline->op2.constant, 2);
167+
} else {
168+
LITERAL_INFO(opline->op2.constant, 1);
169169
}
170170
break;
171171
case ZEND_FETCH_CLASS_CONSTANT:
172172
if (opline->op1_type == IS_CONST) {
173-
LITERAL_INFO(opline->op1.constant, 2);
173+
LITERAL_INFO(opline->op1.constant, 1);
174174
}
175175
if (opline->op2_type == IS_CONST) {
176176
LITERAL_INFO(opline->op2.constant, 1);
@@ -192,7 +192,7 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
192192
case ZEND_POST_DEC_STATIC_PROP:
193193
case ZEND_ASSIGN_STATIC_PROP_OP:
194194
if (opline->op2_type == IS_CONST) {
195-
LITERAL_INFO(opline->op2.constant, 2);
195+
LITERAL_INFO(opline->op2.constant, 1);
196196
}
197197
if (opline->op1_type == IS_CONST) {
198198
LITERAL_INFO(opline->op1.constant, 1);
@@ -201,12 +201,12 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
201201
case ZEND_FETCH_CLASS:
202202
case ZEND_INSTANCEOF:
203203
if (opline->op2_type == IS_CONST) {
204-
LITERAL_INFO(opline->op2.constant, 2);
204+
LITERAL_INFO(opline->op2.constant, 1);
205205
}
206206
break;
207207
case ZEND_NEW:
208208
if (opline->op1_type == IS_CONST) {
209-
LITERAL_INFO(opline->op1.constant, 2);
209+
LITERAL_INFO(opline->op1.constant, 1);
210210
}
211211
break;
212212
case ZEND_DECLARE_CLASS:

Zend/Optimizer/dfa_pass.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -277,9 +277,7 @@ static inline bool can_elide_list_type(
277277
return can_elide_list_type(script, op_array, use_info, *single_type);
278278
}
279279
if (ZEND_TYPE_HAS_NAME(*single_type)) {
280-
zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(*single_type));
281-
const zend_class_entry *ce = zend_optimizer_get_class_entry(script, op_array, lcname);
282-
zend_string_release(lcname);
280+
const zend_class_entry *ce = zend_optimizer_get_class_entry(script, op_array, ZEND_TYPE_NAME(*single_type));
283281
bool result = ce && safe_instanceof(use_info->ce, ce);
284282
if (result == !is_intersection) {
285283
return result;
@@ -410,7 +408,7 @@ static uint32_t zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa)
410408
if ((op->opcode == ZEND_FRAMELESS_ICALL_2
411409
|| (op->opcode == ZEND_FRAMELESS_ICALL_3 && (op + 1)->op1_type == IS_CONST))
412410
&& call_info->callee_func
413-
&& zend_string_equals_literal_ci(call_info->callee_func->common.function_name, "in_array")) {
411+
&& zend_string_equals_literal(call_info->callee_func->common.function_name, "in_array")) {
414412
bool strict = false;
415413
bool has_opdata = op->opcode == ZEND_FRAMELESS_ICALL_3;
416414
ZEND_ASSERT(!call_info->is_prototype);

Zend/Optimizer/optimize_func_calls.c

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,14 +200,12 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
200200
} else if (fcall->opcode == ZEND_INIT_FCALL_BY_NAME) {
201201
fcall->opcode = ZEND_INIT_FCALL;
202202
fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func);
203-
literal_dtor(&ZEND_OP2_LITERAL(fcall));
204-
fcall->op2.constant = fcall->op2.constant + 1;
205203
} else if (fcall->opcode == ZEND_INIT_NS_FCALL_BY_NAME) {
206204
fcall->opcode = ZEND_INIT_FCALL;
207205
fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func);
208-
literal_dtor(&op_array->literals[fcall->op2.constant]);
209-
literal_dtor(&op_array->literals[fcall->op2.constant + 2]);
210-
fcall->op2.constant = fcall->op2.constant + 1;
206+
/* The func was resolved by the ns-qualified name (slot 0);
207+
* drop the unqualified fallback (slot 1). */
208+
literal_dtor(&op_array->literals[fcall->op2.constant + 1]);
211209
} else if (fcall->opcode == ZEND_INIT_STATIC_METHOD_CALL
212210
|| fcall->opcode == ZEND_INIT_METHOD_CALL
213211
|| fcall->opcode == ZEND_INIT_PARENT_PROPERTY_HOOK_CALL

Zend/Optimizer/pass1.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx)
220220
}
221221

222222
/* define("name", scalar); */
223-
if (zend_string_equals_literal_ci(Z_STR(ZEND_OP2_LITERAL(init_opline)), "define")) {
223+
if (zend_string_equals_literal(Z_STR(ZEND_OP2_LITERAL(init_opline)), "define")) {
224224

225225
if (Z_TYPE(ZEND_OP1_LITERAL(send1_opline)) == IS_STRING && send2_opline) {
226226

Zend/Optimizer/zend_cfg.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,8 +333,8 @@ ZEND_API void zend_build_cfg(zend_arena **arena, const zend_op_array *op_array,
333333
case ZEND_INIT_NS_FCALL_BY_NAME:
334334
zv = CRT_CONSTANT(opline->op2);
335335
if (opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME) {
336-
/* The third literal is the lowercased unqualified name */
337-
zv += 2;
336+
/* The second literal is the unqualified fallback name */
337+
zv += 1;
338338
}
339339
if ((fn = zend_hash_find_ptr(EG(function_table), Z_STR_P(zv))) != NULL) {
340340
if (fn->type == ZEND_INTERNAL_FUNCTION) {

Zend/Optimizer/zend_inference.c

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2387,10 +2387,8 @@ static uint32_t zend_convert_type(const zend_script *script, zend_type type, zen
23872387
/* As we only have space to store one CE,
23882388
* we use a plain object type for class unions. */
23892389
if (ZEND_TYPE_HAS_NAME(type)) {
2390-
zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(type));
23912390
// TODO: Pass through op_array.
2392-
*pce = zend_optimizer_get_class_entry(script, NULL, lcname);
2393-
zend_string_release_ex(lcname, 0);
2391+
*pce = zend_optimizer_get_class_entry(script, NULL, ZEND_TYPE_NAME(type));
23942392
}
23952393
}
23962394
}
@@ -2477,7 +2475,7 @@ static const zend_property_info *zend_fetch_static_prop_info(const zend_script *
24772475
}
24782476
} else if (opline->op2_type == IS_CONST) {
24792477
const zval *zv = CRT_CONSTANT(opline->op2);
2480-
ce = zend_optimizer_get_class_entry(script, op_array, Z_STR_P(zv + 1));
2478+
ce = zend_optimizer_get_class_entry(script, op_array, Z_STR_P(zv));
24812479
}
24822480

24832481
if (ce) {
@@ -3369,7 +3367,7 @@ static zend_always_inline zend_result _zend_update_type_info(
33693367
} else if (opline->op2_type == IS_CONST) {
33703368
zval *zv = CRT_CONSTANT(opline->op2);
33713369
if (Z_TYPE_P(zv) == IS_STRING) {
3372-
ce = zend_optimizer_get_class_entry(script, op_array, Z_STR_P(zv+1));
3370+
ce = zend_optimizer_get_class_entry(script, op_array, Z_STR_P(zv));
33733371
UPDATE_SSA_OBJ_TYPE(ce, 0, ssa_op->result_def);
33743372
} else {
33753373
UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);

0 commit comments

Comments
 (0)