fixes ReraiseDefect after typeless except: + finally: (cpp backend) (#25777)

## 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>
This commit is contained in:
puffball1567
2026-05-29 14:53:37 +09:00
committed by GitHub
parent f4dd00c4cc
commit 7d2f28b046
2 changed files with 31 additions and 0 deletions

View File

@@ -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):

View File

@@ -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"