Skip to content

Commit ab3ddff

Browse files
authored
Merge pull request #724 from goblint/chrony
Interactive: improvements for chrony story
2 parents 21e75ec + c491dbb commit ab3ddff

21 files changed

+582
-26
lines changed

docs/user-guide/annotating.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,11 @@ The following string arguments are supported:
2525
3. `base.non-ptr`/`base.no-non-ptr` to override the `ana.base.context.non-ptr` option.
2626
4. `apron.context`/`apron.no-context` to override the `ana.apron.context` option.
2727
5. `widen`/`no-widen` to override the `ana.context.widen` option.
28+
29+
30+
## Functions
31+
Goblint-specific functions can be called in the code, where they assist the analyzer but have no runtime effect.
32+
33+
* `__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.
34+
Notably, this annotation can be used after a threads joining loop to make the assumption that the loop correctly joined all those threads.
35+
_Misuse of this annotation can cause unsoundness._

scripts/update_suite.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ def collect_warnings
159159
when /Assertion .* is unknown/ then "unknown"
160160
when /^\[Warning\]/ then "warn"
161161
when /^\[Error\]/ then "warn"
162+
when /^\[Info\]/ then "warn"
162163
when /\[Debug\]/ then next # debug "warnings" shouldn't count as other warnings (against NOWARN)
163164
when /^ on line \d+ $/ then next # dead line warnings shouldn't count (used for unreachability with NOWARN)
164165
when /^ on lines \d+..\d+ $/ then next # dead line warnings shouldn't count (used for unreachability with NOWARN)

src/analyses/accessAnalysis.ml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,11 @@ struct
207207
let arg_acc act =
208208
match act, LF.get_threadsafe_inv_ac x with
209209
| _, Some fnc -> (fnc act arglist)
210-
| `Read, None -> arglist
210+
| `Read, None ->
211+
if get_bool "sem.unknown_function.read.args" then
212+
arglist
213+
else
214+
[]
211215
| (`Write | `Free), None ->
212216
if get_bool "sem.unknown_function.invalidate.args" then
213217
arglist
@@ -220,6 +224,7 @@ struct
220224
| "memset" | "__builtin_memset" | "__builtin___memset_chk" -> false
221225
| "bzero" | "__builtin_bzero" | "explicit_bzero" | "__explicit_bzero_chk" -> false
222226
| "__builtin_object_size" -> false
227+
| "realloc" -> false
223228
| _ -> true
224229
in
225230
List.iter (access_one_top ctx `Read reach) (arg_acc `Read);

src/analyses/apron/apronAnalysis.apron.ml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,9 @@ struct
323323
| Some lv -> invalidate_one st' lv
324324
| None -> st'
325325
)
326+
| `Unknown "__goblint_assume_join" ->
327+
let id = List.hd args in
328+
Priv.thread_join ~force:true ask ctx.global id st
326329
| _ ->
327330
let ask = Analyses.ask_of_ctx ctx in
328331
let invalidate_one st lv =

src/analyses/apron/apronPriv.apron.ml

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ module type S =
3737
val enter_multithreaded: Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> apron_components_t -> apron_components_t
3838
val threadenter: Q.ask -> (V.t -> G.t) -> apron_components_t -> apron_components_t
3939

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

@@ -62,7 +62,7 @@ struct
6262
let lock ask getg st m = st
6363
let unlock ask getg sideg st m = st
6464

65-
let thread_join ask getg exp st = st
65+
let thread_join ?(force=false) ask getg exp st = st
6666
let thread_return ask getg sideg tid st = st
6767

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

272272

273-
let thread_join ask getg exp st = st
273+
let thread_join ?(force=false) ask getg exp st = st
274274
let thread_return ask getg sideg tid st = st
275275

276276
let sync ask getg sideg (st: apron_components_t) reason =
@@ -461,7 +461,7 @@ struct
461461
let apr_local = remove_globals_unprotected_after_unlock ask m apr in
462462
{st with apr = apr_local}
463463

464-
let thread_join ask getg exp st = st
464+
let thread_join ?(force=false) ask getg exp st = st
465465
let thread_return ask getg sideg tid st = st
466466

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

986-
let thread_join (ask:Q.ask) getg exp (st: apron_components_t) =
986+
let thread_join ?(force=false) (ask:Q.ask) getg exp (st: apron_components_t) =
987987
let w,lmust,l = st.priv in
988988
let tids = ask.f (Q.EvalThread exp) in
989-
if ConcDomain.ThreadSet.is_top tids then
990-
st (* TODO: why needed? *)
989+
if force then (
990+
if ConcDomain.ThreadSet.is_top tids then (
991+
M.info ~category:Unsound "Unknown thread ID assume-joined, Apron privatization unsound"; (* TODO: something more sound *)
992+
st (* cannot find all thread IDs to join them all *)
993+
)
994+
else (
995+
(* fold throws if the thread set is top *)
996+
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 *)
997+
let (lmust', l') = ConcDomain.ThreadSet.fold (fun tid (lmust, l) ->
998+
let lmust',l' = G.thread (getg (V.thread tid)) in
999+
(LMust.union lmust' lmust, L.join l l')
1000+
) tids' (lmust, l)
1001+
in
1002+
{st with priv = (w, lmust', l')}
1003+
)
1004+
)
9911005
else (
992-
(* elements throws if the thread set is top *)
993-
let tids = ConcDomain.ThreadSet.elements tids in
994-
match tids with
995-
| [tid] ->
996-
let lmust',l' = G.thread (getg (V.thread tid)) in
997-
{st with priv = (w, LMust.union lmust' lmust, L.join l l')}
998-
| _ ->
999-
(* To match the paper more closely, one would have to join in the non-definite case too *)
1000-
(* Given how we handle lmust (for initialization), doing this might actually be beneficial given that it grows lmust *)
1001-
st
1006+
if ConcDomain.ThreadSet.is_top tids then
1007+
st (* TODO: why needed? *)
1008+
else (
1009+
(* elements throws if the thread set is top *)
1010+
let tids = ConcDomain.ThreadSet.elements tids in
1011+
match tids with
1012+
| [tid] ->
1013+
let lmust',l' = G.thread (getg (V.thread tid)) in
1014+
{st with priv = (w, LMust.union lmust' lmust, L.join l l')}
1015+
| _ ->
1016+
(* To match the paper more closely, one would have to join in the non-definite case too *)
1017+
(* Given how we handle lmust (for initialization), doing this might actually be beneficial given that it grows lmust *)
1018+
st
1019+
)
10021020
)
10031021

10041022
let thread_return ask getg sideg tid (st: apron_components_t) =

src/analyses/libraryFunctions.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,7 @@ let invalidate_actions = [
521521
"down_trylock", readsAll;
522522
"up", readsAll;
523523
"ZSTD_customFree", frees [1]; (* only used with extraspecials *)
524+
"__goblint_assume_join", readsAll;
524525
]
525526

526527
(* used by get_invalidate_action to make sure

src/analyses/symbLocks.ml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,12 @@ struct
5959
| a when not (Queries.ES.is_bot a) -> Queries.ES.add e a
6060
| _ -> Queries.ES.singleton e
6161
in
62+
if M.tracing then M.tracel "symb_locks" "get_all_locks exps %a = %a\n" d_plainexp e Queries.ES.pretty exps;
63+
if M.tracing then M.tracel "symb_locks" "get_all_locks st = %a\n" D.pretty st;
6264
let add_locks x xs = PS.union (get_locks x st) xs in
63-
Queries.ES.fold add_locks exps (PS.empty ())
65+
let r = Queries.ES.fold add_locks exps (PS.empty ()) in
66+
if M.tracing then M.tracel "symb_locks" "get_all_locks %a = %a\n" d_plainexp e PS.pretty r;
67+
r
6468

6569
let same_unknown_index (ask: Queries.ask) exp slocks =
6670
let uk_index_equal i1 i2 = ask.f (Queries.MustBeEqual (i1, i2)) in
@@ -148,6 +152,7 @@ struct
148152
*)
149153
let one_perelem (e,a,l) xs =
150154
(* ignore (printf "one_perelem (%a,%a,%a)\n" Exp.pretty e Exp.pretty a Exp.pretty l); *)
155+
if M.tracing then M.tracel "symb_locks" "one_perelem (%a,%a,%a)\n" Exp.pretty e Exp.pretty a Exp.pretty l;
151156
match Exp.fold_offs (Exp.replace_base (dummyFunDec.svar,`NoOffset) e l) with
152157
| Some (v, o) ->
153158
(* ignore (printf "adding lock %s\n" l); *)

src/analyses/threadJoins.ml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,35 @@ struct
4545
| _ -> ctx.local (* if multiple possible thread ids are joined, none of them is must joined*)
4646
(* Possible improvement: Do the intersection first, things that are must joined in all possibly joined threads are must-joined *)
4747
)
48+
| `Unknown "__goblint_assume_join" ->
49+
let id = List.hd arglist in
50+
let threads = ctx.ask (Queries.EvalThread id) in
51+
if TIDs.is_top threads then (
52+
M.info ~category:Unsound "Unknown thread ID assume-joined, assuming ALL threads must-joined.";
53+
D.bot () (* consider everything joined, D is reversed so bot is All threads *)
54+
)
55+
else (
56+
(* elements throws if the thread set is top *)
57+
let threads = TIDs.elements threads in
58+
List.fold_left (fun acc tid ->
59+
let joined = ctx.global tid in
60+
D.union (D.add tid acc) joined
61+
) ctx.local threads
62+
)
4863
| _ -> ctx.local
4964

65+
let threadspawn ctx lval f args fctx =
66+
if D.is_bot ctx.local then ( (* bot is All threads *)
67+
M.info ~category:Imprecise "Thread created while ALL threads must-joined, continuing with no threads joined.";
68+
D.top () (* top is no threads *)
69+
)
70+
else
71+
match ThreadId.get_current (Analyses.ask_of_ctx fctx) with
72+
| `Lifted tid ->
73+
D.remove tid ctx.local
74+
| _ ->
75+
ctx.local
76+
5077
let query ctx (type a) (q: a Queries.t): a Queries.result =
5178
match q with
5279
| Queries.MustJoinedThreads -> (ctx.local:ConcDomain.MustThreadSet.t) (* type annotation needed to avoid "would escape the scope of its equation" *)

src/analyses/varEq.ml

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -315,15 +315,18 @@ struct
315315
| _ -> failwith "Unmatched pattern."
316316
in
317317
let r =
318-
if Queries.LS.is_top bls || Queries.LS.mem (dummyFunDec.svar, `NoOffset) bls
318+
if Cil.isConstant b then false
319+
else if Queries.LS.is_top bls || Queries.LS.mem (dummyFunDec.svar, `NoOffset) bls
319320
then ((*Messages.warn "No PT-set: switching to types ";*) type_may_change_apt a )
320321
else Queries.LS.exists (lval_may_change_pt a) bls
321322
in
322323
(* if r
323324
then (Messages.warn ~msg:("Kill " ^sprint 80 (Exp.pretty () a)^" because of "^sprint 80 (Exp.pretty () b)) (); r)
324325
else (Messages.warn ~msg:("Keep " ^sprint 80 (Exp.pretty () a)^" because of "^sprint 80 (Exp.pretty () b)) (); r)
325326
Messages.warn ~msg:(sprint 80 (Exp.pretty () b) ^" changed lvalues: "^sprint 80 (Queries.LS.pretty () bls)) ();
326-
*) r
327+
*)
328+
if M.tracing then M.tracel "var_eq" "may_change %a %a = %B\n" CilType.Exp.pretty b CilType.Exp.pretty a r;
329+
r
327330

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

521530
let rec eq_set_clos e s =
522-
match e with
531+
if M.tracing then M.traceli "var_eq" "eq_set_clos %a\n" d_plainexp e;
532+
let r = match e with
533+
| AddrOf (Mem (BinOp (IndexPI, a, i, _)), os) ->
534+
(* convert IndexPI to Index offset *)
535+
(* TODO: this applies eq_set_clos under the offset, unlike cases below; should generalize? *)
536+
Queries.ES.fold (fun e acc -> (* filter_map *)
537+
match e with
538+
| CastE (_, StartOf a') -> (* eq_set adds casts *)
539+
let e' = AddrOf (Cil.addOffsetLval (Index (i, os)) a') in (* TODO: re-add cast? *)
540+
Queries.ES.add e' acc
541+
| _ -> acc
542+
) (eq_set_clos a s) (Queries.ES.empty ())
523543
| SizeOf _
524544
| SizeOfE _
525545
| SizeOfStr _
@@ -541,6 +561,9 @@ struct
541561
Queries.ES.map (fun e -> CastE (t,e)) (eq_set_clos e s)
542562
| Question _ -> failwith "Logical operations should be compiled away by CIL."
543563
| _ -> failwith "Unmatched pattern."
564+
in
565+
if M.tracing then M.traceu "var_eq" "eq_set_clos %a = %a\n" d_plainexp e Queries.ES.pretty r;
566+
r
544567

545568

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

src/cdomains/exp.ml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
open Pretty
22
open Cil
33

4+
module M = Messages
5+
46
module Exp =
57
struct
68
include CilType.Exp
@@ -10,6 +12,8 @@ struct
1012
(* TODO: what does interesting mean? *)
1113
let rec interesting x =
1214
match x with
15+
| AddrOf (Mem (BinOp (IndexPI, a, _i, _)), _os) ->
16+
interesting a
1317
| SizeOf _
1418
| SizeOfE _
1519
| SizeOfStr _
@@ -290,19 +294,21 @@ struct
290294
in
291295
let rec helper exp =
292296
match exp with
297+
(* TODO: handle IndexPI like var_eq eq_set_clos? *)
293298
| SizeOf _
294299
| SizeOfE _
295300
| SizeOfStr _
296301
| AlignOf _
297302
| AlignOfE _
298303
| UnOp _
299304
| BinOp _
300-
| StartOf _
301305
| Const _ -> raise NotSimpleEnough
302306
| Lval (Var v, os) -> EVar v :: conv_o os
303307
| Lval (Mem e, os) -> helper e @ [EDeref] @ conv_o os
304308
| AddrOf (Var v, os) -> EVar v :: conv_o os @ [EAddr]
305309
| AddrOf (Mem e, os) -> helper e @ [EDeref] @ conv_o os @ [EAddr]
310+
| StartOf (Var v, os) -> EVar v :: conv_o os @ [EAddr]
311+
| StartOf (Mem e, os) -> helper e @ [EDeref] @ conv_o os @ [EAddr]
306312
| CastE (_,e) -> helper e
307313
| Question _ -> failwith "Logical operations should be compiled away by CIL."
308314
| _ -> failwith "Unmatched pattern."
@@ -331,6 +337,7 @@ struct
331337
List.rev el, fs
332338

333339
let from_exps a l : t option =
340+
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));
334341
let a, l = toEl a, toEl l in
335342
(* ignore (printf "from_exps:\n %s\n %s\n" (ees_to_str a) (ees_to_str l)); *)
336343
(*let rec fold_left2 f a xs ys =

0 commit comments

Comments
 (0)