inline-c-cpp-0.5.0.2: src/Language/C/Inline/Cpp/Exception.hs
-- | A module that contains exception-safe equivalents of @inline-c@ QuasiQuoters.
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE QuasiQuotes #-}
module Language.C.Inline.Cpp.Exception
( CppException(..)
, CppExceptionPtr
, toSomeException
, throwBlock
, tryBlock
, catchBlock
, tryBlockQuoteExp
) where
import Control.Exception.Safe
import qualified Data.ByteString.Unsafe as BS (unsafePackMallocCString)
import Data.ByteString (ByteString)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.Text.Encoding.Error as T
import qualified Language.C.Inline as C
import qualified Language.C.Inline.Internal as C
import qualified Language.C.Inline.Cpp as Cpp
import Language.C.Inline.Cpp (AbstractCppExceptionPtr)
import Language.Haskell.TH
import Language.Haskell.TH.Quote
import Foreign
import Foreign.C
import System.IO.Unsafe(unsafePerformIO)
C.context Cpp.cppCtx
C.include "HaskellException.hxx"
-- | An exception thrown in C++ code.
data CppException
= CppStdException CppExceptionPtr ByteString (Maybe ByteString)
| CppHaskellException SomeException
| CppNonStdException CppExceptionPtr (Maybe ByteString)
instance Show CppException where
showsPrec p (CppStdException _ msg typ) = showParen (p >= 11) (showString "CppStdException e " . showsPrec 11 msg . showsPrec 11 typ)
showsPrec p (CppHaskellException e) = showParen (p >= 11) (showString "CppHaskellException " . showsPrec 11 e)
showsPrec p (CppNonStdException _ typ) = showParen (p >= 11) (showString "CppOtherException e " . showsPrec 11 typ)
instance Exception CppException where
displayException (CppStdException _ msg _typ) = bsToChars msg
displayException (CppHaskellException e) = displayException e
displayException (CppNonStdException _ (Just typ)) = "exception: Exception of type " <> bsToChars typ
displayException (CppNonStdException _ Nothing) = "exception: Non-std exception of unknown type"
type CppExceptionPtr = ForeignPtr AbstractCppExceptionPtr
-- | This converts a plain pointer to a managed object.
--
-- The pointer must have been created with @new@. The returned 'CppExceptionPtr'
-- will @delete@ it when it is garbage collected, so you must not @delete@ it
-- on your own. This function is called "unsafe" because it is not memory safe
-- by itself, but safe when used correctly; similar to for example
-- 'BS.unsafePackMallocCString'.
unsafeFromNewCppExceptionPtr :: Ptr AbstractCppExceptionPtr -> IO CppExceptionPtr
unsafeFromNewCppExceptionPtr = newForeignPtr finalizeAbstractCppExceptionPtr
finalizeAbstractCppExceptionPtr :: FinalizerPtr AbstractCppExceptionPtr
{-# NOINLINE finalizeAbstractCppExceptionPtr #-}
finalizeAbstractCppExceptionPtr =
unsafePerformIO
[C.exp|
void (*)(std::exception_ptr *) {
[](std::exception_ptr *v){ delete v; }
}|]
-- | Like 'toException' but unwrap 'CppHaskellException'
toSomeException :: CppException -> SomeException
toSomeException (CppHaskellException e) = e
toSomeException x = toException x
-- 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 ExTypeHaskellException :: CInt
pattern ExTypeHaskellException = 2
pattern ExTypeOtherException :: CInt
pattern ExTypeOtherException = 3
handleForeignCatch :: (Ptr (Ptr ()) -> IO a) -> IO (Either CppException a)
handleForeignCatch cont =
allocaBytesAligned (sizeOf (undefined :: Ptr ()) * 5) (alignment (undefined :: Ptr ())) $ \basePtr -> do
let ptrSize = sizeOf (undefined :: Ptr ())
exTypePtr = castPtr basePtr :: Ptr CInt
msgCStringPtr = castPtr (basePtr `plusPtr` ptrSize) :: Ptr CString
typCStringPtr = castPtr (basePtr `plusPtr` (ptrSize*2)) :: Ptr CString
exPtr = castPtr (basePtr `plusPtr` (ptrSize*3)) :: Ptr (Ptr AbstractCppExceptionPtr)
haskellExPtrPtr = castPtr (basePtr `plusPtr` (ptrSize*4)) :: Ptr (Ptr ())
-- 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 basePtr
exType <- peek exTypePtr
case exType of
ExTypeNoException -> return (Right res)
ExTypeStdException -> do
ex <- unsafeFromNewCppExceptionPtr =<< peek exPtr
-- BS.unsafePackMallocCString: safe because setMessageOfStdException
-- (invoked via tryBlockQuoteExp) sets msgCStringPtr to a newly
-- malloced string.
errMsg <- BS.unsafePackMallocCString =<< peek msgCStringPtr
-- BS.unsafePackMallocCString: safe because currentExceptionTypeName
-- returns a newly malloced string
mbExcType <- maybePeek BS.unsafePackMallocCString =<< peek typCStringPtr
return (Left (CppStdException ex errMsg mbExcType))
ExTypeHaskellException -> do
haskellExPtr <- peek haskellExPtrPtr
stablePtr <- [C.block| void * {
return (static_cast<HaskellException *>($(void *haskellExPtr)))->haskellExceptionStablePtr->stablePtr;
} |]
someExc <- deRefStablePtr (castPtrToStablePtr stablePtr)
[C.block| void{
delete static_cast<HaskellException *>($(void *haskellExPtr));
} |]
return (Left (CppHaskellException someExc))
ExTypeOtherException -> do
ex <- unsafeFromNewCppExceptionPtr =<< peek exPtr
-- BS.unsafePackMallocCString: safe because currentExceptionTypeName
-- returns a newly malloced string
mbExcType <- maybePeek BS.unsafePackMallocCString =<< peek typCStringPtr
return (Left (CppNonStdException ex mbExcType)) :: IO (Either CppException a)
_ -> error "Unexpected C++ exception type."
-- | Like 'tryBlock', but will throw unwrapped 'CppHaskellException's or other 'CppException's rather than returning
-- them in an 'Either'
throwBlock :: QuasiQuoter
throwBlock = QuasiQuoter
{ quoteExp = \blockStr -> do
[e| either (throwIO . toSomeException) return =<< $(tryBlockQuoteExp C.block 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."
exceptionalValue :: String -> String
exceptionalValue typeStr =
case typeStr of
"void" -> ""
"char" -> "0"
"short" -> "0"
"long" -> "0"
"int" -> "0"
"int8_t" -> "0"
"int16_t" -> "0"
"int32_t" -> "0"
"int64_t" -> "0"
"uint8_t" -> "0"
"uint16_t" -> "0"
"uint32_t" -> "0"
"uint64_t" -> "0"
"float" -> "0"
"double" -> "0"
"bool" -> "0"
"signed char" -> "0"
"signed short" -> "0"
"signed int" -> "0"
"signed long" -> "0"
"unsigned char" -> "0"
"unsigned short" -> "0"
"unsigned int" -> "0"
"unsigned long" -> "0"
"size_t" -> "0"
"wchar_t" -> "0"
"ptrdiff_t" -> "0"
"sig_atomic_t" -> "0"
"intptr_t" -> "0"
"uintptr_t" -> "0"
"intmax_t" -> "0"
"uintmax_t" -> "0"
"clock_t" -> "0"
"time_t" -> "0"
"useconds_t" -> "0"
"suseconds_t" -> "0"
"FILE" -> "0"
"fpos_t" -> "0"
"jmp_buf" -> "0"
_ -> "{}"
tryBlockQuoteExp :: QuasiQuoter -> String -> Q Exp
tryBlockQuoteExp block blockStr = do
let (ty, body, bodyLineShift) = C.splitTypedC blockStr
_ <- C.include "HaskellException.hxx"
basePtrVarName <- newName "basePtr"
there <- location
let inlineCStr = unlines
[ ty ++ " {"
, " void** __inline_c_cpp_base_ptr__ = $(void** " ++ nameBase basePtrVarName ++ ");"
, " int* __inline_c_cpp_exception_type__ = (int*)__inline_c_cpp_base_ptr__;"
, " const char** __inline_c_cpp_error_message__ = (const char**)(__inline_c_cpp_base_ptr__ + 1);"
, " const char** __inline_c_cpp_error_typ__ = (const char**)(__inline_c_cpp_base_ptr__ + 2);"
, " std::exception_ptr** __inline_c_cpp_exception_ptr__ = (std::exception_ptr**)(__inline_c_cpp_base_ptr__ + 3);"
, " HaskellException** __inline_c_cpp_haskellexception__ = (HaskellException**)(__inline_c_cpp_base_ptr__ + 4);"
, " *__inline_c_cpp_exception_type__ = 0;"
, " try {"
, C.lineDirective (C.shiftLines (bodyLineShift - 1) there)
, body
, C.lineDirective $(C.here)
, " } catch (const HaskellException &e) {"
, " *__inline_c_cpp_exception_type__ = " ++ show ExTypeHaskellException ++ ";"
, " *__inline_c_cpp_haskellexception__ = new HaskellException(e);"
, " return " ++ exceptionalValue ty ++ ";"
, " } catch (const std::exception &e) {"
, " *__inline_c_cpp_exception_ptr__ = new std::exception_ptr(std::current_exception());"
, " *__inline_c_cpp_exception_type__ = " ++ show ExTypeStdException ++ ";"
, " setMessageOfStdException(e, __inline_c_cpp_error_message__, __inline_c_cpp_error_typ__);"
, " return " ++ exceptionalValue ty ++ ";"
, " } catch (...) {"
, " *__inline_c_cpp_exception_ptr__ = new std::exception_ptr(std::current_exception());"
, " *__inline_c_cpp_exception_type__ = " ++ show ExTypeOtherException ++ ";"
, " setCppExceptionType(__inline_c_cpp_error_typ__);"
, " return " ++ exceptionalValue ty ++ ";"
, " }"
, "}"
]
[e| handleForeignCatch $ \ $(varP basePtrVarName) -> $(quoteExp 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 C.block
, quotePat = unsupported
, quoteType = unsupported
, quoteDec = unsupported
} where
unsupported _ = fail "Unsupported quasiquotation."
bsToChars :: ByteString -> String
bsToChars = T.unpack . T.decodeUtf8With T.lenientDecode