-
Notifications
You must be signed in to change notification settings - Fork 660
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
Correctly update symbolic variables that have been changed externally #1407
Correctly update symbolic variables that have been changed externally #1407
Conversation
Codecov Report
Additional details and impacted files@@ Coverage Diff @@
## master #1407 +/- ##
==========================================
+ Coverage 70.19% 70.21% +0.01%
==========================================
Files 162 162
Lines 19395 19405 +10
Branches 4630 4634 +4
==========================================
+ Hits 13615 13625 +10
- Misses 3741 3743 +2
+ Partials 2039 2037 -2
|
92d93b8
to
2e3ebe0
Compare
f3550f4
to
24df55c
Compare
@MartinNowack thanks for this. I have finally looked at this patch, which is a bit tricky (in particular, I find the ObjectState class to be poorly documented, I'm currently doing a pass over it). Here's how I would fix this: if the object was modified by the external call ( Does this make sense? |
@ccadar Thanks for the review and suggestion. One goal of the implementation was to keep as much symbolic information as possible. |
24df55c
to
dd3f8b7
Compare
b8defb8
to
8133382
Compare
That one is ready to be merged. |
@ccadar Please merge this. |
Hi @MartinNowack, thanks for this patch and sorry for the delay. However, this patch is not straightforward at all, despite its small size. As I said, to me the straightforward solution I suggested in #1407 (comment) makes most sense, but you seem to suggest that a more complex one is needed, and I want to understand why. Let me start by adding some review comments. |
lib/Core/AddressSpace.cpp
Outdated
// functions. Do a byte-by-byte comparison and update if needed. | ||
for (size_t i = 0, ie = mo->size; i < ie; ++i) { | ||
u_int8_t updated_value = *(address + i); | ||
if (updated_value != wos->concreteStore[i]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand why this if statement is needed. Can you explain? You cannot rely at this point that wos->concreteStore[i]
is valid, can you?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is where all the magic happens:
wos->concreteStore[i]
contains the concrete object-state representation before the call and is copied to the external address before the call. This includes the concrete values representing symbolic expressions.
update_value
represents the current external value at location i
after the call. If update_value
and wos->concreteStore[i]
differ, we know that the external function has changed the object, so we do need to propagate this back to the object. This is especially important, if the external function has changed the value that has been previously represented by a symbolic value. In this case we need to update the object state accordingly, therefore we call the wos->write8()
function. This has been handled incorrectly by KLEE before, i.e. not updating the information that a value is not symbolic anymore, only updating the concreteStore
representation. This lead to subsequent reads of that object still using the symbolic values instead of the concrete one.
In summary, we have to update the object state's meta-data one way or another.
The major drawback of your solution is that always the full object will be concretised even if only one byte has changed externally.
This PR preserves all the symbolic information of an object that has not been modified by external functions but updates everything else accordingly.
This shouldn't have a higher performance impact for smaller objects.
The performance impact of your suggestion depends on the original state of the object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MartinNowack thanks for the detailed explanation. I would add a (shorter) comment along those lines in the text -- the key part would be the first sentence, I think.
This being said, while your solution is more performant in some cases, it over-approximates things. That's because it can happen that the external call happened to write the same values for some bytes, and keeping those symbolic would lead to inconsistencies. It is easy to construct such an example, and might not be so unlikely in practice given that you do this at the byte granularity (and some values, such as 0, might be quite common).
One solution would be to do this only under the over-approx
policy I introduced in #1696, what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ccadar Indeed this is a problem. The only upside is that it's less likely to have an impact as the concretisation (i.e. using the results of the solver call) still has the same result. Therefore, recurring queries with the same constraint set would lead to the same results.
I like the over-approx
solution, that would make a cleaner design and we could switch between both implementations easily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, then let's guard this by the --over-approx
flag once #1696 is merged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like a good plan!
lib/Core/AddressSpace.cpp
Outdated
if (os->readOnly) { | ||
return false; | ||
} else { | ||
ObjectState *wos = getWriteable(mo, os); | ||
memcpy(wos->concreteStore, address, mo->size); | ||
if (wos->unflushedMask) { | ||
// Handle the case, that objects have been modified by external |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Handle the case, that objects" -> "Handle the case when symbolic objects"
// RUN: rm -rf %t.klee-out | ||
// RUN: %klee --output-dir=%t.klee-out -external-calls=all %t.bc > %t.log | ||
// RUN: FileCheck -input-file=%t.log %s | ||
// REQUIRES: not-darwin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not Darwin?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the specific printf
feature is not supported under Darwin.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But then can we choose something more portable, maybe sscanf
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reminder about this
int b; | ||
int c; | ||
|
||
// Symbolize argument that gets constified by the external call |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
constified -> concretized
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reminder about this
lib/Core/Memory.cpp
Outdated
@@ -199,7 +199,7 @@ const UpdateList &ObjectState::getUpdates() const { | |||
void ObjectState::flushToConcreteStore(TimingSolver *solver, | |||
const ExecutionState &state) const { | |||
for (unsigned i = 0; i < size; i++) { | |||
if (isByteKnownSymbolic(i)) { | |||
if (!isByteConcrete(i)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! This seems like it could have affected other types of external calls, right?
@ccadar Thanks for the review, I added a comment to the location of interest. |
1d93749
to
9b73c9c
Compare
lib/Core/Memory.h
Outdated
/// in the concrete store. | ||
/// | ||
/// \param executor | ||
/// \param state containt |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be a misspelling
lib/Core/Memory.h
Outdated
/// | ||
/// \param executor | ||
/// \param state containt | ||
/// \param concretize provide value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a more clear explanation? E.g., reusing the text from toConstant
// RUN: rm -rf %t.klee-out | ||
// RUN: %klee --output-dir=%t.klee-out -external-calls=all %t.bc > %t.log | ||
// RUN: FileCheck -input-file=%t.log %s | ||
// REQUIRES: not-darwin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reminder about this
int b; | ||
int c; | ||
|
||
// Symbolize argument that gets constified by the external call |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reminder about this
state.addressSpace.resolveOne(cvalue, op) && !op.second->readOnly) { | ||
auto *os = state.addressSpace.getWriteable(op.first, op.second); | ||
os->flushToConcreteStore(*this, state, | ||
ExternalCalls == ExternalCallPolicy::All); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this should be ExternalCalls != ExternalCallPolicy::OverApprox
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to write a test case demonstrating the bug you fixed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First comment: This is already guarded in the earlier lines by:
if (ExternalCalls == ExternalCallPolicy::All ||
ExternalCalls == ExternalCallPolicy::OverApprox) {
I'm using the same approach as in line 4020 to be consistent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, nothing to do for this one then.
Thanks, @MartinNowack , great changes. Only a few minor issues left, see comments. The only bigger request, if feasible, would be for a test case demonstrating the bug you fixed. |
…ymbolic variables The test case is based on the example provided by Mingyi Liu from the KLEE mailing list.
Before, external changes to symbolic variables have not been propagated back to their internal representation. Do a byte-by-byte comparison and update object state if required.
…bolic Before, only partially symbolic variables have been concretized. Now, every object that is not fully concrete is concretized correctly this includes fully symbolic objects.
Use existing `Executor::toConstant()` function to transform a symbolic byte of an `ObjectState` to its concrete representation. This will also add constraints if required.
Provide an additional argument to select the concretisation policy. Fix a bug where the concretisation of a shared memory object was visible across different states by retrieving a writable object state first.
Propagate ExternalCallPolicy to allow user-based selection.
6d92472
to
7021a89
Compare
Thanks, @MartinNowack, let's merge this one for v3.1, and keep in mind my remaining comments for future work involving external calls. |
Before, external changes to symbolic variables have not been propagated
back to their internal representation.
Do a byte-by-byte comparison and potential update if required.
The test case is based on the example provided by Mingyi Liu from the
mailing list.
Summary:
Checklist: