-
Notifications
You must be signed in to change notification settings - Fork 31
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
Support alloc-free dev #794
base: alloc-free
Are you sure you want to change the base?
Conversation
Signed-off-by: Tianrui Zheng <[email protected]>
Signed-off-by: Tianrui Zheng <[email protected]>
Signed-off-by: Tianrui Zheng <[email protected]>
Signed-off-by: Tianrui Zheng <[email protected]>
Signed-off-by: Tianrui Zheng <[email protected]>
Signed-off-by: Tianrui Zheng <[email protected]>
Signed-off-by: Tianrui Zheng <[email protected]>
Signed-off-by: Tianrui Zheng <[email protected]>
Signed-off-by: Tianrui Zheng <[email protected]>
Signed-off-by: Tianrui Zheng <[email protected]>
Signed-off-by: Tianrui Zheng <[email protected]>
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.
On a more general note about handling Free
/Alloc
: I'm not convinced that changing aliasing is the right concept for Alloc/Free. I think it is a misleading road.
What you really care about is not the individual addresses that the Alloc/Free targets, but only the memory objects as a whole. Similarly, you care about the objects a memory access can address if you want to talk about bugs like use-after-free.
So a canAccessSameObject(x, y)
method would be more appropriate and would also elevate the issue of requiring allocations of known size (the size does not matter!).
If you then introduce a corresponding sameObj
relation in .cat
, you should be able to treat Free
and Alloc
more-or-less as single-address events (i.e., simple memory events).
A use-after-free (or racy free) would then simply be ~empty ([Free];sameObj;[M] \ hb)
.
boolean mustAlias(Alloc a, MemoryCoreEvent e); | ||
|
||
boolean mayAlias(Alloc a, MemoryCoreEvent e); | ||
|
||
boolean mustAlias(Alloc a, MemFree f); | ||
|
||
boolean mayAlias(Alloc a, MemFree f); | ||
|
||
boolean mustAlias(MemFree a, MemFree b); | ||
|
||
boolean mayAlias(MemFree a, MemFree b); |
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 know this is a draft, but you don't really want to go with so many overloads, do you?
I think you didn't really check what the code is doing. We need both, individual addresses (i.e. the pointer returned by alloc) and the full allocated memory region to compare with addresses of memory accesses. |
Why do you need the individual addresses in the full region if you instead had a |
please read the code first |
Huh, I read the code?! I can see that all the checks about bounded size allocations and |
Isn't the later some kind of alias (maybe not as fine grained as per address, but at least per object)?
This is assuming no OOB, right?
Isn't this kind of what the two new relations |
Yes, it is a kind of aliasing. But I think it is worth to differentiate between the concepts of "same address", "overlapping address" and "same object". Covering them all under the term "aliasing" is bound to cause confusion.
All alias analyses assume no OOB anyways. So nothing changes there.
Yes, I know about those relations. I'm saying that you can likely replace them by a general |
dartagnan/src/main/java/com/dat3m/dartagnan/program/analysis/alias/AliasAnalysis.java
Outdated
Show resolved
Hide resolved
public boolean mayAlias(MemFree a, MemFree b) { | ||
return a1.mayAlias(a, b) && a2.mayAlias(a, b); | ||
} | ||
|
||
@Override | ||
public Graphviz getGraphVisualization() { |
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.
We will also need to add the new events to the graph
dartagnan/src/main/java/com/dat3m/dartagnan/program/analysis/alias/AndersenAliasAnalysis.java
Outdated
Show resolved
Hide resolved
dartagnan/src/main/java/com/dat3m/dartagnan/program/analysis/alias/FieldSensitiveAndersen.java
Outdated
Show resolved
Hide resolved
dartagnan/src/main/java/com/dat3m/dartagnan/program/analysis/alias/AndersenAliasAnalysis.java
Outdated
Show resolved
Hide resolved
dartagnan/src/main/java/com/dat3m/dartagnan/program/analysis/alias/AndersenAliasAnalysis.java
Outdated
Show resolved
Hide resolved
dartagnan/src/main/java/com/dat3m/dartagnan/program/analysis/alias/AndersenAliasAnalysis.java
Outdated
Show resolved
Hide resolved
dartagnan/src/main/java/com/dat3m/dartagnan/program/analysis/alias/AliasAnalysis.java
Outdated
Show resolved
Hide resolved
Signed-off-by: Tianrui Zheng <[email protected]>
@@ -774,14 +774,24 @@ public MutableKnowledge visitAllocPtr(AllocPtr aref) { | |||
MutableEventGraph must = new MapEventGraph(); |
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.
The TODOs can be removed now from both relations.
Could you also rename parameter aref -> allocPtr and aloc -> allocMem to match the other names in the class? I forgot to rename them after changing relation names.
@@ -774,14 +774,24 @@ public MutableKnowledge visitAllocPtr(AllocPtr aref) { | |||
MutableEventGraph must = new MapEventGraph(); | |||
for (Alloc e1 : program.getThreadEvents(Alloc.class)) { | |||
if (e1.isHeapAllocation()) { |
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.
We can merge these two loops info one, something like this
List allocEvents = program.getThreadEvents(Alloc.class).stream().filter(a -> a.isHeapAllocation()).toList();
List freeEvents = program.getThreadEvents(MemFree.class);
Stream.concat(allocEvents.stream(), freeEvents.stream()).forEach(e1 -> freeEvents.forEach(e2 -> {
...
}));
@@ -795,8 +805,13 @@ public MutableKnowledge visitAllocMem(AllocMem aloc) { | |||
MutableEventGraph must = new MapEventGraph(); | |||
for (Alloc e1 : program.getThreadEvents(Alloc.class)) { | |||
if (e1.isHeapAllocation()) { | |||
for (Event e2 : program.getThreadEvents(MemoryEvent.class)) { | |||
may.add(e1, e2); | |||
for (MemoryCoreEvent e2 : program.getThreadEvents(MemoryCoreEvent.class)) { |
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.
Could you create a list of MemoryCoreEvent before the first loop, so that we don't need to call getThreadEvents each time?
@@ -795,8 +805,13 @@ public MutableKnowledge visitAllocMem(AllocMem aloc) { | |||
MutableEventGraph must = new MapEventGraph(); | |||
for (Alloc e1 : program.getThreadEvents(Alloc.class)) { | |||
if (e1.isHeapAllocation()) { |
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 think it should be easy to extend this approach to stack events, but I guess you want to get the heap part ready first.
Set<Location> target = targets.get(address); | ||
addresses = target != null ? target : getAddresses(address); | ||
if (addrExpr instanceof Register register) { | ||
Set<Location> target = targets.get(register); |
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 be the same addrExpr (without cast)
return getMaxAddressSet(e).stream().anyMatch( | ||
l -> l.base.equals(a.getAllocatedObject()) && l.offset < getAllocatedSize(a) | ||
); | ||
public boolean mayAlias(Event a, Event b) { |
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 think this typecheck is a bit on overkill. We have two cases:
- A pair of alloc and memory event -> we need to check if memory event may/must access any address from the allocated region.
- Everything else, including alloc-free and free-free pairs -> we need to check the "normal" may/must alias for a single pointer.
How about something like this?
@OverRide
public boolean mayAlias(Event a, Event b) {
if (a instanceof Alloc alloc && b instanceof MemoryCoreEvent mem) {
return mayAccessAllocatedBy(alloc, mem);
}
if (b instanceof Alloc alloc && a instanceof MemoryCoreEvent mem) {
// This case shouldn't be called because alloc->mem relation always starts at alloc, we can keep both to be on the safe side.
return mayAccessAllocatedBy(alloc, mem);
}
return mayAccessSameAddress(a, b);
}
And the same for must sets and the other analysis classes.
@Override | ||
public boolean mayAlias(MemFree a, MemFree b) { | ||
return !Sets.intersection(getFreedAddresses(a), getFreedAddresses(b)).isEmpty(); | ||
private boolean mayAccessAllocatedBy(Alloc a, Event e) { |
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 would keep only the first part and do alloc-free pair in may/mustAccessSameAddress.
} | ||
} | ||
|
||
private void processAllocs(Alloc a) { | ||
if (!a.isHeapAllocation()) { | ||
return; | ||
} | ||
Register r = a.getResultRegister(); | ||
if (a.getAllocationSize() instanceof IntLiteral 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.
We can add alloc to eventAddressSpaceMap and then check alloc-free pair via getMaxAddressSet:
eventAddressSpaceMap.put(a, Set.of(new Location(a.getAllocatedObject(), 0)));
throw new IllegalArgumentException("Unsupported event types for EqualityAliasAnalysis"); | ||
} | ||
|
||
private boolean mustAccessSameAddress(MemoryCoreEvent a, MemoryCoreEvent b) { |
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 think we don't need all these complex reasoning in mayAlias/mustAlias methods. In the old comment, I meant that we can implement mustAccessAllocatedBy(Alloc a, MemoryCoreEvent e) following the same logic as for mustAccessSameAddress, i.e. must access is true if 1) the memory event uses the register of the alloc event and 2) the register value has not been overwritten.
We can also try reasoning about register + index accesses, but it will be a bit more complex, because we also need to consider sizes of the member elements and offsets.
final DerivedVariable vx = addressVariables.get(x); | ||
final DerivedVariable vy = addressVariables.get(y); | ||
return vx != null && vy != null && vx.base == vy.base && vx.modifier.offset == vy.modifier.offset && | ||
isConstant(vx.modifier) && isConstant(vy.modifier); | ||
} | ||
|
||
private boolean mayAccessAllocatedBy(Alloc a, Event e) { |
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 will carefully check it and add comments a bit later.
You may add the following methods to boolean mayObjectAlias(Event a, Event b);
boolean mustObjectAlias(Event a, Event b); Implementing it in @Override
public boolean mayObjectAlias(Event a, Event b) {
DerivedVariable addressA = addressVariables.get(a);
DerivedVariable addressB = addressVariables.get(b);
return addressA == null || addressB == null ||
!Collections.disjoint(getAccessibleObjects(addressA), getAccessibleObjects(addressB));
}
@Override
public boolean mustObjectAlias(Event a, Event b) {
DerivedVariable addressA = addressVariables.get(a);
DerivedVariable addressB = addressVariables.get(b);
if (addressA == null | addressB == null) {
return false;
}
if (addressA.base == addressB.base) {
return true;
}
Set<MemoryObject> objectsA = getAccessibleObjects(addressA);
return objectsA.size() == 1 && objectsA.equals(getAccessibleObjects(addressB));
}
private Set<MemoryObject> getAccessibleObjects(DerivedVariable address) {
var objects = new HashSet<MemoryObject>();
objects.add(address.base.object);
for (IncludeEdge edge : address.base.includes) {
objects.add(edge.source.object);
}
objects.remove(null);
return objects;
}
private void run(Program program, AliasAnalysis.Config configuration) {
...
for (Alloc alloc : program.getThreadEvents(Alloc.class)) {
addressVariables.put(alloc, derive(objectVariables.get(alloc.getAllocatedObject())));
}
...
} Implementing it in @Override
public boolean mayObjectAlias(MemoryCoreEvent a, MemoryCoreEvent b) {
return true;
}
@Override
public boolean mustObjectAlias(MemoryCoreEvent a, MemoryCoreEvent b) {
return mustAlias(a, b);
} Implementation for @Override
public boolean mayObjectAlias(MemoryCoreEvent a, MemoryCoreEvent b) {
return a1.mayObjectAlias(a, b) && a2.mayObjectAlias(a, b);
}
@Override
public boolean mustObjectAlias(MemoryCoreEvent a, MemoryCoreEvent b) {
return a1.mustObjectAlias(a, b) || a2.mustObjectAlias(a, b);
} Implementing it in @Override
public boolean mayObjectAlias(MemoryCoreEvent a, MemoryCoreEvent b) {
return !Collections.disjoint(getAccessibleObjects(a), getAccessibleObjects(b));
}
@Override
public boolean mustObjectAlias(MemoryCoreEvent a, MemoryCoreEvent b) {
Set<MemoryObject> objects = getAccessibleObjects(a);
return objects.size() == 1 && objects.containsAll(getAccessibleObjects(b));
}
private Set<MemoryObject> getAccessibleObjects(MemoryCoreEvent event) {
var objects = new HashSet<MemoryObject>();
for (Location location : getMaxAddressSet(event)) {
objects.add(location.base);
}
return objects;
} |
Are you suggesting that we only care about the |
Not really. You would only get precision in the presence of out-of-bounds accesses (UB), which we don't handle correctly either way. |
for (Event e2 : program.getThreadEvents(MemoryEvent.class)) { | ||
may.add(e1, e2); | ||
for (MemoryCoreEvent e2 : program.getThreadEvents(MemoryCoreEvent.class)) { | ||
if (alias.mayAlias(e1, e2)) { |
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.
We can skip e2 if it is an instance of Init
@@ -202,6 +332,9 @@ private void run(Program program, AliasAnalysis.Config configuration) { | |||
for (final MemoryCoreEvent memoryEvent : program.getThreadEvents(MemoryCoreEvent.class)) { | |||
processMemoryEvent(memoryEvent); | |||
} | |||
for (final MemFree free : program.getThreadEvents(MemFree.class)) { |
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.
Similar to the other analysis, Alloc -> MemFree should check exact pointer match, not the whole allocated region. So, in order to reuse the same algorithm, you need to allocs to addressVariables.
continue; | ||
} | ||
final Modifier m = compose(i.modifier, v.modifier); | ||
final boolean may = isConstant(m) ? m.offset < size && m.offset >= 0 |
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 would use Rene's suggestion and compare only base objects when checking the whole memory region.
Signed-off-by: Tianrui Zheng <[email protected]>
No description provided.