Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interactive: improvements for chrony story #724

Merged
merged 33 commits into from
May 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
0709d82
Add symb_locks test with irrelevant index access
sim642 May 5, 2022
30c1d7a
Add var_eq EqualSet tracing
sim642 May 5, 2022
11cc869
Quick fix var_eq eq_set_clos for IndexPI
sim642 May 5, 2022
dd43200
Add chrony Name2IPAddress extracted test for symb_locks
sim642 May 5, 2022
1ba9590
Add symb_locks tracing
sim642 May 5, 2022
f5c4e89
Quick fix symb_locks toEl for IndexPI
sim642 May 5, 2022
752d16c
Fix symb_locks toEl for StartOf
sim642 May 5, 2022
180ed23
Fix VarEq.may_change with constant
sim642 May 5, 2022
653d0bb
Enable all code paths in chrony-name2ipaddress
sim642 May 5, 2022
a4be262
Add var_eq add_eq tracing
sim642 May 5, 2022
f753df5
Quick fix var_eq interesting for IndexPI
sim642 May 5, 2022
e10102e
Add __goblint_assume_join test
sim642 May 5, 2022
4a8aa85
Implement __goblint_assume_join in threadJoins analysis
sim642 May 5, 2022
ed6b2e4
Fix MHP.must_be_joined for top joined set
sim642 May 5, 2022
c5cf526
Quick fix realloc read accesses to be shallow
sim642 May 5, 2022
2b60af7
Add option sem.unknown_function.read.args
sim642 May 5, 2022
ea05ea8
Fix 02-base/78-realloc-free write-free race
sim642 May 5, 2022
59f7343
Merge branch 'interactive' into chrony
sim642 May 5, 2022
577f6f3
Add test that's unsound because of __goblint_assume_join(...)
michael-schwarz May 11, 2022
76f9b29
Add threadJoins test where recreated threads should be removed from m…
sim642 May 12, 2022
84a98bf
Make threadspawn remove must-joined threads
sim642 May 12, 2022
b09b1fd
Add SKIP to 36-apron2/03-other-assume like other Apron tests
sim642 May 12, 2022
f2b8673
Fix __goblint_assume_join with Apron mutex-meet-tid privatization
sim642 May 12, 2022
d5a0c27
Add __goblint_assume_join to annotating docs
sim642 May 12, 2022
a4db1a0
Add assume join test with unknown thread ID
sim642 May 13, 2022
e6e33b2
Change assume join behavior with unknown thread ID
sim642 May 13, 2022
6016835
Add example where `__goblint_assume_join` causes unneccessary precisi…
michael-schwarz May 13, 2022
22d6da8
Add problematic index access
michael-schwarz May 13, 2022
a482bd1
Fix Apron mutex-meet-tid imprecision by force joining must-joined thr…
sim642 May 16, 2022
67211c2
Fix annotations in 06-symbeq/39-funloop_index_bad
sim642 May 16, 2022
92c7ea4
Replace symb_locks and var_eq IndexPI handling with very ad-hoc one
sim642 May 16, 2022
94fd11f
Handle IndexPI in Exp.interesting to fix string_fortified.h race in 0…
sim642 May 16, 2022
c491dbb
Re-allow All threads must joined for chrony story, but warn about it
sim642 May 19, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/user-guide/annotating.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,11 @@ The following string arguments are supported:
3. `base.non-ptr`/`base.no-non-ptr` to override the `ana.base.context.non-ptr` option.
4. `apron.context`/`apron.no-context` to override the `ana.apron.context` option.
5. `widen`/`no-widen` to override the `ana.context.widen` option.


## Functions
Goblint-specific functions can be called in the code, where they assist the analyzer but have no runtime effect.

* `__goblint_assume_join(id)` is like `pthread_join(id)`, but considers the given thread IDs must-joined even if Goblint cannot, e.g. due to non-uniqueness.
Notably, this annotation can be used after a threads joining loop to make the assumption that the loop correctly joined all those threads.
_Misuse of this annotation can cause unsoundness._
1 change: 1 addition & 0 deletions scripts/update_suite.rb
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ def collect_warnings
when /Assertion .* is unknown/ then "unknown"
when /^\[Warning\]/ then "warn"
when /^\[Error\]/ then "warn"
when /^\[Info\]/ then "warn"
when /\[Debug\]/ then next # debug "warnings" shouldn't count as other warnings (against NOWARN)
when /^ on line \d+ $/ then next # dead line warnings shouldn't count (used for unreachability with NOWARN)
when /^ on lines \d+..\d+ $/ then next # dead line warnings shouldn't count (used for unreachability with NOWARN)
Expand Down
7 changes: 6 additions & 1 deletion src/analyses/accessAnalysis.ml
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,11 @@ struct
let arg_acc act =
match act, LF.get_threadsafe_inv_ac x with
| _, Some fnc -> (fnc act arglist)
| `Read, None -> arglist
| `Read, None ->
if get_bool "sem.unknown_function.read.args" then
arglist
else
[]
| (`Write | `Free), None ->
if get_bool "sem.unknown_function.invalidate.args" then
arglist
Expand All @@ -220,6 +224,7 @@ struct
| "memset" | "__builtin_memset" | "__builtin___memset_chk" -> false
| "bzero" | "__builtin_bzero" | "explicit_bzero" | "__explicit_bzero_chk" -> false
| "__builtin_object_size" -> false
| "realloc" -> false
| _ -> true
in
List.iter (access_one_top ctx `Read reach) (arg_acc `Read);
Expand Down
3 changes: 3 additions & 0 deletions src/analyses/apron/apronAnalysis.apron.ml
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,9 @@ struct
| Some lv -> invalidate_one st' lv
| None -> st'
)
| `Unknown "__goblint_assume_join" ->
let id = List.hd args in
Priv.thread_join ~force:true ask ctx.global id st
| _ ->
let ask = Analyses.ask_of_ctx ctx in
let invalidate_one st lv =
Expand Down
52 changes: 35 additions & 17 deletions src/analyses/apron/apronPriv.apron.ml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ module type S =
val enter_multithreaded: Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> apron_components_t -> apron_components_t
val threadenter: Q.ask -> (V.t -> G.t) -> apron_components_t -> apron_components_t

val thread_join: Q.ask -> (V.t -> G.t) -> Cil.exp -> apron_components_t -> apron_components_t
val thread_join: ?force:bool -> Q.ask -> (V.t -> G.t) -> Cil.exp -> apron_components_t -> apron_components_t
val thread_return: Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> ThreadIdDomain.Thread.t -> apron_components_t -> apron_components_t
val iter_sys_vars: (V.t -> G.t) -> VarQuery.t -> V.t VarQuery.f -> unit (** [Queries.IterSysVars] for apron. *)

Expand All @@ -62,7 +62,7 @@ struct
let lock ask getg st m = st
let unlock ask getg sideg st m = st

let thread_join ask getg exp st = st
let thread_join ?(force=false) ask getg exp st = st
let thread_return ask getg sideg tid st = st

let sync ask getg sideg st reason = st
Expand Down Expand Up @@ -270,7 +270,7 @@ struct
{apr = apr_local'; priv = (p', w')}


let thread_join ask getg exp st = st
let thread_join ?(force=false) ask getg exp st = st
let thread_return ask getg sideg tid st = st

let sync ask getg sideg (st: apron_components_t) reason =
Expand Down Expand Up @@ -461,7 +461,7 @@ struct
let apr_local = remove_globals_unprotected_after_unlock ask m apr in
{st with apr = apr_local}

let thread_join ask getg exp st = st
let thread_join ?(force=false) ask getg exp st = st
let thread_return ask getg sideg tid st = st

let sync ask getg sideg (st: apron_components_t) reason =
Expand Down Expand Up @@ -983,22 +983,40 @@ struct
let l' = L.add lm apr_side l in
{apr = apr_local; priv = (w',LMust.add lm lmust,l')}

let thread_join (ask:Q.ask) getg exp (st: apron_components_t) =
let thread_join ?(force=false) (ask:Q.ask) getg exp (st: apron_components_t) =
let w,lmust,l = st.priv in
let tids = ask.f (Q.EvalThread exp) in
if ConcDomain.ThreadSet.is_top tids then
st (* TODO: why needed? *)
if force then (
if ConcDomain.ThreadSet.is_top tids then (
M.info ~category:Unsound "Unknown thread ID assume-joined, Apron privatization unsound"; (* TODO: something more sound *)
st (* cannot find all thread IDs to join them all *)
)
else (
(* fold throws if the thread set is top *)
let tids' = ConcDomain.ThreadSet.diff tids (ask.f Q.MustJoinedThreads) in (* avoid unnecessary imprecision by force joining already must-joined threads, e.g. 46-apron2/04-other-assume-inprec *)
let (lmust', l') = ConcDomain.ThreadSet.fold (fun tid (lmust, l) ->
let lmust',l' = G.thread (getg (V.thread tid)) in
(LMust.union lmust' lmust, L.join l l')
) tids' (lmust, l)
in
{st with priv = (w, lmust', l')}
)
)
else (
(* elements throws if the thread set is top *)
let tids = ConcDomain.ThreadSet.elements tids in
match tids with
| [tid] ->
let lmust',l' = G.thread (getg (V.thread tid)) in
{st with priv = (w, LMust.union lmust' lmust, L.join l l')}
| _ ->
(* To match the paper more closely, one would have to join in the non-definite case too *)
(* Given how we handle lmust (for initialization), doing this might actually be beneficial given that it grows lmust *)
st
if ConcDomain.ThreadSet.is_top tids then
st (* TODO: why needed? *)
else (
(* elements throws if the thread set is top *)
let tids = ConcDomain.ThreadSet.elements tids in
match tids with
| [tid] ->
let lmust',l' = G.thread (getg (V.thread tid)) in
{st with priv = (w, LMust.union lmust' lmust, L.join l l')}
| _ ->
(* To match the paper more closely, one would have to join in the non-definite case too *)
(* Given how we handle lmust (for initialization), doing this might actually be beneficial given that it grows lmust *)
st
)
)

let thread_return ask getg sideg tid (st: apron_components_t) =
Expand Down
1 change: 1 addition & 0 deletions src/analyses/libraryFunctions.ml
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,7 @@ let invalidate_actions = [
"down_trylock", readsAll;
"up", readsAll;
"ZSTD_customFree", frees [1]; (* only used with extraspecials *)
"__goblint_assume_join", readsAll;
]

(* used by get_invalidate_action to make sure
Expand Down
7 changes: 6 additions & 1 deletion src/analyses/symbLocks.ml
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,12 @@ struct
| a when not (Queries.ES.is_bot a) -> Queries.ES.add e a
| _ -> Queries.ES.singleton e
in
if M.tracing then M.tracel "symb_locks" "get_all_locks exps %a = %a\n" d_plainexp e Queries.ES.pretty exps;
if M.tracing then M.tracel "symb_locks" "get_all_locks st = %a\n" D.pretty st;
let add_locks x xs = PS.union (get_locks x st) xs in
Queries.ES.fold add_locks exps (PS.empty ())
let r = Queries.ES.fold add_locks exps (PS.empty ()) in
if M.tracing then M.tracel "symb_locks" "get_all_locks %a = %a\n" d_plainexp e PS.pretty r;
r

let same_unknown_index (ask: Queries.ask) exp slocks =
let uk_index_equal i1 i2 = ask.f (Queries.MustBeEqual (i1, i2)) in
Expand Down Expand Up @@ -148,6 +152,7 @@ struct
*)
let one_perelem (e,a,l) xs =
(* ignore (printf "one_perelem (%a,%a,%a)\n" Exp.pretty e Exp.pretty a Exp.pretty l); *)
if M.tracing then M.tracel "symb_locks" "one_perelem (%a,%a,%a)\n" Exp.pretty e Exp.pretty a Exp.pretty l;
match Exp.fold_offs (Exp.replace_base (dummyFunDec.svar,`NoOffset) e l) with
| Some (v, o) ->
(* ignore (printf "adding lock %s\n" l); *)
Expand Down
27 changes: 27 additions & 0 deletions src/analyses/threadJoins.ml
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,35 @@ struct
| _ -> ctx.local (* if multiple possible thread ids are joined, none of them is must joined*)
(* Possible improvement: Do the intersection first, things that are must joined in all possibly joined threads are must-joined *)
)
| `Unknown "__goblint_assume_join" ->
let id = List.hd arglist in
let threads = ctx.ask (Queries.EvalThread id) in
if TIDs.is_top threads then (
M.info ~category:Unsound "Unknown thread ID assume-joined, assuming ALL threads must-joined.";
D.bot () (* consider everything joined, D is reversed so bot is All threads *)
)
else (
(* elements throws if the thread set is top *)
let threads = TIDs.elements threads in
List.fold_left (fun acc tid ->
let joined = ctx.global tid in
D.union (D.add tid acc) joined
) ctx.local threads
)
| _ -> ctx.local

let threadspawn ctx lval f args fctx =
if D.is_bot ctx.local then ( (* bot is All threads *)
M.info ~category:Imprecise "Thread created while ALL threads must-joined, continuing with no threads joined.";
D.top () (* top is no threads *)
)
else
match ThreadId.get_current (Analyses.ask_of_ctx fctx) with
| `Lifted tid ->
D.remove tid ctx.local
| _ ->
ctx.local

let query ctx (type a) (q: a Queries.t): a Queries.result =
match q with
| Queries.MustJoinedThreads -> (ctx.local:ConcDomain.MustThreadSet.t) (* type annotation needed to avoid "would escape the scope of its equation" *)
Expand Down
30 changes: 27 additions & 3 deletions src/analyses/varEq.ml
Original file line number Diff line number Diff line change
Expand Up @@ -315,15 +315,18 @@ struct
| _ -> failwith "Unmatched pattern."
in
let r =
if Queries.LS.is_top bls || Queries.LS.mem (dummyFunDec.svar, `NoOffset) bls
if Cil.isConstant b then false
else if Queries.LS.is_top bls || Queries.LS.mem (dummyFunDec.svar, `NoOffset) bls
then ((*Messages.warn "No PT-set: switching to types ";*) type_may_change_apt a )
else Queries.LS.exists (lval_may_change_pt a) bls
in
(* if r
then (Messages.warn ~msg:("Kill " ^sprint 80 (Exp.pretty () a)^" because of "^sprint 80 (Exp.pretty () b)) (); r)
else (Messages.warn ~msg:("Keep " ^sprint 80 (Exp.pretty () a)^" because of "^sprint 80 (Exp.pretty () b)) (); r)
Messages.warn ~msg:(sprint 80 (Exp.pretty () b) ^" changed lvalues: "^sprint 80 (Queries.LS.pretty () bls)) ();
*) r
*)
if M.tracing then M.tracel "var_eq" "may_change %a %a = %B\n" CilType.Exp.pretty b CilType.Exp.pretty a r;
r

(* Remove elements, that would change if the given lval would change.*)
let remove_exp ask (e:exp) (st:D.t) : D.t =
Expand Down Expand Up @@ -376,6 +379,12 @@ struct
let st =
*) let lvt = unrollType @@ Cilfacade.typeOfLval lv in
(* Messages.warn ~msg:(sprint 80 (d_type () lvt)) (); *)
if M.tracing then (
M.tracel "var_eq" "add_eq is_global_var %a = %B\n" d_plainlval lv (is_global_var ask (Lval lv) = Some false);
M.tracel "var_eq" "add_eq interesting %a = %B\n" d_plainexp rv (Exp.interesting rv);
M.tracel "var_eq" "add_eq is_global_var %a = %B\n" d_plainexp rv (is_global_var ask rv = Some false);
M.tracel "var_eq" "add_eq type %a = %B\n" d_plainlval lv ((isArithmeticType lvt && match lvt with | TFloat _ -> false | _ -> true ) || isPointerType lvt);
);
if is_global_var ask (Lval lv) = Some false
&& Exp.interesting rv
&& is_global_var ask rv = Some false
Expand Down Expand Up @@ -519,7 +528,18 @@ struct
D.B.fold add es (Queries.ES.empty ())

let rec eq_set_clos e s =
match e with
if M.tracing then M.traceli "var_eq" "eq_set_clos %a\n" d_plainexp e;
let r = match e with
| AddrOf (Mem (BinOp (IndexPI, a, i, _)), os) ->
(* convert IndexPI to Index offset *)
(* TODO: this applies eq_set_clos under the offset, unlike cases below; should generalize? *)
Queries.ES.fold (fun e acc -> (* filter_map *)
match e with
| CastE (_, StartOf a') -> (* eq_set adds casts *)
let e' = AddrOf (Cil.addOffsetLval (Index (i, os)) a') in (* TODO: re-add cast? *)
Queries.ES.add e' acc
| _ -> acc
) (eq_set_clos a s) (Queries.ES.empty ())
| SizeOf _
| SizeOfE _
| SizeOfStr _
Expand All @@ -541,6 +561,9 @@ struct
Queries.ES.map (fun e -> CastE (t,e)) (eq_set_clos e s)
| Question _ -> failwith "Logical operations should be compiled away by CIL."
| _ -> failwith "Unmatched pattern."
in
if M.tracing then M.traceu "var_eq" "eq_set_clos %a = %a\n" d_plainexp e Queries.ES.pretty r;
r


let query ctx (type a) (x: a Queries.t): a Queries.result =
Expand All @@ -550,6 +573,7 @@ struct
| Queries.EqualSet e ->
let r = eq_set_clos e ctx.local in
(* Messages.warn ~msg:("equset of "^(sprint 80 (d_exp () e))^" is "^(Queries.ES.short 80 r)) (); *)
if M.tracing then M.tracel "var_eq" "equalset %a = %a\n" d_plainexp e Queries.ES.pretty r;
r
| _ -> Queries.Result.top x

Expand Down
9 changes: 8 additions & 1 deletion src/cdomains/exp.ml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
open Pretty
open Cil

module M = Messages

module Exp =
struct
include CilType.Exp
Expand All @@ -10,6 +12,8 @@ struct
(* TODO: what does interesting mean? *)
let rec interesting x =
match x with
| AddrOf (Mem (BinOp (IndexPI, a, _i, _)), _os) ->
interesting a
| SizeOf _
| SizeOfE _
| SizeOfStr _
Expand Down Expand Up @@ -290,19 +294,21 @@ struct
in
let rec helper exp =
match exp with
(* TODO: handle IndexPI like var_eq eq_set_clos? *)
| SizeOf _
| SizeOfE _
| SizeOfStr _
| AlignOf _
| AlignOfE _
| UnOp _
| BinOp _
| StartOf _
| Const _ -> raise NotSimpleEnough
| Lval (Var v, os) -> EVar v :: conv_o os
| Lval (Mem e, os) -> helper e @ [EDeref] @ conv_o os
| AddrOf (Var v, os) -> EVar v :: conv_o os @ [EAddr]
| AddrOf (Mem e, os) -> helper e @ [EDeref] @ conv_o os @ [EAddr]
| StartOf (Var v, os) -> EVar v :: conv_o os @ [EAddr]
| StartOf (Mem e, os) -> helper e @ [EDeref] @ conv_o os @ [EAddr]
| CastE (_,e) -> helper e
| Question _ -> failwith "Logical operations should be compiled away by CIL."
| _ -> failwith "Unmatched pattern."
Expand Down Expand Up @@ -331,6 +337,7 @@ struct
List.rev el, fs

let from_exps a l : t option =
if M.tracing then M.tracel "symb_locks" "from_exps %a (%s) %a (%s)\n" d_plainexp a (ees_to_str (toEl a)) d_plainexp l (ees_to_str (toEl l));
let a, l = toEl a, toEl l in
(* ignore (printf "from_exps:\n %s\n %s\n" (ees_to_str a) (ees_to_str l)); *)
(*let rec fold_left2 f a xs ys =
Expand Down
2 changes: 1 addition & 1 deletion src/cdomains/mHP.ml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ let exists_definitely_not_started_in_joined (current,created) other_joined =
(** Must the thread with thread id other be already joined *)
let must_be_joined other joined =
if ConcDomain.ThreadSet.is_top joined then
false
true (* top means all threads are joined, so [other] must be as well *)
else
List.mem other (ConcDomain.ThreadSet.elements joined)

Expand Down
14 changes: 14 additions & 0 deletions src/util/options.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,20 @@
}
},
"additionalProperties": false
},
"read": {
"title": "sem.unknown_function.read",
"type": "object",
"properties": {
"args": {
"title": "sem.unknown_function.read.args",
"description":
"Unknown function call reads arguments passed to it",
"type": "boolean",
"default": true
}
},
"additionalProperties": false
}
},
"additionalProperties": false
Expand Down
4 changes: 2 additions & 2 deletions tests/regression/02-base/78-realloc-free.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ void test1() {

void* test2_f(void *arg) {
int *p = arg;
*p = 1; // RACE!
*p = 1; // NORACE
return NULL;
}

void test2() {
int *p = malloc(sizeof(int));
pthread_t id;
pthread_create(&id, NULL, test2_f, p);
realloc(p, sizeof(int)); // RACE!
realloc(p, sizeof(int)); // NORACE
}

void* test3_f(void *arg) {
Expand Down
Loading