Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

only allow re-raise in scope of except #1170

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/ast/report_enums.nim
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,7 @@ type
rsemExpectedObjectForMethod
rsemUnexpectedPragmaInDefinitionOf
rsemMisplacedRunnableExample
rsemCannotReraise

# Expressions
rsemConstantOfTypeHasNoValue
Expand Down
4 changes: 4 additions & 0 deletions compiler/front/cli_reporter.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1350,6 +1350,10 @@ proc reportBody*(conf: ConfigRef, r: SemReport): string =
of rsemMisplacedRunnableExample:
result = "runnableExamples must appear before the first non-comment statement"

of rsemCannotReraise:
result = "an exception can only be re-raised within the scope of an" &
" except, with no finally in-between"

of rsemCannotInferTypeOfLiteral:
result = "cannot infer the type of the $1" % r.typ.kind.toHumanStr

Expand Down
19 changes: 16 additions & 3 deletions compiler/sem/sempass2.nim
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ type
exc: PNode ## stack of exceptions
tags: PNode ## list of tags
bottom, inTryStmt, inExceptOrFinallyStmt, leftPartOfAsgn: int
isReraiseAllowed: int
## > 0 if a re-raise statement is allowed
owner: PSym
ownerModule: PSym
init: seq[int] ## list of initialized variables
Expand Down Expand Up @@ -577,12 +579,19 @@ proc trackTryStmt(tracked: PEffects, n: PNode) =
let b = n[i]
if b.kind == nkExceptBranch:
setLen(tracked.init, oldState)
inc tracked.isReraiseAllowed
track(tracked, b[^1])
dec tracked.isReraiseAllowed
for i in oldState..<tracked.init.len:
addToIntersection(inter, tracked.init[i])
else:
setLen(tracked.init, oldState)
let prev = tracked.isReraiseAllowed
# re-raising in a finally clause would allow handling the exception,
# which is illegal. Therefore, re-raising is disallowed:
tracked.isReraiseAllowed = 0
track(tracked, b[^1])
tracked.isReraiseAllowed = prev
hasFinally = true

tracked.bottom = oldBottom
Expand Down Expand Up @@ -1170,11 +1179,15 @@ proc track(tracked: PEffects, n: PNode) =
for i in 0..<n.safeLen:
track(tracked, n[i])
createTypeBoundOps(tracked, n[0].typ, n.info)
else:
elif tracked.isReraiseAllowed > 0:
# A `raise` with no arguments means we're going to re-raise the exception
# being handled or, if outside of an `except` block, a `ReraiseDefect`.
# Here we add a `Exception` tag in order to cover both the cases.
# being handled
# XXX: using an `Exception` tag is overly conservative. It is statically
# known which exceptions an except branch covers, but this
# information isn't available here, at the moment.
addRaiseEffect(tracked, createRaise(tracked.graph, n), nil)
else:
localReport(tracked.config, n, reportSem(rsemCannotReraise))
of nkCallKinds:
trackCall(tracked, n)
of nkDotExpr:
Expand Down
11 changes: 7 additions & 4 deletions doc/manual.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4690,10 +4690,13 @@ the `raise` statement is the only way to raise an exception.

.. XXX document this better!

If no exception name is given, the current exception is `re-raised`:idx:. The
`ReraiseDefect`:idx: exception is raised if there is no exception to
re-raise. It follows that the `raise` statement *always* raises an
exception.
If no exception name is given, the current exception is `re-raised`:idx:.
There are two restrictions for `raise` statements without an exception name:

1. They must appear within the scope of an `except`.
2. They must not appear within the direct scope of a `finally`. A re-raise
statement within an `except` that appears within a `finally` is okay, but
the other way around is not.


Exception hierarchy
Expand Down
62 changes: 62 additions & 0 deletions tests/exception/tdisallowed_reraise.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
discard """
description: '''
Ensure that re-raise statements are rejected where required by the
language specification.
'''
cmd: "$nim check --hints:off $options $file"
action: reject
nimoutFull: true
nimout: '''
tdisallowed_reraise.nim(26, 5) Error: an exception can only be re-raised within the scope of an except, with no finally in-between
tdisallowed_reraise.nim(20, 3) Error: an exception can only be re-raised within the scope of an except, with no finally in-between
tdisallowed_reraise.nim(39, 5) Error: an exception can only be re-raised within the scope of an except, with no finally in-between
tdisallowed_reraise.nim(50, 7) Error: an exception can only be re-raised within the scope of an except, with no finally in-between
'''
"""


block standalone_reraise:
# a standalone re-raise statement is disallowed
raise

block reraise_in_procedure:
proc reraise() =
# the re-raise statement doesn't appear within an ``except`` clause's
# scope
raise

try:
raise CatchableError.newException("")
except:
# where the procedure is called has no effect on the rules
reraise()

block reraise_in_finally:
try:
raise CatchableError.newException("")
finally:
# a re-raise statement within a finally clause is disallowed
raise

block reraise_in_nested_finally:
try:
raise CatchableError.newException("")
except:
try:
break
finally:
# a re-raise statement within a finally clause that's within an except
# clause is still disallowed
raise

block reraise_in_nested_try:
# no error is reported here
try:
raise CatchableError.newException("")
except:
try:
# a re-raise within a try's scope is allowed, if the try is within the
# scope of an except
raise
finally:
discard
zerbina marked this conversation as resolved.
Show resolved Hide resolved
18 changes: 0 additions & 18 deletions tests/exception/tfinally6.nim
Original file line number Diff line number Diff line change
Expand Up @@ -49,24 +49,6 @@ proc raiseFromFinally() =

test raiseFromFinally(), [1, 2, 3]

proc reraiseFromFinally() =
try:
try:
raise ValueError.newException("a")
finally:
steps.add 1
# abort the exception but immediately re-raise it
raise
doAssert false, "unreachable"
except ValueError as e:
steps.add 2
doAssert e.msg == "a"
doAssert getCurrentException() == e

steps.add 3

test reraiseFromFinally(), [1, 2, 3]

proc exceptionInFinally() =
## Raise, and fully handle, an exception within a finally clause that was
## entered through exceptional control-flow.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ action: compile
import std/macros

macro foo(x: typed) = newProc ident"bar"
proc bar() {.foo.} = raise
proc bar() {.foo.} = raise CatchableError.newException("")
bar()

Loading