-
Notifications
You must be signed in to change notification settings - Fork 50
Expand file tree
/
Copy pathExceptions.hs
More file actions
160 lines (146 loc) · 6.33 KB
/
Copy pathExceptions.hs
File metadata and controls
160 lines (146 loc) · 6.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
-- | A module that contains exception-safe equivalents of @inline-c@ QuasiQuoters.
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE PatternSynonyms #-}
module Language.C.Inline.Cpp.Exceptions
( CppException(..)
, throwBlock
, tryBlock
, catchBlock
) where
import Control.Exception.Safe
import qualified Language.C.Inline as C
import qualified Language.C.Inline.Internal as C
import Language.Haskell.TH
import Language.Haskell.TH.Quote
import Foreign
import Foreign.C
-- | An exception thrown in C++ code.
data CppException
= CppStdException String
| CppOtherException (Maybe String) -- contains the exception type, if available.
deriving (Eq, Ord, Show)
instance Exception CppException
-- NOTE: Other C++ exception types (std::runtime_error etc) could be distinguished like this in the future.
pattern ExTypeNoException :: CInt
pattern ExTypeNoException = 0
pattern ExTypeStdException :: CInt
pattern ExTypeStdException = 1
pattern ExTypeOtherException :: CInt
pattern ExTypeOtherException = 2
handleForeignCatch :: (Ptr CInt -> Ptr CString -> IO a) -> IO (Either CppException a)
handleForeignCatch cont =
alloca $ \exTypePtr ->
alloca $ \msgPtrPtr -> do
poke exTypePtr ExTypeNoException
-- we need to mask this entire block because the C++ allocates the
-- string for the exception message and we need to make sure that
-- we free it (see the @free@ below). The foreign code would not be
-- preemptable anyway, so I do not think this loses us anything.
mask_ $ do
res <- cont exTypePtr msgPtrPtr
exType <- peek exTypePtr
case exType of
ExTypeNoException -> return (Right res)
ExTypeStdException -> do
msgPtr <- peek msgPtrPtr
errMsg <- peekCString msgPtr
free msgPtr
return (Left (CppStdException errMsg))
ExTypeOtherException -> do
msgPtr <- peek msgPtrPtr
mbExcType <- if msgPtr == nullPtr
then return Nothing
else do
excType <- peekCString msgPtr
free msgPtr
return (Just excType)
return (Left (CppOtherException mbExcType))
_ -> error "Unexpected C++ exception type."
-- | Like 'tryBlock', but will throw 'CppException's rather than returning
-- them in an 'Either'
throwBlock :: QuasiQuoter
throwBlock = QuasiQuoter
{ quoteExp = \blockStr ->
[e| either throwIO return =<< $(tryBlockQuoteExp blockStr) |]
, quotePat = unsupported
, quoteType = unsupported
, quoteDec = unsupported
} where
unsupported _ = fail "Unsupported quasiquotation."
-- | Variant of 'throwBlock' for blocks which return 'void'.
catchBlock :: QuasiQuoter
catchBlock = QuasiQuoter
{ quoteExp = \blockStr -> quoteExp throwBlock ("void {" ++ blockStr ++ "}")
, quotePat = unsupported
, quoteType = unsupported
, quoteDec = unsupported
} where
unsupported _ = fail "Unsupported quasiquotation."
tryBlockQuoteExp :: String -> Q Exp
tryBlockQuoteExp blockStr = do
let (ty, body) = C.splitTypedC blockStr
_ <- C.include "<exception>"
_ <- C.include "<cstring>"
_ <- C.include "<cstdlib>"
-- see
-- <https://stackoverflow.com/questions/28166565/detect-gcc-as-opposed-to-msvc-clang-with-macro>
-- regarding how to detect g++ or clang.
--
-- the defined(__clang__) should actually be redundant, since apparently it also
-- defines GNUC, but but let's be safe.
_ <- C.verbatim $ unlines
[ "#if defined(__GNUC__) || defined(__clang__)"
, "#include <cxxabi.h>"
, "#include <string>"
, "#endif"
]
typePtrVarName <- newName "exTypePtr"
msgPtrVarName <- newName "msgPtr"
-- see
-- <https://stackoverflow.com/questions/561997/determining-exception-type-after-the-exception-is-caught/47164539#47164539>
-- regarding how to show the type of an exception.
let inlineCStr = unlines
[ ty ++ " {"
, " int* __inline_c_cpp_exception_type__ = $(int* " ++ nameBase typePtrVarName ++ ");"
, " char** __inline_c_cpp_error_message__ = $(char** " ++ nameBase msgPtrVarName ++ ");"
, " try {"
, body
, " } catch (std::exception &e) {"
, " *__inline_c_cpp_exception_type__ = " ++ show ExTypeStdException ++ ";"
, "#if defined(__GNUC__) || defined(__clang__)"
, " int demangle_status;"
, " const char* demangle_result = abi::__cxa_demangle(abi::__cxa_current_exception_type()->name(), 0, 0, &demangle_status);"
, " std::string message = \"Exception: \" + std::string(e.what()) + \"; type: \" + std::string(demangle_result);"
, "#else"
, " std::string message = \"Exception: \" + std::string(e.what()) + \"; type: not available (please use g++ or clang)\";"
, "#endif"
, " size_t message_len = message.size() + 1;"
, " *__inline_c_cpp_error_message__ = static_cast<char*>(std::malloc(message_len));"
, " std::memcpy(*__inline_c_cpp_error_message__, message.c_str(), message_len);"
, if ty == "void" then "return;" else "return {};"
, " } catch (...) {"
, " *__inline_c_cpp_exception_type__ = " ++ show ExTypeOtherException ++ ";"
, "#if defined(__GNUC__) || defined(__clang__)"
, " int demangle_status;"
, " const char* message = abi::__cxa_demangle(abi::__cxa_current_exception_type()->name(), 0, 0, &demangle_status);"
, " size_t message_len = strlen(message) + 1;"
, " *__inline_c_cpp_error_message__ = static_cast<char*>(std::malloc(message_len));"
, " std::memcpy(*__inline_c_cpp_error_message__, message, message_len);"
, "#else"
, " *__inline_c_cpp_error_message__ = NULL;"
, "#endif"
, if ty == "void" then "return;" else "return {};"
, " }"
, "}"
]
[e| handleForeignCatch $ \ $(varP typePtrVarName) $(varP msgPtrVarName) -> $(quoteExp C.block inlineCStr) |]
-- | Similar to `C.block`, but C++ exceptions will be caught and the result is (Either CppException value). The return type must be void or constructible with @{}@.
-- Using this will automatically include @exception@, @cstring@ and @cstdlib@.
tryBlock :: QuasiQuoter
tryBlock = QuasiQuoter
{ quoteExp = tryBlockQuoteExp
, quotePat = unsupported
, quoteType = unsupported
, quoteDec = unsupported
} where
unsupported _ = fail "Unsupported quasiquotation."