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

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

* fixes some easy ones

* Update doc/manual.md

* fixes docs

* Update changelog.md

* addition

* Apply suggestions from code review

Co-authored-by: Jacek Sieka <arnetheduck@gmail.com>

* Update doc/tut2.md

Co-authored-by: Jacek Sieka <arnetheduck@gmail.com>
This commit is contained in:
ringabout 2022-12-15 13:45:36 +08:00 • committed by GitHub
commit 91ce8c385d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 50 additions and 26 deletions

View file

@ -133,6 +133,8 @@
`foo` had type `proc ()` were assumed by the compiler to mean `foo(a, b, proc () = ...)`. `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. 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.
## Standard library additions and changes ## Standard library additions and changes
[//]: # "Changes:" [//]: # "Changes:"

View file

@ -151,3 +151,4 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimHasWarnUnnamedBreak") defineSymbol("nimHasWarnUnnamedBreak")
defineSymbol("nimHasGenericDefine") defineSymbol("nimHasGenericDefine")
defineSymbol("nimHasDefineAliases") defineSymbol("nimHasDefineAliases")
defineSymbol("nimHasWarnBareExcept")

View file

@ -1746,7 +1746,7 @@ proc commandJson*(cache: IdentCache, conf: ConfigRef) =
let filename = getOutFile(conf, RelativeFile conf.projectName, JsonExt) let filename = getOutFile(conf, RelativeFile conf.projectName, JsonExt)
try: try:
writeFile(filename, content) writeFile(filename, content)
except: except IOError:
rawMessage(conf, errCannotOpenFile, filename.string) rawMessage(conf, errCannotOpenFile, filename.string)
proc commandTags*(cache: IdentCache, conf: ConfigRef) = proc commandTags*(cache: IdentCache, conf: ConfigRef) =
@ -1768,7 +1768,7 @@ proc commandTags*(cache: IdentCache, conf: ConfigRef) =
let filename = getOutFile(conf, RelativeFile conf.projectName, TagsExt) let filename = getOutFile(conf, RelativeFile conf.projectName, TagsExt)
try: try:
writeFile(filename, content) writeFile(filename, content)
except: except IOError:
rawMessage(conf, errCannotOpenFile, filename.string) rawMessage(conf, errCannotOpenFile, filename.string)
proc commandBuildIndex*(conf: ConfigRef, dir: string, outFile = RelativeFile"") = proc commandBuildIndex*(conf: ConfigRef, dir: string, outFile = RelativeFile"") =
@ -1789,7 +1789,7 @@ proc commandBuildIndex*(conf: ConfigRef, dir: string, outFile = RelativeFile"")
try: try:
writeFile(filename, code) writeFile(filename, code)
except: except IOError:
rawMessage(conf, errCannotOpenFile, filename.string) rawMessage(conf, errCannotOpenFile, filename.string)
proc commandBuildIndexJson*(conf: ConfigRef, dir: string, outFile = RelativeFile"") = proc commandBuildIndexJson*(conf: ConfigRef, dir: string, outFile = RelativeFile"") =
@ -1803,5 +1803,5 @@ proc commandBuildIndexJson*(conf: ConfigRef, dir: string, outFile = RelativeFile
try: try:
writeFile(filename, $body) writeFile(filename, $body)
except: except IOError:
rawMessage(conf, errCannotOpenFile, filename.string) rawMessage(conf, errCannotOpenFile, filename.string)

View file

@ -528,7 +528,7 @@ proc ccHasSaneOverflow*(conf: ConfigRef): bool =
var exe = getConfigVar(conf, conf.cCompiler, ".exe") var exe = getConfigVar(conf, conf.cCompiler, ".exe")
if exe.len == 0: exe = CC[conf.cCompiler].compilerExe if exe.len == 0: exe = CC[conf.cCompiler].compilerExe
# NOTE: should we need the full version, use -dumpfullversion # 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: if exitCode == 0:
var major: int var major: int
discard parseInt(s, major) discard parseInt(s, major)
@ -1018,7 +1018,7 @@ proc changeDetectedViaJsonBuildInstructions*(conf: ConfigRef; jsonFile: Absolute
proc runJsonBuildInstructions*(conf: ConfigRef; jsonFile: AbsoluteFile) = proc runJsonBuildInstructions*(conf: ConfigRef; jsonFile: AbsoluteFile) =
var bcache: BuildCache var bcache: BuildCache
try: bcache.fromJson(jsonFile.string.parseFile) try: bcache.fromJson(jsonFile.string.parseFile)
except: except ValueError, KeyError, JsonKindError:
let e = getCurrentException() let e = getCurrentException()
conf.quitOrRaise "\ncaught exception:\n$#\nstacktrace:\n$#error evaluating JSON file: $#" % conf.quitOrRaise "\ncaught exception:\n$#\nstacktrace:\n$#error evaluating JSON file: $#" %
[e.msg, e.getStackTrace(), jsonFile.string] [e.msg, e.getStackTrace(), jsonFile.string]

View file

@ -86,6 +86,7 @@ type
warnImplicitTemplateRedefinition = "ImplicitTemplateRedefinition", warnImplicitTemplateRedefinition = "ImplicitTemplateRedefinition",
warnUnnamedBreak = "UnnamedBreak", warnUnnamedBreak = "UnnamedBreak",
warnStmtListLambda = "StmtListLambda", warnStmtListLambda = "StmtListLambda",
warnBareExcept = "BareExcept",
warnUser = "User", warnUser = "User",
# hints # hints
hintSuccess = "Success", hintSuccessX = "SuccessX", hintSuccess = "Success", hintSuccessX = "SuccessX",
@ -185,6 +186,7 @@ const
warnImplicitTemplateRedefinition: "template '$1' is implicitly redefined; this is deprecated, add an explicit .redefine pragma", 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", 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", warnStmtListLambda: "statement list expression assumed to be anonymous proc; this is deprecated, use `do (): ...` or `proc () = ...` instead",
warnBareExcept: "$1",
warnUser: "$1", warnUser: "$1",
hintSuccess: "operation successful: $#", hintSuccess: "operation successful: $#",
# keep in sync with `testament.isSuccess` # keep in sync with `testament.isSuccess`

View file

@ -38,3 +38,7 @@ define:useStdoutAsStdmsg
@if nimHasWarnUnnamedBreak: @if nimHasWarnUnnamedBreak:
warningAserror[UnnamedBreak]:on warningAserror[UnnamedBreak]:on
@end @end
@if nimHasWarnBareExcept:
warningAserror[BareExcept]:on
@end

View file

@ -210,6 +210,8 @@ proc semTry(c: PContext, n: PNode; flags: TExprFlags; expectedType: PType = nil)
isImported = true isImported = true
elif not isException(typ): elif not isException(typ):
localError(c.config, typeNode.info, errExprCannotBeRaised) 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): if containsOrIncl(check, typ.id):
localError(c.config, typeNode.info, errExceptionAlreadyHandled) localError(c.config, typeNode.info, errExceptionAlreadyHandled)
@ -251,7 +253,8 @@ proc semTry(c: PContext, n: PNode; flags: TExprFlags; expectedType: PType = nil)
elif a.len == 1: elif a.len == 1:
# count number of ``except: body`` blocks # count number of ``except: body`` blocks
inc catchAllExcepts inc catchAllExcepts
message(c.config, a.info, warnBareExcept,
"The bare except clause is deprecated; use `except CatchableError:` instead")
else: else:
# support ``except KeyError, ValueError, ... : body`` # support ``except KeyError, ValueError, ... : body``
if catchAllExcepts > 0: if catchAllExcepts > 0:

View file

@ -1721,6 +1721,18 @@ proc isDefectException*(t: PType): bool =
t = skipTypes(t[0], abstractPtrs) t = skipTypes(t[0], abstractPtrs)
return false 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
t.sym.name.s == "CatchableError"):
return true
if t[0] == nil: break
t = skipTypes(t[0], abstractPtrs)
return false
proc isSinkTypeForParam*(t: PType): bool = proc isSinkTypeForParam*(t: PType): bool =
# a parameter like 'seq[owned T]' must not be used only once, but its # a parameter like 'seq[owned T]' must not be used only once, but its
# elements must, so we detect this case here: # elements must, so we detect this case here:

View file

@ -4773,8 +4773,8 @@ Example:
echo "overflow!" echo "overflow!"
except ValueError, IOError: except ValueError, IOError:
echo "catch multiple exceptions!" echo "catch multiple exceptions!"
except: except CatchableError:
echo "Unknown exception!" echo "Catchable exception!"
finally: finally:
close(f) close(f)
``` ```
@ -4786,9 +4786,6 @@ listed in an `except` clause, the corresponding statements are executed.
The statements following the `except` clauses are called The statements following the `except` clauses are called
`exception handlers`:idx:. `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 If there is a `finally`:idx: clause, it is always executed after the
exception handlers. exception handlers.
@ -4806,11 +4803,11 @@ 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` needs to fit the types of `except` branches, but the type of the `finally`
branch always has to be `void`: branch always has to be `void`:
```nim ```nim test
from std/strutils import parseInt from std/strutils import parseInt
let x = try: parseInt("133a") let x = try: parseInt("133a")
except: -1 except ValueError: -1
finally: echo "hi" finally: echo "hi"
``` ```
@ -4818,8 +4815,9 @@ branch always has to be `void`:
To prevent confusing code there is a parsing limitation; if the `try` To prevent confusing code there is a parsing limitation; if the `try`
follows a `(` it has to be written as a one liner: follows a `(` it has to be written as a one liner:
```nim ```nim test
let x = (try: parseInt("133a") except: -1) from std/strutils import parseInt
let x = (try: parseInt("133a") except ValueError: -1)
``` ```
@ -4867,7 +4865,7 @@ error message from `e`, and for such situations, it is enough to use
```nim ```nim
try: try:
# ... # ...
except: except CatchableError:
echo getCurrentExceptionMsg() echo getCurrentExceptionMsg()
``` ```
@ -5055,7 +5053,7 @@ An empty `raises` list (`raises: []`) means that no exception may be raised:
try: try:
unsafeCall() unsafeCall()
result = true result = true
except: except CatchableError:
result = false result = false
``` ```

View file

@ -393,7 +393,7 @@ The `try` statement handles exceptions:
echo "could not convert string to integer" echo "could not convert string to integer"
except IOError: except IOError:
echo "IO error!" echo "IO error!"
except: except CatchableError:
echo "Unknown exception!" echo "Unknown exception!"
# reraise the unknown exception: # reraise the unknown exception:
raise raise
@ -425,7 +425,7 @@ module. Example:
```nim ```nim
try: try:
doSomethingHere() doSomethingHere()
except: except CatchableError:
let let
e = getCurrentException() e = getCurrentException()
msg = getCurrentExceptionMsg() msg = getCurrentExceptionMsg()

View file

@ -1610,7 +1610,7 @@ proc getFootnoteType(label: PRstNode): (FootnoteType, int) =
elif label.len == 1 and label.sons[0].kind == rnLeaf: elif label.len == 1 and label.sons[0].kind == rnLeaf:
try: try:
result = (fnManualNumber, parseInt(label.sons[0].text)) result = (fnManualNumber, parseInt(label.sons[0].text))
except: except ValueError:
result = (fnCitation, -1) result = (fnCitation, -1)
else: else:
result = (fnCitation, -1) result = (fnCitation, -1)
@ -2899,13 +2899,13 @@ proc parseEnumList(p: var RstParser): PRstNode =
let enumerator = p.tok[p.idx + 1 + wildIndex[w]].symbol let enumerator = p.tok[p.idx + 1 + wildIndex[w]].symbol
# check that it's in sequence: enumerator == next(prevEnum) # check that it's in sequence: enumerator == next(prevEnum)
if "n" in wildcards[w]: # arabic numeral 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 enumerator in autoEnums:
if prevAE != "" and enumerator != prevAE: if prevAE != "" and enumerator != prevAE:
break break
prevAE = enumerator prevAE = enumerator
curEnum = prevEnumI + 1 curEnum = prevEnumI + 1
else: curEnum = (try: parseInt(enumerator) except: 1) else: curEnum = (try: parseInt(enumerator) except ValueError: 1)
if curEnum - prevEnumI != 1: if curEnum - prevEnumI != 1:
break break
prevEnum = enumerator prevEnum = enumerator

View file

@ -394,10 +394,10 @@ proc entityToRune*(entity: string): Rune =
case entity[1] case entity[1]
of '0'..'9': of '0'..'9':
try: runeValue = parseInt(entity[1..^1]) try: runeValue = parseInt(entity[1..^1])
except: discard except ValueError: discard
of 'x', 'X': # not case sensitive here of 'x', 'X': # not case sensitive here
try: runeValue = parseHexInt(entity[2..^1]) try: runeValue = parseHexInt(entity[2..^1])
except: discard except ValueError: discard
else: discard # other entities are not defined with prefix `#` else: discard # other entities are not defined with prefix `#`
if runeValue notin 0..0x10FFFF: runeValue = 0 # only return legal values if runeValue notin 0..0x10FFFF: runeValue = 0 # only return legal values
return Rune(runeValue) return Rune(runeValue)

View file

@ -98,6 +98,7 @@ template doAssertRaises*(exception: typedesc, code: untyped) =
const begin = "expected raising '" & astToStr(exception) & "', instead" const begin = "expected raising '" & astToStr(exception) & "', instead"
const msgEnd = " by: " & astToStr(code) const msgEnd = " by: " & astToStr(code)
template raisedForeign {.gensym.} = raiseAssert(begin & " raised foreign exception" & msgEnd) template raisedForeign {.gensym.} = raiseAssert(begin & " raised foreign exception" & msgEnd)
{.warning[BareExcept]:off.}
when Exception is exception: when Exception is exception:
try: try:
if true: if true:
@ -116,5 +117,6 @@ template doAssertRaises*(exception: typedesc, code: untyped) =
mixin `$` # alternatively, we could define $cstring in this module mixin `$` # alternatively, we could define $cstring in this module
raiseAssert(begin & " raised '" & $e.name & "'" & msgEnd) raiseAssert(begin & " raised '" & $e.name & "'" & msgEnd)
except: raisedForeign() except: raisedForeign()
{.warning[BareExcept]:on.}
if wrong: if wrong:
raiseAssert(begin & " nothing was raised" & msgEnd) raiseAssert(begin & " nothing was raised" & msgEnd)

View file

@ -65,7 +65,7 @@ when true: # issue #12746
runnableExamples: runnableExamples:
try: try:
discard discard
except: except CatchableError:
# just the general except will work # just the general except will work
discard discard