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

Generate flow-insensitive YAML witness invariants with ghosts for privatized variables #1394

Open
wants to merge 98 commits into
base: master
Choose a base branch
from

Conversation

sim642
Copy link
Member

@sim642 sim642 commented Mar 14, 2024

This is a quick implementation of the idea we had a long time ago for exporting our protected invariants into YAML witnesses with ghosts.

protection privatization

For the Freiburg mutex.c example, we essentially generate the same witness with ghosts as the example:

  • A ghost variable m_locked is declared and updated to match lock and unlock operations of the mutex m.
  • A flow-insensitive invariant used == 0 || m_locked is generated using the ghost variable.

This is still our special flow_insensitive_invariant because there's the question where one would place the location_invariant when our result in fact holds at every point.

Currently tested only manually with:

./goblint ~/Documents/concurrency-witnesses/examples/VEWIT2023/mutex.c --enable ana.sv-comp.functions --set pre.cppflags[+] "-DGOBLINT_NO_PTHREAD_ONCE" --html --set ana.activated[+] mutexGhosts --enable witness.yaml.enabled --set witness.yaml.entry-types[+] flow_insensitive_invariant

mutex-meet privatization

For this example, the result is the same, but one invariant is generated per-mutex with a conjunction for all the protected variables. Also for the relational privatization.

TODO

  • Add tests using Add cram tests for YAML witnesses #1357.
  • Clean ghost variable management up to not be string-based.
  • What is the right invariant for multiple protecting mutexes?
  • What to do about ambiguous/unknown mutex lock and unlock operations? When do we have to generate a ghost update to which ghost variable?
    • Detect ambiguity and give up on those mutexes and globals.
  • Valid C names for ghost variables, in particular for (alloc@...) locks.
  • Add ghost variable for multi-threaded mode to avoid needing earlyglobs.
  • Prune unused ghost variables.
  • Are mutex-meet multiple-protecting invariants even right?
  • Adapt ghost update locations to Change YAML witness columns to 1-indexed #1400.
  • Unlock updates are on wrong node (but lock updates are right)?
  • e235ba7 broke OSX only
  • Options to disable ghost_variable and ghost_update entry types.
  • Relational mutex-meet.
  • Option to output __VERIFIER_atomic invariants unconditionally: they should not be observable anyway, but those ghost updates can be technically tricky to work with.

@sim642 sim642 added feature sv-comp SV-COMP (analyses, results), witnesses labels Mar 14, 2024
@sim642 sim642 self-assigned this Mar 14, 2024
@sim642 sim642 added the pr-dependency Depends or builds on another PR, which should be merged before label Mar 18, 2024
@sim642 sim642 marked this pull request as ready for review June 5, 2024 10:04
tests/regression/13-privatized/74-mutex.c Outdated Show resolved Hide resolved
src/witness/witnessGhostVar.ml Outdated Show resolved Hide resolved
let invs = WitnessUtil.InvariantExp.process_exp inv in
List.fold_left (fun acc inv ->
let invariant = Entry.invariant (CilType.Exp.show inv) in
let entry = Entry.flow_insensitive_invariant ~task ~invariant in
entry :: acc
) acc invs
| `Bot | `Top -> (* global bot might only be possible for alloc variables, if at all, so emit nothing *)
| `Lifted inv, true ->
(* TODO: or do at location_invariant loop for each node and query if should also do global invariants there? *)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happened with this TODO?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be possible to do this without the InvariantGlobalNodes query by querying InvariantGlobal for each location_invariant location. It might be more confusing though (the same query can be made both globally or locally) and possibly less efficient (although that might depend on how many nodes InvariantGlobalNodes returns and how big the invariants are).

else if VD.equal (getg (V.protected g')) (getg (V.unprotected g')) then
Invariant.none (* don't output protected invariant because it's the same as unprotected *)
else (
let inv = ValueDomain.invariant_global (fun g -> getg (V.protected g)) g' in (* TODO: this takes protected values of everything *)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's up with this TODO?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem here is that it can be unsound I think. If a global pointer is protected but what it points to isn't, then this invariant will assume both are protected.
Surprisingly, Ultimate hasn't found any of such to be wrong. Maybe because if the pointee is jointly protected, then it's fine again?

We cannot just delegate to read_global here because this is in flow-sensitive context, so there's no notion of current lockset, etc.

(* Very conservative about multiple (write-)protecting mutexes: invariant is not claimed when any of them is held.
It should be possible to be more precise because writes only happen with all of them held,
but conjunction is unsound when one of the mutexes is temporarily unlocked.
Hypothetical read-protection is also somehow relevant. *)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this sentence mean?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We decided not to come up with more precise invariants here, so it's hard to say anything more precisely about it. Simply that read-protection also plays into it (if nobody reads it with fewer locks then we can claim a stronger invariant, kind of like the protection-read privatization).

@@ -250,7 +250,7 @@ struct

let invariant_global ask getg = function
| `Right g' -> (* global *)
ValueDomain.invariant_global (read_unprotected_global getg) g'
ValueDomain.invariant_global (read_unprotected_global getg) g' (* TODO: disjunct with mutex_inits instead of join? *)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the plan with this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using duplicate versions of read_unprotected_global and get_mutex_global_x_with_mutex_inits which operate on invariant expressions rather than ValueDomain, we could make these invariants maybe more precise.

I just don't know if it's worth it though. Currently the logic is shared with the usual read_global used by the analysis. Duplicating would require them to be maintained in sync.

Comment on lines +397 to +399
if !earlyglobs && not (ThreadFlag.is_currently_multi ask) then
sideg V.mutex_inits side_cpa (* Also side to inits because with earlyglobs enter_multithreaded does not side everything to inits *)
(* Unlock after invariant will still side effect refined value (if protected) from CPA, because cannot distinguish from non-invariant write. *)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed and what effect does it have?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you check whether this causes any precision regressions?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without it

goblint --enable ana.sv-comp.functions --set ana.base.privatization mutex-meet --enable exp.earlyglobs 74-mutex.c

does not pass because the variable will be missing from mutex_inits, so we'll read bottom from the map and turn it into top I think. So this is actually to increase precision with earlyglobs.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A similar thing is already in protection privatization. I think we never really tested mutex-meet with earlyglobs.

src/analyses/base.ml Outdated Show resolved Hide resolved
else
Invariant.none
| g -> (* global *)
Invariant.none (* TODO: ? *)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plan?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be possible to emit unprotected invariants from relational mutex-meet, but these will be inherently non-relational, so this might be rather useless.

@sim642 sim642 added this to the v2.5.0 milestone Jul 31, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature sv-comp SV-COMP (analyses, results), witnesses
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants