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 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
|
-- Routines dealing with memory management in marshalling functions.
module Data.GI.CodeGen.Transfer
( freeInArg
, freeInArgOnError
, freeContainerType
) where
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative ((<$>), (<*>))
#endif
import Control.Monad (when)
#if !MIN_VERSION_base(4,11,0)
import Data.Monoid ((<>))
#endif
import Data.Text (Text)
import Data.GI.CodeGen.API
import Data.GI.CodeGen.Code
import Data.GI.CodeGen.Conversions
import Data.GI.CodeGen.GObject
import Data.GI.CodeGen.Type
import Data.GI.CodeGen.Util
-- Basic primitives for freeing the given types. Types that point to
-- Haskell objects with memory managed by the GC should not be freed
-- here. For containers this is only for freeing the container itself,
-- freeing the elements is done separately.
basicFreeFn :: Type -> Maybe Text
basicFreeFn (TBasicType TUTF8) = Just "freeMem"
basicFreeFn (TBasicType TFileName) = Just "freeMem"
basicFreeFn (TBasicType _) = Nothing
basicFreeFn (TInterface _) = Nothing
-- Just passed along
basicFreeFn (TCArray False (-1) (-1) (TBasicType TUInt8)) = Nothing
basicFreeFn (TCArray{}) = Just "freeMem"
basicFreeFn (TGArray _) = Just "unrefGArray"
basicFreeFn (TPtrArray _) = Just "unrefPtrArray"
basicFreeFn (TByteArray) = Just "unrefGByteArray"
basicFreeFn (TGList _) = Just "g_list_free"
basicFreeFn (TGSList _) = Just "g_slist_free"
basicFreeFn (TGHash _ _) = Just "unrefGHashTable"
basicFreeFn (TError) = Nothing
basicFreeFn (TVariant) = Nothing
basicFreeFn (TGValue) = Nothing
basicFreeFn (TParamSpec) = Nothing
basicFreeFn (TGClosure _) = Nothing
-- Basic free primitives in the case that an error occured. This is
-- run in the exception handler, so any type which we ref/allocate
-- with the expectation that the called function will consume it (on
-- TransferEverything) should be freed here.
basicFreeFnOnError :: Type -> Transfer -> CodeGen e (Maybe Text)
basicFreeFnOnError (TBasicType TUTF8) _ = return $ Just "freeMem"
basicFreeFnOnError (TBasicType TFileName) _ = return $ Just "freeMem"
basicFreeFnOnError (TBasicType _) _ = return Nothing
basicFreeFnOnError TVariant transfer =
return $ if transfer == TransferEverything
then Just "unrefGVariant"
else Nothing
basicFreeFnOnError TParamSpec transfer =
return $ if transfer == TransferEverything
then Just "unrefGParamSpec"
else Nothing
basicFreeFnOnError TGValue transfer =
return $ if transfer == TransferEverything
then Just "SP.freeMem"
else Nothing
basicFreeFnOnError (TGClosure _) transfer =
return $ if transfer == TransferEverything
then Just "B.GClosure.unrefGClosure"
else Nothing
basicFreeFnOnError t@(TInterface _) transfer = do
api <- findAPI t
case api of
Just (APIObject _) -> if transfer == TransferEverything
then do
isGO <- isGObject t
if isGO
then return $ Just "unrefObject"
else do
line "-- XXX Transfer a non-GObject object"
return Nothing
else return Nothing
Just (APIInterface _) -> if transfer == TransferEverything
then do
isGO <- isGObject t
if isGO
then return $ Just "unrefObject"
else do
line "-- XXX Transfer a non-GObject object"
return Nothing
else return Nothing
Just (APIUnion u) -> if transfer == TransferEverything
then if unionIsBoxed u
then return $ Just "freeBoxed"
else do
line "-- XXX Transfer a non-boxed union"
return Nothing
else return Nothing
Just (APIStruct s) -> if transfer == TransferEverything
then if structIsBoxed s
then return $ Just "freeBoxed"
else do
line "-- XXX Transfer a non-boxed struct"
return Nothing
else return Nothing
_ -> return Nothing
-- Just passed along
basicFreeFnOnError (TCArray False (-1) (-1) (TBasicType TUInt8)) _ = return Nothing
basicFreeFnOnError (TCArray{}) _ = return $ Just "freeMem"
basicFreeFnOnError (TGArray _) _ = return $ Just "unrefGArray"
basicFreeFnOnError (TPtrArray _) _ = return $ Just "unrefPtrArray"
basicFreeFnOnError (TByteArray) _ = return $ Just "unrefGByteArray"
basicFreeFnOnError (TGList _) _ = return $ Just "g_list_free"
basicFreeFnOnError (TGSList _) _ = return $ Just "g_slist_free"
basicFreeFnOnError (TGHash _ _) _ = return $ Just "unrefGHashTable"
basicFreeFnOnError (TError) _ = return Nothing
-- Free just the container, but not the elements.
freeContainer :: Type -> Text -> CodeGen e [Text]
freeContainer t label =
case basicFreeFn t of
Nothing -> return []
Just fn -> return [fn <> " " <> label]
-- Free one element using the given free function.
freeElem :: Type -> Text -> Text -> ExcCodeGen Text
freeElem t label free =
case elementTypeAndMap t undefined of
Nothing -> return free
Just (TCArray False _ _ _, _) ->
badIntroError $ "Element type in container \"" <> label <>
"\" is an array of unknown length."
Just (innerType, mapFn) -> do
let elemFree = "freeElemOf" <> ucFirst label
fullyFree innerType (prime label) >>= \case
Nothing -> return $ free <> " e"
Just elemInnerFree -> do
line $ "let " <> elemFree <> " e = " <> mapFn <> " "
<> elemInnerFree <> " e >> " <> free <> " e"
return elemFree
-- Construct a function to free the memory associated with a type, and
-- recursively free any elements of this type in case that it is a
-- container.
fullyFree :: Type -> Text -> ExcCodeGen (Maybe Text)
fullyFree t label = case basicFreeFn t of
Nothing -> return Nothing
Just free -> Just <$> freeElem t label free
-- Like fullyFree, but free the toplevel element using basicFreeFnOnError.
fullyFreeOnError :: Type -> Text -> Transfer -> ExcCodeGen (Maybe Text)
fullyFreeOnError t label transfer =
basicFreeFnOnError t transfer >>= \case
Nothing -> return Nothing
Just free -> Just <$> freeElem t label free
-- Free the elements in a container type.
freeElements :: Type -> Text -> Text -> ExcCodeGen [Text]
freeElements t label len =
case elementTypeAndMap t len of
Nothing -> return []
Just (inner, mapFn) ->
fullyFree inner label >>= \case
Nothing -> return []
Just innerFree ->
return [mapFn <> " " <> innerFree <> " " <> label]
-- | Free a container and/or the contained elements, depending on the
-- transfer mode.
freeContainerType :: Transfer -> Type -> Text -> Text -> ExcCodeGen ()
freeContainerType transfer (TGHash _ _) label _ = freeGHashTable transfer label
freeContainerType transfer t label len = do
when (transfer == TransferEverything) $
mapM_ line =<< freeElements t label len
when (transfer /= TransferNothing) $
mapM_ line =<< freeContainer t label
freeGHashTable :: Transfer -> Text -> ExcCodeGen ()
freeGHashTable TransferNothing _ = return ()
freeGHashTable TransferContainer label =
notImplementedError $ "Hash table argument with transfer = Container? "
<> label
-- Hash tables support setting a free function for keys and elements,
-- we assume that these are always properly set. The worst that can
-- happen this way is a memory leak, as opposed to a double free if we
-- try do free anything here.
freeGHashTable TransferEverything label =
line $ "unrefGHashTable " <> label
-- Free the elements of a container type in the case an error ocurred,
-- in particular args that should have been transferred did not get
-- transfered.
freeElementsOnError :: Transfer -> Type -> Text -> Text ->
ExcCodeGen [Text]
freeElementsOnError transfer t label len =
case elementTypeAndMap t len of
Nothing -> return []
Just (inner, mapFn) ->
fullyFreeOnError inner label transfer >>= \case
Nothing -> return []
Just innerFree ->
return [mapFn <> " " <> innerFree <> " " <> label]
freeIn :: Transfer -> Type -> Text -> Text -> ExcCodeGen [Text]
freeIn transfer (TGHash _ _) label _ =
freeInGHashTable transfer label
freeIn transfer t label len =
case transfer of
TransferNothing -> (<>) <$> freeElements t label len <*> freeContainer t label
TransferContainer -> freeElements t label len
TransferEverything -> return []
freeInOnError :: Transfer -> Type -> Text -> Text -> ExcCodeGen [Text]
freeInOnError transfer (TGHash _ _) label _ =
freeInGHashTable transfer label
freeInOnError transfer t label len =
(<>) <$> freeElementsOnError transfer t label len
<*> freeContainer t label
-- See freeGHashTable above.
freeInGHashTable :: Transfer -> Text -> ExcCodeGen [Text]
freeInGHashTable TransferEverything _ = return []
freeInGHashTable TransferContainer label =
notImplementedError $ "Hash table argument with TransferContainer? "
<> label
freeInGHashTable TransferNothing label = return ["unrefGHashTable " <> label]
freeOut :: Text -> CodeGen e [Text]
freeOut label = return ["freeMem " <> label]
-- | Given an input argument to a C callable, and its label in the code,
-- return the list of actions relevant to freeing the memory allocated
-- for the argument (if appropriate, depending on the ownership
-- transfer semantics of the callable).
freeInArg :: Arg -> Text -> Text -> ExcCodeGen [Text]
freeInArg arg label len = do
-- Arguments that we alloc ourselves do not always need to be freed,
-- they will sometimes be soaked up by the wrapPtr constructor, or
-- they will be DirectionIn.
if willWrap arg
then return []
else case direction arg of
DirectionIn -> freeIn (transfer arg) (argType arg) label len
DirectionOut -> freeOut label
DirectionInout -> freeOut label
-- Whether memory ownership of the pointer passed in to the function
-- will be assumed by the C->Haskell wrapper.
where willWrap :: Arg -> Bool
willWrap = argCallerAllocates
-- | Same thing as freeInArg, but called in case the call to C didn't
-- succeed. We thus free everything we allocated in preparation for
-- the call, including args that would have been transferred to C.
freeInArgOnError :: Arg -> Text -> Text -> ExcCodeGen [Text]
freeInArgOnError arg label len =
case direction arg of
DirectionIn -> freeInOnError (transfer arg) (argType arg) label len
DirectionOut -> freeOut label
DirectionInout ->
-- Caller-allocates arguments are like "in" arguments for
-- memory management purposes.
if argCallerAllocates arg
then freeInOnError (transfer arg) (argType arg) label len
else freeOut label
|