Fix Apron handling of escaped locals - #2100
Conversation
|
Locally, the change appears fine, although to me it looks like a whack-a-mole fix. For what it's worth, I tried to prompt AI to poke holes in the code for counterexamples, and (before the session got derailed by cybersecurity guardrails) it got me the following adjacent case: // PARAM: --set ana.activated[+] apron --enable ana.sv-comp.functions
#include <goblint.h>
#include <string.h>
static int *target;
static void modify(void) {
*target = __VERIFIER_nondet_int();
}
int main(void) {
int left = __VERIFIER_nondet_int();
int right = left;
int *source = &right;
memcpy(&target, &source, sizeof(target));
modify();
__goblint_check(left == right); // UNKNOWN!
}which crashes Goblint with Given that the failure is different from the original one, I am approving this pull request. |
I took a look at your example, thank you! I think the issue there is fundamentally different. The problem is that the escape analysis does not consider the escape via // PARAM: --enable ana.sv-comp.functions
#include <goblint.h>
#include <pthread.h>
#include <string.h>
static int *target;
static void *modify(void *arg) {
*target = 7;
return NULL;
}
int main(void) {
int right = 42;
int *source = &right;
memcpy(&target, &source, sizeof(target));
pthread_t thread;
pthread_create(&thread, NULL, modify, NULL);
pthread_join(thread, NULL);
__goblint_check(right == 42); // Incorrectly succeeds
}Given that there it is an issue with |
| let new_rel = RD.keep_filter st.rel (fun var -> | ||
| match RV.find_metadata var with | ||
| | Some (Local v) when ThreadEscape.has_escaped ask v -> false (* escaped locals may be modified through globals *) | ||
| | Some (Local _) when not (belongs_to_fundec fundec var || any_local_reachable) -> true (* keep caller locals, provided they were not passed to the function *) |
There was a problem hiding this comment.
How come this is different from the make_callee_rel change above?
Previously, both used the exact same condition (negated) to either remove or keep the corresponding locals.
Now, they are no longer symmetric like that.
There was a problem hiding this comment.
It is not semantically different here, it's just that I broke the symmetry in formatting. I have restored the symmetry now.
There was a problem hiding this comment.
Initially I thought so too that it's just different formatting, but that's not true.
For an escaped local:
- Before the symmetry fix, this immediately goes to
false. - After the symmetry fix, all the negated escape condition does is not allow it to go to that
true. But there are otherLocalcases below with different conditions that may still returntrue.
There was a problem hiding this comment.
That's true, I hadn't thought about this. Looking at it, the case where this can now actually go to true is
| Some ((Local v | Global v)) when not (TaintPartialContexts.VS.mem v tainted_vars) -> true (* keep locals and globals, which have not been touched by the call *)This is correct: If the escaped variable was definitely not modified in the callee, we can preserve the caller state.
Fix Apron’s handling of locals whose addresses escape through global pointers.
In single-threaded execution, escaped locals were read using their local relational variable but written using a separate global relational variable. Calls could also omit escaped locals from callee states and restore stale pre-call relations afterward. This allowed Apron to retain relations invalidated by indirect writes.
This change:
Tested with:
opam exec -- scripts/update_suite.rb group apron3 -qopam exec -- dune build @runaprontest --display quietCloses #2091.