guarded-allocation-0.0.1: src/Foreign/Marshal/Array/Guarded/Debug.hs
module Foreign.Marshal.Array.Guarded.Debug (
-- * immutable arrays
create,
alloca,
-- * mutable arrays
MutablePtr,
new,
withMutablePtr,
thaw,
thawInplace,
freeze,
freezeInplace,
) where
import qualified Foreign.Marshal.Array.Guarded.Plain as Plain
import Foreign.Marshal.Array
(mallocArray, allocaArray, pokeArray, copyArray, advancePtr)
import Foreign.Marshal.Alloc (free)
import Foreign.Storable (Storable, peekByteOff, sizeOf)
import Foreign.Concurrent (newForeignPtr, addForeignPtrFinalizer)
import Foreign.ForeignPtr (ForeignPtr, withForeignPtr)
import Foreign.Ptr (Ptr, castPtr)
import Control.Monad (when)
import Control.Applicative ((<$>))
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import Data.Foldable (for_)
import Data.Word (Word8)
import Text.Printf (printf)
{- |
Array creation with additional immutability check, electrical fence
and pollution of uncleaned memory.
The function checks that the array is not altered anymore after creation.
-}
create :: (Storable a) => Int -> (Ptr a -> IO b) -> IO (ForeignPtr a, b)
create = flip asTypeOf Plain.create $ \size f -> do
let border = 64
let fullSize = size + 2*border
ptrApre <- mallocArray fullSize
ptrsA@(_ptrApre, ptrA, _ptrApost) <- fillAll border size ptrApre
result <- f ptrA
checkAll border ptrsA
ptrB <- copyToNew size ptrA
fmap (flip (,) result) $ newForeignPtr ptrA $ do
verify size ptrA ptrB
trash fullSize ptrApre
free ptrApre
free ptrB
alloca :: (Storable a) => Int -> (Ptr a -> IO b) -> IO b
alloca = flip asTypeOf Plain.alloca $ \size f -> do
let border = 64
let fullSize = size + 2*border
allocaArray fullSize $ \ptrPre -> do
ptrs@(_ptrPre, ptr, _ptrPost) <- fillAll border size ptrPre
result <- f ptr
checkAll border ptrs
trash fullSize ptrPre
return result
data MutablePtr a =
MutablePtr {
_mutableSize :: !Int,
_mutableFPtr :: !(ForeignPtr a),
_mutableFrozenRef :: !(IORef Bool)
}
new :: (Storable a) => Int -> IO (MutablePtr a)
new size = do
let border = 64
let fullSize = size + 2*border
ptrApre <- mallocArray fullSize
ptrsA <- fillAll border size ptrApre
newMutablePtr size =<< newCheckedForeignPtr border fullSize ptrsA
withMutablePtr :: MutablePtr a -> (Ptr a -> IO b) -> IO b
withMutablePtr (MutablePtr _size fptr frozenRef) f = do
checkFrozen "withMutablePtr" frozenRef
withForeignPtr fptr f
thaw :: (Storable a) => Int -> ForeignPtr a -> IO (MutablePtr a)
thaw size fptrB = do
let border = 64
let fullSize = size + 2*border
ptrApre <- mallocArray fullSize
ptrsA@(_ptrApre, ptrA, _ptrApost) <- fillBorder border size ptrApre
withForeignPtr fptrB $ \ptrB -> copyArray ptrA ptrB size
newMutablePtr size =<< newCheckedForeignPtr border fullSize ptrsA
newCheckedForeignPtr ::
(Storable a) => Int -> Int -> (Ptr a, Ptr a, Ptr a) -> IO (ForeignPtr a)
newCheckedForeignPtr border fullSize ptrsA@(ptrApre, ptrA, _ptrApost) =
newForeignPtr ptrA $ do
checkAll border ptrsA
trash fullSize ptrApre
free ptrApre
{- |
There is not much we can debug here.
We cannot add a fence since we do not copy the immutable array.
We could debug more
if we would implement 'thawInplace' as a redirection to 'thaw'.
-}
thawInplace :: (Storable a) => Int -> ForeignPtr a -> IO (MutablePtr a)
thawInplace = newMutablePtr
{- |
The 'size' parameter must match the size passed to 'new'.
This is checked.
-}
freeze :: (Storable a) => Int -> MutablePtr a -> IO (ForeignPtr a)
freeze freezeSize (MutablePtr size fptr frozenRef) = do
checkSize freezeSize size
checkFrozen "freeze" frozenRef
ptrA <- withForeignPtr fptr $ copyToNew size
ptrB <- withForeignPtr fptr $ copyToNew size
newForeignPtr ptrA $ do
verify size ptrA ptrB
trash size ptrA
free ptrA
free ptrB
{- |
'freezeInplace' must be the last operation on the 'MutablePtr'
and its associated array.
The 'size' parameter must match the size passed to 'new'.
This is checked.
It may fail if the 'MutablePtr' was constructed using 'thawInplace'
from a 'ForeignPtr' with a C finalizer.
-}
freezeInplace :: (Storable a) => Int -> MutablePtr a -> IO (ForeignPtr a)
freezeInplace freezeSize (MutablePtr size fptr frozenRef) = do
checkSize freezeSize size
checkFrozen "freezeInplace" frozenRef
writeIORef frozenRef True
withForeignPtr fptr $ \ptrA -> do
ptrB <- copyToNew size ptrA
addForeignPtrFinalizer fptr $ do
verify size ptrA ptrB
free ptrB
return fptr
newMutablePtr :: Int -> ForeignPtr a -> IO (MutablePtr a)
newMutablePtr size fptr = MutablePtr size fptr <$> newIORef False
checkSize :: Int -> Int -> IO ()
checkSize freezeSize size =
when (freezeSize/=size) $ error $
printf "allocation (%d) and freeze (%d) size differ" size freezeSize
checkFrozen :: String -> IORef Bool -> IO ()
checkFrozen name frozenRef =
flip when (error $ name ++ ": array already frozen")
=<< readIORef frozenRef
fillAll :: (Storable a) => Int -> Int -> Ptr a -> IO (Ptr a, Ptr a, Ptr a)
fillAll border size ptrPre = do
ptrs@(_ptrPre, ptr, _ptrPost) <- fillBorder border size ptrPre
fill ptr size [0xDE,0xAD,0xF0,0x0D]
return ptrs
fillBorder :: (Storable a) => Int -> Int -> Ptr a -> IO (Ptr a, Ptr a, Ptr a)
fillBorder border size ptrPre = do
let ptr = advancePtr ptrPre border
let ptrPost = advancePtr ptr size
fill ptrPre border [0xAB,0xAD,0xCA,0xFE]
fill ptrPost border [0xAB,0xAD,0xCA,0xFE]
return (ptrPre, ptr, ptrPost)
checkAll :: (Storable a) => Int -> (Ptr a, Ptr a, Ptr a) -> IO ()
checkAll border (ptrPre, _ptr, ptrPost) = do
check "leading" ptrPre border [0xAB,0xAD,0xCA,0xFE]
check "trailing" ptrPost border [0xAB,0xAD,0xCA,0xFE]
trash :: (Storable a) => Int -> Ptr a -> IO ()
trash fullSize ptrPre = fill ptrPre fullSize [0xDE,0xAD,0xBE,0xEF]
{-# INLINE fill #-}
fill :: (Storable a) => Ptr a -> Int -> [Word8] -> IO ()
fill ptr n bytes =
pokeArray (castPtr ptr) $ take (arraySize ptr n) $ cycle bytes
{-# INLINE check #-}
check :: (Storable a) => String -> Ptr a -> Int -> [Word8] -> IO ()
check name ptr n bytes =
for_ (take (arraySize ptr n) $ zip [0..] $ cycle bytes) $ \(i,b) -> do
a <- peekByteOff ptr i
when (a/=(b::Word8)) $
error $ "damaged " ++ name ++ " fence at position " ++ show i
copyToNew :: (Storable a) => Int -> Ptr a -> IO (Ptr a)
copyToNew size ptrA = do
ptrB <- mallocArray size
copyArray ptrB ptrA size
return ptrB
verify :: (Storable a) => Int -> Ptr a -> Ptr a -> IO ()
verify size ptrA ptrB =
for_ (take (arraySize ptrA size) [0..]) $ \i -> do
a <- peekByteOff ptrA i
b <- peekByteOff ptrB i
when (a/=(b::Word8)) $
error $ "immutable array was altered at byte position " ++ show i
arraySize :: (Storable a) => Ptr a -> Int -> Int
arraySize ptr n = arraySizeAux ptr n $ error "arraySize: undefined element"
{- |
Correct size computation should also respect padding caused by alignment.
However, mallocArray uses this simple arithmetic.
-}
arraySizeAux :: (Storable a) => Ptr a -> Int -> a -> Int
arraySizeAux _ n a = n * sizeOf a