-
Notifications
You must be signed in to change notification settings - Fork 42
/
Copy pathMemLog.hs
765 lines (672 loc) · 27.9 KB
/
MemLog.hs
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
------------------------------------------------------------------------
-- |
-- Module : Lang.Crucible.LLVM.MemModel.MemLog
-- Description : Data types supporting the LLVM memory model
-- Copyright : (c) Galois, Inc 2011-2020
-- License : BSD3
-- Maintainer : Rob Dockins <rdockins@galois.com>
-- Stability : provisional
------------------------------------------------------------------------
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# Language ImplicitParams #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE UndecidableInstances #-}
module Lang.Crucible.LLVM.MemModel.MemLog
(
-- * Allocation logs
AllocType(..)
, Mutability(..)
, AllocInfo(..)
, MemAllocs(..)
, MemAlloc(..)
, sizeMemAllocs
, allocMemAllocs
, freeMemAllocs
, muxMemAllocs
, popMemAllocs
, possibleAllocInfo
, isAllocatedGeneric
-- * Write logs
, WriteSource(..)
, MemWrite(..)
, MemWrites(..)
, MemWritesChunk(..)
, memWritesSingleton
-- * Memory logs
, MemState(..)
, MemChanges
, memState
, Mem(..)
, emptyChanges
, emptyMem
, memEndian
, memInsertArrayBlock
, memMemberArrayBlock
-- * Pretty printing
, ppType
, ppPtr
, ppAllocInfo
, ppAllocs
, ppMem
, ppMemWrites
, ppWrite
-- * Write ranges
, writeRangesMem
, writeSourceSize
-- * Concretization
, concPtr
, concLLVMVal
, concMem
) where
import Control.Applicative ((<|>))
import Control.Lens
import Control.Monad.State
import Control.Monad.Trans.Maybe
import Data.Foldable
import Data.IntMap (IntMap)
import qualified Data.IntMap as IntMap
import qualified Data.List.Extra as List
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe (mapMaybe)
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Text (Text)
import Numeric.Natural
import Prettyprinter
import qualified Data.BitVector.Sized as BV
import Data.Parameterized.Ctx (SingleCtx)
import What4.Interface
import What4.Expr (GroundValue)
import Lang.Crucible.LLVM.DataLayout (Alignment, fromAlignment, EndianForm(..))
import Lang.Crucible.LLVM.MemModel.Pointer
import Lang.Crucible.LLVM.MemModel.Type
import Lang.Crucible.LLVM.MemModel.Value
--------------------------------------------------------------------------------
-- Allocations
data AllocType = StackAlloc | HeapAlloc | GlobalAlloc
deriving (Eq, Ord, Show)
data Mutability = Mutable | Immutable
deriving (Eq, Ord, Show)
-- | Details of a single allocation. The @Maybe SymBV@ argument is either a
-- size or @Nothing@ representing an unbounded allocation. The 'Mutability'
-- indicates whether the region is read-only. The 'String' contains source
-- location information for use in error messages.
data AllocInfo sym =
forall w. (1 <= w) => AllocInfo AllocType (Maybe (SymBV sym w)) Mutability Alignment String
-- | Stores writeable memory allocations.
data MemAlloc sym
-- | A collection of consecutive basic allocations.
= Allocations (Map Natural (AllocInfo sym))
-- | Freeing of the given block ID.
| MemFree (SymNat sym) String
-- | The merger of two allocations.
| AllocMerge (Pred sym) (MemAllocs sym) (MemAllocs sym)
-- | A record of which memory regions have been allocated or freed.
-- Memory allocations are represented as a list with the invariant
-- that any two adjacent 'Allocations' constructors must be merged
-- together, and that no 'Allocations' constructor has an empty map.
newtype MemAllocs sym = MemAllocs [MemAlloc sym]
instance Semigroup (MemAllocs sym) where
(MemAllocs lhs_allocs) <> (MemAllocs rhs_allocs)
| Just (lhs_head_allocs, Allocations lhs_m) <- List.unsnoc lhs_allocs
, Allocations rhs_m : rhs_tail_allocs <- rhs_allocs
= MemAllocs (lhs_head_allocs ++ [Allocations (Map.union lhs_m rhs_m)] ++ rhs_tail_allocs)
| otherwise = MemAllocs (lhs_allocs ++ rhs_allocs)
instance Monoid (MemAllocs sym) where
mempty = MemAllocs []
sizeMemAlloc :: MemAlloc sym -> Int
sizeMemAlloc =
\case
Allocations m -> Map.size m
MemFree{} -> 1
AllocMerge{} -> 1
-- | Compute the size of a 'MemAllocs' log.
sizeMemAllocs :: MemAllocs sym -> Int
sizeMemAllocs (MemAllocs allocs) = sum (map sizeMemAlloc allocs)
-- | Returns true if this consists of a empty collection of memory allocs.
nullMemAllocs :: MemAllocs sym -> Bool
nullMemAllocs (MemAllocs xs) = null xs
-- | Allocate a new block with the given allocation ID.
allocMemAllocs :: Natural -> AllocInfo sym -> MemAllocs sym -> MemAllocs sym
allocMemAllocs blk info ma = MemAllocs [Allocations (Map.singleton blk info)] <> ma
-- | Free the block with the given allocation ID.
freeMemAllocs :: SymNat sym -> String {- ^ Location info for debugging -} -> MemAllocs sym -> MemAllocs sym
freeMemAllocs blk loc (MemAllocs xs) = MemAllocs (MemFree blk loc : xs)
muxMemAllocs :: IsExpr (SymExpr sym) => Pred sym -> MemAllocs sym -> MemAllocs sym -> MemAllocs sym
muxMemAllocs _ (MemAllocs []) (MemAllocs []) = MemAllocs []
muxMemAllocs c xs ys =
case asConstantPred c of
Just True -> xs
Just False -> ys
Nothing -> MemAllocs [AllocMerge c xs ys]
-- | Purge all stack allocations from the allocation log.
popMemAllocs :: forall sym. MemAllocs sym -> MemAllocs sym
popMemAllocs (MemAllocs xs) = MemAllocs (mapMaybe popMemAlloc xs)
where
popMemAlloc :: MemAlloc sym -> Maybe (MemAlloc sym)
popMemAlloc (Allocations am) =
if Map.null am' then Nothing else Just (Allocations am')
where am' = Map.filter notStackAlloc am
popMemAlloc a@(MemFree _ _) = Just a
popMemAlloc (AllocMerge c x y) = Just (AllocMerge c (popMemAllocs x) (popMemAllocs y))
notStackAlloc :: AllocInfo sym -> Bool
notStackAlloc (AllocInfo x _ _ _ _) = x /= StackAlloc
-- | Look up the 'AllocInfo' for the given allocation number. A 'Just'
-- result indicates that the specified memory region may or may not
-- still be allocated; 'Nothing' indicates that the memory region is
-- definitely not allocated.
possibleAllocInfo ::
forall sym.
IsExpr (SymExpr sym) =>
Natural ->
MemAllocs sym ->
Maybe (AllocInfo sym)
possibleAllocInfo n (MemAllocs xs) = asum (map helper xs)
where
helper :: MemAlloc sym -> Maybe (AllocInfo sym)
helper =
\case
MemFree _ _ -> Nothing
Allocations m -> Map.lookup n m
AllocMerge cond ma1 ma2 ->
case asConstantPred cond of
Just True -> possibleAllocInfo n ma1
Just False -> possibleAllocInfo n ma2
Nothing -> possibleAllocInfo n ma1 <|> possibleAllocInfo n ma2
-- | Generalized function for checking whether a memory region ID is allocated.
--
-- The first predicate indicates whether the region was allocated, the second
-- indicates whether it has *not* been freed.
isAllocatedGeneric ::
forall sym.
(IsExpr (SymExpr sym), IsExprBuilder sym) =>
sym ->
(AllocInfo sym -> IO (Pred sym)) ->
SymNat sym ->
MemAllocs sym ->
IO (Pred sym, Pred sym)
isAllocatedGeneric sym inAlloc blk = go (pure (falsePred sym)) (pure (truePred sym))
where
go :: IO (Pred sym) -> IO (Pred sym) -> MemAllocs sym -> IO (Pred sym, Pred sym)
go fallback fallbackFreed (MemAllocs []) = (,) <$> fallback <*> fallbackFreed
go fallback fallbackFreed (MemAllocs (alloc : r)) =
case alloc of
Allocations am ->
case asNat blk of
Just b -> -- concrete block number, look up entry
case Map.lookup b am of
Nothing -> go fallback fallbackFreed (MemAllocs r)
Just ba -> (,) <$> inAlloc ba <*> fallbackFreed
Nothing -> -- symbolic block number, need to check all entries
Map.foldrWithKey checkEntry (go fallback fallbackFreed (MemAllocs r)) am
where
checkEntry a ba k =
do
sameBlock <- natEq sym blk =<< natLit sym a
case asConstantPred sameBlock of
Just True ->
-- This is where where this block was allocated, and it
-- couldn't have been freed before it was allocated.
--
-- NOTE(lb): It's not clear to me that this branch is
-- reachable: If the equality test can succeed
-- concretely, wouldn't asNat have returned a Just
-- above? In either case, this answer should be sound.
return (truePred sym, truePred sym)
Just False -> k
Nothing ->
do (fallback', fallbackFreed') <- k
here <- itePredM sym sameBlock (inAlloc ba) (pure fallback')
pure (here, fallbackFreed')
MemFree a _ ->
do sameBlock <- natEq sym blk a
case asConstantPred sameBlock of
Just True ->
-- If it was freed, it also must have been allocated beforehand.
return (truePred sym, falsePred sym)
Just False -> go fallback fallbackFreed (MemAllocs r)
Nothing ->
do (inRest, notFreedInRest) <-
go fallback fallbackFreed (MemAllocs r)
notSameBlock <- notPred sym sameBlock
(inRest,) <$> andPred sym notSameBlock notFreedInRest
AllocMerge _ (MemAllocs []) (MemAllocs []) ->
go fallback fallbackFreed (MemAllocs r)
AllocMerge c xr yr ->
do (inRest, notFreedInRest) <- go fallback fallbackFreed (MemAllocs r) -- TODO: wrap this in a delay
(inTrue, notFreedInTrue) <- go (pure inRest) (pure notFreedInRest) xr
(inFalse, notFreedInFalse) <- go (pure inRest) (pure notFreedInRest) yr
(,) <$> itePred sym c inTrue inFalse
<*> itePred sym c notFreedInTrue notFreedInFalse
--------------------------------------------------------------------------------
-- Writes
data WriteSource sym w
-- | @MemCopy src len@ copies @len@ bytes from [src..src+len).
= MemCopy (LLVMPtr sym w) (SymBV sym w)
-- | @MemSet val len@ fills the destination with @len@ copies of byte @val@.
| MemSet (SymBV sym 8) (SymBV sym w)
-- | @MemStore val ty al@ writes value @val@ with type @ty@ at the destination.
-- with alignment at least @al@.
| MemStore (LLVMVal sym) StorageType Alignment
-- | @MemArrayStore block (Just len)@ writes byte-array @block@ of size
-- @len@ at the destination; @MemArrayStore block Nothing@ writes byte-array
-- @block@ of unbounded size
| MemArrayStore (SymArray sym (SingleCtx (BaseBVType w)) (BaseBVType 8)) (Maybe (SymBV sym w))
-- | @MemInvalidate len@ flags @len@ bytes as uninitialized.
| MemInvalidate Text (SymBV sym w)
data MemWrite sym
-- | @MemWrite dst src@ represents a write to @dst@ from the given source.
= forall w. (1 <= w) => MemWrite (LLVMPtr sym w) (WriteSource sym w)
-- | The merger of two memories.
| WriteMerge (Pred sym) (MemWrites sym) (MemWrites sym)
--------------------------------------------------------------------------------
-- Memory
-- | A symbolic representation of the LLVM heap.
--
-- This representation is designed to support a variety of operations
-- including reads and writes of symbolic data to symbolic addresses,
-- symbolic memcpy and memset, and merging two memories in a single
-- memory using an if-then-else operation.
--
-- A common use case is that the symbolic simulator will branch into
-- two execution states based on a symbolic branch, make different
-- memory modifications on each branch, and then need to merge the two
-- memories to resume execution along a single path using the branch
-- condition. To support this, our memory representation supports
-- "branch frames", at any point one can insert a fresh branch frame
-- (see `branchMem`), and then at some later point merge two memories
-- back into a single memory (see `mergeMem`). Our `mergeMem`
-- implementation is able to efficiently merge memories, but requires
-- that one only merge memories that were identical prior to the last
-- branch.
data Mem sym = Mem { memEndianForm :: EndianForm, _memState :: MemState sym, memArrayBlocks :: Set Natural }
memState :: Lens' (Mem sym) (MemState sym)
memState = lens _memState (\s v -> s { _memState = v })
-- | A state of memory as of a stack push, branch, or merge. Counts
-- of the total number of allocations and writes are kept for
-- performance metrics.
data MemState sym =
-- | Represents initial memory and changes since then.
-- Changes are stored in order, with more recent changes closer to the head
-- of the list.
EmptyMem !Int !Int (MemChanges sym)
-- | Represents a push of a stack frame, and changes since that stack push.
-- The text value gives a user-consumable name for the stack frame.
-- Changes are stored in order, with more recent changes closer to the head
-- of the list.
| StackFrame !Int !Int Text (MemChanges sym) (MemState sym)
-- | Represents a push of a branch frame, and changes since that branch.
-- Changes are stored in order, with more recent changes closer to the head
-- of the list.
| BranchFrame !Int !Int (MemChanges sym) (MemState sym)
type MemChanges sym = (MemAllocs sym, MemWrites sym)
-- | Memory writes are represented as a list of chunks of writes.
-- Chunks alternate between being indexed and being flat.
newtype MemWrites sym = MemWrites [MemWritesChunk sym]
-- | Returns true if this consists of a empty collection of memory writes
nullMemWrites :: MemWrites sym -> Bool
nullMemWrites (MemWrites ws) = null ws
-- | A chunk of memory writes is either indexed or flat (unindexed).
-- An indexed chunk consists of writes to addresses with concrete
-- base pointers and is represented as a map. A flat chunk consists of
-- writes to addresses with symbolic base pointers. A merge of two
-- indexed chunks is a indexed chunk, while any other merge is part of
-- a flat chunk.
data MemWritesChunk sym =
MemWritesChunkFlat [MemWrite sym]
| MemWritesChunkIndexed (IntMap [MemWrite sym])
instance Semigroup (MemWrites sym) where
(MemWrites lhs_writes) <> (MemWrites rhs_writes)
| Just (lhs_head_writes, lhs_tail_write) <- List.unsnoc lhs_writes
, MemWritesChunkIndexed lhs_tail_indexed_writes <- lhs_tail_write
, rhs_head_write : rhs_tail_writes <- rhs_writes
, (MemWritesChunkIndexed rhs_head_indexed_writes) <- rhs_head_write = do
let merged_chunk = MemWritesChunkIndexed $ IntMap.mergeWithKey
(\_ lhs_alloc_writes rhs_alloc_writes ->
Just $ lhs_alloc_writes ++ rhs_alloc_writes)
id
id
lhs_tail_indexed_writes
rhs_head_indexed_writes
MemWrites $ lhs_head_writes ++ [merged_chunk] ++ rhs_tail_writes
| otherwise = MemWrites $ lhs_writes ++ rhs_writes
instance Monoid (MemWrites sym) where
mempty = MemWrites []
memWritesSingleton ::
(IsExprBuilder sym, 1 <= w) =>
LLVMPtr sym w ->
WriteSource sym w ->
MemWrites sym
memWritesSingleton ptr src
| Just blk <- asNat (llvmPointerBlock ptr)
, isIndexableSource src =
MemWrites
[ MemWritesChunkIndexed $
IntMap.singleton (fromIntegral blk) [MemWrite ptr src]
]
| otherwise = MemWrites [MemWritesChunkFlat [MemWrite ptr src]]
where
isIndexableSource :: WriteSource sym w -> Bool
isIndexableSource = \case
MemStore{} -> True
MemArrayStore{} -> True
MemSet{} -> True
MemInvalidate{} -> True
MemCopy{} -> False
emptyChanges :: MemChanges sym
emptyChanges = (mempty, mempty)
emptyMem :: EndianForm -> Mem sym
emptyMem e = Mem { memEndianForm = e, _memState = EmptyMem 0 0 emptyChanges, memArrayBlocks = Set.empty }
memEndian :: Mem sym -> EndianForm
memEndian = memEndianForm
memInsertArrayBlock :: IsExprBuilder sym => SymNat sym -> Mem sym -> Mem sym
memInsertArrayBlock blk mem = case asNat blk of
Just blk_no -> mem { memArrayBlocks = Set.insert blk_no (memArrayBlocks mem) }
Nothing -> mem { memArrayBlocks = Set.empty }
memMemberArrayBlock :: IsExprBuilder sym => SymNat sym -> Mem sym -> Bool
memMemberArrayBlock blk mem = case asNat blk of
Just blk_no -> Set.member blk_no (memArrayBlocks mem)
Nothing -> False
--------------------------------------------------------------------------------
-- Pretty printing
ppMerge :: IsExpr e
=> (v -> Doc ann)
-> e tp
-> [v]
-> [v]
-> Doc ann
ppMerge vpp c x y =
indent 2 $
vcat
[ "Condition:"
, indent 2 (printSymExpr c)
, ppAllocList x "True Branch:"
, ppAllocList y "False Branch:"
]
where ppAllocList [] d = d <+> "<none>"
ppAllocList xs d = vcat [d, indent 2 (vcat $ map vpp xs)]
ppAlignment :: Alignment -> Doc ann
ppAlignment a =
pretty $ show (fromAlignment a) ++ "-byte-aligned"
ppAllocInfo :: IsExpr (SymExpr sym) => (Natural, AllocInfo sym) -> Doc ann
ppAllocInfo (base, AllocInfo atp sz mut alignment loc) =
viaShow atp
<+> pretty base
<+> maybe mempty printSymExpr sz
<+> viaShow mut
<+> ppAlignment alignment
<+> pretty loc
ppAlloc :: IsExpr (SymExpr sym) => MemAlloc sym -> Doc ann
ppAlloc (Allocations xs) =
vcat $ map ppAllocInfo (reverse (Map.assocs xs))
ppAlloc (MemFree base loc) =
"Free" <+> printSymNat base <+> pretty loc
ppAlloc (AllocMerge c (MemAllocs x) (MemAllocs y)) =
vcat ["Merge", ppMerge ppAlloc c x y]
ppAllocs :: IsExpr (SymExpr sym) => MemAllocs sym -> Doc ann
ppAllocs (MemAllocs xs) = vcat $ map ppAlloc xs
ppWrite :: IsExpr (SymExpr sym) => MemWrite sym -> Doc ann
ppWrite (MemWrite d (MemCopy s l)) = do
"memcopy" <+> ppPtr d <+> ppPtr s <+> printSymExpr l
ppWrite (MemWrite d (MemSet v l)) = do
"memset" <+> ppPtr d <+> printSymExpr v <+> printSymExpr l
ppWrite (MemWrite d (MemStore v _ _)) = do
pretty '*' <> ppPtr d <+> ":=" <+> ppTermExpr v
ppWrite (MemWrite d (MemArrayStore arr _)) = do
pretty '*' <> ppPtr d <+> ":=" <+> printSymExpr arr
ppWrite (MemWrite d (MemInvalidate msg l)) = do
"invalidate" <+> parens (pretty msg) <+> ppPtr d <+> printSymExpr l
ppWrite (WriteMerge c (MemWrites x) (MemWrites y)) = do
vcat ["merge", ppMerge ppMemWritesChunk c x y]
ppMemWritesChunk :: IsExpr (SymExpr sym) => MemWritesChunk sym -> Doc ann
ppMemWritesChunk = \case
MemWritesChunkFlat [] ->
"No writes"
MemWritesChunkFlat flat_writes ->
vcat $ map ppWrite flat_writes
MemWritesChunkIndexed indexed_writes ->
vcat
[ "Indexed chunk:"
, indent 2 (vcat $ map
(\(blk, blk_writes) ->
case blk_writes of
[] -> mempty
_ -> viaShow blk <+> "|->" <> softline <>
indent 2 (vcat $ map ppWrite blk_writes))
(IntMap.toList indexed_writes))
]
ppMemWrites :: IsExpr (SymExpr sym) => MemWrites sym -> Doc ann
ppMemWrites (MemWrites ws) = vcat $ map ppMemWritesChunk ws
ppMemChanges :: IsExpr (SymExpr sym) => MemChanges sym -> [Doc ann]
ppMemChanges (al,wl)
| nullMemAllocs al && nullMemWrites wl = ["No writes or allocations"]
| otherwise =
(if nullMemAllocs al then [] else ["Allocations:", indent 2 (ppAllocs al)]) <>
(if nullMemWrites wl then [] else ["Writes:", indent 2 (ppMemWrites wl)])
ppMemState :: (MemChanges sym -> [Doc ann]) -> MemState sym -> Doc ann
ppMemState f (EmptyMem _ _ d) =
vcat ("Base memory" : map (indent 2) (f d))
ppMemState f (StackFrame _ _ nm d ms) =
vcat (("Stack frame" <+> pretty nm) : map (indent 2) (f d) ++ [ppMemState f ms])
ppMemState f (BranchFrame _ _ d ms) =
vcat ("Branch frame" : map (indent 2) (f d) ++ [ppMemState f ms])
ppMem :: IsExpr (SymExpr sym) => Mem sym -> Doc ann
ppMem m = ppMemState ppMemChanges (m^.memState)
------------------------------------------------------------------------------
-- Write ranges
multiUnion :: (Ord k, Semigroup a) => Map k a -> Map k a -> Map k a
multiUnion = Map.unionWith (<>)
-- | This will return 'Just' if the size of the write is bounded and 'Nothing'
-- is the size of the write is unbounded.
writeSourceSize ::
(IsExprBuilder sym, 1 <= w) =>
sym ->
NatRepr w ->
WriteSource sym w ->
MaybeT IO (SymBV sym w)
writeSourceSize sym w = \case
MemCopy _src sz -> pure sz
MemSet _val sz -> pure sz
MemStore _val st _align ->
liftIO $ bvLit sym w $ BV.mkBV w $ toInteger $ typeEnd 0 st
MemArrayStore _arr Nothing -> MaybeT $ pure Nothing
MemArrayStore _arr (Just sz) -> pure sz
MemInvalidate _nm sz -> pure sz
writeRangesMemWrite ::
(IsExprBuilder sym, HasPtrWidth w) =>
sym ->
MemWrite sym ->
MaybeT IO (Map Natural [(SymBV sym w, SymBV sym w)])
writeRangesMemWrite sym = \case
MemWrite ptr wsrc
| Just Refl <- testEquality ?ptrWidth (ptrWidth ptr) ->
case asNat (llvmPointerBlock ptr) of
Just blk -> do
sz <- writeSourceSize sym ?ptrWidth wsrc
pure $ Map.singleton blk [(llvmPointerOffset ptr, sz)]
Nothing -> MaybeT $ pure Nothing
| otherwise -> fail "foo"
WriteMerge _p ws1 ws2 ->
multiUnion <$> writeRangesMemWrites sym ws1 <*> writeRangesMemWrites sym ws2
writeRangesMemWritesChunk ::
(IsExprBuilder sym, HasPtrWidth w) =>
sym ->
MemWritesChunk sym ->
MaybeT IO (Map Natural [(SymBV sym w, SymBV sym w)])
writeRangesMemWritesChunk sym = \case
MemWritesChunkFlat ws -> foldl multiUnion Map.empty <$> mapM (writeRangesMemWrite sym) ws
MemWritesChunkIndexed mp ->
foldl multiUnion Map.empty <$> mapM (writeRangesMemWrite sym) (concat $ IntMap.elems mp)
writeRangesMemWrites ::
(IsExprBuilder sym, HasPtrWidth w) =>
sym ->
MemWrites sym ->
MaybeT IO (Map Natural [(SymBV sym w, SymBV sym w)])
writeRangesMemWrites sym (MemWrites ws) =
foldl multiUnion Map.empty <$> mapM (writeRangesMemWritesChunk sym) ws
writeRangesMemChanges ::
(IsExprBuilder sym, HasPtrWidth w) =>
sym ->
MemChanges sym ->
MaybeT IO (Map Natural [(SymBV sym w, SymBV sym w)])
writeRangesMemChanges sym (_as, ws) = writeRangesMemWrites sym ws
writeRangesMemState ::
(IsExprBuilder sym, HasPtrWidth w) =>
sym ->
MemState sym ->
MaybeT IO (Map Natural [(SymBV sym w, SymBV sym w)])
writeRangesMemState sym = \case
EmptyMem _a _w chs -> writeRangesMemChanges sym chs
StackFrame _a _w _nm chs st ->
multiUnion <$> writeRangesMemChanges sym chs <*> writeRangesMemState sym st
BranchFrame _a _w chs st ->
multiUnion <$> writeRangesMemChanges sym chs <*> writeRangesMemState sym st
-- | Compute the ranges (pairs of the form pointer offset and size) for all
-- memory writes, indexed by the pointer base. The result is Nothing if the
-- memory contains any writes with symbolic pointer base, or without size.
writeRangesMem ::
(IsExprBuilder sym, HasPtrWidth w) =>
sym ->
Mem sym ->
MaybeT IO ((Map Natural [(SymBV sym w, SymBV sym w)]))
writeRangesMem sym mem = writeRangesMemState sym $ mem ^. memState
------------------------------------------------------------------------------
-- Concretization
concAllocInfo ::
IsExprBuilder sym =>
sym ->
(forall tp. SymExpr sym tp -> IO (GroundValue tp)) ->
AllocInfo sym -> IO (AllocInfo sym)
concAllocInfo sym conc (AllocInfo atp sz m a nm) =
do sz' <- traverse (concBV sym conc) sz
pure (AllocInfo atp sz' m a nm)
concAlloc ::
IsExprBuilder sym =>
sym ->
(forall tp. SymExpr sym tp -> IO (GroundValue tp)) ->
MemAlloc sym -> IO (MemAllocs sym)
concAlloc sym conc (Allocations m) =
do m' <- traverse (concAllocInfo sym conc) m
pure (MemAllocs [Allocations m'])
concAlloc sym conc (MemFree blk loc) =
do blk' <- integerToNat sym =<< intLit sym =<< conc =<< natToInteger sym blk
pure (MemAllocs [MemFree blk' loc])
concAlloc sym conc (AllocMerge p m1 m2) =
do b <- conc p
if b then concMemAllocs sym conc m1
else concMemAllocs sym conc m2
concMemAllocs ::
IsExprBuilder sym =>
sym ->
(forall tp. SymExpr sym tp -> IO (GroundValue tp)) ->
MemAllocs sym -> IO (MemAllocs sym)
concMemAllocs sym conc (MemAllocs cs) =
fold <$> traverse (concAlloc sym conc) cs
concLLVMVal ::
IsExprBuilder sym =>
sym ->
(forall tp. SymExpr sym tp -> IO (GroundValue tp)) ->
LLVMVal sym -> IO (LLVMVal sym)
concLLVMVal sym conc (LLVMValInt blk off) =
do blk' <- integerToNat sym =<< intLit sym =<< conc =<< natToInteger sym blk
off' <- concBV sym conc off
pure (LLVMValInt blk' off')
concLLVMVal _sym _conc (LLVMValFloat fs fi) =
pure (LLVMValFloat fs fi) -- TODO concreteize floats
concLLVMVal sym conc (LLVMValStruct fs) =
LLVMValStruct <$> traverse (\ (fi,v) -> (,) <$> pure fi <*> concLLVMVal sym conc v) fs
concLLVMVal sym conc (LLVMValArray st vs) =
LLVMValArray st <$> traverse (concLLVMVal sym conc) vs
concLLVMVal _ _ v@LLVMValString{} = pure v
concLLVMVal _ _ v@LLVMValZero{} = pure v
concLLVMVal _ _ (LLVMValUndef st) =
pure (LLVMValZero st) -- ??? does it make sense to turn Undef into Zero?
concWriteSource ::
(IsExprBuilder sym, 1 <= w) =>
sym ->
(forall tp. SymExpr sym tp -> IO (GroundValue tp)) ->
WriteSource sym w -> IO (WriteSource sym w)
concWriteSource sym conc (MemCopy src len) =
MemCopy <$> (concPtr sym conc src) <*> (concBV sym conc len)
concWriteSource sym conc (MemSet val len) =
MemSet <$> (concBV sym conc val) <*> (concBV sym conc len)
concWriteSource sym conc (MemStore val st a) =
MemStore <$> concLLVMVal sym conc val <*> pure st <*> pure a
concWriteSource _sym _conc (MemArrayStore arr Nothing) =
-- TODO, concretize the actual array
pure (MemArrayStore arr Nothing)
concWriteSource sym conc (MemArrayStore arr (Just sz)) =
-- TODO, concretize the actual array
MemArrayStore arr . Just <$> concBV sym conc sz
concWriteSource sym conc (MemInvalidate nm len) =
MemInvalidate nm <$> concBV sym conc len
concMemWrite ::
IsExprBuilder sym =>
sym ->
(forall tp. SymExpr sym tp -> IO (GroundValue tp)) ->
MemWrite sym -> IO (MemWrites sym)
concMemWrite sym conc (MemWrite ptr wsrc) =
do ptr' <- concPtr sym conc ptr
wsrc' <- concWriteSource sym conc wsrc
pure $ memWritesSingleton ptr' wsrc'
concMemWrite sym conc (WriteMerge p m1 m2) =
do b <- conc p
if b then concMemWrites sym conc m1
else concMemWrites sym conc m2
concMemWrites ::
IsExprBuilder sym =>
sym ->
(forall tp. SymExpr sym tp -> IO (GroundValue tp)) ->
MemWrites sym -> IO (MemWrites sym)
concMemWrites sym conc (MemWrites cs) =
fold <$> mapM (concMemWritesChunk sym conc) cs
concMemWritesChunk ::
IsExprBuilder sym =>
sym ->
(forall tp. SymExpr sym tp -> IO (GroundValue tp)) ->
MemWritesChunk sym -> IO (MemWrites sym)
concMemWritesChunk sym conc (MemWritesChunkFlat ws) =
fold <$> mapM (concMemWrite sym conc) ws
concMemWritesChunk sym conc (MemWritesChunkIndexed mp) =
fold <$> mapM (concMemWrite sym conc) (concat (IntMap.elems mp))
concMemChanges ::
IsExprBuilder sym =>
sym ->
(forall tp. SymExpr sym tp -> IO (GroundValue tp)) ->
MemChanges sym -> IO (MemChanges sym)
concMemChanges sym conc (as, ws) =
(,) <$> concMemAllocs sym conc as <*> concMemWrites sym conc ws
concMemState ::
IsExprBuilder sym =>
sym ->
(forall tp. SymExpr sym tp -> IO (GroundValue tp)) ->
MemState sym -> IO (MemState sym)
concMemState sym conc (EmptyMem a w chs) =
EmptyMem a w <$> concMemChanges sym conc chs
concMemState sym conc (StackFrame a w nm frm stk) =
StackFrame a w nm <$> concMemChanges sym conc frm <*> concMemState sym conc stk
concMemState sym conc (BranchFrame a w frm stk) =
BranchFrame a w <$> concMemChanges sym conc frm <*> concMemState sym conc stk
concMem ::
IsExprBuilder sym =>
sym ->
(forall tp. SymExpr sym tp -> IO (GroundValue tp)) ->
Mem sym -> IO (Mem sym)
concMem sym conc mem = do
conc_st <- concMemState sym conc $ mem ^. memState
return $ mem & memState .~ conc_st