summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md4
-rw-r--r--compiler/ast.nim13
-rw-r--r--compiler/ccgstmts.nim30
-rw-r--r--compiler/ccgtypes.nim12
-rw-r--r--compiler/lookups.nim2
-rw-r--r--compiler/sempass2.nim9
-rw-r--r--compiler/semstmts.nim47
-rw-r--r--compiler/transf.nim2
-rw-r--r--doc/manual/exceptions.txt22
-rw-r--r--lib/system.nim1
-rw-r--r--lib/system/excpt.nim33
-rw-r--r--tests/exception/tcpp_imported_exc.nim134
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)