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 26 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._
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
49 changes: 32 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,37 @@ 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
st (* don't consider anything more joined, matches threadJoins analysis *)
else (
(* fold throws if the thread set is top *)
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')}
)
)
michael-schwarz marked this conversation as resolved.
Show resolved Hide resolved
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
20 changes: 20 additions & 0 deletions src/analyses/threadJoins.ml
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,28 @@ 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
ctx.local (* don't consider everything joined, because would be confusing to have All threads unsoundly joined due to imprecision *)
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 =
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
22 changes: 19 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,10 @@ 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
| BinOp ((PlusPI | IndexPI), e1, e2, _) ->
eq_set_clos e1 s (* TODO: what about e2? add to some Index offset to all? *)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you maybe comment on the impact that this has now? Are we tracking equalities involving pointer arithmetic? If yes, how confident are we that we do useful things?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These additions aren't changing the equalities that are tracked, but just allows them to be used with array indexing via pointer addition (which CIL sometimes inserts instead of actual Index offset that the analysis already handles).

It's needed for the following pattern from one of the added tests:

  pthread_mutex_lock(&entry->refs_mutex);
  entry[0].refs++; // NORACE
  pthread_mutex_unlock(&entry->refs_mutex);

The symb_locks analysis uses the EqualSet query to look up equalities for entry[0].refs, but the old behavior meant that an empty set was returned, so we couldn't infer that it has the same prefix as the locking expression.

A less trivial case of it comes from the minimized chrony test, where the locking actually happens outside the function where the access is, so there's already an equality ip_addrs = inst->addresses known to the analysis and there's a symbolic lock inst->mutex, but then the access ip_addrs[0] isn't found to be protected because to find that out, the equality needs to be used inside the index expression.
Note that the index itself is completely irrelevant because all that's needed is the common inst prefix.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we are a bit too eager to declare access as non-racy here, consider the example I added:

void cache_entry_addref(struct cache_entry *entry) {
  pthread_mutex_lock(&entry->refs_mutex);
  entry->refs++; // NORACE
  (*entry).refs++; // NORACE
  entry[1].refs++; // RACE
  pthread_mutex_unlock(&entry->refs_mutex);
}

These accesses at entry[1] are racy, as the protecting mutex for that element is not held (I fixed the loop to only run until 9, so it's not UB). Also passing a t* and then using array access on it is a very common idiom in C afaik.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's quite an evil example, but I'm rather skeptical that we can do much about this. The function in isolation, as presented here, just gets a pointer and then accesses entry[1]. For an arbitrary pointer that may point to any memory and therefore a write to it may invalidate any memory, including any other program variable (any global, any local of any stack frame, any dynamically allocated memory). Of course in this particular case it doesn't, but in Goblint we have no way of knowing that, because for addresses (like the pointer here) we don't keep track of their context (whether they're pointing into an array or not).

I think our much broader assumption is that pointer arithmetic stays "within the direct object" (for the lack of a better definition) regardless of its context, because we simply don't know about the broader memory layout (even if in this case it seems obvious). To make weaker assumptions than that, we would either need some crazy possible memory layout analysis or, lacking that, assume the worst and invalidate everything (not just globals!).

Also passing a t* and then using array access on it is a very common idiom in C afaik.

Is it though? If you want to use it as an array, you might as well pass it as an array (which is a pointer under the hood anyway), to make it clear that it's an array (usually with another length argument), not a pointer to an isolated object.

Now thinking about bad things, if the argument were an array and we copy the array domain into the function and mutated it, do those writes even propagate back to the caller's array domain?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the assumption we should be making is that

    1. pointer arithmetic never leaves the corresponding varinfo (or blob for malloced things),
    1. and never can cross struct boundaries once one has descended (because the compiler may have inserted padding), so no adding magic offsets to get from struct member a to a in the next element of the array unless a is the first struct element.
      That seems not too strong but also not as radical as setting everything to top. (And should be possible as our addresses maintain offsets and have abstract values at the granularity of variables anyway).

I think if we assume anything more, we cannot seriously claim we are sound anymore. I personally would be extremely unhappy with Goblint accepting this program as race-free! (How do @vesalvojdani @stilscher @jerhard feel here?)

Should we put this on the agenda at the Gobcon?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now thinking about bad things, if the argument were an array and we copy the array domain into the function and mutated it, do those writes even propagate back to the caller's array domain?

I'd hope so. We pass a pointer to the array, hence the entire(!) array will go to the callee, we update things there and should then copy the value over in combine?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe this is somehow also related to #434? Because the thing is, this change simply allows the entry[0] or entry[1] expression to preserve its prefix (previously we gave up on index access of pointer). Index access on array type has been previously supported for a long time and this shouldn't be different: symbolic locking happens on the basis of common prefix between the mutex and the access (and we have at least one paper on this!). So I'm quite surprised how this can suddenly be going wrong.

cc: @vesalvojdani

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd hope so. We pass a pointer to the array, hence the entire(!) array will go to the callee, we update things there and should then copy the value over in combine?

I haven't dug into this now, but this is exactly where I'm worried. Sure, if you take a pointer of the array and pass that to a function, it gets passed along as reachable and combined back. But what if you pass the array directly (without making a pointer to it first). In the caller, the local domain contains an array abstraction directly under the array variable name, there isn't an address domain containing an address to the array contents. This is analogous to how an integer variable contains the integer abstraction directly. And when they are passed as arguments, they are copied — there's a copy of the integer's abstraction directly under the callee's argument and copy of the array's abstraction directly under the callee's argument. On combine, the internal integer abstraction is simply removed and so would the callee's (modified) array one. Or do we have some special logic somewhere there specifically for arrays? I don't recall at least.

I personally would be extremely unhappy with Goblint accepting this program as race-free!

Indeed in the non-UB test you added, we should not. It's just the symbolic locks that then might be assuming something stronger.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized there's a granularity difference at play here as well:

  • The cache_entry tests in our suite use an array of structs, where each struct has a mutex protecting its data.
  • The chrony DNS_Async_Instance struct, for which this is needed, is a standalone struct (not one in an array), where the single mutex protects an entire array inside the struct.

Because they are structurally different like that, I'd hope we can actually be sound for the tests you added and the story in chrony. I'll have to dig into how exactly these different granularity locking schemes, and per-field vs per-index symbolic locks are supposed to work, because these cases involve both at the same time (but in different order!).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The case for chrony is easier than the more general one, where the unsoundness problem came in. I now fixed that using very ad-hoc handling that's still sufficient for chrony.

| SizeOf _
| SizeOfE _
| SizeOfStr _
Expand All @@ -541,6 +553,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 +565,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
10 changes: 9 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
| BinOp ((PlusPI | IndexPI), e1, e2, _) ->
interesting e1 (* TODO: what about e2? *)
michael-schwarz marked this conversation as resolved.
Show resolved Hide resolved
| SizeOf _
| SizeOfE _
| SizeOfStr _
Expand Down Expand Up @@ -290,19 +294,22 @@ struct
in
let rec helper exp =
match exp with
| BinOp ((PlusPI | IndexPI), e1, e2, _) ->
helper e1 (* TODO: what about e2? add to some Index offset to all? *)
michael-schwarz marked this conversation as resolved.
Show resolved Hide resolved
| 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 +338,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
36 changes: 36 additions & 0 deletions tests/regression/06-symbeq/37-funloop_index.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// PARAM: --disable ana.mutex.disjoint_types --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'"
// copy of 06/02 with additional index accesses
#include<pthread.h>
#include<stdio.h>

struct cache_entry {
int refs;
pthread_mutex_t refs_mutex;
} cache[10];

void cache_entry_addref(struct cache_entry *entry) {
pthread_mutex_lock(&entry->refs_mutex);
entry->refs++; // NORACE
(*entry).refs++; // NORACE
entry[0].refs++; // NORACE
pthread_mutex_unlock(&entry->refs_mutex);
}

void *t_fun(void *arg) {
int i;
for(i=0; i<10; i++)
cache_entry_addref(&cache[i]); // NORACE
return NULL;
}

int main () {
for (int i = 0; i < 10; i++)
pthread_mutex_init(&cache[i].refs_mutex, NULL);

int i;
pthread_t t1;
pthread_create(&t1, NULL, t_fun, NULL);
for(i=0; i<10; i++)
cache_entry_addref(&cache[i]); // NORACE
return 0;
}
Loading