-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathSimulator.hs
2244 lines (2037 loc) · 86.1 KB
/
Simulator.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
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
{- |
Module : Verifier.Java.Simulator
Description : JVM Symbolic Simulator implementation
License : BSD3
Stability : provisional
Point-of-contact : acfoltzer
Implementation of the JVM Symbolic Simulator. This module exposes an
interface that allows clients to execute JVM semantics, yielding
symbolic results. The primary interface is the combination of
'runStaticMethod' and 'runSimulator', which allows the specification
of an entrypoint to a computation.
From an implementor's perspective, the bulk of this module is an
instance of 'Execution.JavaSemantics.JavaSemantics' for the simulator
types defined in "Verifier.Java.Common".
-}
{-# LANGUAGE DoAndIfThenElse #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE CPP #-}
-- for the JavaSemantics instance of Simulator
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Verifier.Java.Simulator
( -- * Simulator
Simulator (SM)
, getVerbosity
, setVerbosity
, whenVerbosity
, SEH(..)
-- ** Simulator control
, runDefSimulator
, runSimulator
, runStaticMethod
, getProgramReturnValue
, getProgramFinalMem
, getProgramErrorPaths
, setSEH
, defaultSEH
, withSBE
-- ** Low-level control
, run
, execInstanceMethod
, execStaticMethod
, execMethod
, setupLocals
, errorPath
-- * Method overriding
, overrideInstanceMethod
, overrideStaticMethod
-- * Heap operations
, genRef
-- ** Array operations
, getArrayLength
, getArrayValue
, getByteArray
, getIntArray
, getLongArray
, getRefArray
, getSymbolicArray
, newIntArray
, newLongArray
, newSymbolicArray
, setSymbolicArray
, updateSymbolicArray
-- ** Fields and Strings
, lookupStringRef
, getStaticFieldValue
, getInstanceFieldValue
-- * Dynamic binding
, dynBind
, dynBindSuper
-- * Pretty-printing and debugging
, addBreakpoint
, removeBreakpoint
, dbugM
, abort
, prettyTermSBE
, ppValueFull
, ppNamedLocals
, dumpCurrentMethod
-- * Re-exported modules
, module Execution.JavaSemantics
, module Verifier.Java.Backend
, module Verifier.Java.Common
, module Verifier.Java.Codebase
) where
import Prelude hiding (EQ, LT, GT, (<>))
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative hiding (empty)
#endif
import Control.Lens
import Control.Monad
import Control.Monad.Catch
import Control.Monad.IO.Class
import Control.Monad.State.Class (get, put)
import Control.Monad.Trans.Except
import Control.Monad.Trans.State.Strict (evalStateT)
import Data.Array
import Data.Char
import Data.Int
import Data.Word
import Data.List (find, foldl', sort)
import qualified Data.Map as M
import Data.Maybe
import Data.Set (Set)
import qualified Data.Set as S
import qualified Data.Vector as V
import System.IO (hFlush, stdout)
import Text.PrettyPrint
import Language.JVM.CFG
import Data.JVM.Symbolic.AST
import Data.JVM.Symbolic.Translation
import Execution.JavaSemantics
import qualified Execution.Stepper as Stepper
import Language.JVM.Common
import Language.JVM.Parser
import Verifier.Java.Codebase hiding (lookupClass)
import Verifier.Java.Backend
import Verifier.Java.Common hiding (getCurrentClassName, getCurrentMethod)
import qualified Verifier.Java.Common as Common
import Verifier.Java.Utils
getVerbosity :: Monad m => Simulator sbe m Int
getVerbosity = use verbosity
setVerbosity :: Monad m => Int -> Simulator sbe m ()
setVerbosity = assign verbosity
whenVerbosity :: Monad m => (Int -> Bool) -> Simulator sbe m () -> Simulator sbe m ()
whenVerbosity f m = getVerbosity >>= \v -> when (f v) m
dbugM' :: (Monad m, MonadIO m) => Int -> String -> Simulator sbe m ()
dbugM' lvl = whenVerbosity (>=lvl) . dbugM
--banners' :: (Monad m, MonadIO m) => Int -> String -> Simulator sbe m ()
--banners' lvl = whenVerbosity (>=lvl) . banners
-- | Run a static method with the given arguments. Class, method name,
-- and method type are given in JVM internal format, e.g.,
-- @\"com\/example\/App\" \"main\" \"([Ljava\/lang\/String;)V\"@. Returns all
-- successful paths, along with return values if present.
runStaticMethod :: MonadSim sbe m
=> ClassName -- ^ Class name
-> String -- ^ Method name
-> String -- ^ Method type
-> [Value (SBETerm sbe)] -- ^ Arguments
-> Simulator sbe m [(Path sbe, Maybe (Value (SBETerm sbe)))]
runStaticMethod cName mName mType args = do
let methodKey = makeMethodKey mName mType
pushStaticMethodCall cName methodKey args Nothing
run
cs <- use ctrlStk
when (not $ isFinished cs)
$ err "runStaticMethod: unexpected active control stack after executing method"
case currentPath cs of
Just p -> do
rv <- getProgramReturnValue
return [(p, rv)]
Nothing -> return []-- Nothing -> err "runStaticMethod: all paths failed"
-- | Low-level function. If you do not know you need to call this, you
-- likely do not need to! This is the main loop of the simulator which
-- inspects the control stack and applies one continuation at a time
-- until the program is complete.
run :: forall sbe m . MonadSim sbe m => Simulator sbe m ()
run = do
cs <- use ctrlStk
if isResumed cs then do
dbugM' 5 "run: subcomputation finished, resuming outer computation"
let ResumedCS p cs' = cs
Just suspPath = currentPath cs'
p' = p & pathStack .~ suspPath^.pathStack
& pathStackHt .~ suspPath^.pathStackHt
-- & pathRetVal .~ Nothing
case modifyPath (const p') cs' of
Just cs'' -> ctrlStk .= cs''
Nothing -> err "all paths failed in resumed computation"
else if isFinished cs then
case currentPath cs of
Just (p :: Path sbe) -> do
-- Normal program termination on at least one path.
-- Report termination info at appropriate verbosity levels; also,
-- inform user about error paths when present and optionally dump
-- them.
whenVerbosity (>=5) $ dumpCtrlStk
whenVerbosity (>=6) $ dumpMemory "run"
whenVerbosity (>=2) $ do
dbugM "run terminating normally: found valid exit frame"
case p^.pathRetVal of
Nothing -> dbugM "Program had no return value."
Just rv -> dbugValue "Program returned value" rv
numErrs <- uses errorPaths length
showEPs <- use printErrPaths
when (numErrs > 0 && showEPs) $ do
dbugM $ showErrCnt numErrs
dumpErrorPaths
Nothing -> do
-- All paths ended in errors.
showEPs <- use printErrPaths
if showEPs then
tellUser "All paths yielded errors!" >> dumpErrorPaths
else
tellUser "All paths yielded errors! To see details, use --errpaths."
else do
(p :: Path sbe) <- case currentPath cs of
Just p -> return p
Nothing -> err "impossible"
sbe <- use backend
dbugM' 5 . render $ "run:" <+> ppPath sbe p
flip catchSM handleError $ do
let Just bid = p^.pathBlockId
when (pathAssertedFalse sbe p) $
errorPath $ FailRsn $ "This path is infeasible"
cf <- case currentCallFrame p of
Just cf -> return cf
Nothing -> err "empty call stack"
let method = cf^.cfMethod
cName = cf^.cfClass
cb <- use codebase
whenVerbosity (>= 6) $ do
liftIO $ dumpSymASTs cb cName
symBlocks <- do
mblocks <- liftIO $ lookupSymbolicMethod cb cName (methodKey method)
case mblocks of
Just blocks -> return blocks
_ -> err . render $ "unable to symbolically translate"
<+> text (unClassName cName) <+> ppMethod method
case lookupSymBlock bid symBlocks of
Just symBlock -> do
dbugM' 6 . render $ "run:" <+> ppSymBlock symBlock
runInsns $ sbInsns symBlock
Nothing -> err . render
$ "invalid basic block" <+> ppBlockId bid
<+> "for method" <+> ppMethod method
run
where
lookupSymBlock :: BlockId -> [SymBlock] -> Maybe SymBlock
lookupSymBlock bid = find $ \SymBlock { sbId } -> bid == sbId
handleError (ErrorPathExc _rsn s) = do
-- errorPath ensures that the simulator state provided in the
-- exception data is correct for the next invocation of run,
-- so overwrite the current state here.
put s
handleError e = throwSM e
showErrCnt x
| x == 1 = "Encountered errors on exactly one path. Details below."
| otherwise = "Encountered errors on " ++ show x ++ " paths. Details below."
dumpErrorPaths = do
dbugM $ replicate 80 '-'
eps <- use errorPaths
forM_ eps $ \ep -> do
let p = ep^.epPath
sbe <- use backend
dbugM $ "Error reason : " ++ show (ppFailRsn (ep^.epRsn))
dbugM $ "Error path state :\n" ++ show (nest 2 $ ppPath sbe p)
-- whenVerbosity (>= 3) $ do
-- dbugM "Error path memory: "
-- withSBE (\s -> memDump s (pathMem p) Nothing)
when (length eps > 1) $ dbugM "--"
dbugM $ replicate 80 '-'
-- | Return true if the path has asserted false to be true, and therefore we
-- can call errorPath on it.
pathAssertedFalse :: Backend sbe -> Path' (SBETerm sbe) -> Bool
pathAssertedFalse sbe p = asBool sbe (p^.pathAssertions) == Just False
lookupOverride :: M.Map (ClassName, MethodKey) a
-> ClassName
-> MethodKey
-> Simulator sbe m (Maybe a)
lookupOverride overrides cName key = do
cb <- use codebase
cl <- lookupClass cName
tgts <- map className <$> liftIO (supers cb cl)
let mtgts = [ M.lookup (cName', key) overrides | cName' <- tgts ]
case catMaybes mtgts of
[] -> return Nothing
(ovr:_) -> return (Just ovr)
lookupStaticOverride :: ClassName
-> MethodKey
-> Simulator sbe m (Maybe (StaticOverride sbe m))
lookupStaticOverride cName key = do
override <- use staticOverrides
lookupOverride override cName key
-- | Invoke a static method in the current simulator state. If there
-- is no basic block to return to, this is either a top-level
-- invocation, or a special case like an overridden method.
pushStaticMethodCall :: MonadSim sbe m
=> ClassName
-> MethodKey
-> [Value (SBETerm sbe)]
-> Maybe BlockId
-> Simulator sbe m ()
pushStaticMethodCall cName key args mRetBlock = do
initializeClass cName
let locals = setupLocals args
override <- lookupStaticOverride cName key
case override of
Just f -> do f args
case mRetBlock of
Just retBlock -> setCurrentBlock retBlock
Nothing -> return ()
Nothing -> do
cb <- use codebase
impls <- liftIO $ findStaticMethodsByRef cb cName key
dbugM' 6 . render $ "found static impls:" <+> sep (map (text . unClassName) impls)
cl <- case impls of
cl:_ -> lookupClass cl
_ -> err . render
$ "pushStaticMethodCall: could not find method"
<+> text (unClassName cName) <> "." <> ppMethodKey key
let cName' = className cl
method <- case cl `lookupMethod` key of
Just m -> return m
Nothing -> err . render
$ "pushStaticMethodCall: could not find method"
<+> text (unClassName cName') <> "." <> ppMethodKey key
if methodIsNative method then
err $ "Unsupported native static method " ++ show key ++ " in " ++ unClassName cName'
else do
when (cName' == "com/galois/symbolic/Symbolic") $
expectOverride "Symbolic"
when (cName' == "com/galois/symbolic/Symbolic$Debug") $
expectOverride "Symbolic$Debug"
case mRetBlock of
Just retBB -> modifyCSM_ $ \cs ->
case pushCallFrame cName' method retBB locals cs of
Just cs' -> return cs'
Nothing -> err "cannot invoke method: all paths failed"
Nothing -> void $ execMethod cName' key locals
where
expectOverride cn =
err $ "expected static override for " ++ cn ++ "."
++ methodKeyName key ++ unparseMethodDescriptor key
lookupInstanceOverride :: ClassName
-> MethodKey
-> Simulator sbe m (Maybe (InstanceOverride sbe m))
lookupInstanceOverride cName key = do
overrides <- use instanceOverrides
lookupOverride overrides cName key
-- | Invoke an instance method in the current simulator state. If
-- there is no basic block to return to, this is either a top-level
-- invocation, or a special case like an overridden method. The given
-- class is assumed to be the concrete type, with dynamic dispatch
-- handled by the caller.
pushInstanceMethodCall :: MonadSim sbe m
=> ClassName
-> MethodKey
-> JSRef (Simulator sbe m)
-> [Value (SBETerm sbe)]
-> Maybe BlockId
-> Simulator sbe m ()
pushInstanceMethodCall cName key objectRef args mRetBlock = do
let locals = setupLocals (RValue objectRef : args)
override <- lookupInstanceOverride cName key
case override of
Just f -> do f objectRef args
case mRetBlock of
Just retBlock -> setCurrentBlock retBlock
Nothing -> return ()
Nothing -> do
cl <- lookupClass cName
method <- case cl `lookupMethod` key of
Just m -> return m
Nothing -> err . render
$ "pushInstanceMethodCall: could not find method"
<+> text (unClassName cName) <> "." <> ppMethodKey key
if methodIsNative method then
err $ "Unsupported native instance method " ++ show key ++ " in " ++ unClassName cName
else do
-- dbugM $ "pushCallFrame: (instance) method call to " ++ cName ++ "." ++ methodName method
case mRetBlock of
Just retBB -> do
modifyCSM_ $ \cs ->
case pushCallFrame cName method retBB locals cs of
Just cs' -> return cs'
Nothing -> err "cannot invoke method: all paths failed"
Nothing -> void $ execMethod cName key locals
setupLocals :: [Value term]
-> M.Map LocalVariableIndex (Value term)
setupLocals vals = M.fromList (snd $ foldl setupLocal (0, []) vals)
where
setupLocal (n, acc) v@LValue{} = (n + 2, (n, v) : acc)
setupLocal (n, acc) v@DValue{} = (n + 2, (n, v) : acc)
setupLocal (n, acc) v = (n + 1, (n, v) : acc)
-- | Run the simulator with default event handlers and simulation flags
runDefSimulator ::
MonadSim sbe IO
=> Codebase -- ^ JVM Codebase
-> Backend sbe -- ^ A symbolic backend
-> Simulator sbe IO a -- ^ Simulator action to perform
-> IO a
runDefSimulator cb sbe m = runSimulator cb sbe defaultSEH Nothing m
-- | Run the simulator with the given configuration
runSimulator ::
MonadSim sbe IO
=> Codebase -- ^ JVM Codebase
-> Backend sbe -- ^ A symbolic backend
-> SEH sbe IO -- ^ Simulation event handlers (use defaultSEH if no
-- event handling is needed)
-> Maybe SimulationFlags -- ^ Simulation flags
-> Simulator sbe IO a -- ^ Simulator action to perform
-> IO a
runSimulator cb sbe seh mflags m = do
newSt <- initialState cb sbe (fromMaybe defaultSimFlags mflags) seh
ea <- flip evalStateT newSt $ runExceptT $ runSM $ do
stdOverrides
m
case ea of
Left e -> throwM $ Common.SimulatorException $ show $ ppInternalExc e
Right x -> return x
-- | Get the return value of a completed simulator execution. If there
-- is none (e.g., we executed @main@), or if the simulator is still
-- running, return 'Nothing'.
getProgramReturnValue :: MonadSim sbe m
=> Simulator sbe m (Maybe (Value (SBETerm sbe)))
getProgramReturnValue = do
cs <- use ctrlStk
if isFinished cs
then case currentPath cs of
Just p -> return (p^.pathRetVal)
_ -> return Nothing
else return Nothing
-- | Get the return value of a completed simulator execution. If the simulator is still
-- running, or all paths ended in errors, return 'Nothing'.
getProgramFinalMem :: MonadSim sbe m
=> Simulator sbe m (Maybe (Memory (SBETerm sbe)))
getProgramFinalMem = do
cs <- use ctrlStk
if isFinished cs
then do mp <- getPathMaybe
case mp of
Just p -> return . Just $ p^.pathMemory
_ -> return Nothing
else return Nothing
-- | Get a list of all the 'ErrorPath's that have arisen so far during
-- a simulator execution.
getProgramErrorPaths :: MonadSim sbe m
=> Simulator sbe m [ErrorPath sbe]
getProgramErrorPaths = use errorPaths
--------------------------------------------------------------------------------
-- Memory operations
-- | Returns a new reference value with the given type, but does not
-- initialize and fields or array elements.
-- Note: Assumes class for reference has already been initialized.
genRef :: Type -> Simulator sbe m Ref
genRef tp = do
r <- nextRef <+= 1
return $ Ref r tp
-- Array Operations {{{1
-- | Create a new symbolic array with the given type, length and initial value.
-- TODO: Identify appropriate error to throw if type is not an array of IValues or
-- LValues or cnt is negative.
newSymbolicArray :: MonadSim sbe m
=> Type -> Int32 -> SBETerm sbe -> Simulator sbe m Ref
newSymbolicArray tp@(ArrayType eltType) cnt arr = do
assert "newSymbolicArray type"
((isIValue eltType || eltType == LongType) && cnt >= 0)
r <- genRef tp
m <- getMem "newSymbolicArray"
setMem (m & memScalarArrays %~ M.insert r (cnt, arr))
return r
newSymbolicArray _ _ _ = err "internal: newSymbolicArray called with invalid type"
-- | Returns length and symbolic value associated with array reference,
-- and nothing if this is not an array reference.
getSymbolicArray :: MonadSim sbe m
=> Ref -> Simulator sbe m (Maybe (SBETerm sbe, SBETerm sbe))
getSymbolicArray r = do
m <- getMem "getSymbolicArray"
case M.lookup r (m^.memScalarArrays) of
Just (len, a) -> do
sbe <- use backend
len' <- liftIO $ termInt sbe len
return . Just $ (len', a)
Nothing -> return Nothing
-- | Sets integer or long array to use using given update function.
-- TODO: Revisit what error should be thrown if ref is not an array reference.
setSymbolicArray :: MonadSim sbe m => Ref -> SBETerm sbe -> Simulator sbe m ()
setSymbolicArray r arr = updateSymbolicArray r (\_ _ _ -> return arr)
-- | Updates integer or long array using given update function.
updateSymbolicArray :: MonadSim sbe m
=> Ref
-> (Backend sbe -> SBETerm sbe -> SBETerm sbe -> IO (SBETerm sbe))
-> Simulator sbe m ()
updateSymbolicArray r modFn = do
m <- getMem "updateSymbolicArray"
(len,arr) <-
maybe (err "updateSymbolicArray: reference is not a symbolic array") return
$ M.lookup r (m^.memScalarArrays)
sbe <- use backend
len' <- liftIO $ termInt sbe len
newArr <- liftIO $ modFn sbe len' arr
setMem (m & memScalarArrays %~ M.insert r (len, newArr))
-- | @newIntArray arTy terms@ produces a reference to a new array of type
-- @arTy@ and populated with given @terms@.
newIntArray :: MonadSim sbe m => Type -> [SBETerm sbe] -> Simulator sbe m Ref
newIntArray tp@(ArrayType eltType) values
| isIValue eltType = do
sbe <- use backend
arr <- liftIO $ do
let v = V.fromList values
l <- termInt sbe (fromIntegral (V.length v))
Just a0 <- termIntArray sbe l
let fn a i = do
ti <- termInt sbe (fromIntegral i)
termSetIntArray sbe l a ti (v V.! i)
V.foldM fn a0 (V.enumFromN 0 (V.length v))
newSymbolicArray tp (safeCast (length values)) arr
newIntArray _ _ = err "internal: newIntArray called with invalid type"
-- | @newLongArray arTy terms@ produces a reference to a new long array
-- populated with given @terms@.
newLongArray :: MonadSim sbe m => [SBETerm sbe] -> Simulator sbe m Ref
newLongArray values = do
sbe <- use backend
arr <- liftIO $ do
let v = V.fromList values
l <- termInt sbe (fromIntegral (V.length v))
Just a0 <- termLongArray sbe l
let fn a i = do
ti <- termInt sbe (fromIntegral i)
termSetLongArray sbe l a ti (v V.! i)
V.foldM fn a0 (V.enumFromN 0 (V.length v))
newSymbolicArray (ArrayType LongType) (safeCast (length values)) arr
-- | Returns array length at given index in the current path.
getArrayLength :: MonadSim sbe m
=> Ref -> Simulator sbe m (SBETerm sbe)
getArrayLength ref =
getType ref >>= \case
ArrayType tp -> do
m <- getMem "getArrayLength"
sbe <- use backend
if isRefType tp
then let Just arr = M.lookup ref (m^.memRefArrays) in
liftIO $ termInt sbe (1 + snd (bounds arr))
else let Just (len,_) = M.lookup ref (m^.memScalarArrays) in
liftIO $ termInt sbe len
errVal -> throwSM $ InvalidType "getArrayLength::ArrayType" $ show errVal
-- | Returns value in array at given index.
getArrayValue :: (AigOps sbe, Functor m, MonadIO m)
=> Ref
-> SBETerm sbe
-> Simulator sbe m (Value (SBETerm sbe))
getArrayValue r idx =
getType r >>= \case
ArrayType tp -> do
sbe <- use backend
m <- getMem "getArrayValue"
if isRefType tp then do
let Just arr = M.lookup r (m^.memRefArrays)
maxIdx = snd (bounds arr)
case asInt sbe idx of
Just i | i <= maxIdx -> return $ RValue (arr ! fromIntegral i)
-- TODO: the following should really be an exception
| otherwise -> err $ "Out of bounds array access (" ++ show i ++
", " ++ show maxIdx ++ ")"
_ -> err ("Not supported: symbolic indexing into arrays of references: " ++ show idx ++ " " ++ show arr)
else if tp == LongType then
liftIO $ do
let Just (l,rslt) = M.lookup r (m^.memScalarArrays)
l' <- termInt sbe l
LValue <$> termGetLongArray sbe l' rslt idx
else do
assert "getArrayValue type" (isIValue tp)
liftIO $ do
let Just (l,rslt) = M.lookup r (m^.memScalarArrays)
l' <- termInt sbe l
IValue <$> termGetIntArray sbe l' rslt idx
errVal -> throwSM $ InvalidType "getArrayValue::ArrayType" $ show errVal
-- | Returns values in an array at a given reference, passing them through the
-- given projection
getArray :: MonadSim sbe m
=> (Value (SBETerm sbe) -> a)
-> Ref
-> Simulator sbe m [a]
getArray f ref = do
sbe <- use backend
len <- getArrayLength ref
case asInt sbe len of
Nothing -> err "Not supported: getArray called on array with symbolic length"
Just l ->
forM [0..l-1] $ \i -> do
liftM f . getArrayValue ref =<< liftIO (termInt sbe i)
-- | Returns values in byte array at given reference.
getByteArray :: MonadSim sbe m
=> Ref -> Simulator sbe m [SBETerm sbe]
getByteArray ref = do
a <- getIntArray ref
withSBE $ \sbe -> mapM (termByteFromInt sbe) a
-- | Returns values in integer array at given reference.
getIntArray :: MonadSim sbe m
=> Ref -> Simulator sbe m [SBETerm sbe]
getIntArray = getArray unIValue
-- | Returns values in the long array at given reference.
getLongArray :: MonadSim sbe m
=> Ref -> Simulator sbe m [SBETerm sbe]
getLongArray = getArray unLValue
-- | Returns elements in reference array at given reference.
getRefArray :: MonadSim sbe m
=> Ref -> Simulator sbe m [Ref]
getRefArray = getArray unRValue
--setStaticFieldValue :: MonadSim sbe m
-- => FieldId -> Value (SBETerm sbe) -> Simulator sbe m ()
-- | Returns type of reference and throws null pointer exception if reference is null.
getType :: (AigOps sbe, Functor m, MonadIO m) => Ref -> Simulator sbe m Type
getType NullRef = throwNullPtrExc
getType (Ref _ tp) = return tp
getInstanceFieldValue :: MonadSim sbe m
=> Ref
-> FieldId
-> Simulator sbe m (Value (SBETerm sbe))
getInstanceFieldValue ref fldId = do
m <- getMem "getInstanceFieldValue"
case M.lookup (ref, fldId) (m^.memInstanceFields) of
Just v -> return v
Nothing -> err $ "getInstanceFieldValue: instance field " ++
show fldId ++ " does not exist"
getStaticFieldValue :: MonadSim sbe m
=> FieldId
-> Simulator sbe m (Value (SBETerm sbe))
getStaticFieldValue fldId = do
let cName = fieldIdClass fldId
-- this might be the first time we reference this class, so initialize
initializeClass cName
cl <- lookupClass cName
m <- getMem "getStaticFieldValue"
case M.lookup fldId (m^.memStaticFields) of
Just v -> return v
Nothing -> do
assert "validStaticField" (validStaticField cl)
withSBE $ \sbe -> defaultValue sbe (fieldIdType fldId)
where
validStaticField cl =
maybe False (\f -> fieldIsStatic f && fieldType f == fieldIdType fldId)
$ find (\fld -> fieldName fld == fieldIdName fldId)
$ classFields cl
tryModifyCS :: Monad m => String -> (CS sbe -> Maybe (CS sbe)) -> Simulator sbe m ()
tryModifyCS ctx f = ctrlStk %= fn
where fn = fromMaybe (error e) . f
where e = "internal: tryModifyCS " ++ show ctx
-- @setMem@ sets the memory model in the current path, which must exist.
setMem :: (Functor m, Monad m) => Memory (SBETerm sbe) -> Simulator sbe m ()
setMem mem = tryModifyCS "setMem" $ modifyPath $ set pathMemory mem
withSBE :: MonadIO m
=> (Backend sbe -> IO a) -> Simulator sbe m a
withSBE f = uses backend f >>= liftIO
withSBE' :: Monad m
=> (Backend sbe -> a) -> Simulator sbe m a
withSBE' f = uses backend f
setSEH :: Monad m => SEH sbe m -> Simulator sbe m ()
setSEH = assign evHandlers
ppCurrentMethod :: Path' term -> Doc
ppCurrentMethod p = case currentCallFrame p of
Nothing -> "<no current method>"
Just cf -> text (unClassName (cf^.cfClass)) <> "." <> ppMethod (cf^.cfMethod)
-- | Converts integral into bounded num class.
-- TODO: Revisit error handling when integer is out of range.
safeCast :: (Integral s, Bounded t, Integral t, Num t) => s -> t
safeCast = impl minBound maxBound . toInteger
where impl :: Integral t => t -> t -> Integer -> t
impl minb maxb s
| toInteger minb <= s && s <= toInteger maxb = fromInteger s
| otherwise = error "internal: safeCast argument out of range"
-- initializeClass :: MonadSim sbe m => String -> Simulator sbe m ()
-- | Run a static method in the current simulator context, temporarily
-- suspending the current call stack. Useful for implementing method
-- overrides. For top-level invocations, use 'runStaticMethod' instead.
execStaticMethod :: MonadSim sbe m
=> ClassName
-> MethodKey
-> [Value (SBETerm sbe)]
-> Simulator sbe m (Maybe (Value (SBETerm sbe)))
execStaticMethod cName key args = do
mp <- execMethod cName key (setupLocals args)
case mp of
Nothing -> err "execStaticMethod: all paths failed"
Just (_, mrv) -> return mrv
-- | Run an instance method in the current simulator context, using
-- @ref@ as @this@, temporarily suspending the current call
-- stack. Useful for implementing method overrides.
execInstanceMethod :: MonadSim sbe m
=> ClassName
-> MethodKey
-> Ref
-> [Value (SBETerm sbe)]
-> Simulator sbe m (Maybe (Value (SBETerm sbe)))
execInstanceMethod cName key ref args = do
mp <- execMethod cName key (setupLocals (RValue ref : args))
case mp of
Nothing -> err "execInstanceMethod: all paths failed"
Just (_, mrv) -> return mrv
-- | Low-level method call for when we need to make a method call not
-- prescribed by the symbolic instructions. This arises mainly in
-- class initialization, as well as synthetic code as for
-- printStreams. Does not check for overrides.
execMethod :: MonadSim sbe m
=> ClassName
-> MethodKey
-> M.Map LocalVariableIndex (Value (SBETerm sbe))
-> Simulator sbe m (Maybe (Path sbe, (Maybe (Value (SBETerm sbe)))))
execMethod cName key locals = do
cl <- lookupClass cName
method <- case cl `lookupMethod` key of
Just m -> return m
Nothing -> err . render
$ "runMethod: could not find method" <+> text (unClassName cName)
<> "." <> ppMethodKey key
-- suspend the current computation so we can do a nested invocation of @run@
cs <- use ctrlStk
cs' <- maybe (err "execMethod: no active path") return
$ suspendCS "execMethod" cs
-- partial pattern OK: already handled no-path case
let Just cs'' = pushCallFrame cName method entryBlock locals cs'
-- set suspended control stack
ctrlStk .= cs''
run
finishedCS <- use ctrlStk
case currentPath finishedCS of
Just _ -> modifyPathM "execMethod" $ \p -> do
let p' = p -- p & pathRetVal .~ Nothing
return (Just (p', p^.pathRetVal), p')
Nothing -> return Nothing
hasCustomClassInitialization :: ClassName -> Bool
hasCustomClassInitialization "java/lang/System" = True
hasCustomClassInitialization _ = False
runCustomClassInitialization :: MonadSim sbe m => ClassName -> Simulator sbe m ()
runCustomClassInitialization cName = do
let ctx = "runCustomClassInitialization"
case cName of
"java/lang/System" -> do
dbugM' 5 $ "initializing java/lang/System"
-- we're only here if initializeClass found Nothing for
-- initialization status, so we don't need to check again
setMem . setInitializationStatus cName Started =<< getMem ctx
initializeClass pstreamName
outStream <- RValue `liftM` genRef (ClassType pstreamName)
errStream <- RValue `liftM` genRef (ClassType pstreamName)
setStaticFieldValue (FieldId cName "out" pstreamType) outStream
setStaticFieldValue (FieldId cName "err" pstreamType) errStream
setMem . setInitializationStatus cName Initialized =<< getMem ctx
_ -> return ()
where pstreamName = "java/io/PrintStream"
pstreamType = ClassType pstreamName
--newObject :: MonadSim sbe m => String -> Simulator sbe m Ref
errorPath ::
( MonadIO m
, Functor m
)
=> FailRsn -> Simulator sbe m a
errorPath rsn = do
sbe <- use backend
-- Update control stack.
p <- getPath $ "errorPath" <+> parens (ppFailRsn rsn)
-- Log error path
whenVerbosity (>=3) $ do
dbugM $ "Error path encountered: " ++ show (ppFailRsn rsn)
dbugM $ show $ ppPath sbe p
cs' <- markCurrentPathAsError =<< use ctrlStk
s <- get
let s' = s & ctrlStk .~ cs'
& errorPaths %~ (EP rsn p :)
-- Merge negation of assumptions in current path into conditions on merge frame.
-- NB: Since we've set up the control stack for the next invocation of
-- run, and explicitly captured the error path, we need to be sure to
-- ship that modified state back to the catch site so it execution can
-- continue correctly.
throwSM $ ErrorPathExc rsn s'
-- | Returns a default value for objects with given type, suitable for
-- initializing fields.
defaultValue :: Backend sbe -> Type -> IO (Value (SBETerm sbe))
defaultValue _ (ArrayType _tp) = return $ RValue NullRef
defaultValue sbe BooleanType = IValue <$> termInt sbe 0
defaultValue sbe ByteType = IValue <$> termInt sbe 0
defaultValue sbe CharType = IValue <$> termInt sbe 0
defaultValue _ (ClassType _st) = return $ RValue NullRef
defaultValue _ DoubleType = return $ DValue 0.0
defaultValue _ FloatType = return $ FValue 0.0
defaultValue sbe IntType = IValue <$> termInt sbe 0
defaultValue sbe LongType = LValue <$> termLong sbe 0
defaultValue sbe ShortType = IValue <$> termInt sbe 0
-- REVISIT: it may make sense for this to be dynamic
skipInit :: ClassName -> Bool
skipInit cname = cname `elem` [ "java/lang/Object"
, "java/lang/System"
, "java/io/Reader"
, "java/io/InputStreamReader"
]
runInsns :: MonadSim sbe m
=> [(Maybe PC, SymInsn)] -> Simulator sbe m ()
runInsns insns = mapM_ dbugStep insns
dbugStep :: MonadSim sbe m
=> (Maybe PC, SymInsn) -> Simulator sbe m ()
dbugStep (pc, insn) = do
mp <- getPathMaybe
case mp of
Nothing -> dbugM' 4 . render $ "Executing: (no current path)"
<> colon <+> (ppSymInsn insn)
Just p -> do
let loc = case currentCallFrame p of
Nothing -> "<unknown method>" <> parens bid
Just cf -> text (unClassName (cf^.cfClass))
<> "." <> ppMethod (cf^.cfMethod)
<> parens bid
bid = case p^.pathBlockId of
Nothing -> "<no current block>"
Just b -> ppBlockId b
dbugM' 4 . render $
"Executing" <+> parens ("#" <> integer (p^.pathName))
<+> loc <> colon <+> (ppSymInsn insn)
-- repl
cb2 onPreStep pc insn
step insn
cb2 onPostStep pc insn
whenVerbosity (>=6) $ do
p <- getPath "dbugStep"
sbe <- use backend
dbugM . render $ ppPath sbe p
-- | Execute a single LLVM-Sym AST instruction
step :: MonadSim sbe m
=> SymInsn -> Simulator sbe m ()
-- invokeinterface
step (PushInvokeFrame InvInterface (ClassType iName) key retBlock) = do
reverseArgs <- replicateM (length (methodKeyParameterTypes key)) popValue
objectRef <- rPop
throwIfRefNull objectRef
dynBind iName key objectRef $ \iName' -> do
pushInstanceMethodCall iName'
key
objectRef
(reverse reverseArgs)
(Just retBlock)
step (PushInvokeFrame InvInterface ty _ _) = do
err . render $ "step: invokeinterface on type" <+> ppType ty
-- invokespecial
-- arises from super.m() calls
step (PushInvokeFrame InvSpecial (ClassType cName) key retBlock) = do
currentClassName <- Common.getCurrentClassName
reverseArgs <- replicateM (length (methodKeyParameterTypes key)) popValue
currentClass <- lookupClass currentClassName
objectRef <- rPop
cb <- use codebase
b <- liftIO $ isStrictSuper cb cName currentClass
let args = reverse reverseArgs
call cName' = pushInstanceMethodCall cName' key objectRef args (Just retBlock)
if classHasSuperAttribute currentClass && b && methodKeyName key /= "<init>"
then do
dynBindSuper cName key objectRef call
else
do throwIfRefNull objectRef
call cName
step (PushInvokeFrame InvSpecial (ArrayType _methodType) key retBlock) = do
reverseArgs <- replicateM (length (methodKeyParameterTypes key)) popValue
objectRef <- rPop
throwIfRefNull objectRef
pushInstanceMethodCall "java/lang/Object"
key
objectRef
(reverse reverseArgs)
(Just retBlock)
step (PushInvokeFrame InvSpecial ty _ _) = do
err . render $ "step: invokespecial on type" <+> ppType ty
-- invokevirtual
step (PushInvokeFrame InvVirtual (ArrayType _methodType) key retBlock) = do
reverseArgs <- replicateM (length (methodKeyParameterTypes key)) popValue
objectRef <- rPop
throwIfRefNull objectRef
pushInstanceMethodCall "java/lang/Object"
key
objectRef
(reverse reverseArgs)
(Just retBlock)
step (PushInvokeFrame InvVirtual (ClassType cName) key retBlock) = do
reverseArgs <- replicateM (length (methodKeyParameterTypes key)) popValue
objectRef <- rPop
throwIfRefNull objectRef
dynBind cName key objectRef $ \cName' -> do
pushInstanceMethodCall cName'
key
objectRef
(reverse reverseArgs)
(Just retBlock)
step (PushInvokeFrame InvVirtual ty _ _) = do
err . render $ "step: invokevirtual on type" <+> ppType ty
-- invokestatic
step (PushInvokeFrame InvStatic (ClassType cName) key retBlock) = do
reverseArgs <- replicateM (length (methodKeyParameterTypes key)) popValue
pushStaticMethodCall cName key (reverse reverseArgs) (Just retBlock)
step (PushInvokeFrame InvStatic ty _ _) = do
err . render $ "step: invokestatic on type" <+> ppType ty
step (PushInvokeFrame InvDynamic _ _ _) =
err . render $ "step: invokedynamic not supported"
step ReturnVoid = modifyCSM_ $ returnCurrentPath Nothing
step ReturnVal = do
rv <- popValue
modifyCSM_ $ returnCurrentPath (Just rv)
step (PushPendingExecution bid cond ml elseInsns) = do
sbe <- use backend
c <- evalCond cond
flags <- use simulationFlags
p <- getPath "PushPendingExecution"
tsat <- if satAtBranches flags
then liftIO $ satTerm sbe =<< termAnd sbe c (p^.pathAssertions)
else return True
fsat <- if satAtBranches flags
then liftIO $ do
cnot <- termNot sbe c
satTerm sbe =<< termAnd sbe cnot (p^.pathAssertions)
else return True
b <- if alwaysBitBlastBranchTerms flags
then liftIO (blastTerm sbe c)