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

Optimize none base privatization, add eager Vojdani privatization #1552

Merged
merged 14 commits into from
Dec 30, 2024
Merged
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
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}
michael-schwarz marked this conversation as resolved.
Show resolved Hide resolved
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
Loading