{-# LANGUAGE FlexibleContexts, ScopedTypeVariables #-}
{-# LANGUAGE ImplicitParams, KindSignatures, ConstraintKinds #-}
module Data.GI.Base.ManagedPtr
(
newManagedPtr
, newManagedPtr'
, newManagedPtr_
, withManagedPtr
, maybeWithManagedPtr
, withManagedPtrList
, withTransient
, unsafeManagedPtrGetPtr
, unsafeManagedPtrCastPtr
, touchManagedPtr
, disownManagedPtr
, castTo
, unsafeCastTo
, newObject
, wrapObject
, unrefObject
, disownObject
, newBoxed
, wrapBoxed
, copyBoxed
, copyBoxedPtr
, freeBoxed
, disownBoxed
, wrapPtr
, newPtr
, copyBytes
) where
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative ((<$>))
#endif
import Control.Monad (when, void)
import Data.Coerce (coerce)
import Data.IORef (newIORef, readIORef, writeIORef, IORef)
import Data.Maybe (isNothing, isJust)
#if !MIN_VERSION_base(4,11,0)
import Data.Monoid ((<>))
#endif
import Foreign.C (CInt(..))
import Foreign.Ptr (Ptr, FunPtr, castPtr, nullPtr)
import Foreign.ForeignPtr (FinalizerPtr, touchForeignPtr, newForeignPtr_)
import qualified Foreign.Concurrent as FC
import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
import Data.GI.Base.BasicTypes
import Data.GI.Base.CallStack (CallStack, HasCallStack,
prettyCallStack, callStack)
import Data.GI.Base.Utils
import qualified Data.Text as T
import System.IO (hPutStrLn, stderr)
import System.Environment (lookupEnv)
newManagedPtr :: HasCallStack => Ptr a -> IO () -> IO (ManagedPtr a)
newManagedPtr ptr finalizer = do
isDisownedRef <- newIORef Nothing
dbgMode <- isJust <$> lookupEnv "HASKELL_GI_DEBUG_MEM"
let dbgCallStack = if dbgMode
then Just callStack
else Nothing
fPtr <- FC.newForeignPtr ptr (ownedFinalizer finalizer ptr dbgCallStack isDisownedRef)
return $ ManagedPtr {
managedForeignPtr = fPtr
, managedPtrAllocCallStack = dbgCallStack
, managedPtrIsDisowned = isDisownedRef
}
ownedFinalizer :: IO () -> Ptr a -> Maybe CallStack -> IORef (Maybe CallStack)
-> IO ()
ownedFinalizer finalizer ptr allocCallStack callStackRef = do
cs <- readIORef callStackRef
when (isNothing cs) $ do
maybe (return ()) (printAllocDebug ptr) allocCallStack
finalizer
printAllocDebug :: Ptr a -> CallStack -> IO ()
printAllocDebug ptr allocCS =
(dbgLog . T.pack) ("Releasing <" <> show ptr <> ">. "
<> "Callstack for allocation was:\n"
<> prettyCallStack allocCS <> "\n\n")
foreign import ccall "dynamic"
mkFinalizer :: FinalizerPtr a -> Ptr a -> IO ()
newManagedPtr' :: HasCallStack => FinalizerPtr a -> Ptr a -> IO (ManagedPtr a)
newManagedPtr' finalizer ptr = newManagedPtr ptr (mkFinalizer finalizer ptr)
newManagedPtr_ :: Ptr a -> IO (ManagedPtr a)
newManagedPtr_ ptr = do
isDisownedRef <- newIORef Nothing
fPtr <- newForeignPtr_ ptr
return $ ManagedPtr {
managedForeignPtr = fPtr
, managedPtrAllocCallStack = Nothing
, managedPtrIsDisowned = isDisownedRef
}
disownManagedPtr :: forall a. (HasCallStack, ManagedPtrNewtype a) => a -> IO (Ptr a)
disownManagedPtr managed = do
ptr <- unsafeManagedPtrGetPtr managed
writeIORef (managedPtrIsDisowned c) (Just callStack)
return ptr
where c = coerce managed :: ManagedPtr ()
withManagedPtr :: (HasCallStack, ManagedPtrNewtype a) => a -> (Ptr a -> IO c) -> IO c
withManagedPtr managed action = do
ptr <- unsafeManagedPtrGetPtr managed
result <- action ptr
touchManagedPtr managed
return result
maybeWithManagedPtr :: (HasCallStack, ManagedPtrNewtype a) => Maybe a -> (Ptr a -> IO c) -> IO c
maybeWithManagedPtr Nothing action = action nullPtr
maybeWithManagedPtr (Just managed) action = withManagedPtr managed action
withManagedPtrList :: (HasCallStack, ManagedPtrNewtype a) => [a] -> ([Ptr a] -> IO c) -> IO c
withManagedPtrList managedList action = do
ptrs <- mapM unsafeManagedPtrGetPtr managedList
result <- action ptrs
mapM_ touchManagedPtr managedList
return result
withTransient :: (HasCallStack, ManagedPtrNewtype a)
=> (ManagedPtr a -> a) -> Ptr a -> (a -> IO b) -> IO b
withTransient constructor ptr action = do
managed <- constructor <$> newManagedPtr_ ptr
r <- action managed
_ <- disownManagedPtr managed
return r
unsafeManagedPtrGetPtr :: (HasCallStack, ManagedPtrNewtype a) => a -> IO (Ptr a)
unsafeManagedPtrGetPtr = unsafeManagedPtrCastPtr
unsafeManagedPtrCastPtr :: forall a b. (HasCallStack, ManagedPtrNewtype a) =>
a -> IO (Ptr b)
unsafeManagedPtrCastPtr m = do
let c = coerce m :: ManagedPtr ()
ptr = (castPtr . unsafeForeignPtrToPtr . managedForeignPtr) c
disowned <- readIORef (managedPtrIsDisowned c)
maybe (return ptr) (notOwnedWarning ptr) disowned
notOwnedWarning :: HasCallStack => Ptr a -> CallStack -> IO (Ptr a)
notOwnedWarning ptr cs = do
hPutStrLn stderr ("WARNING: Accessing a disowned pointer <" ++ show ptr
++ ">, this may lead to crashes.\n\n"
++ "• Callstack for the unsafe access to the pointer:\n"
++ prettyCallStack callStack ++ "\n\n"
++ "• The pointer was disowned at:\n"
++ prettyCallStack cs ++ "\n")
return ptr
touchManagedPtr :: forall a. ManagedPtrNewtype a => a -> IO ()
touchManagedPtr m = let c = coerce m :: ManagedPtr ()
in (touchForeignPtr . managedForeignPtr) c
foreign import ccall unsafe "check_object_type"
c_check_object_type :: Ptr o -> CGType -> CInt
castTo :: forall o o'. (GObject o, GObject o') =>
(ManagedPtr o' -> o') -> o -> IO (Maybe o')
castTo constructor obj =
withManagedPtr obj $ \objPtr -> do
GType t <- gobjectType (undefined :: o')
if c_check_object_type objPtr t /= 1
then return Nothing
else Just <$> newObject constructor objPtr
unsafeCastTo :: forall o o'. (HasCallStack, GObject o, GObject o') =>
(ManagedPtr o' -> o') -> o -> IO o'
unsafeCastTo constructor obj =
withManagedPtr obj $ \objPtr -> do
GType t <- gobjectType (undefined :: o')
if c_check_object_type objPtr t /= 1
then do
srcType <- gobjectType obj >>= gtypeName
destType <- gobjectType (undefined :: o') >>= gtypeName
error $ "unsafeCastTo :: invalid conversion from " ++ srcType ++ " to "
++ destType ++ " requested."
else newObject constructor objPtr
foreign import ccall "&dbg_g_object_unref"
ptr_to_g_object_unref :: FunPtr (Ptr a -> IO ())
foreign import ccall "g_object_ref_sink" g_object_ref_sink ::
Ptr a -> IO (Ptr a)
nullPtrWarning :: String -> CallStack -> IO ()
nullPtrWarning fn cs =
hPutStrLn stderr ("WARNING: Trying to wrap a null pointer in " ++ quotedFn
++ ", this may lead to crashes.\n\n"
++ "• Callstack for the unsafe call to "
++ quotedFn ++ ":\n"
++ prettyCallStack cs ++ "\n\n"
++ "This is probably a bug in the introspection data,\n"
++ "please report it at https://github.com/haskell-gi/haskell-gi/issues")
where quotedFn = "‘" ++ fn ++ "’"
newObject :: (HasCallStack, GObject a, GObject b) =>
(ManagedPtr a -> a) -> Ptr b -> IO a
newObject constructor ptr = do
when (ptr == nullPtr) (nullPtrWarning "newObject" callStack)
void $ g_object_ref_sink ptr
fPtr <- newManagedPtr' ptr_to_g_object_unref $ castPtr ptr
return $! constructor fPtr
wrapObject :: forall a b. (HasCallStack, GObject a, GObject b) =>
(ManagedPtr a -> a) -> Ptr b -> IO a
wrapObject constructor ptr = do
when (ptr == nullPtr) (nullPtrWarning "wrapObject" callStack)
fPtr <- newManagedPtr' ptr_to_g_object_unref $ castPtr ptr
return $! constructor fPtr
foreign import ccall unsafe "dbg_g_object_unref"
dbg_g_object_unref :: Ptr a -> IO ()
unrefObject :: (HasCallStack, GObject a) => a -> IO ()
unrefObject obj = withManagedPtr obj $ \ptr -> do
dbgDealloc obj
dbg_g_object_unref ptr
foreign import ccall "dbg_g_object_disown"
dbg_g_object_disown :: Ptr a -> IO ()
disownObject :: (HasCallStack, GObject a) => a -> IO (Ptr b)
disownObject obj = withManagedPtr obj $ \ptr -> do
dbgDealloc obj
dbg_g_object_disown ptr
castPtr <$> disownManagedPtr obj
foreign import ccall unsafe "boxed_free_helper" boxed_free_helper ::
CGType -> Ptr a -> IO ()
foreign import ccall "g_boxed_copy" g_boxed_copy ::
CGType -> Ptr a -> IO (Ptr a)
newBoxed :: forall a. (HasCallStack, BoxedObject a) => (ManagedPtr a -> a) -> Ptr a -> IO a
newBoxed constructor ptr = do
GType gtype <- boxedType (undefined :: a)
ptr' <- g_boxed_copy gtype ptr
fPtr <- newManagedPtr ptr' (boxed_free_helper gtype ptr')
return $! constructor fPtr
wrapBoxed :: forall a. (HasCallStack, BoxedObject a) => (ManagedPtr a -> a) -> Ptr a -> IO a
wrapBoxed constructor ptr = do
GType gtype <- boxedType (undefined :: a)
fPtr <- newManagedPtr ptr (boxed_free_helper gtype ptr)
return $! constructor fPtr
copyBoxed :: forall a. (HasCallStack, BoxedObject a) => a -> IO (Ptr a)
copyBoxed b = do
GType gtype <- boxedType b
withManagedPtr b (g_boxed_copy gtype)
copyBoxedPtr :: forall a. BoxedObject a => Ptr a -> IO (Ptr a)
copyBoxedPtr ptr = do
GType gtype <- boxedType (undefined :: a)
g_boxed_copy gtype ptr
foreign import ccall "g_boxed_free" g_boxed_free ::
CGType -> Ptr a -> IO ()
freeBoxed :: forall a. (HasCallStack, BoxedObject a) => a -> IO ()
freeBoxed boxed = do
GType gtype <- boxedType (undefined :: a)
ptr <- disownManagedPtr boxed
dbgDealloc boxed
g_boxed_free gtype ptr
disownBoxed :: (HasCallStack, BoxedObject a) => a -> IO (Ptr a)
disownBoxed = disownManagedPtr
wrapPtr :: (HasCallStack, WrappedPtr a) => (ManagedPtr a -> a) -> Ptr a -> IO a
wrapPtr constructor ptr = do
fPtr <- case wrappedPtrFree of
Nothing -> newManagedPtr_ ptr
Just finalizer -> newManagedPtr' finalizer ptr
return $! constructor fPtr
newPtr :: (HasCallStack, WrappedPtr a) => (ManagedPtr a -> a) -> Ptr a -> IO a
newPtr constructor ptr = do
tmpWrap <- newManagedPtr_ ptr
ptr' <- wrappedPtrCopy (constructor tmpWrap)
return $! ptr'
copyBytes :: WrappedPtr a => Int -> Ptr a -> IO (Ptr a)
copyBytes size ptr = do
ptr' <- wrappedPtrCalloc
memcpy ptr' ptr size
return ptr'
foreign import ccall unsafe "g_thread_self" g_thread_self :: IO (Ptr ())
dbgDealloc :: (HasCallStack, ManagedPtrNewtype a) => a -> IO ()
dbgDealloc m = do
env <- lookupEnv "HASKELL_GI_DEBUG_MEM"
case env of
Nothing -> return ()
Just _ -> do
let mPtr = coerce m :: ManagedPtr ()
ptr = (unsafeForeignPtrToPtr . managedForeignPtr) mPtr
threadPtr <- g_thread_self
hPutStrLn stderr ("Releasing <" ++ show ptr ++ "> from thread ["
++ show threadPtr ++ "].\n"
++ (case managedPtrAllocCallStack mPtr of
Just allocCS -> "• Callstack for allocation:\n"
++ prettyCallStack allocCS ++ "\n\n"
Nothing -> "")
++ "• CallStack for deallocation:\n"
++ prettyCallStack callStack ++ "\n")