[Haskell-beginners] FFI. unIO safety considerations

Folsk Pratima folsk0pratima at cock.li
Sat Apr 13 14:38:45 UTC 2024


import Control.Exception
import Control.Monad.ST
import Control.Monad.ST.Unsafe
import Data.Word
import Foreign.C.String
import Foreign.C.String
import Foreign.C.Types
import Foreign.Marshal
import Foreign.Ptr
import Foreign.Storable

foreign import ccall unsafe "strerror_r_portalbe.c strerror_r_portable" c_strerror_r
    :: CInt -> Ptr CChar -> CSize -> IO CInt

memSet :: Ptr a -> Word8 -> Word32 -> IO (Ptr a)
memSet ptr _ 0 = return ptr
memSet ptr byte size = do
    let voidptr = castPtr ptr :: Ptr Word8
        acts =
            map
                (\i -> pokeByteOff voidptr i byte)
                [0 .. fromIntegral (size - 1)]
    mapM_ id acts
    return ptr

strError :: Int -> String
strError errnum = runST $ unsafeIOToST ioString
  where
    baseSize :: CSize
    baseSize = 50
    ioString = run baseSize
    run :: CSize -> IO String
    run size
        | size > 100000 =
            return $
            "!!! INTERNAL strError memory leak detected, " ++
            "you can not recover !!!"
        | otherwise = do
            may <- tryIOString size
            case may of
                Just str -> return str
                Nothing -> run (size + baseSize)
    tryIOString :: CSize -> IO (Maybe String)
    tryIOString size =
        allocaBytes (fromIntegral size) $ \ptr -> do
            zeroptr <- memSet ptr 0 (fromIntegral size)
            st <- c_strerror_r (fromIntegral errnum) ptr size
            -- heuristic
            case st of
                22 -> return . return $ "Unknown error " ++ show errnum
                34 -> return Nothing
                _ -> peekCAString ptr >>= return . return

main = mapM_ (putStrLn) $ map strError [1 .. 1000]

On Fri, 12 Apr 2024 15:27:57 -0000
Folsk Pratima <folsk0pratima at cock.li> wrote:

> Also see
> [Haskell-beginners] FFI to POSIX libc; strerror (errnum);  unfreed
> memory
> 
> Exactly how unsafe this code is? What do I do about it? It is so very
> stupid to have a pure function strerror_r in the IO monad!
> 
> StrError.hs
> 
> 
> import Control.Exception
> import Control.Monad.ST
> import Control.Monad.ST.Unsafe
> import Data.Word
> import Foreign.C.String
> import Foreign.C.String
> import Foreign.C.Types
> import Foreign.Marshal
> import Foreign.Ptr
> import Foreign.Storable
> 
> foreign import ccall unsafe "strerror_r_portalbe.c
> strerror_r_portable" c_strerror_r :: CInt -> Ptr CChar -> CSize -> IO
> CInt
> 
> memSet :: Ptr a -> Word8 -> Word32 -> IO (Ptr a)
> memSet ptr _ 0 = return ptr
> memSet ptr byte size = do
>     let voidptr = castPtr ptr :: Ptr Word8
>         acts =
>             map
>                 (\i -> pokeByteOff voidptr i byte)
>                 [0 .. fromIntegral (size - 1)]
>     mapM_ id acts
>     return ptr
> 
> strError :: Int -> String
> strError errnum = runST $ unsafeIOToST ioString
>   where
>     buflen = 512 :: CSize
>     ioString =
>         allocaBytes (fromIntegral buflen) $ \ptr -> do
>             zeroptr <- memSet ptr 0 (fromIntegral buflen)
>             code <- c_strerror_r (fromIntegral errnum) ptr buflen
>             -- heuristic!
>             case code of
>                 22 -> return $ "Unknown error " ++ show errnum
>                 -- this is very dangerous, as far as I understand,
> and nobody in -- his right might should not do it if he later intends
> to -- unsafely escape IO
>                 34 ->
>                     throwIO $
>                     userError $
>                     "strError: " ++
>                     show code ++
>                     ": ERANGE: " ++
>                     "Numerical result out of range: " ++
>                     "this is internal an error, which means not
> enough space " ++ "was allocated to store the error. You can not
> recover" _ -> peekCAString ptr
> 
> main = mapM_ (putStrLn) $ map strError [1 .. 1000]
> 
> 
> 
> 
> strerror_portable.c
> 
> 
> #define _POSIX_C_SOURCE 200112L
> #include <string.h>
> 
> int
> strerror_r_portable (int e, char *c, size_t s)
> {
>     return strerror_r (e, c, s);
> }
> 
> 
> 
> 
> The C code is needed because GHC uses _GNU_SOURCE, which I personally
> do not want to use. Besides, I do not know how to predict whether GHC
> will define _GNU_SOURCE or not, so this also feels more reliable.
> 
> To compile, do
> $ mkdir lib
> $ gcc -c -o lib/portable.o strerror_portable.c
> $ ar -csr lib/portable.a lib/portable.o
> $ ghc StrError.hs lib/portable.a
> 
> No leaks, of course.
> _______________________________________________
> Beginners mailing list
> Beginners at haskell.org
> http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners



More information about the Beginners mailing list