{-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE ImplicitParams #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeOperators #-} module TestMemory ( memoryTests ) where import Control.Lens ( (^.), _1, _2 ) import Control.Monad ( foldM, forM_, void ) import Data.Foldable ( foldlM ) import qualified Data.Vector as V import qualified Test.Tasty as T import Test.Tasty.HUnit ( testCase, (@=?), assertFailure ) import qualified Data.BitVector.Sized as BV import Data.Parameterized.Context as Ctx import Data.Parameterized.NatRepr ( knownNat ) import Data.Parameterized.Nonce ( withIONonceGenerator ) import qualified What4.Expr as WE import qualified What4.Expr.Builder as WE import qualified What4.Config as What4 import qualified What4.Interface as What4 import What4.ProblemFeatures ( noFeatures ) import qualified What4.Protocol.Online as W4O import qualified What4.SatResult as W4Sat import qualified Lang.Crucible.Backend as CB import qualified Lang.Crucible.Backend.Online as CBO import qualified Lang.Crucible.Simulator as CS import qualified Lang.Crucible.Types as Crucible import Lang.Crucible.LLVM.DataLayout ( noAlignment ) import qualified Lang.Crucible.LLVM.DataLayout as LLVMD import Lang.Crucible.LLVM.MemModel ( doLoad, doStore, projectLLVM_bv, ptrAdd ) import qualified Lang.Crucible.LLVM.MemModel as LLVMMem import qualified Lang.Crucible.LLVM.MemModel.Generic as LLVMMemG memoryTests :: T.TestTree memoryTests = T.testGroup "Memory" [ testArrayStride , testMemAllocs , testMemWritesIndexed , testMemArrayWithConstants , testMemArray , testPointerStore , testStructStore , testMemArrayCopy , testMemArraySet , testMemInvalidate ] withMem :: LLVMD.EndianForm -> (forall bak sym scope solver st fs wptr . ( sym ~ WE.ExprBuilder scope st fs , bak ~ CBO.OnlineBackend solver scope st fs , CB.IsSymBackend sym bak , LLVMMem.HasLLVMAnn sym , W4O.OnlineSolver solver , LLVMMem.HasPtrWidth wptr , ?memOpts :: LLVMMem.MemOptions ) => bak -> LLVMMem.MemImpl sym -> IO a) -> IO a withMem endianess action = withIONonceGenerator $ \nonce_gen -> do sym <- WE.newExprBuilder WE.FloatIEEERepr WE.EmptyExprBuilderState nonce_gen CBO.withZ3OnlineBackend sym CBO.NoUnsatFeatures noFeatures $ \bak -> do let ?ptrWidth = knownNat @64 let ?recordLLVMAnnotation = \_ _ _ -> pure () let ?memOpts = LLVMMem.defaultMemOptions mem <- LLVMMem.emptyMem endianess action bak mem setCacheTerms :: CB.IsSymInterface sym => sym -> Bool ->IO () setCacheTerms sym cache_terms_option = do cache_terms_setting <- What4.getOptionSetting WE.cacheTerms $ What4.getConfiguration sym void $ What4.setOpt cache_terms_setting cache_terms_option userSymbol' :: String -> What4.SolverSymbol userSymbol' s = case What4.userSymbol s of Left e -> error $ show e Right symbol -> symbol assume :: (CB.IsSymBackend sym bak) => bak -> What4.Pred sym -> IO () assume bak p = do let sym = CB.backendGetSym bak loc <- What4.getCurrentProgramLoc sym CB.addAssumption bak (CB.GenericAssumption loc "assume" p) checkSat :: W4O.OnlineSolver solver => CBO.OnlineBackend solver scope st fs -> WE.BoolExpr scope -> IO (W4Sat.SatResult () ()) checkSat bak p = let err = fail "Online solving not enabled!" in CBO.withSolverProcess bak err $ \proc -> W4O.checkSatisfiable proc "" p testArrayStride :: T.TestTree testArrayStride = testCase "array stride" $ withMem LLVMD.BigEndian $ \bak mem0 -> do let sym = CB.backendGetSym bak sz <- What4.bvLit sym ?ptrWidth $ BV.mkBV ?ptrWidth (1024 * 1024) (base_ptr, mem1) <- LLVMMem.mallocRaw bak mem0 sz noAlignment let byte_type_repr = Crucible.baseToType $ What4.BaseBVRepr $ knownNat @8 let byte_storage_type = LLVMMem.bitvectorType 1 let ptr_byte_repr = LLVMMem.LLVMPointerRepr $ knownNat @8 init_array_val <- LLVMMem.LLVMValArray byte_storage_type <$> V.generateM (1024 * 1024) (\i -> LLVMMem.packMemValue sym byte_storage_type byte_type_repr =<< What4.bvLit sym (knownNat @8) (BV.mkBV knownNat (fromIntegral (mod i (512 * 1024))))) mem2 <- LLVMMem.storeRaw bak mem1 base_ptr (LLVMMem.arrayType (1024 * 1024) byte_storage_type) noAlignment init_array_val stride <- What4.bvLit sym ?ptrWidth $ BV.mkBV ?ptrWidth (512 * 1024) i <- What4.freshConstant sym (userSymbol' "i") $ What4.BaseBVRepr ?ptrWidth ptr_i <- ptrAdd sym ?ptrWidth base_ptr =<< What4.bvMul sym stride i ptr_i' <- ptrAdd sym ?ptrWidth ptr_i =<< What4.bvLit sym ?ptrWidth (BV.one ?ptrWidth) zero_bv <- What4.bvLit sym (knownNat @8) (BV.zero knownNat) mem3 <- doStore bak mem2 ptr_i byte_type_repr byte_storage_type noAlignment zero_bv one_bv <- What4.bvLit sym (knownNat @8) (BV.one knownNat) mem4 <- doStore bak mem3 ptr_i' byte_type_repr byte_storage_type noAlignment one_bv at_0_val <- projectLLVM_bv bak =<< doLoad bak mem4 base_ptr byte_storage_type ptr_byte_repr noAlignment (Just (BV.zero knownNat)) @=? What4.asBV at_0_val j <- What4.freshConstant sym (userSymbol' "j") $ What4.BaseBVRepr ?ptrWidth ptr_j <- ptrAdd sym ?ptrWidth base_ptr =<< What4.bvMul sym stride j ptr_j' <- ptrAdd sym ?ptrWidth ptr_j =<< What4.bvLit sym ?ptrWidth (BV.one ?ptrWidth) at_j_val <- projectLLVM_bv bak =<< doLoad bak mem4 ptr_j byte_storage_type ptr_byte_repr noAlignment (Just (BV.zero knownNat)) @=? What4.asBV at_j_val at_j'_val <- projectLLVM_bv bak =<< doLoad bak mem4 ptr_j' byte_storage_type ptr_byte_repr noAlignment (Just (BV.one knownNat)) @=? What4.asBV at_j'_val allocFreshArray :: ( CB.IsSymBackend sym bak, LLVMMem.HasLLVMAnn sym, LLVMMem.HasPtrWidth wptr , ?memOpts :: LLVMMem.MemOptions ) => bak -> LLVMMem.MemImpl sym -> Integer -> IO (LLVMMem.LLVMPtr sym wptr, What4.SymArray sym (SingleCtx (What4.BaseBVType wptr)) (What4.BaseBVType 8), LLVMMem.MemImpl sym) allocFreshArray bak mem sz = do let sym = CB.backendGetSym bak sz_bv <- What4.bvLit sym ?ptrWidth $ BV.mkBV ?ptrWidth sz (base_ptr, mem1) <- LLVMMem.mallocRaw bak mem sz_bv noAlignment arr <- What4.freshConstant sym (userSymbol' "a") (What4.BaseArrayRepr (Ctx.singleton $ What4.BaseBVRepr ?ptrWidth) (What4.BaseBVRepr (knownNat @8))) mem2 <- LLVMMem.doArrayStore bak mem1 base_ptr noAlignment arr sz_bv return (base_ptr, arr, mem2) -- | This test case verifies that the symbolic aspects of the SMT-backed array -- memory model works (e.g., that constraints on symbolic indexes work as -- expected) testMemArray :: T.TestTree testMemArray = testCase "smt array memory model" $ withMem LLVMD.BigEndian $ \bak mem0 -> do let sym = CB.backendGetSym bak -- Make a fresh allocation (backed by a fresh SMT array) of size 1024*1024 bytes. -- The base pointer of the array is base_ptr sz <- What4.bvLit sym ?ptrWidth $ BV.mkBV ?ptrWidth (1024 * 1024) (base_ptr, mem1) <- LLVMMem.mallocRaw bak mem0 sz noAlignment arr <- What4.freshConstant sym (userSymbol' "a") (What4.BaseArrayRepr (Ctx.singleton $ What4.BaseBVRepr ?ptrWidth) (What4.BaseBVRepr (knownNat @8))) mem2 <- LLVMMem.doArrayStore bak mem1 base_ptr noAlignment arr sz let long_type_repr = Crucible.baseToType $ What4.BaseBVRepr $ knownNat @64 let long_storage_type = LLVMMem.bitvectorType 8 let ptr_long_repr = LLVMMem.LLVMPointerRepr $ knownNat @64 -- Store a large known 8 byte value at a symbolic location in the array (at -- @i@ bytes from the beginning of the array). The assumption constrains it -- such that the location is within the first 1024 bytes of the array. i <- What4.freshConstant sym (userSymbol' "i") $ What4.BaseBVRepr ?ptrWidth ptr_i <- ptrAdd sym ?ptrWidth base_ptr i assume bak =<< What4.bvUlt sym i =<< What4.bvLit sym ?ptrWidth (BV.mkBV ?ptrWidth 1024) some_val <- What4.bvLit sym (knownNat @64) (BV.mkBV knownNat 0x88888888f0f0f0f0) mem3 <- doStore bak mem2 ptr_i long_type_repr long_storage_type noAlignment some_val memset_ptr <- ptrAdd sym ?ptrWidth base_ptr =<< What4.bvLit sym ?ptrWidth (BV.mkBV ?ptrWidth 2048) memset_val <- What4.bvLit sym knownNat (BV.mkBV knownNat 0) memset_sz <- What4.bvLit sym (knownNat @64) (BV.mkBV knownNat 10) mem4 <- LLVMMem.doMemset bak (knownNat @64) mem3 memset_ptr memset_val memset_sz -- Read that same value back and make sure that they are the same at_i_val <- projectLLVM_bv bak =<< doLoad bak mem4 ptr_i long_storage_type ptr_long_repr noAlignment res_i <- checkSat bak =<< What4.bvNe sym some_val at_i_val True @=? W4Sat.isUnsat res_i -- Allocate another fresh arbitrary constant and add it to the base pointer. -- Assume that i = j, then verify that reading from j yields the same value as -- was written at i. j <- What4.freshConstant sym (userSymbol' "j") $ What4.BaseBVRepr ?ptrWidth ptr_j <- ptrAdd sym ?ptrWidth base_ptr j assume bak =<< What4.bvEq sym i j at_j_val <- projectLLVM_bv bak =<< doLoad bak mem4 ptr_j long_storage_type ptr_long_repr noAlignment res_j <- checkSat bak =<< What4.bvNe sym some_val at_j_val True @=? W4Sat.isUnsat res_j -- | Like testMemArray, but using some concrete indexes in a few places. This -- test checks the implementation of saturated addition of two numbers. -- -- This is simulating the use of an SMT array to represent a program stack, and -- ensures that: -- -- * Concrete indexing works as expected -- * Goals that depend on the values of values stored in memory work testMemArrayWithConstants :: T.TestTree testMemArrayWithConstants = testCase "smt array memory model (with constant indexing)" $ do withMem LLVMD.LittleEndian $ \bak mem0 -> do let sym = CB.backendGetSym bak sz <- What4.bvLit sym ?ptrWidth (BV.mkBV ?ptrWidth (2 * 1024)) (region_ptr, mem1) <- LLVMMem.mallocRaw bak mem0 sz noAlignment let mRepr = What4.BaseArrayRepr (Ctx.singleton (What4.BaseBVRepr ?ptrWidth)) (What4.BaseBVRepr (knownNat @8)) backingArray <- What4.freshConstant sym (userSymbol' "backingArray") mRepr mem2 <- LLVMMem.doArrayStore bak mem1 region_ptr noAlignment backingArray sz let long_type_repr = Crucible.baseToType $ What4.BaseBVRepr $ knownNat @64 let long_storage_type = LLVMMem.bitvectorType 8 let ptr_long_repr = LLVMMem.LLVMPointerRepr $ knownNat @64 -- Make our actual base pointer the middle of the stack, to simulate having -- some active frames above us base_off <- What4.freshConstant sym (userSymbol' "baseOffset") (What4.BaseBVRepr ?ptrWidth) assume bak =<< What4.bvUlt sym base_off =<< (What4.bvLit sym ?ptrWidth (BV.mkBV ?ptrWidth 10)) base_ptr <- ptrAdd sym ?ptrWidth region_ptr base_off -- =<< What4.bvLit sym ?ptrWidth (BV.mkBV ?ptrWidth 1024) -- Assume we have two arguments to our virtual function: param_a <- What4.freshConstant sym (userSymbol' "paramA") (What4.BaseBVRepr (knownNat @64)) param_b <- What4.freshConstant sym (userSymbol' "paramB") (What4.BaseBVRepr (knownNat @64)) -- The fake stack frame will start at @sp@ be: -- -- sp+8 : Stack slot for spilling a slot_a <- ptrAdd sym ?ptrWidth base_ptr =<< What4.bvLit sym ?ptrWidth (BV.mkBV ?ptrWidth 8) -- sp+16 : Stack slot for spilling b slot_b <- ptrAdd sym ?ptrWidth base_ptr =<< What4.bvLit sym ?ptrWidth (BV.mkBV ?ptrWidth 16) -- sp+24 : Stack slot for local variable c slot_c <- ptrAdd sym ?ptrWidth base_ptr =<< What4.bvLit sym ?ptrWidth (BV.mkBV ?ptrWidth 24) -- Store a and b onto the stack mem3 <- doStore bak mem2 slot_a long_type_repr long_storage_type noAlignment param_a mem4 <- doStore bak mem3 slot_b long_type_repr long_storage_type noAlignment param_b -- Read a and b off of the stack and compute c = a+b (storing the result on the stack in c's slot) valA0 <- projectLLVM_bv bak =<< doLoad bak mem4 slot_a long_storage_type ptr_long_repr noAlignment valB0 <- projectLLVM_bv bak =<< doLoad bak mem4 slot_b long_storage_type ptr_long_repr noAlignment mem5 <- doStore bak mem4 slot_c long_type_repr long_storage_type noAlignment =<< What4.bvAdd sym valA0 valB0 valA1 <- projectLLVM_bv bak =<< doLoad bak mem5 slot_a long_storage_type ptr_long_repr noAlignment valB1 <- projectLLVM_bv bak =<< doLoad bak mem5 slot_b long_storage_type ptr_long_repr noAlignment valC1 <- projectLLVM_bv bak =<< doLoad bak mem5 slot_c long_storage_type ptr_long_repr noAlignment -- Add some assumptions to make our assertion actually hold (i.e., avoiding overflow) let n64 = knownNat @64 -- assume sym =<< What4.bvUlt sym param_a =<< What4.bvLit sym n64 (BV.mkBV n64 100) -- assume sym =<< What4.bvUlt sym param_b =<< What4.bvLit sym n64 (BV.mkBV n64 100) cLessThanA <- What4.bvSlt sym valC1 valA1 cLessThanB <- What4.bvSlt sym valC1 valB1 ifOverflow <- What4.orPred sym cLessThanA cLessThanB i64Max <- What4.bvLit sym n64 (BV.mkBV n64 0x7fffffffffffffff) clamped_c <- What4.bvIte sym ifOverflow i64Max valC1 mem6 <- doStore bak mem5 slot_c long_type_repr long_storage_type noAlignment clamped_c valC2 <- projectLLVM_bv bak =<< doLoad bak mem6 slot_c long_storage_type ptr_long_repr noAlignment aLessThanC <- What4.bvSle sym param_a valC2 bLessThanC <- What4.bvSle sym param_b valC2 assertion <- What4.andPred sym aLessThanC bLessThanC goal <- What4.notPred sym assertion res <- checkSat bak goal True @=? W4Sat.isUnsat res -- | This test case checks the memcpy aspect of the SMT-backed array memory model testMemArrayCopy :: T.TestTree testMemArrayCopy = testCase "smt array copy memory model" $ withMem LLVMD.LittleEndian $ \bak mem0 -> do let sym = CB.backendGetSym bak setCacheTerms sym True (dst_base_ptr, dst_arr, mem1) <- allocFreshArray bak mem0 (1024 * 1024) (src_base_ptr, src_arr, mem2) <- allocFreshArray bak mem1 1024 i <- What4.freshConstant sym (userSymbol' "i") $ What4.BaseBVRepr ?ptrWidth dst_ptr <- ptrAdd sym ?ptrWidth dst_base_ptr i assume bak =<< What4.bvUlt sym i =<< What4.bvLit sym ?ptrWidth (BV.mkBV ?ptrWidth 1024) len <- What4.freshConstant sym (userSymbol' "l") $ What4.BaseBVRepr ?ptrWidth assume bak =<< What4.bvUlt sym len =<< What4.bvLit sym ?ptrWidth (BV.mkBV ?ptrWidth 1024) mem3 <- LLVMMem.doMemcpy bak ?ptrWidth mem2 False dst_ptr src_base_ptr len zero_bv <- What4.bvLit sym ?ptrWidth $ BV.zero ?ptrWidth expected_arr <- What4.arrayCopy sym dst_arr i src_arr zero_bv len expected_val <- What4.arrayLookup sym expected_arr $ Ctx.singleton i actual_val <- projectLLVM_bv bak =<< doLoad bak mem3 dst_ptr (LLVMMem.bitvectorType 1) (LLVMMem.LLVMPointerRepr $ knownNat @8) noAlignment foo <- What4.bvEq sym expected_val actual_val (Just True) @=? What4.asConstantPred foo -- | This test case checks the memset aspect of the SMT-backed array memory model testMemArraySet :: T.TestTree testMemArraySet = testCase "smt array copy memory model" $ withMem LLVMD.LittleEndian $ \bak mem0 -> do let sym = CB.backendGetSym bak setCacheTerms sym True (base_ptr, arr, mem1) <- allocFreshArray bak mem0 (1024 * 1024) i <- What4.freshConstant sym (userSymbol' "i") $ What4.BaseBVRepr ?ptrWidth ptr_i <- ptrAdd sym ?ptrWidth base_ptr i assume bak =<< What4.bvUlt sym i =<< What4.bvLit sym ?ptrWidth (BV.mkBV ?ptrWidth 1024) val <- What4.freshConstant sym (userSymbol' "v") $ What4.BaseBVRepr $ knownNat @8 len <- What4.freshConstant sym (userSymbol' "l") $ What4.BaseBVRepr ?ptrWidth assume bak =<< What4.bvUlt sym len =<< What4.bvLit sym ?ptrWidth (BV.mkBV ?ptrWidth 1024) mem3 <- LLVMMem.doMemset bak ?ptrWidth mem1 ptr_i val len expected_arr <- What4.arraySet sym arr i val len expected_val <- What4.arrayLookup sym expected_arr $ Ctx.singleton i actual_val <- projectLLVM_bv bak =<< doLoad bak mem3 ptr_i (LLVMMem.bitvectorType 1) (LLVMMem.LLVMPointerRepr $ knownNat @8) noAlignment foo <- What4.bvEq sym expected_val actual_val (Just True) @=? What4.asConstantPred foo testMemWritesIndexed :: T.TestTree testMemWritesIndexed = testCase "indexed memory writes" $ withMem LLVMD.BigEndian $ \bak mem0 -> do let sym = CB.backendGetSym bak let count = 100 * 1000 sz <- What4.bvLit sym ?ptrWidth (BV.mkBV ?ptrWidth 8) (base_ptr1, mem1) <- LLVMMem.mallocRaw bak mem0 sz noAlignment (base_ptr2, mem2) <- LLVMMem.mallocRaw bak mem1 sz noAlignment let long_type_repr = Crucible.baseToType $ What4.BaseBVRepr $ knownNat @64 let long_storage_type = LLVMMem.bitvectorType 8 let ptr_long_repr = LLVMMem.LLVMPointerRepr $ knownNat @64 zero_val <- What4.bvLit sym (knownNat @64) (BV.zero knownNat) mem3 <- doStore bak mem2 base_ptr1 long_type_repr long_storage_type noAlignment zero_val mem4 <- foldlM (\mem' i -> doStore bak mem' base_ptr2 long_type_repr long_storage_type noAlignment =<< What4.bvLit sym (knownNat @64) i) mem3 (BV.enumFromToUnsigned (BV.zero (knownNat @64)) (BV.mkBV knownNat count)) forM_ [0 .. count] $ \_ -> do val1 <- projectLLVM_bv bak =<< doLoad bak mem4 base_ptr1 long_storage_type ptr_long_repr noAlignment (Just (BV.zero knownNat)) @=? What4.asBV val1 val2 <- projectLLVM_bv bak =<< doLoad bak mem4 base_ptr2 long_storage_type ptr_long_repr noAlignment (Just (BV.mkBV knownNat count)) @=? What4.asBV val2 testMemAllocs :: T.TestTree testMemAllocs = testCase "memory model alloc/free" $ withMem LLVMD.BigEndian $ \bak mem0 -> do let sym = CB.backendGetSym bak sz1 <- What4.bvLit sym ?ptrWidth $ BV.mkBV ?ptrWidth 128 sz2 <- What4.bvLit sym ?ptrWidth $ BV.mkBV ?ptrWidth 72 sz3 <- What4.bvLit sym ?ptrWidth $ BV.mkBV ?ptrWidth 32 (ptr1, mem1) <- LLVMMem.mallocRaw bak mem0 sz1 noAlignment (ptr2, mem2) <- LLVMMem.mallocRaw bak mem1 sz2 noAlignment mem3 <- LLVMMem.doFree bak mem2 ptr2 (ptr3, mem4) <- LLVMMem.mallocRaw bak mem3 sz3 noAlignment mem5 <- LLVMMem.doFree bak mem4 ptr1 mem6 <- LLVMMem.doFree bak mem5 ptr3 let isAllocated = LLVMMem.isAllocatedAlignedPointer sym ?ptrWidth noAlignment LLVMMem.Mutable assertions <- sequence [ isAllocated ptr1 (Just sz1) mem1 , isAllocated ptr1 (Just sz1) mem2 , isAllocated ptr1 (Just sz1) mem3 , isAllocated ptr1 (Just sz1) mem4 , isAllocated ptr1 (Just sz1) mem5 >>= What4.notPred sym , isAllocated ptr1 (Just sz1) mem6 >>= What4.notPred sym , isAllocated ptr2 (Just sz2) mem1 >>= What4.notPred sym , isAllocated ptr2 (Just sz2) mem2 , isAllocated ptr2 (Just sz2) mem3 >>= What4.notPred sym , isAllocated ptr2 (Just sz2) mem4 >>= What4.notPred sym , isAllocated ptr2 (Just sz2) mem5 >>= What4.notPred sym , isAllocated ptr2 (Just sz2) mem6 >>= What4.notPred sym , isAllocated ptr3 (Just sz3) mem1 >>= What4.notPred sym , isAllocated ptr3 (Just sz3) mem2 >>= What4.notPred sym , isAllocated ptr3 (Just sz3) mem3 >>= What4.notPred sym , isAllocated ptr3 (Just sz3) mem4 , isAllocated ptr3 (Just sz3) mem5 , isAllocated ptr3 (Just sz3) mem6 >>= What4.notPred sym ] assertion <- foldM (What4.andPred sym) (What4.truePred sym) assertions res <- checkSat bak =<< What4.notPred sym assertion True @=? W4Sat.isUnsat res -- | This test case checks that 'doInvalidate' behaves as expected with and -- without 'laxLoadsAndStores' enabled. testMemInvalidate :: T.TestTree testMemInvalidate = testCase "memory invalidation" $ withMem LLVMD.BigEndian $ \bak mem0 -> do let sym = CB.backendGetSym bak sz <- What4.bvLit sym ?ptrWidth $ BV.mkBV ?ptrWidth 64 let long_type_repr = Crucible.baseToType $ What4.BaseBVRepr $ knownNat @64 long_storage_type = LLVMMem.bitvectorType 8 zero_val <- What4.bvLit sym (knownNat @64) (BV.zero knownNat) let withInvalidatedReadVal memOpts k = do let ?memOpts = memOpts -- First, allocate some memory on the stack... (ptr, mem1) <- LLVMMem.doAlloca bak mem0 sz noAlignment "" -- ...write some value to it (the exact value is unimportant)... mem2 <- LLVMMem.doStore bak mem1 ptr long_type_repr long_storage_type noAlignment zero_val -- ...invalidate the memory... mem3 <- LLVMMem.doInvalidate bak ?ptrWidth mem2 ptr "" sz -- ...and finally, read from the invalidated memory. partVal <- LLVMMemG.readMem sym ?ptrWidth Nothing ptr long_storage_type noAlignment (LLVMMem.memImplHeap mem3) k partVal testLaxInvalidatedRead :: String -> LLVMMem.IndeterminateLoadBehavior -> IO () testLaxInvalidatedRead stability indeterminateLoadBehavior = withInvalidatedReadVal (?memOpts{ LLVMMem.laxLoadsAndStores = True , LLVMMem.indeterminateLoadBehavior = indeterminateLoadBehavior }) $ \partVal -> case partVal of LLVMMem.NoErr p _val -> do res <- checkSat bak p True @=? W4Sat.isSat res LLVMMem.Err p -> assertFailure $ unlines [ "Reading from invalidated, " ++ stability ++ "-symbolic memory unexpectedly failed" , "Predicate: " ++ show p ] -- Test with laxLoadsAndStores disabled, where reading from invalidated -- memory should result in an error. withInvalidatedReadVal (?memOpts{LLVMMem.laxLoadsAndStores = False}) $ \partVal -> case partVal of LLVMMem.Err p -> do res <- checkSat bak p True @=? W4Sat.isUnsat res LLVMMem.NoErr p val -> assertFailure $ unlines [ "Reading from invalidated memory unexpectedly succeeded" , "Predicate: " ++ show p , "LLVM value read: " ++ show val ] -- Test with laxLoadsAndStores enabled, using both the -- StableSymbolic and UnstableSymbolic settings for -- indeterminateLoadBehavior. Here, reading from invalidated memory should succeed. testLaxInvalidatedRead "stable" LLVMMem.StableSymbolic testLaxInvalidatedRead "unstable" LLVMMem.UnstableSymbolic -- | Test storing and retrieving pointer in an SMT-backed array memory model testPointerStore :: T.TestTree testPointerStore = testCase "pointer store" $ withMem LLVMD.BigEndian $ \bak mem0 -> do let sym = CB.backendGetSym bak -- Allocate two blocks sz <- What4.bvLit sym ?ptrWidth $ BV.mkBV ?ptrWidth (1024 * 1024) (base_ptr1, _) <- LLVMMem.mallocRaw bak mem0 sz noAlignment (base_ptr2, block2_mem1) <- LLVMMem.mallocRaw bak mem0 sz noAlignment -- Store the first base pointer in the second block let pointer_storage_type = LLVMMem.bitvectorType 8 let base_ptr1_val = LLVMMem.ptrToPtrVal base_ptr1 block2_mem2 <- LLVMMem.storeRaw bak block2_mem1 base_ptr2 pointer_storage_type noAlignment base_ptr1_val -- Read the pointer back base_ptr1_back <- LLVMMem.loadRaw sym block2_mem2 base_ptr2 pointer_storage_type noAlignment -- Assert that the read pointer is equal to the original pointer base_ptr1_back_safe <- LLVMMem.assertSafe bak base_ptr1_back is_equal <- LLVMMem.testEqual sym base_ptr1_val base_ptr1_back_safe case is_equal of Nothing -> assertFailure "testEqual failed" Just p -> do goal <- What4.notPred sym p res <- checkSat bak goal True @=? W4Sat.isUnsat res -- | Test storing and retrieving a struct with and without 'laxLoadsAndStores' -- enabled. testStructStore :: T.TestTree testStructStore = testCase "struct store" $ withMem LLVMD.BigEndian $ \bak mem0 -> do let sym = CB.backendGetSym bak sz <- What4.bvLit sym ?ptrWidth $ BV.mkBV ?ptrWidth 64 let struct_storage_type = LLVMMem.mkStructType $ V.fromList [ (LLVMMem.bitvectorType 2, 2) , (LLVMMem.bitvectorType 4, 0) ] let w16 = knownNat @16 let w32 = knownNat @32 let struct_type_repr = Crucible.StructRepr $ Ctx.Empty Ctx.:> LLVMMem.LLVMPointerRepr w16 Ctx.:> LLVMMem.LLVMPointerRepr w32 let struct_bv1 = BV.mkBV w16 27 let struct_bv2 = BV.mkBV w32 42 struct_sym_bv1 <- What4.bvLit sym w16 struct_bv1 struct_sym_bv2 <- What4.bvLit sym w32 struct_bv2 struct_field1 <- LLVMMem.llvmPointer_bv sym struct_sym_bv1 struct_field2 <- LLVMMem.llvmPointer_bv sym struct_sym_bv2 let struct_val = Ctx.Empty Ctx.:> CS.RV struct_field1 Ctx.:> CS.RV struct_field2 let testWithOpts memOpts = do let ?memOpts = memOpts -- First, allocate some memory on the stack... (ptr, mem1) <- LLVMMem.doAlloca bak mem0 sz noAlignment "" -- ...write a struct to it... mem2 <- LLVMMem.doStore bak mem1 ptr struct_type_repr struct_storage_type noAlignment struct_val -- ...read back the struct... struct_val' <- LLVMMem.doLoad bak mem2 ptr struct_storage_type struct_type_repr noAlignment -- ...and finally, check that the struct read from memory is the -- same as the original struct. let checkField :: forall w sym bak. CB.IsSymBackend sym bak => bak -> BV.BV w -> CS.RegValue' sym (LLVMMem.LLVMPointerType w) -> IO () checkField bak' expectedBV actualPtrRV = do actualSymBV <- projectLLVM_bv bak' $ CS.unRV actualPtrRV Just expectedBV @=? What4.asBV actualSymBV checkField bak struct_bv1 (struct_val'^._1) checkField bak struct_bv2 (struct_val'^._2) testWithOpts (?memOpts{ LLVMMem.laxLoadsAndStores = False }) testWithOpts (?memOpts{ LLVMMem.laxLoadsAndStores = True , LLVMMem.indeterminateLoadBehavior = LLVMMem.StableSymbolic }) testWithOpts (?memOpts{ LLVMMem.laxLoadsAndStores = True , LLVMMem.indeterminateLoadBehavior = LLVMMem.UnstableSymbolic })