Skip to content

Commit 91ba355

Browse files
Merge pull request #1485 from reb-ddm/thesis-weakly-relational-pointer
C-2PO: Thesis About a Weakly-Relational Pointer Analysis
2 parents 66b5df8 + 078d7f5 commit 91ba355

39 files changed

+4507
-0
lines changed

src/analyses/c2poAnalysis.ml

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
(** C-2PO: A Weakly-Relational Pointer Analysis for C based on 2 Pointer Logic. The analysis can infer equalities and disequalities between terms which are built from pointer variables, with the addition of constants and dereferencing. ([c2po])*)
2+
3+
open Analyses
4+
open GoblintCil
5+
open C2poDomain
6+
open CongruenceClosure
7+
open Batteries
8+
open SingleThreadedLifter
9+
open DuplicateVars
10+
11+
module Spec =
12+
struct
13+
include DefaultSpec
14+
include Analyses.IdentitySpec
15+
module D = D
16+
module C = D
17+
18+
let name () = "c2po"
19+
let startcontext () = D.top ()
20+
21+
(** Find reachable variables in a function *)
22+
let reachable_from_args ctx args =
23+
let collect_reachable_from_exp acc e =
24+
let reachable_from_exp = ctx.ask (ReachableFrom e) in
25+
let reachable_from_exp = Queries.AD.to_var_may reachable_from_exp in
26+
reachable_from_exp @ acc
27+
in
28+
let res = List.fold collect_reachable_from_exp [] args in
29+
if M.tracing then M.tracel "c2po-reachable" "reachable vars: %s\n" (List.fold_left (fun s v -> s ^ v.vname ^"; ") "" res);
30+
res
31+
32+
(* Returns Some true if we know for sure that it is true,
33+
and Some false if we know for sure that it is false,
34+
and None if we don't know anyhing. *)
35+
let eval_guard ask cc e ik =
36+
let open Queries in
37+
let prop_list = T.prop_of_cil ask e true in
38+
match split prop_list with
39+
| [], [], [] ->
40+
ID.top()
41+
| x::xs, _, [] ->
42+
if fst (eq_query cc x) then
43+
ID.of_bool ik true
44+
else if neq_query cc x then
45+
ID.of_bool ik false
46+
else
47+
ID.top()
48+
| _, y::ys, [] ->
49+
if neq_query cc y then
50+
ID.of_bool ik true
51+
else if fst (eq_query cc y) then
52+
ID.of_bool ik false
53+
else
54+
ID.top()
55+
| _ ->
56+
ID.top() (*there should never be block disequalities here...*)
57+
58+
(** Convert a conjunction to an invariant.*)
59+
let conj_to_invariant ask conjs =
60+
let f a prop =
61+
try
62+
let exp = T.prop_to_cil prop in (* May raise UnsupportedCilExpression *)
63+
if M.tracing then M.trace "c2po-invariant" "Adding invariant: %a" d_exp exp;
64+
Invariant.(a && of_exp exp)
65+
with T.UnsupportedCilExpression _ ->
66+
a
67+
in
68+
List.fold f (Invariant.top()) conjs
69+
70+
let query ctx (type a) (q: a Queries.t): a Queries.result =
71+
let open Queries in
72+
match ctx.local with
73+
| `Bot -> Result.top q
74+
| `Lifted cc ->
75+
match q with
76+
| EvalInt e ->
77+
let ik = Cilfacade.get_ikind_exp e in
78+
eval_guard (ask_of_man ctx) cc e ik
79+
| Queries.Invariant context ->
80+
let scope = Node.find_fundec ctx.node in
81+
let t = D.remove_vars_not_in_scope scope cc in
82+
let conj = get_conjunction t in
83+
let ask = ask_of_man ctx in
84+
conj_to_invariant ask conj
85+
| _ ->
86+
Result.top q
87+
88+
(** Assign the right hand side rhs (that is already
89+
converted to a C-2PO term) to the term `lterm`. *)
90+
let assign_term cc ask lterm rhs lval_t =
91+
(* ignore assignments to values that are not 64 bits *)
92+
match T.get_element_size_in_bits lval_t, rhs with
93+
(* Indefinite assignment *)
94+
| lval_size, (None, _) ->
95+
D.remove_may_equal_terms ask lval_size lterm cc
96+
(* Definite assignment *)
97+
| lval_size, (Some rterm, Some roffset) ->
98+
let dummy_var = MayBeEqual.dummy_var lval_t in
99+
100+
if M.tracing then M.trace "c2po-assign" "assigning: var: %s; expr: %s + %s. \nTo_cil: lval: %a; expr: %a\n" (T.show lterm) (T.show rterm) (Z.to_string roffset) d_exp (T.to_cil lterm) d_exp (T.to_cil rterm);
101+
102+
let equal_dummy_rterm = [Equal (dummy_var, rterm, roffset)] in
103+
let equal_dummy_lterm = [Equal (lterm, dummy_var, Z.zero)] in
104+
105+
cc |>
106+
meet_conjs_opt equal_dummy_rterm |>
107+
D.remove_may_equal_terms ask lval_size lterm |>
108+
meet_conjs_opt equal_dummy_lterm |>
109+
D.remove_terms_containing_aux_variable
110+
111+
| _ -> (* this is impossible *)
112+
C2PODomain.top ()
113+
114+
(** Assign Cil Lval to a right hand side that is already converted to
115+
C-2PO terms.*)
116+
let assign_lval cc ask lval expr =
117+
let lval_t = typeOfLval lval in
118+
try
119+
let lterm = T.of_lval ask lval in
120+
assign_term cc ask lterm expr lval_t
121+
with T.UnsupportedCilExpression _ ->
122+
(* the assigned variables couldn't be parsed, so we don't know which addresses were written to.
123+
We have to forget all the information we had.
124+
This should almost never happen.
125+
Except if the left hand side is a complicated expression like myStruct.field1[i]->field2[z+k], and Goblint can't infer the offset.*)
126+
if M.tracing then M.trace "c2po-invalidate" "Invalidate lval: %a" d_lval lval;
127+
C2PODomain.top ()
128+
129+
let assign ctx lval expr =
130+
let ask = (ask_of_man ctx) in
131+
match ctx.local with
132+
| `Bot ->
133+
`Bot
134+
| `Lifted cc ->
135+
let cc = assign_lval cc ask lval (T.of_cil ask expr) in
136+
let cc = reset_normal_form cc in
137+
let res = `Lifted cc in
138+
if M.tracing then M.trace "c2po-assign" "assign: var: %a; expr: %a; result: %s.\n" d_lval lval d_plainexp expr (D.show res);
139+
res
140+
141+
let branch ctx e pos =
142+
let props = T.prop_of_cil (ask_of_man ctx) e pos in
143+
let valid_props = T.filter_valid_pointers props in
144+
let res =
145+
match ctx.local with
146+
| `Bot -> `Bot
147+
| `Lifted cc ->
148+
if List.is_empty valid_props then
149+
`Lifted cc
150+
else
151+
try
152+
let meet = meet_conjs_opt valid_props cc in
153+
let t = reset_normal_form meet in
154+
`Lifted t
155+
with Unsat ->
156+
`Bot
157+
in
158+
if M.tracing then M.trace "c2po" "branch:\n Actual equality: %a; pos: %b; valid_prop_list: %s; is_bot: %b\n" d_exp e pos (show_conj valid_props) (D.is_bot res);
159+
if D.is_bot res then raise Deadcode;
160+
res
161+
162+
let body ctx f =
163+
ctx.local
164+
165+
let assign_return ask d return_var expr =
166+
(* the return value is not stored on the heap, therefore we don't need to remove any terms *)
167+
match T.of_cil ask expr with
168+
| (Some term, Some offset) ->
169+
let ret_var_eq_term = [Equal (return_var, term, offset)] in
170+
let assign_by_meet = meet_conjs_opt ret_var_eq_term d in
171+
reset_normal_form assign_by_meet
172+
| _ -> d
173+
174+
let return ctx exp_opt f =
175+
let res =
176+
match exp_opt with
177+
| Some e ->
178+
begin match ctx.local with
179+
| `Bot ->
180+
`Bot
181+
| `Lifted d ->
182+
let return_var = MayBeEqual.return_var (typeOf e) in
183+
let d = assign_return (ask_of_man ctx) d return_var e in
184+
`Lifted d
185+
end
186+
| None -> ctx.local
187+
in
188+
if M.tracing then M.trace "c2po-function" "return: exp_opt: %a; state: %s; result: %s\n" d_exp (BatOption.default (MayBeEqual.dummy_lval_print (TVoid [])) exp_opt) (D.show ctx.local) (D.show res);
189+
res
190+
191+
(** var_opt is the variable we assign to. It has type lval. v=malloc.*)
192+
let special ctx lval_opt v exprs =
193+
let desc = LibraryFunctions.find v in
194+
let ask = ask_of_man ctx in
195+
match ctx.local with
196+
| `Bot -> `Bot
197+
| `Lifted cc ->
198+
let t =
199+
begin match lval_opt with
200+
| None ->
201+
cc
202+
| Some lval ->
203+
(* forget information about var,
204+
but ignore assignments to values that are not 64 bits *)
205+
try
206+
let size = T.get_element_size_in_bits (typeOfLval lval) in
207+
let lterm = T.of_lval ask lval in
208+
let cc = D.remove_may_equal_terms ask size lterm cc in
209+
begin match desc.special exprs with
210+
| Malloc _
211+
| Calloc _
212+
| Alloca _ ->
213+
add_block_diseqs cc lterm
214+
| _ -> cc
215+
end
216+
with T.UnsupportedCilExpression _ ->
217+
C2PODomain.top ()
218+
end
219+
in
220+
match desc.special exprs with
221+
| Assert { exp; refine; _ } ->
222+
if not refine then
223+
ctx.local
224+
else
225+
branch ctx exp true
226+
| _ ->
227+
`Lifted (reset_normal_form t)
228+
229+
(** First all local variables of the function are duplicated,
230+
then we remember the value of each local variable at the beginning of the function by using the analysis startState.
231+
This way we can infer the relations between the local variables of the caller and the pointers that were modified by the function. *)
232+
let enter ctx var_opt f args =
233+
(* add duplicated variables, and set them equal to the original variables *)
234+
match ctx.local with
235+
| `Bot -> [`Bot, `Bot]
236+
| `Lifted cc ->
237+
let ghost_equality v =
238+
Equal (T.term_of_varinfo (DuplicVar v), T.term_of_varinfo (NormalVar v), Z.zero)
239+
in
240+
let ghost_equalities_for_params = List.map ghost_equality f.sformals in
241+
let equalities_to_add = T.filter_valid_pointers ghost_equalities_for_params in
242+
let state_with_ghosts = meet_conjs_opt equalities_to_add cc in
243+
if M.tracing then begin
244+
let dummy_lval = Var (Var.dummy_varinfo (TVoid [])), NoOffset in
245+
let lval = BatOption.default dummy_lval var_opt in
246+
M.trace "c2po-function" "enter1: var_opt: %a; state: %s; state_with_ghosts: %s\n" d_lval lval (D.show ctx.local) (C2PODomain.show state_with_ghosts);
247+
end;
248+
(* remove callee vars that are not reachable and not global *)
249+
let reachable_variables =
250+
let reachable = f.sformals @ f.slocals @ reachable_from_args ctx args in
251+
Var.from_varinfo reachable f.sformals
252+
in
253+
let new_state = D.remove_terms_not_containing_variables reachable_variables state_with_ghosts in
254+
if M.tracing then M.trace "c2po-function" "enter2: result: %s\n" (C2PODomain.show new_state);
255+
let new_state = reset_normal_form new_state in
256+
[ctx.local, `Lifted new_state]
257+
258+
let remove_out_of_scope_vars cc f =
259+
let local_vars = f.sformals @ f.slocals in
260+
let duplicated_vars = f.sformals in
261+
let cc = D.remove_terms_containing_return_variable cc in
262+
D.remove_terms_containing_variables (Var.from_varinfo local_vars duplicated_vars) cc
263+
264+
let combine_env ctx lval_opt expr f args t_context_opt f_d (f_ask: Queries.ask) =
265+
match ctx.local with
266+
| `Bot -> `Bot
267+
| `Lifted cc ->
268+
let caller_ask = ask_of_man ctx in
269+
(* assign function parameters to duplicated values *)
270+
let arg_assigns = GobList.combine_short f.sformals args in
271+
let assign_term st (var, arg) =
272+
let ghost_var = T.term_of_varinfo (DuplicVar var) in
273+
let arg = T.of_cil f_ask arg in
274+
assign_term st caller_ask ghost_var arg var.vtype
275+
in
276+
let state_with_assignments = List.fold_left assign_term cc arg_assigns in
277+
278+
if M.tracing then M.trace "c2po-function" "combine_env0: state_with_assignments: %s\n" (C2PODomain.show state_with_assignments);
279+
280+
(*remove all variables that were tainted by the function*)
281+
let tainted = f_ask.f (MayBeTainted) in
282+
if M.tracing then M.trace "c2po-tainted" "combine_env1: %a\n" MayBeEqual.AD.pretty tainted;
283+
284+
let local = D.remove_tainted_terms caller_ask tainted state_with_assignments in
285+
match D.meet (`Lifted local) f_d with
286+
| `Bot -> `Bot
287+
| `Lifted cc ->
288+
let cc = reset_normal_form @@ remove_out_of_scope_vars cc f in
289+
if M.tracing then begin
290+
let dummy_lval = Var (Var.dummy_varinfo (TVoid[])), NoOffset in
291+
let lval = BatOption.default dummy_lval lval_opt in
292+
M.trace "c2po-function" "combine_env2: var_opt: %a; local_state: %s; f_state: %s; meeting everything: %s\n" d_lval lval (D.show ctx.local) (D.show f_d) (C2PODomain.show cc)
293+
end;
294+
`Lifted cc
295+
296+
let combine_assign ctx var_opt expr f args t_context_opt f_d (f_ask: Queries.ask) =
297+
match ctx.local with
298+
| `Bot -> `Bot
299+
| `Lifted cc ->
300+
let caller_ask = ask_of_man ctx in
301+
(* assign function parameters to duplicated values *)
302+
let arg_assigns = GobList.combine_short f.sformals args in
303+
let assign_term st (var, arg) =
304+
let ghost_var = T.term_of_varinfo (DuplicVar var) in
305+
let arg = T.of_cil f_ask arg in
306+
assign_term st caller_ask ghost_var arg var.vtype
307+
in
308+
let state_with_assignments = List.fold_left assign_term cc arg_assigns in
309+
match D.meet (`Lifted state_with_assignments) f_d with
310+
| `Bot -> `Bot
311+
| `Lifted cc ->
312+
let cc = match var_opt with
313+
| None ->
314+
cc
315+
| Some lval ->
316+
let return_type = typeOfLval lval in
317+
let return_var = MayBeEqual.return_var return_type in
318+
let return_var = (Some return_var, Some Z.zero) in
319+
assign_lval cc f_ask lval return_var
320+
in
321+
if M.tracing then M.trace "c2po-function" "combine_assign1: assigning return value: %s\n" (C2PODomain.show cc);
322+
let cc = remove_out_of_scope_vars cc f in
323+
let cc = reset_normal_form cc in
324+
if M.tracing then M.trace "c2po-function" "combine_assign2: result: %s\n" (C2PODomain.show cc);
325+
`Lifted cc
326+
327+
let startstate v =
328+
D.top ()
329+
let threadenter ctx ~multiple lval f args =
330+
[D.top ()]
331+
let threadspawn ctx ~multiple lval f args fctx =
332+
D.top()
333+
let exitstate v =
334+
D.top ()
335+
336+
end
337+
338+
let _ =
339+
MCP.register_analysis ~dep:["startState"; "taintPartialContexts"] (module SingleThreadedLifter(Spec) : MCPSpec)

0 commit comments

Comments
 (0)