Catch exceptions without capturing them to variables by MaxSem · Pull Request #5345 · php/php-src

A suggested fix for the test exiting without properly reporting the uncaught exception - @nikic might know more about the edge case I'm worried about.

I'm also not sure why the GC_ADDREF leads to a memory leak when the exception has no variable, but taking it out seems to work.

The test for catch_novar_2.phpt seems to be crashing. I think that EG(exception) should be set to null before calling the destructor on the caught exception.

I'm not 100% sure of what to do about public function __destruct() { throw $this; }. The below case is different in that the RuntimeException is the uncaught exception which still has a reference count, not the ThrowsOnDestruct. Maybe the object still exists but __destruct won't be called again?

php > class ThrowsOnDestruct extends Exception { public function __destruct() { echo "throwing self\n"; throw $this; }}
php > $e = new ThrowsOnDestruct();
php > try { throw new RuntimeException(); } catch (Exception $e) {}
throwing self
Warning: Uncaught RuntimeException in php shell code:1
Stack trace:
#0 {main}

Next ThrowsOnDestruct in php shell code:1
Stack trace:
#0 {main}
  thrown in php shell code on line 1
diff --git a/Zend/tests/try/catch_novar_2.phpt b/Zend/tests/try/catch_novar_2.phpt
index 60a7ebb283..846fb86484 100644
--- a/Zend/tests/try/catch_novar_2.phpt
+++ b/Zend/tests/try/catch_novar_2.phpt
@@ -15,5 +15,11 @@ try {
 }
 echo "Unreachable fallthrough\n";
 
---EXPECT--
+--EXPECTF--
 Throwing
+
+Fatal error: Uncaught RuntimeException: ThrowsOnDestruct::__destruct in %scatch_novar_2.php:5
+Stack trace:
+#0 %scatch_novar_2.php(10): ThrowsOnDestruct->__destruct()
+#1 {main}
+  thrown in %scatch_novar_2.php on line 5
diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h
index ab44b9ca7c..20058b5337 100644
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@ -4493,15 +4493,22 @@ ZEND_VM_HANDLER(107, ZEND_CATCH, CONST, JMP_ADDR, LAST_CATCH|CACHE_SLOT)
 		}
 		zval_ptr_dtor(ex);
 		ZVAL_OBJ(ex, EG(exception));
-	} else {
-		OBJ_RELEASE(EG(exception));
-	}
-	if (UNEXPECTED(EG(exception) != exception)) {
-		GC_ADDREF(EG(exception));
-		HANDLE_EXCEPTION();
+		if (UNEXPECTED(EG(exception) != exception)) {
+			GC_ADDREF(EG(exception));
+			HANDLE_EXCEPTION();
+		} else {
+			EG(exception) = NULL;
+			ZEND_VM_NEXT_OPCODE();
+		}
 	} else {
 		EG(exception) = NULL;
-		ZEND_VM_NEXT_OPCODE();
+		OBJ_RELEASE(exception);
+		// TODO: What about function __destruct() { throw $this; }
+		if (UNEXPECTED(EG(exception) != NULL)) {
+			HANDLE_EXCEPTION();
+		} else {
+			ZEND_VM_NEXT_OPCODE();
+		}
 	}
 }
 
diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h
index 7ecc72a280..b8f511075a 100644
--- a/Zend/zend_vm_execute.h
+++ b/Zend/zend_vm_execute.h
@@ -3709,15 +3709,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CATCH_SPEC_CONST_HANDLER(ZEND_
 		}
 		zval_ptr_dtor(ex);
 		ZVAL_OBJ(ex, EG(exception));
-	} else {
-		OBJ_RELEASE(EG(exception));
-	}
-	if (UNEXPECTED(EG(exception) != exception)) {
-		GC_ADDREF(EG(exception));
-		HANDLE_EXCEPTION();
+		if (UNEXPECTED(EG(exception) != exception)) {
+			GC_ADDREF(EG(exception));
+			HANDLE_EXCEPTION();
+		} else {
+			EG(exception) = NULL;
+			ZEND_VM_NEXT_OPCODE();
+		}
 	} else {
 		EG(exception) = NULL;
-		ZEND_VM_NEXT_OPCODE();
+		OBJ_RELEASE(exception);
+		if (UNEXPECTED(EG(exception) != NULL)) {
+			HANDLE_EXCEPTION();
+		} else {
+			ZEND_VM_NEXT_OPCODE();
+		}
 	}
 }