Skip to content

Commit 0862bee

Browse files
authored
Merge pull request #1630 from goblint/apron-invariant-simplify
Simplify relational witness invariants
2 parents 1278886 + 29cc0c8 commit 0862bee

File tree

8 files changed

+167
-93
lines changed

8 files changed

+167
-93
lines changed

src/analyses/apron/relationAnalysis.apron.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ struct
619619
|> List.enum
620620
|> Enum.filter_map (fun (lincons1: Apron.Lincons1.t) ->
621621
(* filter one-vars and exact *)
622-
(* TODO: exact filtering doesn't really work with octagon because it returns two SUPEQ constraints instead *)
622+
(* RD.invariant simplifies two octagon SUPEQ constraints to one EQ, so exact works *)
623623
if (one_var || GobApron.Lincons1.num_vars lincons1 >= 2) && (exact || Apron.Lincons1.get_typ lincons1 <> EQ) then
624624
RD.cil_exp_of_lincons1 lincons1
625625
|> Option.map e_inv

src/analyses/apron/relationPriv.apron.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,7 @@ struct
730730
|> List.enum
731731
|> Enum.filter_map (fun (lincons1: Apron.Lincons1.t) ->
732732
(* filter one-vars and exact *)
733-
(* TODO: exact filtering doesn't really work with octagon because it returns two SUPEQ constraints instead *)
733+
(* RD.invariant simplifies two octagon SUPEQ constraints to one EQ, so exact works *)
734734
if (one_var || GobApron.Lincons1.num_vars lincons1 >= 2) && (exact || Apron.Lincons1.get_typ lincons1 <> EQ) then
735735
RD.cil_exp_of_lincons1 lincons1
736736
|> Option.filter (fun exp -> not (InvariantCil.exp_contains_tmp exp))

src/cdomains/apron/apronDomain.apron.ml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,9 @@ struct
552552
|> Enum.map (fun (lincons0: Lincons0.t) ->
553553
Lincons1.{lincons0; env = array_env}
554554
)
555-
|> List.of_enum
555+
|> Lincons1Set.of_enum
556+
|> (if Oct.manager_is_oct Man.mgr then Lincons1Set.simplify else Fun.id)
557+
|> Lincons1Set.elements
556558
end
557559

558560
(** With heterogeneous environments. *)
@@ -808,6 +810,7 @@ sig
808810

809811
module V: RV
810812
module Tracked: RelationDomain.Tracked
813+
module Man: Manager
811814

812815
val assert_inv : Queries.ask -> t -> exp -> bool -> bool Lazy.t -> t
813816
val eval_int : Queries.ask -> t -> exp -> bool Lazy.t -> Queries.ID.t
@@ -928,6 +931,7 @@ struct
928931
let lcb = D.to_lincons_array (D.of_lincons_array (BoxD.to_lincons_array b)) in (* convert through D to make lincons use the same format *)
929932
let lcd = D.to_lincons_array d in
930933
Lincons1Set.(diff (of_earray lcd) (of_earray lcb))
934+
|> (if Oct.manager_is_oct D.Man.mgr then Lincons1Set.simplify else Fun.id)
931935
|> Lincons1Set.elements
932936
end
933937

src/cdomains/apron/gobApron.apron.ml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,30 @@ struct
3838
let equal x y = Var.compare x y = 0
3939
end
4040

41+
module Linexpr0 =
42+
struct
43+
include Linexpr0
44+
45+
(** Negate linear expression. *)
46+
let neg (linexpr0: t): t =
47+
let r = copy linexpr0 in
48+
let n = Linexpr0.get_size r in
49+
for i = 0 to n - 1 do
50+
Linexpr0.set_coeff r i (Coeff.neg (Linexpr0.get_coeff r i))
51+
done;
52+
Linexpr0.set_cst r (Coeff.neg (Linexpr0.get_cst r));
53+
r
54+
end
55+
56+
module Linexpr1 =
57+
struct
58+
include Linexpr1
59+
60+
(** Negate linear expression. *)
61+
let neg (linexpr1: t): t =
62+
{linexpr0 = Linexpr0.neg linexpr1.linexpr0; env = linexpr1.env}
63+
end
64+
4165
module Lincons1 =
4266
struct
4367
include Lincons1
@@ -62,6 +86,12 @@ struct
6286
incr size
6387
) x;
6488
!size
89+
90+
(** Flip comparison operator in linear constraint, i.e., swap sides. *)
91+
let flip (lincons1: t): t =
92+
(* Apron constraints have rhs 0 and inequality only in one direction, so do the following: *)
93+
(* e >= 0 -> e <= 0 -> -e >= 0 *)
94+
make (Linexpr1.neg (get_linexpr1 lincons1)) (get_typ lincons1)
6595
end
6696

6797
module Lincons1Set =
@@ -74,6 +104,26 @@ struct
74104
Lincons1.{lincons0; env = array_env}
75105
)
76106
|> of_enum
107+
108+
(** Simplify (octagon) constraint set to replace two {!SUPEQ}-s with single {!EQ}. *)
109+
let simplify (lincons1s: t): t =
110+
fold (fun lincons1 acc ->
111+
match Lincons1.get_typ lincons1 with
112+
| SUPEQ ->
113+
let flipped = Lincons1.flip lincons1 in
114+
if mem flipped lincons1s then (
115+
if Lincons1.compare lincons1 flipped < 0 then (
116+
Lincons1.set_typ flipped EQ; (* reuse flipped copy for equality *)
117+
add flipped acc
118+
)
119+
else
120+
acc
121+
)
122+
else
123+
add lincons1 acc
124+
| _ ->
125+
add lincons1 acc
126+
) lincons1s empty
77127
end
78128

79129
module Texpr1 =

src/cdomains/apron/sharedFunctions.apron.ml

Lines changed: 66 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -268,44 +268,55 @@ module CilOfApron (V: SV) =
268268
struct
269269
exception Unsupported_Linexpr1
270270

271+
let longlong = TInt(ILongLong,[])
272+
273+
274+
(** Returned boolean indicates whether returned expression should be negated. *)
275+
let coeff_to_const ~scalewith (c:Coeff.union_5) =
276+
match c with
277+
| Scalar c ->
278+
(match int_of_scalar ?scalewith c with
279+
| Some i ->
280+
let ci,truncation = truncateCilint ILongLong i in
281+
if truncation = NoTruncation then
282+
if Z.compare i Z.zero >= 0 then
283+
false, Const (CInt(i,ILongLong,None))
284+
else
285+
(* attempt to negate if that does not cause an overflow *)
286+
let cneg, truncation = truncateCilint ILongLong (Z.neg i) in
287+
if truncation = NoTruncation then
288+
true, Const (CInt((Z.neg i),ILongLong,None))
289+
else
290+
false, Const (CInt(i,ILongLong,None))
291+
else
292+
(M.warn ~category:Analyzer "Invariant Apron: coefficient is not int: %a" Scalar.pretty c; raise Unsupported_Linexpr1)
293+
| None -> raise Unsupported_Linexpr1)
294+
| _ -> raise Unsupported_Linexpr1
295+
296+
(** Returned boolean indicates whether returned expression should be negated. *)
297+
let cil_exp_of_linexpr1_term ~scalewith (c: Coeff.t) v =
298+
match V.to_cil_varinfo v with
299+
| Some vinfo when IntDomain.Size.is_cast_injective ~from_type:vinfo.vtype ~to_type:(TInt(ILongLong,[])) ->
300+
let var = Cilfacade.mkCast ~e:(Lval(Var vinfo,NoOffset)) ~newt:longlong in
301+
let flip, coeff = coeff_to_const ~scalewith c in
302+
let prod = BinOp(Mult, coeff, var, longlong) in
303+
flip, prod
304+
| None ->
305+
M.warn ~category:Analyzer "Invariant Apron: cannot convert to cil var: %a" Var.pretty v;
306+
raise Unsupported_Linexpr1
307+
| _ ->
308+
M.warn ~category:Analyzer "Invariant Apron: cannot convert to cil var in overflow preserving manner: %a" Var.pretty v;
309+
raise Unsupported_Linexpr1
310+
311+
(** Returned booleans indicates whether returned expressions should be negated. *)
271312
let cil_exp_of_linexpr1 ?scalewith (linexpr1:Linexpr1.t) =
272-
let longlong = TInt(ILongLong,[]) in
273-
let coeff_to_const consider_flip (c:Coeff.union_5) = match c with
274-
| Scalar c ->
275-
(match int_of_scalar ?scalewith c with
276-
| Some i ->
277-
let ci,truncation = truncateCilint ILongLong i in
278-
if truncation = NoTruncation then
279-
if not consider_flip || Z.compare i Z.zero >= 0 then
280-
Const (CInt(i,ILongLong,None)), false
281-
else
282-
(* attempt to negate if that does not cause an overflow *)
283-
let cneg, truncation = truncateCilint ILongLong (Z.neg i) in
284-
if truncation = NoTruncation then
285-
Const (CInt((Z.neg i),ILongLong,None)), true
286-
else
287-
Const (CInt(i,ILongLong,None)), false
288-
else
289-
(M.warn ~category:Analyzer "Invariant Apron: coefficient is not int: %a" Scalar.pretty c; raise Unsupported_Linexpr1)
290-
| None -> raise Unsupported_Linexpr1)
291-
| _ -> raise Unsupported_Linexpr1
292-
in
293-
let expr = ref (fst @@ coeff_to_const false (Linexpr1.get_cst linexpr1)) in
313+
let terms = ref [coeff_to_const ~scalewith (Linexpr1.get_cst linexpr1)] in
294314
let append_summand (c:Coeff.union_5) v =
295-
match V.to_cil_varinfo v with
296-
| Some vinfo when IntDomain.Size.is_cast_injective ~from_type:vinfo.vtype ~to_type:(TInt(ILongLong,[])) ->
297-
let var = Cilfacade.mkCast ~e:(Lval(Var vinfo,NoOffset)) ~newt:longlong in
298-
let coeff, flip = coeff_to_const true c in
299-
let prod = BinOp(Mult, coeff, var, longlong) in
300-
if flip then
301-
expr := BinOp(MinusA,!expr,prod,longlong)
302-
else
303-
expr := BinOp(PlusA,!expr,prod,longlong)
304-
| None -> M.warn ~category:Analyzer "Invariant Apron: cannot convert to cil var: %a" Var.pretty v; raise Unsupported_Linexpr1
305-
| _ -> M.warn ~category:Analyzer "Invariant Apron: cannot convert to cil var in overflow preserving manner: %a" Var.pretty v; raise Unsupported_Linexpr1
315+
if not (Coeff.is_zero c) then
316+
terms := cil_exp_of_linexpr1_term ~scalewith c v :: !terms
306317
in
307318
Linexpr1.iter append_summand linexpr1;
308-
!expr
319+
!terms
309320

310321

311322
let lcm_den linexpr1 =
@@ -339,13 +350,27 @@ struct
339350
try
340351
let linexpr1 = Lincons1.get_linexpr1 lincons1 in
341352
let common_denominator = lcm_den linexpr1 in
342-
let cilexp = cil_exp_of_linexpr1 ~scalewith:common_denominator linexpr1 in
343-
match Lincons1.get_typ lincons1 with
344-
| EQ -> Some (Cil.constFold false @@ BinOp(Eq, cilexp, zero, TInt(IInt,[])))
345-
| SUPEQ -> Some (Cil.constFold false @@ BinOp(Ge, cilexp, zero, TInt(IInt,[])))
346-
| SUP -> Some (Cil.constFold false @@ BinOp(Gt, cilexp, zero, TInt(IInt,[])))
347-
| DISEQ -> Some (Cil.constFold false @@ BinOp(Ne, cilexp, zero, TInt(IInt,[])))
348-
| EQMOD _ -> None
353+
let terms = cil_exp_of_linexpr1 ~scalewith:common_denominator linexpr1 in
354+
let (nterms, pterms) = Tuple2.mapn (List.map snd) (List.partition fst terms) in (* partition terms into negative (nterms) and positive (pterms) *)
355+
let fold_terms terms =
356+
List.fold_left (fun acc term ->
357+
match acc with
358+
| None -> Some term
359+
| Some exp -> Some (BinOp (PlusA, exp, term, longlong))
360+
) None terms
361+
|> Option.default zero
362+
in
363+
let lhs = fold_terms pterms in
364+
let rhs = fold_terms nterms in (* negative terms are moved from Apron's lhs to our rhs, so they all become positive there *)
365+
let binop =
366+
match Lincons1.get_typ lincons1 with
367+
| EQ -> Eq
368+
| SUPEQ -> Ge
369+
| SUP -> Gt
370+
| DISEQ -> Ne
371+
| EQMOD _ -> raise Unsupported_Linexpr1
372+
in
373+
Some (Cil.constFold false @@ BinOp(binop, lhs, rhs, TInt(IInt,[]))) (* constFold removes multiplication by factor 1 *)
349374
with
350375
Unsupported_Linexpr1 -> None
351376
end

tests/regression/36-apron/12-traces-min-rpb1.t

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
column: 3
3232
function: main
3333
location_invariant:
34-
string: (0LL - (long long )g) + (long long )h == 0LL
34+
string: (long long )h == (long long )g
3535
type: assertion
3636
format: C
3737
- entry_type: location_invariant
@@ -41,7 +41,7 @@
4141
column: 3
4242
function: t_fun
4343
location_invariant:
44-
string: (0LL - (long long )g) + (long long )h == 0LL
44+
string: (long long )h == (long long )g
4545
type: assertion
4646
format: C
4747
- entry_type: location_invariant
@@ -51,7 +51,7 @@
5151
column: 3
5252
function: t_fun
5353
location_invariant:
54-
string: (0LL - (long long )g) + (long long )h == 0LL
54+
string: (long long )h == (long long )g
5555
type: assertion
5656
format: C
5757

@@ -163,7 +163,6 @@
163163
format: c_expression
164164
- entry_type: flow_insensitive_invariant
165165
flow_insensitive_invariant:
166-
string: '! multithreaded || (A_locked || ((0LL - (long long )g) + (long long )h
167-
>= 0LL && (long long )g - (long long )h >= 0LL))'
166+
string: '! multithreaded || (A_locked || (long long )g == (long long )h)'
168167
type: assertion
169168
format: C

0 commit comments

Comments
 (0)