-
Notifications
You must be signed in to change notification settings - Fork 77
/
Copy pathbase.ml
3134 lines (2974 loc) · 146 KB
/
base.ml
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
(** Non-relational value analysis aka {e base analysis} ([base]). *)
open Batteries
open GoblintCil
open Pretty
open Analyses
open GobConfig
open BaseUtil
open ReturnUtil
module A = Analyses
module H = Hashtbl
module Q = Queries
module ID = ValueDomain.ID
module FD = ValueDomain.FD
module IdxDom = ValueDomain.IndexDomain
module AD = ValueDomain.AD
module Addr = ValueDomain.Addr
module Offs = ValueDomain.Offs
module ZeroInit = ValueDomain.ZeroInit
module LF = LibraryFunctions
module CArrays = ValueDomain.CArrays
module PU = PrecisionUtil
module VD = BaseDomain.VD
module CPA = BaseDomain.CPA
module Dep = BaseDomain.PartDeps
module WeakUpdates = BaseDomain.WeakUpdates
module BaseComponents = BaseDomain.BaseComponents
module MainFunctor (Priv:BasePriv.S) (RVEval:BaseDomain.ExpEvaluator with type t = BaseComponents (Priv.D).t) =
struct
include Analyses.DefaultSpec
exception Top
module Dom = BaseDomain.DomFunctor (Priv.D) (RVEval)
type t = Dom.t
module D = Dom
include Analyses.ValueContexts(D)
(* Two global invariants:
1. Priv.V -> Priv.G -- used for Priv
2. thread -> VD -- used for thread returns *)
module V =
struct
include Printable.Either (struct include Priv.V let name () = "priv" end) (struct include ThreadIdDomain.Thread let name () = "threadreturn" end)
let priv x = `Left x
let thread x = `Right x
include StdV
end
module G =
struct
include Lattice.Lift2Conf (struct include Printable.DefaultConf let expand1 = false let expand2 = false end) (Priv.G) (VD)
let priv = function
| `Bot -> Priv.G.bot ()
| `Lifted1 x -> x
| _ -> failwith "Base.priv"
let thread = function
| `Bot -> VD.bot ()
| `Lifted2 x -> x
| _ -> failwith "Base.thread"
let create_priv priv = `Lifted1 priv
let create_thread thread = `Lifted2 thread
end
let priv_getg getg g = G.priv (getg (V.priv g))
let priv_sideg sideg g d = sideg (V.priv g) (G.create_priv d)
type extra = (varinfo * Offs.t * bool) list
type store = D.t
type value = VD.t
type address = AD.t
type glob_fun = V.t -> G.t
type glob_diff = (V.t * G.t) list
let name () = "base"
let startstate v: store = { cpa = CPA.bot (); deps = Dep.bot (); weak = WeakUpdates.bot (); priv = Priv.startstate ()}
let exitstate v: store = { cpa = CPA.bot (); deps = Dep.bot (); weak = WeakUpdates.bot (); priv = Priv.startstate ()}
(**************************************************************************
* Helpers
**************************************************************************)
let is_privglob v = GobConfig.get_bool "annotation.int.privglobs" && v.vglob
(*This is a bit of a hack to be able to change array domains if a pointer to an array is given as an argument*)
(*We have to prevent different domains to be used at the same time for the same array*)
(*After a function call, the domain has to be the same as before and we can not depend on the pointers staying the same*)
(*-> we determine the arrays a pointer can point to once at the beginning of a function*)
(*There surely is a better way, because this means that often the wrong one gets chosen*)
module VarH = Hashtbl.Make(CilType.Varinfo)
module VarMap = Map.Make(CilType.Varinfo)
let array_map = ref (VarH.create 20)
type marshal = attributes VarMap.t VarH.t
let array_domain_annotation_enabled = lazy (GobConfig.get_bool "annotation.goblint_array_domain")
let add_to_array_map fundec arguments =
if Lazy.force array_domain_annotation_enabled then
let rec pointedArrayMap = function
| [] -> VarMap.empty
| (info,(value:VD.t))::xs ->
match value with
| Address t when hasAttribute "goblint_array_domain" info.vattr ->
let possibleVars = List.to_seq (AD.to_var_may t) in
Seq.fold_left (fun map arr -> VarMap.add arr (info.vattr) map) (pointedArrayMap xs) @@ Seq.filter (fun info -> isArrayType info.vtype) possibleVars
| _ -> pointedArrayMap xs
in
match VarH.find_option !array_map fundec.svar with
| Some _ -> () (*We already have something -> do not change it*)
| None -> VarH.add !array_map fundec.svar (pointedArrayMap arguments)
let attributes_varinfo info fundec =
if Lazy.force array_domain_annotation_enabled then
let map = VarH.find !array_map fundec.svar in
match VarMap.find_opt info map with
| Some attr -> Some (attr, typeAttrs (info.vtype)) (*if the function has a different domain for this array, use it*)
| None -> Some (info.vattr, typeAttrs (info.vtype))
else
None
let project_val ask array_attr p_opt value is_glob =
let p = if GobConfig.get_bool "annotation.int.enabled" then (
if is_glob then
Some PU.max_int_precision
else p_opt
) else None
in
let a = if GobConfig.get_bool "annotation.goblint_array_domain" then array_attr else None in
VD.project ask p a value
let project ask p_opt cpa fundec =
CPA.mapi (fun varinfo value -> project_val ask (attributes_varinfo varinfo fundec) p_opt value (is_privglob varinfo)) cpa
(**************************************************************************
* Initializing my variables
**************************************************************************)
let heap_var on_stack ctx =
let info = match (ctx.ask (Q.AllocVar {on_stack})) with
| `Lifted vinfo -> vinfo
| _ -> failwith("Ran without a malloc analysis.") in
info
(* hack for char a[] = {"foo"} or {'f','o','o', '\000'} *)
let char_array : (lval, bytes) Hashtbl.t = Hashtbl.create 500
let init marshal =
begin match marshal with
| Some marshal -> array_map := marshal
| None -> ()
end;
return_varstore := Cilfacade.create_var @@ makeVarinfo false "RETURN" voidType;
longjmp_return := Cilfacade.create_var @@ makeVarinfo false "LONGJMP_RETURN" intType;
Priv.init ()
let finalize () =
Priv.finalize ();
!array_map
(**************************************************************************
* Abstract evaluation functions
**************************************************************************)
let iDtoIdx = ID.cast_to (Cilfacade.ptrdiff_ikind ())
let unop_ID = function
| Neg -> ID.neg
| BNot -> ID.lognot
| LNot -> ID.c_lognot
let unop_FD = function
| Neg -> (fun v -> (Float (FD.neg v):value))
| LNot -> (fun c -> Int (FD.eq c (FD.of_const (FD.get_fkind c) 0.)))
| BNot -> failwith "BNot on a value of type float!"
(* Evaluating Cil's unary operators. *)
let evalunop op typ: value -> value = function
| Int v1 -> Int (ID.cast_to (Cilfacade.get_ikind typ) (unop_ID op v1))
| Float v -> unop_FD op v
| Address a when op = LNot ->
if AD.is_null a then
Int (ID.of_bool (Cilfacade.get_ikind typ) true)
else if AD.is_not_null a then
Int (ID.of_bool (Cilfacade.get_ikind typ) false)
else
Int (ID.top_of (Cilfacade.get_ikind typ))
| Bot -> Bot
| _ -> VD.top ()
let binop_ID (result_ik: Cil.ikind) = function
| PlusA -> ID.add
| MinusA -> ID.sub
| Mult -> ID.mul
| Div -> ID.div
| Mod -> ID.rem
| Lt -> ID.lt
| Gt -> ID.gt
| Le -> ID.le
| Ge -> ID.ge
| Eq -> ID.eq
(* TODO: This causes inconsistent results:
def_exc and interval definitely in conflict:
evalint: base eval_rv m -> (Not {0, 1}([-31,31]),[1,1])
evalint: base eval_rv 1 -> (1,[1,1])
evalint: base query_evalint m == 1 -> (0,[1,1]) *)
| Ne -> ID.ne
| BAnd -> ID.logand
| BOr -> ID.logor
| BXor -> ID.logxor
| Shiftlt -> ID.shift_left
| Shiftrt -> ID.shift_right
| LAnd -> ID.c_logand
| LOr -> ID.c_logor
| b -> (fun x y -> (ID.top_of result_ik))
let binop_FD (result_fk: Cil.fkind) = function
| PlusA -> FD.add
| MinusA -> FD.sub
| Mult -> FD.mul
| Div -> FD.div
| _ -> (fun _ _ -> FD.top_of result_fk)
let int_returning_binop_FD = function
| Lt -> FD.lt
| Gt -> FD.gt
| Le -> FD.le
| Ge -> FD.ge
| Eq -> FD.eq
| Ne -> FD.ne
| _ -> (fun _ _ -> ID.top ())
let is_int_returning_binop_FD = function
| Lt | Gt | Le | Ge | Eq | Ne -> true
| _ -> false
(* Evaluate binop for two abstract values: *)
let evalbinop_base ~ctx (op: binop) (t1:typ) (a1:value) (t2:typ) (a2:value) (t:typ) :value =
if M.tracing then M.tracel "eval" "evalbinop %a %a %a" d_binop op VD.pretty a1 VD.pretty a2;
(* We define a conversion function for the easy cases when we can just use
* the integer domain operations. *)
let bool_top ik = ID.(join (of_int ik Z.zero) (of_int ik Z.one)) in
(* An auxiliary function for ptr arithmetic on array values. *)
let addToAddr n (addr:Addr.t) =
let typeOffsetOpt o t =
try
Some (Cilfacade.typeOffset t o)
with Cilfacade.TypeOfError _ ->
None
in
(* adds n to the last offset *)
let rec addToOffset n (t:typ option) = function
| `Index (i, `NoOffset) ->
(* Binary operations on pointer types should not generate warnings in SV-COMP *)
GobRef.wrap AnalysisState.executing_speculative_computations true @@ fun () ->
(* If we have arrived at the last Offset and it is an Index, we add our integer to it *)
`Index(IdxDom.add i (iDtoIdx n), `NoOffset)
| `Field (f, `NoOffset) ->
(* If we have arrived at the last Offset and it is a Field,
* then check if we're subtracting exactly its offsetof.
* If so, n cancels out f exactly.
* This is to better handle container_of hacks. *)
let n_offset = iDtoIdx n in
begin match t with
| Some t ->
let (f_offset_bits, _) = bitsOffset t (Field (f, NoOffset)) in
let f_offset = IdxDom.of_int (Cilfacade.ptrdiff_ikind ()) (Z.of_int (f_offset_bits / 8)) in
begin match IdxDom.(to_bool (eq f_offset (neg n_offset))) with
| Some true -> `NoOffset
| _ -> `Field (f, `Index (n_offset, `NoOffset))
end
| None -> `Field (f, `Index (n_offset, `NoOffset))
end
| `Index (i, o) ->
let t' = BatOption.bind t (typeOffsetOpt (Index (integer 0, NoOffset))) in (* actual index value doesn't matter for typeOffset *)
`Index(i, addToOffset n t' o)
| `Field (f, o) ->
let t' = BatOption.bind t (typeOffsetOpt (Field (f, NoOffset))) in
`Field(f, addToOffset n t' o)
| `NoOffset -> `Index(iDtoIdx n, `NoOffset)
in
let default = function
| Addr.NullPtr when GobOption.exists (Z.equal Z.zero) (ID.to_int n) -> Addr.NullPtr
| _ -> Addr.UnknownPtr
in
match Addr.to_mval addr with
| Some (x, o) -> Addr.of_mval (x, addToOffset n (Some x.vtype) o)
| None -> default addr
in
let addToAddrOp p (n:ID.t):value =
match op with
(* For array indexing e[i] and pointer addition e + i we have: *)
| IndexPI | PlusPI ->
Address (AD.map (addToAddr n) p)
(* Pointer subtracted by a value (e-i) is very similar *)
(* Cast n to the (signed) ptrdiff_ikind, then add the its negated value. *)
| MinusPI ->
let n = ID.neg (ID.cast_to (Cilfacade.ptrdiff_ikind ()) n) in
Address (AD.map (addToAddr n) p)
| Mod -> Int (ID.top_of (Cilfacade.ptrdiff_ikind ())) (* we assume that address is actually casted to int first*)
| _ -> Address AD.top_ptr
in
(* The main function! *)
match a1,a2 with
(* For the integer values, we apply the int domain operator *)
| Int v1, Int v2 ->
let result_ik = Cilfacade.get_ikind t in
Int (ID.cast_to result_ik (binop_ID result_ik op v1 v2))
(* For the float values, we apply the float domain operators *)
| Float v1, Float v2 when is_int_returning_binop_FD op ->
let result_ik = Cilfacade.get_ikind t in
Int (ID.cast_to result_ik (int_returning_binop_FD op v1 v2))
| Float v1, Float v2 -> Float (binop_FD (Cilfacade.get_fkind t) op v1 v2)
(* For address +/- value, we try to do some elementary ptr arithmetic *)
| Address p, Int n
| Int n, Address p when op=Eq || op=Ne ->
let ik = Cilfacade.get_ikind t in
let res =
if AD.is_null p then
match ID.equal_to Z.zero n with
| `Neq ->
(* n is definitely not 0, p is NULL *)
ID.of_bool ik (op = Ne)
| `Eq ->
(* n is zero, p is NULL*)
ID.of_bool ik (op = Eq)
| _ -> bool_top ik
else if AD.is_not_null p then
match ID.equal_to Z.zero n with
| `Eq ->
(* n is zero, p is not NULL *)
ID.of_bool ik (op = Ne)
| _ -> bool_top ik
else
bool_top ik
in
Int res
| Address p, Int n ->
addToAddrOp p n
| Address p, Top ->
(* same as previous, but with Unknown instead of int *)
(* TODO: why does this even happen in zstd-thread-pool-add? *)
let n = ID.top_of (Cilfacade.ptrdiff_ikind ()) in (* pretend to have unknown ptrdiff int instead *)
addToAddrOp p n
(* If both are pointer values, we can subtract them and well, we don't
* bother to find the result in most cases, but it's an integer. *)
| Address p1, Address p2 -> begin
let ik = Cilfacade.get_ikind t in
let eq x y =
if AD.is_definite x && AD.is_definite y then
let ax = AD.choose x in
let ay = AD.choose y in
let handle_address_is_multiple addr = begin match Addr.to_var addr with
| Some v when ctx.ask (Q.IsMultiple v) ->
if M.tracing then M.tracel "addr" "IsMultiple %a" CilType.Varinfo.pretty v;
None
| _ ->
Some true
end
in
match Addr.semantic_equal ax ay with
| Some true ->
if M.tracing then M.tracel "addr" "semantic_equal %a %a" AD.pretty x AD.pretty y;
handle_address_is_multiple ax
| Some false -> Some false
| None -> None
else
None
in
match op with
(* TODO use ID.of_incl_list [0; 1] for all comparisons *)
| MinusPP ->
(* when subtracting pointers to arrays, per 6.5.6 of C-standard if we subtract two pointers to the same array, the difference *)
(* between them is the difference in subscript *)
begin
let rec calculateDiffFromOffset x y:value =
match x, y with
| `Field ((xf:Cil.fieldinfo), xo), `Field((yf:Cil.fieldinfo), yo)
when CilType.Fieldinfo.equal xf yf ->
calculateDiffFromOffset xo yo
| `Index (i, `NoOffset), `Index(j, `NoOffset) ->
begin
let diff = ValueDomain.IndexDomain.sub i j in
match ValueDomain.IndexDomain.to_int diff with
| Some z -> Int(ID.of_int ik z)
| _ -> Int (ID.top_of ik)
end
| `Index (xi, xo), `Index(yi, yo) when xi = yi -> (* TODO: ID.equal? *)
calculateDiffFromOffset xo yo
| _ -> Int (ID.top_of ik)
in
if AD.is_definite p1 && AD.is_definite p2 then
match Addr.to_mval (AD.choose p1), Addr.to_mval (AD.choose p2) with
| Some (x, xo), Some (y, yo) when CilType.Varinfo.equal x y ->
calculateDiffFromOffset xo yo
| _, _ ->
Int (ID.top_of ik)
else
Int (ID.top_of ik)
end
| Eq ->
Int (if AD.is_bot (AD.meet p1 p2) then ID.of_int ik Z.zero else match eq p1 p2 with Some x when x -> ID.of_int ik Z.one | _ -> bool_top ik)
| Ne ->
Int (if AD.is_bot (AD.meet p1 p2) then ID.of_int ik Z.one else match eq p1 p2 with Some x when x -> ID.of_int ik Z.zero | _ -> bool_top ik)
| IndexPI when AD.to_string p2 = ["all_index"] ->
addToAddrOp p1 (ID.top_of (Cilfacade.ptrdiff_ikind ()))
| IndexPI | PlusPI ->
addToAddrOp p1 (AD.to_int p2) (* sometimes index is AD for some reason... *)
| _ -> VD.top ()
end
(* For other values, we just give up! *)
| Bot, _ -> Bot
| _, Bot -> Bot
| _ -> VD.top ()
(* TODO: Use AddressDomain for queries *)
(* We need the previous function with the varinfo carried along, so we can
* map it on the address sets. *)
let add_offset_varinfo add ad =
match Addr.to_mval ad with
| Some (x,ofs) -> Addr.of_mval (x, Addr.Offs.add_offset ofs add)
| None -> ad
(**************************************************************************
* State functions
**************************************************************************)
let sync' reason ctx: D.t =
let multi =
match reason with
| `Init
| `Thread ->
true
| _ ->
ThreadFlag.has_ever_been_multi (Analyses.ask_of_ctx ctx)
in
if M.tracing then M.tracel "sync" "sync multi=%B earlyglobs=%B" multi !earlyglobs;
if !earlyglobs || multi then
WideningTokens.with_local_side_tokens (fun () ->
Priv.sync (Analyses.ask_of_ctx ctx) (priv_getg ctx.global) (priv_sideg ctx.sideg) ctx.local reason
)
else
ctx.local
let sync ctx reason = sync' (reason :> [`Normal | `Join | `JoinCall | `Return | `Init | `Thread]) ctx
let publish_all ctx reason =
ignore (sync' reason ctx)
let get_var ~ctx (st: store) (x: varinfo): value =
let ask = Analyses.ask_of_ctx ctx in
if (!earlyglobs || ThreadFlag.has_ever_been_multi ask) && is_global ask x then
Priv.read_global ask (priv_getg ctx.global) st x
else begin
if M.tracing then M.tracec "get" "Singlethreaded mode.";
CPA.find x st.cpa
end
(** [get st addr] returns the value corresponding to [addr] in [st]
* adding proper dependencies.
* For the exp argument it is always ok to put None. This means not using precise information about
* which part of an array is involved. *)
let rec get ~ctx ?(top=VD.top ()) ?(full=false) (st: store) (addrs:address) (exp:exp option): value =
let firstvar = if M.tracing then match AD.to_var_may addrs with [] -> "" | x :: _ -> x.vname else "" in
if M.tracing then M.traceli "get" ~var:firstvar "Address: %a\nState: %a" AD.pretty addrs CPA.pretty st.cpa;
(* Finding a single varinfo*offset pair *)
let res =
let f_addr (x, offs) =
(* get hold of the variable value, either from local or global state *)
let var = get_var ~ctx st x in
let v = VD.eval_offset (Queries.to_value_domain_ask (Analyses.ask_of_ctx ctx)) (fun x -> get ~ctx st x exp) var offs exp (Some (Var x, Offs.to_cil_offset offs)) x.vtype in
if M.tracing then M.tracec "get" "var = %a, %a = %a" VD.pretty var AD.pretty (AD.of_mval (x, offs)) VD.pretty v;
if full then var else match v with
| Blob (c,s,_) -> c
| x -> x
in
let f = function
| Addr.Addr (x, o) -> f_addr (x, o)
| Addr.NullPtr ->
begin match get_string "sem.null-pointer.dereference" with
| "assume_none" -> VD.bot ()
| "assume_top" -> top
| _ -> assert false
end
| Addr.UnknownPtr -> top (* top may be more precise than VD.top, e.g. for address sets, such that known addresses are kept for soundness *)
| Addr.StrPtr _ -> Int (ID.top_of IChar)
in
(* We form the collecting function by joining *)
let c (x:value) = match x with (* If address type is arithmetic, and our value is an int, we cast to the correct ik *)
| Int _ ->
let at = AD.type_of addrs in
if Cil.isArithmeticType at then
VD.cast at x
else
x
| _ -> x
in
let f x a = VD.join (c @@ f x) a in (* Finally we join over all the addresses in the set. *)
AD.fold f addrs (VD.bot ())
in
if M.tracing then M.traceu "get" "Result: %a" VD.pretty res;
res
(**************************************************************************
* Auxiliary functions for function calls
**************************************************************************)
(* From a list of values, presumably arguments to a function, simply extract
* the pointer arguments. *)
let get_ptrs (vals: value list): address list =
let f (x:value) acc = match x with
| Address adrs when AD.is_top adrs ->
M.info ~category:Unsound "Unknown address given as function argument"; acc
| Address adrs when AD.to_var_may adrs = [] -> acc
| Address adrs ->
let typ = AD.type_of adrs in
if isFunctionType typ then acc else adrs :: acc
| Top -> M.info ~category:Unsound "Unknown value type given as function argument"; acc
| _ -> acc
in
List.fold_right f vals []
let rec reachable_from_value ask (value: value) (t: typ) (description: string) =
let empty = AD.empty () in
if M.tracing then M.trace "reachability" "Checking value %a" VD.pretty value;
match value with
| Top ->
if not (VD.is_immediate_type t) then M.info ~category:Unsound "Unknown value in %s could be an escaped pointer address!" description; empty
| Bot -> (*M.debug ~category:Analyzer "A bottom value when computing reachable addresses!";*) empty
| Address adrs when AD.is_top adrs ->
M.info ~category:Unsound "Unknown address in %s has escaped." description; AD.remove Addr.NullPtr adrs (* return known addresses still to be a bit more sane (but still unsound) *)
(* The main thing is to track where pointers go: *)
| Address adrs -> AD.remove Addr.NullPtr adrs
(* Unions are easy, I just ingore the type info. *)
| Union (f,e) -> reachable_from_value ask e t description
(* For arrays, we ask to read from an unknown index, this will cause it
* join all its values. *)
| Array a -> reachable_from_value ask (ValueDomain.CArrays.get (Queries.to_value_domain_ask ask) a (None, ValueDomain.ArrIdxDomain.top ())) t description
| Blob (e,_,_) -> reachable_from_value ask e t description
| Struct s -> ValueDomain.Structs.fold (fun k v acc -> AD.join (reachable_from_value ask v t description) acc) s empty
| Int _ -> empty
| Float _ -> empty
| MutexAttr _ -> empty
| Thread _ -> empty (* thread IDs are abstract and nothing known can be reached from them *)
| JmpBuf _ -> empty (* Jump buffers are abstract and nothing known can be reached from them *)
| Mutex -> empty (* mutexes are abstract and nothing known can be reached from them *)
(* Get the list of addresses accessable immediately from a given address, thus
* all pointers within a structure should be considered, but we don't follow
* pointers. We return a flattend representation, thus simply an address (set). *)
let reachable_from_address ~ctx st (adr: address): address =
if M.tracing then M.tracei "reachability" "Checking for %a" AD.pretty adr;
let res = reachable_from_value (Analyses.ask_of_ctx ctx) (get ~ctx st adr None) (AD.type_of adr) (AD.show adr) in
if M.tracing then M.traceu "reachability" "Reachable addresses: %a" AD.pretty res;
res
(* The code for getting the variables reachable from the list of parameters.
* This section is very confusing, because I use the same construct, a set of
* addresses, as both AD elements abstracting individual (ambiguous) addresses
* and the workset of visited addresses. *)
let reachable_vars ~ctx (st: store) (args: address list): address list =
if M.tracing then M.traceli "reachability" "Checking reachable arguments from [%a]!" (d_list ", " AD.pretty) args;
let empty = AD.empty () in
(* We begin looking at the parameters: *)
let argset = List.fold_right (AD.join) args empty in
let workset = ref argset in
(* And we keep a set of already visited variables *)
let visited = ref empty in
while not (AD.is_empty !workset) do
visited := AD.union !visited !workset;
(* ok, let's visit all the variables in the workset and collect the new variables *)
let visit_and_collect var (acc: address): address =
let var = AD.singleton var in (* Very bad hack! Pathetic really! *)
AD.union (reachable_from_address ~ctx st var) acc in
let collected = AD.fold visit_and_collect !workset empty in
(* And here we remove the already visited variables *)
workset := AD.diff collected !visited
done;
(* Return the list of elements that have been visited. *)
if M.tracing then M.traceu "reachability" "All reachable vars: %a" AD.pretty !visited;
List.map AD.singleton (AD.elements !visited)
let reachable_vars ~ctx st args = Timing.wrap "reachability" (reachable_vars ~ctx st) args
let drop_non_ptrs (st:CPA.t) : CPA.t =
if CPA.is_top st then st else
let rec replace_val = function
| VD.Address _ as v -> v
| Blob (v,s,o) ->
begin match replace_val v with
| Blob (Top,_,_)
| Top -> Top
| t -> Blob (t,s,o)
end
| Struct s -> Struct (ValueDomain.Structs.map replace_val s)
| _ -> Top
in
CPA.map replace_val st
let drop_ints (st:CPA.t) : CPA.t =
if CPA.is_top st then st else
let rec replace_val: value -> value = function
| Int _ -> Top
| Array n -> Array (ValueDomain.CArrays.map replace_val n)
| Struct n -> Struct (ValueDomain.Structs.map replace_val n)
| Union (f,v) -> Union (f,replace_val v)
| Blob (n,s,o) -> Blob (replace_val n,s,o)
| Address x -> Address (AD.map ValueDomain.Addr.top_indices x)
| x -> x
in
CPA.map replace_val st
let drop_interval = CPA.map (function Int x -> Int (ID.no_interval x) | x -> x)
let drop_intervalSet = CPA.map (function Int x -> Int (ID.no_intervalSet x) | x -> x )
let context ctx (fd: fundec) (st: store): store =
let f keep drop_fn (st: store) = if keep then st else { st with cpa = drop_fn st.cpa} in
st |>
(* Here earlyglobs only drops syntactic globals from the context and does not consider e.g. escaped globals. *)
(* This is equivalent to having escaped globals excluded from earlyglobs for contexts *)
f (not !earlyglobs) (CPA.filter (fun k v -> (not k.vglob) || is_excluded_from_earlyglobs k))
%> f (ContextUtil.should_keep ~isAttr:GobContext ~keepOption:"ana.base.context.non-ptr" ~removeAttr:"base.no-non-ptr" ~keepAttr:"base.non-ptr" fd) drop_non_ptrs
%> f (ContextUtil.should_keep ~isAttr:GobContext ~keepOption:"ana.base.context.int" ~removeAttr:"base.no-int" ~keepAttr:"base.int" fd) drop_ints
%> f (ContextUtil.should_keep ~isAttr:GobContext ~keepOption:"ana.base.context.interval" ~removeAttr:"base.no-interval" ~keepAttr:"base.interval" fd) drop_interval
%> f (ContextUtil.should_keep ~isAttr:GobContext ~keepOption:"ana.base.context.interval_set" ~removeAttr:"base.no-interval_set" ~keepAttr:"base.interval_set" fd) drop_intervalSet
let reachable_top_pointers_types ctx (ps: AD.t) : Queries.TS.t =
let module TS = Queries.TS in
let empty = AD.empty () in
let reachable_from_address (adr: address) =
let with_type t = function
| (ad,ts,true) ->
begin match unrollType t with
| TPtr (p,_) ->
(ad, TS.add (unrollType p) ts, false)
| _ ->
(ad, ts, false)
end
| x -> x
in
let with_field (a,t,b) = function
| `Top -> (AD.empty (), TS.top (), false)
| `Bot -> (a,t,false)
| `Lifted f -> with_type f.ftype (a,t,b)
in
let rec reachable_from_value (value: value) =
match value with
| Top -> (empty, TS.top (), true)
| Bot -> (empty, TS.bot (), false)
| Address adrs when AD.is_top adrs -> (empty,TS.bot (), true)
| Address adrs -> (adrs,TS.bot (), AD.may_be_unknown adrs)
| Union (t,e) -> with_field (reachable_from_value e) t
| Array a -> reachable_from_value (ValueDomain.CArrays.get (Queries.to_value_domain_ask (Analyses.ask_of_ctx ctx)) a (None, ValueDomain.ArrIdxDomain.top ()))
| Blob (e,_,_) -> reachable_from_value e
| Struct s ->
let join_tr (a1,t1,_) (a2,t2,_) = AD.join a1 a2, TS.join t1 t2, false in
let f k v =
join_tr (with_type k.ftype (reachable_from_value v))
in
ValueDomain.Structs.fold f s (empty, TS.bot (), false)
| Int _ -> (empty, TS.bot (), false)
| Float _ -> (empty, TS.bot (), false)
| MutexAttr _ -> (empty, TS.bot (), false)
| Thread _ -> (empty, TS.bot (), false) (* TODO: is this right? *)
| JmpBuf _ -> (empty, TS.bot (), false) (* TODO: is this right? *)
| Mutex -> (empty, TS.bot (), false) (* TODO: is this right? *)
in
reachable_from_value (get ~ctx ctx.local adr None)
in
let visited = ref empty in
let work = ref ps in
let collected = ref (TS.empty ()) in
while not (AD.is_empty !work) do
let next = ref empty in
let do_one a =
let (x,y,_) = reachable_from_address (AD.singleton a) in
collected := TS.union !collected y;
next := AD.union !next x
in
if not (AD.is_top !work) then
AD.iter do_one !work;
visited := AD.union !visited !work;
work := AD.diff !next !visited
done;
!collected
(* The evaluation function as mutually recursive eval_lv & eval_rv *)
let rec eval_rv ~(ctx: _ ctx) (st: store) (exp:exp): value =
if M.tracing then M.traceli "evalint" "base eval_rv %a" d_exp exp;
let r =
(* we have a special expression that should evaluate to top ... *)
if exp = MyCFG.unknown_exp then
VD.top ()
else
eval_rv_ask_evalint ~ctx st exp
in
if M.tracing then M.traceu "evalint" "base eval_rv %a -> %a" d_exp exp VD.pretty r;
r
(** Evaluate expression using EvalInt query.
Base itself also answers EvalInt, so recursion goes indirectly through queries.
This allows every subexpression to also meet more precise value from other analyses.
Non-integer expression just delegate to next eval_rv function. *)
and eval_rv_ask_evalint ~ctx st exp =
let eval_next () = eval_rv_no_ask_evalint ~ctx st exp in
if M.tracing then M.traceli "evalint" "base eval_rv_ask_evalint %a" d_exp exp;
let r:value =
match Cilfacade.typeOf exp with
| typ when Cil.isIntegralType typ && not (Cil.isConstant exp) -> (* don't EvalInt integer constants, base can do them precisely itself *)
if M.tracing then M.traceli "evalint" "base ask EvalInt %a" d_exp exp;
let a = ctx.ask (Q.EvalInt exp) in (* through queries includes eval_next, so no (exponential) branching is necessary *)
if M.tracing then M.traceu "evalint" "base ask EvalInt %a -> %a" d_exp exp Queries.ID.pretty a;
begin match a with
| `Bot -> eval_next () (* Base EvalInt returns bot on incorrect type (e.g. pthread_t); ignore and continue. *)
(* | x -> Some (Int x) *)
| `Lifted x -> Int x (* cast should be unnecessary, EvalInt should guarantee right ikind already *)
| `Top -> Int (ID.top_of (Cilfacade.get_ikind typ)) (* query cycle *)
end
| exception Cilfacade.TypeOfError _ (* Bug: typeOffset: Field on a non-compound *)
| _ -> eval_next ()
in
if M.tracing then M.traceu "evalint" "base eval_rv_ask_evalint %a -> %a" d_exp exp VD.pretty r;
r
(** Evaluate expression without EvalInt query on outermost expression.
This is used by base responding to EvalInt to immediately directly avoid EvalInt query cycle, which would return top.
Recursive [eval_rv] calls on subexpressions still go through [eval_rv_ask_evalint]. *)
and eval_rv_no_ask_evalint ~ctx st exp =
eval_rv_base ~ctx st exp (* just as alias, so query doesn't weirdly have to call eval_rv_base *)
and eval_rv_back_up ~ctx st exp =
if get_bool "ana.base.eval.deep-query" then
eval_rv ~ctx st exp
else (
(* duplicate unknown_exp check from eval_rv since we're bypassing it now *)
if exp = MyCFG.unknown_exp then
VD.top ()
else
eval_rv_base ~ctx st exp (* bypass all queries *)
)
(** Evaluate expression structurally by base.
This handles constants directly and variables using CPA.
Subexpressions delegate to [eval_rv], which may use queries on them. *)
and eval_rv_base ~ctx (st: store) (exp:exp): value =
let eval_rv = eval_rv_back_up in
if M.tracing then M.traceli "evalint" "base eval_rv_base %a" d_exp exp;
let binop_remove_same_casts ~extra_is_safe ~e1 ~e2 ~t1 ~t2 ~c1 ~c2 =
let te1 = Cilfacade.typeOf e1 in
let te2 = Cilfacade.typeOf e2 in
let both_arith_type = isArithmeticType te1 && isArithmeticType te2 in
let is_safe = (extra_is_safe || VD.is_statically_safe_cast t1 te1 && VD.is_statically_safe_cast t2 te2) && not both_arith_type in
if M.tracing then M.tracel "cast" "remove cast on both sides for %a? -> %b" d_exp exp is_safe;
if is_safe then ( (* we can ignore the casts if the casts can't change the value *)
let e1 = if isArithmeticType te1 then c1 else e1 in
let e2 = if isArithmeticType te2 then c2 else e2 in
(e1, e2)
)
else
(c1, c2)
in
let r =
(* query functions were no help ... now try with values*)
match constFold true exp with
(* Integer literals *)
(* seems like constFold already converts CChr to CInt *)
| Const (CChr x) -> eval_rv ~ctx st (Const (charConstToInt x)) (* char becomes int, see Cil doc/ISO C 6.4.4.4.10 *)
| Const (CInt (num,ikind,str)) ->
(match str with Some x -> if M.tracing then M.tracel "casto" "CInt (%s, %a, %s)" (Z.to_string num) d_ikind ikind x | None -> ());
Int (ID.cast_to ikind (IntDomain.of_const (num,ikind,str)))
| Const (CReal (_,fkind, Some str)) when not (Cilfacade.isComplexFKind fkind) -> Float (FD.of_string fkind str) (* prefer parsing from string due to higher precision *)
| Const (CReal (num, fkind, None)) when not (Cilfacade.isComplexFKind fkind) && num = 0.0 -> Float (FD.of_const fkind num) (* constant 0 is ok, CIL creates these for zero-initializers; it is safe across float types *)
| Const (CReal (_, fkind, None)) when not (Cilfacade.isComplexFKind fkind) -> assert false (* Cil does not create other CReal without string representation *)
(* String literals *)
| Const (CStr (x,_)) -> Address (AD.of_string x) (* normal 8-bit strings, type: char* *)
| Const (CWStr (xs,_) as c) -> (* wide character strings, type: wchar_t* *)
let x = CilType.Constant.show c in (* escapes, see impl. of d_const in cil.ml *)
let x = String.sub x 2 (String.length x - 3) in (* remove surrounding quotes: L"foo" -> foo *)
Address (AD.of_string x) (* Address (AD.str_ptr ()) *)
| Const _ -> VD.top ()
(* Variables and address expressions *)
| Lval lv ->
eval_rv_base_lval ~eval_lv ~ctx st exp lv
(* Binary operators *)
(* Eq/Ne when both values are equal and casted to the same type *)
| BinOp ((Eq | Ne) as op, (CastE (t1, e1) as c1), (CastE (t2, e2) as c2), typ) when typeSig t1 = typeSig t2 ->
let a1 = eval_rv ~ctx st e1 in
let a2 = eval_rv ~ctx st e2 in
let extra_is_safe =
match evalbinop_base ~ctx op t1 a1 t2 a2 typ with
| Int i -> ID.to_bool i = Some true
| _
| exception IntDomain.IncompatibleIKinds _ -> false
in
let (e1, e2) = binop_remove_same_casts ~extra_is_safe ~e1 ~e2 ~t1 ~t2 ~c1 ~c2 in
(* re-evaluate e1 and e2 in evalbinop because might be with cast *)
evalbinop ~ctx st op ~e1 ~t1 ~e2 ~t2 typ
| BinOp (LOr, e1, e2, typ) as exp ->
let open GobOption.Syntax in
(* split nested LOr Eqs to equality pairs, if possible *)
let rec split = function
(* copied from above to support pointer equalities with implicit casts inserted *)
| BinOp (Eq, (CastE (t1, e1) as c1), (CastE (t2, e2) as c2), typ) when typeSig t1 = typeSig t2 ->
Some [binop_remove_same_casts ~extra_is_safe:false ~e1 ~e2 ~t1 ~t2 ~c1 ~c2]
| BinOp (Eq, arg1, arg2, _) ->
Some [(arg1, arg2)]
| BinOp (LOr, arg1, arg2, _) ->
let+ s1 = split arg1
and+ s2 = split arg2 in
s1 @ s2
| _ ->
None
in
(* find common exp from all equality pairs and list of other sides, if possible *)
let find_common = function
| [] -> assert false
| (e1, e2) :: eqs ->
let eqs_for_all_mem e = List.for_all (fun (e1, e2) -> CilType.Exp.(equal e1 e || equal e2 e)) eqs in
let eqs_map_remove e = List.map (fun (e1, e2) -> if CilType.Exp.equal e1 e then e2 else e1) eqs in
if eqs_for_all_mem e1 then
Some (e1, e2 :: eqs_map_remove e1)
else if eqs_for_all_mem e2 then
Some (e2, e1 :: eqs_map_remove e2)
else
None
in
let eqs_value: value option =
let* eqs = split exp in
let* (e, es) = find_common eqs in
let v = eval_rv ~ctx st e in (* value of common exp *)
let vs = List.map (eval_rv ~ctx st) es in (* values of other sides *)
let ik = Cilfacade.get_ikind typ in
match v with
| Address a ->
(* get definite addrs from vs *)
let rec to_definite_ad: value list -> AD.t = function
| [] -> AD.empty ()
| Address a :: vs when AD.is_definite a ->
AD.union a (to_definite_ad vs)
| _ :: vs ->
to_definite_ad vs
in
let definite_ad = to_definite_ad vs in
if AD.leq a definite_ad then (* other sides cover common address *)
Some (VD.Int (ID.of_bool ik true))
else (* TODO: detect disjoint cases using may: https://github.com/goblint/analyzer/pull/757#discussion_r898105918 *)
None
| Int i ->
let module BISet = IntDomain.BISet in
(* get definite ints from vs *)
let rec to_int_set: value list -> BISet.t = function
| [] -> BISet.empty ()
| Int i :: vs ->
begin match ID.to_int i with
| Some i' -> BISet.add i' (to_int_set vs)
| None -> to_int_set vs
end
| _ :: vs ->
to_int_set vs
in
let* incl_list = ID.to_incl_list i in
let incl_set = BISet.of_list incl_list in
let int_set = to_int_set vs in
if BISet.leq incl_set int_set then (* other sides cover common int *)
Some (VD.Int (ID.of_bool ik true))
else (* TODO: detect disjoint cases using may: https://github.com/goblint/analyzer/pull/757#discussion_r898105918 *)
None
| _ ->
None
in
begin match eqs_value with
| Some x -> x
| None -> evalbinop ~ctx st LOr ~e1 ~e2 typ (* fallback to general case *)
end
| BinOp (op,e1,e2,typ) ->
evalbinop ~ctx st op ~e1 ~e2 typ
(* Unary operators *)
| UnOp (op,arg1,typ) ->
let a1 = eval_rv ~ctx st arg1 in
evalunop op typ a1
(* The &-operator: we create the address abstract element *)
| AddrOf lval -> Address (eval_lv ~ctx st lval)
(* CIL's very nice implicit conversion of an array name [a] to a pointer
* to its first element [&a[0]]. *)
| StartOf lval ->
let array_ofs = `Index (IdxDom.of_int (Cilfacade.ptrdiff_ikind ()) Z.zero, `NoOffset) in
let array_start = add_offset_varinfo array_ofs in
Address (AD.map array_start (eval_lv ~ctx st lval))
| CastE (t, Const (CStr (x,e))) -> (* VD.top () *) eval_rv ~ctx st (Const (CStr (x,e))) (* TODO safe? *)
| CastE (t, exp) ->
(let v = eval_rv ~ctx st exp in
try
VD.cast ~torg:(Cilfacade.typeOf exp) t v
with Cilfacade.TypeOfError _ ->
VD.cast t v)
| SizeOf _
| Real _
| Imag _
| SizeOfE _
| SizeOfStr _
| AlignOf _
| AlignOfE _
| Question _
| AddrOfLabel _ ->
VD.top ()
in
if M.tracing then M.traceu "evalint" "base eval_rv_base %a -> %a" d_exp exp VD.pretty r;
r
and eval_rv_base_lval ~eval_lv ~ctx (st: store) (exp: exp) (lv: lval): value =
match lv with
| (Var v, ofs) -> get ~ctx st (eval_lv ~ctx st (Var v, ofs)) (Some exp)
(* | Lval (Mem e, ofs) -> get ~ctx st (eval_lv ~ctx (Mem e, ofs)) *)
| (Mem e, ofs) ->
(*if M.tracing then M.tracel "cast" "Deref: lval: %a" d_plainlval lv;*)
let rec contains_vla (t:typ) = match t with
| TPtr (t, _) -> contains_vla t
| TArray(t, None, args) -> true
| TArray(t, Some exp, args) when isConstant exp -> contains_vla t
| TArray(t, Some exp, args) -> true
| _ -> false
in
let b = Mem e, NoOffset in (* base pointer *)
let t = Cilfacade.typeOfLval b in (* static type of base *)
let p = eval_lv ~ctx st b in (* abstract base addresses *)
(* pre VLA: *)
(* let cast_ok = function Addr a -> sizeOf t <= sizeOf (get_type_addr a) | _ -> false in *)
let cast_ok a =
let open Addr in
match a with
| Addr (x, o) ->
begin
let at = Addr.Mval.type_of (x, o) in
if M.tracing then M.tracel "evalint" "cast_ok %a %a %a" Addr.pretty (Addr (x, o)) CilType.Typ.pretty (Cil.unrollType x.vtype) CilType.Typ.pretty at;
if at = TVoid [] then (* HACK: cast from alloc variable is always fine *)
true
else
match Cil.getInteger (sizeOf t), Cil.getInteger (sizeOf at) with
| Some i1, Some i2 -> Z.compare i1 i2 <= 0
| _ ->
if contains_vla t || contains_vla (Addr.Mval.type_of (x, o)) then
begin
(* TODO: Is this ok? *)
M.info ~category:Unsound "Casting involving a VLA is assumed to work";
true
end
else
false
end
| NullPtr | UnknownPtr -> true (* TODO: are these sound? *)
| _ -> false
in
(** Lookup value at base address [addr] with given offset [ofs]. *)
let lookup_with_offs addr =
let v = (* abstract base value *)
if cast_ok addr then
get ~ctx ~top:(VD.top_value t) st (AD.singleton addr) (Some exp) (* downcasts are safe *)
else
VD.top () (* upcasts not! *)
in
let v' = VD.cast t v in (* cast to the expected type (the abstract type might be something other than t since we don't change addresses upon casts!) *)
if M.tracing then M.tracel "cast" "Ptr-Deref: cast %a to %a = %a!" VD.pretty v d_type t VD.pretty v';
let v' = VD.eval_offset (Queries.to_value_domain_ask (Analyses.ask_of_ctx ctx)) (fun x -> get ~ctx st x (Some exp)) v' (convert_offset ~ctx st ofs) (Some exp) None t in (* handle offset *)
v'
in
AD.fold (fun a acc -> VD.join acc (lookup_with_offs a)) p (VD.bot ())
and evalbinop ~ctx (st: store) (op: binop) ~(e1:exp) ?(t1:typ option) ~(e2:exp) ?(t2:typ option) (t:typ): value =
evalbinop_mustbeequal ~ctx st op ~e1 ?t1 ~e2 ?t2 t
(** Evaluate BinOp using MustBeEqual query as fallback. *)
and evalbinop_mustbeequal ~ctx (st: store) (op: binop) ~(e1:exp) ?(t1:typ option) ~(e2:exp) ?(t2:typ option) (t:typ): value =
(* Evaluate structurally using base at first. *)
let a1 = eval_rv ~ctx st e1 in
let a2 = eval_rv ~ctx st e2 in
let t1 = Option.default_delayed (fun () -> Cilfacade.typeOf e1) t1 in
let t2 = Option.default_delayed (fun () -> Cilfacade.typeOf e2) t2 in
let r = evalbinop_base ~ctx op t1 a1 t2 a2 t in
if Cil.isIntegralType t then (
match r with
| Int i when ID.to_int i <> None -> r (* Avoid fallback, cannot become any more precise. *)
| _ ->
(* Fallback to MustBeEqual query, could get extra precision from exprelation/var_eq. *)
let must_be_equal () =
let r = Q.must_be_equal (Analyses.ask_of_ctx ctx) e1 e2 in
if M.tracing then M.tracel "query" "MustBeEqual (%a, %a) = %b" d_exp e1 d_exp e2 r;