Skip to content

Commit 90d8705

Browse files
Merge pull request #1652 from goblint/issue_1651
Analysis of `pthread_barrier`s
2 parents 63df974 + f70b193 commit 90d8705

26 files changed

+780
-1
lines changed

.github/workflows/coverage.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ jobs:
4646
- name: Install dependencies
4747
run: opam install . --deps-only --locked --with-test
4848

49+
- name: Install os gem for operating system detection
50+
run: sudo gem install os
51+
4952
- name: Install coverage dependencies
5053
run: opam install bisect_ppx
5154

.github/workflows/locked.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ jobs:
4848
- name: Install dependencies
4949
run: opam install . --deps-only --locked --with-test
5050

51+
- name: Install os gem for operating system detection
52+
run: sudo gem install os
53+
5154
- name: Build
5255
run: ./make.sh nat
5356

@@ -100,6 +103,9 @@ jobs:
100103
- name: Install dependencies
101104
run: opam install . --deps-only --locked --with-test
102105

106+
- name: Install os gem for operating system detection
107+
run: sudo gem install os
108+
103109
- name: Build
104110
run: ./make.sh nat
105111

.github/workflows/unlocked.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ jobs:
5757

5858
- name: Install dependencies
5959
run: opam install . --deps-only --with-test
60+
61+
- name: Install os gem for operating system detection
62+
run: sudo gem install os
6063

6164
- name: Install Apron dependencies
6265
if: ${{ matrix.apron }}
@@ -118,6 +121,9 @@ jobs:
118121
- name: Install dependencies
119122
run: opam install . --deps-only --with-test
120123

124+
- name: Install os gem for operating system detection
125+
run: sudo gem install os
126+
121127
- name: Install Apron dependencies
122128
run: |
123129
opam depext apron

make.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ rule() {
7575
;; setup)
7676
echo "Make sure you have the following installed: opam >= 2.0.0, git, patch, m4, autoconf, libgmp-dev, libmpfr-dev, pkg-config"
7777
echo "For the --html output you also need: javac, ant, dot (graphviz)"
78-
echo "For running the regression tests you also need: ruby, gem, curl"
78+
echo "For running the regression tests you also need: ruby, gem, curl, and the `os` gem"
7979
echo "For reference see ./Dockerfile or ./scripts/travis-ci.sh"
8080
opam_setup
8181
;; dev)
@@ -90,6 +90,7 @@ rule() {
9090
# Use `git commit -n` to temporarily bypass the hook if necessary.
9191
echo "Installing gem parallel (not needed for ./scripts/update_suite.rb -s)"
9292
sudo gem install parallel
93+
sudo gem install os
9394
;; headers)
9495
curl -L -O https://github.com/goblint/linux-headers/archive/master.tar.gz
9596
tar xf master.tar.gz && rm master.tar.gz

scripts/update_suite.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,15 @@ def run ()
575575
end
576576
end
577577

578+
maybemac = true
579+
580+
begin
581+
require 'os'
582+
maybemac = OS.mac?
583+
rescue LoadError => e
584+
puts "Missing os gem (install with: gem install os), skipping tests that do not work on mac"
585+
end
586+
578587
#processing the file information
579588
projects = []
580589
project_ids = Set.new
@@ -603,6 +612,7 @@ def run ()
603612
lines = IO.readlines(path, :encoding => "UTF-8")
604613

605614
next if not future and only.nil? and lines[0] =~ /SKIP/
615+
next if maybemac and lines[0] =~ /NOMAC/
606616
next if marshal and lines[0] =~ /NOMARSHAL/
607617
next if not has_linux_headers and lines[0] =~ /kernel/
608618
if incremental then

src/analyses/pthreadBarriers.ml

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
(** Must have waited barriers for Pthread barriers ([pthreadBarriers]). *)
2+
3+
open GoblintCil
4+
open Analyses
5+
module LF = LibraryFunctions
6+
7+
module Spec =
8+
struct
9+
module Barriers = struct
10+
include SetDomain.ToppedSet (ValueDomain.Addr) (struct let topname = "All barriers" end)
11+
let name () = "mayBarriers"
12+
end
13+
14+
module MustBarriers = struct
15+
include Lattice.Reverse (Barriers)
16+
let name () = "mustBarriers"
17+
end
18+
19+
module Capacity = Queries.ID
20+
21+
include Analyses.IdentitySpec
22+
module V = VarinfoV
23+
24+
module TID = ThreadIdDomain.ThreadLifted
25+
26+
(* Tracking TID separately, as there is no type-safe way to get it from MCPATuple *)
27+
module MCPAPlusTID = struct
28+
include Printable.Prod (MCPAccess.A) (TID)
29+
let name () = "MCPAPlusTID"
30+
end
31+
32+
let part_access man: MCPAccess.A.t =
33+
Obj.obj (man.ask (PartAccess Point))
34+
35+
module Waiters = SetDomain.ToppedSet (MCPAPlusTID) (struct let topname = "All MHP" end)
36+
module Multiprocess = BoolDomain.MayBool
37+
module G = Lattice.Prod3 (Multiprocess) (Capacity) (Waiters)
38+
39+
let name () = "pthreadBarriers"
40+
41+
module MustObserved = MapDomain.MapTop_LiftBot (TID) (MustBarriers)
42+
module D = Lattice.Prod (Barriers) (MustObserved)
43+
44+
include Analyses.ValueContexts(D)
45+
46+
let possible_vinfos (a: Queries.ask) barrier =
47+
Queries.AD.to_var_may (a.f (Queries.MayPointTo barrier))
48+
49+
let special man (lval: lval option) (f:varinfo) (arglist:exp list) : D.t =
50+
let exists_k pred k waiters =
51+
let k_product elems =
52+
let rec doit k =
53+
if k = 1 then
54+
Seq.map (Seq.return) elems
55+
else
56+
let arg = doit (k-1) in
57+
Seq.map_product (Seq.cons) elems arg
58+
in
59+
doit k
60+
in
61+
Seq.exists pred (k_product (Waiters.to_seq waiters))
62+
in
63+
let desc = LF.find f in
64+
match desc.special arglist with
65+
| BarrierWait barrier ->
66+
let ask = Analyses.ask_of_man man in
67+
let may, must = man.local in
68+
let mcpa = part_access man in
69+
let tid = ThreadId.get_current ask in
70+
let barriers = possible_vinfos ask barrier in
71+
let handle_one b =
72+
try
73+
man.sideg b (Multiprocess.bot (), Capacity.bot (), Waiters.singleton (mcpa, tid));
74+
let addr = ValueDomain.Addr.of_var b in
75+
let (multiprocess, capacity, waiters) = man.global b in
76+
let may = Barriers.add addr may in
77+
if multiprocess then
78+
(may, must)
79+
else
80+
let relevant_waiters = Waiters.filter (fun (othermcpa, _) -> MCPAccess.A.may_race mcpa othermcpa) waiters in
81+
if Waiters.exists (fun (t,_) -> MCPAccess.A.may_race t t) relevant_waiters then
82+
(may, must)
83+
else
84+
match capacity with
85+
| `Top | `Bot -> (may, must)
86+
| `Lifted c ->
87+
let count = Waiters.cardinal relevant_waiters in
88+
(* Add 1 as the thread calling wait at the moment will not be MHP with itself *)
89+
let min_cap = (BatOption.default Z.zero (Capacity.I.minimal c)) in
90+
if Z.leq min_cap Z.one then
91+
(may, must)
92+
else if Z.geq (Z.of_int (count + 1)) min_cap then
93+
(* This is quite a cute problem: Do (min_cap-1) elements exist in the set such that
94+
MHP is pairwise true? This solution is a sledgehammer, there should be something much
95+
better algorithmically (beyond just laziness)
96+
Simmo: This sounds like looking for a (min_cap - 1)-clique in the MHP graph with the set of nodes.
97+
That seems to be exponential in min_cap though: https://en.wikipedia.org/wiki/Clique_problem#Cliques_of_fixed_size.
98+
*)
99+
let must =
100+
let waiters = Waiters.elements relevant_waiters in
101+
let min_cap = Z.to_int min_cap in
102+
let can_proceed_pred seq =
103+
let rec doit seq = match Seq.uncons seq with
104+
| None -> true
105+
| Some ((h,_), t) -> Seq.for_all (fun (x,_) -> MCPAccess.A.may_race h x) t && (doit [@tailcall]) t
106+
in
107+
doit seq
108+
in
109+
let can_proceed = exists_k can_proceed_pred (min_cap - 1) relevant_waiters in
110+
if not can_proceed then raise Analyses.Deadcode;
111+
(* limit to this case to avoid having to construct all permutations above *)
112+
if BatList.compare_length_with waiters (min_cap - 1) = 0 then
113+
List.fold_left (fun acc (_,tid) ->
114+
let curr = MustObserved.find tid acc in
115+
let must' = MustObserved.add tid (Barriers.add addr curr) acc in
116+
must'
117+
) must waiters
118+
else
119+
must
120+
in
121+
(may, must)
122+
else
123+
raise Analyses.Deadcode;
124+
with Analyses.Deadcode -> D.bot ()
125+
in
126+
let (may, must) = List.fold_left (fun acc b-> D.join acc (handle_one b)) (D.bot ()) barriers in
127+
if Barriers.is_empty may then raise Analyses.Deadcode;
128+
(may, must)
129+
| BarrierInit { barrier; attr; count } ->
130+
let multiprocess = not @@ Queries.AD.is_null @@ man.ask (Queries.MayPointTo attr) in
131+
if multiprocess then M.warn "Barrier initialized with a non-NULL attr argument. Handled as if PTHREAD_PROCESS_SHARED potentially set.";
132+
let count = man.ask (Queries.EvalInt count) in
133+
let publish_one b = man.sideg b (multiprocess, count, Waiters.bot ()) in
134+
let barriers = possible_vinfos (Analyses.ask_of_man man) barrier in
135+
List.iter publish_one barriers;
136+
man.local
137+
| _ -> man.local
138+
139+
let startstate v = (Barriers.empty (), MustObserved.empty ())
140+
let threadenter man ~multiple lval f args = [man.local]
141+
let exitstate v = (Barriers.empty (), MustObserved.empty ())
142+
143+
module A =
144+
struct
145+
include Lattice.Prod3 (Barriers) (MustObserved) (TID)
146+
let name () = "barriers"
147+
let may_race (may_await_t1, must_observed_by_t1, t1) (may_await_t2, must_observed_by_t2, t2) =
148+
let observed_from_t2 = MustObserved.find t2 must_observed_by_t1 in
149+
if not (Barriers.subset observed_from_t2 may_await_t2) then
150+
false
151+
else
152+
let observed_from_t1 = MustObserved.find t1 must_observed_by_t2 in
153+
Barriers.subset observed_from_t1 may_await_t1
154+
let should_print f = true
155+
end
156+
157+
let access man (a: Queries.access) =
158+
let (may,must) = man.local in
159+
let mhp = MHP.current (Analyses.ask_of_man man) in
160+
(may, must, mhp.tid)
161+
end
162+
163+
let _ =
164+
MCP.register_analysis (module Spec : MCPSpec)

src/cdomains/mHP.ml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,8 @@ let may_happen_in_parallel one two =
9292
else
9393
true
9494
| _ -> true
95+
96+
let is_unique_thread mhp =
97+
match mhp.tid with
98+
| `Lifted tid -> TID.is_unique tid
99+
| _ -> false

src/domain/disjointDomain.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ struct
117117
add e acc
118118
) (empty ()) es
119119
let elements m = fold List.cons m [] (* no intermediate per-bucket lists *)
120+
let to_seq m = fold Seq.cons m Seq.empty (* Less efficient than possible, eagerly folds over set *)
120121
let map f m = fold (fun e acc ->
121122
add (f e) acc
122123
) m (empty ()) (* no intermediate lists *)
@@ -323,6 +324,7 @@ struct
323324
add e acc
324325
) (empty ()) es
325326
let elements m = fold List.cons m [] (* no intermediate per-bucket lists *)
327+
let to_seq m = fold Seq.cons m Seq.empty (* Less efficient than possible, eagerly folds over set *)
326328
let map f s = fold (fun e acc ->
327329
add (f e) acc
328330
) s (empty ()) (* no intermediate lists *)

src/domain/setDomain.ml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ sig
9595
On set abstractions this lists only canonical elements,
9696
not all subsumed elements. *)
9797

98+
val to_seq: t -> elt Seq.t
99+
(** See {!Set.S.to_seq}.
100+
101+
On set abstractions this lists only canonical elements,
102+
not all subsumed elements. *)
103+
98104
val of_list: elt list -> t
99105

100106
val min_elt: t -> elt
@@ -332,6 +338,7 @@ struct
332338
let exists f = schema_default true (S.exists f)
333339
let filter f = schema (fun t -> `Lifted (S.filter f t)) "filter on `Top"
334340
let elements = schema S.elements "elements on `Top"
341+
let to_seq = schema S.to_seq "to_seq on `Top"
335342
let of_list xs = `Lifted (S.of_list xs)
336343
let cardinal = schema S.cardinal "cardinal on `Top"
337344
let min_elt = schema S.min_elt "min_elt on `Top"
@@ -471,6 +478,7 @@ struct
471478
let mem e e' = E.leq e e'
472479
let choose e = e
473480
let elements e = [e]
481+
let to_seq e = Seq.return e
474482
let remove e e' =
475483
if E.leq e' e then
476484
E.bot () (* NB! strong removal *)

src/goblint_lib.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ module BasePriv = BasePriv
128128
module RelationPriv = RelationPriv
129129
module ThreadEscape = ThreadEscape
130130
module PthreadSignals = PthreadSignals
131+
module PthreadBarriers = PthreadBarriers
131132
module ExtractPthread = ExtractPthread
132133

133134
(** {2 Longjmp}

0 commit comments

Comments
 (0)