diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProvider.java index 09eb4c5791..0de60c933d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProvider.java @@ -381,7 +381,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter { } protected void updateStack() { - Set toAdd = new LinkedHashSet<>(currentStack.getFrames()); + Set toAdd = new LinkedHashSet<>(currentStack.getFrames(current.getSnap())); for (Iterator it = stackTableModel.getModelData().iterator(); it .hasNext();) { StackFrameRow row = it.next(); @@ -409,13 +409,13 @@ public class DebuggerStackProvider extends ComponentProviderAdapter { contextChanged(); return; } - if (currentStack == stack) { + if (currentStack == stack && stack.hasFixedFrames()) { stackTableModel.fireTableDataChanged(); return; } currentStack = stack; stackTableModel.clear(); - for (TraceStackFrame frame : currentStack.getFrames()) { + for (TraceStackFrame frame : currentStack.getFrames(current.getSnap())) { stackTableModel.add(new StackFrameRow(this, frame)); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/StackFrameRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/StackFrameRow.java index 72b1533ef2..24794c177f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/StackFrameRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/StackFrameRow.java @@ -75,13 +75,13 @@ public class StackFrameRow { } public String getComment() { - return frame == null ? "" : frame.getComment(); + return frame == null ? "" : frame.getComment(getSnap()); } public void setComment(String comment) { try (UndoableTransaction tid = UndoableTransaction .start(frame.getStack().getThread().getTrace(), "Frame comment", true)) { - frame.setComment(comment); + frame.setComment(getSnap(), comment); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java index 2f0d47cf2e..1d1ed83e19 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java @@ -277,7 +277,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin private void breakpointDeleted(TraceBreakpoint tb) { if (!tb.getLifespan().contains(info.recorder.getSnap())) { // NOTE: User/script probably removed historical breakpoint - assert false; + // assert false; return; } info.forgetTraceBreakpoint(c.r, tb); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java index a3dcbe4608..3b4e06415f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java @@ -420,7 +420,10 @@ public class ObjectBasedTraceRecorder implements TraceRecorder { @Override public TraceThread getTraceThreadForSuccessor(TargetObject 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); } @@ -432,7 +435,10 @@ public class ObjectBasedTraceRecorder implements TraceRecorder { @Override public TraceStackFrame getTraceStackFrameForSuccessor(TargetObject 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); } @@ -546,7 +552,7 @@ public class ObjectBasedTraceRecorder implements TraceRecorder { TraceObject object = thread.getObject(); this.process = object .queryAncestorsTargetInterface(Range.singleton(getSnap()), TargetProcess.class) - .map(p -> p.getFirstParent(object)) + .map(p -> p.getSource(object)) .findFirst() .orElse(null); } @@ -562,7 +568,7 @@ public class ObjectBasedTraceRecorder implements TraceRecorder { } return object .queryAncestorsTargetInterface(Range.singleton(getSnap()), TargetProcess.class) - .map(p -> p.getFirstParent(object)) + .map(p -> p.getSource(object)) .anyMatch(p -> p == process); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectRecorder.java index a65a667f04..9f37ec4227 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectRecorder.java @@ -82,11 +82,14 @@ class ObjectRecorder { traceObject = objectManager.getRootObject(); } else { - traceObject = objectManager - .createObject(TraceObjectKeyPath.of(object.getPath()), Range.atLeast(snap)); + traceObject = objectManager.createObject(TraceObjectKeyPath.of(object.getPath())); } synchronized (objectMap) { - objectMap.put(new IDKeyed<>(object), new IDKeyed<>(traceObject)); + IDKeyed 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); return; } - traceObject.obj.truncateOrDelete(Range.atLeast(snap)); + traceObject.obj.removeTree(Range.atLeast(snap)); } protected String encodeEnum(Enum e) { @@ -269,7 +272,7 @@ class ObjectRecorder { if (found == null) { return null; } - TraceObject last = found.getLastChild(null); + TraceObject last = found.getDestination(null); if (last == null) { return null; } @@ -284,7 +287,7 @@ class ObjectRecorder { return List.of(); } 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()); } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java index b0708d7795..a03e688fdd 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java @@ -1079,7 +1079,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge } @Test - public void testEditLiveBytesWritesTarget() throws Exception { + public void testEditLiveBytesWritesTarget() throws Throwable { createTestModel(); mb.createTestProcessesAndThreads(); @@ -1097,12 +1097,12 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge performAction(actionEdit); triggerText(memBytesProvider.getByteViewerPanel().getCurrentComponent(), "42"); performAction(actionEdit); + waitForSwing(); + waitRecorder(recorder); byte[] data = new byte[4]; - waitForPass(() -> { - mb.testProcess1.memory.getMemory(mb.addr(0x55550800), data); - assertArrayEquals(mb.arr(0x42, 0, 0, 0), data); - }); + mb.testProcess1.memory.getMemory(mb.addr(0x55550800), data); + assertArrayEquals(mb.arr(0x42, 0, 0, 0), data); } @Test diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderTest.java index 41da51bc7f..1736ddb96c 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderTest.java @@ -95,11 +95,11 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe TraceStackFrame frame = stack.getFrame(0, false); frame.setProgramCounter(Range.all(), tb.addr(0x00400100)); - frame.setComment("Hello"); + frame.setComment(stack.getSnap(), "Hello"); frame = stack.getFrame(1, false); frame.setProgramCounter(Range.all(), tb.addr(0x00400200)); - frame.setComment("World"); + frame.setComment(stack.getSnap(), "World"); } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorderTest.java index d8825628a9..66ae949b9a 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorderTest.java @@ -169,19 +169,22 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerGU public void testRecordThreadNameReuse() throws Throwable { startRecording(); 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(); mb.testProcess1.threads.removeThreads(mb.testThread1); - - waitForPass(() -> assertEquals(Range.singleton(0L), thread1a.getLifespan())); + waitRecorder(recorder); + assertEquals(Range.singleton(0L), thread1.getLifespan()); assertNull(recorder.getTraceThread(mb.testThread1)); recorder.forceSnapshot(); mb.testThread1 = mb.testProcess1.addThread(1); - TraceThread thread1b = waitForValue(() -> recorder.getTraceThread(mb.testThread1)); - - assertNotSame(thread1a, thread1b); + waitRecorder(recorder); + assertSame(thread1, recorder.getTraceThread(mb.testThread1)); + assertEquals(Set.of(Range.singleton(0L), Range.atLeast(2L)), object1.getLife().asRanges()); } @Test @@ -510,7 +513,7 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerGU TraceObject traceBank = thread.getObject() .querySuccessorsTargetInterface(Range.singleton(recorder.getSnap()), TargetRegisterBank.class) - .map(p -> p.getLastChild(thread.getObject())) + .map(p -> p.getDestination(thread.getObject())) .findAny() .orElseThrow(); diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/AllPathsMatcher.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/AllPathsMatcher.java new file mode 100644 index 0000000000..a8fa825b64 --- /dev/null +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/AllPathsMatcher.java @@ -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 path) { + return true; + } + + @Override + public boolean successorCouldMatch(List path, boolean strict) { + return true; + } + + @Override + public boolean ancestorMatches(List path, boolean strict) { + if (path.isEmpty() && strict) { + return false; + } + return true; + } + + @Override + public Set getNextKeys(List path) { + return Set.of("", "[]"); + } + + @Override + public Set getNextNames(List path) { + return Set.of(""); + } + + @Override + public Set getNextIndices(List path) { + return Set.of(""); + } + + @Override + public List getSingletonPath() { + return null; + } + + @Override + public PathPattern getSingletonPattern() { + return null; + } + + @Override + public PathPredicates applyKeys(List keys) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isEmpty() { + return false; + } +} diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathPredicates.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathPredicates.java index f1a5326081..11eb80be93 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathPredicates.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathPredicates.java @@ -54,6 +54,10 @@ public interface PathPredicates { return new PathPattern(PathUtils.parse(pattern)); } + static PathPredicates all() { + return AllPathsMatcher.INSTANCE; + } + PathPredicates or(PathPredicates that); /** diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java index 4fe48334fa..c384b7da21 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java @@ -81,6 +81,7 @@ public class DBTraceObjectBreakpointLocation // Keep copies here for when the object gets invalidated private AddressRange range; + private Range lifespan; public DBTraceObjectBreakpointLocation(DBTraceObject object) { this.object = object; @@ -98,29 +99,43 @@ public class DBTraceObjectBreakpointLocation return object.getCanonicalPath().toString(); } + @Override + public void setName(Range lifespan, String name) { + object.setValue(lifespan, TargetObject.DISPLAY_ATTRIBUTE_NAME, name); + } + @Override public void setName(String name) { - object.setValue(getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name); + try (LockHold hold = object.getTrace().lockWrite()) { + setName(getLifespan(), name); + } } @Override public String getName() { - return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), - TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, ""); + try (LockHold hold = object.getTrace().lockRead()) { + return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), + TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, ""); + } } @Override - public void setRange(AddressRange range) { + public void setRange(Range lifespan, AddressRange range) { try (LockHold hold = object.getTrace().lockWrite()) { - object.setValue(getLifespan(), KEY_RANGE, range); + object.setValue(lifespan, KEY_RANGE, range); this.range = range; } } @Override public AddressRange getRange() { - return range = TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), KEY_RANGE, - AddressRange.class, range); + try (LockHold hold = object.getTrace().lockRead()) { + if (object.getLife().isEmpty()) { + return range; + } + return range = TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), KEY_RANGE, + AddressRange.class, range); + } } @Override @@ -146,17 +161,33 @@ public class DBTraceObjectBreakpointLocation try (LockHold hold = object.getTrace().lockWrite()) { TraceObjectInterfaceUtils.setLifespan(TraceObjectBreakpointLocation.class, object, lifespan); + this.lifespan = lifespan; } } @Override public Range getLifespan() { - return object.getLifespan(); + try (LockHold hold = object.getTrace().lockRead()) { + Range computed = computeSpan(); + if (computed != null) { + lifespan = computed; + } + return lifespan; + } + } + + @Override + public Range computeSpan() { + Range span = TraceObjectBreakpointLocation.super.computeSpan(); + if (span != null) { + return span; + } + return getSpecification().computeSpan(); } @Override public long getPlacedSnap() { - return object.getMinSnap(); + return DBTraceUtils.lowerEndpoint(getLifespan()); } @Override @@ -168,7 +199,7 @@ public class DBTraceObjectBreakpointLocation @Override public long getClearedSnap() { - return object.getMaxSnap(); + return DBTraceUtils.upperEndpoint(getLifespan()); } @Override @@ -183,10 +214,15 @@ public class DBTraceObjectBreakpointLocation } } + @Override + public void setEnabled(Range lifespan, boolean enabled) { + object.setValue(lifespan, TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME, enabled); + } + @Override public void setEnabled(boolean enabled) { 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 - public void setKinds(Collection kinds) { + public void setKinds(Range lifespan, Collection kinds) { try (LockHold hold = object.getTrace().lockWrite()) { TraceObjectBreakpointSpec spec = getSpecification(); if (spec.getObject() != this.getObject()) { throw new UnsupportedOperationException("Set via the specification instead"); } - spec.setKinds(kinds); + spec.setKinds(lifespan, kinds); + } + } + + @Override + public void setKinds(Collection kinds) { + try (LockHold hold = object.getTrace().lockWrite()) { + setKinds(getLifespan(), kinds); } } @@ -235,17 +278,22 @@ public class DBTraceObjectBreakpointLocation PathMatcher procMatcher = schema.searchFor(TargetProcess.class, false); return object.getAncestors(getLifespan(), procMatcher) - .flatMap(proc -> proc.getFirstParent(object) + .flatMap(proc -> proc.getSource(object) .querySuccessorsInterface(getLifespan(), TraceObjectThread.class)) .collect(Collectors.toSet()); } } + @Override + public void setComment(Range lifespan, String comment) { + object.setValue(lifespan, KEY_COMMENT, comment); + } + @Override public void setComment(String comment) { try (LockHold hold = object.getTrace().lockWrite()) { - object.setValue(getLifespan(), KEY_COMMENT, comment); + setComment(getLifespan(), comment); } } @@ -257,7 +305,9 @@ public class DBTraceObjectBreakpointLocation @Override public void delete() { - object.deleteTree(); + try (LockHold hold = object.getTrace().lockWrite()) { + object.removeTree(computeSpan()); + } } @Override @@ -268,16 +318,14 @@ public class DBTraceObjectBreakpointLocation @Override public TraceObjectBreakpointSpec getSpecification() { try (LockHold hold = object.getTrace().lockRead()) { - return object - .queryCanonicalAncestorsInterface(getLifespan(), - TraceObjectBreakpointSpec.class) + return object.queryCanonicalAncestorsInterface(TraceObjectBreakpointSpec.class) .findAny() .orElseThrow(); } } public TraceAddressSpace getTraceAddressSpace() { - return spaceForValue(object.getMinSnap(), KEY_RANGE); + return spaceForValue(computeMinSnap(), KEY_RANGE); } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointSpec.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointSpec.java index fa4a24fd24..de88095cae 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointSpec.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointSpec.java @@ -99,12 +99,12 @@ public class DBTraceObjectBreakpointSpec @Override public Range getLifespan() { - return object.getLifespan(); + return computeSpan(); } @Override public long getPlacedSnap() { - return object.getMinSnap(); + return computeMinSnap(); } @Override @@ -116,7 +116,7 @@ public class DBTraceObjectBreakpointSpec @Override public long getClearedSnap() { - return object.getMaxSnap(); + return computeMaxSnap(); } @Override @@ -144,16 +144,23 @@ public class DBTraceObjectBreakpointSpec } @Override - public void setKinds(Collection kinds) { + public void setKinds(Range lifespan, Collection kinds) { // TODO: More efficient encoding // TODO: Target-Trace mapping is implied by encoded name. Seems bad. try (LockHold hold = object.getTrace().lockWrite()) { - object.setValue(getLifespan(), TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME, + object.setValue(lifespan, TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME, TraceBreakpointKindSet.encode(kinds)); this.kinds = TraceBreakpointKindSet.copyOf(kinds); } } + @Override + public void setKinds(Collection kinds) { + try (LockHold hold = object.getTrace().lockWrite()) { + setKinds(getLifespan(), kinds); + } + } + @Override public Set getKinds() { String kindsStr = TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), @@ -187,7 +194,9 @@ public class DBTraceObjectBreakpointSpec @Override public void delete() { - object.deleteTree(); + try (LockHold hold = object.getTrace().lockWrite()) { + object.removeTree(computeSpan()); + } } @Override @@ -206,9 +215,9 @@ public class DBTraceObjectBreakpointSpec @Override public TraceChangeRecord translateEvent(TraceChangeRecord rec) { - if (rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType()) { - TraceChangeRecord cast = - TraceObjectChangeType.VALUE_CHANGED.cast(rec); + if (rec.getEventType() == TraceObjectChangeType.VALUE_CREATED.getType()) { + TraceChangeRecord cast = + TraceObjectChangeType.VALUE_CREATED.cast(rec); TraceObjectValue affected = cast.getAffectedObject(); String key = affected.getEntryKey(); boolean applies = TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME.equals(key) || diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectMemoryRegion.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectMemoryRegion.java index bbdbfa67d0..a0f60b4826 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectMemoryRegion.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectMemoryRegion.java @@ -98,6 +98,10 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra private final DBTraceObject object; private final RegionChangeTranslator translator; + // Keep copies here for when the object gets invalidated + private AddressRange range; + private Range lifespan; + public DBTraceObjectMemoryRegion(DBTraceObject object) { this.object = object; @@ -114,10 +118,15 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra return object.getCanonicalPath().toString(); } + @Override + public void setName(Range lifespan, String name) { + object.setValue(lifespan, TargetObject.DISPLAY_ATTRIBUTE_NAME, name); + } + @Override public void setName(String name) { 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 public void setLifespan(Range newLifespan) throws DuplicateNameException { try (LockHold hold = object.getTrace().lockWrite()) { - Range oldLifespan = getLifespan(); - if (Objects.equals(oldLifespan, newLifespan)) { - return; - } TraceObjectInterfaceUtils.setLifespan(TraceObjectMemoryRegion.class, object, newLifespan); + this.lifespan = newLifespan; } } @Override public Range getLifespan() { - return object.getLifespan(); + try (LockHold hold = object.getTrace().lockRead()) { + Range computed = computeSpan(); + if (computed != null) { + lifespan = computed; + } + return lifespan; + } } @Override @@ -154,7 +166,7 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra @Override public long getCreationSnap() { - return object.getMinSnap(); + return computeMinSnap(); } @Override @@ -166,25 +178,32 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra @Override public long getDestructionSnap() { - return object.getMaxSnap(); + return computeMaxSnap(); + } + + @Override + public void setRange(Range lifespan, AddressRange newRange) { + try (LockHold hold = object.getTrace().lockWrite()) { + object.setValue(lifespan, TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, newRange); + this.range = newRange; + } } @Override public void setRange(AddressRange newRange) { try (LockHold hold = object.getTrace().lockWrite()) { - AddressRange oldRange = getRange(); - if (Objects.equals(oldRange, newRange)) { - return; - } - object.setValue(getLifespan(), TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, newRange); + setRange(computeSpan(), newRange); } } @Override public AddressRange getRange() { try (LockHold hold = object.getTrace().lockRead()) { - return TraceObjectInterfaceUtils.getValue(object, getCreationSnap(), - TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, AddressRange.class, null); + if (object.getLife().isEmpty()) { + 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 public void delete() { try (LockHold hold = object.getTrace().lockWrite()) { - object.deleteTree(); + object.removeTree(computeSpan()); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectRegister.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectRegister.java index fec11fed57..987c29b518 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectRegister.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectRegister.java @@ -45,7 +45,7 @@ public class DBTraceObjectRegister implements TraceObjectRegister, DBTraceObject @Override public TraceObjectThread getThread() { - return object.queryAncestorsInterface(object.getLifespan(), TraceObjectThread.class) + return object.queryCanonicalAncestorsInterface(TraceObjectThread.class) .findAny() .orElseThrow(); } @@ -61,7 +61,7 @@ public class DBTraceObjectRegister implements TraceObjectRegister, DBTraceObject @Override public int getLength() { - return TraceObjectInterfaceUtils.getValue(object, object.getMinSnap(), + return TraceObjectInterfaceUtils.getValue(object, computeMinSnap(), TargetRegister.LENGTH_ATTRIBUTE_NAME, Integer.class, 0); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectModule.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectModule.java index b6cfa0f61e..b39282def7 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectModule.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectModule.java @@ -74,6 +74,10 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte private final DBTraceObject object; private final ModuleChangeTranslator translator; + // Keep copies here for when the object gets invalidated + private AddressRange range; + private Range lifespan; + public DBTraceObjectModule(DBTraceObject object) { this.object = object; @@ -104,10 +108,15 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte return object.getCanonicalPath().toString(); } + @Override + public void setName(Range lifespan, String name) { + object.setValue(lifespan, TargetModule.MODULE_NAME_ATTRIBUTE_NAME, name); + } + @Override public void setName(String name) { 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, ""); } + @Override + public void setRange(Range lifespan, AddressRange range) { + try (LockHold hold = object.getTrace().lockWrite()) { + object.setValue(lifespan, TargetModule.RANGE_ATTRIBUTE_NAME, range); + this.range = range; + } + } + @Override public void setRange(AddressRange range) { try (LockHold hold = object.getTrace().lockWrite()) { - object.setValue(getLifespan(), TargetModule.RANGE_ATTRIBUTE_NAME, range); + setRange(computeSpan(), range); } } @Override public AddressRange getRange() { - return TraceObjectInterfaceUtils.getValue(object, getLoadedSnap(), - TargetModule.RANGE_ATTRIBUTE_NAME, AddressRange.class, null); + try (LockHold hold = object.getTrace().lockRead()) { + if (object.getLife().isEmpty()) { + return range; + } + return range = TraceObjectInterfaceUtils.getValue(object, getLoadedSnap(), + TargetModule.RANGE_ATTRIBUTE_NAME, AddressRange.class, range); + } } @Override @@ -172,15 +194,23 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte public void setLifespan(Range lifespan) throws DuplicateNameException { try (LockHold hold = object.getTrace().lockWrite()) { TraceObjectInterfaceUtils.setLifespan(TraceObjectModule.class, object, lifespan); + this.lifespan = lifespan; for (TraceObjectSection section : getSections()) { - section.getObject().setLifespan(lifespan); + TraceObjectInterfaceUtils.setLifespan(TraceObjectSection.class, section.getObject(), + lifespan); } } } @Override public Range getLifespan() { - return object.getLifespan(); + try (LockHold hold = object.getTrace().lockRead()) { + Range computed = computeSpan(); + if (computed != null) { + lifespan = computed; + } + return lifespan; + } } @Override @@ -192,7 +222,7 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte @Override public long getLoadedSnap() { - return object.getMinSnap(); + return computeMinSnap(); } @Override @@ -204,7 +234,7 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte @Override public long getUnloadedSnap() { - return object.getMaxSnap(); + return computeMaxSnap(); } @Override @@ -220,14 +250,16 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte PathMatcher matcher = object.getTargetSchema().searchFor(TargetSection.class, true); PathMatcher applied = matcher.applyKeys(List.of(sectionName)); return object.getSuccessors(getLifespan(), applied) - .map(p -> p.getLastChild(object).queryInterface(TraceObjectSection.class)) + .map(p -> p.getDestination(object).queryInterface(TraceObjectSection.class)) .findAny() .orElse(null); } @Override public void delete() { - object.deleteTree(); + try (LockHold hold = object.getTrace().lockWrite()) { + object.removeTree(computeSpan()); + } } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectSection.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectSection.java index df8e7c614b..892475a5bb 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectSection.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectSection.java @@ -23,13 +23,13 @@ import ghidra.trace.database.target.DBTraceObject; import ghidra.trace.database.target.DBTraceObjectInterface; import ghidra.trace.model.Trace; 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.annot.TraceObjectInterfaceUtils; import ghidra.trace.util.TraceChangeRecord; import ghidra.trace.util.TraceChangeType; import ghidra.util.LockHold; -import ghidra.util.exception.DuplicateNameException; public class DBTraceObjectSection implements TraceObjectSection, DBTraceObjectInterface { @@ -68,6 +68,9 @@ public class DBTraceObjectSection implements TraceObjectSection, DBTraceObjectIn private final DBTraceObject object; private final SectionTranslator translator; + // Keep copies here for when the object gets invalidated + private AddressRange range; + public DBTraceObjectSection(DBTraceObject object) { this.object = object; @@ -80,9 +83,9 @@ public class DBTraceObjectSection implements TraceObjectSection, DBTraceObjectIn } @Override - public TraceModule getModule() { + public TraceObjectModule getModule() { try (LockHold hold = object.getTrace().lockRead()) { - return object.queryAncestorsInterface(object.getLifespan(), TraceObjectModule.class) + return object.queryCanonicalAncestorsInterface(TraceObjectModule.class) .findAny() .orElseThrow(); } @@ -94,30 +97,56 @@ public class DBTraceObjectSection implements TraceObjectSection, DBTraceObjectIn } @Override - public void setName(String name) throws DuplicateNameException { - object.setValue(object.getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name); + public void setName(Range lifespan, String 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 public String getName() { - return TraceObjectInterfaceUtils.getValue(object, object.getMinSnap(), + return TraceObjectInterfaceUtils.getValue(object, computeMinSnap(), TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, ""); } @Override - public void setRange(AddressRange range) { - object.setValue(object.getLifespan(), TargetModule.RANGE_ATTRIBUTE_NAME, range); + public void setRange(Range lifespan, AddressRange range) { + try (LockHold hold = object.getTrace().lockWrite()) { + object.setValue(lifespan, TargetModule.RANGE_ATTRIBUTE_NAME, range); + this.range = range; + } } @Override public AddressRange getRange() { - return TraceObjectInterfaceUtils.getValue(object, object.getMinSnap(), - TargetModule.RANGE_ATTRIBUTE_NAME, AddressRange.class, null); + try (LockHold hold = object.getTrace().lockRead()) { + if (object.getLife().isEmpty()) { + return range; + } + return range = TraceObjectInterfaceUtils.getValue(object, computeMinSnap(), + TargetModule.RANGE_ATTRIBUTE_NAME, AddressRange.class, range); + } + } + + @Override + public Range computeSpan() { + Range span = DBTraceObjectInterface.super.computeSpan(); + if (span != null) { + return span; + } + return getModule().computeSpan(); } @Override public void delete() { - object.deleteTree(); + try (LockHold hold = object.getTrace().lockWrite()) { + object.removeTree(computeSpan()); + } } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/TraceObjectSection.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/TraceObjectSection.java index 3a8e903008..a34fb2d0df 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/TraceObjectSection.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/TraceObjectSection.java @@ -15,6 +15,8 @@ */ package ghidra.trace.database.module; +import com.google.common.collect.Range; + import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetSection; import ghidra.program.model.address.AddressRange; @@ -30,5 +32,7 @@ import ghidra.trace.model.target.annot.TraceObjectInfo; TargetSection.RANGE_ATTRIBUTE_NAME }) public interface TraceObjectSection extends TraceSection, TraceObjectInterface { - void setRange(AddressRange range); + void setName(Range lifespan, String name); + + void setRange(Range lifespan, AddressRange range); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceObjectStack.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceObjectStack.java index 8701d81e82..bf04fbdaa4 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceObjectStack.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceObjectStack.java @@ -80,7 +80,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf @Override public TraceThread getThread() { try (LockHold hold = object.getTrace().lockRead()) { - return object.queryAncestorsInterface(object.getLifespan(), TraceObjectThread.class) + return object.queryAncestorsInterface(computeSpan(), TraceObjectThread.class) .findAny() .orElseThrow(); } @@ -88,14 +88,14 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf @Override public long getSnap() { - return object.getMinSnap(); + return computeMinSnap(); } @Override public int getDepth() { try (LockHold hold = object.getTrace().lockRead()) { return object - .querySuccessorsInterface(object.getLifespan(), TraceObjectStackFrame.class) + .querySuccessorsInterface(computeSpan(), TraceObjectStackFrame.class) .map(f -> f.getLevel()) .reduce(Integer::max) .map(m -> m + 1) @@ -119,8 +119,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf protected void copyFrameAttributes(TraceObjectStackFrame from, TraceObjectStackFrame to) { // TODO: All attributes within a given span, intersected to that span? - to.setProgramCounter(to.getObject().getLifespan(), - from.getProgramCounter(from.getObject().getMaxSnap())); + to.setProgramCounter(computeSpan(), from.getProgramCounter(computeMaxSnap())); } 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 frames) { for (int i = start; i < end; 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 try (LockHold hold = object.getTrace().lockWrite()) { List frames = // Want mutable list - doGetFrames().collect(Collectors.toCollection(ArrayList::new)); + doGetFrames(computeMinSnap()).collect(Collectors.toCollection(ArrayList::new)); int curDepth = frames.size(); if (curDepth == depth) { return; @@ -163,7 +162,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf shiftFrameAttributes(diff, 0, depth, frames); } for (int i = depth; i < curDepth; i++) { - frames.get(i).getObject().deleteTree(); + frames.get(i).getObject().removeTree(computeSpan()); } } else { @@ -183,9 +182,9 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf TargetObjectSchema schema = object.getTargetSchema(); PathPredicates matcher = schema.searchFor(TargetStackFrame.class, true); matcher = matcher.applyKeys(PathUtils.makeIndex(level)); - return object.getSuccessors(object.getLifespan(), matcher) + return object.getSuccessors(computeSpan(), matcher) .findAny() - .map(p -> p.getLastChild(object).queryInterface(TraceObjectStackFrame.class)) + .map(p -> p.getDestination(object).queryInterface(TraceObjectStackFrame.class)) .orElse(null); } @@ -207,22 +206,24 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf } } - protected Stream doGetFrames() { + protected Stream doGetFrames(long snap) { return object - .querySuccessorsInterface(object.getLifespan(), TraceObjectStackFrame.class) + .querySuccessorsInterface(Range.singleton(snap), TraceObjectStackFrame.class) .sorted(Comparator.comparing(f -> f.getLevel())); } @Override - public List getFrames() { + public List getFrames(long snap) { try (LockHold hold = object.getTrace().lockRead()) { - return doGetFrames().collect(Collectors.toList()); + return doGetFrames(snap).collect(Collectors.toList()); } } @Override public void delete() { - object.deleteTree(); + try (LockHold hold = object.getTrace().lockWrite()) { + object.removeTree(computeSpan()); + } } @Override @@ -234,4 +235,9 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf public TraceChangeRecord translateEvent(TraceChangeRecord rec) { return translator.translate(rec); } + + @Override + public boolean hasFixedFrames() { + return false; + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceObjectStackFrame.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceObjectStackFrame.java index 3477101e29..ed25c063f3 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceObjectStackFrame.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceObjectStackFrame.java @@ -17,12 +17,13 @@ package ghidra.trace.database.stack; import java.util.List; -import com.google.common.collect.Range; +import com.google.common.collect.*; import ghidra.dbg.target.TargetStackFrame; import ghidra.dbg.util.PathUtils; import ghidra.program.model.address.Address; import ghidra.program.model.listing.CodeUnit; +import ghidra.trace.database.DBTraceUtils; import ghidra.trace.database.target.DBTraceObject; import ghidra.trace.database.target.DBTraceObjectInterface; 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.TraceObjectValue; import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils; -import ghidra.trace.util.TraceAddressSpace; import ghidra.trace.util.TraceChangeRecord; import ghidra.util.LockHold; public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceObjectInterface { private final DBTraceObject object; + // TODO: Memorizing life is not optimal. + // GP-1887 means to expose multiple lifespans in, e.g., TraceThread + private RangeSet life = TreeRangeSet.create(); public DBTraceObjectStackFrame(DBTraceObject object) { this.object = object; @@ -46,8 +49,7 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb @Override public TraceObjectStack getStack() { try (LockHold hold = object.getTrace().lockRead()) { - return object - .queryCanonicalAncestorsInterface(object.getLifespan(), TraceObjectStack.class) + return object.queryCanonicalAncestorsInterface(TraceObjectStack.class) .findAny() .orElseThrow(); } @@ -85,16 +87,14 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb if (pc == Address.NO_ADDRESS) { pc = null; } - object.setValue(object.getLifespan().intersection(span), - TargetStackFrame.PC_ATTRIBUTE_NAME, pc); + object.setValue(span, TargetStackFrame.PC_ATTRIBUTE_NAME, pc); } } @Override - public String getComment() { - // TODO: Do I need to add a snap argument? + public String getComment(long snap) { // 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 * 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 @@ -105,21 +105,23 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb * follow the "same frame" as its level changes. */ try (LockHold hold = object.getTrace().lockRead()) { - Address pc = getProgramCounter(object.getMaxSnap()); + Address pc = getProgramCounter(snap); return pc == null ? null : object.getTrace() .getCommentAdapter() - .getComment(object.getMaxSnap(), pc, CodeUnit.EOL_COMMENT); + .getComment(snap, pc, CodeUnit.EOL_COMMENT); } } @Override - public void setComment(String comment) { - // TODO: Do I need to add a span argument? + public void setComment(long snap, String comment) { + /* See rant in getComment */ try (LockHold hold = object.getTrace().lockWrite()) { + TraceObjectValue pcAttr = + object.getValue(snap, TargetStackFrame.PC_ATTRIBUTE_NAME); object.getTrace() .getCommentAdapter() - .setComment(object.getLifespan(), getProgramCounter(object.getMaxSnap()), + .setComment(pcAttr.getLifespan(), (Address) pcAttr.getValue(), CodeUnit.EOL_COMMENT, comment); } } @@ -130,8 +132,8 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb } protected boolean changeApplies(TraceChangeRecord rec) { - TraceChangeRecord cast = - TraceObjectChangeType.VALUE_CHANGED.cast(rec); + TraceChangeRecord cast = + TraceObjectChangeType.VALUE_CREATED.cast(rec); TraceObjectValue affected = cast.getAffectedObject(); assert affected.getParent() == object; if (!TargetStackFrame.PC_ATTRIBUTE_NAME.equals(affected.getEntryKey())) { @@ -143,24 +145,45 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb return true; } - protected long snapFor(TraceChangeRecord rec) { - if (rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType()) { - return TraceObjectChangeType.VALUE_CHANGED.cast(rec).getAffectedObject().getMinSnap(); + @Override + public Range computeSpan() { + Range 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 public TraceChangeRecord translateEvent(TraceChangeRecord rec) { - if (rec.getEventType() == TraceObjectChangeType.INSERTED.getType() || - rec.getEventType() == TraceObjectChangeType.DELETED.getType() || - rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType() && - changeApplies(rec)) { - TraceAddressSpace space = - spaceForValue(object.getMinSnap(), TargetStackFrame.PC_ATTRIBUTE_NAME); - TraceObjectStack stack = getStack(); - return new TraceChangeRecord<>(TraceStackChangeType.CHANGED, space, stack, - 0L, snapFor(rec)); + int type = rec.getEventType(); + if (type == TraceObjectChangeType.LIFE_CHANGED.getType()) { + RangeSet newLife = object.getLife(); + if (!newLife.isEmpty()) { + life = newLife; + } + return createChangeRecord(); + } + 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; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStack.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStack.java index 799886c647..76adb1366a 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStack.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStack.java @@ -237,7 +237,7 @@ public class DBTraceStack extends DBAnnotatedObject implements TraceStack { } @Override - public List getFrames() { + public List getFrames(long snap) { try (LockHold hold = LockHold.lock(manager.lock.readLock())) { return List.copyOf(frames); } @@ -254,4 +254,9 @@ public class DBTraceStack extends DBAnnotatedObject implements TraceStack { manager.trace .setChanged(new TraceChangeRecord<>(TraceStackChangeType.DELETED, null, this)); } + + @Override + public boolean hasFixedFrames() { + return true; + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStackFrame.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStackFrame.java index 9468404aab..d178f29208 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStackFrame.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStackFrame.java @@ -122,12 +122,12 @@ public class DBTraceStackFrame extends DBAnnotatedObject } @Override - public String getComment() { + public String getComment(long snap) { return comment; } @Override - public void setComment(String comment) { + public void setComment(long snap, String comment) { try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { this.comment = comment; update(COMMENT_COLUMN); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObject.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObject.java index c0b9951f84..4c88fc207d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObject.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObject.java @@ -23,8 +23,7 @@ import java.util.stream.*; import org.apache.commons.collections4.IteratorUtils; -import com.google.common.collect.Iterators; -import com.google.common.collect.Range; +import com.google.common.collect.*; import db.DBRecord; import db.StringField; @@ -32,7 +31,6 @@ import ghidra.dbg.target.TargetBreakpointLocation; import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.schema.TargetObjectSchema; import ghidra.dbg.util.*; -import ghidra.lifecycle.Experimental; import ghidra.program.model.address.*; import ghidra.trace.database.DBTrace; import ghidra.trace.database.DBTraceUtils; @@ -47,8 +45,7 @@ import ghidra.trace.database.stack.DBTraceObjectStack; import ghidra.trace.database.stack.DBTraceObjectStackFrame; import ghidra.trace.database.target.DBTraceObjectValue.PrimaryTriple; import ghidra.trace.database.target.InternalTraceObjectValue.ValueLifespanSetter; -import ghidra.trace.database.target.LifespanCorrector.Direction; -import ghidra.trace.database.target.LifespanCorrector.Operation; +import ghidra.trace.database.target.InternalTreeTraversal.Visitor; import ghidra.trace.database.thread.DBTraceObjectThread; import ghidra.trace.model.Trace.TraceObjectChangeType; import ghidra.trace.model.breakpoint.TraceObjectBreakpointLocation; @@ -128,30 +125,18 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { // Canonical path static final String PATH_COLUMN_NAME = "Path"; - static final String MIN_SNAP_COLUMN_NAME = "MinSnap"; - static final String MAX_SNAP_COLUMN_NAME = "MaxSnap"; @DBAnnotatedColumn(PATH_COLUMN_NAME) static DBObjectColumn PATH_COLUMN; - @DBAnnotatedColumn(MIN_SNAP_COLUMN_NAME) - static DBObjectColumn MIN_SNAP_COLUMN; - @DBAnnotatedColumn(MAX_SNAP_COLUMN_NAME) - static DBObjectColumn MAX_SNAP_COLUMN; @DBAnnotatedField( column = PATH_COLUMN_NAME, codec = ObjectPathDBFieldCodec.class, indexed = true) private TraceObjectKeyPath path; - @DBAnnotatedField(column = MIN_SNAP_COLUMN_NAME) - private long minSnap; - @DBAnnotatedField(column = MAX_SNAP_COLUMN_NAME) - private long maxSnap; protected final DBTraceObjectManager manager; - private Range lifespan; - private Map, TraceObjectInterface> ifaces; public DBTraceObject(DBTraceObjectManager manager, DBCachedObjectStore store, @@ -168,7 +153,6 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { if (path != null) { freshIfaces(); } - lifespan = DBTraceUtils.toRange(minSnap, maxSnap); } @Override @@ -189,29 +173,13 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { Collectors.toUnmodifiableMap(e -> e.getKey(), e -> e.getValue().apply(this))); } - protected void set(TraceObjectKeyPath path, Range lifespan) { + protected void set(TraceObjectKeyPath path) { this.path = path; - this.lifespan = lifespan; - this.doSetLifespan(lifespan); update(PATH_COLUMN); freshIfaces(); } - protected void doSetLifespan(Range lifespan) { - this.minSnap = DBTraceUtils.lowerEndpoint(lifespan); - this.maxSnap = DBTraceUtils.upperEndpoint(lifespan); - update(MIN_SNAP_COLUMN, MAX_SNAP_COLUMN); - this.lifespan = DBTraceUtils.toRange(minSnap, maxSnap); - } - - protected void doSetLifespanAndEmit(Range lifespan) { - Range oldLifespan = getLifespan(); - doSetLifespan(lifespan); - emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.LIFESPAN_CHANGED, null, this, - oldLifespan, lifespan)); - } - @Override public DBTrace getTrace() { return manager.trace; @@ -234,42 +202,97 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { } @Override - public void insert(ConflictResolution resolution) { + public RangeSet getLife() { + try (LockHold hold = manager.trace.lockRead()) { + RangeSet result = TreeRangeSet.create(); + // NOTE: connected ranges should already be coalesced + // No need to apply discreet domain + getCanonicalParents(Range.all()).forEach(v -> result.add(v.getLifespan())); + return result; + } + } + + protected DBTraceObject doCreateCanonicalParentObject() { + return manager.doCreateObject(path.parent()); + } + + protected DBTraceObject doGetCanonicalParentObject() { + return manager.doGetObject(path.parent()); + } + + protected void doInsert(Range lifespan, ConflictResolution resolution) { + if (path.isRoot()) { + return; + } + DBTraceObject parent = doCreateCanonicalParentObject(); + parent.setValue(lifespan, path.key(), this, resolution); + parent.doInsert(lifespan, resolution); + } + + @Override + public void insert(Range lifespan, ConflictResolution resolution) { try (LockHold hold = manager.trace.lockWrite()) { - for (InternalTraceObjectValue val : getParents()) { - if (val.isCanonical() && DBTraceUtils.intersect(val.getLifespan(), lifespan)) { - return; - } + doInsert(lifespan, resolution); + } + } + + protected void doRemove(Range span) { + if (isRoot()) { + throw new IllegalArgumentException("Cannot remove the root object"); + } + DBTraceObject parent = doGetCanonicalParentObject(); + parent.setValue(span, path.key(), null); + // Do not recurse on parent + } + + @Override + public void remove(Range span) { + try (LockHold hold = manager.trace.lockWrite()) { + doRemove(span); + } + } + + protected void doRemoveTree(Range span) { + for (InternalTraceObjectValue value : getValues()) { + value.doTruncateOrDeleteAndEmitLifeChange(span); + if (value.isCanonical()) { + value.getChild().doRemoveTree(span); } - TraceObjectKeyPath parentPath = path.parent(); - for (DBTraceObject parent : manager.getObjectsByCanonicalPath(parentPath)) { - if (DBTraceUtils.intersect(parent.getLifespan(), lifespan)) { - parent.setValue(lifespan, path.key(), this, resolution); - return; - } - } - DBTraceObject parent = manager.createObject(parentPath, lifespan); - parent.setValue(lifespan, path.key(), this, resolution); - parent.insert(resolution); + } + } + + @Override + public void removeTree(Range span) { + try (LockHold hold = manager.trace.lockWrite()) { + getCanonicalParents(span).forEach(v -> v.doTruncateOrDeleteAndEmitLifeChange(span)); + doRemoveTree(span); } } @Override public TraceObjectValue getCanonicalParent(long snap) { - // TODO: If this is invoked often, perhaps keep as field try (LockHold hold = manager.trace.lockRead()) { if (isRoot()) { return manager.valueStore.getObjectAt(0); } + return getCanonicalParents(Range.singleton(snap)).findAny().orElse(null); + } + } + + @Override + public Stream getCanonicalParents(Range lifespan) { + // TODO: If this is invoked often, perhaps index + try (LockHold hold = manager.trace.lockRead()) { + if (isRoot()) { + return Stream.of(manager.valueStore.getObjectAt(0)); + } String canonicalKey = path.key(); TraceObjectKeyPath canonicalTail = path.parent(); return manager.valuesByChild.getLazily(this) .stream() .filter(v -> canonicalKey.equals(v.getEntryKey())) - .filter(v -> v.getLifespan().contains(snap)) - .filter(v -> canonicalTail.equals(v.getParent().getCanonicalPath())) - .findAny() - .orElse(null); + .filter(v -> DBTraceUtils.intersect(v.getLifespan(), lifespan)) + .filter(v -> canonicalTail.equals(v.getParent().getCanonicalPath())); } } @@ -280,65 +303,13 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { } } - protected Stream doGetAllPaths(Range span, - DBTraceObjectValPath post) { - if (isRoot()) { - return Stream.of(post); - } - return getParents().stream() - .filter(e -> !post.contains(e)) - .flatMap(e -> e.doGetAllPaths(span, post)); - } - @Override - public Stream getAllPaths(Range span) { + public Stream getAllPaths(Range span) { try (LockHold hold = manager.trace.lockRead()) { - return doGetAllPaths(span, DBTraceObjectValPath.of()); - } - } - - @Override - public void setLifespan(Range lifespan) { - // TODO: Could derive fixed attributes from schema and set their lifespans, too.... - try (LockHold hold = manager.trace.lockWrite()) { - doSetLifespanAndEmit(lifespan); - } - } - - @Experimental - public void correctLifespans(Direction direction, Operation operation, - ConflictResolution resolution) { - new LifespanCorrector(direction, operation, resolution).correctLifespans(this); - } - - @Override - public Range getLifespan() { - try (LockHold hold = manager.trace.lockRead()) { - return lifespan; - } - } - - @Override - public void setMinSnap(long minSnap) { - setLifespan(DBTraceUtils.toRange(minSnap, maxSnap)); - } - - @Override - public long getMinSnap() { - try (LockHold hold = manager.trace.lockRead()) { - return minSnap; - } - } - - @Override - public void setMaxSnap(long maxSnap) { - setLifespan(DBTraceUtils.toRange(minSnap, maxSnap)); - } - - @Override - public long getMaxSnap() { - try (LockHold hold = manager.trace.lockRead()) { - return maxSnap; + if (isRoot()) { + return Stream.of(DBTraceObjectValPath.of()); + } + return doStreamVisitor(span, InternalAllPathsVisitor.INSTANCE); } } @@ -374,24 +345,65 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { } } + protected boolean doHasAnyNonRangedValues() { + for (DBTraceObjectValue val : manager.valuesByTriple + .tail(new PrimaryTriple(this, "", Long.MIN_VALUE), true) + .values()) { + if (val.getParent() != this) { + return false; + } + return true; + } + return false; + } + + protected void collectRangedValues(Collection result) { + for (DBTraceAddressSnapRangePropertyMapSpace space // + : manager.rangeValueMap.getActiveMemorySpaces()) { + for (DBTraceObjectAddressRangeValue val : space.values()) { + if (val.getParent() != this) { + continue; + } + result.add(val); + } + } + } + + protected boolean doHasAnyRangedValues() { + for (DBTraceAddressSnapRangePropertyMapSpace space // + : manager.rangeValueMap.getActiveMemorySpaces()) { + for (DBTraceObjectAddressRangeValue val : space.values()) { + if (val.getParent() == this) { + return true; + } + } + } + return false; + } + + protected Collection doGetValues() { + List result = new ArrayList<>(); + collectNonRangedValues(result); + collectRangedValues(result); + return result; + } + + protected boolean doHasAnyValues() { + return doHasAnyNonRangedValues() || doHasAnyRangedValues(); + } + + protected boolean doHasAnyParents() { + return manager.valuesByChild.containsKey(this); + } + + protected boolean doIsConnected() { + return doHasAnyParents() || doHasAnyValues(); + } + @Override public Collection getValues() { try (LockHold hold = manager.trace.lockRead()) { - List result = new ArrayList<>(); - collectNonRangedValues(result); - - for (DBTraceAddressSnapRangePropertyMapSpace space : manager.rangeValueMap - .getActiveMemorySpaces()) { - for (DBTraceObjectAddressRangeValue val : space.values()) { - if (val.getParent() != this) { - continue; - } - result.add(val); - } - } - - return result; + return doGetValues(); } } @@ -607,67 +619,27 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { return getValue(snap, name); } - protected Stream doGetAncestors(Range span, - DBTraceObjectValPath post, PathPredicates predicates) { - if (predicates.matches(getCanonicalPath().getKeyList())) { - return Stream.of(post); - } - if (isRoot()) { - return Stream.empty(); - } - return getParents().stream() - .filter(e -> !post.contains(e)) - .flatMap(e -> e.doGetAncestors(span, post, predicates)); + protected Stream doStreamVisitor(Range span, + Visitor visitor) { + return InternalTreeTraversal.INSTANCE.walkObject(visitor, this, span, + DBTraceObjectValPath.of()); } @Override public Stream getAncestors( Range span, PathPredicates rootPredicates) { try (LockHold hold = manager.trace.lockRead()) { - return doGetAncestors(span, DBTraceObjectValPath.of(), rootPredicates); + return doStreamVisitor(span, new InternalAncestorsVisitor(rootPredicates)); } } - protected Stream doGetSuccessors( - Range span, DBTraceObjectValPath pre, PathPredicates predicates) { - Set nextKeys = predicates.getNextKeys(pre.getKeyList()); - if (nextKeys.isEmpty()) { - return Stream.empty(); - } - - Stream attrStream; - if (nextKeys.contains("")) { - attrStream = doGetAttributes().stream() - .filter(v -> DBTraceUtils.intersect(span, v.getLifespan())); - } - else { - attrStream = Stream.empty(); - } - - Stream elemStream; - if (nextKeys.contains("[]")) { - elemStream = doGetElements().stream() - .filter(v -> DBTraceUtils.intersect(span, v.getLifespan())); - } - else { - elemStream = Stream.empty(); - } - - Stream restStream = nextKeys.stream() - .filter(k -> !"".equals(k) && !"[]".equals(k)) - .flatMap(k -> doGetValues(span, k).stream()); - - return Stream.concat(Stream.concat(attrStream, elemStream), restStream) - .flatMap(v -> v.doGetSuccessors(span, pre, predicates)); - } - @Override public Stream getSuccessors( Range span, PathPredicates relativePredicates) { DBTraceObjectValPath empty = DBTraceObjectValPath.of(); try (LockHold hold = manager.trace.lockRead()) { Stream succcessors = - doGetSuccessors(span, empty, relativePredicates); + doStreamVisitor(span, new InternalSuccessorsVisitor(relativePredicates)); if (relativePredicates.matches(List.of())) { // Pre-cat the empty path (not the empty stream) return Stream.concat(Stream.of(empty), succcessors); @@ -676,23 +648,6 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { } } - protected Stream doGetOrderedSuccessors(Range span, - DBTraceObjectValPath pre, PathPredicates predicates, boolean forward) { - Set nextKeys = predicates.getNextKeys(pre.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 doGetOrderedValues(span, next, forward) - .flatMap(v -> v.doGetOrderedSuccessors(span, pre, predicates, forward)); - } - @Override public Stream getOrderedSuccessors(Range span, TraceObjectKeyPath relativePath, boolean forward) { @@ -701,8 +656,8 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { if (relativePath.isRoot()) { return Stream.of(empty); // Not the empty stream } - return doGetOrderedSuccessors(span, empty, - new PathPattern(relativePath.getKeyList()), forward); + return doStreamVisitor(span, + new InternalOrderedSuccessorsVisitor(relativePath, forward)); } } @@ -752,17 +707,6 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { } } - protected void emitIfCanonicalInsertion(InternalTraceObjectValue value) { - if (value == null || !(value.getValue() instanceof DBTraceObject)) { - return; - } - DBTraceObject child = (DBTraceObject) value.getValue(); - if (this.path.extend(value.getEntryKey()).equals(child.getCanonicalPath())) { - child.emitEvents( - new TraceChangeRecord<>(TraceObjectChangeType.INSERTED, null, child, value)); - } - } - @Override public InternalTraceObjectValue setValue(Range lifespan, String key, Object value, ConflictResolution resolution) { @@ -770,35 +714,49 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { if (isDeleted()) { throw new IllegalStateException("Cannot set value on deleted object."); } - InternalTraceObjectValue oldEntry = getValue(DBTraceUtils.lowerEndpoint(lifespan), key); - Object oldVal = null; - if (oldEntry != null && oldEntry.getLifespan().encloses(lifespan)) { - oldVal = oldEntry.getValue(); - } if (resolution == ConflictResolution.DENY) { doCheckConflicts(lifespan, key, value); } - InternalTraceObjectValue result = new ValueLifespanSetter(lifespan, value) { + var setter = new ValueLifespanSetter(lifespan, value) { + DBTraceObject canonicalLifeChanged = null; + @Override protected Iterable getIntersecting(Long lower, Long upper) { return Collections.unmodifiableCollection(doGetValues(lower, upper, key)); } + @Override + protected void remove(InternalTraceObjectValue entry) { + if (entry.isCanonical()) { + canonicalLifeChanged = entry.getChild(); + } + super.remove(entry); + } + + @Override + protected InternalTraceObjectValue put(Range range, Object value) { + InternalTraceObjectValue entry = super.put(range, value); + if (entry != null && entry.isCanonical()) { + canonicalLifeChanged = entry.getChild(); + } + return entry; + } + @Override protected InternalTraceObjectValue create(Range range, Object value) { return doCreateValue(range, key, value); } - }.set(lifespan, value); - if (result == null && oldEntry == null) { - return null; - } - emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.VALUE_CHANGED, - null, result != null ? result : oldEntry, oldVal, value)); + }; + InternalTraceObjectValue result = setter.set(lifespan, value); - // NB. It will cause another event. good. + // NB. This hack will cause more value events. good. applyBreakpointRangeHack(lifespan, key, value, resolution); - emitIfCanonicalInsertion(result); + DBTraceObject child = setter.canonicalLifeChanged; + if (child != null) { + child.emitEvents( + new TraceChangeRecord<>(TraceObjectChangeType.LIFE_CHANGED, null, child)); + } return result; } } @@ -843,36 +801,25 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { public Stream queryAncestorsInterface(Range span, Class ifClass) { return queryAncestorsTargetInterface(span, TraceObjectInterfaceUtils.toTargetIf(ifClass)) - .map(p -> p.getFirstParent(this).queryInterface(ifClass)); + .map(p -> p.getSource(this).queryInterface(ifClass)); } @Override - public Stream queryCanonicalAncestorsTargetInterface(Range span, + public Stream queryCanonicalAncestorsTargetInterface( Class targetIf) { // This is a sort of meet-in-the-middle. The type search must originate from the root PathMatcher matcher = getManager().getRootSchema().searchFor(targetIf, false); - List parentPath = getCanonicalPath().getKeyList(); - if (!matcher.ancestorMatches(parentPath, false)) { - return Stream.of(); + try (LockHold hold = manager.trace.lockRead()) { + return path.streamMatchingAncestry(matcher) + .map(kp -> manager.getObjectByCanonicalPath(kp)); } - for (; !parentPath.isEmpty(); parentPath = PathUtils.parent(parentPath)) { - if (matcher.matches(parentPath)) { - return manager.getObjectsByCanonicalPath(TraceObjectKeyPath.of(parentPath)) - .stream() - .filter(o -> DBTraceUtils.intersect(span, o.getLifespan())) - // TODO: Post filter until GP-1301 - .filter(o -> o.getTargetSchema().getInterfaces().contains(targetIf)); - } - } - return Stream.of(); } @Override public Stream queryCanonicalAncestorsInterface( - Range span, Class ifClass) { - return queryCanonicalAncestorsTargetInterface(span, - TraceObjectInterfaceUtils.toTargetIf(ifClass)) - .map(o -> o.queryInterface(ifClass)); + Class ifClass) { + return queryCanonicalAncestorsTargetInterface(TraceObjectInterfaceUtils.toTargetIf(ifClass)) + .map(o -> o.queryInterface(ifClass)); } @Override @@ -881,14 +828,14 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { PathMatcher matcher = getTargetSchema().searchFor(targetIf, true); // TODO: Post filter until GP-1301 return getSuccessors(span, matcher).filter( - p -> p.getLastChild(this).getTargetSchema().getInterfaces().contains(targetIf)); + p -> p.getDestination(this).getTargetSchema().getInterfaces().contains(targetIf)); } @Override public Stream querySuccessorsInterface(Range span, Class ifClass) { return querySuccessorsTargetInterface(span, TraceObjectInterfaceUtils.toTargetIf(ifClass)) - .map(p -> p.getLastChild(this).queryInterface(ifClass)); + .map(p -> p.getDestination(this).queryInterface(ifClass)); } protected void doDelete() { @@ -897,18 +844,10 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { protected void doDeleteReferringValues() { for (InternalTraceObjectValue child : getValues()) { - child.doDelete(); + child.doDeleteAndEmit(); } for (DBTraceObjectValue parent : getParents()) { - parent.doDelete(); - } - } - - protected void doDeleteSuccessors() { - List children = new ArrayList<>(); - collectNonRangedValues(children); - for (DBTraceObjectValue child : children) { - child.doDeleteSuccessors(); + parent.doDeleteAndEmit(); } } @@ -920,36 +859,6 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { } } - protected void doDeleteTree() { - doDeleteSuccessors(); - doDeleteReferringValues(); - doDelete(); - } - - @Override - public void deleteTree() { - try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { - doDeleteTree(); - } - } - - @Override - public DBTraceObject truncateOrDelete(Range span) { - try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { - List> removed = DBTraceUtils.subtract(lifespan, span); - if (removed.isEmpty()) { - doDeleteReferringValues(); - doDelete(); - return null; - } - if (removed.size() == 2) { - throw new IllegalArgumentException("Cannot create a gap in an object's lifespan"); - } - doSetLifespanAndEmit(removed.get(0)); - return this; - } - } - protected void emitEvents(TraceChangeRecord rec) { manager.trace.setChanged(rec); for (TraceObjectInterface iface : ifaces.values()) { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectAddressRangeValue.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectAddressRangeValue.java index ca32ebd6e2..0cc2825900 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectAddressRangeValue.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectAddressRangeValue.java @@ -15,12 +15,9 @@ */ package ghidra.trace.database.target; -import java.util.stream.Stream; - import com.google.common.collect.Range; import db.DBRecord; -import ghidra.dbg.util.PathPredicates; import ghidra.trace.database.DBTraceUtils; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData; @@ -124,6 +121,11 @@ public class DBTraceObjectAddressRangeValue throw new ClassCastException(); } + @Override + public DBTraceObject getChildOrNull() { + return null; + } + @Override public boolean isCanonical() { return false; @@ -162,22 +164,6 @@ public class DBTraceObjectAddressRangeValue } } - @Override - public Stream doGetSuccessors(Range 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 doGetOrderedSuccessors(Range span, - DBTraceObjectValPath pre, PathPredicates predicates, boolean forward) { - return doGetSuccessors(span, pre, predicates); - } - @Override public void doDelete() { manager.rangeValueMap.deleteData(this); @@ -186,19 +172,14 @@ public class DBTraceObjectAddressRangeValue @Override public void delete() { try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { - doDelete(); + doDeleteAndEmit(); } } - @Override - public void deleteTree() { - delete(); - } - @Override public TraceObjectValue truncateOrDelete(Range span) { try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { - return doTruncateOrDelete(span); + return doTruncateOrDeleteAndEmitLifeChange(span); } } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectInterface.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectInterface.java index 6696d8770c..d711f2f058 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectInterface.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectInterface.java @@ -15,8 +15,9 @@ */ 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.TraceUniqueObject; import ghidra.trace.model.target.*; @@ -29,6 +30,9 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu private final String spaceValueKey; private final DBTraceObject object; private final T iface; + // TODO: Memorizing life is not optimal. + // GP-1887 means to expose multiple lifespans in, e.g., TraceThread + private RangeSet life = TreeRangeSet.create(); public Translator(String spaceValueKey, DBTraceObject object, T iface) { this.spaceValueKey = spaceValueKey; @@ -63,35 +67,67 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu // Extension point } - public TraceChangeRecord translate(TraceChangeRecord rec) { - TraceAddressSpace space = spaceValueKey == null ? null - : spaceForValue(object, object.getMinSnap(), spaceValueKey); - if (rec.getEventType() == TraceObjectChangeType.INSERTED.getType()) { - TraceChangeType type = getAddedType(); - if (type == null) { - return null; - } - assert rec.getAffectedObject() == object; - emitExtraAdded(); - return new TraceChangeRecord<>(type, space, iface, null, - null); + protected TraceAddressSpace getSpace(RangeSet life) { + if (life.isEmpty()) { + return null; } - if (rec.getEventType() == TraceObjectChangeType.LIFESPAN_CHANGED.getType()) { + return spaceValueKey == null ? null + : spaceForValue(object, DBTraceUtils.lowerEndpoint(life.span()), spaceValueKey); + } + + protected TraceChangeRecord translateAdded() { + TraceChangeType type = getAddedType(); + if (type == null) { + return null; + } + emitExtraAdded(); + return new TraceChangeRecord<>(type, getSpace(life), iface, null, null); + } + + protected TraceChangeRecord translateLifespanChanged(RangeSet oldLife) { + TraceChangeType> type = getLifespanChangedType(); + if (type == null) { + return null; + } + Range oldLifespan = oldLife.span(); + Range newLifespan = life.span(); + emitExtraLifespanChanged(oldLifespan, newLifespan); + return new TraceChangeRecord<>(type, getSpace(life), iface, oldLifespan, newLifespan); + } + + protected TraceChangeRecord translateDeleted(RangeSet life) { + TraceChangeType 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()) { return null; } - TraceChangeType> type = getLifespanChangedType(); - if (type == null) { - return null; - } assert rec.getAffectedObject() == object; - TraceChangeRecord> cast = - TraceObjectChangeType.LIFESPAN_CHANGED.cast(rec); - emitExtraLifespanChanged(cast.getOldValue(), cast.getNewValue()); - return new TraceChangeRecord<>(type, space, iface, - cast.getOldValue(), cast.getNewValue()); + RangeSet oldLife = life; + life = object.getLife(); + boolean oldHasLife = !oldLife.isEmpty(); + boolean newHasLife = !life.isEmpty(); + 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()) { return null; } @@ -99,8 +135,8 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu if (type == null) { return null; } - TraceChangeRecord cast = - TraceObjectChangeType.VALUE_CHANGED.cast(rec); + TraceChangeRecord cast = + TraceObjectChangeType.VALUE_CREATED.cast(rec); TraceObjectValue affected = cast.getAffectedObject(); String key = affected.getEntryKey(); if (!appliesToKey(key)) { @@ -112,16 +148,10 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu } emitExtraValueChanged(affected.getLifespan(), key, cast.getOldValue(), 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()) { - TraceChangeType type = getDeletedType(); - if (type == null) { - return null; - } - assert rec.getAffectedObject() == object; - emitExtraDeleted(); - return new TraceChangeRecord<>(type, space, iface, null, null); + return translateDeleted(life); } return null; } @@ -162,6 +192,6 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu @Override default boolean isDeleted() { - return getObject().isDeleted(); + return getObject().getLife().isEmpty(); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java index a7b22d8a4d..75eaa04d19 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java @@ -163,6 +163,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager protected final DBCachedObjectIndex valuesByChild; protected final Collection objectsView; + protected final Collection valuesView; protected TargetObjectSchema rootSchema; @@ -193,6 +194,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager valueStore.getIndex(DBTraceObject.class, DBTraceObjectValue.CHILD_COLUMN); objectsView = Collections.unmodifiableCollection(objectStore.asMap().values()); + valuesView = Collections.unmodifiableCollection(valueStore.asMap().values()); } protected void loadRootSchema() { @@ -276,18 +278,31 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager } DBTraceObjectValue entry = valueStore.create(); 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; } - protected DBTraceObject doCreateObject(TraceObjectKeyPath path, Range lifespan) { - DBTraceObject obj = objectStore.create(); - obj.set(path, lifespan); + protected DBTraceObject doCreateObject(TraceObjectKeyPath path) { + DBTraceObject obj = objectsByPath.getOne(path); + if (obj != null) { + return obj; + } + obj = objectStore.create(); + obj.set(path); obj.emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.CREATED, null, obj)); return obj; } + protected DBTraceObject doGetObject(TraceObjectKeyPath path) { + return objectsByPath.getOne(path); + } + @Override - public DBTraceObject createObject(TraceObjectKeyPath path, Range lifespan) { + public DBTraceObject createObject(TraceObjectKeyPath path) { if (path.isRoot()) { throw new IllegalArgumentException("Cannot create non-root object with root path"); } @@ -295,7 +310,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager if (rootSchema == null) { 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) { try (LockHold hold = trace.lockWrite()) { setSchema(schema); - DBTraceObject root = doCreateObject(TraceObjectKeyPath.of(), Range.all()); + DBTraceObject root = doCreateObject(TraceObjectKeyPath.of()); assert root.getKey() == 0; InternalTraceObjectValue val = doCreateValue(Range.all(), null, "", root); assert val.getKey() == 0; @@ -331,29 +346,29 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager } @Override - public Collection getObjectsByCanonicalPath( - TraceObjectKeyPath path) { - return objectsByPath.get(path); + public DBTraceObject getObjectByCanonicalPath(TraceObjectKeyPath path) { + return objectsByPath.getOne(path); } @Override public Stream getObjectsByPath(Range span, TraceObjectKeyPath path) { + DBTraceObject root = getRootObject(); return getValuePaths(span, new PathPattern(path.getKeyList())) - .map(p -> p.getLastChild(getRootObject())) + .map(p -> p.getDestinationValue(root)) .filter(DBTraceObject.class::isInstance) .map(DBTraceObject.class::cast); } @Override - public Stream getValuePaths( - Range span, PathPredicates predicates) { + public Stream getValuePaths(Range span, + PathPredicates predicates) { try (LockHold hold = trace.lockRead()) { DBTraceObjectValue rootVal = valueStore.getObjectAt(0); if (rootVal == null) { 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; } + @Override + public Collection getAllValues() { + return valuesView; + } + @Override public Collection getValuesIntersecting(Range span, AddressRange range) { @@ -384,14 +404,25 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager PathMatcher matcher = rootSchema.searchFor(targetIf, true); return getValuePaths(span, matcher) .filter(p -> { - TraceObject object = p.getLastChild(getRootObject()); + TraceObject object = p.getDestination(getRootObject()); if (object == null) { Msg.error(this, "NULL VALUE! " + p.getLastEntry()); return false; } 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 @@ -419,7 +450,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager } protected I doAddWithInterface(List keyList, - Range lifespan, Class iface, ConflictResolution resolution) { + Class iface) { Class targetIf = TraceObjectInterfaceUtils.toTargetIf(iface); TargetObjectSchema schema = rootSchema.getSuccessorSchema(keyList); if (!schema.getInterfaces().contains(targetIf)) { @@ -427,14 +458,12 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager "Schema " + schema + " at " + PathUtils.toString(keyList) + " does not provide interface " + iface.getSimpleName()); } - DBTraceObject obj = createObject(TraceObjectKeyPath.of(keyList), lifespan); - obj.insert(resolution); + DBTraceObject obj = createObject(TraceObjectKeyPath.of(keyList)); return obj.queryInterface(iface); } - protected I doAddWithInterface(String path, - Range lifespan, Class iface, ConflictResolution resolution) { - return doAddWithInterface(PathUtils.parse(path), lifespan, iface, resolution); + protected I doAddWithInterface(String path, Class iface) { + return doAddWithInterface(PathUtils.parse(path), iface); } public Collection getAllObjects(Class iface) { @@ -523,7 +552,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager PathPredicates predicates, long snap, Class iface) { try (LockHold hold = trace.lockRead()) { 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) .findAny() .orElse(null); @@ -534,7 +563,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager TraceObjectKeyPath path, long snap, Class iface) { try (LockHold hold = trace.lockRead()) { 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) .findAny() .orElse(null); @@ -553,14 +582,15 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager "breakpoint specification on the given path."); } try (LockHold hold = trace.lockWrite()) { - TraceObjectBreakpointLocation loc = doAddWithInterface(path, lifespan, - TraceObjectBreakpointLocation.class, ConflictResolution.DENY); - loc.setName(path); - loc.setRange(range); + TraceObjectBreakpointLocation loc = + doAddWithInterface(path, TraceObjectBreakpointLocation.class); + loc.setName(lifespan, path); + loc.setRange(lifespan, range); // NB. Ignore threads. I'd like to deprecate that field, anyway. - loc.setKinds(kinds); - loc.setEnabled(enabled); - loc.setComment(comment); + loc.setKinds(lifespan, kinds); + loc.setEnabled(lifespan, enabled); + loc.setComment(lifespan, comment); + loc.getObject().insert(lifespan, ConflictResolution.DENY); return loc; } catch (DuplicateKeyException e) { @@ -572,11 +602,12 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager AddressRange range, Collection flags) throws TraceOverlappedRegionException { try (LockHold hold = trace.lockWrite()) { - TraceObjectMemoryRegion region = doAddWithInterface(path, lifespan, - TraceObjectMemoryRegion.class, ConflictResolution.TRUNCATE); - region.setName(path); - region.setRange(range); - region.setFlags(flags); + TraceObjectMemoryRegion region = + doAddWithInterface(path, TraceObjectMemoryRegion.class); + region.setName(lifespan, path); + region.setRange(lifespan, range); + region.setFlags(lifespan, flags); + region.getObject().insert(lifespan, ConflictResolution.TRUNCATE); return region; } } @@ -584,10 +615,10 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager public TraceObjectModule addModule(String path, String name, Range lifespan, AddressRange range) throws DuplicateNameException { try (LockHold hold = trace.lockWrite()) { - TraceObjectModule module = doAddWithInterface(path, lifespan, TraceObjectModule.class, - ConflictResolution.DENY); - module.setName(name); - module.setRange(range); + TraceObjectModule module = doAddWithInterface(path, TraceObjectModule.class); + module.setName(lifespan, name); + module.setRange(lifespan, range); + module.getObject().insert(lifespan, ConflictResolution.DENY); return module; } catch (DuplicateKeyException e) { @@ -598,10 +629,10 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager public TraceObjectSection addSection(String path, String name, Range lifespan, AddressRange range) throws DuplicateNameException { try (LockHold hold = trace.lockWrite()) { - TraceObjectSection section = doAddWithInterface(path, lifespan, - TraceObjectSection.class, ConflictResolution.DENY); - section.setName(name); - section.setRange(range); + TraceObjectSection section = doAddWithInterface(path, TraceObjectSection.class); + section.setName(lifespan, name); + section.setRange(lifespan, range); + section.getObject().insert(lifespan, ConflictResolution.DENY); return section; } catch (DuplicateKeyException e) { @@ -611,24 +642,41 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager public TraceObjectStack addStack(List keyList, long snap) { try (LockHold hold = trace.lockWrite()) { - return doAddWithInterface(keyList, Range.singleton(snap), TraceObjectStack.class, - ConflictResolution.DENY); + TraceObjectStack stack = doAddWithInterface(keyList, TraceObjectStack.class); + stack.getObject().insert(Range.singleton(snap), ConflictResolution.DENY); + return stack; } } public TraceObjectStackFrame addStackFrame(List keyList, long snap) { try (LockHold hold = trace.lockWrite()) { - return doAddWithInterface(keyList, Range.singleton(snap), TraceObjectStackFrame.class, - ConflictResolution.DENY); + TraceObjectStackFrame frame = doAddWithInterface(keyList, TraceObjectStackFrame.class); + frame.getObject().insert(Range.singleton(snap), ConflictResolution.DENY); + return frame; } } + protected void checkDuplicateThread(String path, Range 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 lifespan) throws DuplicateNameException { try (LockHold hold = trace.lockWrite()) { - TraceObjectThread thread = doAddWithInterface(path, lifespan, TraceObjectThread.class, - ConflictResolution.DENY); - thread.setName(display); + checkDuplicateThread(path, lifespan); + TraceObjectThread thread = doAddWithInterface(path, TraceObjectThread.class); + thread.setName(lifespan, display); + thread.getObject().insert(lifespan, ConflictResolution.DENY); return thread; } catch (DuplicateKeyException e) { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValPath.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValPath.java index e898962a70..73fe2ff742 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValPath.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValPath.java @@ -93,7 +93,7 @@ public class DBTraceObjectValPath implements TraceObjectValPath { } @Override - public TraceObject getFirstParent(TraceObject ifEmpty) { + public TraceObject getSource(TraceObject ifEmpty) { InternalTraceObjectValue first = getFirstEntry(); return first == null ? ifEmpty : first.getParent(); } @@ -107,13 +107,13 @@ public class DBTraceObjectValPath implements TraceObjectValPath { } @Override - public Object getLastValue(Object ifEmpty) { + public Object getDestinationValue(Object ifEmpty) { InternalTraceObjectValue last = getLastEntry(); return last == null ? ifEmpty : last.getValue(); } @Override - public TraceObject getLastChild(TraceObject ifEmpty) { + public TraceObject getDestination(TraceObject ifEmpty) { InternalTraceObjectValue last = getLastEntry(); return last == null ? ifEmpty : last.getChild(); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValue.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValue.java index d7017eb4ab..b18677e30d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValue.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValue.java @@ -27,11 +27,10 @@ import org.apache.commons.lang3.ArrayUtils; import com.google.common.collect.Range; import db.*; -import ghidra.dbg.util.PathPredicates; import ghidra.trace.database.DBTraceUtils; +import ghidra.trace.database.target.InternalTreeTraversal.Visitor; import ghidra.trace.model.Trace; import ghidra.trace.model.target.TraceObject; -import ghidra.trace.model.target.TraceObjectValPath; import ghidra.util.LockHold; import ghidra.util.database.*; import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec; @@ -276,6 +275,11 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra return (DBTraceObject) getValue(); } + @Override + public DBTraceObject getChildOrNull() { + return child; + } + @Override public Range getLifespan() { try (LockHold hold = manager.trace.lockRead()) { @@ -311,46 +315,9 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra } } - protected Stream doGetAllPaths(Range span, - DBTraceObjectValPath post) { - return triple.parent.doGetAllPaths(span, post.prepend(this)); - } - - protected Stream doGetAncestors(Range span, - DBTraceObjectValPath post, PathPredicates predicates) { - return triple.parent.doGetAncestors(span, post.prepend(this), predicates); - } - - @Override - public Stream doGetSuccessors( - Range 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 doGetOrderedSuccessors(Range 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 Stream doStreamVisitor(Range span, + Visitor visitor) { + return InternalTreeTraversal.INSTANCE.walkValue(visitor, this, span, null); } protected boolean doIsCanonical() { @@ -370,13 +337,6 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra } } - protected void doDeleteSuccessors() { - if (!doIsCanonical()) { - return; - } - child.doDeleteTree(); - } - @Override public void doDelete() { manager.doDeleteEdge(this); @@ -388,19 +348,7 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra if (triple.parent == null) { throw new IllegalArgumentException("Cannot delete root value"); } - doDelete(); - } - } - - protected void doDeleteTree() { - doDeleteSuccessors(); - doDelete(); - } - - @Override - public void deleteTree() { - try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { - doDeleteTree(); + doDeleteAndEmit(); } } @@ -410,7 +358,7 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra if (triple.parent == null) { throw new IllegalArgumentException("Cannot truncate or delete root value"); } - return doTruncateOrDelete(span); + return doTruncateOrDeleteAndEmitLifeChange(span); } } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalAllPathsVisitor.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalAllPathsVisitor.java new file mode 100644 index 0000000000..cdb13f3251 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalAllPathsVisitor.java @@ -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 continueValues(DBTraceObject object, + Range span, DBTraceObjectValPath path) { + return object.getParents().stream().filter(v -> !path.contains(v)); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalAncestorsVisitor.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalAncestorsVisitor.java new file mode 100644 index 0000000000..8a0f087175 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalAncestorsVisitor.java @@ -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 continueValues(DBTraceObject object, + Range 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)); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalOrderedSuccessorsVisitor.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalOrderedSuccessorsVisitor.java new file mode 100644 index 0000000000..6012fedf2d --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalOrderedSuccessorsVisitor.java @@ -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 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 continueValues(DBTraceObject object, + Range span, DBTraceObjectValPath path) { + Set 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); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalSuccessorsVisitor.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalSuccessorsVisitor.java new file mode 100644 index 0000000000..e8ab73d9c0 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalSuccessorsVisitor.java @@ -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 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 continueValues(DBTraceObject object, + Range span, DBTraceObjectValPath pre) { + Set nextKeys = predicates.getNextKeys(pre.getKeyList()); + if (nextKeys.isEmpty()) { + return Stream.empty(); + } + + Stream attrStream; + if (nextKeys.contains("")) { + attrStream = object.doGetAttributes() + .stream() + .filter(v -> DBTraceUtils.intersect(span, v.getLifespan())); + } + else { + attrStream = Stream.empty(); + } + + Stream elemStream; + if (nextKeys.contains("[]")) { + elemStream = object.doGetElements() + .stream() + .filter(v -> DBTraceUtils.intersect(span, v.getLifespan())); + } + else { + elemStream = Stream.empty(); + } + + Stream restStream = nextKeys.stream() + .filter(k -> !"".equals(k) && !"[]".equals(k)) + .flatMap(k -> object.doGetValues(span, k).stream()); + + return Stream.concat(Stream.concat(attrStream, elemStream), restStream); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalTraceObjectValue.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalTraceObjectValue.java index bf7c7543e1..1958e7b9fc 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalTraceObjectValue.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalTraceObjectValue.java @@ -16,13 +16,11 @@ package ghidra.trace.database.target; import java.util.*; -import java.util.stream.Stream; import org.apache.commons.collections4.IterableUtils; import com.google.common.collect.Range; -import ghidra.dbg.util.PathPredicates; import ghidra.trace.database.DBTraceUtils; import ghidra.trace.database.DBTraceUtils.LifespanMapSetter; import ghidra.trace.model.Trace.TraceObjectChangeType; @@ -67,7 +65,7 @@ interface InternalTraceObjectValue extends TraceObjectValue { keep = entry; } else { - entry.doDelete(); + entry.doDeleteAndEmit(); } } else { @@ -87,7 +85,7 @@ interface InternalTraceObjectValue extends TraceObjectValue { return null; } if (keep != null && Objects.equals(this.value, value)) { - keep.doSetLifespan(range); + keep.doSetLifespanAndEmit(range); return keep; } for (InternalTraceObjectValue k : kept) { @@ -114,7 +112,16 @@ interface InternalTraceObjectValue extends TraceObjectValue { @Override DBTraceObject getChild(); - void doSetLifespan(Range range); + DBTraceObject getChildOrNull(); + + void doSetLifespan(Range lifespan); + + default void doSetLifespanAndEmit(Range lifespan) { + Range oldLifespan = getLifespan(); + doSetLifespan(lifespan); + getParent().emitEvents(new TraceChangeRecord<>( + TraceObjectChangeType.VALUE_LIFESPAN_CHANGED, null, this, oldLifespan, lifespan)); + } @Override default void setLifespan(Range lifespan) { @@ -124,7 +131,6 @@ interface InternalTraceObjectValue extends TraceObjectValue { @Override default void setLifespan(Range lifespan, ConflictResolution resolution) { try (LockHold hold = getTrace().lockWrite()) { - Range oldLifespan = getLifespan(); if (getParent() == null) { throw new IllegalArgumentException("Cannot set lifespan of root value"); } @@ -145,29 +151,37 @@ interface InternalTraceObjectValue extends TraceObjectValue { return getParent().doCreateValue(range, getEntryKey(), value); } }.set(lifespan, getValue()); - getParent().emitEvents(new TraceChangeRecord<>( - TraceObjectChangeType.VALUE_LIFESPAN_CHANGED, null, this, oldLifespan, lifespan)); } } - Stream doGetSuccessors(Range span, - DBTraceObjectValPath pre, PathPredicates predicates); - - Stream doGetOrderedSuccessors(Range span, - DBTraceObjectValPath pre, PathPredicates predicates, boolean forward); - void doDelete(); + default void doDeleteAndEmit() { + DBTraceObject parent = getParent(); + doDelete(); + parent.emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.VALUE_DELETED, null, this)); + } + @Override DBTraceObject getParent(); + default InternalTraceObjectValue doTruncateOrDeleteAndEmitLifeChange(Range 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 span) { List> removed = DBTraceUtils.subtract(getLifespan(), span); if (removed.isEmpty()) { - doDelete(); + doDeleteAndEmit(); return null; } - doSetLifespan(removed.get(0)); + doSetLifespanAndEmit(removed.get(0)); if (removed.size() == 2) { return getParent().doCreateValue(removed.get(1), getEntryKey(), getValue()); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalTreeTraversal.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalTreeTraversal.java new file mode 100644 index 0000000000..2e5b4eb8b0 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalTreeTraversal.java @@ -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 composeSpan(Range pre, InternalTraceObjectValue value); + + DBTraceObjectValPath composePath(DBTraceObjectValPath pre, InternalTraceObjectValue value); + + VisitResult visitValue(InternalTraceObjectValue value, DBTraceObjectValPath path); + + DBTraceObject continueObject(InternalTraceObjectValue value); + + Stream continueValues(DBTraceObject object, + Range span, DBTraceObjectValPath path); + } + + public interface SpanIntersectingVisitor extends Visitor { + @Override + default Range composeSpan(Range pre, InternalTraceObjectValue value) { + Range valSpan = value.getLifespan(); + if (!pre.isConnected(valSpan)) { + return null; + } + Range span = pre.intersection(valSpan); + if (span.isEmpty()) { + return null; + } + return span; + } + } + + public Stream walkValue(Visitor visitor, + InternalTraceObjectValue value, Range span, DBTraceObjectValPath path) { + Range 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 walkObject(Visitor visitor, DBTraceObject object, + Range span, DBTraceObjectValPath path) { + return visitor.continueValues(object, span, path) + .flatMap(v -> walkValue(visitor, v, span, path)); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/LifespanCorrector.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/LifespanCorrector.java deleted file mode 100644 index d39327871d..0000000000 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/LifespanCorrector.java +++ /dev/null @@ -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 - * - *

- * 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 span = object.getLifespan(); - for (TraceObjectValue value : object.getValues()) { - span = span.span(value.getLifespan()); - } - object.setLifespan(span); - } - - @Override - public void visitValueUpward(TraceObjectValue value) { - Range 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 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)); - } -} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/thread/DBTraceObjectThread.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/thread/DBTraceObjectThread.java index de7cca4ca2..20ba672c0d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/thread/DBTraceObjectThread.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/thread/DBTraceObjectThread.java @@ -100,10 +100,15 @@ public class DBTraceObjectThread implements TraceObjectThread, DBTraceObjectInte TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, ""); } + @Override + public void setName(Range lifespan, String name) { + object.setValue(lifespan, TargetObject.DISPLAY_ATTRIBUTE_NAME, name); + } + @Override public void setName(String name) { 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 public long getCreationSnap() { - return object.getMinSnap(); + return computeMinSnap(); } @Override @@ -128,7 +133,7 @@ public class DBTraceObjectThread implements TraceObjectThread, DBTraceObjectInte @Override public long getDestructionSnap() { - return object.getMaxSnap(); + return computeMaxSnap(); } @Override @@ -138,7 +143,7 @@ public class DBTraceObjectThread implements TraceObjectThread, DBTraceObjectInte @Override public Range getLifespan() { - return object.getLifespan(); + return computeSpan(); } @Override @@ -156,7 +161,9 @@ public class DBTraceObjectThread implements TraceObjectThread, DBTraceObjectInte @Override public void delete() { - object.deleteTree(); + try (LockHold hold = object.getTrace().lockWrite()) { + object.removeTree(computeSpan()); + } } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java index 5d7e2fa139..3a9d798ee4 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java @@ -60,24 +60,22 @@ public interface Trace extends DataTypeManagerDomainObject { * An object was created, but not yet inserted. * *

- * Between the {@link #CREATED} and {@link #INSERTED} events, an object is considered - * "incomplete," because it is likely missing its attributes. Thus, a trace client must take - * care to ensure all attributes, especially fixed attributes, are added to the object - * before it is inserted at its canonical path. Listeners may use + * Between the {@link #CREATED} event and the first {@link #LIFE_CHANGED} event, an object + * is considered "incomplete," because it is likely missing its attributes. Thus, a trace + * client must take care to ensure all attributes, especially fixed attributes, are added to + * 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 * given snapshot. */ public static final TraceObjectChangeType CREATED = new TraceObjectChangeType<>(); /** - * An object was inserted at its canonical path. + * An object's life changed. + * + *

+ * One of its canonical parents was created, deleted, or had its lifespan change. */ - public static final TraceObjectChangeType INSERTED = - new TraceObjectChangeType<>(); - /** - * An object's lifespan changed. - */ - public static final TraceObjectChangeType> LIFESPAN_CHANGED = + public static final TraceObjectChangeType LIFE_CHANGED = new TraceObjectChangeType<>(); /** * An object was deleted. @@ -85,27 +83,20 @@ public interface Trace extends DataTypeManagerDomainObject { public static final TraceObjectChangeType DELETED = new TraceObjectChangeType<>(); /** - * An object's value changed. - * - *

- * 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. + * A value entry was created. */ - public static final TraceObjectChangeType VALUE_CHANGED = + public static final TraceObjectChangeType VALUE_CREATED = new TraceObjectChangeType<>(); /** - * An object's value changed in lifespan. - * - *

- * 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. + * A value entry changed in lifespan. */ public static final TraceObjectChangeType> // VALUE_LIFESPAN_CHANGED = new TraceObjectChangeType<>(); + /** + * A value entry was deleted. + */ + public static final TraceObjectChangeType VALUE_DELETED = + new TraceObjectChangeType<>(); } public static final class TraceBookmarkChangeType extends DefaultTraceChangeType { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointLocation.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointLocation.java index 16227eb082..f16167fbd4 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointLocation.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointLocation.java @@ -15,6 +15,8 @@ */ package ghidra.trace.model.breakpoint; +import java.util.Collection; + import com.google.common.collect.Range; import ghidra.dbg.target.TargetBreakpointLocation; @@ -42,5 +44,13 @@ public interface TraceObjectBreakpointLocation extends TraceBreakpoint, TraceObj void setLifespan(Range lifespan) throws DuplicateNameException; - void setRange(AddressRange range); + void setRange(Range lifespan, AddressRange range); + + void setName(Range lifespan, String name); + + void setKinds(Range lifespan, Collection kinds); + + void setEnabled(Range lifespan, boolean enabled); + + void setComment(Range lifespan, String comment); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointSpec.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointSpec.java index 3b01ee821c..ef1e14271d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointSpec.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointSpec.java @@ -43,4 +43,6 @@ public interface TraceObjectBreakpointSpec extends TraceBreakpoint, TraceObjectI void setLifespan(Range lifespan) throws DuplicateNameException; Collection getLocations(); + + void setKinds(Range lifespan, Collection kinds); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceObjectMemoryRegion.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceObjectMemoryRegion.java index e60dec23a1..a650d47baa 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceObjectMemoryRegion.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceObjectMemoryRegion.java @@ -22,6 +22,7 @@ import com.google.common.collect.Range; import ghidra.dbg.target.TargetMemoryRegion; import ghidra.dbg.target.TargetObject; +import ghidra.program.model.address.AddressRange; import ghidra.trace.model.target.TraceObjectInterface; import ghidra.trace.model.target.annot.TraceObjectInfo; @@ -35,6 +36,10 @@ import ghidra.trace.model.target.annot.TraceObjectInfo; public interface TraceObjectMemoryRegion extends TraceMemoryRegion, TraceObjectInterface { String KEY_VOLATILE = "_volatile"; + void setName(Range lifespan, String name); + + void setRange(Range lifespan, AddressRange range); + void setFlags(Range lifespan, Collection flags); void addFlags(Range lifespan, Collection flags); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceObjectModule.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceObjectModule.java index 90ca1a04e5..4ee20b4b77 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceObjectModule.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceObjectModule.java @@ -17,8 +17,11 @@ package ghidra.trace.model.modules; import java.util.Collection; +import com.google.common.collect.Range; + import ghidra.dbg.target.TargetModule; import ghidra.dbg.target.TargetObject; +import ghidra.program.model.address.AddressRange; import ghidra.trace.database.module.TraceObjectSection; import ghidra.trace.model.target.TraceObjectInterface; import ghidra.trace.model.target.annot.TraceObjectInfo; @@ -31,6 +34,11 @@ import ghidra.trace.model.target.annot.TraceObjectInfo; TargetModule.RANGE_ATTRIBUTE_NAME }) public interface TraceObjectModule extends TraceModule, TraceObjectInterface { + + void setName(Range lifespan, String name); + + void setRange(Range lifespan, AddressRange range); + @Override Collection getSections(); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceStack.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceStack.java index a2b2c37bcb..4fcc7c9b5a 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceStack.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceStack.java @@ -17,7 +17,10 @@ package ghidra.trace.model.stack; import java.util.List; +import ghidra.lifecycle.Experimental; +import ghidra.lifecycle.Transitional; import ghidra.trace.model.TraceUniqueObject; +import ghidra.trace.model.memory.TraceMemoryManager; 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 * - * Note that pushing new frames onto a stack does not adjust the frame level of any associated - * annotations. Some utility to adjust those annotations. Cannot just rename tables, since those - * contain annotations for the given level, regardless of the particular stack. + *

+ * Note that pushing new frames onto a stack does not adjust the frame level of any + * frame-associated managers or spaces, e.g., that returned by + * {@link TraceMemoryManager#getMemoryRegisterSpace(TraceThread, int, boolean)}. + * + *

+ * If the experimental object mode is successful, this method should be deleted. * * @param depth the desired depth * @param atInner true if frames should be "pushed" @@ -78,12 +85,27 @@ public interface TraceStack extends TraceUniqueObject { /** * 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 */ - List getFrames(); + List getFrames(@Experimental long snap); /** * Delete this stack and its frames */ void delete(); + + /** + * Check if this stack'sframes are fixed for its lifetime + * + *

+ * 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(); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceStackFrame.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceStackFrame.java index 59cdaae658..a6ce209b93 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceStackFrame.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceStackFrame.java @@ -17,18 +17,69 @@ package ghidra.trace.model.stack; import com.google.common.collect.Range; +import ghidra.lifecycle.Experimental; import ghidra.program.model.address.Address; +/** + * A frame in a {@link TraceStack} + */ public interface TraceStackFrame { + /** + * Get the containing stack + * + * @return the stack + */ TraceStack getStack(); + /** + * Get the frame's position in the containing stack + * + *

+ * 0 represents the innermost frame or top of the stack. + * + * @return the frame's level + */ 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 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 span, Address pc); - String getComment(); + /** + * Get the user comment for the frame + * + *

+ * 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 + * + *

+ * 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); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/DuplicateKeyException.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/DuplicateKeyException.java index 0aad18719b..68d3296ec8 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/DuplicateKeyException.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/DuplicateKeyException.java @@ -15,7 +15,22 @@ */ 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 + * + *

+ * 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 { + /** + * Notify of a given conflicting key + * + * @param key the key in conflict + */ public DuplicateKeyException(String key) { super(key); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java index 64f404a2e4..2fa7f799b8 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java @@ -19,6 +19,7 @@ import java.util.Collection; import java.util.stream.Stream; import com.google.common.collect.Range; +import com.google.common.collect.RangeSet; import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.schema.TargetObjectSchema; @@ -59,33 +60,81 @@ public interface TraceObject extends TraceUniqueObject { TraceObjectKeyPath getCanonicalPath(); /** - * Inserts this object at its canonical path for its lifespan + * Get all ranges of this object's life * *

- * Any ancestor which does not exist is created with the same lifespan as this object. 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. + * Essentially, this is the union of the lifespans of all canonical parent values * + * @return the range set for snaps at which this object is considered "inserted." + */ + RangeSet getLife(); + + /** + * Inserts this object at its canonical path for the given lifespan + * + *

+ * 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. */ - void insert(ConflictResolution resolution); + void insert(Range lifespan, ConflictResolution resolution); + + /** + * Remove this object from its canonical path for the given lifespan + * + *

+ * 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 span); + + /** + * Remove this object and its successors from their canonical paths for the given span + * + *

+ * 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 span); /** * Get the parent value along this object's canonical path for a given snapshot * *

* 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 - * 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 * @return the canonical parent value, or null */ TraceObjectValue getCanonicalParent(long snap); + /** + * Get the parent values along this object's canonical path for a given lifespan + * + *

+ * 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 getCanonicalParents(Range lifespan); + /** * 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 * @return the paths */ - Stream getAllPaths(Range span); + Stream getAllPaths(Range span); /** * Specifies a strategy for resolving duplicate keys @@ -116,61 +165,12 @@ public interface TraceObject extends TraceUniqueObject { */ 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; } - /** - * Set the lifespan of this object - * - *

- * 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 lifespan); - - /** - * Get the lifespan of this object - * - * @return the lifespan - */ - Range 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 * @@ -314,8 +314,9 @@ public interface TraceObject extends TraceUniqueObject { * @param lifespan the lifespan of the value * @param key the key to set * @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 + * @throws DuplicateKeyException if there are denied duplicate keys */ TraceObjectValue setValue(Range lifespan, String key, Object value, ConflictResolution resolution); @@ -407,11 +408,10 @@ public interface TraceObject extends TraceUniqueObject { *

* 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 * @return the stream of objects */ - Stream queryCanonicalAncestorsTargetInterface(Range span, + Stream queryCanonicalAncestorsTargetInterface( Class targetIf); /** @@ -421,13 +421,18 @@ public interface TraceObject extends TraceUniqueObject { * The object may not yet be inserted at its canonical path * * @param the interface type - * @param span the span which the found objects must intersect * @param ifClass the interface class * @return the stream of interfaces */ - Stream queryCanonicalAncestorsInterface( - Range span, Class ifClass); + Stream queryCanonicalAncestorsInterface(Class 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 querySuccessorsTargetInterface(Range span, Class targetIf); @@ -446,22 +451,14 @@ public interface TraceObject extends TraceUniqueObject { * Delete this object along with parent and child value entries referring to it * *

- * Note, this does not delete the children or any successors. Use {@link #deleteTree()} to - * delete an entire subtree, regardless of lifespan. It is not recommended to invoke this on the - * root object, since it cannot be replaced without first clearing the manager. + * Warning: This will remove the object from the manager entirely, not just over + * a given span. In general, this is used for cleaning and maintenance. Consider + * {@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(); - /** - * Delete this object and its successors along with value entries referring to any - * - *

- * 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 * @@ -469,21 +466,4 @@ public interface TraceObject extends TraceUniqueObject { */ @Override boolean isDeleted(); - - /** - * Modify the lifespan or delete this object, such that it no longer intersects the given span. - * - *

- * 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 span); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectInterface.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectInterface.java index 8fb1ff6c2e..da6f656894 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectInterface.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectInterface.java @@ -15,6 +15,46 @@ */ 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 { + /** + * Get the object backing this implementation + * + * @return the object + */ TraceObject getObject(); + + /** + * Compute the lifespan of this object + * + * @return the span of all lifespans + */ + @Transitional + default Range computeSpan() { + RangeSet 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()); + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectKeyPath.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectKeyPath.java index 58f3d77061..3220afec6b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectKeyPath.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectKeyPath.java @@ -16,20 +16,48 @@ package ghidra.trace.model.target; import java.util.*; +import java.util.stream.Stream; +import ghidra.dbg.util.PathPredicates; import ghidra.dbg.util.PathUtils; import ghidra.dbg.util.PathUtils.PathComparator; +/** + * An immutable path of keys leading from one object object to another + * + *

+ * 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 { + /** + * 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 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) { 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) { return new TraceObjectKeyPath(PathUtils.parse(path)); } @@ -67,30 +95,82 @@ public final class TraceObjectKeyPath implements Comparable return this.keyList.equals(that.keyList); } + /** + * Get the (immutable) list of keys from source to destination + * + * @return the key list + */ public List getKeyList() { 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() { return keyList.isEmpty(); } + /** + * Create a new path by appending the given key + * + *

+ * 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) { return new TraceObjectKeyPath(PathUtils.extend(keyList, name)); } + /** + * Get the final key of this path + * + * @return the final key + */ public String key() { return PathUtils.getKey(keyList); } + /** + * Create a new path by appending the given element index + * + *

+ * 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) { return index(PathUtils.makeIndex(index)); } + /** + * Create a new path by appending the given element index + * + *

+ * 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) { 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() { return PathUtils.getIndex(keyList); } @@ -100,16 +180,54 @@ public final class TraceObjectKeyPath implements Comparable 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() { List pkl = PathUtils.parent(keyList); return pkl == null ? null : new TraceObjectKeyPath(pkl); } + /** + * Create a new path by appending the given list of keys + * + *

+ * 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 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) { return extend(Arrays.asList(subKeyList)); } + + /** + * Stream, starting with the longer paths, paths that match the given predicates + * + * @param matcher + * @return + */ + public Stream 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); + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectManager.java index 6edd65febe..1cf921f5f5 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectManager.java @@ -53,13 +53,12 @@ public interface TraceObjectManager { 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 lifespan the initial lifespan * @return the new object */ - TraceObject createObject(TraceObjectKeyPath path, Range lifespan); + TraceObject createObject(TraceObjectKeyPath path); /** * Get the schema of the root object @@ -93,7 +92,7 @@ public interface TraceObjectManager { * @param path the canonical path of the desired objects * @return the collection of objects */ - Collection getObjectsByCanonicalPath(TraceObjectKeyPath path); + TraceObject getObjectByCanonicalPath(TraceObjectKeyPath path); /** * Get objects in the database having the given path intersecting the given span @@ -129,6 +128,13 @@ public interface TraceObjectManager { */ Collection getAllObjects(); + /** + * Get all the values (edges) in the database + * + * @return the collect of all values + */ + Collection getAllValues(); + /** * Get all address-ranged values intersecting the given span and address range * @@ -150,6 +156,15 @@ public interface TraceObjectManager { Stream queryAllInterface(Range span, Class ifClass); + /** + * For maintenance, remove all disconnected objects + * + *

+ * 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 entire object model, including the schema * diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectValPath.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectValPath.java index cc9acaa65a..4bb64248c8 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectValPath.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectValPath.java @@ -17,20 +17,100 @@ package ghidra.trace.model.target; import java.util.List; +import ghidra.dbg.util.PathPredicates; + +/** + * A path of values leading from one object to another + * + *

+ * 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 { + /** + * Get the values in the path, ordered from source to destination + * + * @return the list of value entries + */ List getEntryList(); + /** + * Get the keys in the path, ordered from source to destination + * + *

+ * The returned list is suited for testing with {@link PathPredicates} or other + * path-manipulation methods. + * + * @return the list of keys + */ List 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); + /** + * 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(); - TraceObject getFirstParent(TraceObject ifEmpty); + /** + * Get the source object + * + *

+ * 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(); - Object getLastValue(Object ifEmpty); + /** + * Get the destination value + * + *

+ * 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 + * + *

+ * 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); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectValue.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectValue.java index 0a01634496..c6f8b460f7 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectValue.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectValue.java @@ -91,6 +91,7 @@ public interface TraceObjectValue { * * @param lifespan the new lifespan * @param resolution specifies how to resolve duplicate keys with intersecting lifespans + * @throws DuplicateKeyException if there are denied duplicate keys */ void setLifespan(Range span, ConflictResolution resolution); @@ -133,17 +134,9 @@ public interface TraceObjectValue { /** * Delete this entry - * - *

- * If this entry is part of the child object's canonical path, then the child is also deleted. */ void delete(); - /** - * Delete this entry and, if it is canonical, its successors - */ - void deleteTree(); - /** * Check if this value entry has been deleted * diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/annot/TraceObjectInterfaceUtils.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/annot/TraceObjectInterfaceUtils.java index 7de8cbd8b7..f86cc389f5 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/annot/TraceObjectInterfaceUtils.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/annot/TraceObjectInterfaceUtils.java @@ -21,7 +21,7 @@ import java.util.List; import com.google.common.collect.Range; 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.TraceObject.ConflictResolution; import ghidra.util.LockHold; @@ -66,8 +66,8 @@ public enum TraceObjectInterfaceUtils { throw new DuplicateNameException( "Duplicate " + getShortName(traceIf) + ": " + e.getMessage()); } - object.setLifespan(lifespan); - long lower = object.getMinSnap(); + object.insert(lifespan, ConflictResolution.TRUNCATE); + long lower = DBTraceUtils.lowerEndpoint(lifespan); for (String key : getFixedKeys(traceIf)) { TraceObjectValue val = object.getValue(lower, key); if (val != null) { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/thread/TraceObjectThread.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/thread/TraceObjectThread.java index 4b52e3a393..856173bdf2 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/thread/TraceObjectThread.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/thread/TraceObjectThread.java @@ -15,6 +15,8 @@ */ package ghidra.trace.model.thread; +import com.google.common.collect.Range; + import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetThread; import ghidra.trace.model.target.TraceObjectInterface; @@ -29,4 +31,6 @@ import ghidra.trace.model.target.annot.TraceObjectInfo; }) public interface TraceObjectThread extends TraceThread, TraceObjectInterface { String KEY_COMMENT = "_comment"; + + void setName(Range lifespan, String name); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceAddressSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceAddressSpace.java index 523db0df7d..e35e0eef48 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceAddressSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceAddressSpace.java @@ -22,10 +22,10 @@ import ghidra.trace.model.thread.TraceThread; * Identify the "full" address space in a trace. * *

- * Whenever the address space is {@code register}, then the thread and frame level become - * necessarily to uniquely identify it. This will be deprecated when either, 1) unique register - * overlay spaces are created for each thread/frame, or 2) register values are fully transitioned to - * object model storage. + * Whenever the address space is {@code register}, then the thread and frame level become necessary + * to uniquely identify it. This will be deprecated when either, 1) unique register overlay spaces + * are created for each thread/frame, or 2) register values are fully transitioned to object model + * storage. */ public interface TraceAddressSpace { AddressSpace getAddressSpace(); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManagerTest.java index 4de23d7418..875b470107 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManagerTest.java @@ -17,6 +17,7 @@ package ghidra.trace.database.breakpoint; import static org.junit.Assert.*; +import java.util.List; import java.util.Set; import org.junit.*; @@ -91,7 +92,8 @@ public class DBTraceBreakpointManagerTest extends AbstractGhidraHeadlessIntegrat @Test public void testGetAllBreakpoints() throws Exception { addBreakpoints(); - assertEquals(Set.of(breakMain, breakVarA, breakVarB), + // breakVarA == breakVarB in object mode + assertEquals(Set.copyOf(List.of(breakMain, breakVarA, breakVarB)), Set.copyOf(breakpointManager.getAllBreakpoints())); } @@ -100,7 +102,7 @@ public class DBTraceBreakpointManagerTest extends AbstractGhidraHeadlessIntegrat addBreakpoints(); assertEquals(Set.of(breakMain), Set.copyOf(breakpointManager.getBreakpointsByPath("Breakpoints[0]"))); - assertEquals(Set.of(breakVarA, breakVarB), + assertEquals(Set.copyOf(List.of(breakVarA, breakVarB)), // Same breakpoint in object mode Set.copyOf(breakpointManager.getBreakpointsByPath("Breakpoints[1]"))); } @@ -259,14 +261,11 @@ public class DBTraceBreakpointManagerTest extends AbstractGhidraHeadlessIntegrat @Test public void testDelete() throws Exception { addBreakpoints(); - assertEquals(Set.of(breakMain), - Set.copyOf(breakpointManager.getBreakpointsByPath("Breakpoints[0]"))); + assertEquals(breakMain, breakpointManager.getPlacedBreakpointByPath(0, "Breakpoints[0]")); try (UndoableTransaction tid = b.startTransaction()) { breakMain.delete(); - assertEquals(Set.of(), - Set.copyOf(breakpointManager.getBreakpointsByPath("Breakpoints[0]"))); + assertNull(breakpointManager.getPlacedBreakpointByPath(0, "Breakpoints[0]")); } - assertEquals(Set.of(), - Set.copyOf(breakpointManager.getBreakpointsByPath("Breakpoints[0]"))); + assertNull(breakpointManager.getPlacedBreakpointByPath(0, "Breakpoints[0]")); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/module/DBTraceModuleManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/module/DBTraceModuleManagerTest.java index e18e5ea752..a3f7aa5e6b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/module/DBTraceModuleManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/module/DBTraceModuleManagerTest.java @@ -108,8 +108,8 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT b.range(0x7f400000, 0x7f60002f), Range.closed(11L, 20L)); } assertEquals(Set.of(mod1), new HashSet<>(moduleManager.getModulesByPath("Modules[first]"))); - assertEquals(Set.of(mod2, mod3), - new HashSet<>(moduleManager.getModulesByPath("Modules[second]"))); + assertEquals(Set.copyOf(List.of(mod2, mod3)), // Same in object mode + Set.copyOf(moduleManager.getModulesByPath("Modules[second]"))); } @Test diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/stack/DBTraceStackManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/stack/DBTraceStackManagerTest.java index 888b6cdf33..4c836e0d4e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/stack/DBTraceStackManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/stack/DBTraceStackManagerTest.java @@ -68,7 +68,7 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe stack.setDepth(5, true); } int expectedLevel = 0; - for (TraceStackFrame frame : stack.getFrames()) { + for (TraceStackFrame frame : stack.getFrames(0)) { assertEquals(expectedLevel++, frame.getLevel()); } assertEquals(5, expectedLevel); @@ -78,7 +78,7 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe } expectedLevel = 0; - for (TraceStackFrame frame : stack.getFrames()) { + for (TraceStackFrame frame : stack.getFrames(0)) { assertEquals(expectedLevel++, frame.getLevel()); } assertEquals(3, expectedLevel); @@ -88,7 +88,7 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe } expectedLevel = 0; - for (TraceStackFrame frame : stack.getFrames()) { + for (TraceStackFrame frame : stack.getFrames(0)) { assertEquals(expectedLevel++, frame.getLevel()); } assertEquals(1, expectedLevel); @@ -139,16 +139,21 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe TraceStack stack1 = stackManager.getStack(thread, 0, true); stack1.setDepth(2, true); - (frame1a = stack1.getFrame(0, false)).setProgramCounter(Range.all(), b.addr(0x0040100)); - (frame1b = stack1.getFrame(1, false)).setProgramCounter(Range.all(), b.addr(0x0040300)); + frame1a = stack1.getFrame(0, false); + frame1b = stack1.getFrame(1, false); + frame1a.setProgramCounter(Range.atLeast(0L), b.addr(0x0040100)); + frame1b.setProgramCounter(Range.atLeast(0L), b.addr(0x0040300)); TraceStack stack2 = stackManager.getStack(thread, 1, true); stack2.setDepth(2, true); - (frame2a = stack2.getFrame(0, false)).setProgramCounter(Range.all(), b.addr(0x0040200)); - (frame2b = stack2.getFrame(1, false)).setProgramCounter(Range.all(), b.addr(0x0040400)); + frame2a = stack2.getFrame(0, false); + frame2b = stack2.getFrame(1, false); + frame2a.setProgramCounter(Range.atLeast(1L), b.addr(0x0040200)); + frame2b.setProgramCounter(Range.atLeast(1L), b.addr(0x0040400)); } - assertEquals(Set.of(frame1a, frame2a, frame1b, frame2b), toSet(stackManager + // stack1 == stack2, and corresponding frames, in object mode + assertEquals(Set.copyOf(List.of(frame1a, frame2a, frame1b, frame2b)), toSet(stackManager .getFramesIn(b.set(b.drng(0x0040000, 0x0050000))))); assertEquals(Set.of(frame1a, frame1b), toSet(stackManager @@ -202,7 +207,7 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe stack.setDepth(2, true); } - List frames = stack.getFrames(); + List frames = stack.getFrames(0); assertEquals(2, frames.size()); assertEquals(stack.getFrame(0, false), frames.get(0)); assertEquals(stack.getFrame(1, false), frames.get(1)); @@ -220,7 +225,7 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe assertFalse(stack.isDeleted()); assertEquals(stack, stackManager.getStack(thread, 0, false)); - assertEquals(2, stack.getFrames().size()); + assertEquals(2, stack.getFrames(0).size()); try (UndoableTransaction tid = b.startTransaction()) { stack.delete(); @@ -293,10 +298,10 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe // NB. Object-mode sets comment at pc in listing, not on frame itself frame.setProgramCounter(Range.all(), b.addr(0x00400123)); - assertNull(frame.getComment()); - frame.setComment("Hello, World!"); + assertNull(frame.getComment(0)); + frame.setComment(0, "Hello, World!"); } - assertEquals("Hello, World!", frame.getComment()); + assertEquals("Hello, World!", frame.getComment(0)); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java index 68c30cac59..12e11aed86 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java @@ -83,19 +83,19 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT try (UndoableTransaction tid = b.startTransaction()) { root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); TraceObjectKeyPath pathTargets = TraceObjectKeyPath.of("Targets"); - targetContainer = manager.createObject(pathTargets, Range.atLeast(0L)); + targetContainer = manager.createObject(pathTargets); root.setAttribute(Range.atLeast(0L), "Targets", targetContainer); dumpStore(manager.valueStore); for (int i = 0; i < targetCount; i++) { - TraceObject target = - manager.createObject(pathTargets.index(i), Range.atLeast((long) i)); - target.setAttribute(target.getLifespan(), "self", target); + Range lifespan = Range.atLeast((long) i); + TraceObject target = manager.createObject(pathTargets.index(i)); + target.setAttribute(Range.all(), "self", target); dumpStore(manager.valueStore); - targetContainer.setElement(target.getLifespan(), i, target); + targetContainer.setElement(lifespan, i, target); dumpStore(manager.valueStore); targets.add(target); - root.setAttribute(target.getLifespan(), "curTarget", target); + root.setAttribute(lifespan, "curTarget", target); dumpStore(manager.valueStore); } @@ -109,25 +109,37 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT assertEquals(b.trace, manager.getTrace()); } + /** + * Fails because you cannot use the object manager until the schema is specified and the root + * object is created. + */ @Test(expected = IllegalStateException.class) - public void testCreateObjectWithRootErr() { + public void testCreateObjectWithoutRootErr() { try (UndoableTransaction tid = b.startTransaction()) { - manager.createObject(TraceObjectKeyPath.of("Test"), Range.all()); + manager.createObject(TraceObjectKeyPath.of("Test")); } } + /** + * Fails because you cannot create the root object using createObject. Instead, you must use + * createRootObject, specifying the schema. + */ @Test(expected = IllegalArgumentException.class) - public void testCreateObjectAsRootErr() { + public void testCreateObjectAsRootErrNoSchema() { try (UndoableTransaction tid = b.startTransaction()) { - manager.createObject(TraceObjectKeyPath.of(), Range.all()); + manager.createObject(TraceObjectKeyPath.of()); } } + /** + * Fails because you cannot create a second root object, nor can you create any root object with + * createObject. + */ @Test(expected = IllegalArgumentException.class) public void testCreateObjectAsRootErrRootExists() { try (UndoableTransaction tid = b.startTransaction()) { manager.createRootObject(ctx.getSchema(new SchemaName("Session"))); - manager.createObject(TraceObjectKeyPath.of(), Range.all()); + manager.createObject(TraceObjectKeyPath.of()); } } @@ -138,6 +150,9 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT } } + /** + * Fails because you cannot create a second root object. + */ @Test(expected = IllegalStateException.class) public void testCreate2ndRootErr() { try (UndoableTransaction tid = b.startTransaction()) { @@ -163,24 +178,22 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT TraceObject obj; try (UndoableTransaction tid = b.startTransaction()) { manager.createRootObject(ctx.getSchema(new SchemaName("Session"))); - obj = manager.createObject(TraceObjectKeyPath.of("Targets"), Range.all()); + obj = manager.createObject(TraceObjectKeyPath.of("Targets")); } assertEquals(TraceObjectKeyPath.of("Targets"), obj.getCanonicalPath()); - assertEquals(Range.all(), obj.getLifespan()); } @Test public void testGetObjectsByCanonicalPath() { try (UndoableTransaction tid = b.startTransaction()) { root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); - targetContainer = manager.createObject(TraceObjectKeyPath.of("Targets"), Range.all()); + targetContainer = manager.createObject(TraceObjectKeyPath.of("Targets")); } - assertTrue(manager.getObjectsByCanonicalPath(TraceObjectKeyPath.of("Nothing")).isEmpty()); - assertEquals(root, - Unique.assertOne(manager.getObjectsByCanonicalPath(TraceObjectKeyPath.of()))); + assertNull(manager.getObjectByCanonicalPath(TraceObjectKeyPath.of("Nothing"))); + assertEquals(root, manager.getObjectByCanonicalPath(TraceObjectKeyPath.of())); assertEquals(targetContainer, - Unique.assertOne(manager.getObjectsByCanonicalPath(TraceObjectKeyPath.of("Targets")))); + manager.getObjectByCanonicalPath(TraceObjectKeyPath.of("Targets"))); } @Test @@ -223,8 +236,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT public void testGetRangeValues() { try (UndoableTransaction tid = b.startTransaction()) { root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); - targetContainer = - manager.createObject(TraceObjectKeyPath.parse("Targets"), Range.all()); + targetContainer = manager.createObject(TraceObjectKeyPath.parse("Targets")); root.setAttribute(Range.all(), "Targets", targetContainer); TraceObjectValue rangeVal = @@ -266,9 +278,8 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT populateModel(3); TraceObject thread; try (UndoableTransaction tid = b.startTransaction()) { - thread = manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]"), - Range.atLeast(0L)); - thread.insert(ConflictResolution.DENY); + thread = manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]")); + thread.insert(Range.atLeast(0L), ConflictResolution.DENY); } assertEquals(Set.of(), @@ -332,7 +343,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT public void testIsRoot() { try (UndoableTransaction tid = b.startTransaction()) { root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); - targetContainer = manager.createObject(TraceObjectKeyPath.of("Targets"), Range.all()); + targetContainer = manager.createObject(TraceObjectKeyPath.of("Targets")); } assertTrue(root.isRoot()); @@ -354,59 +365,25 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT TraceObjectValPath path; path = paths.get(0); - assertEquals(object, path.getLastChild(root)); + assertEquals(object, path.getDestination(root)); assertEquals(PathUtils.parse("Targets[0]"), path.getKeyList()); path = paths.get(1); - assertEquals(object, path.getLastChild(root)); + assertEquals(object, path.getDestination(root)); assertEquals(PathUtils.parse("Targets[0].self"), path.getKeyList()); path = paths.get(2); - assertEquals(object, path.getLastChild(root)); + assertEquals(object, path.getDestination(root)); assertEquals(List.of("curTarget"), path.getKeyList()); path = paths.get(3); - assertEquals(object, path.getLastChild(root)); + assertEquals(object, path.getDestination(root)); assertEquals(PathUtils.parse("curTarget.self"), path.getKeyList()); paths = root.getAllPaths(Range.all()).collect(Collectors.toList()); assertEquals(1, paths.size()); path = paths.get(0); - assertEquals(root, path.getLastChild(root)); - } - - @Test - public void testObjectSetLifespan() { - try (UndoableTransaction tid = b.startTransaction()) { - root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); - assertEquals(Range.all(), root.getLifespan()); - - root.setLifespan(Range.singleton(0L)); - assertEquals(Range.singleton(0L), root.getLifespan()); - - try { - root.setMinSnap(10); - fail(); - } - catch (IllegalArgumentException e) { - // pass - } - try { - root.setMaxSnap(-10); - fail(); - } - catch (IllegalArgumentException e) { - // pass - } - - root.setMinSnap(-10); - assertEquals(Range.closed(-10L, 0L), root.getLifespan()); - assertEquals(-10, root.getMinSnap()); - - root.setMaxSnap(10); - assertEquals(Range.closed(-10L, 10L), root.getLifespan()); - assertEquals(10, root.getMaxSnap()); - } + assertEquals(root, path.getDestination(root)); } @Test @@ -415,9 +392,8 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT try (UndoableTransaction tid = b.startTransaction()) { root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); - thread = manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]"), - Range.atLeast(0L)); - thread.insert(ConflictResolution.DENY); + thread = manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]")); + thread.insert(Range.atLeast(0L), ConflictResolution.DENY); } assertEquals(Set.of(), root.getInterfaces()); assertEquals(Set.of(TraceObjectThread.class), thread.getInterfaces()); @@ -429,9 +405,8 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT try (UndoableTransaction tid = b.startTransaction()) { root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); - thread = manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]"), - Range.atLeast(0L)); - thread.insert(ConflictResolution.DENY); + thread = manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]")); + thread.insert(Range.atLeast(0L), ConflictResolution.DENY); } assertNull(root.queryInterface(TraceObjectThread.class)); TraceObjectThread threadIf = thread.queryInterface(TraceObjectThread.class); @@ -539,20 +514,20 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT assertEquals(List.of(root), root.getOrderedSuccessors(Range.all(), TraceObjectKeyPath.parse(""), true) - .map(p -> p.getLastChild(root)) + .map(p -> p.getDestination(root)) .collect(Collectors.toList())); assertEquals(List.of(root), root.getOrderedSuccessors(Range.all(), TraceObjectKeyPath.parse(""), false) - .map(p -> p.getLastChild(root)) + .map(p -> p.getDestination(root)) .collect(Collectors.toList())); assertEquals(List.of(targets.get(0), targets.get(1), targets.get(2)), root.getOrderedSuccessors(Range.all(), TraceObjectKeyPath.parse("curTarget"), true) - .map(p -> p.getLastChild(root)) + .map(p -> p.getDestination(root)) .collect(Collectors.toList())); assertEquals(List.of(targets.get(2), targets.get(1), targets.get(0)), root.getOrderedSuccessors(Range.all(), TraceObjectKeyPath.parse("curTarget"), false) - .map(p -> p.getLastChild(root)) + .map(p -> p.getDestination(root)) .collect(Collectors.toList())); } @@ -856,10 +831,10 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT try (UndoableTransaction tid = b.startTransaction()) { root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); - root.setAttribute(root.getLifespan(), "myAttribute", 1234); + root.setAttribute(Range.all(), "myAttribute", 1234); try { - root.setAttribute(root.getLifespan(), "[0]", 1234); + root.setAttribute(Range.all(), "[0]", 1234); fail(); } catch (IllegalArgumentException e) { @@ -921,50 +896,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT } @Test - public void testDeleteTree() { - populateModel(3); - - // This deletes everything except the root, since curTarget refers to successors - try (UndoableTransaction tid = b.startTransaction()) { - targetContainer.deleteTree(); - } - - assertTrue(targetContainer.isDeleted()); - for (TraceObject t : targets) { - assertTrue(t.isDeleted()); - } - - assertEquals(0, root.getSuccessors(Range.all(), PathPredicates.parse("curTarget")).count()); - assertEquals(root, Unique.assertOne(manager.getAllObjects())); - } - - @Test - public void testRootObjectTruncateOrDelete() { - try (UndoableTransaction tid = b.startTransaction()) { - root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); - - try { - root.truncateOrDelete(Range.singleton(0L)); - fail(); - } - catch (IllegalArgumentException e) { - // pass - } - assertEquals(Range.all(), root.getLifespan()); - - assertEquals(root, root.truncateOrDelete(Range.atLeast(10L))); - assertEquals(Range.atMost(9L), root.getLifespan()); - - assertEquals(root, root.truncateOrDelete(Range.atMost(-1L))); - assertEquals(Range.closed(0L, 9L), root.getLifespan()); - - assertNull(root.truncateOrDelete(Range.all())); - assertTrue(root.isDeleted()); - } - } - - @Test - public void testValueSetLifespan_TruncatesOrDeletes() { + public void testValueSetLifespanTruncatesOrDeletes() { try (UndoableTransaction tid = b.startTransaction()) { TraceObjectValue rootVal = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))); @@ -1040,8 +972,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT TraceObjectValue primVal = root.setValue(Range.all(), "primitive", "A string"); assertFalse(primVal.isCanonical()); - TraceObject child = - manager.createObject(TraceObjectKeyPath.parse("child"), Range.all()); + TraceObject child = manager.createObject(TraceObjectKeyPath.parse("child")); TraceObjectValue objVal = root.setValue(Range.all(), "child", child); assertTrue(objVal.isCanonical()); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/thread/DBTraceThreadManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/thread/DBTraceThreadManagerTest.java index 27d02e103f..cde0fea903 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/thread/DBTraceThreadManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/thread/DBTraceThreadManagerTest.java @@ -59,6 +59,7 @@ public class DBTraceThreadManagerTest extends AbstractGhidraHeadlessIntegrationT public void testAddThread() throws Exception { addThreads(); try (UndoableTransaction tid = b.startTransaction()) { + // TODO: Let this work by expanding the life instead threadManager.createThread("Threads[1]", 1); fail(); } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/lifecycle/Experimental.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/lifecycle/Experimental.java index 355d3cc0ea..55567a431b 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/lifecycle/Experimental.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/lifecycle/Experimental.java @@ -26,6 +26,6 @@ import java.lang.annotation.Target; * The items are intended to become part of the public API, but the interfaces are unstable, and * there's no guarantee they will ever become public. */ -@Target({ TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE }) +@Target({ TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE, PARAMETER }) public @interface Experimental { }