Commit 084e4096942 for php.net

commit 084e409694202af1562d3c5f91caeb88534afe2c
Author: Ilija Tovilo <ilija.tovilo@me.com>
Date:   Wed Oct 22 01:03:43 2025 +0200

    Remove zend_exception_save() and zend_exception_restore()

    These are leftovers from the pre-PHP-7.0 era. This also implicitly solves
    GH-20564 by not clearing exceptions before entering the autoloader.

    Closes GH-20256
    Fixes GH-20564

diff --git a/NEWS b/NEWS
index f42cfc4e5ee..74816c94a29 100644
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,7 @@ PHP                                                                        NEWS
   . Added `clamp()`. (kylekatarnls, thinkverse)
   . Fix OSS-Fuzz #429429090 (Failed assertion on unset() with uninitialized
     container). (ilutov)
+  . Fixed GH-20564 (Don't call autoloaders with pending exception). (ilutov)

 - Date:
   . Update timelib to 2022.16. (Derick)
diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS
index 748549e085b..2132006540c 100644
--- a/UPGRADING.INTERNALS
+++ b/UPGRADING.INTERNALS
@@ -54,6 +54,8 @@ PHP 8.6 INTERNALS UPGRADE NOTES
     ZEND_ACC_USER_ARG_INFO flag was set.
   . Added zend_ast_call_get_args() to fetch the argument node from any call
     node.
+  . The zend_exception_save() and zend_exception_restore() functions were
+    removed.

 ========================
 2. Build system changes
diff --git a/Zend/tests/gh20564.phpt b/Zend/tests/gh20564.phpt
new file mode 100644
index 00000000000..53311d952de
--- /dev/null
+++ b/Zend/tests/gh20564.phpt
@@ -0,0 +1,24 @@
+--TEST--
+GH-20564: Don't call autoloaders with pending exception
+--CREDITS--
+Viet Hoang Luu (@vi3tL0u1s)
+--FILE--
+<?php
+
+class A {
+    function __call($method, $args) {
+        eval("<<<ENDOFSTRING\n Test\n ENDOFSTRING;");
+        spl_autoload_register('A::test');
+        array_map('B::test', []);
+    }
+}
+
+try {
+    (new A)->test();
+} catch (Throwable $e) {
+    echo $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+array_map(): Argument #1 ($callback) must be a valid callback or null, class "B" not found
diff --git a/Zend/zend.c b/Zend/zend.c
index 6b0ffb73ad7..d2be69a7576 100644
--- a/Zend/zend.c
+++ b/Zend/zend.c
@@ -1978,7 +1978,6 @@ ZEND_API zend_result zend_execute_script(int type, zval *retval, zend_file_handl
 	zend_result ret = SUCCESS;
 	if (op_array) {
 		zend_execute(op_array, retval);
-		zend_exception_restore();
 		if (UNEXPECTED(EG(exception))) {
 			if (Z_TYPE(EG(user_exception_handler)) != IS_UNDEF) {
 				zend_user_exception_handler();
diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c
index 191d8f7fe6a..52e3ab00925 100644
--- a/Zend/zend_exceptions.c
+++ b/Zend/zend_exceptions.c
@@ -145,31 +145,6 @@ void zend_exception_set_previous(zend_object *exception, zend_object *add_previo
 }
 /* }}} */

-void zend_exception_save(void) /* {{{ */
-{
-	if (EG(prev_exception)) {
-		zend_exception_set_previous(EG(exception), EG(prev_exception));
-	}
-	if (EG(exception)) {
-		EG(prev_exception) = EG(exception);
-	}
-	EG(exception) = NULL;
-}
-/* }}} */
-
-void zend_exception_restore(void) /* {{{ */
-{
-	if (EG(prev_exception)) {
-		if (EG(exception)) {
-			zend_exception_set_previous(EG(exception), EG(prev_exception));
-		} else {
-			EG(exception) = EG(prev_exception);
-		}
-		EG(prev_exception) = NULL;
-	}
-}
-/* }}} */
-
 static zend_always_inline bool is_handle_exception_set(void) {
 	zend_execute_data *execute_data = EG(current_execute_data);
 	return !execute_data
@@ -241,10 +216,6 @@ ZEND_API ZEND_COLD void zend_throw_exception_internal(zend_object *exception) /*
 ZEND_API void zend_clear_exception(void) /* {{{ */
 {
 	zend_object *exception;
-	if (EG(prev_exception)) {
-		OBJ_RELEASE(EG(prev_exception));
-		EG(prev_exception) = NULL;
-	}
 	if (!EG(exception)) {
 		return;
 	}
diff --git a/Zend/zend_exceptions.h b/Zend/zend_exceptions.h
index 24d9f4efd80..e5a6be2f32f 100644
--- a/Zend/zend_exceptions.h
+++ b/Zend/zend_exceptions.h
@@ -41,8 +41,6 @@ extern ZEND_API zend_class_entry *zend_ce_unhandled_match_error;
 extern ZEND_API zend_class_entry *zend_ce_request_parse_body_exception;

 ZEND_API void zend_exception_set_previous(zend_object *exception, zend_object *add_previous);
-ZEND_API void zend_exception_save(void);
-ZEND_API void zend_exception_restore(void);

 ZEND_API ZEND_COLD void zend_throw_exception_internal(zend_object *exception);

diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c
index a4460286bf6..e95931276ef 100644
--- a/Zend/zend_execute.c
+++ b/Zend/zend_execute.c
@@ -1315,6 +1315,7 @@ ZEND_API bool zend_internal_call_should_throw(const zend_function *fbc, zend_exe

 	if ((fbc->common.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) &&
 			!zend_verify_internal_arg_types(fbc, call)) {
+		zend_clear_exception();
 		return 1;
 	}

diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c
index e134d3d496b..69337e27fd5 100644
--- a/Zend/zend_execute_API.c
+++ b/Zend/zend_execute_API.c
@@ -176,7 +176,6 @@ void init_executor(void) /* {{{ */
 	ZEND_ATOMIC_BOOL_INIT(&EG(timed_out), false);

 	EG(exception) = NULL;
-	EG(prev_exception) = NULL;

 	EG(fake_scope) = NULL;
 	EG(trampoline).common.function_name = NULL;
@@ -1268,9 +1267,7 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string *
 	zend_long previous_lineno = EG(lineno_override);
 	EG(filename_override) = NULL;
 	EG(lineno_override) = -1;
-	zend_exception_save();
 	ce = zend_autoload(autoload_name, lc_name);
-	zend_exception_restore();
 	EG(filename_override) = previous_filename;
 	EG(lineno_override) = previous_lineno;

diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h
index ef81ae5faaf..f09b81acb31 100644
--- a/Zend/zend_globals.h
+++ b/Zend/zend_globals.h
@@ -255,7 +255,7 @@ struct _zend_executor_globals {

 	zend_objects_store objects_store;
 	zend_lazy_objects_store lazy_objects_store;
-	zend_object *exception, *prev_exception;
+	zend_object *exception;
 	const zend_op *opline_before_exception;
 	zend_op exception_op[3];

diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l
index 1e26ddbd991..5a8a78cc3bd 100644
--- a/Zend/zend_language_scanner.l
+++ b/Zend/zend_language_scanner.l
@@ -2765,7 +2765,8 @@ skip_escape_conversion:

 		zend_ptr_stack_reverse_apply(&current_state.heredoc_label_stack, copy_heredoc_label_stack);

-		zend_exception_save();
+		zend_object *prev_exception = EG(exception);
+		EG(exception) = NULL;
 		while (heredoc_nesting_level) {
 			zval zv;
 			int retval;
@@ -2794,7 +2795,7 @@ skip_escape_conversion:
 					heredoc_nesting_level = 0;
 			}
 		}
-		zend_exception_restore();
+		EG(exception) = prev_exception;

 		if (
 		    (first_token == T_VARIABLE
diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h
index 86de5992a8f..9840bf28040 100644
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@ -4797,10 +4797,8 @@ ZEND_VM_COLD_CONST_HANDLER(108, ZEND_THROW, CONST|TMPVAR|CV, ANY)
 		}
 	} while (0);

-	zend_exception_save();
 	Z_TRY_ADDREF_P(value);
 	zend_throw_exception_object(value);
-	zend_exception_restore();
 	FREE_OP1();
 	HANDLE_EXCEPTION();
 }
@@ -4813,7 +4811,6 @@ ZEND_VM_HANDLER(107, ZEND_CATCH, CONST, JMP_ADDR, LAST_CATCH|CACHE_SLOT)

 	SAVE_OPLINE();
 	/* Check whether an exception has been thrown, if not, jump over code */
-	zend_exception_restore();
 	if (EG(exception) == NULL) {
 		ZEND_VM_JMP_EX(OP_JMP_ADDR(opline, opline->op2), 0);
 	}
diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h
index 62d645c1510..7aa0296aa42 100644
Binary files a/Zend/zend_vm_execute.h and b/Zend/zend_vm_execute.h differ
diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c
index 7acb14b778f..f4134212bc4 100644
--- a/ext/opcache/ZendAccelerator.c
+++ b/ext/opcache/ZendAccelerator.c
@@ -4663,7 +4663,6 @@ static zend_result accel_preload(const char *config, bool in_child)
 		zend_destroy_file_handle(&file_handle);
 		if (op_array) {
 			zend_execute(op_array, NULL);
-			zend_exception_restore();
 			if (UNEXPECTED(EG(exception))) {
 				if (Z_TYPE(EG(user_exception_handler)) != IS_UNDEF) {
 					zend_user_exception_handler();
diff --git a/sapi/fuzzer/fuzzer-execute-common.h b/sapi/fuzzer/fuzzer-execute-common.h
index 338c771e551..ef2ff4ee79b 100644
--- a/sapi/fuzzer/fuzzer-execute-common.h
+++ b/sapi/fuzzer/fuzzer-execute-common.h
@@ -134,7 +134,6 @@ ZEND_ATTRIBUTE_UNUSED static void create_file(void) {

 ZEND_ATTRIBUTE_UNUSED static void opcache_invalidate(void) {
 	steps_left = MAX_STEPS;
-	zend_exception_save();
 	zval retval, args[2];
 	zend_function *fn = zend_hash_str_find_ptr(CG(function_table), ZEND_STRL("opcache_invalidate"));
 	ZEND_ASSERT(fn != NULL);
@@ -145,5 +144,4 @@ ZEND_ATTRIBUTE_UNUSED static void opcache_invalidate(void) {
 	ZEND_ASSERT(Z_TYPE(retval) == IS_TRUE);
 	zval_ptr_dtor(&args[0]);
 	zval_ptr_dtor(&retval);
-	zend_exception_restore();
 }
diff --git a/sapi/phpdbg/phpdbg_prompt.c b/sapi/phpdbg/phpdbg_prompt.c
index 7215888cb25..9566c1abd4e 100644
--- a/sapi/phpdbg/phpdbg_prompt.c
+++ b/sapi/phpdbg/phpdbg_prompt.c
@@ -716,10 +716,6 @@ static inline void phpdbg_handle_exception(void) /* {{{ */
 	phpdbg_writeln("%s", ZSTR_VAL(msg));
 	zend_string_release(msg);

-	if (EG(prev_exception)) {
-		OBJ_RELEASE(EG(prev_exception));
-		EG(prev_exception) = 0;
-	}
 	OBJ_RELEASE(ex);
 	EG(opline_before_exception) = NULL;

@@ -876,7 +872,6 @@ PHPDBG_COMMAND(run) /* {{{ */
 		} zend_end_try();

 		if (restore) {
-			zend_exception_restore();
 			zend_try {
 				zend_try_exception_handler();
 				PHPDBG_G(in_execution) = 1;