10000 Clear recorded errors before executing shutdown functions · php/php-src@da6afe0 · GitHub
[go: up one dir, main page]

Skip to content

Commit da6afe0

Browse files
committed
Clear recorded errors before executing shutdown functions
Recorded errors may be attached to the wrong cached script when a fatal error occurs during recording. This happens because the fatal error will cause a bailout, which may prevent the recorded errors from being freed. If an other script is compiled after bailout, or if a class is linked after bailout, the recorded errors will be attached to it. This change fixes this by freeing recorded errors before executing shutdown functions. Fixes GH-8063
1 parent 15ee285 commit da6afe0

File tree

9 files changed

+239
-94
lines changed

9 files changed

+239
-94
lines changed

Zend/zend_inheritance.c

Lines changed: 110 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -2775,101 +2775,110 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string
27752775
#endif
27762776

27772777
bool orig_record_errors = EG(record_errors);
2778-
if (ce->ce_flags & ZEND_ACC_IMMUTABLE) {
2779-
if (is_cacheable) {
2780-
if (zend_inheritance_cache_get && zend_inheritance_cache_add) {
2781-
zend_class_entry *ret = zend_inheritance_cache_get(ce, parent, traits_and_interfaces);
2782-
if (ret) {
2783-
if (traits_and_interfaces) {
2784-
free_alloca(traits_and_interfaces, use_heap);
2785-
}
2786-
zv = zend_hash_find_known_hash(CG(class_table), key);
2787-
Z_CE_P(zv) = ret;
2788-
return ret;
2789-
}
27902778

2791-
/* Make sure warnings (such as deprecations) thrown during inheritance
2792-
* will be recoreded in the inheritance cache. */
2793-
zend_begin_record_errors();
2794-
} else {
2795-
is_cacheable = 0;
2779+
if (ce->ce_flags & ZEND_ACC_IMMUTABLE && is_cacheable) {
2780+
if (zend_inheritance_cache_get && zend_inheritance_cache_add) {
2781+
zend_class_entry *ret = zend_inheritance_cache_get(ce, parent, traits_and_interfaces);
2782+
if (ret) {
2783+
if (traits_and_interfaces) {
2784+
free_alloca(traits_and_interfaces, use_heap);
2785+
}
2786+
zv = zend_hash_find_known_hash(CG(class_table), key);
2787+
Z_CE_P(zv) = ret;
2788+
return ret;
27962789
}
2797-
proto = ce;
2790+
2791+
/* Make sure warnings (such as deprecations) thrown during inheritance
2792+
* will be recorded in the inheritance cache. */
2793+
zend_begin_record_errors();
2794+
} else {
2795+
is_cacheable = 0;
27982796
}
2799-
/* Lazy class loading */
2800-
ce = zend_lazy_class_load(ce);
2801-
zv = zend_hash_find_known_hash(CG(class_table), key);
2802-
Z_CE_P(zv) = ce;
2803-
} else if (ce->ce_flags & ZEND_ACC_FILE_CACHED) {
2804-
/* Lazy class loading */
2805-
ce = zend_lazy_class_load(ce);
2806-
ce->ce_flags &= ~ZEND_ACC_FILE_CACHED;
2807-
zv = zend_hash_find_known_hash(CG(class_table), key);
2808-
Z_CE_P(zv) = ce;
2797+
proto = ce;
28092798
}
28102799

2811-
if (CG(unlinked_uses)) {
2812-
zend_hash_index_del(CG(unlinked_uses), (zend_long)(zend_uintptr_t) ce);
2813-
}
2800+
zend_try {
2801+
if (ce->ce_flags & ZEND_ACC_IMMUTABLE) {
2802+
/* Lazy class loading */
2803+
ce = zend_lazy_class_load(ce);
2804+
zv = zend_hash_find_known_hash(CG(class_table), key);
2805+
Z_CE_P(zv) = ce;
2806+
} else if (ce->ce_flags & ZEND_ACC_FILE_CACHED) {
2807+
/* Lazy class loading */
2808+
ce = zend_lazy_class_load(ce);
2809+
ce->ce_flags &= ~ZEND_ACC_FILE_CACHED;
2810+
zv = zend_hash_find_known_hash(CG(class_table), key);
2811+
Z_CE_P(zv) = ce;
2812+
}
28142813

2815-
orig_linking_class = CG(current_linking_class);
2816-
CG(current_linking_class) = is_cacheable ? ce : NULL;
2814+
if (CG(unlinked_uses)) {
2815+
zend_hash_index_del(CG(unlinked_uses), (zend_long)(zend_uintptr_t) ce);
2816+
}
28172817

2818-
if (ce->ce_flags & ZEND_ACC_ENUM) {
2819-
/* Only register builtin enum methods during inheritance to avoid persisting them in
2820-
* opcache. */
2821-
zend_enum_register_funcs(ce);
2822-
}
2818+
orig_linking_class = CG(current_linking_class);
2819+
CG(current_linking_class) = is_cacheable ? ce : NULL;
28232820

2824-
if (parent) {
2825-
if (!(parent->ce_flags & ZEND_ACC_LINKED)) {
2826-
add_dependency_obligation(ce, parent);
2821+
if (ce->ce_flags & ZEND_ACC_ENUM) {
2822+
/* Only register builtin enum methods during inheritance to avoid persisting them in
2823+
* opcache. */
2824+
zend_enum_register_funcs(ce);
28272825
}
2828-
zend_do_inheritance(ce, parent);
2829-
}
2830-
if (ce->num_traits) {
2831-
zend_do_bind_traits(ce, traits_and_interfaces);
2832-
}
2833-
if (ce->num_interfaces) {
2834-
/* Also copy the parent interfaces here, so we don't need to reallocate later. */
2835-
uint32_t num_parent_interfaces = parent ? parent->num_interfaces : 0;
2836-
zend_class_entry **interfaces = emalloc(
2837-
sizeof(zend_class_entry *) * (ce->num_interfaces + num_parent_interfaces));
28382826

2839-
if (num_parent_interfaces) {
2840-
memcpy(interfaces, parent->interfaces,
2841-
sizeof(zend_class_entry *) * num_parent_interfaces);
2827+
if (parent) {
2828+
if (!(parent->ce_flags & ZEND_ACC_LINKED)) {
2829+
add_dependency_obligation(ce, parent);
2830+
}
2831+
zend_do_inheritance(ce, parent);
2832+
}
2833+
if (ce->num_traits) {
2834+
zend_do_bind_traits(ce, traits_and_interfaces);
28422835
}
2843-
memcpy(interfaces + num_parent_interfaces, traits_and_interfaces + ce->num_traits,
2844-
sizeof(zend_class_entry *) * ce->num_interfaces);
2836+
if (ce->num_interfaces) {
2837+
/* Also copy the parent interfaces here, so we don't need to reallocate later. */
2838+
uint32_t num_parent_interfaces = parent ? parent->num_interfaces : 0;
2839+
zend_class_entry **interfaces = emalloc(
2840+
sizeof(zend_class_entry *) * (ce->num_interfaces + num_parent_interfaces));
28452841

2846-
zend_do_implement_interfaces(ce, interfaces);
2847-
} else if (parent && parent->num_interfaces) {
2848-
zend_do_inherit_interfaces(ce, parent);
2849-
}
2850-
if (!(ce->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT))
2851-
&& (ce->ce_flags & (ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS))
2852-
) {
2853-
zend_verify_abstract_class(ce);
2854-
}
2855-
if (ce->ce_flags & ZEND_ACC_ENUM) {
2856-
zend_verify_enum(ce);
2857-
}
2842+
if (num_parent_interfaces) {
2843+
memcpy(interfaces, parent->interfaces,
2844+
sizeof(zend_class_entry *) * num_parent_interfaces);
2845+
}
2846+
memcpy(interfaces + num_parent_interfaces, traits_and_interfaces + ce->num_traits,
2847+
sizeof(zend_class_entry *) * ce->num_interfaces);
2848+
2849+
zend_do_implement_interfaces(ce, interfaces);
2850+
} else if (parent && parent->num_interfaces) {
2851+
zend_do_inherit_interfaces(ce, parent);
2852+
}
2853+
if (!(ce->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT))
2854+
&& (ce->ce_flags & (ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS))
2855+
) {
2856+
zend_verify_abstract_class(ce);
2857+
}
2858+
if (ce->ce_flags & ZEND_ACC_ENUM) {
2859+
zend_verify_enum(ce);
2860+
}
28582861

2859-
/* Normally Stringable is added during compilation. However, if it is imported from a trait,
2860-
* we need to explicilty add the interface here. */
2861-
if (ce->__tostring && !(ce->ce_flags & ZEND_ACC_TRAIT)
2862+
/* Normally Stringable is added during compilation. However, if it is imported from a trait,
2863+
* we need to explicilty add the interface here. */
2864+
if (ce->__tostring && !(ce->ce_flags & ZEND_ACC_TRAIT)
28622865
&& !zend_class_implements_interface(ce, zend_ce_stringable)) {
2863-
ZEND_ASSERT(ce->__tostring->common.fn_flags & ZEND_ACC_TRAIT_CLONE);
2864-
ce->ce_flags |= ZEND_ACC_RESOLVED_INTERFACES;
2865-
ce->num_interfaces++;
2866-
ce->interfaces = perealloc(ce->interfaces,
2867-
sizeof(zend_class_entry *) * ce->num_interfaces, ce->type == ZEND_INTERNAL_CLASS);
2868-
ce->interfaces[ce->num_interfaces - 1] = zend_ce_stringable;
2869-
do_interface_implementation(ce, zend_ce_stringable);
2870-
}
2866+
ZEND_ASSERT(ce->__tostring->common.fn_flags & ZEND_ACC_TRAIT_CLONE);
2867+
ce->ce_flags |= ZEND_ACC_RESOLVED_INTERFACES;
2868+
ce->num_interfaces++;
2869+
ce->interfaces = perealloc(ce->interfaces,
2870+
sizeof(zend_class_entry *) * ce->num_interfaces, ce->type == ZEND_INTERNAL_CLASS);
2871+
ce->interfaces[ce->num_interfaces - 1] = zend_ce_stringable;
2872+
do_interface_implementation(ce, zend_ce_stringable);
2873+
}
2874+
2875+
zend_build_properties_info_table(ce);
2876+
} zend_catch {
2877+
EG(record_errors) = false;
2878+
zend_free_recorded_errors();
2879+
zend_bailout();
2880+
} zend_end_try();
28712881

2872-
zend_build_properties_info_table(ce);
28732882
EG(record_errors) = orig_record_errors;
28742883

28752884
if (!(ce->ce_flags & ZEND_ACC_UNRESOLVED_VARIANCE)) {
@@ -3038,22 +3047,29 @@ zend_class_entry *zend_try_early_bind(zend_class_entry *ce, zend_class_entry *pa
30383047
orig_linking_class = CG(current_linking_class);
30393048
CG(current_linking_class) = is_cacheable ? ce : NULL;
30403049

3041-
if (is_cacheable) {
3042-
zend_begin_record_errors();
3043-
}
3050+
zend_try{
3051+
if (is_cacheable) {
3052+
zend_begin_record_errors();
3053+
}
30443054

3045-
zend_do_inheritance_ex(ce, parent_ce, status == INHERITANCE_SUCCESS);
3046-
if (parent_ce && parent_ce->num_interfaces) {
3047-
zend_do_inherit_interfaces(ce, parent_ce);
3048-
}
3049-
zend_build_properties_info_table(ce);
3050-
if ((ce->ce_flags & (ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) == ZEND_ACC_IMPLICIT_ABSTRACT_CLASS) {
3051-
zend_verify_abstract_class(ce);
3052-
}
3053-
ZEND_ASSERT(!(ce->ce_flags & ZEND_ACC_UNRESOLVED_VARIANCE));
3054-
ce->ce_flags |= ZEND_ACC_LINKED;
3055+
zend_do_inheritance_ex(ce, parent_ce, status == INHERITANCE_SUCCESS);
3056+
if (parent_ce && parent_ce->num_interfaces) {
3057+
zend_do_inherit_interfaces(ce, parent_ce);
3058+
}
3059+
zend_build_properties_info_table(ce);
3060+
if ((ce->ce_flags & (ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) == ZEND_ACC_IMPLICIT_ABSTRACT_CLASS) {
3061+
zend_verify_abstract_class(ce);
3062+
}
3063+
ZEND_ASSERT(!(ce->ce_flags & ZEND_ACC_UNRESOLVED_VARIANCE));
3064+
ce->ce_flags |= ZEND_ACC_LINKED;
3065+
3066+
CG(current_linking_class) = orig_linking_class;
3067+
} zend_catch {
3068+
EG(record_errors) = false;
3069+
zend_free_recorded_errors();
3070+
zend_bailout();
3071+
} zend_end_try();
30553072

3056-
CG(current_linking_class) = orig_linking_class;
30573073
EG(record_errors) = false;
30583074

30593075
if (is_cacheable) {

ext/opcache/tests/gh8063-001.phpt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
Bug GH-8063 (Opcache breaks autoloading after E_COMPILE_ERROR) 001
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.record_warnings=0
7+
--EXTENSIONS--
8+
opcache
9+
--FILE--
10+
<?php
11+
12+
spl_autoload_register(function ($class) {
13+
printf("Autoloading %s\n", $class);
14+
include __DIR__.DIRECTORY_SEPARATOR.'gh8063'.DIRECTORY_SEPARATOR.$class.'.inc';
15+
});
16+
17+
register_shutdown_function(function () {
18+
new Bar();
19+
new Baz();
20+
print "Finished\n";
21+
});
22+
23+
new BadClass();
24+
--EXPECTF--
25+
Autoloading BadClass
26+
Autoloading Foo
27+
28+
Fatal error: Declaration of BadClass::dummy() must be compatible with Foo::dummy(): void in %sBadClass.inc on line 5
29+
Autoloading Bar
30+
Autoloading Baz
31+
Finished

ext/opcache/tests/gh8063-002.phpt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
Bug GH-8063 (Opcache breaks autoloading after E_COMPILE_ERROR) 002
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.record_warnings=1
7+
--EXTENSIONS--
8+
opcache
9+
--FILE--
10+
<?php
11+
12+
spl_autoload_register(function ($class) {
13+
printf("Autoloading %s\n", $class);
14+
include __DIR__.DIRECTORY_SEPARATOR.'gh8063'.DIRECTORY_SEPARATOR.$class.'.inc';
15+
}); 10000
16+
17+
register_shutdown_function(function () {
18+
new Bar();
19+
new Baz();
20+
print "Finished\n";
21+
});
22+
23+
new BadClass();
24+
--EXPECTF--
25+
Autoloading BadClass
26+
Autoloading Foo
27+
28+
Fatal error: Declaration of BadClass::dummy() must be compatible with Foo::dummy(): void in %sBadClass.inc on line 5
29+
Autoloading Bar
30+
Autoloading Baz
31+
Finished

ext/opcache/tests/gh8063-003.phpt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
Bug GH-8063 (Opcache breaks autoloading after E_COMPILE_ERROR) 003
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.record_warnings=0
7+
--EXTENSIONS--
8+
opcache
9+
--FILE--
10+
<?php
11+
12+
spl_autoload_register(function ($class) {
13+
printf("Autoloading %s\n", $class);
14+
include __DIR__.DIRECTORY_SEPARATOR.'gh8063'.DIRECTORY_SEPARATOR.$class.'.inc';
15+
});
16+
17+
register_shutdown_function(function () {
18+
new Bar();
19+
new Baz();
20+
print "Finished\n";
21+
});
22+
23+
new BadClass2();
24+
--EXPECTF--
25+
Autoloading BadClass2
26+
27+
Fatal error: Declaration of BadClass2::dummy() must be compatible with Foo2::dummy(): void in %sBadClass2.inc on line %d
28+
Autoloading Bar
29+
Autoloading Baz
30+
Finished

ext/opcache/tests/gh8063/BadClass.inc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
class BadClass extends Foo
4+
{
5+
function dummy()
6+
{
7+
}
8+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
class Foo2
4+
{
5+
function dummy(): void
6+
{
7+
}
8+
}
9+
10+
class BadClass2 extends Foo2
11+
{
12+
function dummy()
13+
{
14+
}
15+
}

ext/opcache/tests/gh8063/Bar.inc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
3+
class Bar {}

ext/opcache/tests/gh8063/Baz.inc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
3+
class Baz {}

ext/opcache/tests/gh8063/Foo.inc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
class Foo
4+
{
5+
function dummy(): void
6+
{
7+
}
8+
}

0 commit comments

Comments
 (0)
0