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

fix #19580; add warning for bare except: clause #21099

Merged
merged 10 commits into from
Dec 15, 2022
2 changes: 2 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@
`foo` had type `proc ()` were assumed by the compiler to mean `foo(a, b, proc () = ...)`.
This behavior is now deprecated. Use `foo(a, b) do (): ...` or `foo(a, b, proc () = ...)` instead.

- If no exception or any exception deriving from Exception but not Defect or CatchableError given in except, a `warnBareExcept`warning will be triggered.
ringabout marked this conversation as resolved.
Show resolved Hide resolved

## Standard library additions and changes

[//]: # "Changes:"
Expand Down
1 change: 1 addition & 0 deletions compiler/condsyms.nim
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,4 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimHasWarnUnnamedBreak")
defineSymbol("nimHasGenericDefine")
defineSymbol("nimHasDefineAliases")
defineSymbol("nimHasWarnBareExcept")
8 changes: 4 additions & 4 deletions compiler/docgen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1746,7 +1746,7 @@ proc commandJson*(cache: IdentCache, conf: ConfigRef) =
let filename = getOutFile(conf, RelativeFile conf.projectName, JsonExt)
try:
writeFile(filename, content)
except:
except IOError:
rawMessage(conf, errCannotOpenFile, filename.string)

proc commandTags*(cache: IdentCache, conf: ConfigRef) =
Expand All @@ -1768,7 +1768,7 @@ proc commandTags*(cache: IdentCache, conf: ConfigRef) =
let filename = getOutFile(conf, RelativeFile conf.projectName, TagsExt)
try:
writeFile(filename, content)
except:
except IOError:
rawMessage(conf, errCannotOpenFile, filename.string)

proc commandBuildIndex*(conf: ConfigRef, dir: string, outFile = RelativeFile"") =
Expand All @@ -1789,7 +1789,7 @@ proc commandBuildIndex*(conf: ConfigRef, dir: string, outFile = RelativeFile"")

try:
writeFile(filename, code)
except:
except IOError:
rawMessage(conf, errCannotOpenFile, filename.string)

proc commandBuildIndexJson*(conf: ConfigRef, dir: string, outFile = RelativeFile"") =
Expand All @@ -1803,5 +1803,5 @@ proc commandBuildIndexJson*(conf: ConfigRef, dir: string, outFile = RelativeFile

try:
writeFile(filename, $body)
except:
except IOError:
rawMessage(conf, errCannotOpenFile, filename.string)
4 changes: 2 additions & 2 deletions compiler/extccomp.nim
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ proc ccHasSaneOverflow*(conf: ConfigRef): bool =
var exe = getConfigVar(conf, conf.cCompiler, ".exe")
if exe.len == 0: exe = CC[conf.cCompiler].compilerExe
# NOTE: should we need the full version, use -dumpfullversion
let (s, exitCode) = try: execCmdEx(exe & " -dumpversion") except: ("", 1)
let (s, exitCode) = try: execCmdEx(exe & " -dumpversion") except IOError, OSError, ValueError: ("", 1)
if exitCode == 0:
var major: int
discard parseInt(s, major)
Expand Down Expand Up @@ -1018,7 +1018,7 @@ proc changeDetectedViaJsonBuildInstructions*(conf: ConfigRef; jsonFile: Absolute
proc runJsonBuildInstructions*(conf: ConfigRef; jsonFile: AbsoluteFile) =
var bcache: BuildCache
try: bcache.fromJson(jsonFile.string.parseFile)
except:
except ValueError, KeyError, JsonKindError:
let e = getCurrentException()
conf.quitOrRaise "\ncaught exception:\n$#\nstacktrace:\n$#error evaluating JSON file: $#" %
[e.msg, e.getStackTrace(), jsonFile.string]
Expand Down
2 changes: 2 additions & 0 deletions compiler/lineinfos.nim
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ type
warnImplicitTemplateRedefinition = "ImplicitTemplateRedefinition",
warnUnnamedBreak = "UnnamedBreak",
warnStmtListLambda = "StmtListLambda",
warnBareExcept = "BareExcept",
warnUser = "User",
# hints
hintSuccess = "Success", hintSuccessX = "SuccessX",
Expand Down Expand Up @@ -185,6 +186,7 @@ const
warnImplicitTemplateRedefinition: "template '$1' is implicitly redefined; this is deprecated, add an explicit .redefine pragma",
warnUnnamedBreak: "Using an unnamed break in a block is deprecated; Use a named block with a named break instead",
warnStmtListLambda: "statement list expression assumed to be anonymous proc; this is deprecated, use `do (): ...` or `proc () = ...` instead",
warnBareExcept: "$1",
warnUser: "$1",
hintSuccess: "operation successful: $#",
# keep in sync with `testament.isSuccess`
Expand Down
4 changes: 4 additions & 0 deletions compiler/nim.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,7 @@ define:useStdoutAsStdmsg
@if nimHasWarnUnnamedBreak:
warningAserror[UnnamedBreak]:on
@end

@if nimHasWarnBareExcept:
warningAserror[BareExcept]:on
@end
5 changes: 4 additions & 1 deletion compiler/semstmts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ proc semTry(c: PContext, n: PNode; flags: TExprFlags; expectedType: PType = nil)
isImported = true
elif not isException(typ):
localError(c.config, typeNode.info, errExprCannotBeRaised)
elif not isDefectOrCatchableError(typ):
message(c.config, a.info, warnBareExcept, "catch a more precise Exception deriving from CatchableError or Defect.")

if containsOrIncl(check, typ.id):
localError(c.config, typeNode.info, errExceptionAlreadyHandled)
Expand Down Expand Up @@ -251,7 +253,8 @@ proc semTry(c: PContext, n: PNode; flags: TExprFlags; expectedType: PType = nil)
elif a.len == 1:
# count number of ``except: body`` blocks
inc catchAllExcepts

message(c.config, a.info, warnBareExcept,
"The bare except clause is deprecated; use `except CatchableError, Defect:` instead")
ringabout marked this conversation as resolved.
Show resolved Hide resolved
else:
# support ``except KeyError, ValueError, ... : body``
if catchAllExcepts > 0:
Expand Down
12 changes: 12 additions & 0 deletions compiler/types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1721,6 +1721,18 @@ proc isDefectException*(t: PType): bool =
t = skipTypes(t[0], abstractPtrs)
return false

proc isDefectOrCatchableError*(t: PType): bool =
var t = t.skipTypes(abstractPtrs)
while t.kind == tyObject:
if t.sym != nil and t.sym.owner != nil and
sfSystemModule in t.sym.owner.flags and
(t.sym.name.s == "Defect" or
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s.name.id == getIdent(c.graph.cache, "Defect").id better avoiding string comparation ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No.

t.sym.name.s == "CatchableError"):
return true
if t[0] == nil: break
t = skipTypes(t[0], abstractPtrs)
return false

proc isSinkTypeForParam*(t: PType): bool =
# a parameter like 'seq[owned T]' must not be used only once, but its
# elements must, so we detect this case here:
Expand Down
20 changes: 9 additions & 11 deletions doc/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -4773,8 +4773,8 @@ Example:
echo "overflow!"
except ValueError, IOError:
echo "catch multiple exceptions!"
except:
echo "Unknown exception!"
except CatchableError:
echo "Catchable exception!"
ringabout marked this conversation as resolved.
Show resolved Hide resolved
finally:
close(f)
```
Expand All @@ -4786,9 +4786,6 @@ listed in an `except` clause, the corresponding statements are executed.
The statements following the `except` clauses are called
`exception handlers`:idx:.

The empty `except`:idx: clause is executed if there is an exception that is
not listed otherwise. It is similar to an `else` clause in `if` statements.

If there is a `finally`:idx: clause, it is always executed after the
exception handlers.

Expand All @@ -4806,20 +4803,21 @@ Try can also be used as an expression; the type of the `try` branch then
needs to fit the types of `except` branches, but the type of the `finally`
branch always has to be `void`:

```nim
```nim test
from std/strutils import parseInt

let x = try: parseInt("133a")
except: -1
except ValueError: -1
finally: echo "hi"
```


To prevent confusing code there is a parsing limitation; if the `try`
follows a `(` it has to be written as a one liner:

```nim
let x = (try: parseInt("133a") except: -1)
```nim test
from std/strutils import parseInt
let x = (try: parseInt("133a") except ValueError: -1)
```


Expand Down Expand Up @@ -4867,7 +4865,7 @@ error message from `e`, and for such situations, it is enough to use
```nim
try:
# ...
except:
except CatchableError:
echo getCurrentExceptionMsg()
```

Expand Down Expand Up @@ -5055,7 +5053,7 @@ An empty `raises` list (`raises: []`) means that no exception may be raised:
try:
unsafeCall()
result = true
except:
except CatchableError:
result = false
```

Expand Down
4 changes: 2 additions & 2 deletions doc/tut2.md
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ The `try` statement handles exceptions:
echo "could not convert string to integer"
except IOError:
echo "IO error!"
except:
except CatchableError, Defect:
ringabout marked this conversation as resolved.
Show resolved Hide resolved
ringabout marked this conversation as resolved.
Show resolved Hide resolved
echo "Unknown exception!"
# reraise the unknown exception:
raise
Expand Down Expand Up @@ -425,7 +425,7 @@ module. Example:
```nim
try:
doSomethingHere()
except:
except CatchableError, Defect:
ringabout marked this conversation as resolved.
Show resolved Hide resolved
let
e = getCurrentException()
msg = getCurrentExceptionMsg()
Expand Down
6 changes: 3 additions & 3 deletions lib/packages/docutils/rst.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1610,7 +1610,7 @@ proc getFootnoteType(label: PRstNode): (FootnoteType, int) =
elif label.len == 1 and label.sons[0].kind == rnLeaf:
try:
result = (fnManualNumber, parseInt(label.sons[0].text))
except:
except ValueError:
result = (fnCitation, -1)
else:
result = (fnCitation, -1)
Expand Down Expand Up @@ -2899,13 +2899,13 @@ proc parseEnumList(p: var RstParser): PRstNode =
let enumerator = p.tok[p.idx + 1 + wildIndex[w]].symbol
# check that it's in sequence: enumerator == next(prevEnum)
if "n" in wildcards[w]: # arabic numeral
let prevEnumI = try: parseInt(prevEnum) except: 1
let prevEnumI = try: parseInt(prevEnum) except ValueError: 1
if enumerator in autoEnums:
if prevAE != "" and enumerator != prevAE:
break
prevAE = enumerator
curEnum = prevEnumI + 1
else: curEnum = (try: parseInt(enumerator) except: 1)
else: curEnum = (try: parseInt(enumerator) except ValueError: 1)
if curEnum - prevEnumI != 1:
break
prevEnum = enumerator
Expand Down
4 changes: 2 additions & 2 deletions lib/pure/htmlparser.nim
Original file line number Diff line number Diff line change
Expand Up @@ -394,10 +394,10 @@ proc entityToRune*(entity: string): Rune =
case entity[1]
of '0'..'9':
try: runeValue = parseInt(entity[1..^1])
except: discard
except ValueError: discard
of 'x', 'X': # not case sensitive here
try: runeValue = parseHexInt(entity[2..^1])
except: discard
except ValueError: discard
else: discard # other entities are not defined with prefix `#`
if runeValue notin 0..0x10FFFF: runeValue = 0 # only return legal values
return Rune(runeValue)
Expand Down
2 changes: 2 additions & 0 deletions lib/std/assertions.nim
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ template doAssertRaises*(exception: typedesc, code: untyped) =
const begin = "expected raising '" & astToStr(exception) & "', instead"
const msgEnd = " by: " & astToStr(code)
template raisedForeign {.gensym.} = raiseAssert(begin & " raised foreign exception" & msgEnd)
{.warning[BareExcept]:off.}
when Exception is exception:
try:
if true:
Expand All @@ -116,5 +117,6 @@ template doAssertRaises*(exception: typedesc, code: untyped) =
mixin `$` # alternatively, we could define $cstring in this module
raiseAssert(begin & " raised '" & $e.name & "'" & msgEnd)
except: raisedForeign()
{.warning[BareExcept]:on.}
if wrong:
raiseAssert(begin & " nothing was raised" & msgEnd)
2 changes: 1 addition & 1 deletion tests/nimdoc/trunnableexamples.nim
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ when true: # issue #12746
runnableExamples:
try:
discard
except:
except CatchableError, Defect:
ringabout marked this conversation as resolved.
Show resolved Hide resolved
# just the general except will work
discard

Expand Down