Skip to content

Commit

Permalink
Merge pull request #1552 from goblint/traces-vojdani
Browse files Browse the repository at this point in the history
Optimize `none` base privatization, add eager Vojdani privatization
  • Loading branch information
sim642 authored Dec 30, 2024
2 parents 91bbf20 + c6a2c9e commit ab54be8
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 50 deletions.
212 changes: 169 additions & 43 deletions src/analyses/basePriv.ml
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,12 @@ module DigestWrapper(Digest: Digest):PrivatizationWrapper = functor (GBase:Latt
end)


(* No Privatization *)
(** No Privatization. *)
module NonePriv: S =
struct
include NoFinalize

module G = BaseDomain.VD
module G = VD
module V = VarinfoV
module D = Lattice.Unit

Expand All @@ -117,63 +117,86 @@ struct
let lock ask getg st m = st
let unlock ask getg sideg st m = st

let escape ask getg sideg st escaped = st
let enter_multithreaded ask getg sideg st = st
let threadenter = old_threadenter
let threadspawn ask getg sideg st = st

let iter_sys_vars getg vq vf =
match vq with
| VarQuery.Global g -> vf g
| _ -> ()


let read_global ask getg (st: BaseComponents (D).t) x =
let read_global (ask: Queries.ask) getg (st: BaseComponents (D).t) x =
getg x

let write_global ?(invariant=false) ask getg sideg (st: BaseComponents (D).t) x v =
if invariant then (
(* Do not impose invariant, will not hold without privatization *)
if M.tracing then M.tracel "set" ~var:x.vname "update_one_addr: BAD! effect = '%B', or else is private! " (not invariant);
st
)
else (
(* Here, an effect should be generated, but we add it to the local
* state, waiting for the sync function to publish it. *)
(* Copied from MainFunctor.update_variable *)
if ((get_bool "exp.volatiles_are_top") && (is_always_unknown x)) then
{st with cpa = CPA.add x (VD.top ()) st.cpa}
let write_global ?(invariant=false) (ask: Queries.ask) getg sideg (st: BaseComponents (D).t) x v =
let v = (* Copied from MainFunctor.update_variable *)
if get_bool "exp.volatiles_are_top" && is_always_unknown x then (* TODO: why don't other privatizations do this? why in write_global, not read_global? why not in base directly? why not in other value analyses? *)
VD.top ()
else
{st with cpa = CPA.add x v st.cpa}
)
v
in
if not invariant then
sideg x v;
st

let sync ask getg sideg (st: BaseComponents (D).t) reason =
(* For each global variable, we create the side effect *)
let side_var (v: varinfo) (value) (st: BaseComponents (D).t) =
if M.tracing then M.traceli "globalize" ~var:v.vname "Tracing for %s" v.vname;
let res =
if is_global ask v then begin
if M.tracing then M.tracec "globalize" "Publishing its value: %a" VD.pretty value;
sideg v value;
{st with cpa = CPA.remove v st.cpa}
end else
st
in
if M.tracing then M.traceu "globalize" "Done!";
res
let branched_sync () =
(* required for branched thread creation *)
CPA.fold (fun x v (st: BaseComponents (D).t) ->
if is_global ask x then (
sideg x v;
{st with cpa = CPA.remove x st.cpa}
)
else
st
) st.cpa st
in
(* We fold over the local state, and side effect the globals *)
CPA.fold side_var st.cpa st
match reason with
| `Join when ConfCheck.branched_thread_creation () ->
branched_sync ()
| `JoinCall f when ConfCheck.branched_thread_creation_at_call ask f ->
branched_sync ()
| `Join
| `JoinCall _
| `Return
| `Normal
| `Init
| `Thread ->
st

let escape ask getg sideg (st: BaseComponents (D).t) escaped =
let cpa' = CPA.fold (fun x v acc ->
if EscapeDomain.EscapedVars.mem x escaped then (
sideg x v;
CPA.remove x acc
)
else
acc
) st.cpa st.cpa
in
{st with cpa = cpa'}

let enter_multithreaded ask getg sideg (st: BaseComponents (D).t) =
CPA.fold (fun x v (st: BaseComponents (D).t) ->
if is_global ask x then (
sideg x v;
{st with cpa = CPA.remove x st.cpa}
)
else
st
) st.cpa st

let threadenter ask st = st
let threadspawn ask get set st = st

let thread_join ?(force=false) ask get e st = st
let thread_return ask get set tid st = st

let iter_sys_vars getg vq vf =
match vq with
| VarQuery.Global g ->
vf g;
| _ -> ()

let invariant_global ask getg g =
ValueDomain.invariant_global getg g

let invariant_vars ask getg st = []
end


module PerMutexPrivBase =
struct
include NoFinalize
Expand Down Expand Up @@ -711,6 +734,108 @@ struct
end


(** Vojdani privatization with eager reading. *)
module VojdaniPriv: S =
struct
include NoFinalize
include ConfCheck.RequireMutexActivatedInit
open Protection

module D = Lattice.Unit
module G = VD
module V = VarinfoV

let startstate () = ()

let read_global (ask: Queries.ask) getg (st: BaseComponents (D).t) x =
if is_unprotected ask ~write:false x then
VD.join (CPA.find x st.cpa) (getg x)
else
CPA.find x st.cpa

let write_global ?(invariant=false) (ask: Queries.ask) getg sideg (st: BaseComponents (D).t) x v =
if not invariant then (
if is_unprotected ask ~write:false x then
sideg x v;
if !earlyglobs then (* earlyglobs workaround for 13/60 *)
sideg x v (* TODO: is this needed for anything? 13/60 doesn't work for other reasons *)
);
{st with cpa = CPA.add x v st.cpa}

let lock ask getg (st: BaseComponents (D).t) m =
let cpa' = CPA.mapi (fun x v ->
if is_protected_by ask ~write:false m x && is_unprotected ask ~write:false x then (* is_in_Gm *)
VD.join (CPA.find x st.cpa) (getg x)
else
v
) st.cpa
in
{st with cpa = cpa'}

let unlock ask getg sideg (st: BaseComponents (D).t) m =
CPA.iter (fun x v ->
if is_protected_by ask ~write:false m x then ( (* is_in_Gm *)
if is_unprotected_without ask ~write:false x m then (* is_in_V' *)
sideg x v
)
) st.cpa;
st

let sync ask getg sideg (st: BaseComponents (D).t) reason =
let branched_sync () =
(* required for branched thread creation *)
CPA.iter (fun x v ->
if is_global ask x && is_unprotected ask ~write:false x then
sideg x v
) st.cpa;
st
in
match reason with
| `Join when ConfCheck.branched_thread_creation () ->
branched_sync ()
| `JoinCall f when ConfCheck.branched_thread_creation_at_call ask f ->
branched_sync ()
| `Join
| `JoinCall _
| `Return
| `Normal
| `Init
| `Thread ->
st

let escape ask getg sideg (st: BaseComponents (D).t) escaped =
CPA.iter (fun x v ->
if EscapeDomain.EscapedVars.mem x escaped then
sideg x v
) st.cpa;
st

let enter_multithreaded ask getg sideg (st: BaseComponents (D).t) =
CPA.iter (fun x v ->
if is_global ask x then
sideg x v
) st.cpa;
st

let threadenter ask st = st
let threadspawn ask get set st = st

let thread_join ?(force=false) ask get e st = st
let thread_return ask get set tid st = st

let iter_sys_vars getg vq vf =
match vq with
| VarQuery.Global g ->
vf g;
| _ -> ()

let invariant_global ask getg g =
ValueDomain.invariant_global getg g

let invariant_vars ask getg st = protected_vars ask
end


module type PerGlobalPrivParam =
sig
(** Whether to also check unprotectedness by reads for extra precision. *)
Expand Down Expand Up @@ -1972,6 +2097,7 @@ let priv_module: (module S) Lazy.t =
let module Priv: S =
(val match get_string "ana.base.privatization" with
| "none" -> (module NonePriv: S)
| "vojdani" -> (module VojdaniPriv: S)
| "mutex-oplus" -> (module PerMutexOplusPriv)
| "mutex-meet" -> (module PerMutexMeetPriv)
| "mutex-meet-tid" -> (module PerMutexMeetTIDPriv (ThreadDigest))
Expand Down
8 changes: 4 additions & 4 deletions src/analyses/commonPriv.ml
Original file line number Diff line number Diff line change
Expand Up @@ -82,22 +82,22 @@ end
module Protection =
struct
open Q.Protection
let is_unprotected ask ?(protection=Strong) x: bool =
let is_unprotected ask ?(write=true) ?(protection=Strong) x: bool =
let multi = if protection = Weak then ThreadFlag.is_currently_multi ask else ThreadFlag.has_ever_been_multi ask in
(!GobConfig.earlyglobs && not multi && not (is_excluded_from_earlyglobs x)) ||
(
multi &&
ask.f (Q.MayBePublic {global=x; write=true; protection})
ask.f (Q.MayBePublic {global=x; write; protection})
)

let is_unprotected_without ask ?(write=true) ?(protection=Strong) x m: bool =
(if protection = Weak then ThreadFlag.is_currently_multi ask else ThreadFlag.has_ever_been_multi ask) &&
ask.f (Q.MayBePublicWithout {global=x; write; without_mutex=m; protection})

let is_protected_by ask ?(protection=Strong) m x: bool =
let is_protected_by ask ?(write=true) ?(protection=Strong) m x: bool =
is_global ask x &&
not (VD.is_immediate_type x.vtype) &&
ask.f (Q.MustBeProtectedBy {mutex=m; global=x; write=true; protection})
ask.f (Q.MustBeProtectedBy {mutex=m; global=x; write; protection})

let protected_vars (ask: Q.ask): varinfo list =
LockDomain.MustLockset.fold (fun ml acc ->
Expand Down
4 changes: 2 additions & 2 deletions src/config/options.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -761,9 +761,9 @@
"privatization": {
"title": "ana.base.privatization",
"description":
"Which privatization to use? none/mutex-oplus/mutex-meet/mutex-meet-tid/protection/protection-read/mine/mine-nothread/mine-W/mine-W-noinit/lock/lock-tid/write/write-tid/write+lock/write+lock-tid",
"Which privatization to use? none/vojdani/mutex-oplus/mutex-meet/mutex-meet-tid/protection/protection-read/mine/mine-nothread/mine-W/mine-W-noinit/lock/lock-tid/write/write-tid/write+lock/write+lock-tid",
"type": "string",
"enum": ["none", "mutex-oplus", "mutex-meet", "protection", "protection-tid", "protection-atomic", "protection-read", "protection-read-tid", "protection-read-atomic", "mine", "mine-nothread", "mine-W", "mine-W-noinit", "lock", "lock-tid", "write", "write-tid", "write+lock", "write+lock-tid", "mutex-meet-tid"],
"enum": ["none", "vojdani", "mutex-oplus", "mutex-meet", "protection", "protection-tid", "protection-atomic", "protection-read", "protection-read-tid", "protection-read-atomic", "mine", "mine-nothread", "mine-W", "mine-W-noinit", "lock", "lock-tid", "write", "write-tid", "write+lock", "write+lock-tid", "mutex-meet-tid"],
"default": "protection-read"
},
"priv": {
Expand Down
12 changes: 11 additions & 1 deletion src/framework/constraints.ml
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,18 @@ struct
ignore (getl (Function fd, c))
| exception Not_found ->
(* unknown function *)
M.error ~category:Imprecise ~tags:[Category Unsound] "Created a thread from unknown function %s" f.vname
M.error ~category:Imprecise ~tags:[Category Unsound] "Created a thread from unknown function %s" f.vname;
(* actual implementation (e.g. invalidation) is done by threadenter *)
(* must still sync for side effects, e.g., old sync-based none privatization soundness in 02-base/51-spawn-special *)
let rec sync_man =
{ man with
ask = (fun (type a) (q: a Queries.t) -> S.query sync_man q);
local = d;
prev_node = Function dummyFunDec;
}
in
(* TODO: more accurate man? *)
ignore (sync sync_man)
) ds
in
(* ... nice, right! *)
Expand Down
57 changes: 57 additions & 0 deletions tests/regression/13-privatized/01-priv_nr.t
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,63 @@
type: assertion
format: C

`vojdani` privatization:

$ goblint --enable witness.yaml.enabled --set witness.yaml.entry-types '["location_invariant"]' --disable witness.invariant.other --set ana.base.privatization vojdani 01-priv_nr.c
[Success][Assert] Assertion "glob1 == 5" will succeed (01-priv_nr.c:22:3-22:30)
[Success][Assert] Assertion "t == 5" will succeed (01-priv_nr.c:12:3-12:26)
[Success][Assert] Assertion "glob1 == -10" will succeed (01-priv_nr.c:14:3-14:32)
[Success][Assert] Assertion "glob1 == 6" will succeed (01-priv_nr.c:26:3-26:30)
[Info][Deadcode] Logical lines of code (LLoC) summary:
live: 19
dead: 0
total lines: 19
[Info][Witness] witness generation summary:
location invariants: 3
loop invariants: 0
flow-insensitive invariants: 0
total generation entries: 3
[Info][Race] Memory locations race summary:
safe: 1
vulnerable: 0
unsafe: 0
total memory locations: 1

$ yamlWitnessStrip < witness.yml
- entry_type: location_invariant
location:
file_name: 01-priv_nr.c
file_hash: $FILE_HASH
line: 25
column: 3
function: main
location_invariant:
string: glob1 == 5
type: assertion
format: C
- entry_type: location_invariant
location:
file_name: 01-priv_nr.c
file_hash: $FILE_HASH
line: 11
column: 3
function: t_fun
location_invariant:
string: glob1 == 5
type: assertion
format: C
- entry_type: location_invariant
location:
file_name: 01-priv_nr.c
file_hash: $FILE_HASH
line: 11
column: 3
function: t_fun
location_invariant:
string: (unsigned long )arg == 0UL
type: assertion
format: C

`mutex-meet` privatization:

$ goblint --enable witness.yaml.enabled --set witness.yaml.entry-types '["location_invariant"]' --disable witness.invariant.other --set ana.base.privatization mutex-meet 01-priv_nr.c
Expand Down

0 comments on commit ab54be8

Please sign in to comment.