From 7d2f28b046f859fcdc70eb148e096de93c7fc829 Mon Sep 17 00:00:00 2001 From: puffball1567 Date: Fri, 29 May 2026 14:53:37 +0900 Subject: [PATCH] fixes ReraiseDefect after typeless `except:` + `finally:` (cpp backend) (#25777) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Bug A bare `except:` followed by a `finally:` block raises a spurious `ReraiseDefect: no exception to reraise` when compiled with `nim cpp`: ```nim proc test() = try: raise newException(CatchableError, "x") except: discard finally: echo "finally" test() echo "after" ``` Expected output: ``` finally after ``` Actual output: ``` finally fatal.nim(53) sysFatal Error: unhandled exception: no exception to reraise [ReraiseDefect] ``` This reproduces on every memory manager (`--mm:arc`, `--mm:orc`, `--mm:refc`). ## Root cause `genTryCpp` emits `try { ... } catch (Exception* T_) { ... }` followed by a finally block that ends with `if (T_) std::rethrow_exception(T_);`. In the *typed* except branches the codegen explicitly sets `T_ = nullptr;` once the exception is handled, so the rethrow check in the finally is a no-op. The typeless `except:` branch (the `if t[i].len == 1` arm) emitted only `popCurrentException()` and forgot to clear `T_`. After the handler body finished, `T_` still pointed at the original exception, so the trailing `if (T_) std::rethrow_exception(T_);` rethrew it. By that point Nim's current-exception stack had already been popped, and the rethrow surfaced as `ReraiseDefect`. ## Fix Emit `T_ = nullptr;` at the start of the typeless `except:` handler body, mirroring what is already done for the typed branches. This is the same one-line treatment that fixed the analogous typed-except case for #5871. ## Tests Adds `tests/exception/treraise_typeless_except_finally.nim`, exercising the bug pattern on `--mm:arc`, `--mm:orc`, and `--mm:refc`. Locally: - `tests/exception/` — 43 PASS, 0 FAIL, 3 SKIP - new test passes on all three memory managers ## Backport Tagged `[backport]` in the commit message — the same bug exists in `version-2-2` and the fix applies cleanly there. ## Related Independent of, but in the same family as, #25775 (also currently open). Both are silent-finally / cpp-backend exception handling fixes; they touch different lines of `genTryCpp` and don't conflict. Co-authored-by: puffball1567 <17452514+puffball1567@users.noreply.github.com> --- compiler/ccgstmts.nim | 1 + .../treraise_typeless_except_finally.nim | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 tests/exception/treraise_typeless_except_finally.nim diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 83f5e90172..bc9c06fa1d 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -1237,6 +1237,7 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) = else: scope = initScope(p.s(cpsStmts)) # we handled the error: + linefmt(p, cpsStmts, "T$1_ = nullptr;$n", [etmp]) expr(p, t[i][0], d) linefmt(p, cpsStmts, "#popCurrentException();$n", []) endBlockWith(p): diff --git a/tests/exception/treraise_typeless_except_finally.nim b/tests/exception/treraise_typeless_except_finally.nim new file mode 100644 index 0000000000..259863e4d4 --- /dev/null +++ b/tests/exception/treraise_typeless_except_finally.nim @@ -0,0 +1,30 @@ +discard """ + targets: "cpp" + matrix: "--mm:arc; --mm:orc; --mm:refc" + output: ''' +finally +after +''' +""" + +# Regression test: typeless `except:` followed by `finally:` must not +# trigger ReraiseDefect at the end of the proc. +# +# Previously, `genTryCpp` only emitted `T_ = nullptr;` in the *typed* +# except branches, leaving the typeless `except:` path with a still-set +# `T_`. After the handler body and `popCurrentException`, the trailing +# `if (T_) std::rethrow_exception(T_);` in the finally block would still +# fire — but with the Nim exception stack already popped, the rethrow +# bubbled up as a `ReraiseDefect: no exception to reraise`. + +proc test() = + try: + raise newException(CatchableError, "x") + except: + let e = getCurrentException() + discard e + finally: + echo "finally" + +test() +echo "after"