diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 735ab9d4..00516a6e 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -6565,7 +6565,7 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont foreach ($catches as $catch) { if ($catch->var) { $varName = $this->parseIdentifier($catch->var); - if (!$this->hasVar($varName)) { + if (!$this->hasVar($varName) && $this->stmtListUsesVariable($finally->stmts, $varName)) { $this->addLocalVar($varName, self::TYPE_OBJECT); } } @@ -6582,9 +6582,11 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont $code .= 'catch(zend_object *_ex) {' . PHP_EOL; $code .= $this->getIndent() . $exVar . ' = php::catchException();' . PHP_EOL; if ($catches) { + $catchMatched = $this->genTmpVarName(); + $code .= $this->getIndent() . 'bool ' . $catchMatched . ' = false;' . PHP_EOL; $this->indentLevel++; foreach ($catches as $catch) { - $code .= $this->parseCatch($catch, $exVar, $finally?->stmts ?? []); + $code .= $this->parseCatch($catch, $exVar, $catchMatched, $finally?->stmts ?? []); } $this->indentLevel--; } @@ -6681,32 +6683,44 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont return array_map(static fn (Node\Stmt $stmt): Node\Stmt => clone $stmt, $stmts); } - protected function parseCatch(Node\Stmt\Catch_ $catch, string $exVar, array $finallyStmts = []): string + protected function stmtListUsesVariable(array $stmts, string $name): bool + { + $nodeFinder = new NodeFinder(); + foreach ($nodeFinder->findInstanceOf($stmts, Variable::class) as $var) { + if (is_string($var->name) && $this->escapeVarName($var->name) === $name) { + return true; + } + } + return false; + } + + protected function parseCatch(Node\Stmt\Catch_ $catch, string $exVar, string $catchMatched, array $finallyStmts = []): string { $types = $catch->types; - $var = $catch->var ? $this->parseIdentifier($catch->var) : $this->genTmpVarName(); - if (!$this->hasVar($var)) { + $var = $catch->var ? $this->parseIdentifier($catch->var) : ''; + if ($var !== '' && !$this->hasVar($var)) { $this->addLocalVar($var, self::TYPE_OBJECT); } - $code = $this->getIndent() . $var . ' = ' . $exVar . ';' . PHP_EOL; - - $code .= $this->parseBeforeStmtLines() . PHP_EOL; - $code .= $this->getIndent() . 'if (' . $var . ' && '; + $code = $this->parseBeforeStmtLines() . PHP_EOL; + $code .= $this->getIndent() . 'if (!' . $catchMatched . ' && ' . $exVar . ' && '; $conditions = []; foreach ($types as $type) { if ($this->isNameExpr($type) or $this->isFullNameExpr($type)) { $class = $this->getNamespacedClassName($this->parseIdentifier($type)); $ce = $this->getClassEntryPtr($class); - $conditions[] = Symbol::instanceOf() . '(' . $var . ', ' . $ce . ')'; + $conditions[] = Symbol::instanceOf() . '(' . $exVar . ', ' . $ce . ')'; } else { $this->fatalError($type, 'Unsupported catch type'); } } - $code .= implode(' || ', $conditions); - $code .= ') {' . PHP_EOL; + $code .= '(' . implode(' || ', $conditions) . ')) {' . PHP_EOL; $this->indentLevel++; + $code .= $this->getIndent() . $catchMatched . ' = true;' . PHP_EOL; + if ($var !== '') { + $code .= $this->getIndent() . $var . ' = ' . $exVar . ';' . PHP_EOL; + } $code .= $this->getIndent() . "{$exVar} = php::null;" . PHP_EOL; $stmts = $finallyStmts ? $this->injectFinallyBeforeReturn($catch->stmts, $finallyStmts) : $catch->stmts; if ($finallyStmts) { diff --git a/tests/aot/exception/finally-catch-throw-sibling.phpt b/tests/aot/exception/finally-catch-throw-sibling.phpt new file mode 100644 index 00000000..2b2c71f4 --- /dev/null +++ b/tests/aot/exception/finally-catch-throw-sibling.phpt @@ -0,0 +1,30 @@ +--TEST-- +exception thrown inside catch is not handled by sibling catch +--FILE-- +getMessage() . "\n"; + } +} +?> +--EXPECT-- +catch-a +finally +outer:b diff --git a/tests/aot/exception/finally-unmatched-catch-var.phpt b/tests/aot/exception/finally-unmatched-catch-var.phpt new file mode 100644 index 00000000..be9e28eb --- /dev/null +++ b/tests/aot/exception/finally-unmatched-catch-var.phpt @@ -0,0 +1,26 @@ +--TEST-- +finally must not see variables from unmatched catch clauses +--FILE-- +getMessage() . "\n"; + } +} +?> +--EXPECT-- +unset +outer:a diff --git a/tests/aot/exception/finally-use-var.phpt b/tests/aot/exception/finally-use-var.phpt index 2fb09e61..83d5818a 100644 --- a/tests/aot/exception/finally-use-var.phpt +++ b/tests/aot/exception/finally-use-var.phpt @@ -1,29 +1,26 @@ ---TEST-- -try-catch-finally: finally block must execute when exception is re-thrown inside catch block. Verifies that finally can access the caught exception variable ($e) even when the catch block re-throws it. ---FILE-- -getMessage(), "\n"; - throw $e; - } finally { - var_dump($a); - echo 'Finally exception: ', $e->getMessage(), "\n"; - } - var_dump('This should not be reached'); -} -?> ---EXPECT-- -Caught exception: test -int(1) -Finally exception: test - -Fatal error: Uncaught RuntimeException: test in Unknown(0) : eval():1 -Stack trace: -#0 Unknown(0) : eval()(1): main() -#1 {main} - thrown in Unknown(0) : eval() on line 1 +--TEST-- +finally can use caught exception variable when catch rethrows +--FILE-- +getMessage(), "\n"; + throw $e; + } finally { + var_dump($a); + echo 'Finally exception: ', $e->getMessage(), "\n"; + } +} +?> +--EXPECTF-- +Caught exception: test +int(1) +Finally exception: test + +Fatal error: Uncaught RuntimeException: test in %A diff --git a/tests/aot/exception/multi-catch-finally-sibling.phpt b/tests/aot/exception/multi-catch-finally-sibling.phpt new file mode 100644 index 00000000..5e98a758 --- /dev/null +++ b/tests/aot/exception/multi-catch-finally-sibling.phpt @@ -0,0 +1,31 @@ +--TEST-- +multi-catch conditions respect catch match state with finally +--FILE-- +getMessage() . "\n"; + } +} +?> +--EXPECT-- +catch-ab +finally +outer:c