mirror of
https://github.com/nim-lang/Nim.git
synced 2026-06-09 21:28:13 +00:00
fixes #25851 ## Summary: `--panics:on` drops `nimErr_` check after closure calls (#25851) ### Bug With `--exceptions:goto` and `--panics:on`, the compiler skipped the `nimErr_` check after indirect closure calls whose result flows directly into another call (e.g., `result.add elem(src)`). A raise inside the closure was silently swallowed — the loop continued, and the next `raise` hit the already-set `nimInErrorMode` flag, overflowing its `bool` storage into `OverflowDefect`. ### Root Cause **ast.nim** — `canRaise` checked `fn.typ.n[0].len < effectListLen` first (false after the expansion) and then `exceptionEffects != nil` (also false, nil), so it returned `false` — meaning "cannot raise." The C codegen trusted this and omitted the `nimErr_` check. ### Fix **ast.nim** — `canRaise` now treats `nil` `exceptionEffects` as "unknown → can raise" (`exceptionEffects == nil` as an additional true condition). This is defense-in-depth: even if some other path expands the list but leaves `exceptionEffects` nil (e.g., a type with `{.tags.}` but no `{.raises.}`), the error check is still emitted. ### Test tclosure_err_panic_goto.nim — exercises the double-trigger pattern (`drawBool` sets the error flag → closure call must propagate it) with `matrix: "; --panics:on"` covering both exception modes.
This commit is contained in:
@@ -1647,9 +1647,13 @@ proc canRaise*(fn: PNode): bool =
|
||||
if fn.typ.n[0].kind == nkSym:
|
||||
result = false
|
||||
else:
|
||||
# A proc-typed value with no explicit raises slot still has
|
||||
# unspecified effects, which sempass2 treats conservatively.
|
||||
# Codegen needs to do the same in order to keep goto-exception
|
||||
# checks after indirect/closure calls.
|
||||
result = ((fn.typ.n[0].len < effectListLen) or
|
||||
(fn.typ.n[0][exceptionEffects] != nil and
|
||||
fn.typ.n[0][exceptionEffects].safeLen > 0))
|
||||
fn.typ.n[0][exceptionEffects] == nil or
|
||||
fn.typ.n[0][exceptionEffects].safeLen > 0)
|
||||
else:
|
||||
result = false
|
||||
|
||||
|
||||
36
tests/ccg/tclosure_err_panic_goto.nim
Normal file
36
tests/ccg/tclosure_err_panic_goto.nim
Normal file
@@ -0,0 +1,36 @@
|
||||
discard """
|
||||
matrix: "; --panics:on"
|
||||
"""
|
||||
# issue #25851: --panics:on must not drop the nimErr_ check after a closure
|
||||
# call whose result is consumed directly (e.g. `result.add elem(src)`).
|
||||
# Regression from #25295.
|
||||
|
||||
type
|
||||
Overrun = object of CatchableError
|
||||
Source = object
|
||||
data: seq[bool]
|
||||
cursor: int
|
||||
ElemFn = proc(src: var Source): bool {.closure.}
|
||||
|
||||
proc drawBool(src: var Source): bool =
|
||||
if src.cursor >= src.data.len: raise newException(Overrun, "exhausted")
|
||||
result = src.data[src.cursor]; inc src.cursor
|
||||
|
||||
proc listRun(elem: ElemFn, src: var Source): seq[bool] =
|
||||
result = @[]
|
||||
while true:
|
||||
if not src.drawBool(): break
|
||||
result.add elem(src) # closure call – the result flows straight
|
||||
# into `add`, which previously caused the
|
||||
# compiler to skip the nimErr_ check.
|
||||
|
||||
let elem: ElemFn = proc(src: var Source): bool = src.drawBool()
|
||||
|
||||
# Both --panics:on and --panics:off must propagate the Overrun.
|
||||
var caught = false
|
||||
try:
|
||||
var src = Source(data: @[true])
|
||||
discard listRun(elem, src)
|
||||
except Overrun:
|
||||
caught = true
|
||||
doAssert caught, "Overrun exception was swallowed"
|
||||
Reference in New Issue
Block a user