Skip to content

Commit 3b9ed98

Browse files
authored
Merge pull request #1977 from goblint/copilot/fix-invalid-widen-issue
Refactor `bot` of threadJoins analysis to mean uncomputed
2 parents 8cfeedb + 8dbff69 commit 3b9ed98

11 files changed

Lines changed: 77 additions & 56 deletions

File tree

src/analyses/apron/relationPriv.apron.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1242,7 +1242,7 @@ struct
12421242
)
12431243
else (
12441244
(* fold throws if the thread set is top *)
1245-
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 *)
1245+
let tids' = ConcDomain.ThreadSet.diff_mustset tids (ask.f Q.MustJoinedThreads) in (* avoid unnecessary imprecision by force joining already must-joined threads, e.g. 46-apron2/04-other-assume-inprec *)
12461246
let (lmust', l') = ConcDomain.ThreadSet.fold (fun tid (lmust, l) ->
12471247
let lmust',l' = G.thread (getg (V.thread tid)) in
12481248
(LMust.union lmust' lmust, L.join l l')

src/analyses/basePriv.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,7 @@ struct
636636
)
637637
else (
638638
(* fold throws if the thread set is top *)
639-
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 *)
639+
let tids' = ConcDomain.ThreadSet.diff_mustset tids (ask.f Q.MustJoinedThreads) in (* avoid unnecessary imprecision by force joining already must-joined threads, e.g. 46-apron2/04-other-assume-inprec *)
640640
let (lmust', l') = ConcDomain.ThreadSet.fold (fun tid (lmust, l) ->
641641
let lmust',l' = G.thread (getg (V.thread tid)) in
642642
(LMust.union lmust' lmust, L.join l l')

src/analyses/creationLockset.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ module Spec = struct
7171
(TIDs.empty ())
7272
in
7373
let must_joined_tids = man.ask Queries.MustJoinedThreads in
74-
TIDs.diff may_transitively_created_tids must_joined_tids
74+
TIDs.diff_mustset may_transitively_created_tids must_joined_tids
7575

7676
(** handle unlock of mutex [lock] *)
7777
let unlock man tid possibly_running_tids lock =

src/analyses/mHPAnalysis.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ struct
1515
let should_print {tid; created; must_joined} =
1616
GobConfig.get_bool "dbg.full-output" ||
1717
(not (ConcDomain.ThreadSet.is_empty created) ||
18-
not (ConcDomain.ThreadSet.is_empty must_joined))
18+
not (ConcDomain.FiniteMustThreadSet.is_empty must_joined))
1919
end
2020

2121
let access man _: MHP.t = MHP.current (Analyses.ask_of_man man)

src/analyses/threadId.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ struct
121121
ConcDomain.ThreadSet.is_empty created
122122
else if man.ask Queries.ThreadsJoinedCleanly then
123123
let joined = man.ask Queries.MustJoinedThreads in
124-
ConcDomain.ThreadSet.is_empty (ConcDomain.ThreadSet.diff created joined)
124+
ConcDomain.ThreadSet.is_empty (ConcDomain.ThreadSet.diff_mustset created joined)
125125
else
126126
false
127127
| _ -> false

src/analyses/threadJoins.ml

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ open Analyses
77

88
module TID = ThreadIdDomain.Thread
99
module TIDs = ConcDomain.ThreadSet
10-
module MustTIDs = ConcDomain.MustThreadSet
1110
module CleanExit = Queries.MustBool
1211

1312
module Spec =
@@ -16,9 +15,13 @@ struct
1615

1716
let name () = "threadJoins"
1817

18+
19+
(* Must thread set, join is intersection, meet is union. Bottom denotes unreachability. Put here to not expose weirdness to the outsside *)
20+
module MustTIDsWithBot = SetDomain.Reverse (SetDomain.ToppedSet (ThreadIdDomain.FlagConfiguredTID) (struct let topname = "All Threads" end))
21+
1922
(* The first component is the set of must-joined TIDs, the second component tracks whether all TIDs recorded in MustTIDs have been exited cleanly, *)
2023
(* i.e., all created subthreads have also been joined. This is helpful as there is no set of all transitively created threads available. *)
21-
module D = Lattice.Prod(MustTIDs)(CleanExit)
24+
module D = Lattice.Prod(MustTIDsWithBot)(CleanExit)
2225
include Analyses.ValueContexts(D)
2326
module G = D
2427
module V =
@@ -34,7 +37,15 @@ struct
3437
let (j,joined_clean) = man.local in
3538
(* the current thread has been exited cleanly if all joined threads where exited cleanly, and all created threads are joined *)
3639
let created = man.ask Queries.CreatedThreads in
37-
let clean = TIDs.subset created j in
40+
let clean =
41+
if MustTIDsWithBot.is_bot j then
42+
raise Deadcode
43+
else
44+
TIDs.for_all (function
45+
| ThreadIdDomain.Thread ft -> MustTIDsWithBot.mem ft j
46+
| ThreadIdDomain.UnknownThread -> false
47+
) created
48+
in
3849
man.sideg tid (j, joined_clean && clean)
3950
| _ -> () (* correct? *)
4051

@@ -55,59 +66,63 @@ struct
5566
(* all elements are known *)
5667
let threads = TIDs.elements threads in
5768
match threads with
58-
| [tid] when TID.is_unique tid->
69+
| [(ThreadIdDomain.Thread tid_ft) as tid] when TID.is_unique tid->
5970
let (local_joined, local_clean) = man.local in
6071
let (other_joined, other_clean) = man.global tid in
61-
(MustTIDs.union (MustTIDs.add tid local_joined) other_joined, local_clean && other_clean)
72+
(MustTIDsWithBot.union (MustTIDsWithBot.add tid_ft local_joined) other_joined, local_clean && other_clean)
6273
| _ -> man.local (* if multiple possible thread ids are joined, none of them is must joined *)
6374
(* Possible improvement: Do the intersection first, things that are must joined in all possibly joined threads are must-joined *)
6475
)
6576
| Unknown, "__goblint_assume_join" ->
6677
let id = List.hd arglist in
6778
let threads = man.ask (Queries.EvalThread id) in
6879
if TIDs.is_top threads then (
69-
M.info ~category:Unsound "Unknown thread ID assume-joined, assuming ALL threads must-joined.";
70-
(MustTIDs.bot(), true) (* consider everything joined, MustTIDs is reversed so bot is All threads *)
71-
)
72-
else (
73-
(* all elements are known *)
74-
let threads = TIDs.elements threads in
75-
if List.compare_length_with threads 1 > 0 then
76-
M.info ~category:Unsound "Ambiguous thread ID assume-joined, assuming all of those threads must-joined.";
77-
List.fold_left (fun (joined, clean) tid ->
80+
M.info ~category:Unsound "Unknown thread ID assume-joined, continuing with known thread ids.";
81+
);
82+
let threads = TIDs.remove (ThreadIdDomain.UnknownThread) threads in
83+
(* all elements are known *)
84+
let threads = TIDs.elements threads in
85+
if List.compare_length_with threads 1 > 0 then
86+
M.info ~category:Unsound "Ambiguous thread ID assume-joined, assuming all of those threads must-joined.";
87+
List.fold_left (fun (joined, clean) tid ->
88+
match tid with
89+
| ThreadIdDomain.Thread tid_ft ->
7890
let (other_joined, other_clean) = man.global tid in
79-
(MustTIDs.union (MustTIDs.add tid joined) other_joined, clean && other_clean)
80-
) (man.local) threads
81-
)
91+
(MustTIDsWithBot.union (MustTIDsWithBot.add tid_ft joined) other_joined, clean && other_clean)
92+
| ThreadIdDomain.UnknownThread -> assert false (* unreachable *)
93+
) (man.local) threads
8294
| _, _ -> man.local
8395

8496
let threadspawn man ~multiple lval f args fman =
85-
if D.is_bot man.local then ( (* bot is All threads *)
86-
M.info ~category:Imprecise "Thread created while ALL threads must-joined, continuing with no threads joined.";
87-
D.top () (* top is no threads *)
97+
let (j, clean) = man.local in
98+
if MustTIDsWithBot.is_bot j then ( (* bot is All threads *)
99+
raise Deadcode
88100
)
89101
else
90102
match ThreadId.get_current (Analyses.ask_of_man fman) with
91-
| `Lifted tid ->
92-
let (j, clean) = man.local in
93-
(MustTIDs.remove tid j, clean)
103+
| `Lifted (ThreadIdDomain.Thread tid) ->
104+
(MustTIDsWithBot.remove tid j, clean)
94105
| _ ->
95106
man.local
96107

97108
let query man (type a) (q: a Queries.t): a Queries.result =
98109
match q with
99-
| Queries.MustJoinedThreads -> (fst man.local:ConcDomain.MustThreadSet.t) (* type annotation needed to avoid "would escape the scope of its equation" *)
110+
| Queries.MustJoinedThreads ->
111+
(match ((fst man.local):MustTIDsWithBot.t) with
112+
| `Lifted set -> set
113+
| `Top -> Queries.Result.top q (* This is the lifted top of the reversed lattice, i.e., bottom, needed because of https://github.com/goblint/analyzer/issues/1978 *)
114+
)
100115
| Queries.ThreadsJoinedCleanly -> (snd man.local:bool)
101116
| _ -> Queries.Result.top q
102117

103118
let combine_env man lval fexp f args fc au f_ask =
104119
let (caller_joined, local_clean) = man.local in
105120
let (callee_joined, callee_clean) = au in
106-
(MustTIDs.union caller_joined callee_joined, local_clean && callee_clean)
107-
121+
if (MustTIDsWithBot.is_bot callee_joined) then raise Deadcode;
122+
(MustTIDsWithBot.union caller_joined callee_joined, local_clean && callee_clean)
108123

109-
let startstate v = (MustTIDs.empty (), true)
110-
let exitstate v = (MustTIDs.empty (), true)
124+
let startstate v = (MustTIDsWithBot.empty (), true)
125+
let exitstate v = (MustTIDsWithBot.empty (), true)
111126
end
112127

113128
let _ = MCP.register_analysis ~dep:["threadid"] (module Spec : MCPSpec)

src/analyses/useAfterFree.ml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ module HeapVars = SetDomain.ToppedSet(CilType.Varinfo)(struct let topname = "All
1111
(* Heap vars created by alloca() and deallocated at function exit * Heap vars deallocated by free() *)
1212
module StackAndHeapVars = Lattice.Prod(AllocaVars)(HeapVars)
1313

14-
module ThreadIdToJoinedThreadsMap = MapDomain.MapBot(ThreadIdDomain.ThreadLifted)(ConcDomain.MustThreadSet)
14+
module ThreadIdToJoinedThreadsMap = MapDomain.MapBot(ThreadIdDomain.ThreadLifted)(ConcDomain.FiniteMustThreadSet)
1515

1616
module Spec : Analyses.MCPSpec =
1717
struct
@@ -46,8 +46,7 @@ struct
4646
let not_started = MHP.definitely_not_started (current, created_threads) tid in
4747
let possibly_started = not not_started in
4848
(* If [current] is possibly running together with [tid], but is also joined before the free() in [tid], then no need to WARN *)
49-
let current_joined_before_free = ConcDomain.MustThreadSet.mem current joined_threads in
50-
possibly_started && not current_joined_before_free
49+
possibly_started && not (ConcDomain.FiniteMustThreadSet.mem_lifted current joined_threads)
5150
| `Top -> true
5251
| `Bot -> false
5352
in

src/cdomain/value/cdomains/concDomain.ml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
(** Domains for thread sets and their uniqueness. *)
22

3+
module FiniteMustThreadSet = struct
4+
include SetDomain.Reverse (SetDomain.Make (ThreadIdDomain.FlagConfiguredTID))
5+
6+
let mem_lifted lifted_tid set =
7+
match lifted_tid with
8+
| ThreadIdDomain.Thread ft -> mem ft set
9+
| ThreadIdDomain.UnknownThread -> false
10+
end
11+
312
module ThreadSet =
413
struct
514
include SetDomain.Make (ThreadIdDomain.Thread)
@@ -19,8 +28,9 @@ struct
1928

2029
let narrow x y = merge (fun x y -> widen x (join x y)) narrow x y
2130

31+
let diff_mustset x (y: FiniteMustThreadSet.t) =
32+
FiniteMustThreadSet.fold (fun t -> remove (ThreadIdDomain.Thread t)) y x
2233
end
23-
module MustThreadSet = SetDomain.Reverse(ThreadSet)
2434

2535
module CreatedThreadSet = ThreadSet
2636

src/cdomains/mHP.ml

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ module Pretty = GoblintCil.Pretty
1010
type t = {
1111
tid: ThreadIdDomain.ThreadLifted.t;
1212
created: ConcDomain.ThreadSet.t;
13-
must_joined: ConcDomain.ThreadSet.t;
13+
must_joined: ConcDomain.FiniteMustThreadSet.t;
1414
} [@@deriving eq, ord, hash, relift]
1515

1616
let current (ask:Queries.ask) =
@@ -35,10 +35,10 @@ let pretty () {tid; created; must_joined} =
3535
Some (Pretty.dprintf "created=%a" ConcDomain.ThreadSet.pretty created)
3636
in
3737
let must_joined_doc =
38-
if ConcDomain.ThreadSet.is_empty must_joined then
38+
if ConcDomain.FiniteMustThreadSet.is_empty must_joined then
3939
None
4040
else
41-
Some (Pretty.dprintf "must_joined=%a" ConcDomain.ThreadSet.pretty must_joined)
41+
Some (Pretty.dprintf "must_joined=%a" ConcDomain.FiniteMustThreadSet.pretty must_joined)
4242
in
4343
let docs = List.filter_map Fun.id [tid_doc; created_doc; must_joined_doc] in
4444
Pretty.dprintf "{%a}" (Pretty.d_list "; " Pretty.insert) docs
@@ -63,17 +63,10 @@ let definitely_not_started (current, created) other =
6363
not @@ ConcDomain.ThreadSet.exists (ident_or_may_be_created) created
6464

6565
let exists_definitely_not_started_in_joined (current,created) other_joined =
66-
if ConcDomain.ThreadSet.is_top other_joined then
67-
false
68-
else
69-
ConcDomain.ThreadSet.exists (definitely_not_started (current,created)) other_joined
66+
ConcDomain.FiniteMustThreadSet.exists (fun ft -> definitely_not_started (current, created) (ThreadIdDomain.Thread ft)) other_joined
7067

71-
(** Must the thread with thread id other be already joined *)
72-
let must_be_joined other joined =
73-
if ConcDomain.ThreadSet.is_top joined then
74-
true (* top means all threads are joined, so [other] must be as well *)
75-
else
76-
ConcDomain.ThreadSet.mem other joined
68+
(** Must the thread be already joined *)
69+
let must_be_joined = ConcDomain.FiniteMustThreadSet.mem_lifted
7770

7871
(** May two program points with respective MHP information happen in parallel *)
7972
let may_happen_in_parallel one two =

src/domains/queries.ml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ type _ t =
126126
| ActiveJumpBuf: JmpBufDomain.ActiveLongjmps.t t
127127
| ValidLongJmp: JmpBufDomain.JmpBufSet.t t
128128
| CreatedThreads: ConcDomain.ThreadSet.t t
129-
| MustJoinedThreads: ConcDomain.MustThreadSet.t t
129+
| MustJoinedThreads: ConcDomain.FiniteMustThreadSet.t t
130130
| ThreadsJoinedCleanly: MustBool.t t
131131
| MustProtectedVars: mustprotectedvars -> VS.t t
132132
| MustProtectingLocks: mustprotectinglocks -> LockDomain.MustLockset.t t
@@ -202,7 +202,7 @@ struct
202202
| ActiveJumpBuf -> (module JmpBufDomain.ActiveLongjmps)
203203
| ValidLongJmp -> (module JmpBufDomain.JmpBufSet)
204204
| CreatedThreads -> (module ConcDomain.ThreadSet)
205-
| MustJoinedThreads -> (module ConcDomain.MustThreadSet)
205+
| MustJoinedThreads -> (module ConcDomain.FiniteMustThreadSet)
206206
| ThreadsJoinedCleanly -> (module MustBool)
207207
| MustProtectedVars _ -> (module VS)
208208
| MustProtectingLocks _ -> (module LockDomain.MustLockset)
@@ -277,7 +277,7 @@ struct
277277
| ActiveJumpBuf -> JmpBufDomain.ActiveLongjmps.top ()
278278
| ValidLongJmp -> JmpBufDomain.JmpBufSet.top ()
279279
| CreatedThreads -> ConcDomain.ThreadSet.top ()
280-
| MustJoinedThreads -> ConcDomain.MustThreadSet.top ()
280+
| MustJoinedThreads -> ConcDomain.FiniteMustThreadSet.top ()
281281
| ThreadsJoinedCleanly -> MustBool.top ()
282282
| MustProtectedVars _ -> VS.top ()
283283
| MustProtectingLocks _ -> LockDomain.MustLockset.top ()

0 commit comments

Comments
 (0)