Cpp codegen: handling of imported exceptions. Fixes #3571 (#7360)

This commit is contained in:
cooldome
2018-04-10 11:14:59 +01:00
committed by Andreas Rumpf
parent 427490a845
commit 16c1a90857
12 changed files with 267 additions and 42 deletions

View File

@@ -37,6 +37,10 @@
the use of `static[T]` types.
(#6415)
- Native C++ exceptions can now be imported with `importcpp` pragma.
Imported exceptions can be raised and caught just like Nim exceptions.
More details in language manual.
### Tool changes
- ``jsondoc2`` has been renamed ``jsondoc``, similar to how ``doc2`` was renamed

View File

@@ -1674,6 +1674,19 @@ proc isException*(t: PType): bool =
base = base.lastSon
return false
proc isImportedException*(t: PType): bool =
assert(t != nil)
if optNoCppExceptions in gGlobalOptions:
return false
let base = t.skipTypes({tyAlias, tyPtr, tyDistinct, tyGenericInst})
if base.sym != nil and sfCompileToCpp in base.sym.flags:
result = true
proc isInfixAs*(n: PNode): bool =
return n.kind == nkInfix and n[0].kind == nkIdent and n[0].ident.id == getIdent("as").id
proc findUnresolvedStatic*(n: PNode): PNode =
if n.kind == nkSym and n.typ.kind == tyStatic and n.typ.n == nil:
return n

View File

@@ -577,15 +577,18 @@ proc genRaiseStmt(p: BProc, t: PNode) =
# we must execute it before reraising
var finallyBlock = p.nestedTryStmts[^1].n[^1]
if finallyBlock.kind == nkFinally:
genSimpleBlock(p, finallyBlock.sons[0])
if t.sons[0].kind != nkEmpty:
genSimpleBlock(p, finallyBlock[0])
if t[0].kind != nkEmpty:
var a: TLoc
initLocExpr(p, t.sons[0], a)
initLocExprSingleUse(p, t[0], a)
var e = rdLoc(a)
var typ = skipTypes(t.sons[0].typ, abstractPtrs)
var typ = skipTypes(t[0].typ, abstractPtrs)
genLineDir(p, t)
lineCg(p, cpsStmts, "#raiseException((#Exception*)$1, $2);$n",
[e, makeCString(typ.sym.name.s)])
if isImportedException(typ):
lineF(p, cpsStmts, "throw $1;$n", [e])
else:
lineCg(p, cpsStmts, "#raiseException((#Exception*)$1, $2);$n",
[e, makeCString(typ.sym.name.s)])
else:
genLineDir(p, t)
# reraise the last exception:
@@ -799,19 +802,16 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
# general_handler_body
# }
# finallyPart();
template genExceptBranchBody(body: PNode) {.dirty.} =
if optStackTrace in p.options:
linefmt(p, cpsStmts, "#setFrame((TFrame*)&FR_);$n")
expr(p, body, d)
linefmt(p, cpsStmts, "#popCurrentException();$n")
if not isEmptyType(t.typ) and d.k == locNone:
getTemp(p, t.typ, d)
genLineDir(p, t)
let end_label = getLabel(p)
discard cgsym(p.module, "Exception")
discard cgsym(p.module, "popCurrentExceptionEx")
add(p.nestedTryStmts, (t, false))
startBlock(p, "try {$n")
expr(p, t[0], d)
@@ -834,8 +834,12 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
endBlock(p)
else:
for j in 0..t[i].len-2:
assert(t[i][j].kind == nkType)
startBlock(p, "catch ($1*) {$n", getTypeDesc(p.module, t[i][j].typ))
if t[i][j].isInfixAs():
let exvar = t[i][j][2] # ex1 in `except ExceptType as ex1:`
fillLoc(exvar.sym.loc, locTemp, exvar, mangleLocalName(p, exvar.sym), OnUnknown)
startBlock(p, "catch ($1& $2) {$n", getTypeDesc(p.module, t[i][j][1].typ), rdLoc(exvar.sym.loc))
else:
startBlock(p, "catch ($1&) {$n", getTypeDesc(p.module, t[i][j].typ))
genExceptBranchBody(t[i][^1]) # exception handler body will duplicated for every type
endBlock(p)

View File

@@ -14,6 +14,8 @@
import sighashes
from lowerings import createObj
proc genProcHeader(m: BModule, prc: PSym): Rope
proc isKeyword(w: PIdent): bool =
# Nim and C++ share some keywords
# it's more efficient to test the whole Nim keywords range
@@ -573,7 +575,15 @@ proc getRecordDesc(m: BModule, typ: PType, name: Rope,
appcg(m, result, " : public $1 {$n",
[getTypeDescAux(m, typ.sons[0].skipTypes(skipPtrs), check)])
if typ.isException:
appcg(m, result, "virtual void raise() {throw this;}$n") # required for polymorphic exceptions
appcg(m, result, "virtual void raise() {throw *this;}$n") # required for polymorphic exceptions
if typ.sym.magic == mException:
# Add cleanup destructor to Exception base class
appcg(m, result, "~$1() {if(this->raise_id) popCurrentExceptionEx(this->raise_id);}$n", [name])
# hack: forward declare popCurrentExceptionEx() on top of type description,
# proper request to generate popCurrentExceptionEx not possible for 2 reasons:
# generated function will be below declared Exception type and circular dependency
# between Exception and popCurrentExceptionEx function
result = genProcHeader(m, magicsys.getCompilerProc("popCurrentExceptionEx")) & ";" & rnl & result
hasField = true
else:
appcg(m, result, " {$n $1 Sup;$n",

View File

@@ -456,5 +456,3 @@ proc pickSym*(c: PContext, n: PNode; kinds: set[TSymKind];
else: return nil # ambiguous
a = nextOverloadIter(o, c, n)
proc isInfixAs*(n: PNode): bool =
return n.kind == nkInfix and considerQuotedIdent(n[0]).s == "as"

View File

@@ -359,9 +359,12 @@ proc trackTryStmt(tracked: PEffects, n: PNode) =
catchesAll(tracked)
else:
for j in countup(0, blen - 2):
assert(b.sons[j].kind == nkType)
catches(tracked, b.sons[j].typ)
if b.sons[j].isInfixAs():
assert(b.sons[j][1].kind == nkType)
catches(tracked, b.sons[j][1].typ)
else:
assert(b.sons[j].kind == nkType)
catches(tracked, b.sons[j].typ)
setLen(tracked.init, oldState)
track(tracked, b.sons[blen-1])
for i in oldState..<tracked.init.len:

View File

@@ -257,13 +257,19 @@ proc semCase(c: PContext, n: PNode): PNode =
proc semTry(c: PContext, n: PNode): PNode =
var check = initIntSet()
template semExceptBranchType(typeNode: PNode): PNode =
template semExceptBranchType(typeNode: PNode): bool =
# returns true if exception type is imported type
let typ = semTypeNode(c, typeNode, nil).toObject()
if typ.kind != tyObject:
localError(typeNode.info, errExprCannotBeRaised)
var is_imported = false
if isImportedException(typ):
is_imported = true
elif not isException(typ):
localError(typeNode.info, errExprCannotBeRaised)
if containsOrIncl(check, typ.id):
localError(typeNode.info, errExceptionAlreadyHandled)
newNodeIT(nkType, typeNode.info, typ)
typeNode = newNodeIT(nkType, typeNode.info, typ)
is_imported
result = n
inc c.p.inTryStmt
@@ -286,18 +292,24 @@ proc semTry(c: PContext, n: PNode): PNode =
if a.len == 2 and a[0].isInfixAs():
# support ``except Exception as ex: body``
a[0][1] = semExceptBranchType(a[0][1])
let is_imported = semExceptBranchType(a[0][1])
let symbol = newSymG(skLet, a[0][2], c)
symbol.typ = a[0][1].typ.toRef()
symbol.typ = if is_imported: a[0][1].typ
else: a[0][1].typ.toRef()
addDecl(c, symbol)
# Overwrite symbol in AST with the symbol in the symbol table.
a[0][2] = newSymNode(symbol, a[0][2].info)
else:
# support ``except KeyError, ValueError, ... : body``
var is_native, is_imported: bool
for j in 0..a.len-2:
a[j] = semExceptBranchType(a[j])
let tmp = semExceptBranchType(a[j])
if tmp: is_imported = true
else: is_native = true
if is_native and is_imported:
localError(a[0].info, "Mix of imported and native exception types is not allowed in one except branch")
elif a.kind != nkFinally:
illFormedAst(n)
@@ -731,16 +743,19 @@ proc semFor(c: PContext, n: PNode): PNode =
proc semRaise(c: PContext, n: PNode): PNode =
result = n
checkSonsLen(n, 1)
if n.sons[0].kind != nkEmpty:
n.sons[0] = semExprWithType(c, n.sons[0])
var typ = n.sons[0].typ
if typ.kind != tyRef or typ.lastSon.kind != tyObject:
localError(n.info, errExprCannotBeRaised)
if n[0].kind != nkEmpty:
n[0] = semExprWithType(c, n[0])
let typ = n[0].typ
# check if the given object inherits from Exception
if not typ.lastSon.isException():
if not isImportedException(typ):
if typ.kind != tyRef or typ.lastSon.kind != tyObject:
localError(n.info, errExprCannotBeRaised)
if not isException(typ.lastSon):
localError(n.info, "raised object of type $1 does not inherit from Exception",
[typeToString(typ)])
[typeToString(typ)])
proc addGenericParamListToScope(c: PContext, n: PNode) =

View File

@@ -714,7 +714,7 @@ proc transformCall(c: PTransf, n: PNode): PTransNode =
proc transformExceptBranch(c: PTransf, n: PNode): PTransNode =
result = transformSons(c, n)
if n[0].isInfixAs():
if n[0].isInfixAs() and (not isImportedException(n[0][1].typ)):
let excTypeNode = n[0][1]
let actions = newTransNode(nkStmtListExpr, n[1], 2)
# Generating `let exc = (excType)(getCurrentException())`

View File

@@ -156,3 +156,25 @@ Exception hierarchy
The exception tree is defined in the `system <system.html>`_ module:
.. include:: ../exception_hierarchy_fragment.txt
Imported exceptions
-------------------
It is possible to raise/catch imported C++ exceptions. Types imported using
`importcpp` can be raised or caught. Exceptions are raised by value and
caught by reference. Example:
.. code-block:: nim
type
std_exception {.importcpp: "std::exception", header: "<exception>".} = object
proc what(s: std_exception): cstring {.importcpp: "((char *)#.what())".}
try:
raise std_exception()
except std_exception as ex:
echo ex.what()

View File

@@ -483,6 +483,7 @@ type
trace: string
else:
trace: seq[StackTraceEntry]
raise_id: uint # set when exception is raised
up: ref Exception # used for stacking exceptions. Not exported!
SystemError* = object of Exception ## \

View File

@@ -33,6 +33,12 @@ proc showErrorMessage(data: cstring) {.gcsafe.} =
else:
writeToStdErr(data)
proc quitOrDebug() {.inline.} =
when not defined(endb):
quit(1)
else:
endbStep() # call the debugger
proc chckIndx(i, a, b: int): int {.inline, compilerproc, benign.}
proc chckRange(i, a, b: int): int {.inline, compilerproc, benign.}
proc chckRangeF(x, a, b: float): float {.inline, compilerproc, benign.}
@@ -50,6 +56,8 @@ var
# list of exception handlers
# a global variable for the root of all try blocks
currException {.threadvar.}: ref Exception
raise_counter {.threadvar.}: uint
gcFramePtr {.threadvar.}: GcFrame
type
@@ -108,6 +116,21 @@ proc pushCurrentException(e: ref Exception) {.compilerRtl, inl.} =
proc popCurrentException {.compilerRtl, inl.} =
currException = currException.up
proc popCurrentExceptionEx(id: uint) {.compilerRtl.} =
# in cpp backend exceptions can pop-up in the different order they were raised, example #5628
if currException.raise_id == id:
currException = currException.up
else:
var cur = currException.up
var prev = currException
while cur != nil and cur.raise_id != id:
prev = cur
cur = cur.up
if cur == nil:
showErrorMessage("popCurrentExceptionEx() exception was not found in the exception stack. Aborting...")
quitOrDebug()
prev.up = cur.up
# some platforms have native support for stack traces:
const
nativeStackTraceSupported* = (defined(macosx) or defined(linux)) and
@@ -291,12 +314,6 @@ when hasSomeStackTrace:
else:
proc stackTraceAvailable*(): bool = result = false
proc quitOrDebug() {.inline.} =
when not defined(endb):
quit(1)
else:
endbStep() # call the debugger
var onUnhandledException*: (proc (errorMsg: string) {.
nimcall.}) ## set this error \
## handler to override the existing behaviour on an unhandled exception.
@@ -320,6 +337,10 @@ proc raiseExceptionAux(e: ref Exception) =
quitOrDebug()
else:
pushCurrentException(e)
raise_counter.inc
if raise_counter == 0:
raise_counter.inc # skip zero at overflow
e.raise_id = raise_counter
{.emit: "`e`->raise();".}
else:
if excHandler != nil:

View File

@@ -0,0 +1,134 @@
discard """
targets: "cpp"
output: '''caught as std::exception
expected
finally1
finally2
finally2
2
expected
finally 1
finally 2
expected
cpp exception caught
'''
"""
type
std_exception* {.importcpp: "std::exception", header: "<exception>".} = object
std_runtime_error* {.importcpp: "std::runtime_error", header: "<stdexcept>".} = object
std_string* {.importcpp: "std::string", header: "<string>".} = object
proc constructStdString(s: cstring): std_string {.importcpp: "std::string(@)", constructor, header: "<string>".}
proc constructRuntimeError(s: stdstring): std_runtime_error {.importcpp: "std::runtime_error(@)", constructor.}
proc what(ex: std_runtime_error): cstring {.importcpp: "((char *)#.what())".}
proc myexception =
raise constructRuntimeError(constructStdString("cpp_exception"))
try:
myexception() # raise std::runtime_error
except std_exception:
echo "caught as std::exception"
try:
raise constructStdString("x")
except std_exception:
echo "should not happen"
except:
echo "expected"
doAssert(getCurrentException() == nil)
proc earlyReturn =
try:
try:
myexception()
finally:
echo "finally1"
except:
return
finally:
echo "finally2"
earlyReturn()
doAssert(getCurrentException() == nil)
try:
block blk1:
try:
raise newException(ValueError, "mmm")
except:
break blk1
except:
echo "should not happen"
finally:
echo "finally2"
doAssert(getCurrentException() == nil)
#--------------------------------------
# raise by pointer and also generic type
type
std_vector {.importcpp"std::vector", header"<vector>".} [T] = object
proc newVector[T](len: int): ptr std_vector[T] {.importcpp: "new std::vector<'1>(@)".}
proc deleteVector[T](v: ptr std_vector[T]) {.importcpp: "delete @; @ = NIM_NIL;".}
proc len[T](v: std_vector[T]): uint {.importcpp: "size".}
var v = newVector[int](2)
try:
try:
try:
raise v
except ptr std_vector[int] as ex:
echo len(ex[])
raise newException(ValueError, "msg5")
except:
echo "should not happen"
finally:
deleteVector(v)
except:
echo "expected"
doAssert(v == nil)
doAssert(getCurrentException() == nil)
#--------------------------------------
# mix of Nim and imported exceptions
try:
try:
try:
raise newException(KeyError, "msg1")
except KeyError:
raise newException(ValueError, "msg2")
except:
echo "should not happen"
finally:
echo "finally 1"
except:
doAssert(getCurrentExceptionMsg() == "msg2")
raise constructStdString("std::string")
finally:
echo "finally 2"
except:
echo "expected"
doAssert(getCurrentException() == nil)
try:
try:
myexception()
except std_runtime_error as ex:
echo "cpp exception caught"
raise newException(ValueError, "rewritten " & $ex.what())
except:
doAssert(getCurrentExceptionMsg() == "rewritten cpp_exception")
doAssert(getCurrentException() == nil)