Skip to content

Commit cb5ab2e

Browse files
Merge branch 'master' into apron_track_address
2 parents 2c69f46 + 1a8cb7d commit cb5ab2e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+1104
-540
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Extending library
2+
3+
Analyzed programs often call functions for which the implementation is not available to Goblint.
4+
These can belong to the standard libraries of C, POSIX, Linux kernel, etc or to project dependencies whose implementation is not available.
5+
6+
Goblint outputs "Function definition missing" errors in such cases and by default assumes the worst about what the function does (invalidating everything).
7+
Such invalidation is a major source of imprecision and should be avoided at all cost (while still being sound).
8+
The semantics of unknown functions are controlled by options under `sem.unknown_function`, which can be used to avoid the invalidation, but disabling them if any unknown function does such invalidation will lead to unsoundness.
9+
10+
To strike a better balance between soundness and precision, more fine-grained specifications can be provided in the `LibraryFunctions` module.
11+
12+
13+
## Library function specifications
14+
15+
In the `LibraryFunctions` module (implementation), specifications of library functions are organized into a number of association lists.
16+
Functions are grouped based on their origin and context.
17+
This (in the future[^spec-list-selection]) allows groups of library function specifications to be toggled based on the program under analysis.
18+
19+
There are also older (less granular) specifications implemented using `classify` and `invalidate_actions`.
20+
No new specifications should be added there and all the existing ones should be migrated to the new DSL-based format.
21+
22+
[^spec-list-selection]: Specification list toggling will only be possible when all the old specifications are migrated.
23+
24+
25+
## Library function descriptor DSL
26+
27+
A library function's specification is a pair of its name and descriptor (of type `LibraryDesc.t`).
28+
The descriptors are easiest written using the OCaml DSL from `LibraryDsl`.
29+
30+
31+
### Function descriptor
32+
33+
A function descriptor can be one of the following:
34+
35+
1. `special` consists of an arguments descriptor and a continuation function for converting the captured arguments to a `LibraryDesc.special` variant.
36+
This is used when the library function should (or at least could) be specially handled by analyses.
37+
38+
2. `unknown` consists of just an arguments descriptor (which must `drop` all arguments) and defaults to `Unknown` variant.
39+
This is used when the library function isn't specially handled by analyses.
40+
41+
42+
### Arguments descriptor
43+
44+
An arguments descriptor is a list (with standard OCaml list syntax) of individual argument descriptors.
45+
Additionally for library functions with variadic arguments, the list may be terminated with `VarArg` constructor instead of `[]`.
46+
47+
48+
### Argument descriptor
49+
50+
An argument descriptor can be one of the following:
51+
52+
1. `__` captures the argument expression for use in `special`'s continuation function.
53+
2. `drop` ignores (drops) the argument expression to not be used by the continuation.
54+
55+
Both functions consist of an argument name string (for readability purposes) and a list of accesses the function does to the _pointer_ argument.
56+
57+
Unnamed variants of the functions `__'` and `drop'` also exist, but should be avoided.
58+
59+
60+
### Access
61+
62+
An access consists of an access kind and depth.
63+
Access kinds (`AccessKind.t`) are: read, write, free and spawn.
64+
65+
Accesses specify what the library function may do with the corresponding _pointer_ argument.
66+
Since function calls unconditionally read the immediate expressions given as arguments, there would be no point specifying that all the arguments (including of integer) type are read.
67+
Also, non-pointer arguments are copied (passed-by-value) to the function, so write accesses would make no sense.
68+
Therefore only pointer arguments should have accesses specified.
69+
70+
Two depths of accessing are distinguished:
71+
72+
1. Shallow (non-transitive, may-point-to) access means the function dereferences the pointer argument, but doesn't dereference any pointers within (e.g. if a pointer to struct is given as argument, but the struct contains further pointers).
73+
This is is almost always the case for standard library functions, since they take `void*` or `char*` as generic memory arguments and don't know about any pointers within the pointed block of memory.
74+
75+
2. Deep (transitive, reachable) access means the function follows (dereferences) pointers within.
76+
77+
In the DSL, shallow accesses can be specified by `r`, `w`, `f` or `s`, respectively. Deep accesses can be specified using the previous values with an additional `_deep` suffix.
78+
79+

goblint.opam

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ dev-repo: "git+https://github.com/goblint/analyzer.git"
6868
# on `dune build` goblint.opam will be generated from goblint.opam.template and dune-project
6969
# also remember to generate/adjust goblint.opam.locked!
7070
pin-depends: [
71-
[ "goblint-cil.1.8.2" "git+https://github.com/goblint/cil.git#0a37478e08b9ae10587b0355ad2805dfeca1cca9" ]
71+
[ "goblint-cil.1.8.2" "git+https://github.com/goblint/cil.git#9118601602d34edf53f73b35996abf2296c2d900" ]
7272
# TODO: add back after release, only pinned for optimization (https://github.com/ocaml-ppx/ppx_deriving/pull/252)
7373
[ "ppx_deriving.5.2.1" "git+https://github.com/ocaml-ppx/ppx_deriving.git#0a89b619f94cbbfc3b0fb3255ab4fe5bc77d32d6" ]
7474
# quoter workaround reverted for release, so no pin needed

goblint.opam.locked

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ version: "dev"
124124
pin-depends: [
125125
[
126126
"goblint-cil.1.8.2"
127-
"git+https://github.com/goblint/cil.git#0a37478e08b9ae10587b0355ad2805dfeca1cca9"
127+
"git+https://github.com/goblint/cil.git#9118601602d34edf53f73b35996abf2296c2d900"
128128
]
129129
[
130130
"apron.v0.9.13"

goblint.opam.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# on `dune build` goblint.opam will be generated from goblint.opam.template and dune-project
22
# also remember to generate/adjust goblint.opam.locked!
33
pin-depends: [
4-
[ "goblint-cil.1.8.2" "git+https://github.com/goblint/cil.git#0a37478e08b9ae10587b0355ad2805dfeca1cca9" ]
4+
[ "goblint-cil.1.8.2" "git+https://github.com/goblint/cil.git#9118601602d34edf53f73b35996abf2296c2d900" ]
55
# TODO: add back after release, only pinned for optimization (https://github.com/ocaml-ppx/ppx_deriving/pull/252)
66
[ "ppx_deriving.5.2.1" "git+https://github.com/ocaml-ppx/ppx_deriving.git#0a89b619f94cbbfc3b0fb3255ab4fe5bc77d32d6" ]
77
# quoter workaround reverted for release, so no pin needed

gobview

mkdocs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ theme:
1111
markdown_extensions:
1212
- toc:
1313
permalink: True
14+
- footnotes
1415

1516
dev_addr: '127.0.0.1:8010' # different port from default python http.server for g2html
1617

@@ -24,6 +25,7 @@ nav:
2425
- 'Developer guide':
2526
- developer-guide/developing.md
2627
- developer-guide/firstanalysis.md
28+
- developer-guide/extending-library.md
2729
- developer-guide/messaging.md
2830
- developer-guide/testing.md
2931
- developer-guide/debugging.md

src/analyses/accessAnalysis.ml

Lines changed: 31 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ struct
5050
ctx.sideg (lv_opt, ty) d
5151

5252
let do_access (ctx: (D.t, G.t, C.t, V.t) ctx) (kind:AccessKind.t) (reach:bool) (conf:int) (e:exp) =
53+
if M.tracing then M.trace "access" "do_access %a %a %B\n" d_exp e AccessKind.pretty kind reach;
5354
let open Queries in
5455
let part_access ctx (e:exp) (vo:varinfo option) (kind: AccessKind.t): MCPAccess.A.t =
5556
ctx.emit (Access {var_opt=vo; kind});
@@ -106,17 +107,18 @@ struct
106107
| _ ->
107108
add_access (conf - 60) None None
108109

109-
let access_one_top ?(force=false) ctx kind reach exp =
110-
(* ignore (Pretty.printf "access_one_top %b %b %a:\n" write reach d_exp exp); *)
110+
(** Three access levels:
111+
+ [deref=false], [reach=false] - Access [exp] without dereferencing, used for all normal reads and all function call arguments.
112+
+ [deref=true], [reach=false] - Access [exp] by dereferencing once (may-point-to), used for lval writes and shallow special accesses.
113+
+ [deref=true], [reach=true] - Access [exp] by dereferencing transitively (reachable), used for deep special accesses. *)
114+
let access_one_top ?(force=false) ?(deref=false) ctx (kind: AccessKind.t) reach exp =
115+
if M.tracing then M.traceli "access" "access_one_top %a %b %a:\n" AccessKind.pretty kind reach d_exp exp;
111116
if force || ThreadFlag.is_multi (Analyses.ask_of_ctx ctx) then (
112117
let conf = 110 in
113-
let write = match kind with
114-
| `Write | `Free -> true
115-
| `Read -> false
116-
in
117-
if reach || write then do_access ctx kind reach conf exp;
118-
Access.distribute_access_exp (do_access ctx) `Read false conf exp;
119-
)
118+
if deref then do_access ctx kind reach conf exp;
119+
Access.distribute_access_exp (do_access ctx Read false) conf exp;
120+
);
121+
if M.tracing then M.traceu "access" "access_one_top %a %b %a\n" AccessKind.pretty kind reach d_exp exp
120122

121123
(** We just lift start state, global and dependency functions: *)
122124
let startstate v = ()
@@ -129,18 +131,18 @@ struct
129131
let assign ctx lval rval : D.t =
130132
(* ignore global inits *)
131133
if !GU.global_initialization then ctx.local else begin
132-
access_one_top ctx `Write false (AddrOf lval);
133-
access_one_top ctx `Read false rval;
134+
access_one_top ~deref:true ctx Write false (AddrOf lval);
135+
access_one_top ctx Read false rval;
134136
ctx.local
135137
end
136138

137139
let branch ctx exp tv : D.t =
138-
access_one_top ctx `Read false exp;
140+
access_one_top ctx Read false exp;
139141
ctx.local
140142

141143
let return ctx exp fundec : D.t =
142144
begin match exp with
143-
| Some exp -> access_one_top ctx `Read false exp
145+
| Some exp -> access_one_top ctx Read false exp
144146
| None -> ()
145147
end;
146148
ctx.local
@@ -149,18 +151,19 @@ struct
149151
ctx.local
150152

151153
let special ctx lv f arglist : D.t =
152-
match (LF.classify f.vname arglist, f.vname) with
154+
let desc = LF.find f in
155+
match desc.special arglist, f.vname with
153156
(* TODO: remove cases *)
154157
| _, "_lock_kernel" ->
155158
ctx.local
156159
| _, "_unlock_kernel" ->
157160
ctx.local
158-
| `Lock (failing, rw, nonzero_return_when_aquired), _
161+
| Lock { try_ = failing; write = rw; return_on_success = nonzero_return_when_aquired; _ }, _
159162
-> ctx.local
160-
| `Unlock, "__raw_read_unlock"
161-
| `Unlock, "__raw_write_unlock" ->
163+
| Unlock _, "__raw_read_unlock"
164+
| Unlock _, "__raw_write_unlock" ->
162165
ctx.local
163-
| `Unlock, _ ->
166+
| Unlock _, _ ->
164167
ctx.local
165168
| _, "spinlock_check" -> ctx.local
166169
| _, "acquire_console_sem" when get_bool "kernel" ->
@@ -176,51 +179,34 @@ struct
176179
| _, "pthread_cond_wait"
177180
| _, "pthread_cond_timedwait" ->
178181
ctx.local
179-
| _, x ->
180-
let arg_acc act =
181-
match act, LF.get_threadsafe_inv_ac x with
182-
| _, Some fnc -> (fnc act arglist)
183-
| `Read, None -> arglist
184-
| (`Write | `Free), None ->
185-
if get_bool "sem.unknown_function.invalidate.args" then
186-
arglist
187-
else
188-
[]
189-
in
190-
(* TODO: per-argument reach *)
191-
let reach =
192-
match f.vname with
193-
| "memset" | "__builtin_memset" | "__builtin___memset_chk" -> false
194-
| "bzero" | "__builtin_bzero" | "explicit_bzero" | "__explicit_bzero_chk" -> false
195-
| "__builtin_object_size" -> false
196-
| _ -> true
197-
in
198-
List.iter (access_one_top ctx `Read reach) (arg_acc `Read);
199-
List.iter (access_one_top ctx `Write reach) (arg_acc `Write);
200-
List.iter (access_one_top ctx `Free reach) (arg_acc `Free);
182+
| _, _ ->
183+
LibraryDesc.Accesses.iter desc.accs (fun {kind; deep = reach} exp ->
184+
access_one_top ~deref:true ctx kind reach exp (* access dereferenced using special accesses *)
185+
) arglist;
201186
(match lv with
202-
| Some x -> access_one_top ctx `Write false (AddrOf x)
187+
| Some x -> access_one_top ~deref:true ctx Write false (AddrOf x)
203188
| None -> ());
189+
List.iter (access_one_top ctx Read false) arglist; (* always read all argument expressions without dereferencing *)
204190
ctx.local
205191

206192
let enter ctx lv f args : (D.t * D.t) list =
207193
[(ctx.local,ctx.local)]
208194

209195
let combine ctx lv fexp f args fc al =
210-
access_one_top ctx `Read false fexp;
196+
access_one_top ctx Read false fexp;
211197
begin match lv with
212198
| None -> ()
213-
| Some lval -> access_one_top ctx `Write false (AddrOf lval)
199+
| Some lval -> access_one_top ~deref:true ctx Write false (AddrOf lval)
214200
end;
215-
List.iter (access_one_top ctx `Read false) args;
201+
List.iter (access_one_top ctx Read false) args;
216202
al
217203

218204

219205
let threadspawn ctx lval f args fctx =
220206
(* must explicitly access thread ID lval because special to pthread_create doesn't if singlethreaded before *)
221207
begin match lval with
222208
| None -> ()
223-
| Some lval -> access_one_top ~force:true ctx `Write false (AddrOf lval) (* must force because otherwise doesn't if singlethreaded before *)
209+
| Some lval -> access_one_top ~force:true ~deref:true ctx Write false (AddrOf lval) (* must force because otherwise doesn't if singlethreaded before *)
224210
end;
225211
ctx.local
226212

src/analyses/apron/apronAnalysis.apron.ml

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -373,17 +373,18 @@ struct
373373
invalidate_one ask ctx st (Lval.CilLval.to_lval lval)
374374
) rs st
375375

376-
377376
let special ctx r f args =
378377
let ask = Analyses.ask_of_ctx ctx in
379378
let st = ctx.local in
380-
match LibraryFunctions.classify f.vname args with
379+
let desc = LibraryFunctions.find f in
380+
match desc.special args, f.vname with
381381
(* TODO: assert handling from https://github.com/goblint/analyzer/pull/278 *)
382-
| `Assert expression -> st
383-
| `Unknown "__goblint_check" -> st
384-
| `Unknown "__goblint_commit" -> st
385-
| `Unknown "__goblint_assert" -> st
386-
| `ThreadJoin (id,retvar) ->
382+
| Assert expression, _ -> st
383+
| Unknown, "__goblint_check" -> st
384+
| Unknown, "__goblint_commit" -> st
385+
| Unknown, "__goblint_assert" -> st
386+
| ThreadJoin { thread = id; ret_var = retvar }, _ ->
387+
(* nothing to invalidate as only arguments that have their AddrOf taken may be invalidated *)
387388
(
388389
(* Forget value that thread return is assigned to *)
389390
let st' = forget_reachable ctx st [retvar] in
@@ -392,29 +393,33 @@ struct
392393
| Some lv -> invalidate_one ask ctx st' lv
393394
| None -> st'
394395
)
395-
| _ ->
396-
let ask = Analyses.ask_of_ctx ctx in
397-
let st' = match LibraryFunctions.get_invalidate_action f.vname with
398-
| Some fnc -> forget_reachable ctx st (fnc `Write args)
399-
| None ->
400-
let st' = if GobConfig.get_bool "sem.unknown_function.invalidate.globals" then (
401-
let globals = foldGlobals !Cilfacade.current_file (fun acc global ->
402-
match global with
403-
| GVar (vi, _, _) when not (BaseUtil.is_static vi) ->
404-
(Var vi, NoOffset) :: acc
405-
(* TODO: what about GVarDecl? *)
406-
| _ -> acc
407-
) []
408-
in
409-
List.fold_left (invalidate_one ask ctx) st globals)
410-
else
411-
st
396+
| _, _ ->
397+
let lvallist e =
398+
let s = ask.f (Queries.MayPointTo e) in
399+
match s with
400+
| `Top -> []
401+
| `Lifted _ -> List.map (Lval.CilLval.to_lval) (Queries.LS.elements s)
402+
in
403+
let shallow_addrs = LibraryDesc.Accesses.find desc.accs { kind = Write; deep = false } args in
404+
let deep_addrs = LibraryDesc.Accesses.find desc.accs { kind = Write; deep = true } args in
405+
let st' =
406+
if List.mem LibraryDesc.InvalidateGlobals desc.attrs then (
407+
let globals = foldGlobals !Cilfacade.current_file (fun acc global ->
408+
match global with
409+
| GVar (vi, _, _) when not (BaseUtil.is_static vi) ->
410+
(Var vi, NoOffset) :: acc
411+
(* TODO: what about GVarDecl? *)
412+
| _ -> acc
413+
) []
412414
in
413-
if GobConfig.get_bool "sem.unknown_function.invalidate.args" then
414-
forget_reachable ctx st' args
415-
else
416-
st'
415+
List.fold_left (invalidate_one ask ctx) st globals
416+
)
417+
else
418+
st
417419
in
420+
let st' = forget_reachable ctx st' deep_addrs in
421+
let shallow_lvals = List.concat_map lvallist shallow_addrs in
422+
let st' = List.fold_left (invalidate_one ask ctx) st' shallow_lvals in
418423
(* invalidate lval if present *)
419424
match r with
420425
| Some lv -> invalidate_one ask ctx st' lv
@@ -433,7 +438,8 @@ struct
433438
in
434439
match q with
435440
| EvalInt e ->
436-
if M.tracing then M.traceli "evalint" "apron query %a\n" d_exp e;
441+
if M.tracing then M.traceli "evalint" "apron query %a (%a)\n" d_exp e d_plainexp e;
442+
if M.tracing then M.trace "evalint" "apron st: %a\n" D.pretty ctx.local;
437443
let r = eval_int e in
438444
if M.tracing then M.traceu "evalint" "apron query %a -> %a\n" d_exp e ID.pretty r;
439445
r

0 commit comments

Comments
 (0)