diff options
-rw-r--r-- | changelog.md | 4 | ||||
-rw-r--r-- | compiler/ast.nim | 13 | ||||
-rw-r--r-- | compiler/ccgstmts.nim | 30 | ||||
-rw-r--r-- | compiler/ccgtypes.nim | 12 | ||||
-rw-r--r-- | compiler/lookups.nim | 2 | ||||
-rw-r--r-- | compiler/sempass2.nim | 9 | ||||
-rw-r--r-- | compiler/semstmts.nim | 47 | ||||
-rw-r--r-- | compiler/transf.nim | 2 | ||||
-rw-r--r-- | doc/manual/exceptions.txt | 22 | ||||
-rw-r--r-- | lib/system.nim | 1 | ||||
-rw-r--r-- | lib/system/excpt.nim | 33 | ||||
-rw-r--r-- | tests/exception/tcpp_imported_exc.nim | 134 |
12 files changed, 267 insertions, 42 deletions
diff --git a/changelog.md b/changelog.md index 7a817fd81..91c9b3d68 100644 --- a/changelog.md +++ b/changelog.md @@ -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 diff --git a/compiler/ast.nim b/compiler/ast.nim index ad4d6fed8..55032234f 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -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 diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index f6c4204e8..a7858de72 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -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) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 2a4a10555..d351f3610 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -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", diff --git a/compiler/lookups.nim b/compiler/lookups.nim index 0675c5ca0..e0d6e0098 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -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" diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 25525d412..d2a10f714 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -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: diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index ce553cabf..92b5a71e3 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -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 + + if not isImportedException(typ): + + if typ.kind != tyRef or typ.lastSon.kind != tyObject: + localError(n.info, errExprCannotBeRaised) - # check if the given object inherits from Exception - if not typ.lastSon.isException(): + 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) = diff --git a/compiler/transf.nim b/compiler/transf.nim index 94899c5d4..e6dc69b38 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -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())` diff --git a/doc/manual/exceptions.txt b/doc/manual/exceptions.txt index 0f1240a4a..63adff776 100644 --- a/doc/manual/exceptions.txt +++ b/doc/manual/exceptions.txt @@ -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() + + \ No newline at end of file diff --git a/lib/system.nim b/lib/system.nim index 7733a1b20..3b22e9169 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -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 ## \ diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index 476582af2..afeab2b6c 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -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: diff --git a/tests/exception/tcpp_imported_exc.nim b/tests/exception/tcpp_imported_exc.nim new file mode 100644 index 000000000..c8349f7d5 --- /dev/null +++ b/tests/exception/tcpp_imported_exc.nim @@ -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) |