Skip to content

Commit fec2876

Browse files
authored
Merge pull request #1216 from goblint/priv-atomic
Add some hacky atomic privatizations
2 parents 155dcbe + c0da7a7 commit fec2876

30 files changed

+1058
-90
lines changed

docs/user-guide/annotating.md

+1
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,4 @@ Include `goblint.h` when using these.
6666
* `__goblint_assume_join(id)` is like `pthread_join(id, NULL)`, but considers the given thread IDs must-joined even if Goblint cannot, e.g. due to non-uniqueness.
6767
Notably, this annotation can be used after a threads joining loop to make the assumption that the loop correctly joined all those threads.
6868
_Misuse of this annotation can cause unsoundness._
69+
* `__goblint_globalize(ptr)` forces all data potentially pointed to by `ptr` to be treated as global by simulating it escaping the thread.

lib/goblint/runtime/include/goblint.h

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ void __goblint_assume(int exp);
33
void __goblint_assert(int exp);
44

55
void __goblint_assume_join(/* pthread_t thread */); // undeclared argument to avoid pthread.h interfering with Linux kernel headers
6+
void __goblint_globalize(void *ptr);
67

78
void __goblint_split_begin(int exp);
89
void __goblint_split_end(int exp);

src/analyses/apron/relationPriv.apron.ml

+271-74
Large diffs are not rendered by default.

src/analyses/basePriv.ml

+18-6
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,8 @@ module type PerGlobalPrivParam =
634634
sig
635635
(** Whether to also check unprotectedness by reads for extra precision. *)
636636
val check_read_unprotected: bool
637+
638+
include AtomicParam
637639
end
638640

639641
(** Protection-Based Reading. *)
@@ -671,29 +673,35 @@ struct
671673

672674
let startstate () = P.empty ()
673675

674-
let read_global ask getg (st: BaseComponents (D).t) x =
676+
let read_global (ask: Queries.ask) getg (st: BaseComponents (D).t) x =
675677
if P.mem x st.priv then
676678
CPA.find x st.cpa
679+
else if Param.handle_atomic && ask.f MustBeAtomic then
680+
VD.join (CPA.find x st.cpa) (getg (V.unprotected x)) (* Account for previous unpublished unprotected writes in current atomic section. *)
677681
else if is_unprotected ask x then
678682
getg (V.unprotected x) (* CPA unnecessary because all values in GUnprot anyway *)
679683
else
680684
VD.join (CPA.find x st.cpa) (getg (V.protected x))
681685

682-
let write_global ?(invariant=false) ask getg sideg (st: BaseComponents (D).t) x v =
686+
let write_global ?(invariant=false) (ask: Queries.ask) getg sideg (st: BaseComponents (D).t) x v =
683687
if not invariant then (
684-
sideg (V.unprotected x) v;
688+
if not (Param.handle_atomic && ask.f MustBeAtomic) then
689+
sideg (V.unprotected x) v; (* Delay publishing unprotected write in the atomic section. *)
685690
if !earlyglobs then (* earlyglobs workaround for 13/60 *)
686691
sideg (V.protected x) v
687692
(* Unlock after invariant will still side effect refined value (if protected) from CPA, because cannot distinguish from non-invariant write since W is implicit. *)
688693
);
689-
if is_unprotected ask x then
694+
if Param.handle_atomic && ask.f MustBeAtomic then
695+
{st with cpa = CPA.add x v st.cpa; priv = P.add x st.priv} (* Keep write local as if it were protected by the atomic section. *)
696+
else if is_unprotected ask x then
690697
st
691698
else
692699
{st with cpa = CPA.add x v st.cpa; priv = P.add x st.priv}
693700

694701
let lock ask getg st m = st
695702

696703
let unlock ask getg sideg (st: BaseComponents (D).t) m =
704+
let atomic = Param.handle_atomic && LockDomain.Addr.equal m (LockDomain.Addr.of_var LibraryFunctions.verifier_atomic_var) in
697705
(* TODO: what about G_m globals in cpa that weren't actually written? *)
698706
CPA.fold (fun x v (st: BaseComponents (D).t) ->
699707
if is_protected_by ask m x then ( (* is_in_Gm *)
@@ -702,6 +710,8 @@ struct
702710
then inner unlock shouldn't yet publish. *)
703711
if not Param.check_read_unprotected || is_unprotected_without ask ~write:false x m then
704712
sideg (V.protected x) v;
713+
if atomic then
714+
sideg (V.unprotected x) v; (* Publish delayed unprotected write as if it were protected by the atomic section. *)
705715

706716
if is_unprotected_without ask x m then (* is_in_V' *)
707717
{st with cpa = CPA.remove x st.cpa; priv = P.remove x st.priv}
@@ -1779,8 +1789,10 @@ let priv_module: (module S) Lazy.t =
17791789
| "mutex-oplus" -> (module PerMutexOplusPriv)
17801790
| "mutex-meet" -> (module PerMutexMeetPriv)
17811791
| "mutex-meet-tid" -> (module PerMutexMeetTIDPriv (ThreadDigest))
1782-
| "protection" -> (module ProtectionBasedPriv (struct let check_read_unprotected = false end))
1783-
| "protection-read" -> (module ProtectionBasedPriv (struct let check_read_unprotected = true end))
1792+
| "protection" -> (module ProtectionBasedPriv (struct let check_read_unprotected = false let handle_atomic = false end))
1793+
| "protection-atomic" -> (module ProtectionBasedPriv (struct let check_read_unprotected = false let handle_atomic = true end)) (* experimental *)
1794+
| "protection-read" -> (module ProtectionBasedPriv (struct let check_read_unprotected = true let handle_atomic = false end))
1795+
| "protection-read-atomic" -> (module ProtectionBasedPriv (struct let check_read_unprotected = true let handle_atomic = true end)) (* experimental *)
17841796
| "mine" -> (module MinePriv)
17851797
| "mine-nothread" -> (module MineNoThreadPriv)
17861798
| "mine-W" -> (module MineWPriv (struct let side_effect_global_init = true end))

src/analyses/commonPriv.ml

+22-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@ module Q = Queries
99
module IdxDom = ValueDomain.IndexDomain
1010
module VD = BaseDomain.VD
1111

12+
module type AtomicParam =
13+
sig
14+
val handle_atomic: bool
15+
(** Whether to handle SV-COMP atomic blocks (experimental). *)
16+
end
17+
18+
module NoAtomic: AtomicParam =
19+
struct
20+
let handle_atomic = false
21+
end
22+
1223
module ConfCheck =
1324
struct
1425
module RequireMutexActivatedInit =
@@ -110,7 +121,11 @@ end
110121

111122
module Locksets =
112123
struct
113-
module Lock = LockDomain.Addr
124+
module Lock =
125+
struct
126+
include LockDomain.Addr
127+
let name () = "lock"
128+
end
114129

115130
module Lockset = SetDomain.ToppedSet (Lock) (struct let topname = "All locks" end)
116131

@@ -206,7 +221,7 @@ struct
206221

207222
module LLock =
208223
struct
209-
include Printable.Either (Locksets.Lock) (CilType.Varinfo)
224+
include Printable.Either (Locksets.Lock) (struct include CilType.Varinfo let name () = "global" end)
210225
let mutex m = `Left m
211226
let global x = `Right x
212227
end
@@ -218,7 +233,11 @@ struct
218233
end
219234

220235
(* Map from locks to last written values thread-locally *)
221-
module L = MapDomain.MapBot_LiftTop (LLock) (LD)
236+
module L =
237+
struct
238+
include MapDomain.MapBot_LiftTop (LLock) (LD)
239+
let name () = "L"
240+
end
222241
module GMutex = MapDomain.MapBot_LiftTop (Digest) (LD)
223242
module GThread = Lattice.Prod (LMust) (L)
224243

src/analyses/mutexAnalysis.ml

+1-1
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ struct
229229
let mutex_lockset = Lockset.export_locks @@ Lockset.singleton (mutex, true) in
230230
let protecting = protecting ~write protection v in
231231
(* TODO: unsound in 29/24, why did we do this before? *)
232-
(* if LockDomain.Addr.equal mutex verifier_atomic then
232+
(* if LockDomain.Addr.equal mutex (LockDomain.Addr.of_var LF.verifier_atomic_var) then
233233
true
234234
else *)
235235
Mutexes.leq mutex_lockset protecting

src/analyses/threadEscape.ml

+3
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ struct
150150
let special ctx (lval: lval option) (f:varinfo) (args:exp list) : D.t =
151151
let desc = LibraryFunctions.find f in
152152
match desc.special args, f.vname, args with
153+
| Globalize ptr, _, _ ->
154+
let escaped = escape_rval ctx (Analyses.ask_of_ctx ctx) ptr in
155+
D.join ctx.local escaped
153156
| _, "pthread_setspecific" , [key; pt_value] ->
154157
let escaped = escape_rval ctx (Analyses.ask_of_ctx ctx) pt_value in
155158
D.join ctx.local escaped

src/config/options.schema.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -747,7 +747,7 @@
747747
"description":
748748
"Which privatization to use? none/mutex-oplus/mutex-meet/mutex-meet-tid/protection/protection-read/mine/mine-nothread/mine-W/mine-W-noinit/lock/write/write+lock",
749749
"type": "string",
750-
"enum": ["none", "mutex-oplus", "mutex-meet", "protection", "protection-read", "mine", "mine-nothread", "mine-W", "mine-W-noinit", "lock", "write", "write+lock","mutex-meet-tid"],
750+
"enum": ["none", "mutex-oplus", "mutex-meet", "protection", "protection-atomic", "protection-read", "protection-read-atomic", "mine", "mine-nothread", "mine-W", "mine-W-noinit", "lock", "write", "write+lock","mutex-meet-tid"],
751751
"default": "protection-read"
752752
},
753753
"priv": {
@@ -901,7 +901,7 @@
901901
"description":
902902
"Which relation privatization to use? top/protection/protection-path/mutex-meet/mutex-meet-tid/mutex-meet-tid-cluster12/mutex-meet-tid-cluster2/mutex-meet-tid-cluster-max/mutex-meet-tid-cluster-power",
903903
"type": "string",
904-
"enum": ["top", "protection", "protection-path", "mutex-meet", "mutex-meet-tid", "mutex-meet-tid-cluster12", "mutex-meet-tid-cluster2", "mutex-meet-tid-cluster-max", "mutex-meet-tid-cluster-power"],
904+
"enum": ["top", "protection", "protection-path", "mutex-meet", "mutex-meet-atomic", "mutex-meet-tid", "mutex-meet-tid-atomic", "mutex-meet-tid-cluster12", "mutex-meet-tid-cluster2", "mutex-meet-tid-cluster-max", "mutex-meet-tid-cluster-power"],
905905
"default": "mutex-meet"
906906
},
907907
"priv": {

src/util/library/libraryDesc.ml

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ type special =
5656
| ThreadCreate of { thread: Cil.exp; start_routine: Cil.exp; arg: Cil.exp; multiple: bool }
5757
| ThreadJoin of { thread: Cil.exp; ret_var: Cil.exp; }
5858
| ThreadExit of { ret_val: Cil.exp; }
59+
| Globalize of Cil.exp
5960
| Signal of Cil.exp
6061
| Broadcast of Cil.exp
6162
| MutexAttrSetType of { attr:Cil.exp; typ: Cil.exp; }

src/util/library/libraryFunctions.ml

+1
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,7 @@ let goblint_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[
759759
("__goblint_check", special [__ "exp" []] @@ fun exp -> Assert { exp; check = true; refine = false });
760760
("__goblint_assume", special [__ "exp" []] @@ fun exp -> Assert { exp; check = false; refine = true });
761761
("__goblint_assert", special [__ "exp" []] @@ fun exp -> Assert { exp; check = true; refine = get_bool "sem.assert.refine" });
762+
("__goblint_globalize", special [__ "ptr" []] @@ fun ptr -> Globalize ptr);
762763
("__goblint_split_begin", unknown [drop "exp" []]);
763764
("__goblint_split_end", unknown [drop "exp" []]);
764765
("__goblint_bounded", special [__ "exp"[]] @@ fun exp -> Bounded { exp });
+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// PARAM: --enable ana.sv-comp.functions
2+
/*-----------------------------------------------------------------------------
3+
* mutex.c - Concurrent program using locking to access a shared variable
4+
*-----------------------------------------------------------------------------
5+
* Author: Frank Schüssele
6+
* Date: 2023-07-11
7+
*---------------------------------------------------------------------------*/
8+
#include <pthread.h>
9+
#include <goblint.h>
10+
11+
extern void __VERIFIER_atomic_begin();
12+
extern void __VERIFIER_atomic_end();
13+
14+
int used;
15+
pthread_mutex_t m;
16+
17+
void* producer()
18+
{
19+
while (1) {
20+
pthread_mutex_lock(&m);
21+
used++;
22+
used--;
23+
pthread_mutex_unlock(&m);
24+
}
25+
26+
return 0;
27+
}
28+
29+
int main()
30+
{
31+
pthread_t tid;
32+
33+
pthread_mutex_init(&m, 0);
34+
pthread_create(&tid, 0, producer, 0);
35+
36+
pthread_mutex_lock(&m);
37+
__goblint_check(used == 0);
38+
pthread_mutex_unlock(&m);
39+
40+
pthread_mutex_destroy(&m);
41+
return 0;
42+
}

tests/regression/29-svcomp/16-atomic_priv.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// PARAM: --enable ana.sv-comp.functions
1+
// PARAM: --enable ana.sv-comp.functions --set ana.base.privatization protection-atomic
22
#include <pthread.h>
33
#include <goblint.h>
44

@@ -21,7 +21,7 @@ void *t_fun(void *arg) {
2121
int main(void) {
2222
pthread_t id;
2323
pthread_create(&id, NULL, t_fun, NULL);
24-
__goblint_check(myglobal == 5); // TODO
24+
__goblint_check(myglobal == 5);
2525
__VERIFIER_atomic_begin();
2626
__goblint_check(myglobal == 5);
2727
__VERIFIER_atomic_end();

tests/regression/29-svcomp/18-atomic_fun_priv.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// PARAM: --enable ana.sv-comp.functions
1+
// PARAM: --enable ana.sv-comp.functions --set ana.base.privatization protection-atomic
22
#include <pthread.h>
33
#include <goblint.h>
44

@@ -21,7 +21,7 @@ void *t_fun(void *arg) {
2121
int main(void) {
2222
pthread_t id;
2323
pthread_create(&id, NULL, t_fun, NULL);
24-
__goblint_check(myglobal == 5); // TODO
24+
__goblint_check(myglobal == 5);
2525
__VERIFIER_atomic_begin();
2626
__goblint_check(myglobal == 5);
2727
__VERIFIER_atomic_end();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// PARAM: --enable ana.sv-comp.functions --set ana.activated[+] apron --set ana.relation.privatization mutex-meet-atomic --set ana.base.privatization none
2+
#include <pthread.h>
3+
#include <goblint.h>
4+
5+
extern void __VERIFIER_atomic_begin();
6+
extern void __VERIFIER_atomic_end();
7+
8+
int myglobal = 5;
9+
10+
void *t_fun(void *arg) {
11+
__VERIFIER_atomic_begin();
12+
__goblint_check(myglobal == 5);
13+
myglobal++;
14+
__goblint_check(myglobal == 6);
15+
myglobal--;
16+
__goblint_check(myglobal == 5);
17+
__VERIFIER_atomic_end();
18+
return NULL;
19+
}
20+
21+
int main(void) {
22+
pthread_t id;
23+
pthread_create(&id, NULL, t_fun, NULL);
24+
__goblint_check(myglobal == 5);
25+
__VERIFIER_atomic_begin();
26+
__goblint_check(myglobal == 5);
27+
__VERIFIER_atomic_end();
28+
pthread_join (id, NULL);
29+
return 0;
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// PARAM: --enable ana.sv-comp.functions --set ana.activated[+] apron --set ana.relation.privatization mutex-meet-atomic --set ana.base.privatization none
2+
#include <pthread.h>
3+
#include <goblint.h>
4+
5+
int myglobal = 5;
6+
7+
// atomic by function name prefix
8+
void __VERIFIER_atomic_fun() {
9+
__goblint_check(myglobal == 5);
10+
myglobal++;
11+
__goblint_check(myglobal == 6);
12+
myglobal--;
13+
__goblint_check(myglobal == 5);
14+
}
15+
16+
void *t_fun(void *arg) {
17+
__VERIFIER_atomic_fun();
18+
return NULL;
19+
}
20+
21+
int main(void) {
22+
pthread_t id;
23+
pthread_create(&id, NULL, t_fun, NULL);
24+
__goblint_check(myglobal == 5);
25+
__VERIFIER_atomic_begin();
26+
__goblint_check(myglobal == 5);
27+
__VERIFIER_atomic_end();
28+
pthread_join (id, NULL);
29+
return 0;
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// PARAM: --enable ana.sv-comp.functions --set ana.activated[+] apron --set ana.relation.privatization mutex-meet-atomic --set ana.base.privatization none
2+
#include <pthread.h>
3+
#include <goblint.h>
4+
5+
extern void __VERIFIER_atomic_begin();
6+
extern void __VERIFIER_atomic_end();
7+
8+
int myglobal = 5;
9+
10+
void *t_fun(void *arg) {
11+
__VERIFIER_atomic_begin();
12+
__goblint_check(myglobal == 5); // TODO
13+
myglobal++;
14+
__goblint_check(myglobal == 6); // TODO
15+
__VERIFIER_atomic_end();
16+
return NULL;
17+
}
18+
19+
int main(void) {
20+
pthread_t id;
21+
pthread_create(&id, NULL, t_fun, NULL);
22+
__goblint_check(myglobal == 5); // UNKNOWN!
23+
__VERIFIER_atomic_begin();
24+
__goblint_check(myglobal == 5); // UNKNOWN!
25+
__VERIFIER_atomic_end();
26+
pthread_join (id, NULL);
27+
return 0;
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// PARAM: --enable ana.sv-comp.functions --set ana.activated[+] apron --set ana.relation.privatization mutex-meet-atomic --set ana.base.privatization none
2+
#include <pthread.h>
3+
#include <goblint.h>
4+
5+
extern void __VERIFIER_atomic_begin();
6+
extern void __VERIFIER_atomic_end();
7+
8+
int myglobal = 0;
9+
int myglobal2 = 0;
10+
int myglobal3 = 0;
11+
12+
void *t_fun(void *arg) {
13+
__VERIFIER_atomic_begin();
14+
myglobal2++;
15+
__VERIFIER_atomic_end();
16+
__VERIFIER_atomic_begin();
17+
myglobal++;
18+
__VERIFIER_atomic_end();
19+
return NULL;
20+
}
21+
22+
void *t2_fun(void *arg) {
23+
__VERIFIER_atomic_begin();
24+
myglobal3++;
25+
__VERIFIER_atomic_end();
26+
__VERIFIER_atomic_begin();
27+
myglobal++;
28+
__VERIFIER_atomic_end();
29+
return NULL;
30+
}
31+
32+
int main(void) {
33+
pthread_t id, id2;
34+
pthread_create(&id, NULL, t_fun, NULL);
35+
pthread_create(&id2, NULL, t2_fun, NULL);
36+
__goblint_check(myglobal == 2); // UNKNOWN!
37+
__VERIFIER_atomic_begin();
38+
__goblint_check(myglobal == 2); // UNKNOWN!
39+
__VERIFIER_atomic_end();
40+
pthread_join (id, NULL);
41+
pthread_join (id2, NULL);
42+
return 0;
43+
}

0 commit comments

Comments
 (0)