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

Analysis of pthread_barriers #1652

Draft
wants to merge 17 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ jobs:
- name: Install dependencies
run: opam install . --deps-only --locked --with-test

- name: Install os gem for operating system detection
run: sudo gem install os

- name: Install coverage dependencies
run: opam install bisect_ppx

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ jobs:
- name: Install dependencies
run: opam install . --deps-only --locked --with-doc

- name: Install os gem for operating system detection
run: sudo gem install os

- name: Build API docs
run: opam exec -- dune build @doc

Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/locked.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ jobs:
- name: Install dependencies
run: opam install . --deps-only --locked --with-test

- name: Install os gem for operating system detection
run: sudo gem install os

- name: Build
run: ./make.sh nat

Expand Down Expand Up @@ -100,6 +103,9 @@ jobs:
- name: Install dependencies
run: opam install . --deps-only --locked --with-test

- name: Install os gem for operating system detection
run: sudo gem install os

- name: Build
run: ./make.sh nat

Expand Down Expand Up @@ -142,6 +148,9 @@ jobs:
- name: Install dependencies
run: opam install . --deps-only --locked

- name: Install os gem for operating system detection
run: sudo gem install os

- name: Setup Gobview
run: ./make.sh setup_gobview

Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/unlocked.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ jobs:

- name: Install dependencies
run: opam install . --deps-only --with-test

- name: Install os gem for operating system detection
run: sudo gem install os

- name: Install Apron dependencies
if: ${{ matrix.apron }}
Expand Down Expand Up @@ -118,6 +121,9 @@ jobs:
- name: Install dependencies
run: opam install . --deps-only --with-test

- name: Install os gem for operating system detection
run: sudo gem install os

- name: Install Apron dependencies
run: |
opam depext apron
Expand Down
3 changes: 2 additions & 1 deletion make.sh
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ rule() {
;; setup)
echo "Make sure you have the following installed: opam >= 2.0.0, git, patch, m4, autoconf, libgmp-dev, libmpfr-dev, pkg-config"
echo "For the --html output you also need: javac, ant, dot (graphviz)"
echo "For running the regression tests you also need: ruby, gem, curl"
echo "For running the regression tests you also need: ruby, gem, curl, and the `os` gem"
echo "For reference see ./Dockerfile or ./scripts/travis-ci.sh"
opam_setup
;; dev)
Expand All @@ -90,6 +90,7 @@ rule() {
# Use `git commit -n` to temporarily bypass the hook if necessary.
echo "Installing gem parallel (not needed for ./scripts/update_suite.rb -s)"
sudo gem install parallel
sudo gem install os
;; headers)
curl -L -O https://github.com/goblint/linux-headers/archive/master.tar.gz
tar xf master.tar.gz && rm master.tar.gz
Expand Down
4 changes: 4 additions & 0 deletions scripts/update_suite.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

# gobopt environment variable can be used to override goblint defaults and PARAMs

require 'os'
require 'find'
require 'fileutils'
require 'timeout'
Expand Down Expand Up @@ -575,6 +576,8 @@ def run ()
end
end

mac = OS.mac?

#processing the file information
projects = []
project_ids = Set.new
Expand Down Expand Up @@ -603,6 +606,7 @@ def run ()
lines = IO.readlines(path, :encoding => "UTF-8")

next if not future and only.nil? and lines[0] =~ /SKIP/
next if mac and lines[0] =~ /NOMAC/
next if marshal and lines[0] =~ /NOMARSHAL/
next if not has_linux_headers and lines[0] =~ /kernel/
if incremental then
Expand Down
156 changes: 156 additions & 0 deletions src/analyses/pthreadBarriers.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
(** Must have waited barriers for Pthread barriers ([pthreadBarriers]). *)

open GoblintCil
open Analyses
module LF = LibraryFunctions

module Spec =
struct
module Barriers = struct
include SetDomain.ToppedSet (ValueDomain.Addr) (struct let topname = "All barriers" end)
let name () = "mayBarriers"
end

module MustBarriers = struct
include Lattice.Reverse (Barriers)
let name () = "mustBarriers"
end

module Capacity = Queries.ID

include Analyses.IdentitySpec
module V = VarinfoV

module TID = ThreadIdDomain.ThreadLifted

module MHPplusLock = struct
module Locks = LockDomain.MustLockset

include Printable.Prod (MHP) (Locks)
let name () = "MHPplusLock"

let mhp (mhp1, l1) (mhp2, l2) =
MHP.may_happen_in_parallel mhp1 mhp2 && Locks.is_empty (Locks.inter l1 l2)

let tid ((mhp:MHP.t), _) = mhp.tid

let may_be_non_unique_thread (mhp, _) = MHP.may_be_non_unique_thread mhp
end

module Waiters = SetDomain.ToppedSet (MHPplusLock) (struct let topname = "All MHP" end)
module Multiprocess = BoolDomain.MayBool
module G = Lattice.Prod3 (Multiprocess) (Capacity) (Waiters)

let name () = "pthreadBarriers"

module MustObserved = MapDomain.MapTop_LiftBot (TID) (MustBarriers)
module D = Lattice.Prod (Barriers) (MustObserved)

include Analyses.ValueContexts(D)

let possible_vinfos (a: Queries.ask) barrier =
Queries.AD.to_var_may (a.f (Queries.MayPointTo barrier))

let special man (lval: lval option) (f:varinfo) (arglist:exp list) : D.t =
let desc = LF.find f in
match desc.special arglist with
| BarrierWait barrier ->
let ask = Analyses.ask_of_man man in
let may, must = man.local in
let barriers = possible_vinfos ask barrier in
let mhp = MHP.current ask in
let handle_one b =
try
let locks = man.ask (Queries.MustLockset) in
man.sideg b (Multiprocess.bot (), Capacity.bot (), Waiters.singleton (mhp, locks));
let addr = ValueDomain.Addr.of_var b in
let (multiprocess, capacity, waiters) = man.global b in
let may = Barriers.add addr may in
if multiprocess then
(may, must)
else
let relevant_waiters = Waiters.filter (fun other -> MHPplusLock.mhp (mhp, locks) other) waiters in
if Waiters.exists MHPplusLock.may_be_non_unique_thread relevant_waiters then
(may, must)
else
match capacity with
| `Top | `Bot -> (may, must)
| `Lifted c ->
let count = Waiters.cardinal relevant_waiters in
(* Add 1 as the thread calling wait at the moment will not be MHP with itself *)
let min_cap = (BatOption.default Z.zero (Capacity.I.minimal c)) in
if Z.leq min_cap Z.one then
(may, must)
else if Z.geq (Z.of_int (count + 1)) min_cap then
(* This is quite a cute problem: Do (min_cap-1) elements exist in the set such that
MHP is pairwise true? This solution is a sledgehammer, there should be something much
better algorithmically (beyond just laziness) *)
let must =
let waiters = Waiters.elements relevant_waiters in
let min_cap = Z.to_int min_cap in
let lists = List.init (min_cap - 1) (fun _ -> waiters) in
let candidates = BatList.n_cartesian_product lists in
let can_proceed = List.exists (fun candidate ->
let rec do_it = function
| [] -> true
| x::xs -> List.for_all (fun y -> MHPplusLock.mhp x y) xs && do_it xs
in
do_it candidate
) candidates
in
if not can_proceed then raise Analyses.Deadcode;
(* limit to this case to avoid having to construct all permutations above *)
if List.length waiters = min_cap - 1 then

Check warning

Code scanning / Semgrep OSS

computing list length is inefficient for length comparison, use compare_length_with instead Warning

computing list length is inefficient for length comparison, use compare_length_with instead
List.fold_left (fun acc elem ->
let tid = MHPplusLock.tid elem in
let curr = MustObserved.find tid acc in
let must' = MustObserved.add tid (Barriers.add addr curr) acc in
must'
) must waiters
else
must
in
(may, must)
else
raise Analyses.Deadcode;
with Analyses.Deadcode -> D.bot ()
in
let (may, must) = List.fold_left (fun acc b-> D.join acc (handle_one b)) (D.bot ()) barriers in
if Barriers.is_empty may then raise Analyses.Deadcode;
(may, must)
| BarrierInit { barrier; attr; count } ->
let multitprocess = not @@ Queries.AD.is_null @@ man.ask (Queries.MayPointTo attr) in
if multitprocess then M.warn "Barrier initialized with a non-NULL attr argument. Handled as if PTHREAD_PROCESS_SHARED potentially set.";
let count = man.ask (Queries.EvalInt count) in
let publish_one b = man.sideg b (multitprocess, count, Waiters.bot ()) in
let barriers = possible_vinfos (Analyses.ask_of_man man) barrier in
List.iter publish_one barriers;
man.local
| _ -> man.local

let startstate v = (Barriers.empty (), MustObserved.empty ())
let threadenter man ~multiple lval f args = [man.local]
let exitstate v = (Barriers.empty (), MustObserved.empty ())

module A =
struct
include Lattice.Prod3 (Barriers) (MustObserved) (TID)
let name () = "barriers"
let may_race (may_await_t1, must_observed_by_t1, t1) (may_await_t2, must_observed_by_t2, t2) =
let observed_from_t2 = MustObserved.find t2 must_observed_by_t1 in
if not (Barriers.subset observed_from_t2 may_await_t2) then
false
else
let observed_from_t1 = MustObserved.find t1 must_observed_by_t2 in
Barriers.subset observed_from_t1 may_await_t1
let should_print f = true
end

let access man (a: Queries.access) =
let (may,must) = man.local in
let mhp = MHP.current (Analyses.ask_of_man man) in
(may, must, mhp.tid)
end

let _ =
MCP.register_analysis (module Spec : MCPSpec)
5 changes: 5 additions & 0 deletions src/cdomains/mHP.ml
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,8 @@ let may_happen_in_parallel one two =
else
true
| _ -> true

let may_be_non_unique_thread mhp =
match mhp.tid with
| `Lifted tid -> not (TID.is_unique tid)
| _ -> true
1 change: 1 addition & 0 deletions src/goblint_lib.ml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ module BasePriv = BasePriv
module RelationPriv = RelationPriv
module ThreadEscape = ThreadEscape
module PthreadSignals = PthreadSignals
module PthreadBarriers = PthreadBarriers
module ExtractPthread = ExtractPthread

(** {2 Longjmp}
Expand Down
2 changes: 2 additions & 0 deletions src/util/library/libraryDesc.ml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ type special =
| SemDestroy of Cil.exp
| Wait of { cond: Cil.exp; mutex: Cil.exp; }
| TimedWait of { cond: Cil.exp; mutex: Cil.exp; abstime: Cil.exp; (** Unused *) }
| BarrierWait of Cil.exp
| BarrierInit of { barrier: Cil.exp; attr:Cil.exp; count: Cil.exp; }
| Math of { fun_args: math; }
| Memset of { dest: Cil.exp; ch: Cil.exp; count: Cil.exp; }
| Bzero of { dest: Cil.exp; count: Cil.exp; }
Expand Down
2 changes: 2 additions & 0 deletions src/util/library/libraryFunctions.ml
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,8 @@ let pthread_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[
("pthread_getspecific", unknown ~attrs:[InvalidateGlobals] [drop "key" []]);
("pthread_key_create", unknown [drop "key" [w]; drop "destructor" [s]]);
("pthread_key_delete", unknown [drop "key" [f]]);
("pthread_barrier_init", special [__ "barrier" []; __ "attr" []; __ "count" []] @@ fun barrier attr count -> BarrierInit {barrier; attr; count});
("pthread_barrier_wait", special [__ "barrier" []] @@ fun barrier -> BarrierWait barrier);
("pthread_cancel", unknown [drop "thread" []]);
("pthread_testcancel", unknown []);
("pthread_setcancelstate", unknown [drop "state" []; drop "oldstate" [w]]);
Expand Down
26 changes: 26 additions & 0 deletions tests/regression/82-barrier/01-simple.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// NOMAC PARAM: --set ana.activated[+] 'pthreadBarriers'
#include<pthread.h>
#include<stdio.h>
#include<unistd.h>

int g;

pthread_barrier_t barrier;


int main(int argc, char const *argv[])
{
int top;
int i = 0;

pthread_barrier_init(&barrier, NULL, 2);

if(top) {
pthread_barrier_wait(&barrier);
// Unreachable
i = 1;
}

__goblint_check(i == 0);
return 0;
}
45 changes: 45 additions & 0 deletions tests/regression/82-barrier/02-more.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// NOMAC PARAM: --set ana.activated[+] 'pthreadBarriers'
#include<pthread.h>
#include<stdio.h>
#include<unistd.h>

int g;

pthread_barrier_t barrier;

void* f2(void* ptr) {
pthread_barrier_wait(&barrier);
return NULL;
}

void* f1(void* ptr) {
pthread_barrier_wait(&barrier);

// This is past the barrier, so it will not be reached
pthread_t t2;
pthread_create(&t2,NULL,f2,NULL);

return NULL;
}

int main(int argc, char const *argv[])
{
int top;
int i = 0;

pthread_barrier_init(&barrier, NULL, 3);

pthread_t t1;
pthread_create(&t1,NULL,f1,NULL);

if(top) {
pthread_barrier_wait(&barrier);
// Unreachable
i = 1;
}

__goblint_check(i == 0);


return 0;
}
Loading
Loading