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
This commit is contained in:
Arnaud Le Blanc 2022-04-03 13:16:33 +02:00
parent 15ee285f83
commit f20e11cbe1
9 changed files with 245 additions and 97 deletions

View file

@ -2775,8 +2775,8 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string
#endif
bool orig_record_errors = EG(record_errors);
if (ce->ce_flags & ZEND_ACC_IMMUTABLE) {
if (is_cacheable) {
if (ce->ce_flags & ZEND_ACC_IMMUTABLE && is_cacheable) {
if (zend_inheritance_cache_get && zend_inheritance_cache_add) {
zend_class_entry *ret = zend_inheritance_cache_get(ce, parent, traits_and_interfaces);
if (ret) {
@ -2789,13 +2789,16 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string
}
/* Make sure warnings (such as deprecations) thrown during inheritance
* will be recoreded in the inheritance cache. */
* will be recorded in the inheritance cache. */
zend_begin_record_errors();
} else {
is_cacheable = 0;
}
proto = ce;
}
zend_try {
if (ce->ce_flags & ZEND_ACC_IMMUTABLE) {
/* Lazy class loading */
ce = zend_lazy_class_load(ce);
zv = zend_hash_find_known_hash(CG(class_table), key);
@ -2870,6 +2873,15 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string
}
zend_build_properties_info_table(ce);
} zend_catch {
/* Do not leak recorded errors to the next linked class. */
if (!orig_record_errors) {
EG(record_errors) = false;
zend_free_recorded_errors();
}
zend_bailout();
} zend_end_try();
EG(record_errors) = orig_record_errors;
if (!(ce->ce_flags & ZEND_ACC_UNRESOLVED_VARIANCE)) {
@ -3038,6 +3050,7 @@ zend_class_entry *zend_try_early_bind(zend_class_entry *ce, zend_class_entry *pa
orig_linking_class = CG(current_linking_class);
CG(current_linking_class) = is_cacheable ? ce : NULL;
zend_try{
if (is_cacheable) {
zend_begin_record_errors();
}
@ -3054,6 +3067,12 @@ zend_class_entry *zend_try_early_bind(zend_class_entry *ce, zend_class_entry *pa
ce->ce_flags |= ZEND_ACC_LINKED;
CG(current_linking_class) = orig_linking_class;
} zend_catch {
EG(record_errors) = false;
zend_free_recorded_errors();
zend_bailout();
} zend_end_try();
EG(record_errors) = false;
if (is_cacheable) {

View file

@ -0,0 +1,31 @@
--TEST--
Bug GH-8063 (Opcache breaks autoloading after E_COMPILE_ERROR) 001
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.record_warnings=0
--EXTENSIONS--
opcache
--FILE--
<?php
spl_autoload_register(function ($class) {
printf("Autoloading %s\n", $class);
include __DIR__.DIRECTORY_SEPARATOR.'gh8063'.DIRECTORY_SEPARATOR.$class.'.inc';
});
register_shutdown_function(function () {
new Bar();
new Baz();
print "Finished\n";
});
new BadClass();
--EXPECTF--
Autoloading BadClass
Autoloading Foo
Fatal error: Declaration of BadClass::dummy() must be compatible with Foo::dummy(): void in %sBadClass.inc on line 5
Autoloading Bar
Autoloading Baz
Finished

View file

@ -0,0 +1,31 @@
--TEST--
Bug GH-8063 (Opcache breaks autoloading after E_COMPILE_ERROR) 002
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.record_warnings=1
--EXTENSIONS--
opcache
--FILE--
<?php
spl_autoload_register(function ($class) {
printf("Autoloading %s\n", $class);
include __DIR__.DIRECTORY_SEPARATOR.'gh8063'.DIRECTORY_SEPARATOR.$class.'.inc';
});
register_shutdown_function(function () {
new Bar();
new Baz();
print "Finished\n";
});
new BadClass();
--EXPECTF--
Autoloading BadClass
Autoloading Foo
Fatal error: Declaration of BadClass::dummy() must be compatible with Foo::dummy(): void in %sBadClass.inc on line 5
Autoloading Bar
Autoloading Baz
Finished

View file

@ -0,0 +1,30 @@
--TEST--
Bug GH-8063 (Opcache breaks autoloading after E_COMPILE_ERROR) 003
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.record_warnings=0
--EXTENSIONS--
opcache
--FILE--
<?php
spl_autoload_register(function ($class) {
printf("Autoloading %s\n", $class);
include __DIR__.DIRECTORY_SEPARATOR.'gh8063'.DIRECTORY_SEPARATOR.$class.'.inc';
});
register_shutdown_function(function () {
new Bar();
new Baz();
print "Finished\n";
});
new BadClass2();
--EXPECTF--
Autoloading BadClass2
Fatal error: Declaration of BadClass2::dummy() must be compatible with Foo2::dummy(): void in %sBadClass2.inc on line %d
Autoloading Bar
Autoloading Baz
Finished

View file

@ -0,0 +1,8 @@
<?php
class BadClass extends Foo
{
function dummy()
{
}
}

View file

@ -0,0 +1,15 @@
<?php
class Foo2
{
function dummy(): void
{
}
}
class BadClass2 extends Foo2
{
function dummy()
{
}
}

View file

@ -0,0 +1,3 @@
<?php
class Bar {}

View file

@ -0,0 +1,3 @@
<?php
class Baz {}

View file

@ -0,0 +1,8 @@
<?php
class Foo
{
function dummy(): void
{
}
}