GP-1970: Remove TraceObject.getLifespan() and ilk

This commit is contained in:
Dan
2022-05-09 13:28:00 -04:00
parent 17c0b78756
commit c957154730
58 changed files with 1803 additions and 1144 deletions
@@ -381,7 +381,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
} }
protected void updateStack() { protected void updateStack() {
Set<TraceStackFrame> toAdd = new LinkedHashSet<>(currentStack.getFrames()); Set<TraceStackFrame> toAdd = new LinkedHashSet<>(currentStack.getFrames(current.getSnap()));
for (Iterator<StackFrameRow> it = stackTableModel.getModelData().iterator(); it for (Iterator<StackFrameRow> it = stackTableModel.getModelData().iterator(); it
.hasNext();) { .hasNext();) {
StackFrameRow row = it.next(); StackFrameRow row = it.next();
@@ -409,13 +409,13 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
contextChanged(); contextChanged();
return; return;
} }
if (currentStack == stack) { if (currentStack == stack && stack.hasFixedFrames()) {
stackTableModel.fireTableDataChanged(); stackTableModel.fireTableDataChanged();
return; return;
} }
currentStack = stack; currentStack = stack;
stackTableModel.clear(); stackTableModel.clear();
for (TraceStackFrame frame : currentStack.getFrames()) { for (TraceStackFrame frame : currentStack.getFrames(current.getSnap())) {
stackTableModel.add(new StackFrameRow(this, frame)); stackTableModel.add(new StackFrameRow(this, frame));
} }
} }
@@ -75,13 +75,13 @@ public class StackFrameRow {
} }
public String getComment() { public String getComment() {
return frame == null ? "" : frame.getComment(); return frame == null ? "" : frame.getComment(getSnap());
} }
public void setComment(String comment) { public void setComment(String comment) {
try (UndoableTransaction tid = UndoableTransaction try (UndoableTransaction tid = UndoableTransaction
.start(frame.getStack().getThread().getTrace(), "Frame comment", true)) { .start(frame.getStack().getThread().getTrace(), "Frame comment", true)) {
frame.setComment(comment); frame.setComment(getSnap(), comment);
} }
} }
@@ -277,7 +277,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
private void breakpointDeleted(TraceBreakpoint tb) { private void breakpointDeleted(TraceBreakpoint tb) {
if (!tb.getLifespan().contains(info.recorder.getSnap())) { if (!tb.getLifespan().contains(info.recorder.getSnap())) {
// NOTE: User/script probably removed historical breakpoint // NOTE: User/script probably removed historical breakpoint
assert false; // assert false;
return; return;
} }
info.forgetTraceBreakpoint(c.r, tb); info.forgetTraceBreakpoint(c.r, tb);
@@ -420,7 +420,10 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
@Override @Override
public TraceThread getTraceThreadForSuccessor(TargetObject successor) { public TraceThread getTraceThreadForSuccessor(TargetObject successor) {
TraceObject traceObject = objectRecorder.toTrace(successor); TraceObject traceObject = objectRecorder.toTrace(successor);
return traceObject.queryCanonicalAncestorsInterface(Range.singleton(getSnap()), if (traceObject == null) {
return null;
}
return traceObject.queryCanonicalAncestorsInterface(
TraceObjectThread.class).findFirst().orElse(null); TraceObjectThread.class).findFirst().orElse(null);
} }
@@ -432,7 +435,10 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
@Override @Override
public TraceStackFrame getTraceStackFrameForSuccessor(TargetObject successor) { public TraceStackFrame getTraceStackFrameForSuccessor(TargetObject successor) {
TraceObject traceObject = objectRecorder.toTrace(successor); TraceObject traceObject = objectRecorder.toTrace(successor);
return traceObject.queryCanonicalAncestorsInterface(Range.singleton(getSnap()), if (traceObject == null) {
return null;
}
return traceObject.queryCanonicalAncestorsInterface(
TraceObjectStackFrame.class).findFirst().orElse(null); TraceObjectStackFrame.class).findFirst().orElse(null);
} }
@@ -546,7 +552,7 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
TraceObject object = thread.getObject(); TraceObject object = thread.getObject();
this.process = object this.process = object
.queryAncestorsTargetInterface(Range.singleton(getSnap()), TargetProcess.class) .queryAncestorsTargetInterface(Range.singleton(getSnap()), TargetProcess.class)
.map(p -> p.getFirstParent(object)) .map(p -> p.getSource(object))
.findFirst() .findFirst()
.orElse(null); .orElse(null);
} }
@@ -562,7 +568,7 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
} }
return object return object
.queryAncestorsTargetInterface(Range.singleton(getSnap()), TargetProcess.class) .queryAncestorsTargetInterface(Range.singleton(getSnap()), TargetProcess.class)
.map(p -> p.getFirstParent(object)) .map(p -> p.getSource(object))
.anyMatch(p -> p == process); .anyMatch(p -> p == process);
} }
} }
@@ -82,11 +82,14 @@ class ObjectRecorder {
traceObject = objectManager.getRootObject(); traceObject = objectManager.getRootObject();
} }
else { else {
traceObject = objectManager traceObject = objectManager.createObject(TraceObjectKeyPath.of(object.getPath()));
.createObject(TraceObjectKeyPath.of(object.getPath()), Range.atLeast(snap));
} }
synchronized (objectMap) { synchronized (objectMap) {
objectMap.put(new IDKeyed<>(object), new IDKeyed<>(traceObject)); IDKeyed<TraceObject> exists =
objectMap.put(new IDKeyed<>(object), new IDKeyed<>(traceObject));
if (exists != null) {
Msg.error(this, "Received created for an object that already exists: " + exists);
}
} }
} }
@@ -102,7 +105,7 @@ class ObjectRecorder {
Msg.error(this, "Unknown object was invalidated: " + object); Msg.error(this, "Unknown object was invalidated: " + object);
return; return;
} }
traceObject.obj.truncateOrDelete(Range.atLeast(snap)); traceObject.obj.removeTree(Range.atLeast(snap));
} }
protected String encodeEnum(Enum<?> e) { protected String encodeEnum(Enum<?> e) {
@@ -269,7 +272,7 @@ class ObjectRecorder {
if (found == null) { if (found == null) {
return null; return null;
} }
TraceObject last = found.getLastChild(null); TraceObject last = found.getDestination(null);
if (last == null) { if (last == null) {
return null; return null;
} }
@@ -284,7 +287,7 @@ class ObjectRecorder {
return List.of(); return List.of();
} }
return seed.querySuccessorsTargetInterface(Range.singleton(recorder.getSnap()), targetIf) return seed.querySuccessorsTargetInterface(Range.singleton(recorder.getSnap()), targetIf)
.map(p -> toTarget(p.getLastChild(seed)).as(targetIf)) .map(p -> toTarget(p.getDestination(seed)).as(targetIf))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
} }
@@ -1079,7 +1079,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
} }
@Test @Test
public void testEditLiveBytesWritesTarget() throws Exception { public void testEditLiveBytesWritesTarget() throws Throwable {
createTestModel(); createTestModel();
mb.createTestProcessesAndThreads(); mb.createTestProcessesAndThreads();
@@ -1097,12 +1097,12 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
performAction(actionEdit); performAction(actionEdit);
triggerText(memBytesProvider.getByteViewerPanel().getCurrentComponent(), "42"); triggerText(memBytesProvider.getByteViewerPanel().getCurrentComponent(), "42");
performAction(actionEdit); performAction(actionEdit);
waitForSwing();
waitRecorder(recorder);
byte[] data = new byte[4]; byte[] data = new byte[4];
waitForPass(() -> { mb.testProcess1.memory.getMemory(mb.addr(0x55550800), data);
mb.testProcess1.memory.getMemory(mb.addr(0x55550800), data); assertArrayEquals(mb.arr(0x42, 0, 0, 0), data);
assertArrayEquals(mb.arr(0x42, 0, 0, 0), data);
});
} }
@Test @Test
@@ -95,11 +95,11 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe
TraceStackFrame frame = stack.getFrame(0, false); TraceStackFrame frame = stack.getFrame(0, false);
frame.setProgramCounter(Range.all(), tb.addr(0x00400100)); frame.setProgramCounter(Range.all(), tb.addr(0x00400100));
frame.setComment("Hello"); frame.setComment(stack.getSnap(), "Hello");
frame = stack.getFrame(1, false); frame = stack.getFrame(1, false);
frame.setProgramCounter(Range.all(), tb.addr(0x00400200)); frame.setProgramCounter(Range.all(), tb.addr(0x00400200));
frame.setComment("World"); frame.setComment(stack.getSnap(), "World");
} }
} }
@@ -169,19 +169,22 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerGU
public void testRecordThreadNameReuse() throws Throwable { public void testRecordThreadNameReuse() throws Throwable {
startRecording(); startRecording();
mb.createTestProcessesAndThreads(); mb.createTestProcessesAndThreads();
TraceThread thread1a = waitForValue(() -> recorder.getTraceThread(mb.testThread1)); waitRecorder(recorder);
TraceThread thread1 = recorder.getTraceThread(mb.testThread1);
assertNotNull(thread1);
TraceObject object1 = ((TraceObjectThread) thread1).getObject();
recorder.forceSnapshot(); recorder.forceSnapshot();
mb.testProcess1.threads.removeThreads(mb.testThread1); mb.testProcess1.threads.removeThreads(mb.testThread1);
waitRecorder(recorder);
waitForPass(() -> assertEquals(Range.singleton(0L), thread1a.getLifespan())); assertEquals(Range.singleton(0L), thread1.getLifespan());
assertNull(recorder.getTraceThread(mb.testThread1)); assertNull(recorder.getTraceThread(mb.testThread1));
recorder.forceSnapshot(); recorder.forceSnapshot();
mb.testThread1 = mb.testProcess1.addThread(1); mb.testThread1 = mb.testProcess1.addThread(1);
TraceThread thread1b = waitForValue(() -> recorder.getTraceThread(mb.testThread1)); waitRecorder(recorder);
assertSame(thread1, recorder.getTraceThread(mb.testThread1));
assertNotSame(thread1a, thread1b); assertEquals(Set.of(Range.singleton(0L), Range.atLeast(2L)), object1.getLife().asRanges());
} }
@Test @Test
@@ -510,7 +513,7 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerGU
TraceObject traceBank = thread.getObject() TraceObject traceBank = thread.getObject()
.querySuccessorsTargetInterface(Range.singleton(recorder.getSnap()), .querySuccessorsTargetInterface(Range.singleton(recorder.getSnap()),
TargetRegisterBank.class) TargetRegisterBank.class)
.map(p -> p.getLastChild(thread.getObject())) .map(p -> p.getDestination(thread.getObject()))
.findAny() .findAny()
.orElseThrow(); .orElseThrow();
@@ -0,0 +1,81 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.util;
import java.util.List;
import java.util.Set;
public enum AllPathsMatcher implements PathPredicates {
INSTANCE;
@Override
public PathPredicates or(PathPredicates that) {
return this;
}
@Override
public boolean matches(List<String> path) {
return true;
}
@Override
public boolean successorCouldMatch(List<String> path, boolean strict) {
return true;
}
@Override
public boolean ancestorMatches(List<String> path, boolean strict) {
if (path.isEmpty() && strict) {
return false;
}
return true;
}
@Override
public Set<String> getNextKeys(List<String> path) {
return Set.of("", "[]");
}
@Override
public Set<String> getNextNames(List<String> path) {
return Set.of("");
}
@Override
public Set<String> getNextIndices(List<String> path) {
return Set.of("");
}
@Override
public List<String> getSingletonPath() {
return null;
}
@Override
public PathPattern getSingletonPattern() {
return null;
}
@Override
public PathPredicates applyKeys(List<String> keys) {
throw new UnsupportedOperationException();
}
@Override
public boolean isEmpty() {
return false;
}
}
@@ -54,6 +54,10 @@ public interface PathPredicates {
return new PathPattern(PathUtils.parse(pattern)); return new PathPattern(PathUtils.parse(pattern));
} }
static PathPredicates all() {
return AllPathsMatcher.INSTANCE;
}
PathPredicates or(PathPredicates that); PathPredicates or(PathPredicates that);
/** /**
@@ -81,6 +81,7 @@ public class DBTraceObjectBreakpointLocation
// Keep copies here for when the object gets invalidated // Keep copies here for when the object gets invalidated
private AddressRange range; private AddressRange range;
private Range<Long> lifespan;
public DBTraceObjectBreakpointLocation(DBTraceObject object) { public DBTraceObjectBreakpointLocation(DBTraceObject object) {
this.object = object; this.object = object;
@@ -98,29 +99,43 @@ public class DBTraceObjectBreakpointLocation
return object.getCanonicalPath().toString(); return object.getCanonicalPath().toString();
} }
@Override
public void setName(Range<Long> lifespan, String name) {
object.setValue(lifespan, TargetObject.DISPLAY_ATTRIBUTE_NAME, name);
}
@Override @Override
public void setName(String name) { public void setName(String name) {
object.setValue(getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name); try (LockHold hold = object.getTrace().lockWrite()) {
setName(getLifespan(), name);
}
} }
@Override @Override
public String getName() { public String getName() {
return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), try (LockHold hold = object.getTrace().lockRead()) {
TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, ""); return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(),
TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, "");
}
} }
@Override @Override
public void setRange(AddressRange range) { public void setRange(Range<Long> lifespan, AddressRange range) {
try (LockHold hold = object.getTrace().lockWrite()) { try (LockHold hold = object.getTrace().lockWrite()) {
object.setValue(getLifespan(), KEY_RANGE, range); object.setValue(lifespan, KEY_RANGE, range);
this.range = range; this.range = range;
} }
} }
@Override @Override
public AddressRange getRange() { public AddressRange getRange() {
return range = TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), KEY_RANGE, try (LockHold hold = object.getTrace().lockRead()) {
AddressRange.class, range); if (object.getLife().isEmpty()) {
return range;
}
return range = TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), KEY_RANGE,
AddressRange.class, range);
}
} }
@Override @Override
@@ -146,17 +161,33 @@ public class DBTraceObjectBreakpointLocation
try (LockHold hold = object.getTrace().lockWrite()) { try (LockHold hold = object.getTrace().lockWrite()) {
TraceObjectInterfaceUtils.setLifespan(TraceObjectBreakpointLocation.class, object, TraceObjectInterfaceUtils.setLifespan(TraceObjectBreakpointLocation.class, object,
lifespan); lifespan);
this.lifespan = lifespan;
} }
} }
@Override @Override
public Range<Long> getLifespan() { public Range<Long> getLifespan() {
return object.getLifespan(); try (LockHold hold = object.getTrace().lockRead()) {
Range<Long> computed = computeSpan();
if (computed != null) {
lifespan = computed;
}
return lifespan;
}
}
@Override
public Range<Long> computeSpan() {
Range<Long> span = TraceObjectBreakpointLocation.super.computeSpan();
if (span != null) {
return span;
}
return getSpecification().computeSpan();
} }
@Override @Override
public long getPlacedSnap() { public long getPlacedSnap() {
return object.getMinSnap(); return DBTraceUtils.lowerEndpoint(getLifespan());
} }
@Override @Override
@@ -168,7 +199,7 @@ public class DBTraceObjectBreakpointLocation
@Override @Override
public long getClearedSnap() { public long getClearedSnap() {
return object.getMaxSnap(); return DBTraceUtils.upperEndpoint(getLifespan());
} }
@Override @Override
@@ -183,10 +214,15 @@ public class DBTraceObjectBreakpointLocation
} }
} }
@Override
public void setEnabled(Range<Long> lifespan, boolean enabled) {
object.setValue(lifespan, TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME, enabled);
}
@Override @Override
public void setEnabled(boolean enabled) { public void setEnabled(boolean enabled) {
try (LockHold hold = object.getTrace().lockWrite()) { try (LockHold hold = object.getTrace().lockWrite()) {
object.setValue(getLifespan(), TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME, enabled); setEnabled(getLifespan(), enabled);
} }
} }
@@ -203,13 +239,20 @@ public class DBTraceObjectBreakpointLocation
} }
@Override @Override
public void setKinds(Collection<TraceBreakpointKind> kinds) { public void setKinds(Range<Long> lifespan, Collection<TraceBreakpointKind> kinds) {
try (LockHold hold = object.getTrace().lockWrite()) { try (LockHold hold = object.getTrace().lockWrite()) {
TraceObjectBreakpointSpec spec = getSpecification(); TraceObjectBreakpointSpec spec = getSpecification();
if (spec.getObject() != this.getObject()) { if (spec.getObject() != this.getObject()) {
throw new UnsupportedOperationException("Set via the specification instead"); throw new UnsupportedOperationException("Set via the specification instead");
} }
spec.setKinds(kinds); spec.setKinds(lifespan, kinds);
}
}
@Override
public void setKinds(Collection<TraceBreakpointKind> kinds) {
try (LockHold hold = object.getTrace().lockWrite()) {
setKinds(getLifespan(), kinds);
} }
} }
@@ -235,17 +278,22 @@ public class DBTraceObjectBreakpointLocation
PathMatcher procMatcher = schema.searchFor(TargetProcess.class, false); PathMatcher procMatcher = schema.searchFor(TargetProcess.class, false);
return object.getAncestors(getLifespan(), procMatcher) return object.getAncestors(getLifespan(), procMatcher)
.flatMap(proc -> proc.getFirstParent(object) .flatMap(proc -> proc.getSource(object)
.querySuccessorsInterface(getLifespan(), .querySuccessorsInterface(getLifespan(),
TraceObjectThread.class)) TraceObjectThread.class))
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
} }
@Override
public void setComment(Range<Long> lifespan, String comment) {
object.setValue(lifespan, KEY_COMMENT, comment);
}
@Override @Override
public void setComment(String comment) { public void setComment(String comment) {
try (LockHold hold = object.getTrace().lockWrite()) { try (LockHold hold = object.getTrace().lockWrite()) {
object.setValue(getLifespan(), KEY_COMMENT, comment); setComment(getLifespan(), comment);
} }
} }
@@ -257,7 +305,9 @@ public class DBTraceObjectBreakpointLocation
@Override @Override
public void delete() { public void delete() {
object.deleteTree(); try (LockHold hold = object.getTrace().lockWrite()) {
object.removeTree(computeSpan());
}
} }
@Override @Override
@@ -268,16 +318,14 @@ public class DBTraceObjectBreakpointLocation
@Override @Override
public TraceObjectBreakpointSpec getSpecification() { public TraceObjectBreakpointSpec getSpecification() {
try (LockHold hold = object.getTrace().lockRead()) { try (LockHold hold = object.getTrace().lockRead()) {
return object return object.queryCanonicalAncestorsInterface(TraceObjectBreakpointSpec.class)
.queryCanonicalAncestorsInterface(getLifespan(),
TraceObjectBreakpointSpec.class)
.findAny() .findAny()
.orElseThrow(); .orElseThrow();
} }
} }
public TraceAddressSpace getTraceAddressSpace() { public TraceAddressSpace getTraceAddressSpace() {
return spaceForValue(object.getMinSnap(), KEY_RANGE); return spaceForValue(computeMinSnap(), KEY_RANGE);
} }
@Override @Override
@@ -99,12 +99,12 @@ public class DBTraceObjectBreakpointSpec
@Override @Override
public Range<Long> getLifespan() { public Range<Long> getLifespan() {
return object.getLifespan(); return computeSpan();
} }
@Override @Override
public long getPlacedSnap() { public long getPlacedSnap() {
return object.getMinSnap(); return computeMinSnap();
} }
@Override @Override
@@ -116,7 +116,7 @@ public class DBTraceObjectBreakpointSpec
@Override @Override
public long getClearedSnap() { public long getClearedSnap() {
return object.getMaxSnap(); return computeMaxSnap();
} }
@Override @Override
@@ -144,16 +144,23 @@ public class DBTraceObjectBreakpointSpec
} }
@Override @Override
public void setKinds(Collection<TraceBreakpointKind> kinds) { public void setKinds(Range<Long> lifespan, Collection<TraceBreakpointKind> kinds) {
// TODO: More efficient encoding // TODO: More efficient encoding
// TODO: Target-Trace mapping is implied by encoded name. Seems bad. // TODO: Target-Trace mapping is implied by encoded name. Seems bad.
try (LockHold hold = object.getTrace().lockWrite()) { try (LockHold hold = object.getTrace().lockWrite()) {
object.setValue(getLifespan(), TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME, object.setValue(lifespan, TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME,
TraceBreakpointKindSet.encode(kinds)); TraceBreakpointKindSet.encode(kinds));
this.kinds = TraceBreakpointKindSet.copyOf(kinds); this.kinds = TraceBreakpointKindSet.copyOf(kinds);
} }
} }
@Override
public void setKinds(Collection<TraceBreakpointKind> kinds) {
try (LockHold hold = object.getTrace().lockWrite()) {
setKinds(getLifespan(), kinds);
}
}
@Override @Override
public Set<TraceBreakpointKind> getKinds() { public Set<TraceBreakpointKind> getKinds() {
String kindsStr = TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), String kindsStr = TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(),
@@ -187,7 +194,9 @@ public class DBTraceObjectBreakpointSpec
@Override @Override
public void delete() { public void delete() {
object.deleteTree(); try (LockHold hold = object.getTrace().lockWrite()) {
object.removeTree(computeSpan());
}
} }
@Override @Override
@@ -206,9 +215,9 @@ public class DBTraceObjectBreakpointSpec
@Override @Override
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) { public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
if (rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType()) { if (rec.getEventType() == TraceObjectChangeType.VALUE_CREATED.getType()) {
TraceChangeRecord<TraceObjectValue, Object> cast = TraceChangeRecord<TraceObjectValue, Void> cast =
TraceObjectChangeType.VALUE_CHANGED.cast(rec); TraceObjectChangeType.VALUE_CREATED.cast(rec);
TraceObjectValue affected = cast.getAffectedObject(); TraceObjectValue affected = cast.getAffectedObject();
String key = affected.getEntryKey(); String key = affected.getEntryKey();
boolean applies = TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME.equals(key) || boolean applies = TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME.equals(key) ||
@@ -98,6 +98,10 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
private final DBTraceObject object; private final DBTraceObject object;
private final RegionChangeTranslator translator; private final RegionChangeTranslator translator;
// Keep copies here for when the object gets invalidated
private AddressRange range;
private Range<Long> lifespan;
public DBTraceObjectMemoryRegion(DBTraceObject object) { public DBTraceObjectMemoryRegion(DBTraceObject object) {
this.object = object; this.object = object;
@@ -114,10 +118,15 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
return object.getCanonicalPath().toString(); return object.getCanonicalPath().toString();
} }
@Override
public void setName(Range<Long> lifespan, String name) {
object.setValue(lifespan, TargetObject.DISPLAY_ATTRIBUTE_NAME, name);
}
@Override @Override
public void setName(String name) { public void setName(String name) {
try (LockHold hold = object.getTrace().lockWrite()) { try (LockHold hold = object.getTrace().lockWrite()) {
object.setValue(getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name); setName(computeSpan(), name);
} }
} }
@@ -131,18 +140,21 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
@Override @Override
public void setLifespan(Range<Long> newLifespan) throws DuplicateNameException { public void setLifespan(Range<Long> newLifespan) throws DuplicateNameException {
try (LockHold hold = object.getTrace().lockWrite()) { try (LockHold hold = object.getTrace().lockWrite()) {
Range<Long> oldLifespan = getLifespan();
if (Objects.equals(oldLifespan, newLifespan)) {
return;
}
TraceObjectInterfaceUtils.setLifespan(TraceObjectMemoryRegion.class, object, TraceObjectInterfaceUtils.setLifespan(TraceObjectMemoryRegion.class, object,
newLifespan); newLifespan);
this.lifespan = newLifespan;
} }
} }
@Override @Override
public Range<Long> getLifespan() { public Range<Long> getLifespan() {
return object.getLifespan(); try (LockHold hold = object.getTrace().lockRead()) {
Range<Long> computed = computeSpan();
if (computed != null) {
lifespan = computed;
}
return lifespan;
}
} }
@Override @Override
@@ -154,7 +166,7 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
@Override @Override
public long getCreationSnap() { public long getCreationSnap() {
return object.getMinSnap(); return computeMinSnap();
} }
@Override @Override
@@ -166,25 +178,32 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
@Override @Override
public long getDestructionSnap() { public long getDestructionSnap() {
return object.getMaxSnap(); return computeMaxSnap();
}
@Override
public void setRange(Range<Long> lifespan, AddressRange newRange) {
try (LockHold hold = object.getTrace().lockWrite()) {
object.setValue(lifespan, TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, newRange);
this.range = newRange;
}
} }
@Override @Override
public void setRange(AddressRange newRange) { public void setRange(AddressRange newRange) {
try (LockHold hold = object.getTrace().lockWrite()) { try (LockHold hold = object.getTrace().lockWrite()) {
AddressRange oldRange = getRange(); setRange(computeSpan(), newRange);
if (Objects.equals(oldRange, newRange)) {
return;
}
object.setValue(getLifespan(), TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, newRange);
} }
} }
@Override @Override
public AddressRange getRange() { public AddressRange getRange() {
try (LockHold hold = object.getTrace().lockRead()) { try (LockHold hold = object.getTrace().lockRead()) {
return TraceObjectInterfaceUtils.getValue(object, getCreationSnap(), if (object.getLife().isEmpty()) {
TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, AddressRange.class, null); return range;
}
return range = TraceObjectInterfaceUtils.getValue(object, getCreationSnap(),
TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, AddressRange.class, range);
} }
} }
@@ -311,7 +330,7 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
@Override @Override
public void delete() { public void delete() {
try (LockHold hold = object.getTrace().lockWrite()) { try (LockHold hold = object.getTrace().lockWrite()) {
object.deleteTree(); object.removeTree(computeSpan());
} }
} }
@@ -45,7 +45,7 @@ public class DBTraceObjectRegister implements TraceObjectRegister, DBTraceObject
@Override @Override
public TraceObjectThread getThread() { public TraceObjectThread getThread() {
return object.queryAncestorsInterface(object.getLifespan(), TraceObjectThread.class) return object.queryCanonicalAncestorsInterface(TraceObjectThread.class)
.findAny() .findAny()
.orElseThrow(); .orElseThrow();
} }
@@ -61,7 +61,7 @@ public class DBTraceObjectRegister implements TraceObjectRegister, DBTraceObject
@Override @Override
public int getLength() { public int getLength() {
return TraceObjectInterfaceUtils.getValue(object, object.getMinSnap(), return TraceObjectInterfaceUtils.getValue(object, computeMinSnap(),
TargetRegister.LENGTH_ATTRIBUTE_NAME, Integer.class, 0); TargetRegister.LENGTH_ATTRIBUTE_NAME, Integer.class, 0);
} }
@@ -74,6 +74,10 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte
private final DBTraceObject object; private final DBTraceObject object;
private final ModuleChangeTranslator translator; private final ModuleChangeTranslator translator;
// Keep copies here for when the object gets invalidated
private AddressRange range;
private Range<Long> lifespan;
public DBTraceObjectModule(DBTraceObject object) { public DBTraceObjectModule(DBTraceObject object) {
this.object = object; this.object = object;
@@ -104,10 +108,15 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte
return object.getCanonicalPath().toString(); return object.getCanonicalPath().toString();
} }
@Override
public void setName(Range<Long> lifespan, String name) {
object.setValue(lifespan, TargetModule.MODULE_NAME_ATTRIBUTE_NAME, name);
}
@Override @Override
public void setName(String name) { public void setName(String name) {
try (LockHold hold = object.getTrace().lockWrite()) { try (LockHold hold = object.getTrace().lockWrite()) {
object.setValue(getLifespan(), TargetModule.MODULE_NAME_ATTRIBUTE_NAME, name); setName(computeSpan(), name);
} }
} }
@@ -117,17 +126,30 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte
TargetModule.MODULE_NAME_ATTRIBUTE_NAME, String.class, ""); TargetModule.MODULE_NAME_ATTRIBUTE_NAME, String.class, "");
} }
@Override
public void setRange(Range<Long> lifespan, AddressRange range) {
try (LockHold hold = object.getTrace().lockWrite()) {
object.setValue(lifespan, TargetModule.RANGE_ATTRIBUTE_NAME, range);
this.range = range;
}
}
@Override @Override
public void setRange(AddressRange range) { public void setRange(AddressRange range) {
try (LockHold hold = object.getTrace().lockWrite()) { try (LockHold hold = object.getTrace().lockWrite()) {
object.setValue(getLifespan(), TargetModule.RANGE_ATTRIBUTE_NAME, range); setRange(computeSpan(), range);
} }
} }
@Override @Override
public AddressRange getRange() { public AddressRange getRange() {
return TraceObjectInterfaceUtils.getValue(object, getLoadedSnap(), try (LockHold hold = object.getTrace().lockRead()) {
TargetModule.RANGE_ATTRIBUTE_NAME, AddressRange.class, null); if (object.getLife().isEmpty()) {
return range;
}
return range = TraceObjectInterfaceUtils.getValue(object, getLoadedSnap(),
TargetModule.RANGE_ATTRIBUTE_NAME, AddressRange.class, range);
}
} }
@Override @Override
@@ -172,15 +194,23 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte
public void setLifespan(Range<Long> lifespan) throws DuplicateNameException { public void setLifespan(Range<Long> lifespan) throws DuplicateNameException {
try (LockHold hold = object.getTrace().lockWrite()) { try (LockHold hold = object.getTrace().lockWrite()) {
TraceObjectInterfaceUtils.setLifespan(TraceObjectModule.class, object, lifespan); TraceObjectInterfaceUtils.setLifespan(TraceObjectModule.class, object, lifespan);
this.lifespan = lifespan;
for (TraceObjectSection section : getSections()) { for (TraceObjectSection section : getSections()) {
section.getObject().setLifespan(lifespan); TraceObjectInterfaceUtils.setLifespan(TraceObjectSection.class, section.getObject(),
lifespan);
} }
} }
} }
@Override @Override
public Range<Long> getLifespan() { public Range<Long> getLifespan() {
return object.getLifespan(); try (LockHold hold = object.getTrace().lockRead()) {
Range<Long> computed = computeSpan();
if (computed != null) {
lifespan = computed;
}
return lifespan;
}
} }
@Override @Override
@@ -192,7 +222,7 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte
@Override @Override
public long getLoadedSnap() { public long getLoadedSnap() {
return object.getMinSnap(); return computeMinSnap();
} }
@Override @Override
@@ -204,7 +234,7 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte
@Override @Override
public long getUnloadedSnap() { public long getUnloadedSnap() {
return object.getMaxSnap(); return computeMaxSnap();
} }
@Override @Override
@@ -220,14 +250,16 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte
PathMatcher matcher = object.getTargetSchema().searchFor(TargetSection.class, true); PathMatcher matcher = object.getTargetSchema().searchFor(TargetSection.class, true);
PathMatcher applied = matcher.applyKeys(List.of(sectionName)); PathMatcher applied = matcher.applyKeys(List.of(sectionName));
return object.getSuccessors(getLifespan(), applied) return object.getSuccessors(getLifespan(), applied)
.map(p -> p.getLastChild(object).queryInterface(TraceObjectSection.class)) .map(p -> p.getDestination(object).queryInterface(TraceObjectSection.class))
.findAny() .findAny()
.orElse(null); .orElse(null);
} }
@Override @Override
public void delete() { public void delete() {
object.deleteTree(); try (LockHold hold = object.getTrace().lockWrite()) {
object.removeTree(computeSpan());
}
} }
@Override @Override
@@ -23,13 +23,13 @@ import ghidra.trace.database.target.DBTraceObject;
import ghidra.trace.database.target.DBTraceObjectInterface; import ghidra.trace.database.target.DBTraceObjectInterface;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceSectionChangeType; import ghidra.trace.model.Trace.TraceSectionChangeType;
import ghidra.trace.model.modules.*; import ghidra.trace.model.modules.TraceObjectModule;
import ghidra.trace.model.modules.TraceSection;
import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils; import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils;
import ghidra.trace.util.TraceChangeRecord; import ghidra.trace.util.TraceChangeRecord;
import ghidra.trace.util.TraceChangeType; import ghidra.trace.util.TraceChangeType;
import ghidra.util.LockHold; import ghidra.util.LockHold;
import ghidra.util.exception.DuplicateNameException;
public class DBTraceObjectSection implements TraceObjectSection, DBTraceObjectInterface { public class DBTraceObjectSection implements TraceObjectSection, DBTraceObjectInterface {
@@ -68,6 +68,9 @@ public class DBTraceObjectSection implements TraceObjectSection, DBTraceObjectIn
private final DBTraceObject object; private final DBTraceObject object;
private final SectionTranslator translator; private final SectionTranslator translator;
// Keep copies here for when the object gets invalidated
private AddressRange range;
public DBTraceObjectSection(DBTraceObject object) { public DBTraceObjectSection(DBTraceObject object) {
this.object = object; this.object = object;
@@ -80,9 +83,9 @@ public class DBTraceObjectSection implements TraceObjectSection, DBTraceObjectIn
} }
@Override @Override
public TraceModule getModule() { public TraceObjectModule getModule() {
try (LockHold hold = object.getTrace().lockRead()) { try (LockHold hold = object.getTrace().lockRead()) {
return object.queryAncestorsInterface(object.getLifespan(), TraceObjectModule.class) return object.queryCanonicalAncestorsInterface(TraceObjectModule.class)
.findAny() .findAny()
.orElseThrow(); .orElseThrow();
} }
@@ -94,30 +97,56 @@ public class DBTraceObjectSection implements TraceObjectSection, DBTraceObjectIn
} }
@Override @Override
public void setName(String name) throws DuplicateNameException { public void setName(Range<Long> lifespan, String name) {
object.setValue(object.getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name); object.setValue(lifespan, TargetObject.DISPLAY_ATTRIBUTE_NAME, name);
}
@Override
public void setName(String name) {
try (LockHold hold = object.getTrace().lockWrite()) {
setName(computeSpan(), name);
}
} }
@Override @Override
public String getName() { public String getName() {
return TraceObjectInterfaceUtils.getValue(object, object.getMinSnap(), return TraceObjectInterfaceUtils.getValue(object, computeMinSnap(),
TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, ""); TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, "");
} }
@Override @Override
public void setRange(AddressRange range) { public void setRange(Range<Long> lifespan, AddressRange range) {
object.setValue(object.getLifespan(), TargetModule.RANGE_ATTRIBUTE_NAME, range); try (LockHold hold = object.getTrace().lockWrite()) {
object.setValue(lifespan, TargetModule.RANGE_ATTRIBUTE_NAME, range);
this.range = range;
}
} }
@Override @Override
public AddressRange getRange() { public AddressRange getRange() {
return TraceObjectInterfaceUtils.getValue(object, object.getMinSnap(), try (LockHold hold = object.getTrace().lockRead()) {
TargetModule.RANGE_ATTRIBUTE_NAME, AddressRange.class, null); if (object.getLife().isEmpty()) {
return range;
}
return range = TraceObjectInterfaceUtils.getValue(object, computeMinSnap(),
TargetModule.RANGE_ATTRIBUTE_NAME, AddressRange.class, range);
}
}
@Override
public Range<Long> computeSpan() {
Range<Long> span = DBTraceObjectInterface.super.computeSpan();
if (span != null) {
return span;
}
return getModule().computeSpan();
} }
@Override @Override
public void delete() { public void delete() {
object.deleteTree(); try (LockHold hold = object.getTrace().lockWrite()) {
object.removeTree(computeSpan());
}
} }
@Override @Override
@@ -15,6 +15,8 @@
*/ */
package ghidra.trace.database.module; package ghidra.trace.database.module;
import com.google.common.collect.Range;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetSection; import ghidra.dbg.target.TargetSection;
import ghidra.program.model.address.AddressRange; import ghidra.program.model.address.AddressRange;
@@ -30,5 +32,7 @@ import ghidra.trace.model.target.annot.TraceObjectInfo;
TargetSection.RANGE_ATTRIBUTE_NAME TargetSection.RANGE_ATTRIBUTE_NAME
}) })
public interface TraceObjectSection extends TraceSection, TraceObjectInterface { public interface TraceObjectSection extends TraceSection, TraceObjectInterface {
void setRange(AddressRange range); void setName(Range<Long> lifespan, String name);
void setRange(Range<Long> lifespan, AddressRange range);
} }
@@ -80,7 +80,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
@Override @Override
public TraceThread getThread() { public TraceThread getThread() {
try (LockHold hold = object.getTrace().lockRead()) { try (LockHold hold = object.getTrace().lockRead()) {
return object.queryAncestorsInterface(object.getLifespan(), TraceObjectThread.class) return object.queryAncestorsInterface(computeSpan(), TraceObjectThread.class)
.findAny() .findAny()
.orElseThrow(); .orElseThrow();
} }
@@ -88,14 +88,14 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
@Override @Override
public long getSnap() { public long getSnap() {
return object.getMinSnap(); return computeMinSnap();
} }
@Override @Override
public int getDepth() { public int getDepth() {
try (LockHold hold = object.getTrace().lockRead()) { try (LockHold hold = object.getTrace().lockRead()) {
return object return object
.querySuccessorsInterface(object.getLifespan(), TraceObjectStackFrame.class) .querySuccessorsInterface(computeSpan(), TraceObjectStackFrame.class)
.map(f -> f.getLevel()) .map(f -> f.getLevel())
.reduce(Integer::max) .reduce(Integer::max)
.map(m -> m + 1) .map(m -> m + 1)
@@ -119,8 +119,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
protected void copyFrameAttributes(TraceObjectStackFrame from, TraceObjectStackFrame to) { protected void copyFrameAttributes(TraceObjectStackFrame from, TraceObjectStackFrame to) {
// TODO: All attributes within a given span, intersected to that span? // TODO: All attributes within a given span, intersected to that span?
to.setProgramCounter(to.getObject().getLifespan(), to.setProgramCounter(computeSpan(), from.getProgramCounter(computeMaxSnap()));
from.getProgramCounter(from.getObject().getMaxSnap()));
} }
protected void shiftFrameAttributes(int from, int to, int count, protected void shiftFrameAttributes(int from, int to, int count,
@@ -143,7 +142,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
protected void clearFrameAttributes(int start, int end, List<TraceObjectStackFrame> frames) { protected void clearFrameAttributes(int start, int end, List<TraceObjectStackFrame> frames) {
for (int i = start; i < end; i++) { for (int i = start; i < end; i++) {
TraceObjectStackFrame frame = frames.get(i); TraceObjectStackFrame frame = frames.get(i);
frame.setProgramCounter(frame.getObject().getLifespan(), null); frame.setProgramCounter(frame.computeSpan(), null);
} }
} }
@@ -152,7 +151,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
// TODO: Need a span parameter // TODO: Need a span parameter
try (LockHold hold = object.getTrace().lockWrite()) { try (LockHold hold = object.getTrace().lockWrite()) {
List<TraceObjectStackFrame> frames = // Want mutable list List<TraceObjectStackFrame> frames = // Want mutable list
doGetFrames().collect(Collectors.toCollection(ArrayList::new)); doGetFrames(computeMinSnap()).collect(Collectors.toCollection(ArrayList::new));
int curDepth = frames.size(); int curDepth = frames.size();
if (curDepth == depth) { if (curDepth == depth) {
return; return;
@@ -163,7 +162,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
shiftFrameAttributes(diff, 0, depth, frames); shiftFrameAttributes(diff, 0, depth, frames);
} }
for (int i = depth; i < curDepth; i++) { for (int i = depth; i < curDepth; i++) {
frames.get(i).getObject().deleteTree(); frames.get(i).getObject().removeTree(computeSpan());
} }
} }
else { else {
@@ -183,9 +182,9 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
TargetObjectSchema schema = object.getTargetSchema(); TargetObjectSchema schema = object.getTargetSchema();
PathPredicates matcher = schema.searchFor(TargetStackFrame.class, true); PathPredicates matcher = schema.searchFor(TargetStackFrame.class, true);
matcher = matcher.applyKeys(PathUtils.makeIndex(level)); matcher = matcher.applyKeys(PathUtils.makeIndex(level));
return object.getSuccessors(object.getLifespan(), matcher) return object.getSuccessors(computeSpan(), matcher)
.findAny() .findAny()
.map(p -> p.getLastChild(object).queryInterface(TraceObjectStackFrame.class)) .map(p -> p.getDestination(object).queryInterface(TraceObjectStackFrame.class))
.orElse(null); .orElse(null);
} }
@@ -207,22 +206,24 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
} }
} }
protected Stream<TraceObjectStackFrame> doGetFrames() { protected Stream<TraceObjectStackFrame> doGetFrames(long snap) {
return object return object
.querySuccessorsInterface(object.getLifespan(), TraceObjectStackFrame.class) .querySuccessorsInterface(Range.singleton(snap), TraceObjectStackFrame.class)
.sorted(Comparator.comparing(f -> f.getLevel())); .sorted(Comparator.comparing(f -> f.getLevel()));
} }
@Override @Override
public List<TraceStackFrame> getFrames() { public List<TraceStackFrame> getFrames(long snap) {
try (LockHold hold = object.getTrace().lockRead()) { try (LockHold hold = object.getTrace().lockRead()) {
return doGetFrames().collect(Collectors.toList()); return doGetFrames(snap).collect(Collectors.toList());
} }
} }
@Override @Override
public void delete() { public void delete() {
object.deleteTree(); try (LockHold hold = object.getTrace().lockWrite()) {
object.removeTree(computeSpan());
}
} }
@Override @Override
@@ -234,4 +235,9 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) { public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
return translator.translate(rec); return translator.translate(rec);
} }
@Override
public boolean hasFixedFrames() {
return false;
}
} }
@@ -17,12 +17,13 @@ package ghidra.trace.database.stack;
import java.util.List; import java.util.List;
import com.google.common.collect.Range; import com.google.common.collect.*;
import ghidra.dbg.target.TargetStackFrame; import ghidra.dbg.target.TargetStackFrame;
import ghidra.dbg.util.PathUtils; import ghidra.dbg.util.PathUtils;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.CodeUnit; import ghidra.program.model.listing.CodeUnit;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.database.target.DBTraceObject; import ghidra.trace.database.target.DBTraceObject;
import ghidra.trace.database.target.DBTraceObjectInterface; import ghidra.trace.database.target.DBTraceObjectInterface;
import ghidra.trace.model.Trace.TraceObjectChangeType; import ghidra.trace.model.Trace.TraceObjectChangeType;
@@ -32,12 +33,14 @@ import ghidra.trace.model.stack.TraceObjectStackFrame;
import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectValue; import ghidra.trace.model.target.TraceObjectValue;
import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils; import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.trace.util.TraceChangeRecord; import ghidra.trace.util.TraceChangeRecord;
import ghidra.util.LockHold; import ghidra.util.LockHold;
public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceObjectInterface { public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceObjectInterface {
private final DBTraceObject object; private final DBTraceObject object;
// TODO: Memorizing life is not optimal.
// GP-1887 means to expose multiple lifespans in, e.g., TraceThread
private RangeSet<Long> life = TreeRangeSet.create();
public DBTraceObjectStackFrame(DBTraceObject object) { public DBTraceObjectStackFrame(DBTraceObject object) {
this.object = object; this.object = object;
@@ -46,8 +49,7 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
@Override @Override
public TraceObjectStack getStack() { public TraceObjectStack getStack() {
try (LockHold hold = object.getTrace().lockRead()) { try (LockHold hold = object.getTrace().lockRead()) {
return object return object.queryCanonicalAncestorsInterface(TraceObjectStack.class)
.queryCanonicalAncestorsInterface(object.getLifespan(), TraceObjectStack.class)
.findAny() .findAny()
.orElseThrow(); .orElseThrow();
} }
@@ -85,16 +87,14 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
if (pc == Address.NO_ADDRESS) { if (pc == Address.NO_ADDRESS) {
pc = null; pc = null;
} }
object.setValue(object.getLifespan().intersection(span), object.setValue(span, TargetStackFrame.PC_ATTRIBUTE_NAME, pc);
TargetStackFrame.PC_ATTRIBUTE_NAME, pc);
} }
} }
@Override @Override
public String getComment() { public String getComment(long snap) {
// TODO: Do I need to add a snap argument?
// TODO: One day, we'll have dynamic columns in the debugger // TODO: One day, we'll have dynamic columns in the debugger
/** /*
* I don't use an attribute for this, because there's not a nice way track the "identity" of * I don't use an attribute for this, because there's not a nice way track the "identity" of
* a stack frame. If the frame is re-used (the recommendation for connector development), * a stack frame. If the frame is re-used (the recommendation for connector development),
* the same comment may not necessarily apply. It'd be nice if the connector re-assigned * the same comment may not necessarily apply. It'd be nice if the connector re-assigned
@@ -105,21 +105,23 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
* follow the "same frame" as its level changes. * follow the "same frame" as its level changes.
*/ */
try (LockHold hold = object.getTrace().lockRead()) { try (LockHold hold = object.getTrace().lockRead()) {
Address pc = getProgramCounter(object.getMaxSnap()); Address pc = getProgramCounter(snap);
return pc == null ? null return pc == null ? null
: object.getTrace() : object.getTrace()
.getCommentAdapter() .getCommentAdapter()
.getComment(object.getMaxSnap(), pc, CodeUnit.EOL_COMMENT); .getComment(snap, pc, CodeUnit.EOL_COMMENT);
} }
} }
@Override @Override
public void setComment(String comment) { public void setComment(long snap, String comment) {
// TODO: Do I need to add a span argument? /* See rant in getComment */
try (LockHold hold = object.getTrace().lockWrite()) { try (LockHold hold = object.getTrace().lockWrite()) {
TraceObjectValue pcAttr =
object.getValue(snap, TargetStackFrame.PC_ATTRIBUTE_NAME);
object.getTrace() object.getTrace()
.getCommentAdapter() .getCommentAdapter()
.setComment(object.getLifespan(), getProgramCounter(object.getMaxSnap()), .setComment(pcAttr.getLifespan(), (Address) pcAttr.getValue(),
CodeUnit.EOL_COMMENT, comment); CodeUnit.EOL_COMMENT, comment);
} }
} }
@@ -130,8 +132,8 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
} }
protected boolean changeApplies(TraceChangeRecord<?, ?> rec) { protected boolean changeApplies(TraceChangeRecord<?, ?> rec) {
TraceChangeRecord<TraceObjectValue, Object> cast = TraceChangeRecord<TraceObjectValue, Void> cast =
TraceObjectChangeType.VALUE_CHANGED.cast(rec); TraceObjectChangeType.VALUE_CREATED.cast(rec);
TraceObjectValue affected = cast.getAffectedObject(); TraceObjectValue affected = cast.getAffectedObject();
assert affected.getParent() == object; assert affected.getParent() == object;
if (!TargetStackFrame.PC_ATTRIBUTE_NAME.equals(affected.getEntryKey())) { if (!TargetStackFrame.PC_ATTRIBUTE_NAME.equals(affected.getEntryKey())) {
@@ -143,24 +145,45 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
return true; return true;
} }
protected long snapFor(TraceChangeRecord<?, ?> rec) { @Override
if (rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType()) { public Range<Long> computeSpan() {
return TraceObjectChangeType.VALUE_CHANGED.cast(rec).getAffectedObject().getMinSnap(); Range<Long> span = DBTraceObjectInterface.super.computeSpan();
if (span != null) {
return span;
} }
return object.getMinSnap(); return getStack().computeSpan();
}
protected long snapFor(TraceChangeRecord<?, ?> rec) {
if (rec.getEventType() == TraceObjectChangeType.VALUE_CREATED.getType()) {
return TraceObjectChangeType.VALUE_CREATED.cast(rec).getAffectedObject().getMinSnap();
}
return computeMinSnap();
}
protected TraceChangeRecord<?, ?> createChangeRecord() {
return new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, getStack(), 0L,
DBTraceUtils.lowerEndpoint(life.span()));
} }
@Override @Override
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) { public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
if (rec.getEventType() == TraceObjectChangeType.INSERTED.getType() || int type = rec.getEventType();
rec.getEventType() == TraceObjectChangeType.DELETED.getType() || if (type == TraceObjectChangeType.LIFE_CHANGED.getType()) {
rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType() && RangeSet<Long> newLife = object.getLife();
changeApplies(rec)) { if (!newLife.isEmpty()) {
TraceAddressSpace space = life = newLife;
spaceForValue(object.getMinSnap(), TargetStackFrame.PC_ATTRIBUTE_NAME); }
TraceObjectStack stack = getStack(); return createChangeRecord();
return new TraceChangeRecord<>(TraceStackChangeType.CHANGED, space, stack, }
0L, snapFor(rec)); else if (type == TraceObjectChangeType.VALUE_CREATED.getType() && changeApplies(rec)) {
return createChangeRecord();
}
else if (type == TraceObjectChangeType.DELETED.getType()) {
if (life.isEmpty()) {
return null;
}
return createChangeRecord();
} }
return null; return null;
} }
@@ -237,7 +237,7 @@ public class DBTraceStack extends DBAnnotatedObject implements TraceStack {
} }
@Override @Override
public List<TraceStackFrame> getFrames() { public List<TraceStackFrame> getFrames(long snap) {
try (LockHold hold = LockHold.lock(manager.lock.readLock())) { try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
return List.copyOf(frames); return List.copyOf(frames);
} }
@@ -254,4 +254,9 @@ public class DBTraceStack extends DBAnnotatedObject implements TraceStack {
manager.trace manager.trace
.setChanged(new TraceChangeRecord<>(TraceStackChangeType.DELETED, null, this)); .setChanged(new TraceChangeRecord<>(TraceStackChangeType.DELETED, null, this));
} }
@Override
public boolean hasFixedFrames() {
return true;
}
} }
@@ -122,12 +122,12 @@ public class DBTraceStackFrame extends DBAnnotatedObject
} }
@Override @Override
public String getComment() { public String getComment(long snap) {
return comment; return comment;
} }
@Override @Override
public void setComment(String comment) { public void setComment(long snap, String comment) {
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
this.comment = comment; this.comment = comment;
update(COMMENT_COLUMN); update(COMMENT_COLUMN);
@@ -15,12 +15,9 @@
*/ */
package ghidra.trace.database.target; package ghidra.trace.database.target;
import java.util.stream.Stream;
import com.google.common.collect.Range; import com.google.common.collect.Range;
import db.DBRecord; import db.DBRecord;
import ghidra.dbg.util.PathPredicates;
import ghidra.trace.database.DBTraceUtils; import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData;
@@ -124,6 +121,11 @@ public class DBTraceObjectAddressRangeValue
throw new ClassCastException(); throw new ClassCastException();
} }
@Override
public DBTraceObject getChildOrNull() {
return null;
}
@Override @Override
public boolean isCanonical() { public boolean isCanonical() {
return false; return false;
@@ -162,22 +164,6 @@ public class DBTraceObjectAddressRangeValue
} }
} }
@Override
public Stream<? extends DBTraceObjectValPath> doGetSuccessors(Range<Long> span,
DBTraceObjectValPath pre, PathPredicates predicates) {
DBTraceObjectValPath path = pre == null ? DBTraceObjectValPath.of() : pre.append(this);
if (predicates.matches(path.getKeyList())) {
return Stream.of(path);
}
return Stream.empty();
}
@Override
public Stream<? extends DBTraceObjectValPath> doGetOrderedSuccessors(Range<Long> span,
DBTraceObjectValPath pre, PathPredicates predicates, boolean forward) {
return doGetSuccessors(span, pre, predicates);
}
@Override @Override
public void doDelete() { public void doDelete() {
manager.rangeValueMap.deleteData(this); manager.rangeValueMap.deleteData(this);
@@ -186,19 +172,14 @@ public class DBTraceObjectAddressRangeValue
@Override @Override
public void delete() { public void delete() {
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
doDelete(); doDeleteAndEmit();
} }
} }
@Override
public void deleteTree() {
delete();
}
@Override @Override
public TraceObjectValue truncateOrDelete(Range<Long> span) { public TraceObjectValue truncateOrDelete(Range<Long> span) {
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
return doTruncateOrDelete(span); return doTruncateOrDeleteAndEmitLifeChange(span);
} }
} }
} }
@@ -15,8 +15,9 @@
*/ */
package ghidra.trace.database.target; package ghidra.trace.database.target;
import com.google.common.collect.Range; import com.google.common.collect.*;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.model.Trace.TraceObjectChangeType; import ghidra.trace.model.Trace.TraceObjectChangeType;
import ghidra.trace.model.TraceUniqueObject; import ghidra.trace.model.TraceUniqueObject;
import ghidra.trace.model.target.*; import ghidra.trace.model.target.*;
@@ -29,6 +30,9 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
private final String spaceValueKey; private final String spaceValueKey;
private final DBTraceObject object; private final DBTraceObject object;
private final T iface; private final T iface;
// TODO: Memorizing life is not optimal.
// GP-1887 means to expose multiple lifespans in, e.g., TraceThread
private RangeSet<Long> life = TreeRangeSet.create();
public Translator(String spaceValueKey, DBTraceObject object, T iface) { public Translator(String spaceValueKey, DBTraceObject object, T iface) {
this.spaceValueKey = spaceValueKey; this.spaceValueKey = spaceValueKey;
@@ -63,35 +67,67 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
// Extension point // Extension point
} }
public TraceChangeRecord<?, ?> translate(TraceChangeRecord<?, ?> rec) { protected TraceAddressSpace getSpace(RangeSet<Long> life) {
TraceAddressSpace space = spaceValueKey == null ? null if (life.isEmpty()) {
: spaceForValue(object, object.getMinSnap(), spaceValueKey); return null;
if (rec.getEventType() == TraceObjectChangeType.INSERTED.getType()) {
TraceChangeType<T, Void> type = getAddedType();
if (type == null) {
return null;
}
assert rec.getAffectedObject() == object;
emitExtraAdded();
return new TraceChangeRecord<>(type, space, iface, null,
null);
} }
if (rec.getEventType() == TraceObjectChangeType.LIFESPAN_CHANGED.getType()) { return spaceValueKey == null ? null
: spaceForValue(object, DBTraceUtils.lowerEndpoint(life.span()), spaceValueKey);
}
protected TraceChangeRecord<?, ?> translateAdded() {
TraceChangeType<T, Void> type = getAddedType();
if (type == null) {
return null;
}
emitExtraAdded();
return new TraceChangeRecord<>(type, getSpace(life), iface, null, null);
}
protected TraceChangeRecord<?, ?> translateLifespanChanged(RangeSet<Long> oldLife) {
TraceChangeType<T, Range<Long>> type = getLifespanChangedType();
if (type == null) {
return null;
}
Range<Long> oldLifespan = oldLife.span();
Range<Long> newLifespan = life.span();
emitExtraLifespanChanged(oldLifespan, newLifespan);
return new TraceChangeRecord<>(type, getSpace(life), iface, oldLifespan, newLifespan);
}
protected TraceChangeRecord<?, ?> translateDeleted(RangeSet<Long> life) {
TraceChangeType<T, Void> type = getDeletedType();
if (type == null) {
return null;
}
emitExtraDeleted();
return new TraceChangeRecord<>(type, getSpace(life), iface, null, null);
}
public TraceChangeRecord<?, ?> translate(TraceChangeRecord<?, ?> rec) {
if (rec.getEventType() == TraceObjectChangeType.LIFE_CHANGED.getType()) {
if (object.isDeleted()) { if (object.isDeleted()) {
return null; return null;
} }
TraceChangeType<T, Range<Long>> type = getLifespanChangedType();
if (type == null) {
return null;
}
assert rec.getAffectedObject() == object; assert rec.getAffectedObject() == object;
TraceChangeRecord<TraceObject, Range<Long>> cast = RangeSet<Long> oldLife = life;
TraceObjectChangeType.LIFESPAN_CHANGED.cast(rec); life = object.getLife();
emitExtraLifespanChanged(cast.getOldValue(), cast.getNewValue()); boolean oldHasLife = !oldLife.isEmpty();
return new TraceChangeRecord<>(type, space, iface, boolean newHasLife = !life.isEmpty();
cast.getOldValue(), cast.getNewValue()); if (newHasLife && oldHasLife) {
return translateLifespanChanged(oldLife);
}
else if (newHasLife) {
return translateAdded();
}
else if (oldHasLife) {
return translateDeleted(oldLife);
}
else {
throw new AssertionError("Life changed from empty to empty?");
}
} }
if (rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType()) { if (rec.getEventType() == TraceObjectChangeType.VALUE_CREATED.getType()) {
if (object.isDeleted()) { if (object.isDeleted()) {
return null; return null;
} }
@@ -99,8 +135,8 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
if (type == null) { if (type == null) {
return null; return null;
} }
TraceChangeRecord<TraceObjectValue, Object> cast = TraceChangeRecord<TraceObjectValue, Void> cast =
TraceObjectChangeType.VALUE_CHANGED.cast(rec); TraceObjectChangeType.VALUE_CREATED.cast(rec);
TraceObjectValue affected = cast.getAffectedObject(); TraceObjectValue affected = cast.getAffectedObject();
String key = affected.getEntryKey(); String key = affected.getEntryKey();
if (!appliesToKey(key)) { if (!appliesToKey(key)) {
@@ -112,16 +148,10 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
} }
emitExtraValueChanged(affected.getLifespan(), key, cast.getOldValue(), emitExtraValueChanged(affected.getLifespan(), key, cast.getOldValue(),
cast.getNewValue()); cast.getNewValue());
return new TraceChangeRecord<>(type, space, iface, null, null); return new TraceChangeRecord<>(type, getSpace(life), iface, null, null);
} }
if (rec.getEventType() == TraceObjectChangeType.DELETED.getType()) { if (rec.getEventType() == TraceObjectChangeType.DELETED.getType()) {
TraceChangeType<T, Void> type = getDeletedType(); return translateDeleted(life);
if (type == null) {
return null;
}
assert rec.getAffectedObject() == object;
emitExtraDeleted();
return new TraceChangeRecord<>(type, space, iface, null, null);
} }
return null; return null;
} }
@@ -162,6 +192,6 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
@Override @Override
default boolean isDeleted() { default boolean isDeleted() {
return getObject().isDeleted(); return getObject().getLife().isEmpty();
} }
} }
@@ -163,6 +163,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
protected final DBCachedObjectIndex<DBTraceObject, DBTraceObjectValue> valuesByChild; protected final DBCachedObjectIndex<DBTraceObject, DBTraceObjectValue> valuesByChild;
protected final Collection<TraceObject> objectsView; protected final Collection<TraceObject> objectsView;
protected final Collection<TraceObjectValue> valuesView;
protected TargetObjectSchema rootSchema; protected TargetObjectSchema rootSchema;
@@ -193,6 +194,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
valueStore.getIndex(DBTraceObject.class, DBTraceObjectValue.CHILD_COLUMN); valueStore.getIndex(DBTraceObject.class, DBTraceObjectValue.CHILD_COLUMN);
objectsView = Collections.unmodifiableCollection(objectStore.asMap().values()); objectsView = Collections.unmodifiableCollection(objectStore.asMap().values());
valuesView = Collections.unmodifiableCollection(valueStore.asMap().values());
} }
protected void loadRootSchema() { protected void loadRootSchema() {
@@ -276,18 +278,31 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
} }
DBTraceObjectValue entry = valueStore.create(); DBTraceObjectValue entry = valueStore.create();
entry.set(lifespan, parent, key, value); entry.set(lifespan, parent, key, value);
if (parent != null) {
// Don't need event for root value created
parent.emitEvents(
new TraceChangeRecord<>(TraceObjectChangeType.VALUE_CREATED, null, entry));
}
return entry; return entry;
} }
protected DBTraceObject doCreateObject(TraceObjectKeyPath path, Range<Long> lifespan) { protected DBTraceObject doCreateObject(TraceObjectKeyPath path) {
DBTraceObject obj = objectStore.create(); DBTraceObject obj = objectsByPath.getOne(path);
obj.set(path, lifespan); if (obj != null) {
return obj;
}
obj = objectStore.create();
obj.set(path);
obj.emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.CREATED, null, obj)); obj.emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.CREATED, null, obj));
return obj; return obj;
} }
protected DBTraceObject doGetObject(TraceObjectKeyPath path) {
return objectsByPath.getOne(path);
}
@Override @Override
public DBTraceObject createObject(TraceObjectKeyPath path, Range<Long> lifespan) { public DBTraceObject createObject(TraceObjectKeyPath path) {
if (path.isRoot()) { if (path.isRoot()) {
throw new IllegalArgumentException("Cannot create non-root object with root path"); throw new IllegalArgumentException("Cannot create non-root object with root path");
} }
@@ -295,7 +310,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
if (rootSchema == null) { if (rootSchema == null) {
throw new IllegalStateException("No schema! Create the root object, first."); throw new IllegalStateException("No schema! Create the root object, first.");
} }
return doCreateObject(path, lifespan); return doCreateObject(path);
} }
} }
@@ -303,7 +318,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
public TraceObjectValue createRootObject(TargetObjectSchema schema) { public TraceObjectValue createRootObject(TargetObjectSchema schema) {
try (LockHold hold = trace.lockWrite()) { try (LockHold hold = trace.lockWrite()) {
setSchema(schema); setSchema(schema);
DBTraceObject root = doCreateObject(TraceObjectKeyPath.of(), Range.all()); DBTraceObject root = doCreateObject(TraceObjectKeyPath.of());
assert root.getKey() == 0; assert root.getKey() == 0;
InternalTraceObjectValue val = doCreateValue(Range.all(), null, "", root); InternalTraceObjectValue val = doCreateValue(Range.all(), null, "", root);
assert val.getKey() == 0; assert val.getKey() == 0;
@@ -331,29 +346,29 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
} }
@Override @Override
public Collection<? extends DBTraceObject> getObjectsByCanonicalPath( public DBTraceObject getObjectByCanonicalPath(TraceObjectKeyPath path) {
TraceObjectKeyPath path) { return objectsByPath.getOne(path);
return objectsByPath.get(path);
} }
@Override @Override
public Stream<? extends DBTraceObject> getObjectsByPath(Range<Long> span, public Stream<? extends DBTraceObject> getObjectsByPath(Range<Long> span,
TraceObjectKeyPath path) { TraceObjectKeyPath path) {
DBTraceObject root = getRootObject();
return getValuePaths(span, new PathPattern(path.getKeyList())) return getValuePaths(span, new PathPattern(path.getKeyList()))
.map(p -> p.getLastChild(getRootObject())) .map(p -> p.getDestinationValue(root))
.filter(DBTraceObject.class::isInstance) .filter(DBTraceObject.class::isInstance)
.map(DBTraceObject.class::cast); .map(DBTraceObject.class::cast);
} }
@Override @Override
public Stream<? extends DBTraceObjectValPath> getValuePaths( public Stream<? extends DBTraceObjectValPath> getValuePaths(Range<Long> span,
Range<Long> span, PathPredicates predicates) { PathPredicates predicates) {
try (LockHold hold = trace.lockRead()) { try (LockHold hold = trace.lockRead()) {
DBTraceObjectValue rootVal = valueStore.getObjectAt(0); DBTraceObjectValue rootVal = valueStore.getObjectAt(0);
if (rootVal == null) { if (rootVal == null) {
return Stream.of(); return Stream.of();
} }
return rootVal.doGetSuccessors(span, null, predicates); return rootVal.doStreamVisitor(span, new InternalSuccessorsVisitor(predicates));
} }
} }
@@ -362,6 +377,11 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
return objectsView; return objectsView;
} }
@Override
public Collection<? extends TraceObjectValue> getAllValues() {
return valuesView;
}
@Override @Override
public Collection<? extends TraceObjectValue> getValuesIntersecting(Range<Long> span, public Collection<? extends TraceObjectValue> getValuesIntersecting(Range<Long> span,
AddressRange range) { AddressRange range) {
@@ -384,14 +404,25 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
PathMatcher matcher = rootSchema.searchFor(targetIf, true); PathMatcher matcher = rootSchema.searchFor(targetIf, true);
return getValuePaths(span, matcher) return getValuePaths(span, matcher)
.filter(p -> { .filter(p -> {
TraceObject object = p.getLastChild(getRootObject()); TraceObject object = p.getDestination(getRootObject());
if (object == null) { if (object == null) {
Msg.error(this, "NULL VALUE! " + p.getLastEntry()); Msg.error(this, "NULL VALUE! " + p.getLastEntry());
return false; return false;
} }
return true; return true;
}) })
.map(p -> p.getLastChild(getRootObject()).queryInterface(ifClass)); .map(p -> p.getDestination(getRootObject()).queryInterface(ifClass));
}
@Override
public void cullDisconnectedObjects() {
try (LockHold hold = trace.lockWrite()) {
for (DBTraceObject obj : objectStore.asMap().values()) {
if (!obj.doIsConnected()) {
obj.delete();
}
}
}
} }
@Override @Override
@@ -419,7 +450,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
} }
protected <I extends TraceObjectInterface> I doAddWithInterface(List<String> keyList, protected <I extends TraceObjectInterface> I doAddWithInterface(List<String> keyList,
Range<Long> lifespan, Class<I> iface, ConflictResolution resolution) { Class<I> iface) {
Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(iface); Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(iface);
TargetObjectSchema schema = rootSchema.getSuccessorSchema(keyList); TargetObjectSchema schema = rootSchema.getSuccessorSchema(keyList);
if (!schema.getInterfaces().contains(targetIf)) { if (!schema.getInterfaces().contains(targetIf)) {
@@ -427,14 +458,12 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
"Schema " + schema + " at " + PathUtils.toString(keyList) + "Schema " + schema + " at " + PathUtils.toString(keyList) +
" does not provide interface " + iface.getSimpleName()); " does not provide interface " + iface.getSimpleName());
} }
DBTraceObject obj = createObject(TraceObjectKeyPath.of(keyList), lifespan); DBTraceObject obj = createObject(TraceObjectKeyPath.of(keyList));
obj.insert(resolution);
return obj.queryInterface(iface); return obj.queryInterface(iface);
} }
protected <I extends TraceObjectInterface> I doAddWithInterface(String path, protected <I extends TraceObjectInterface> I doAddWithInterface(String path, Class<I> iface) {
Range<Long> lifespan, Class<I> iface, ConflictResolution resolution) { return doAddWithInterface(PathUtils.parse(path), iface);
return doAddWithInterface(PathUtils.parse(path), lifespan, iface, resolution);
} }
public <I extends TraceObjectInterface> Collection<I> getAllObjects(Class<I> iface) { public <I extends TraceObjectInterface> Collection<I> getAllObjects(Class<I> iface) {
@@ -523,7 +552,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
PathPredicates predicates, long snap, Class<I> iface) { PathPredicates predicates, long snap, Class<I> iface) {
try (LockHold hold = trace.lockRead()) { try (LockHold hold = trace.lockRead()) {
return seed.getSuccessors(Range.singleton(snap), predicates) return seed.getSuccessors(Range.singleton(snap), predicates)
.map(p -> p.getLastChild(seed).queryInterface(iface)) .map(p -> p.getDestination(seed).queryInterface(iface))
.filter(i -> i != null) .filter(i -> i != null)
.findAny() .findAny()
.orElse(null); .orElse(null);
@@ -534,7 +563,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
TraceObjectKeyPath path, long snap, Class<I> iface) { TraceObjectKeyPath path, long snap, Class<I> iface) {
try (LockHold hold = trace.lockRead()) { try (LockHold hold = trace.lockRead()) {
return seed.getOrderedSuccessors(Range.atMost(snap), path, false) return seed.getOrderedSuccessors(Range.atMost(snap), path, false)
.map(p -> p.getLastChild(seed).queryInterface(iface)) .map(p -> p.getDestination(seed).queryInterface(iface))
.filter(i -> i != null) .filter(i -> i != null)
.findAny() .findAny()
.orElse(null); .orElse(null);
@@ -553,14 +582,15 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
"breakpoint specification on the given path."); "breakpoint specification on the given path.");
} }
try (LockHold hold = trace.lockWrite()) { try (LockHold hold = trace.lockWrite()) {
TraceObjectBreakpointLocation loc = doAddWithInterface(path, lifespan, TraceObjectBreakpointLocation loc =
TraceObjectBreakpointLocation.class, ConflictResolution.DENY); doAddWithInterface(path, TraceObjectBreakpointLocation.class);
loc.setName(path); loc.setName(lifespan, path);
loc.setRange(range); loc.setRange(lifespan, range);
// NB. Ignore threads. I'd like to deprecate that field, anyway. // NB. Ignore threads. I'd like to deprecate that field, anyway.
loc.setKinds(kinds); loc.setKinds(lifespan, kinds);
loc.setEnabled(enabled); loc.setEnabled(lifespan, enabled);
loc.setComment(comment); loc.setComment(lifespan, comment);
loc.getObject().insert(lifespan, ConflictResolution.DENY);
return loc; return loc;
} }
catch (DuplicateKeyException e) { catch (DuplicateKeyException e) {
@@ -572,11 +602,12 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
AddressRange range, Collection<TraceMemoryFlag> flags) AddressRange range, Collection<TraceMemoryFlag> flags)
throws TraceOverlappedRegionException { throws TraceOverlappedRegionException {
try (LockHold hold = trace.lockWrite()) { try (LockHold hold = trace.lockWrite()) {
TraceObjectMemoryRegion region = doAddWithInterface(path, lifespan, TraceObjectMemoryRegion region =
TraceObjectMemoryRegion.class, ConflictResolution.TRUNCATE); doAddWithInterface(path, TraceObjectMemoryRegion.class);
region.setName(path); region.setName(lifespan, path);
region.setRange(range); region.setRange(lifespan, range);
region.setFlags(flags); region.setFlags(lifespan, flags);
region.getObject().insert(lifespan, ConflictResolution.TRUNCATE);
return region; return region;
} }
} }
@@ -584,10 +615,10 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
public TraceObjectModule addModule(String path, String name, Range<Long> lifespan, public TraceObjectModule addModule(String path, String name, Range<Long> lifespan,
AddressRange range) throws DuplicateNameException { AddressRange range) throws DuplicateNameException {
try (LockHold hold = trace.lockWrite()) { try (LockHold hold = trace.lockWrite()) {
TraceObjectModule module = doAddWithInterface(path, lifespan, TraceObjectModule.class, TraceObjectModule module = doAddWithInterface(path, TraceObjectModule.class);
ConflictResolution.DENY); module.setName(lifespan, name);
module.setName(name); module.setRange(lifespan, range);
module.setRange(range); module.getObject().insert(lifespan, ConflictResolution.DENY);
return module; return module;
} }
catch (DuplicateKeyException e) { catch (DuplicateKeyException e) {
@@ -598,10 +629,10 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
public TraceObjectSection addSection(String path, String name, Range<Long> lifespan, public TraceObjectSection addSection(String path, String name, Range<Long> lifespan,
AddressRange range) throws DuplicateNameException { AddressRange range) throws DuplicateNameException {
try (LockHold hold = trace.lockWrite()) { try (LockHold hold = trace.lockWrite()) {
TraceObjectSection section = doAddWithInterface(path, lifespan, TraceObjectSection section = doAddWithInterface(path, TraceObjectSection.class);
TraceObjectSection.class, ConflictResolution.DENY); section.setName(lifespan, name);
section.setName(name); section.setRange(lifespan, range);
section.setRange(range); section.getObject().insert(lifespan, ConflictResolution.DENY);
return section; return section;
} }
catch (DuplicateKeyException e) { catch (DuplicateKeyException e) {
@@ -611,24 +642,41 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
public TraceObjectStack addStack(List<String> keyList, long snap) { public TraceObjectStack addStack(List<String> keyList, long snap) {
try (LockHold hold = trace.lockWrite()) { try (LockHold hold = trace.lockWrite()) {
return doAddWithInterface(keyList, Range.singleton(snap), TraceObjectStack.class, TraceObjectStack stack = doAddWithInterface(keyList, TraceObjectStack.class);
ConflictResolution.DENY); stack.getObject().insert(Range.singleton(snap), ConflictResolution.DENY);
return stack;
} }
} }
public TraceObjectStackFrame addStackFrame(List<String> keyList, long snap) { public TraceObjectStackFrame addStackFrame(List<String> keyList, long snap) {
try (LockHold hold = trace.lockWrite()) { try (LockHold hold = trace.lockWrite()) {
return doAddWithInterface(keyList, Range.singleton(snap), TraceObjectStackFrame.class, TraceObjectStackFrame frame = doAddWithInterface(keyList, TraceObjectStackFrame.class);
ConflictResolution.DENY); frame.getObject().insert(Range.singleton(snap), ConflictResolution.DENY);
return frame;
} }
} }
protected void checkDuplicateThread(String path, Range<Long> lifespan)
throws DuplicateNameException {
// TODO: Change the semantics to just expand the life rather than complain of duplication
DBTraceObject exists = getObjectByCanonicalPath(TraceObjectKeyPath.parse(path));
if (exists == null) {
return;
}
if (exists.getLife().subRangeSet(lifespan).isEmpty()) {
return;
}
throw new DuplicateNameException("A thread having path '" + path +
"' already exists within an overlapping snap");
}
public TraceObjectThread addThread(String path, String display, Range<Long> lifespan) public TraceObjectThread addThread(String path, String display, Range<Long> lifespan)
throws DuplicateNameException { throws DuplicateNameException {
try (LockHold hold = trace.lockWrite()) { try (LockHold hold = trace.lockWrite()) {
TraceObjectThread thread = doAddWithInterface(path, lifespan, TraceObjectThread.class, checkDuplicateThread(path, lifespan);
ConflictResolution.DENY); TraceObjectThread thread = doAddWithInterface(path, TraceObjectThread.class);
thread.setName(display); thread.setName(lifespan, display);
thread.getObject().insert(lifespan, ConflictResolution.DENY);
return thread; return thread;
} }
catch (DuplicateKeyException e) { catch (DuplicateKeyException e) {
@@ -93,7 +93,7 @@ public class DBTraceObjectValPath implements TraceObjectValPath {
} }
@Override @Override
public TraceObject getFirstParent(TraceObject ifEmpty) { public TraceObject getSource(TraceObject ifEmpty) {
InternalTraceObjectValue first = getFirstEntry(); InternalTraceObjectValue first = getFirstEntry();
return first == null ? ifEmpty : first.getParent(); return first == null ? ifEmpty : first.getParent();
} }
@@ -107,13 +107,13 @@ public class DBTraceObjectValPath implements TraceObjectValPath {
} }
@Override @Override
public Object getLastValue(Object ifEmpty) { public Object getDestinationValue(Object ifEmpty) {
InternalTraceObjectValue last = getLastEntry(); InternalTraceObjectValue last = getLastEntry();
return last == null ? ifEmpty : last.getValue(); return last == null ? ifEmpty : last.getValue();
} }
@Override @Override
public TraceObject getLastChild(TraceObject ifEmpty) { public TraceObject getDestination(TraceObject ifEmpty) {
InternalTraceObjectValue last = getLastEntry(); InternalTraceObjectValue last = getLastEntry();
return last == null ? ifEmpty : last.getChild(); return last == null ? ifEmpty : last.getChild();
} }
@@ -27,11 +27,10 @@ import org.apache.commons.lang3.ArrayUtils;
import com.google.common.collect.Range; import com.google.common.collect.Range;
import db.*; import db.*;
import ghidra.dbg.util.PathPredicates;
import ghidra.trace.database.DBTraceUtils; import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.database.target.InternalTreeTraversal.Visitor;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectValPath;
import ghidra.util.LockHold; import ghidra.util.LockHold;
import ghidra.util.database.*; import ghidra.util.database.*;
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec; import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
@@ -276,6 +275,11 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
return (DBTraceObject) getValue(); return (DBTraceObject) getValue();
} }
@Override
public DBTraceObject getChildOrNull() {
return child;
}
@Override @Override
public Range<Long> getLifespan() { public Range<Long> getLifespan() {
try (LockHold hold = manager.trace.lockRead()) { try (LockHold hold = manager.trace.lockRead()) {
@@ -311,46 +315,9 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
} }
} }
protected Stream<TraceObjectValPath> doGetAllPaths(Range<Long> span, protected Stream<? extends DBTraceObjectValPath> doStreamVisitor(Range<Long> span,
DBTraceObjectValPath post) { Visitor visitor) {
return triple.parent.doGetAllPaths(span, post.prepend(this)); return InternalTreeTraversal.INSTANCE.walkValue(visitor, this, span, null);
}
protected Stream<? extends DBTraceObjectValPath> doGetAncestors(Range<Long> span,
DBTraceObjectValPath post, PathPredicates predicates) {
return triple.parent.doGetAncestors(span, post.prepend(this), predicates);
}
@Override
public Stream<? extends DBTraceObjectValPath> doGetSuccessors(
Range<Long> span, DBTraceObjectValPath pre, PathPredicates predicates) {
DBTraceObjectValPath path = pre == null ? DBTraceObjectValPath.of() : pre.append(this);
boolean includeMe = predicates.matches(path.getKeyList());
boolean descend = child != null;
if (includeMe && descend) {
return Stream.concat(Stream.of(path), child.doGetSuccessors(span, path, predicates));
}
if (includeMe) {
return Stream.of(path);
}
if (descend) {
return child.doGetSuccessors(span, path, predicates);
}
return Stream.empty();
}
@Override
public Stream<? extends DBTraceObjectValPath> doGetOrderedSuccessors(Range<Long> span,
DBTraceObjectValPath pre, PathPredicates predicates, boolean forward) {
DBTraceObjectValPath path = pre == null ? DBTraceObjectValPath.of() : pre.append(this);
if (predicates.matches(path.getKeyList())) {
// Singleton path, so if I match, nothing below can
return Stream.of(path);
}
if (child == null) {
return Stream.of();
}
return child.doGetOrderedSuccessors(span, path, predicates, forward);
} }
protected boolean doIsCanonical() { protected boolean doIsCanonical() {
@@ -370,13 +337,6 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
} }
} }
protected void doDeleteSuccessors() {
if (!doIsCanonical()) {
return;
}
child.doDeleteTree();
}
@Override @Override
public void doDelete() { public void doDelete() {
manager.doDeleteEdge(this); manager.doDeleteEdge(this);
@@ -388,19 +348,7 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
if (triple.parent == null) { if (triple.parent == null) {
throw new IllegalArgumentException("Cannot delete root value"); throw new IllegalArgumentException("Cannot delete root value");
} }
doDelete(); doDeleteAndEmit();
}
}
protected void doDeleteTree() {
doDeleteSuccessors();
doDelete();
}
@Override
public void deleteTree() {
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
doDeleteTree();
} }
} }
@@ -410,7 +358,7 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
if (triple.parent == null) { if (triple.parent == null) {
throw new IllegalArgumentException("Cannot truncate or delete root value"); throw new IllegalArgumentException("Cannot truncate or delete root value");
} }
return doTruncateOrDelete(span); return doTruncateOrDeleteAndEmitLifeChange(span);
} }
} }
} }
@@ -0,0 +1,56 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.trace.database.target;
import java.util.stream.Stream;
import com.google.common.collect.Range;
import ghidra.trace.database.target.InternalTreeTraversal.SpanIntersectingVisitor;
import ghidra.trace.database.target.InternalTreeTraversal.VisitResult;
public enum InternalAllPathsVisitor implements SpanIntersectingVisitor {
INSTANCE;
@Override
public DBTraceObjectValPath composePath(DBTraceObjectValPath pre,
InternalTraceObjectValue value) {
return pre.prepend(value);
}
@Override
public VisitResult visitValue(InternalTraceObjectValue value, DBTraceObjectValPath path) {
if (value.getParent() == null) {
return VisitResult.EXCLUDE_FINISH;
}
if (value.getParent().isRoot()) {
// It may have other parents
return VisitResult.INCLUDE_CONTINUE;
}
return VisitResult.EXCLUDE_CONTINUE;
}
@Override
public DBTraceObject continueObject(InternalTraceObjectValue value) {
return value.getParent();
}
@Override
public Stream<? extends InternalTraceObjectValue> continueValues(DBTraceObject object,
Range<Long> span, DBTraceObjectValPath path) {
return object.getParents().stream().filter(v -> !path.contains(v));
}
}
@@ -0,0 +1,63 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.trace.database.target;
import java.util.stream.Stream;
import com.google.common.collect.Range;
import ghidra.dbg.util.PathPredicates;
import ghidra.trace.database.target.InternalTreeTraversal.SpanIntersectingVisitor;
import ghidra.trace.database.target.InternalTreeTraversal.VisitResult;
public class InternalAncestorsVisitor implements SpanIntersectingVisitor {
protected final PathPredicates predicates;
public InternalAncestorsVisitor(PathPredicates predicates) {
this.predicates = predicates;
}
@Override
public DBTraceObjectValPath composePath(DBTraceObjectValPath pre,
InternalTraceObjectValue value) {
return pre == null ? DBTraceObjectValPath.of() : pre.prepend(value);
}
@Override
public VisitResult visitValue(InternalTraceObjectValue value, DBTraceObjectValPath path) {
return VisitResult.result(
predicates.matches(value.getParent().getCanonicalPath().getKeyList()), true);
}
@Override
public DBTraceObject continueObject(InternalTraceObjectValue value) {
return value.getParent();
}
@Override
public Stream<? extends InternalTraceObjectValue> continueValues(DBTraceObject object,
Range<Long> span, DBTraceObjectValPath path) {
if (object.isRoot()) {
return Stream.empty();
}
/**
* Can't really filter the parent values by predicates here, since the predicates are not
* matching relative paths, but canonical paths.
*/
return object.getParents().stream().filter(v -> !path.contains(v));
}
}
@@ -0,0 +1,80 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.trace.database.target;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
import com.google.common.collect.Range;
import ghidra.dbg.util.PathPattern;
import ghidra.dbg.util.PathPredicates;
import ghidra.trace.database.target.InternalTreeTraversal.SpanIntersectingVisitor;
import ghidra.trace.database.target.InternalTreeTraversal.VisitResult;
import ghidra.trace.model.target.TraceObjectKeyPath;
public class InternalOrderedSuccessorsVisitor implements SpanIntersectingVisitor {
protected final PathPredicates predicates;
protected final boolean forward;
public InternalOrderedSuccessorsVisitor(TraceObjectKeyPath path, boolean forward) {
this.predicates = new PathPattern(path.getKeyList());
this.forward = forward;
}
@Override
public DBTraceObjectValPath composePath(DBTraceObjectValPath pre,
InternalTraceObjectValue value) {
return pre == null ? DBTraceObjectValPath.of() : pre.append(value);
}
@Override
public VisitResult visitValue(InternalTraceObjectValue value, DBTraceObjectValPath path) {
List<String> keyList = path.getKeyList();
if (predicates.matches(keyList)) {
// Singleton path, so if I match, no successor can
return VisitResult.INCLUDE_FINISH;
}
if (value.getChildOrNull() == null || predicates.successorCouldMatch(keyList, true)) {
return VisitResult.EXCLUDE_FINISH;
}
return VisitResult.EXCLUDE_CONTINUE;
}
@Override
public DBTraceObject continueObject(InternalTraceObjectValue value) {
return value.getChildOrNull();
}
@Override
public Stream<? extends InternalTraceObjectValue> continueValues(DBTraceObject object,
Range<Long> span, DBTraceObjectValPath path) {
Set<String> nextKeys = predicates.getNextKeys(path.getKeyList());
if (nextKeys.isEmpty()) {
return Stream.empty();
}
if (nextKeys.size() != 1) {
throw new IllegalArgumentException("predicates must be a singleton");
}
String next = nextKeys.iterator().next();
if (PathPattern.isWildcard(next)) {
throw new IllegalArgumentException("predicates must be a singleton");
}
return object.doGetOrderedValues(span, next, forward);
}
}
@@ -0,0 +1,89 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.trace.database.target;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
import com.google.common.collect.Range;
import ghidra.dbg.util.PathPredicates;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.database.target.InternalTreeTraversal.SpanIntersectingVisitor;
import ghidra.trace.database.target.InternalTreeTraversal.VisitResult;
public class InternalSuccessorsVisitor implements SpanIntersectingVisitor {
protected final PathPredicates predicates;
public InternalSuccessorsVisitor(PathPredicates predicates) {
this.predicates = predicates;
}
@Override
public DBTraceObjectValPath composePath(DBTraceObjectValPath pre,
InternalTraceObjectValue value) {
return pre == null ? DBTraceObjectValPath.of() : pre.append(value);
}
@Override
public VisitResult visitValue(InternalTraceObjectValue value, DBTraceObjectValPath path) {
List<String> keyList = path.getKeyList();
return VisitResult.result(predicates.matches(keyList),
predicates.successorCouldMatch(keyList, true) && value.getChildOrNull() != null);
}
@Override
public DBTraceObject continueObject(InternalTraceObjectValue value) {
return value.getChildOrNull();
}
@Override
public Stream<? extends InternalTraceObjectValue> continueValues(DBTraceObject object,
Range<Long> span, DBTraceObjectValPath pre) {
Set<String> nextKeys = predicates.getNextKeys(pre.getKeyList());
if (nextKeys.isEmpty()) {
return Stream.empty();
}
Stream<? extends DBTraceObjectValue> attrStream;
if (nextKeys.contains("")) {
attrStream = object.doGetAttributes()
.stream()
.filter(v -> DBTraceUtils.intersect(span, v.getLifespan()));
}
else {
attrStream = Stream.empty();
}
Stream<? extends DBTraceObjectValue> elemStream;
if (nextKeys.contains("[]")) {
elemStream = object.doGetElements()
.stream()
.filter(v -> DBTraceUtils.intersect(span, v.getLifespan()));
}
else {
elemStream = Stream.empty();
}
Stream<InternalTraceObjectValue> restStream = nextKeys.stream()
.filter(k -> !"".equals(k) && !"[]".equals(k))
.flatMap(k -> object.doGetValues(span, k).stream());
return Stream.concat(Stream.concat(attrStream, elemStream), restStream);
}
}
@@ -16,13 +16,11 @@
package ghidra.trace.database.target; package ghidra.trace.database.target;
import java.util.*; import java.util.*;
import java.util.stream.Stream;
import org.apache.commons.collections4.IterableUtils; import org.apache.commons.collections4.IterableUtils;
import com.google.common.collect.Range; import com.google.common.collect.Range;
import ghidra.dbg.util.PathPredicates;
import ghidra.trace.database.DBTraceUtils; import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.database.DBTraceUtils.LifespanMapSetter; import ghidra.trace.database.DBTraceUtils.LifespanMapSetter;
import ghidra.trace.model.Trace.TraceObjectChangeType; import ghidra.trace.model.Trace.TraceObjectChangeType;
@@ -67,7 +65,7 @@ interface InternalTraceObjectValue extends TraceObjectValue {
keep = entry; keep = entry;
} }
else { else {
entry.doDelete(); entry.doDeleteAndEmit();
} }
} }
else { else {
@@ -87,7 +85,7 @@ interface InternalTraceObjectValue extends TraceObjectValue {
return null; return null;
} }
if (keep != null && Objects.equals(this.value, value)) { if (keep != null && Objects.equals(this.value, value)) {
keep.doSetLifespan(range); keep.doSetLifespanAndEmit(range);
return keep; return keep;
} }
for (InternalTraceObjectValue k : kept) { for (InternalTraceObjectValue k : kept) {
@@ -114,7 +112,16 @@ interface InternalTraceObjectValue extends TraceObjectValue {
@Override @Override
DBTraceObject getChild(); DBTraceObject getChild();
void doSetLifespan(Range<Long> range); DBTraceObject getChildOrNull();
void doSetLifespan(Range<Long> lifespan);
default void doSetLifespanAndEmit(Range<Long> lifespan) {
Range<Long> oldLifespan = getLifespan();
doSetLifespan(lifespan);
getParent().emitEvents(new TraceChangeRecord<>(
TraceObjectChangeType.VALUE_LIFESPAN_CHANGED, null, this, oldLifespan, lifespan));
}
@Override @Override
default void setLifespan(Range<Long> lifespan) { default void setLifespan(Range<Long> lifespan) {
@@ -124,7 +131,6 @@ interface InternalTraceObjectValue extends TraceObjectValue {
@Override @Override
default void setLifespan(Range<Long> lifespan, ConflictResolution resolution) { default void setLifespan(Range<Long> lifespan, ConflictResolution resolution) {
try (LockHold hold = getTrace().lockWrite()) { try (LockHold hold = getTrace().lockWrite()) {
Range<Long> oldLifespan = getLifespan();
if (getParent() == null) { if (getParent() == null) {
throw new IllegalArgumentException("Cannot set lifespan of root value"); throw new IllegalArgumentException("Cannot set lifespan of root value");
} }
@@ -145,29 +151,37 @@ interface InternalTraceObjectValue extends TraceObjectValue {
return getParent().doCreateValue(range, getEntryKey(), value); return getParent().doCreateValue(range, getEntryKey(), value);
} }
}.set(lifespan, getValue()); }.set(lifespan, getValue());
getParent().emitEvents(new TraceChangeRecord<>(
TraceObjectChangeType.VALUE_LIFESPAN_CHANGED, null, this, oldLifespan, lifespan));
} }
} }
Stream<? extends DBTraceObjectValPath> doGetSuccessors(Range<Long> span,
DBTraceObjectValPath pre, PathPredicates predicates);
Stream<? extends DBTraceObjectValPath> doGetOrderedSuccessors(Range<Long> span,
DBTraceObjectValPath pre, PathPredicates predicates, boolean forward);
void doDelete(); void doDelete();
default void doDeleteAndEmit() {
DBTraceObject parent = getParent();
doDelete();
parent.emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.VALUE_DELETED, null, this));
}
@Override @Override
DBTraceObject getParent(); DBTraceObject getParent();
default InternalTraceObjectValue doTruncateOrDeleteAndEmitLifeChange(Range<Long> span) {
if (!isCanonical()) {
return doTruncateOrDelete(span);
}
DBTraceObject child = getChildOrNull();
InternalTraceObjectValue result = doTruncateOrDelete(span);
child.emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.LIFE_CHANGED, null, child));
return result;
}
default InternalTraceObjectValue doTruncateOrDelete(Range<Long> span) { default InternalTraceObjectValue doTruncateOrDelete(Range<Long> span) {
List<Range<Long>> removed = DBTraceUtils.subtract(getLifespan(), span); List<Range<Long>> removed = DBTraceUtils.subtract(getLifespan(), span);
if (removed.isEmpty()) { if (removed.isEmpty()) {
doDelete(); doDeleteAndEmit();
return null; return null;
} }
doSetLifespan(removed.get(0)); doSetLifespanAndEmit(removed.get(0));
if (removed.size() == 2) { if (removed.size() == 2) {
return getParent().doCreateValue(removed.get(1), getEntryKey(), getValue()); return getParent().doCreateValue(removed.get(1), getEntryKey(), getValue());
} }
@@ -0,0 +1,114 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.trace.database.target;
import java.util.stream.Stream;
import com.google.common.collect.Range;
public enum InternalTreeTraversal {
INSTANCE;
public enum VisitResult {
INCLUDE_CONTINUE,
INCLUDE_FINISH,
EXCLUDE_CONTINUE,
EXCLUDE_FINISH,
;
public static VisitResult result(boolean include, boolean cont) {
if (include) {
if (cont) {
return INCLUDE_CONTINUE;
}
else {
return INCLUDE_FINISH;
}
}
else {
if (cont) {
return EXCLUDE_CONTINUE;
}
else {
return EXCLUDE_FINISH;
}
}
}
}
public interface Visitor {
Range<Long> composeSpan(Range<Long> pre, InternalTraceObjectValue value);
DBTraceObjectValPath composePath(DBTraceObjectValPath pre, InternalTraceObjectValue value);
VisitResult visitValue(InternalTraceObjectValue value, DBTraceObjectValPath path);
DBTraceObject continueObject(InternalTraceObjectValue value);
Stream<? extends InternalTraceObjectValue> continueValues(DBTraceObject object,
Range<Long> span, DBTraceObjectValPath path);
}
public interface SpanIntersectingVisitor extends Visitor {
@Override
default Range<Long> composeSpan(Range<Long> pre, InternalTraceObjectValue value) {
Range<Long> valSpan = value.getLifespan();
if (!pre.isConnected(valSpan)) {
return null;
}
Range<Long> span = pre.intersection(valSpan);
if (span.isEmpty()) {
return null;
}
return span;
}
}
public Stream<? extends DBTraceObjectValPath> walkValue(Visitor visitor,
InternalTraceObjectValue value, Range<Long> span, DBTraceObjectValPath path) {
Range<Long> compSpan = visitor.composeSpan(span, value);
if (compSpan == null) {
return Stream.empty();
}
DBTraceObjectValPath compPath = visitor.composePath(path, value);
if (compPath == null) {
return Stream.empty();
}
switch (visitor.visitValue(value, compPath)) {
case INCLUDE_FINISH:
return Stream.of(compPath);
case EXCLUDE_FINISH:
return Stream.empty();
case INCLUDE_CONTINUE: {
DBTraceObject object = visitor.continueObject(value);
return Stream.concat(Stream.of(compPath),
walkObject(visitor, object, compSpan, compPath));
}
case EXCLUDE_CONTINUE: {
DBTraceObject object = visitor.continueObject(value);
return walkObject(visitor, object, compSpan, compPath);
}
}
throw new AssertionError();
}
public Stream<? extends DBTraceObjectValPath> walkObject(Visitor visitor, DBTraceObject object,
Range<Long> span, DBTraceObjectValPath path) {
return visitor.continueValues(object, span, path)
.flatMap(v -> walkValue(visitor, v, span, path));
}
}
@@ -1,222 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.trace.database.target;
import com.google.common.collect.Range;
import ghidra.lifecycle.Experimental;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObject.ConflictResolution;
import ghidra.trace.model.target.TraceObjectValue;
@Experimental
public class LifespanCorrector {
/**
* A visitor for lifespan correction
*
* <p>
* Implementors must implement only the upward pair, or only the downward pair
*/
public interface Visitor {
/**
* Visit an object on the upward side of traversal
*
* @param object the object
*/
default void visitObjectUpward(TraceObject object) {
}
/**
* Visit an object on the downward side of traversal
*
* @param object the object
*/
default void visitObjectDownward(TraceObject object) {
}
/**
* Visit a value on the upward side of traversal
*
* @param value the value, guaranteed to have a child
*/
default void visitValueUpward(TraceObjectValue value) {
}
/**
* Visit a value on the downward side of traversal
*
* @param value the value, guaranteed to have a child
*/
default void visitValueDownward(TraceObjectValue value) {
}
}
public enum Direction {
ANCESTORS {
@Override
public void visit(TraceObject seed, Visitor visitor) {
visitObjectAncestors(seed, visitor, UP | DOWN);
}
},
SUCCESSORS {
@Override
public void visit(TraceObject seed, Visitor visitor) {
visitObjectSuccessors(seed, visitor, true);
}
},
BOTH {
@Override
public void visit(TraceObject seed, Visitor visitor) {
visitObjectAncestors(seed, visitor, DOWN);
visitObjectSuccessors(seed, visitor, false);
visitObjectAncestors(seed, visitor, UP);
}
};
static final int UP = 1;
static final int DOWN = 2;
public abstract void visit(TraceObject seed, Visitor visitor);
void visitObjectAncestors(TraceObject object, Visitor visitor, int mode) {
if ((mode & UP) == UP) {
visitor.visitObjectUpward(object);
}
if (!object.isRoot()) {
for (TraceObjectValue value : object.getParents()) {
// Yes, over time, there may be multiple canonical values
if (value.isCanonical() && !value.isDeleted()) {
visitValueAncestors(value, visitor, mode);
}
}
}
if ((mode & DOWN) == DOWN) {
visitor.visitObjectDownward(object);
}
}
void visitValueAncestors(TraceObjectValue value, Visitor visitor, int mode) {
visitor.visitValueUpward(value);
visitObjectAncestors(value.getParent(), visitor, mode);
visitor.visitValueDownward(value);
}
void visitObjectSuccessors(TraceObject object, Visitor visitor, boolean includeCur) {
if (includeCur) {
visitor.visitObjectDownward(object);
}
for (TraceObjectValue value : object.getValues()) {
if (value.isCanonical() && !value.isDeleted()) {
visitValueSuccessors(value, visitor);
}
}
if (includeCur) {
visitor.visitObjectUpward(object);
}
}
void visitValueSuccessors(TraceObjectValue value, Visitor visitor) {
if (!(value.getValue() instanceof TraceObject)) {
return;
}
visitor.visitValueDownward(value);
visitObjectSuccessors(value.getChild(), visitor, true);
visitor.visitValueUpward(value);
}
}
// TODO: Consider non-canonical paths?
public enum Operation {
EXPAND {
@Override
Visitor getVisitor(ConflictResolution resolution) {
return new Visitor() {
@Override
public void visitObjectUpward(TraceObject object) {
Range<Long> span = object.getLifespan();
for (TraceObjectValue value : object.getValues()) {
span = span.span(value.getLifespan());
}
object.setLifespan(span);
}
@Override
public void visitValueUpward(TraceObjectValue value) {
Range<Long> newLife =
value.getLifespan().span(value.getChild().getLifespan());
value.setLifespan(newLife, resolution);
}
};
}
},
SHRINK {
@Override
Visitor getVisitor(ConflictResolution resolution) {
return new Visitor() {
@Override
public void visitObjectDownward(TraceObject object) {
for (TraceObjectValue value : object.getValues()) {
if (!DBTraceUtils.intersect(object.getLifespan(),
value.getLifespan())) {
value.delete();
continue;
}
value.setLifespan(
value.getLifespan().intersection(object.getLifespan()), resolution);
}
}
@Override
public void visitValueDownward(TraceObjectValue value) {
/**
* It'd be an odd circumstance for two canonical entries to exist for the
* same parent and child. If that happens, this will cause the child to
* become detached, since those entries cannot intersect.
*/
if (!DBTraceUtils.intersect(value.getLifespan(),
value.getChild().getLifespan())) {
value.getChild().delete();
return;
}
Range<Long> newLife =
value.getLifespan().intersection(value.getChild().getLifespan());
value.getChild().setLifespan(newLife);
}
};
}
};
abstract Visitor getVisitor(ConflictResolution resolution);
}
private final Direction direction;
private final Operation operation;
private final ConflictResolution resolution;
public LifespanCorrector(Direction direction, Operation operation,
ConflictResolution resolution) {
this.direction = direction;
this.operation = operation;
this.resolution = resolution;
}
public void correctLifespans(TraceObject seed) {
direction.visit(seed, operation.getVisitor(resolution));
}
}
@@ -100,10 +100,15 @@ public class DBTraceObjectThread implements TraceObjectThread, DBTraceObjectInte
TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, ""); TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, "");
} }
@Override
public void setName(Range<Long> lifespan, String name) {
object.setValue(lifespan, TargetObject.DISPLAY_ATTRIBUTE_NAME, name);
}
@Override @Override
public void setName(String name) { public void setName(String name) {
try (LockHold hold = object.getTrace().lockWrite()) { try (LockHold hold = object.getTrace().lockWrite()) {
object.setValue(getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name); setName(computeSpan(), name);
} }
} }
@@ -116,7 +121,7 @@ public class DBTraceObjectThread implements TraceObjectThread, DBTraceObjectInte
@Override @Override
public long getCreationSnap() { public long getCreationSnap() {
return object.getMinSnap(); return computeMinSnap();
} }
@Override @Override
@@ -128,7 +133,7 @@ public class DBTraceObjectThread implements TraceObjectThread, DBTraceObjectInte
@Override @Override
public long getDestructionSnap() { public long getDestructionSnap() {
return object.getMaxSnap(); return computeMaxSnap();
} }
@Override @Override
@@ -138,7 +143,7 @@ public class DBTraceObjectThread implements TraceObjectThread, DBTraceObjectInte
@Override @Override
public Range<Long> getLifespan() { public Range<Long> getLifespan() {
return object.getLifespan(); return computeSpan();
} }
@Override @Override
@@ -156,7 +161,9 @@ public class DBTraceObjectThread implements TraceObjectThread, DBTraceObjectInte
@Override @Override
public void delete() { public void delete() {
object.deleteTree(); try (LockHold hold = object.getTrace().lockWrite()) {
object.removeTree(computeSpan());
}
} }
@Override @Override
@@ -60,24 +60,22 @@ public interface Trace extends DataTypeManagerDomainObject {
* An object was created, but not yet inserted. * An object was created, but not yet inserted.
* *
* <p> * <p>
* Between the {@link #CREATED} and {@link #INSERTED} events, an object is considered * Between the {@link #CREATED} event and the first {@link #LIFE_CHANGED} event, an object
* "incomplete," because it is likely missing its attributes. Thus, a trace client must take * is considered "incomplete," because it is likely missing its attributes. Thus, a trace
* care to ensure all attributes, especially fixed attributes, are added to the object * client must take care to ensure all attributes, especially fixed attributes, are added to
* before it is inserted at its canonical path. Listeners may use * the object before it is inserted at its canonical path. Listeners may use
* {@link TraceObject#getCanonicalParent(long)} to check if an object is complete for a * {@link TraceObject#getCanonicalParent(long)} to check if an object is complete for a
* given snapshot. * given snapshot.
*/ */
public static final TraceObjectChangeType<TraceObject, Void> CREATED = public static final TraceObjectChangeType<TraceObject, Void> CREATED =
new TraceObjectChangeType<>(); new TraceObjectChangeType<>();
/** /**
* An object was inserted at its canonical path. * An object's life changed.
*
* <p>
* One of its canonical parents was created, deleted, or had its lifespan change.
*/ */
public static final TraceObjectChangeType<TraceObject, TraceObjectValue> INSERTED = public static final TraceObjectChangeType<TraceObject, Void> LIFE_CHANGED =
new TraceObjectChangeType<>();
/**
* An object's lifespan changed.
*/
public static final TraceObjectChangeType<TraceObject, Range<Long>> LIFESPAN_CHANGED =
new TraceObjectChangeType<>(); new TraceObjectChangeType<>();
/** /**
* An object was deleted. * An object was deleted.
@@ -85,27 +83,20 @@ public interface Trace extends DataTypeManagerDomainObject {
public static final TraceObjectChangeType<TraceObject, Void> DELETED = public static final TraceObjectChangeType<TraceObject, Void> DELETED =
new TraceObjectChangeType<>(); new TraceObjectChangeType<>();
/** /**
* An object's value changed. * A value entry was created.
*
* <p>
* If the old value was equal for the entirety of the new value's lifespan, that old value
* is passed as the old value. Otherwise, {@code null} is passed for the old value. If the
* value was cleared, {@code null} is passed for the new value.
*/ */
public static final TraceObjectChangeType<TraceObjectValue, Object> VALUE_CHANGED = public static final TraceObjectChangeType<TraceObjectValue, Void> VALUE_CREATED =
new TraceObjectChangeType<>(); new TraceObjectChangeType<>();
/** /**
* An object's value changed in lifespan. * A value entry changed in lifespan.
*
* <p>
* This is only called for the value on which {@link TraceObjectValue#setLifespan(Range)} or
* similar is called. If other values are truncated or deleted, there is no event. Listeners
* concerned about a single snap need only check if the snap is contained in the new and old
* lifespans. Listeners concerned about the full timeline can refresh the parent object's
* values, or compute the coalescing and truncation manually.
*/ */
public static final TraceObjectChangeType<TraceObjectValue, Range<Long>> // public static final TraceObjectChangeType<TraceObjectValue, Range<Long>> //
VALUE_LIFESPAN_CHANGED = new TraceObjectChangeType<>(); VALUE_LIFESPAN_CHANGED = new TraceObjectChangeType<>();
/**
* A value entry was deleted.
*/
public static final TraceObjectChangeType<TraceObjectValue, Void> VALUE_DELETED =
new TraceObjectChangeType<>();
} }
public static final class TraceBookmarkChangeType<T, U> extends DefaultTraceChangeType<T, U> { public static final class TraceBookmarkChangeType<T, U> extends DefaultTraceChangeType<T, U> {
@@ -15,6 +15,8 @@
*/ */
package ghidra.trace.model.breakpoint; package ghidra.trace.model.breakpoint;
import java.util.Collection;
import com.google.common.collect.Range; import com.google.common.collect.Range;
import ghidra.dbg.target.TargetBreakpointLocation; import ghidra.dbg.target.TargetBreakpointLocation;
@@ -42,5 +44,13 @@ public interface TraceObjectBreakpointLocation extends TraceBreakpoint, TraceObj
void setLifespan(Range<Long> lifespan) throws DuplicateNameException; void setLifespan(Range<Long> lifespan) throws DuplicateNameException;
void setRange(AddressRange range); void setRange(Range<Long> lifespan, AddressRange range);
void setName(Range<Long> lifespan, String name);
void setKinds(Range<Long> lifespan, Collection<TraceBreakpointKind> kinds);
void setEnabled(Range<Long> lifespan, boolean enabled);
void setComment(Range<Long> lifespan, String comment);
} }
@@ -43,4 +43,6 @@ public interface TraceObjectBreakpointSpec extends TraceBreakpoint, TraceObjectI
void setLifespan(Range<Long> lifespan) throws DuplicateNameException; void setLifespan(Range<Long> lifespan) throws DuplicateNameException;
Collection<? extends TraceObjectBreakpointLocation> getLocations(); Collection<? extends TraceObjectBreakpointLocation> getLocations();
void setKinds(Range<Long> lifespan, Collection<TraceBreakpointKind> kinds);
} }
@@ -22,6 +22,7 @@ import com.google.common.collect.Range;
import ghidra.dbg.target.TargetMemoryRegion; import ghidra.dbg.target.TargetMemoryRegion;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
import ghidra.program.model.address.AddressRange;
import ghidra.trace.model.target.TraceObjectInterface; import ghidra.trace.model.target.TraceObjectInterface;
import ghidra.trace.model.target.annot.TraceObjectInfo; import ghidra.trace.model.target.annot.TraceObjectInfo;
@@ -35,6 +36,10 @@ import ghidra.trace.model.target.annot.TraceObjectInfo;
public interface TraceObjectMemoryRegion extends TraceMemoryRegion, TraceObjectInterface { public interface TraceObjectMemoryRegion extends TraceMemoryRegion, TraceObjectInterface {
String KEY_VOLATILE = "_volatile"; String KEY_VOLATILE = "_volatile";
void setName(Range<Long> lifespan, String name);
void setRange(Range<Long> lifespan, AddressRange range);
void setFlags(Range<Long> lifespan, Collection<TraceMemoryFlag> flags); void setFlags(Range<Long> lifespan, Collection<TraceMemoryFlag> flags);
void addFlags(Range<Long> lifespan, Collection<TraceMemoryFlag> flags); void addFlags(Range<Long> lifespan, Collection<TraceMemoryFlag> flags);
@@ -17,8 +17,11 @@ package ghidra.trace.model.modules;
import java.util.Collection; import java.util.Collection;
import com.google.common.collect.Range;
import ghidra.dbg.target.TargetModule; import ghidra.dbg.target.TargetModule;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
import ghidra.program.model.address.AddressRange;
import ghidra.trace.database.module.TraceObjectSection; import ghidra.trace.database.module.TraceObjectSection;
import ghidra.trace.model.target.TraceObjectInterface; import ghidra.trace.model.target.TraceObjectInterface;
import ghidra.trace.model.target.annot.TraceObjectInfo; import ghidra.trace.model.target.annot.TraceObjectInfo;
@@ -31,6 +34,11 @@ import ghidra.trace.model.target.annot.TraceObjectInfo;
TargetModule.RANGE_ATTRIBUTE_NAME TargetModule.RANGE_ATTRIBUTE_NAME
}) })
public interface TraceObjectModule extends TraceModule, TraceObjectInterface { public interface TraceObjectModule extends TraceModule, TraceObjectInterface {
void setName(Range<Long> lifespan, String name);
void setRange(Range<Long> lifespan, AddressRange range);
@Override @Override
Collection<? extends TraceObjectSection> getSections(); Collection<? extends TraceObjectSection> getSections();
@@ -17,7 +17,10 @@ package ghidra.trace.model.stack;
import java.util.List; import java.util.List;
import ghidra.lifecycle.Experimental;
import ghidra.lifecycle.Transitional;
import ghidra.trace.model.TraceUniqueObject; import ghidra.trace.model.TraceUniqueObject;
import ghidra.trace.model.memory.TraceMemoryManager;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
/** /**
@@ -56,9 +59,13 @@ public interface TraceStack extends TraceUniqueObject {
/** /**
* Set the depth of the stack by adding or deleting frames to or from the specified end * Set the depth of the stack by adding or deleting frames to or from the specified end
* *
* Note that pushing new frames onto a stack does not adjust the frame level of any associated * <p>
* annotations. Some utility to adjust those annotations. Cannot just rename tables, since those * Note that pushing new frames onto a stack does not adjust the frame level of any
* contain annotations for the given level, regardless of the particular stack. * frame-associated managers or spaces, e.g., that returned by
* {@link TraceMemoryManager#getMemoryRegisterSpace(TraceThread, int, boolean)}.
*
* <p>
* If the experimental object mode is successful, this method should be deleted.
* *
* @param depth the desired depth * @param depth the desired depth
* @param atInner true if frames should be "pushed" * @param atInner true if frames should be "pushed"
@@ -78,12 +85,27 @@ public interface TraceStack extends TraceUniqueObject {
/** /**
* Get all (known) frames in this stack * Get all (known) frames in this stack
* *
* @param snap the snap (only relevant in the experimental objects mode. Ordinarily, the frames
* are fixed over the stack's lifetime)
* @return the list of frames * @return the list of frames
*/ */
List<TraceStackFrame> getFrames(); List<TraceStackFrame> getFrames(@Experimental long snap);
/** /**
* Delete this stack and its frames * Delete this stack and its frames
*/ */
void delete(); void delete();
/**
* Check if this stack'sframes are fixed for its lifetime
*
* <p>
* This is a transitional method, since the experimental objects mode breaks with the normal
* stack/frame model. Essentially, this returns true if the normal model is being used, and
* false if the object-based model is being used.
*
* @return true if fixed, false if object-based (dynamic)
*/
@Transitional
boolean hasFixedFrames();
} }
@@ -17,18 +17,69 @@ package ghidra.trace.model.stack;
import com.google.common.collect.Range; import com.google.common.collect.Range;
import ghidra.lifecycle.Experimental;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
/**
* A frame in a {@link TraceStack}
*/
public interface TraceStackFrame { public interface TraceStackFrame {
/**
* Get the containing stack
*
* @return the stack
*/
TraceStack getStack(); TraceStack getStack();
/**
* Get the frame's position in the containing stack
*
* <p>
* 0 represents the innermost frame or top of the stack.
*
* @return the frame's level
*/
int getLevel(); int getLevel();
Address getProgramCounter(long snap); /**
* Get the program counter at the given snap
*
* @param snap the snap (only relevant in the experimental objects mode. Ordinarily, the PC is
* fixed over the containing stack's lifetime)
* @return the program counter
*/
Address getProgramCounter(@Experimental long snap);
void setProgramCounter(Range<Long> span, Address pc); /**
* Set the program counter over the given span
*
* @param span the span (only relevant in the experimental objects mode. Ordinarily, the PC is
* fixed over the containing stack's lifetime)
* @param pc the program counter
*/
void setProgramCounter(@Experimental Range<Long> span, Address pc);
String getComment(); /**
* Get the user comment for the frame
*
* <p>
* In the experimental objects mode, this actually gets the comment in the listing at the
* frame's program counter for the given snap.
*
* @param snap the snap (only relevant in the experimental objects mode)
* @return the (nullable) comment
*/
String getComment(@Experimental long snap);
void setComment(String comment); /**
* Set the user comment for the frame
*
* <p>
* In the experimental objects mode, this actually sets the comment in the listing at the
* frame's program counter for the given snap.
*
* @param snap the snap (only relevant in the experimental objects mode)
* @param comment the (nullable) comment
*/
void setComment(@Experimental long snap, String comment);
} }
@@ -15,7 +15,22 @@
*/ */
package ghidra.trace.model.target; package ghidra.trace.model.target;
import ghidra.trace.model.target.TraceObject.ConflictResolution;
/**
* Thrown when there are "duplicate keys" and the {@link ConflictResolution#DENY} strategy is passed
*
* <p>
* There are said to be "duplicate keys" when two value entries having the same parent and key have
* overlapping lifespans. Such would create the possibility of a non-uniquely-defined value for a
* given path, and so it is not allowed.
*/
public class DuplicateKeyException extends RuntimeException { public class DuplicateKeyException extends RuntimeException {
/**
* Notify of a given conflicting key
*
* @param key the key in conflict
*/
public DuplicateKeyException(String key) { public DuplicateKeyException(String key) {
super(key); super(key);
} }
@@ -19,6 +19,7 @@ import java.util.Collection;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.google.common.collect.Range; import com.google.common.collect.Range;
import com.google.common.collect.RangeSet;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.TargetObjectSchema; import ghidra.dbg.target.schema.TargetObjectSchema;
@@ -59,33 +60,81 @@ public interface TraceObject extends TraceUniqueObject {
TraceObjectKeyPath getCanonicalPath(); TraceObjectKeyPath getCanonicalPath();
/** /**
* Inserts this object at its canonical path for its lifespan * Get all ranges of this object's life
* *
* <p> * <p>
* Any ancestor which does not exist is created with the same lifespan as this object. Values * Essentially, this is the union of the lifespans of all canonical parent values
* are set with the same lifespan. Only the canonical path is considered when looking for
* existing ancestry. Any whose lifespan intersects that of this object is considered
* "existing." If an existing ancestor is detached, this object will still become its successor,
* and the resulting subtree will remain detached.
* *
* @return the range set for snaps at which this object is considered "inserted."
*/
RangeSet<Long> getLife();
/**
* Inserts this object at its canonical path for the given lifespan
*
* <p>
* Any ancestor which does not exist is created. Values' lifespans are added or expanded to
* contain the given lifespan. Only the canonical path is considered when looking for existing
* ancestry.
*
* @param the minimum lifespan of edges from the root to this object
* @param resolution the rule for handling duplicate keys when setting values. * @param resolution the rule for handling duplicate keys when setting values.
*/ */
void insert(ConflictResolution resolution); void insert(Range<Long> lifespan, ConflictResolution resolution);
/**
* Remove this object from its canonical path for the given lifespan
*
* <p>
* Truncate the lifespans of this object's canonical parent value by the given span. If the
* parent value's lifespan is contained in the given span, the parent value will be deleted.
*
* @param span the span during which this object should be removed
*/
void remove(Range<Long> span);
/**
* Remove this object and its successors from their canonical paths for the given span
*
* <p>
* Truncate the lifespans of this object's canonical parent value and all canonical values
* succeeding this object. If a truncated value's lifespan is contained in the given span, the
* value will be deleted.
*
* @param span the span during which this object and its canonical successors should be removed
*/
void removeTree(Range<Long> span);
/** /**
* Get the parent value along this object's canonical path for a given snapshot * Get the parent value along this object's canonical path for a given snapshot
* *
* <p> * <p>
* To be the canonical parent value at a given snapshot, three things must be true: 1) The * To be the canonical parent value at a given snapshot, three things must be true: 1) The
* parent object must have this object's path with the last key removed. 2) The parent value's * parent object must have this object's path with the final key removed. 2) The parent value's
* entry key must be equal to the final key of this object's path. 3) The value's lifespan must * entry key must be equal to the final key of this object's path. 3) The value's lifespan must
* contain the given snapshot. If no value satisfies these, null is returned. * contain the given snapshot. If no value satisfies these, null is returned, and the object and
* its subtree are said to be "detached" at the given snapshot.
* *
* @param snap the snapshot key * @param snap the snapshot key
* @return the canonical parent value, or null * @return the canonical parent value, or null
*/ */
TraceObjectValue getCanonicalParent(long snap); TraceObjectValue getCanonicalParent(long snap);
/**
* Get the parent values along this object's canonical path for a given lifespan
*
* <p>
* To be a canonical parent in a given lifespan, three things must be true: 1) The parent object
* must have this object's path with the final key removed. 2) The parent value's entry key must
* be equal to the final key of this object's path. 3) The value's lifespan must intersect the
* given lifespan. If the result is empty, the object and its subtree are said to be "detatched"
* during the given lifespan.
*
* @param lifespan the lifespan to consider
* @return the stream of canonical parents
*/
Stream<? extends TraceObjectValue> getCanonicalParents(Range<Long> lifespan);
/** /**
* Check if this object is the root * Check if this object is the root
* *
@@ -99,7 +148,7 @@ public interface TraceObject extends TraceUniqueObject {
* @param span the span which every value entry on each path must intersect * @param span the span which every value entry on each path must intersect
* @return the paths * @return the paths
*/ */
Stream<TraceObjectValPath> getAllPaths(Range<Long> span); Stream<? extends TraceObjectValPath> getAllPaths(Range<Long> span);
/** /**
* Specifies a strategy for resolving duplicate keys * Specifies a strategy for resolving duplicate keys
@@ -116,61 +165,12 @@ public interface TraceObject extends TraceUniqueObject {
*/ */
TRUNCATE, TRUNCATE,
/** /**
* Throw an exception if the specified lifespan would result in conflicting entries * Throw {@link DuplicateKeyException} if the specified lifespan would result in conflicting
* entries
*/ */
DENY; DENY;
} }
/**
* Set the lifespan of this object
*
* <p>
* NOTE: Objects with intersecting lifespans are not checked for duplicate canonical paths.
* However, their parent value entries are checked for conflicts. Thus, at any snap, it is
* impossible for any two objects with equal canonical paths to both exist at their canonical
* locations.
*
* @param lifespan the new lifespan
*/
void setLifespan(Range<Long> lifespan);
/**
* Get the lifespan of this object
*
* @return the lifespan
*/
Range<Long> getLifespan();
/**
* Set the minimum snap of this object
*
* @see #setLifespan(Range)
* @param minSnap the minimum snap, or {@link Long#MIN_VALUE} for "since the beginning of time"
*/
void setMinSnap(long minSnap);
/**
* Get the minimum snap of this object
*
* @return the minimum snap, or {@link Long#MIN_VALUE} for "since the beginning of time"
*/
long getMinSnap();
/**
* Set the maximum snap of this object
*
* @see #setLifespan(Range)
* @param maxSnap the maximum snap, or {@link Long#MAX_VALUE} for "to the end of time"
*/
void setMaxSnap(long maxSnap);
/**
* Get the maximum snap of this object
*
* @return the maximum snap, or {@link Long#MAX_VALUE} for "to the end of time"
*/
long getMaxSnap();
/** /**
* Get all the interface classes provided by this object, according to the schema * Get all the interface classes provided by this object, according to the schema
* *
@@ -314,8 +314,9 @@ public interface TraceObject extends TraceUniqueObject {
* @param lifespan the lifespan of the value * @param lifespan the lifespan of the value
* @param key the key to set * @param key the key to set
* @param value the new value * @param value the new value
* @param resolution determines how to resolve conflicting keys with intersecting lifespans * @param resolution determines how to resolve duplicate keys with intersecting lifespans
* @return the created value entry * @return the created value entry
* @throws DuplicateKeyException if there are denied duplicate keys
*/ */
TraceObjectValue setValue(Range<Long> lifespan, String key, Object value, TraceObjectValue setValue(Range<Long> lifespan, String key, Object value,
ConflictResolution resolution); ConflictResolution resolution);
@@ -407,11 +408,10 @@ public interface TraceObject extends TraceUniqueObject {
* <p> * <p>
* The object may not yet be inserted at its canonical path * The object may not yet be inserted at its canonical path
* *
* @param span the span which the found objects must intersect
* @param targetIf the interface class * @param targetIf the interface class
* @return the stream of objects * @return the stream of objects
*/ */
Stream<? extends TraceObject> queryCanonicalAncestorsTargetInterface(Range<Long> span, Stream<? extends TraceObject> queryCanonicalAncestorsTargetInterface(
Class<? extends TargetObject> targetIf); Class<? extends TargetObject> targetIf);
/** /**
@@ -421,13 +421,18 @@ public interface TraceObject extends TraceUniqueObject {
* The object may not yet be inserted at its canonical path * The object may not yet be inserted at its canonical path
* *
* @param <I> the interface type * @param <I> the interface type
* @param span the span which the found objects must intersect
* @param ifClass the interface class * @param ifClass the interface class
* @return the stream of interfaces * @return the stream of interfaces
*/ */
<I extends TraceObjectInterface> Stream<I> queryCanonicalAncestorsInterface( <I extends TraceObjectInterface> Stream<I> queryCanonicalAncestorsInterface(Class<I> ifClass);
Range<Long> span, Class<I> ifClass);
/**
* Search for successors providing the given target interface
*
* @param span the span which the found paths must intersect
* @param targetIf the target interface class
* @return the stream of found paths to values
*/
Stream<? extends TraceObjectValPath> querySuccessorsTargetInterface(Range<Long> span, Stream<? extends TraceObjectValPath> querySuccessorsTargetInterface(Range<Long> span,
Class<? extends TargetObject> targetIf); Class<? extends TargetObject> targetIf);
@@ -446,22 +451,14 @@ public interface TraceObject extends TraceUniqueObject {
* Delete this object along with parent and child value entries referring to it * Delete this object along with parent and child value entries referring to it
* *
* <p> * <p>
* Note, this does not delete the children or any successors. Use {@link #deleteTree()} to * <b>Warning:</b> This will remove the object from the manager <em>entirely</em>, not just over
* delete an entire subtree, regardless of lifespan. It is not recommended to invoke this on the * a given span. In general, this is used for cleaning and maintenance. Consider
* root object, since it cannot be replaced without first clearing the manager. * {@link #remove(Range)} or {@link TraceObjectValue#delete()} instead. Note, this does not
* delete the child objects or any successors. It is not recommended to invoke this on the root
* object, since it cannot be replaced without first clearing the manager.
*/ */
void delete(); void delete();
/**
* Delete this object and its successors along with value entries referring to any
*
* <p>
* It is not recommended to invoke this on the root object. Instead, use
* {@link TraceObjectManager#clear()}. The root object cannot be replaced without first clearing
* the manager.
*/
void deleteTree();
/** /**
* Check if this object has been deleted * Check if this object has been deleted
* *
@@ -469,21 +466,4 @@ public interface TraceObject extends TraceUniqueObject {
*/ */
@Override @Override
boolean isDeleted(); boolean isDeleted();
/**
* Modify the lifespan or delete this object, such that it no longer intersects the given span.
*
* <p>
* If the given span and the current lifespan are already disjoint, this does nothing. If the
* given span splits the current lifespan in two, an exception is thrown. This is because the
* two resulting objects ought to be identical, but they cannot be. Instead the one object
* should remain alive, and the edge(s) pointing to it should be truncated. In other words, a
* single object cannot vanish and then later re-appear, but it can be unlinked and then later
* become relinked.
*
* @param span the span to clear
* @return this if the one object remains, null if the object is deleted.
* @throws IllegalArgumentException if the given span splits the current lifespan in two
*/
TraceObject truncateOrDelete(Range<Long> span);
} }
@@ -15,6 +15,46 @@
*/ */
package ghidra.trace.model.target; package ghidra.trace.model.target;
import com.google.common.collect.Range;
import com.google.common.collect.RangeSet;
import ghidra.lifecycle.Transitional;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.model.thread.TraceObjectThread;
/**
* A common interface for object-based implementations of other trace manager entries, e.g.,
* {@link TraceObjectThread}.
*/
public interface TraceObjectInterface { public interface TraceObjectInterface {
/**
* Get the object backing this implementation
*
* @return the object
*/
TraceObject getObject(); TraceObject getObject();
/**
* Compute the lifespan of this object
*
* @return the span of all lifespans
*/
@Transitional
default Range<Long> computeSpan() {
RangeSet<Long> life = getObject().getLife();
if (life.isEmpty()) {
return null;
}
return life.span();
}
@Transitional
default long computeMinSnap() {
return DBTraceUtils.lowerEndpoint(computeSpan());
}
@Transitional
default long computeMaxSnap() {
return DBTraceUtils.upperEndpoint(computeSpan());
}
} }
@@ -16,20 +16,48 @@
package ghidra.trace.model.target; package ghidra.trace.model.target;
import java.util.*; import java.util.*;
import java.util.stream.Stream;
import ghidra.dbg.util.PathPredicates;
import ghidra.dbg.util.PathUtils; import ghidra.dbg.util.PathUtils;
import ghidra.dbg.util.PathUtils.PathComparator; import ghidra.dbg.util.PathUtils.PathComparator;
/**
* An immutable path of keys leading from one object object to another
*
* <p>
* Often, the source is the root. These are often taken as a parameter when searching for values. In
* essence, they simply wrap a list of string keys, but it provides convenience methods, sensible
* comparison, and better typing.
*/
public final class TraceObjectKeyPath implements Comparable<TraceObjectKeyPath> { public final class TraceObjectKeyPath implements Comparable<TraceObjectKeyPath> {
/**
* Create a path from the given list of keys
*
* @param keyList the list of keys from source to destination
* @return the path
*/
public static TraceObjectKeyPath of(List<String> keyList) { public static TraceObjectKeyPath of(List<String> keyList) {
return new TraceObjectKeyPath(List.copyOf(keyList)); return new TraceObjectKeyPath(List.copyOf(keyList));
} }
/**
* Create a path from the given keys
*
* @param keys the keys from source to destination
* @return the path
*/
public static TraceObjectKeyPath of(String... keys) { public static TraceObjectKeyPath of(String... keys) {
return new TraceObjectKeyPath(List.of(keys)); return new TraceObjectKeyPath(List.of(keys));
} }
/**
* Parse a path from the given string
*
* @param path the dot-separated keys from source to destinattion
* @return the path
*/
public static TraceObjectKeyPath parse(String path) { public static TraceObjectKeyPath parse(String path) {
return new TraceObjectKeyPath(PathUtils.parse(path)); return new TraceObjectKeyPath(PathUtils.parse(path));
} }
@@ -67,30 +95,82 @@ public final class TraceObjectKeyPath implements Comparable<TraceObjectKeyPath>
return this.keyList.equals(that.keyList); return this.keyList.equals(that.keyList);
} }
/**
* Get the (immutable) list of keys from source to destination
*
* @return the key list
*/
public List<String> getKeyList() { public List<String> getKeyList() {
return keyList; return keyList;
} }
/**
* Assuming the source is the root, check if this path refers to that root
*
* @return true if the path is empty, false otherwise
*/
public boolean isRoot() { public boolean isRoot() {
return keyList.isEmpty(); return keyList.isEmpty();
} }
/**
* Create a new path by appending the given key
*
* <p>
* For example, if this path is "{@code Processes[2]}" and {@code name} takes the value
* "{@code Threads}", the result will be "{@code Processes[2].Threads}".
*
* @param name the new final key
* @return the resulting path
*/
public TraceObjectKeyPath key(String name) { public TraceObjectKeyPath key(String name) {
return new TraceObjectKeyPath(PathUtils.extend(keyList, name)); return new TraceObjectKeyPath(PathUtils.extend(keyList, name));
} }
/**
* Get the final key of this path
*
* @return the final key
*/
public String key() { public String key() {
return PathUtils.getKey(keyList); return PathUtils.getKey(keyList);
} }
/**
* Create a new path by appending the given element index
*
* <p>
* For example, if this path is "{@code Processes}" and {@code index} takes the value 2, the
* result will be "{@code Processes[2]}".
*
* @param index the new final index
* @return the resulting path
*/
public TraceObjectKeyPath index(long index) { public TraceObjectKeyPath index(long index) {
return index(PathUtils.makeIndex(index)); return index(PathUtils.makeIndex(index));
} }
/**
* Create a new path by appending the given element index
*
* <p>
* This does the same as {@link #key(String)} but uses brackets instead. For example, if this
* path is "{@code Processes[2].Threads[0].Registers}" and {@code index} takes the value
* "{@code RAX}", the result will be "{@code Processes[2].Threads[0].Registers[RAX]"}.
*
* @param index the new final index
* @return the resulting path
*/
public TraceObjectKeyPath index(String index) { public TraceObjectKeyPath index(String index) {
return new TraceObjectKeyPath(PathUtils.index(keyList, index)); return new TraceObjectKeyPath(PathUtils.index(keyList, index));
} }
/**
* Get the final index of this path
*
* @return the final index
* @throws IllegalArgumentException if the final key is not an index, i.e., in brackets
*/
public String index() { public String index() {
return PathUtils.getIndex(keyList); return PathUtils.getIndex(keyList);
} }
@@ -100,16 +180,54 @@ public final class TraceObjectKeyPath implements Comparable<TraceObjectKeyPath>
return PathUtils.toString(keyList); return PathUtils.toString(keyList);
} }
/**
* Create a new path by removing the final key
*
* @return the resulting path, or null if this path is empty
*/
public TraceObjectKeyPath parent() { public TraceObjectKeyPath parent() {
List<String> pkl = PathUtils.parent(keyList); List<String> pkl = PathUtils.parent(keyList);
return pkl == null ? null : new TraceObjectKeyPath(pkl); return pkl == null ? null : new TraceObjectKeyPath(pkl);
} }
/**
* Create a new path by appending the given list of keys
*
* <p>
* For example, if this path is "{@code Processes[2]}" and {@code subKeyList} takes the value
* {@code List.of("Threads", "[0]")}, the result will be "{@code Processes[2].Threads[0]}".
*
* @param subKeyList the list of keys to append
* @return the resulting path
*/
public TraceObjectKeyPath extend(List<String> subKeyList) { public TraceObjectKeyPath extend(List<String> subKeyList) {
return new TraceObjectKeyPath(PathUtils.extend(keyList, subKeyList)); return new TraceObjectKeyPath(PathUtils.extend(keyList, subKeyList));
} }
/**
* Create a new path by appending the given keys
*
* @see #extend(List)
* @param subKeyList the keys to append
* @return the resulting path
*/
public TraceObjectKeyPath extend(String... subKeyList) { public TraceObjectKeyPath extend(String... subKeyList) {
return extend(Arrays.asList(subKeyList)); return extend(Arrays.asList(subKeyList));
} }
/**
* Stream, starting with the longer paths, paths that match the given predicates
*
* @param matcher
* @return
*/
public Stream<TraceObjectKeyPath> streamMatchingAncestry(PathPredicates predicates) {
if (!predicates.ancestorMatches(keyList, false)) {
return Stream.of();
}
if (predicates.matches(keyList)) {
return Stream.concat(Stream.of(this), parent().streamMatchingAncestry(predicates));
}
return parent().streamMatchingAncestry(predicates);
}
} }
@@ -53,13 +53,12 @@ public interface TraceObjectManager {
TraceObjectValue createRootObject(TargetObjectSchema schema); TraceObjectValue createRootObject(TargetObjectSchema schema);
/** /**
* Create an object with the given canonical path having the given lifespan * Create (or get) an object with the given canonical path
* *
* @param path the object's canonical path * @param path the object's canonical path
* @param lifespan the initial lifespan
* @return the new object * @return the new object
*/ */
TraceObject createObject(TraceObjectKeyPath path, Range<Long> lifespan); TraceObject createObject(TraceObjectKeyPath path);
/** /**
* Get the schema of the root object * Get the schema of the root object
@@ -93,7 +92,7 @@ public interface TraceObjectManager {
* @param path the canonical path of the desired objects * @param path the canonical path of the desired objects
* @return the collection of objects * @return the collection of objects
*/ */
Collection<? extends TraceObject> getObjectsByCanonicalPath(TraceObjectKeyPath path); TraceObject getObjectByCanonicalPath(TraceObjectKeyPath path);
/** /**
* Get objects in the database having the given path intersecting the given span * Get objects in the database having the given path intersecting the given span
@@ -129,6 +128,13 @@ public interface TraceObjectManager {
*/ */
Collection<? extends TraceObject> getAllObjects(); Collection<? extends TraceObject> getAllObjects();
/**
* Get all the values (edges) in the database
*
* @return the collect of all values
*/
Collection<? extends TraceObjectValue> getAllValues();
/** /**
* Get all address-ranged values intersecting the given span and address range * Get all address-ranged values intersecting the given span and address range
* *
@@ -150,6 +156,15 @@ public interface TraceObjectManager {
<I extends TraceObjectInterface> Stream<I> queryAllInterface(Range<Long> span, <I extends TraceObjectInterface> Stream<I> queryAllInterface(Range<Long> span,
Class<I> ifClass); Class<I> ifClass);
/**
* For maintenance, remove all disconnected objects
*
* <p>
* An object is disconnected if it is neither the child nor parent of any value for any span. In
* other words, it's unused.
*/
void cullDisconnectedObjects();
/** /**
* Delete the <em>entire object model</em>, including the schema * Delete the <em>entire object model</em>, including the schema
* *
@@ -17,20 +17,100 @@ package ghidra.trace.model.target;
import java.util.List; import java.util.List;
import ghidra.dbg.util.PathPredicates;
/**
* A path of values leading from one object to another
*
* <p>
* Often, the source object is the root. These are often returned in streams where the search
* involves a desired "span." The path satisfies that requirement, i.e., "the path intersects the
* span" if the cumulative intersection of all values' lifespans along the path and the given span
* is non-empty. Paths may also be empty, implying the source is the destination. Empty paths
* "intersect" any given span.
*/
public interface TraceObjectValPath extends Comparable<TraceObjectValPath> { public interface TraceObjectValPath extends Comparable<TraceObjectValPath> {
/**
* Get the values in the path, ordered from source to destination
*
* @return the list of value entries
*/
List<? extends TraceObjectValue> getEntryList(); List<? extends TraceObjectValue> getEntryList();
/**
* Get the keys in the path, ordered from source to destination
*
* <p>
* The returned list is suited for testing with {@link PathPredicates} or other
* path-manipulation methods.
*
* @return the list of keys
*/
List<String> getKeyList(); List<String> getKeyList();
/**
* Check if a given value appears on this path
*
* @param entry the value entry to check
* @return true if it appears on the path, false otherwise
*/
boolean contains(TraceObjectValue entry); boolean contains(TraceObjectValue entry);
/**
* Get the first entry, i.e., the one adjacent to the source object
*
* @return the entry, or null if the path is empty
*/
TraceObjectValue getFirstEntry(); TraceObjectValue getFirstEntry();
TraceObject getFirstParent(TraceObject ifEmpty); /**
* Get the source object
*
* <p>
* This returns the parent object of the first entry of the path, unless the path is empty. If
* the path is empty, then this returns the value passed in {@code ifEmpty}, which is presumably
* the destination object.
*
* @param ifEmpty the object to return when this path is empty
* @return the source object
*/
TraceObject getSource(TraceObject ifEmpty);
/**
* Get the last entry, i.e., the one adjacent to the destination object
*
* @return the entry, or null if the path is empty
*/
TraceObjectValue getLastEntry(); TraceObjectValue getLastEntry();
Object getLastValue(Object ifEmpty); /**
* Get the destination value
*
* <p>
* This returns the value of the last entry of the path, unless the path is empty. If the path
* is empty, then this returns the object passed in {@code ifEmpty}, which is presumably the
* source object. Note that values may be a primitive, so the destination is not always an
* object, i.e., {@link TraceObject}. Use {@link #getDestination(TraceObject)} to assume the
* destination is an object.
*
* @param ifEmpty the value to return when the path is empty
* @return the destination value
*/
Object getDestinationValue(Object ifEmpty);
TraceObject getLastChild(TraceObject ifEmpty); /**
* Get the destination object
*
* <p>
* This returns the child object of the last entry of the path, unless the path is empty. If the
* path is empty, then this returns the object passed in {@code ifEmpty}, which is presumably
* the source object. Note that values may be primitive, so the destination is not always an
* object, i.e., {@link TraceObject}. Use {@link #getDestinationValue(Object)} when it is not
* safe to assume the destination is an object.
*
* @param ifEmpty the object to return when the path is empty
* @return the destination object
* @throws ClassCastException if the destination value is not an object
*/
TraceObject getDestination(TraceObject ifEmpty);
} }
@@ -91,6 +91,7 @@ public interface TraceObjectValue {
* *
* @param lifespan the new lifespan * @param lifespan the new lifespan
* @param resolution specifies how to resolve duplicate keys with intersecting lifespans * @param resolution specifies how to resolve duplicate keys with intersecting lifespans
* @throws DuplicateKeyException if there are denied duplicate keys
*/ */
void setLifespan(Range<Long> span, ConflictResolution resolution); void setLifespan(Range<Long> span, ConflictResolution resolution);
@@ -133,17 +134,9 @@ public interface TraceObjectValue {
/** /**
* Delete this entry * Delete this entry
*
* <p>
* If this entry is part of the child object's canonical path, then the child is also deleted.
*/ */
void delete(); void delete();
/**
* Delete this entry and, if it is canonical, its successors
*/
void deleteTree();
/** /**
* Check if this value entry has been deleted * Check if this value entry has been deleted
* *
@@ -21,7 +21,7 @@ import java.util.List;
import com.google.common.collect.Range; import com.google.common.collect.Range;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
import ghidra.program.model.address.*; import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.model.target.*; import ghidra.trace.model.target.*;
import ghidra.trace.model.target.TraceObject.ConflictResolution; import ghidra.trace.model.target.TraceObject.ConflictResolution;
import ghidra.util.LockHold; import ghidra.util.LockHold;
@@ -66,8 +66,8 @@ public enum TraceObjectInterfaceUtils {
throw new DuplicateNameException( throw new DuplicateNameException(
"Duplicate " + getShortName(traceIf) + ": " + e.getMessage()); "Duplicate " + getShortName(traceIf) + ": " + e.getMessage());
} }
object.setLifespan(lifespan); object.insert(lifespan, ConflictResolution.TRUNCATE);
long lower = object.getMinSnap(); long lower = DBTraceUtils.lowerEndpoint(lifespan);
for (String key : getFixedKeys(traceIf)) { for (String key : getFixedKeys(traceIf)) {
TraceObjectValue val = object.getValue(lower, key); TraceObjectValue val = object.getValue(lower, key);
if (val != null) { if (val != null) {

Some files were not shown because too many files have changed in this diff Show More