From 16c1a90857992df0960f53f5ffa578f14059cee4 Mon Sep 17 00:00:00 2001 From: cooldome Date: Tue, 10 Apr 2018 11:14:59 +0100 Subject: [PATCH] Cpp codegen: handling of imported exceptions. Fixes #3571 (#7360) --- changelog.md | 4 + compiler/ast.nim | 13 +++ compiler/ccgstmts.nim | 30 +++--- compiler/ccgtypes.nim | 12 ++- compiler/lookups.nim | 2 - compiler/sempass2.nim | 9 +- compiler/semstmts.nim | 47 ++++++--- compiler/transf.nim | 2 +- doc/manual/exceptions.txt | 22 +++++ lib/system.nim | 1 + lib/system/excpt.nim | 33 +++++-- tests/exception/tcpp_imported_exc.nim | 134 ++++++++++++++++++++++++++ 12 files changed, 267 insertions(+), 42 deletions(-) create mode 100644 tests/exception/tcpp_imported_exc.nim diff --git a/changelog.md b/changelog.md index 7a817fd817..91c9b3d686 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 ad4d6fed8f..55032234ff 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 f6c4204e87..a7858de720 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 2a4a105555..d351f36101 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 0675c5ca00..e0d6e00983 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 25525d4123..d2a10f714c 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..`_ 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: "".} = 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 7733a1b20b..3b22e9169c 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 476582af2f..afeab2b6c3 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 0000000000..c8349f7d5b --- /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: "".} = object + std_runtime_error* {.importcpp: "std::runtime_error", header: "".} = object + std_string* {.importcpp: "std::string", header: "".} = object + +proc constructStdString(s: cstring): std_string {.importcpp: "std::string(@)", constructor, header: "".} + +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"".} [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)