Skip to content

Commit 48c25df

Browse files
author
Ronald Judin
committed
Added separate function for everything assignment-like, distinguished between regular and correct returns, added apron test
1 parent 9bf8a6c commit 48c25df

File tree

7 files changed

+114
-48
lines changed

7 files changed

+114
-48
lines changed

src/analyses/ocaml.ml

Lines changed: 40 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ module VarinfoSet = SetDomain.Make(CilType.Varinfo)
1313
(** "Fake" variable to handle returning from a function *)
1414
let return_varinfo = dummyFunDec.svar
1515
(** Flag for first function entered *)
16-
let first_function = true
16+
let first_function = (emptyFunction "@first").svar
17+
(** Flag for deregistering at return *)
18+
let to_deregister = (emptyFunction "@dereg").svar
1719

1820
module Spec : Analyses.MCPSpec =
1921
struct
@@ -106,22 +108,22 @@ struct
106108
| TNamed (info, attr) -> info.tname = "value"
107109
| _ -> false
108110

111+
let assignment (v:varinfo) (rval:exp) (state:D.t) (warning:string): D.t =
112+
(* If rval is a pointer, checks whether rval is accounted for, handles assignment to v accordingly *)
113+
if Cil.isPointerType (Cil.typeOf rval) || is_value_type (Cil.typeOf rval) then
114+
if exp_accounted_for state rval then
115+
if exp_registered state rval then D.add_a v (D.add_r v state)
116+
else D.add_a v (D.remove_r v state)
117+
else (M.info "%s" warning; D.remove_a v state)
118+
else D.add_a v (D.add_r v state)
119+
109120
(* transfer functions *)
110121

111122
(** Handles assignment of [rval] to [lval]. *)
112123
let assign ctx (lval:lval) (rval:exp) : D.t =
113124
let state = ctx.local in
114125
match lval with
115-
| Var v,_ ->
116-
(* If rval is a pointer, checks whether rval is accounted for, handles assignment to v accordingly *) (* state *)
117-
(* Emits an event for the variable v not being zero. *)
118-
119-
if Cil.isPointerType (Cil.typeOf rval) || is_value_type (Cil.typeOf rval) then
120-
if exp_accounted_for state rval then
121-
if exp_registered state rval then D.add_a v (D.add_r v state)
122-
else D.add_a v (D.remove_r v state)
123-
else (M.info "The above is being assigned"; D.remove_a v state)
124-
else D.add_a v (D.add_r v state)
126+
| Var v,_ -> assignment v rval state "The above is being assigned"
125127
| _ -> state
126128

127129
(** Handles conditional branching yielding truth value [tv]. *)
@@ -133,16 +135,14 @@ struct
133135
(** Handles going from start node of function [f] into the function body of [f].
134136
Meant to handle e.g. initializiation of local variables. *)
135137
let body ctx (f:fundec) : D.t =
136-
(* The (non-formals) locals are initially accounted for *)
137138
let state = ctx.local in
138-
(* It is assumed that the startstate's values are not nptrs. *)
139-
let state = List.fold_left (fun st v -> if first_function && is_value_type v.vtype then
140-
(ctx.emit (Events.SplitBranch (Cil.Lval (Cil.var v), true)); D.add_a v st)
141-
else D.add_a v (D.add_r v st))
142-
state f.sformals in
139+
(* It is assumed that the startstate's values are not nptrs. This avoids warnings from other analyses. *)
140+
if D.mem_r first_function state then
141+
List.iter (fun v -> (if is_value_type v.vtype then
142+
(ctx.emit (Events.SplitBranch (Cil.Lval (Cil.var v), true)))))
143+
f.sformals;
143144
(* TODO: Is there a way without a flag to only emit at the start? *)
144-
let first_function = false in
145-
state
145+
D.remove_r first_function state
146146

147147
(** Handles the [return] statement, i.e. "return exp" or "return", in function [f]. *)
148148
let return ctx (exp:exp option) (f:fundec) : D.t =
@@ -151,17 +151,11 @@ struct
151151
| Some e ->
152152
(* Checks that value returned is accounted for. *)
153153
(* Return_varinfo is used in place of a "real" variable. *)
154-
(* TODO: Consider how the return_varinfo needs to be tracked. *)
155-
let return_state =
156-
if Cil.isPointerType (Cil.typeOf e) || is_value_type (Cil.typeOf e) then
157-
if exp_accounted_for state e then
158-
if exp_registered state e then D.add_a return_varinfo (D.add_r return_varinfo state)
159-
else D.add_a return_varinfo (D.remove_r return_varinfo state)
160-
else (M.warn "Value returned might be garbage collected"; D.remove_a return_varinfo state)
161-
else D.add_a return_varinfo (D.add_r return_varinfo state)
162-
in
163-
(* Remove this function's formals and locals *)
164-
List.fold_left (fun st v -> D.remove_a v (D.remove_r v st)) return_state (f.sformals @ f.slocals)
154+
let return_state = assignment return_varinfo e state "The above is being returned" in
155+
(* Remove this function's formals and locals if correctly returned *)
156+
D.remove_r to_deregister (if D.mem_r to_deregister return_state then
157+
List.fold_left (fun st v -> D.remove_a v (D.remove_r v st)) return_state (f.sformals @ f.slocals)
158+
else return_state)
165159
| None -> state
166160

167161
(** For a function call "lval = f(args)" or "f(args)",
@@ -173,16 +167,13 @@ struct
173167
List.iter (fun e -> ignore (exp_accounted_for caller_state e)) args;
174168
(* Entering a function doesn't change the caller state *)
175169
let callee_state = List.fold_left2 (fun st v rval ->
176-
(* At the start, arguments are accounted for and not registered. *)
177-
if rval == MyCFG.unknown_exp then D.add_a v (D.remove_r v st) else
178-
(* Arguments of inner functions inherit the caller's state. *)
179-
if Cil.isPointerType (Cil.typeOf rval) || is_value_type (Cil.typeOf rval) then
180-
if exp_accounted_for st rval then
181-
if exp_registered st rval then D.add_a v (D.add_r v st)
182-
else D.add_a v (D.remove_r v st)
183-
else (M.info "The above is being assigned"; D.remove_a v st)
184-
else D.add_a v (D.add_r v st))
185-
caller_state f.sformals args in
170+
(* At the start, arguments are accounted for and not registered. The first_function flag is added.*)
171+
if rval == MyCFG.unknown_exp then
172+
if is_value_type v.vtype then D.add_r first_function (D.add_a v (D.remove_r v st))
173+
else D.add_a v (D.add_r v st)
174+
(* Arguments of inner functions inherit the caller's state. *)
175+
else assignment v rval st "Entering function with possibly deleted argument")
176+
caller_state f.sformals args in
186177
(* first component is state of caller, second component is state of callee *)
187178
[caller_state, callee_state]
188179

@@ -201,15 +192,15 @@ struct
201192
let caller_state = ctx.local in
202193
(* Records whether lval was accounted for. Registration for v must already be handled. *)
203194
(* TODO: What happens if a pointer to a value is returned? *)
204-
(* TODO: Rethink type checking with return_varinfo. *)
205195
match lval with (* The variable returned is played by return_varinfo *)
206196
| Some (Var v, _) ->
207197
let state =
208-
if Cil.isPointerType return_varinfo.vtype || is_value_type return_varinfo.vtype then
209-
if D.mem_a return_varinfo callee_local then
210-
if D.mem_r return_varinfo callee_local then D.add_a v (D.add_r v caller_state)
198+
(* TODO: It never warns about being combined. Is there a problem with the svar type? *)
199+
if Cil.isPointerType f.svar.vtype || is_value_type f.svar.vtype then
200+
if D.mem_a return_varinfo caller_state then
201+
if D.mem_r return_varinfo caller_state then D.add_a v (D.add_r v caller_state)
211202
else D.add_a v (D.remove_r v caller_state)
212-
else (M.warn "Returned value may be garbage-collected"; D.remove_a v caller_state)
203+
else (M.info "The above is being combined"; D.remove_a v caller_state)
213204
else D.add_a v (D.add_r v caller_state) in
214205
D.remove_a return_varinfo (D.remove_r return_varinfo state)
215206
| _ -> caller_state
@@ -219,7 +210,6 @@ struct
219210
For this analysis, source and sink functions will be considered _special_ and have to be treated here. *)
220211
let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t =
221212
let caller_state = ctx.local in
222-
(* TODO: Check if f is a sink / source and handle it appropriately *)
223213
(* To warn about a potential issue in the code, use M.warn. *)
224214
(* caller_state *)
225215
let desc = LibraryFunctions.find f in
@@ -238,6 +228,10 @@ struct
238228
| Some (Var v, _) -> D.add_a v (D.after_gc caller_state)
239229
| _ -> D.after_gc caller_state
240230
)
231+
(* TODO: Does not deregister, but queues it. This is unsound if deregistration is done before returning instead of at the same time. *)
232+
| OCamlReturn ->
233+
(* Marks a function as deregistering at return *)
234+
D.add_r to_deregister caller_state
241235
| _ -> caller_state
242236

243237
(* You may leave these alone *)

src/util/library/libraryDesc.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ type special =
5050
| OCamlAlloc of Cil.exp
5151
(* TODO: Rethink OCamlParam's placement. *)
5252
| OCamlParam of Cil.exp list
53+
| OCamlReturn
5354
| Calloc of { count: Cil.exp; size: Cil.exp; }
5455
| Realloc of { ptr: Cil.exp; size: Cil.exp; }
5556
| Free of Cil.exp

src/util/library/libraryFunctions.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,6 +1247,7 @@ let ocaml_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[
12471247
("caml_copy_double", special [drop "nptr" []] (OCamlAlloc (GoblintCil.integer 1)));
12481248
(* Example: ("malloc", special [__ "size" []] @@ fun size -> Malloc size); *)
12491249
("caml_alloc_small", special [__ "wosize" []; __ "tag" []] @@ fun wosize tag -> OCamlAlloc wosize);
1250+
("camlidl_apron_policy_ptr_c2ml", special [drop "nptr" []] (OCamlAlloc (GoblintCil.integer 1)));
12501251
("caml_alloc_2", special [__ "tag" []; __ "arg1" [r]; __ "arg2" [r]] @@ fun tag _arg1 _arg2 -> OCamlAlloc (GoblintCil.integer 2));
12511252
("caml_alloc_3", special [__ "tag" []; __ "arg1" [r]; __ "arg2" [r]; __ "arg3" [r]] @@ fun tag _arg1 _arg2 _arg3 -> OCamlAlloc (GoblintCil.integer 3));
12521253
("caml_alloc_4", special [__ "tag" []; __ "arg1" [r]; __ "arg2" [r]; __ "arg3" [r]; __ "arg4" [r]] @@ fun tag _arg1 _arg2 _arg3 _arg4 -> OCamlAlloc (GoblintCil.integer 4));
@@ -1257,6 +1258,7 @@ let ocaml_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[
12571258
("__goblint_caml_param3", special [__ "param1" []; __ "param2" []; __ "param3" []] @@ fun param1 param2 param3 -> OCamlParam [param1; param2; param3]);
12581259
("__goblint_caml_param4", special [__ "param1" []; __ "param2" []; __ "param3" []; __ "param4" []] @@ fun param1 param2 param3 param4 -> OCamlParam [param1; param2; param3; param4]);
12591260
("__goblint_caml_param5", special [__ "param1" []; __ "param2" []; __ "param3" []; __ "param4" []; __ "param5" []] @@ fun param1 param2 param3 param4 param5 -> OCamlParam [param1; param2; param3; param4; param5]);
1261+
("__goblint_caml_return", special [] @@ OCamlReturn);
12601262
]
12611263
[@@coverage off]
12621264

tests/regression/90-ocaml/05-lateparam.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// PARAM: --set "ana.activated[+]" ocaml --disable warn.imprecise --set "exp.extraspecials[+]" printInt
22

33
// Artificial test where the argument v is registered after GC could delete it.
4+
// TODO: Add late local as well.
45

56
#include <stdint.h>
67
#include <string.h>

tests/regression/90-ocaml/06-nested.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ CAMLprim value pringo_LXM_copy_1(value v)
2929
{
3030
value res = caml_alloc_small(Wsizeof(struct LXM_state), Abstract_tag);
3131
memcpy(LXM_val(res), LXM_val(v), sizeof(struct LXM_state)); // WARN
32-
return v;
32+
return v; // WARN
3333
}
3434

3535
CAMLprim value pringo_LXM_copy_2(value v)
@@ -41,6 +41,14 @@ CAMLprim value pringo_LXM_copy_2(value v)
4141
CAMLreturn(res);
4242
}
4343

44+
CAMLprim value pringo_LXM_copy_3(value v)
45+
{
46+
value res = caml_alloc_small(Wsizeof(struct LXM_state), Abstract_tag);
47+
value r = pringo_LXM_copy_1(v); // WARN
48+
memcpy(LXM_val(res), LXM_val(r), sizeof(struct LXM_state));
49+
CAMLreturn(res);
50+
}
51+
4452
CAMLprim value pringo_LXM_init_unboxed(uint64_t i1, uint64_t i2,
4553
uint64_t i3, uint64_t i4)
4654
{
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// PARAM: --set "ana.activated[+]" ocaml --disable warn.imprecise --set "exp.extraspecials[+]" printInt
2+
3+
// Buggy code from https://github.com/antoinemine/apron/pull/112 where v and v2 are initialised as 0.
4+
// TODO: It should warn when a value initialised as 0 is used without being registered after a garbage collection.
5+
6+
#include <stdint.h>
7+
#include <string.h>
8+
#include <caml/mlvalues.h>
9+
#include <caml/alloc.h>
10+
#include <caml/memory.h>
11+
#include "goblint_caml.h"
12+
13+
// In place of value, it was ap_policy_optr*.
14+
CAMLprim value camlidl_apron_policy_optr_c2ml(value p)
15+
{
16+
if (p==NULL){
17+
return Val_int(0);
18+
} else {
19+
value v,v2=0;
20+
Begin_roots1(v2);
21+
v2 = camlidl_apron_policy_ptr_c2ml(p);
22+
v = caml_alloc_small(1,0);
23+
Field(v,0) = v2;
24+
End_roots();
25+
return v;
26+
}
27+
}
28+
29+
CAMLprim value camlidl_apron_policy_optr_c2ml_correct(value p)
30+
{
31+
if (p==NULL){
32+
return Val_int(0);
33+
} else {
34+
value v,v2 = Val_unit;
35+
Begin_roots1(v2);
36+
v2 = camlidl_apron_policy_ptr_c2ml(p);
37+
v = caml_alloc_small(1,0);
38+
Field(v,0) = v2;
39+
End_roots();
40+
return v;
41+
}
42+
}
43+
44+
45+
// This function basically allocates memory with caml_alloc_custom and is simulated with OCamlParam in libraryfunctions.ml.
46+
/*CAMLprim value camlidl_apron_policy_ptr_c2ml(ap_policy_ptr* p)
47+
{
48+
value v;
49+
assert((*p)->pman!=NULL);
50+
v = caml_alloc_custom(&camlidl_apron_custom_policy_ptr, sizeof(ap_policy_ptr),
51+
0,1);
52+
*((ap_policy_ptr *) Data_custom_val(v)) = *p;
53+
return v;
54+
}*/

tests/regression/90-ocaml/goblint_caml.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,13 @@ struct LXM_state { uint64_t a; uint64_t x[2]; uint64_t s; };
3737
#define CAMLlocal5(x, y, z, w, v) value x = Val_unit; value y = Val_unit; value z = Val_unit; value w = Val_unit; value v = Val_unit; __goblint_caml_param5(&x, &y, &z, &w, &v)
3838

3939
#undef CAMLreturn
40-
#define CAMLreturn(x) return (x) // The real CAMLreturn needs some variable named caml__frame, which is not available in our redefinitions above.
40+
#define CAMLreturn(x) __goblint_caml_return(); return (x) // The real CAMLreturn needs some variable named caml__frame, which is not available in our redefinitions above.
41+
42+
// Marking roots is like registering and deregistering them.
43+
#undef Begin_roots1
44+
#undef End_roots
45+
#define Begin_roots1(x) __goblint_caml_param1(&x)
46+
#define End_roots() __goblint_caml_return()
4147

4248
// A reference to caml_gc_minor_words_unboxed can be found in _opam/lib/ocaml/ml/gc.ml.
4349
#define caml_gc_minor_words_unboxed() (0.0)

0 commit comments

Comments
 (0)