mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 17:59:46 +02:00
GP-2068: Indexing, caching, other optimizations for TraceRmi.
This commit is contained in:
parent
9c6eabfbb3
commit
a41c4ca5f7
63 changed files with 3529 additions and 1167 deletions
|
@ -356,6 +356,7 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||||
name = object.getName() + "." + nextId;
|
name = object.getName() + "." + nextId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
name = object.getName() + "." + System.currentTimeMillis();
|
||||||
// Don't catch it this last time
|
// Don't catch it this last time
|
||||||
return parent.createFile(name, object, monitor);
|
return parent.createFile(name, object, monitor);
|
||||||
}
|
}
|
||||||
|
@ -948,9 +949,12 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||||
tx.tx.abortOnClose();
|
tx.tx.abortOnClose();
|
||||||
}
|
}
|
||||||
tx.tx.close();
|
tx.tx.close();
|
||||||
|
OpenTrace open = requireOpenTrace(tx.txId.doId);
|
||||||
if (!tx.undoable) {
|
if (!tx.undoable) {
|
||||||
requireOpenTrace(tx.txId.doId).trace.clearUndo();
|
open.trace.clearUndo();
|
||||||
}
|
}
|
||||||
|
// TODO: Check for other transactions on the same trace?
|
||||||
|
open.trace.setEventsEnabled(true);
|
||||||
return ReplyEndTx.getDefaultInstance();
|
return ReplyEndTx.getDefaultInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -976,10 +980,11 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||||
RequestGetValuesIntersecting req) throws AddressOverflowException {
|
RequestGetValuesIntersecting req) throws AddressOverflowException {
|
||||||
OpenTrace open = requireOpenTrace(req.getOid());
|
OpenTrace open = requireOpenTrace(req.getOid());
|
||||||
AddressRange range = open.toRange(req.getBox().getRange(), false);
|
AddressRange range = open.toRange(req.getBox().getRange(), false);
|
||||||
|
String key = req.getKey() == "" ? null : req.getKey();
|
||||||
Collection<? extends TraceObjectValue> col = range == null
|
Collection<? extends TraceObjectValue> col = range == null
|
||||||
? List.of()
|
? List.of()
|
||||||
: open.trace.getObjectManager()
|
: open.trace.getObjectManager()
|
||||||
.getValuesIntersecting(toLifespan(req.getBox().getSpan()), range);
|
.getValuesIntersecting(toLifespan(req.getBox().getSpan()), range, key);
|
||||||
return ReplyGetValues.newBuilder()
|
return ReplyGetValues.newBuilder()
|
||||||
.addAllValues(col.stream().map(TraceRmiHandler::makeValDesc).toList())
|
.addAllValues(col.stream().map(TraceRmiHandler::makeValDesc).toList())
|
||||||
.build();
|
.build();
|
||||||
|
@ -1074,16 +1079,15 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||||
if (object == null) {
|
if (object == null) {
|
||||||
return ReplyRetainValues.getDefaultInstance();
|
return ReplyRetainValues.getDefaultInstance();
|
||||||
}
|
}
|
||||||
|
Lifespan span = toLifespan(req.getSpan());
|
||||||
Collection<? extends TraceObjectValue> values = switch (req.getKinds()) {
|
Collection<? extends TraceObjectValue> values = switch (req.getKinds()) {
|
||||||
case VK_ELEMENTS -> object.getElements();
|
case VK_ELEMENTS -> object.getElements(span);
|
||||||
case VK_ATTRIBUTES -> object.getAttributes();
|
case VK_ATTRIBUTES -> object.getAttributes(span);
|
||||||
case VK_BOTH -> object.getValues();
|
case VK_BOTH -> object.getValues(span);
|
||||||
default -> throw new TraceRmiError("Protocol error: Invalid value kinds");
|
default -> throw new TraceRmiError("Protocol error: Invalid value kinds");
|
||||||
};
|
};
|
||||||
Lifespan span = toLifespan(req.getSpan());
|
|
||||||
Set<String> keysToKeep = Set.copyOf(req.getKeysList());
|
Set<String> keysToKeep = Set.copyOf(req.getKeysList());
|
||||||
List<String> keysToDelete = values.stream()
|
List<String> keysToDelete = values.stream()
|
||||||
.filter(v -> v.getLifespan().intersects(span))
|
|
||||||
.map(v -> v.getEntryKey())
|
.map(v -> v.getEntryKey())
|
||||||
.filter(k -> !keysToKeep.contains(k))
|
.filter(k -> !keysToKeep.contains(k))
|
||||||
.distinct()
|
.distinct()
|
||||||
|
@ -1123,6 +1127,7 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||||
// Implies request was to set value to null
|
// Implies request was to set value to null
|
||||||
return ReplySetValue.newBuilder().setSpan(makeSpan(Lifespan.EMPTY)).build();
|
return ReplySetValue.newBuilder().setSpan(makeSpan(Lifespan.EMPTY)).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
TraceObjectValue val = object.setValue(toLifespan(value.getSpan()), value.getKey(),
|
TraceObjectValue val = object.setValue(toLifespan(value.getSpan()), value.getKey(),
|
||||||
objVal, toResolution(req.getResolution()));
|
objVal, toResolution(req.getResolution()));
|
||||||
return ReplySetValue.newBuilder()
|
return ReplySetValue.newBuilder()
|
||||||
|
@ -1144,6 +1149,7 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||||
protected ReplyStartTx handleStartTx(RequestStartTx req) {
|
protected ReplyStartTx handleStartTx(RequestStartTx req) {
|
||||||
OpenTrace open = requireOpenTrace(req.getOid());
|
OpenTrace open = requireOpenTrace(req.getOid());
|
||||||
Tid tid = requireAvailableTid(open, req.getTxid());
|
Tid tid = requireAvailableTid(open, req.getTxid());
|
||||||
|
open.trace.setEventsEnabled(false);
|
||||||
@SuppressWarnings("resource")
|
@SuppressWarnings("resource")
|
||||||
OpenTx tx =
|
OpenTx tx =
|
||||||
new OpenTx(tid, open.trace.openTransaction(req.getDescription()), req.getUndoable());
|
new OpenTx(tid, open.trace.openTransaction(req.getDescription()), req.getUndoable());
|
||||||
|
|
|
@ -847,11 +847,10 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
|
|
||||||
protected TraceObject getProcessForSpace(AddressSpace space) {
|
protected TraceObject getProcessForSpace(AddressSpace space) {
|
||||||
for (TraceObjectValue objVal : trace.getObjectManager()
|
for (TraceObjectValue objVal : trace.getObjectManager()
|
||||||
.getValuesIntersecting(Lifespan.at(getSnap()),
|
.getValuesIntersecting(
|
||||||
new AddressRangeImpl(space.getMinAddress(), space.getMaxAddress()))) {
|
Lifespan.at(getSnap()),
|
||||||
if (!TargetMemoryRegion.RANGE_ATTRIBUTE_NAME.equals(objVal.getEntryKey())) {
|
new AddressRangeImpl(space.getMinAddress(), space.getMaxAddress()),
|
||||||
continue;
|
TargetMemoryRegion.RANGE_ATTRIBUTE_NAME)) {
|
||||||
}
|
|
||||||
TraceObject obj = objVal.getParent();
|
TraceObject obj = objVal.getParent();
|
||||||
if (!obj.getInterfaces().contains(TraceObjectMemoryRegion.class)) {
|
if (!obj.getInterfaces().contains(TraceObjectMemoryRegion.class)) {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -361,6 +361,7 @@ message ReplyGetValues {
|
||||||
message RequestGetValuesIntersecting {
|
message RequestGetValuesIntersecting {
|
||||||
DomObjId oid = 1;
|
DomObjId oid = 1;
|
||||||
Box box = 2;
|
Box box = 2;
|
||||||
|
string key = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Analysis operations
|
// Analysis operations
|
||||||
|
|
|
@ -389,10 +389,10 @@ class Trace(object):
|
||||||
span = Lifespan(self.snap(), self.snap())
|
span = Lifespan(self.snap(), self.snap())
|
||||||
return self._make_values(self.client._get_values(self.id, span, pattern))
|
return self._make_values(self.client._get_values(self.id, span, pattern))
|
||||||
|
|
||||||
def get_values_intersecting(self, rng, span=None):
|
def get_values_intersecting(self, rng, span=None, key=""):
|
||||||
if span is None:
|
if span is None:
|
||||||
span = Lifespan(self.snap(), self.snap())
|
span = Lifespan(self.snap(), self.snap())
|
||||||
return self._make_values(self.client._get_values_intersecting(self.id, span, rng))
|
return self._make_values(self.client._get_values_intersecting(self.id, span, rng, key))
|
||||||
|
|
||||||
def _activate_object(self, object):
|
def _activate_object(self, object):
|
||||||
self.client._activate_object(self.id, object)
|
self.client._activate_object(self.id, object)
|
||||||
|
@ -1053,11 +1053,12 @@ class Client(object):
|
||||||
return self._read_values(reply)
|
return self._read_values(reply)
|
||||||
return self._batch_or_now(root, 'reply_get_values', _handle)
|
return self._batch_or_now(root, 'reply_get_values', _handle)
|
||||||
|
|
||||||
def _get_values_intersecting(self, id, span, rng):
|
def _get_values_intersecting(self, id, span, rng, key):
|
||||||
root = bufs.RootMessage()
|
root = bufs.RootMessage()
|
||||||
root.request_get_values_intersecting.oid.id = id
|
root.request_get_values_intersecting.oid.id = id
|
||||||
self._write_span(root.request_get_values_intersecting.box.span, span)
|
self._write_span(root.request_get_values_intersecting.box.span, span)
|
||||||
self._write_range(root.request_get_values_intersecting.box.range, rng)
|
self._write_range(root.request_get_values_intersecting.box.range, rng)
|
||||||
|
root.request_get_values_intersecting.key = key
|
||||||
|
|
||||||
def _handle(reply):
|
def _handle(reply):
|
||||||
return self._read_values(reply)
|
return self._read_values(reply)
|
||||||
|
|
|
@ -136,7 +136,10 @@ public class DebuggerLocationLabel extends JLabel {
|
||||||
if (sections.isEmpty()) {
|
if (sections.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// TODO: DB's R-Tree could probably do this natively
|
/**
|
||||||
|
* TODO: DB's R-Tree could probably do this natively. Not sure it's an optimization, though,
|
||||||
|
* since few, if any, overlapping sections are expected.
|
||||||
|
*/
|
||||||
sections.sort(ComparatorUtils.chainedComparator(List.of(
|
sections.sort(ComparatorUtils.chainedComparator(List.of(
|
||||||
Comparator.comparing(s -> s.getRange().getMinAddress()),
|
Comparator.comparing(s -> s.getRange().getMinAddress()),
|
||||||
Comparator.comparing(s -> -s.getRange().getLength()))));
|
Comparator.comparing(s -> -s.getRange().getLength()))));
|
||||||
|
|
|
@ -32,7 +32,8 @@ import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||||
import ghidra.app.plugin.core.debug.event.*;
|
import ghidra.app.plugin.core.debug.event.*;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractNewListingAction;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractNewListingAction;
|
||||||
import ghidra.app.plugin.core.debug.gui.action.*;
|
import ghidra.app.plugin.core.debug.gui.action.DebuggerProgramLocationActionContext;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.action.NoneLocationTrackingSpec;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.app.util.viewer.format.FormatManager;
|
import ghidra.app.util.viewer.format.FormatManager;
|
||||||
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||||
|
|
|
@ -228,6 +228,9 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener {
|
||||||
private void processTrace(Trace trace) {
|
private void processTrace(Trace trace) {
|
||||||
updateList.clear();
|
updateList.clear();
|
||||||
provider.reset();
|
provider.reset();
|
||||||
|
if (!provider.isVisible()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
TraceThreadManager threadManager = trace.getThreadManager();
|
TraceThreadManager threadManager = trace.getThreadManager();
|
||||||
for (TraceThread thread : threadManager.getAllThreads()) {
|
for (TraceThread thread : threadManager.getAllThreads()) {
|
||||||
threadChanged(thread);
|
threadChanged(thread);
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package ghidra.app.plugin.core.debug.gui.model;
|
package ghidra.app.plugin.core.debug.gui.model;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
@ -303,9 +304,17 @@ public abstract class AbstractQueryTableModel<T> extends ThreadedTableModel<T, T
|
||||||
if (trace == null || query == null || trace.getObjectManager().getRootSchema() == null) {
|
if (trace == null || query == null || trace.getObjectManager().getRootSchema() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
ArrayList<T> batch = new ArrayList<>(100);
|
||||||
for (T t : (Iterable<T>) streamRows(trace, query, span)::iterator) {
|
for (T t : (Iterable<T>) streamRows(trace, query, span)::iterator) {
|
||||||
accumulator.add(t);
|
batch.add(t);
|
||||||
monitor.checkCancelled();
|
if (batch.size() >= 100) {
|
||||||
|
accumulator.addAll(batch);
|
||||||
|
monitor.checkCancelled();
|
||||||
|
batch.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (batch.size() > 0) {
|
||||||
|
accumulator.addAll(batch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -453,7 +453,8 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||||
if (parent == null) {
|
if (parent == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
for (TraceObjectValue value : parent.getValues()) {
|
for (TraceObjectValue value : parent.getValues(
|
||||||
|
isLimitToCurrentSnap() ? Lifespan.at(current.getSnap()) : Lifespan.ALL)) {
|
||||||
if (Objects.equals(object, value.getValue())) {
|
if (Objects.equals(object, value.getValue())) {
|
||||||
return value.getCanonicalPath();
|
return value.getCanonicalPath();
|
||||||
}
|
}
|
||||||
|
|
|
@ -605,9 +605,6 @@ public class ObjectTreeModel implements DisplaysModified {
|
||||||
if (!showMethods && value.isObject() && value.getChild().isMethod(snap)) {
|
if (!showMethods && value.isObject() && value.getChild().isMethod(snap)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!value.getLifespan().intersects(span)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -624,7 +621,9 @@ public class ObjectTreeModel implements DisplaysModified {
|
||||||
|
|
||||||
protected List<GTreeNode> generateObjectChildren(TraceObject object) {
|
protected List<GTreeNode> generateObjectChildren(TraceObject object) {
|
||||||
List<GTreeNode> result = ObjectTableModel
|
List<GTreeNode> result = ObjectTableModel
|
||||||
.distinctCanonical(object.getValues().stream().filter(this::isValueVisible))
|
.distinctCanonical(object.getValues(span)
|
||||||
|
.stream()
|
||||||
|
.filter(this::isValueVisible))
|
||||||
.map(v -> nodeCache.getOrCreateNode(v))
|
.map(v -> nodeCache.getOrCreateNode(v))
|
||||||
.sorted()
|
.sorted()
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
|
@ -101,6 +101,9 @@ public class DebuggerModulesPanel extends AbstractObjectsTableBasedPanel<TraceOb
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
AddressRange range = attr.getValue();
|
AddressRange range = attr.getValue();
|
||||||
|
if (range == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Cache this? Would flush on:
|
// TODO: Cache this? Would flush on:
|
||||||
// 1. Mapping changes
|
// 1. Mapping changes
|
||||||
|
|
|
@ -75,51 +75,51 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerTest {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUpDisassemblyTest() throws Exception {
|
public void setUpDisassemblyTest() throws Exception {
|
||||||
ctx = XmlSchemaContext.deserialize("" + //
|
ctx = XmlSchemaContext.deserialize("""
|
||||||
"<context>" + //
|
<context>
|
||||||
" <schema name='Session' elementResync='NEVER' attributeResync='ONCE'>" + //
|
<schema name='Session' elementResync='NEVER' attributeResync='ONCE'>
|
||||||
" <attribute name='Targets' schema='TargetContainer' />" + //
|
<attribute name='Targets' schema='TargetContainer' />
|
||||||
" </schema>" + //
|
</schema>
|
||||||
" <schema name='TargetContainer' canonical='yes' elementResync='NEVER' " + //
|
<schema name='TargetContainer' canonical='yes' elementResync='NEVER'
|
||||||
" attributeResync='ONCE'>" + //
|
attributeResync='ONCE'>
|
||||||
" <element schema='Target' />" + //
|
<element schema='Target' />
|
||||||
" </schema>" + //
|
</schema>
|
||||||
" <schema name='Target' elementResync='NEVER' attributeResync='NEVER'>" + //
|
<schema name='Target' elementResync='NEVER' attributeResync='NEVER'>
|
||||||
" <interface name='Process' />" + //
|
<interface name='Process' />
|
||||||
" <interface name='Aggregate' />" + //
|
<interface name='Aggregate' />
|
||||||
" <attribute name='Environment' schema='Environment' />" + //
|
<attribute name='Environment' schema='Environment' />
|
||||||
" <attribute name='Memory' schema='Memory' />" + //
|
<attribute name='Memory' schema='Memory' />
|
||||||
" <attribute name='Threads' schema='ThreadContainer' />" + //
|
<attribute name='Threads' schema='ThreadContainer' />
|
||||||
" </schema>" + //
|
</schema>
|
||||||
" <schema name='Environment' elementResync='NEVER' " + //
|
<schema name='Environment' elementResync='NEVER'
|
||||||
" attributeResync='NEVER'>" + //
|
attributeResync='NEVER'>"
|
||||||
" <interface name='Environment' />" + //
|
<interface name='Environment' />
|
||||||
" </schema>" + //
|
</schema>
|
||||||
" <schema name='Memory' canonical='yes' elementResync='NEVER' " + //
|
<schema name='Memory' canonical='yes' elementResync='NEVER'
|
||||||
" attributeResync='NEVER'>" + //
|
attributeResync='NEVER'>
|
||||||
" <element schema='MemoryRegion' />" + //
|
<element schema='MemoryRegion' />"
|
||||||
" </schema>" + //
|
</schema>
|
||||||
" <schema name='MemoryRegion' elementResync='NEVER' attributeResync='NEVER'>" + //
|
<schema name='MemoryRegion' elementResync='NEVER' attributeResync='NEVER'>
|
||||||
" <interface name='MemoryRegion' />" + //
|
<interface name='MemoryRegion' />
|
||||||
" </schema>" + //
|
</schema>"
|
||||||
" <schema name='ThreadContainer' canonical='yes' elementResync='NEVER' " + //
|
<schema name='ThreadContainer' canonical='yes' elementResync='NEVER'
|
||||||
" attributeResync='NEVER'>" + //
|
attributeResync='NEVER'>
|
||||||
" <element schema='Thread' />" + //
|
<element schema='Thread' />
|
||||||
" </schema>" + //
|
</schema>
|
||||||
" <schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>" + //
|
<schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>
|
||||||
" <interface name='Thread' />" + //
|
<interface name='Thread' />
|
||||||
" <interface name='Aggregate' />" + //
|
<interface name='Aggregate' />
|
||||||
" <attribute name='Stack' schema='Stack' />" + //
|
<attribute name='Stack' schema='Stack' />
|
||||||
" </schema>" + //
|
</schema>
|
||||||
" <schema name='Stack' canonical='yes' elementResync='NEVER' " + //
|
<schema name='Stack' canonical='yes' elementResync='NEVER'
|
||||||
" attributeResync='NEVER'>" + //
|
attributeResync='NEVER'>
|
||||||
" <interface name='Stack' />" + //
|
<interface name='Stack' />
|
||||||
" <element schema='Frame' />" + //
|
<element schema='Frame' />
|
||||||
" </schema>" + //
|
</schema>
|
||||||
" <schema name='Frame' elementResync='NEVER' attributeResync='NEVER'>" + //
|
<schema name='Frame' elementResync='NEVER' attributeResync='NEVER'>
|
||||||
" <interface name='StackFrame' />" + //
|
<interface name='StackFrame' />
|
||||||
" </schema>" + //
|
</schema>
|
||||||
"</context>");
|
</context>""");
|
||||||
|
|
||||||
addPlugin(tool, DebuggerListingPlugin.class);
|
addPlugin(tool, DebuggerListingPlugin.class);
|
||||||
platformService = addPlugin(tool, DebuggerPlatformServicePlugin.class);
|
platformService = addPlugin(tool, DebuggerPlatformServicePlugin.class);
|
||||||
|
|
|
@ -26,7 +26,7 @@ import org.junit.Test;
|
||||||
|
|
||||||
import generic.Unique;
|
import generic.Unique;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||||
import ghidra.app.plugin.core.debug.mapping.*;
|
import ghidra.app.plugin.core.debug.mapping.ObjectBasedDebuggerMappingOpinion;
|
||||||
import ghidra.dbg.error.DebuggerMemoryAccessException;
|
import ghidra.dbg.error.DebuggerMemoryAccessException;
|
||||||
import ghidra.dbg.model.*;
|
import ghidra.dbg.model.*;
|
||||||
import ghidra.dbg.target.*;
|
import ghidra.dbg.target.*;
|
||||||
|
@ -77,7 +77,7 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerTe
|
||||||
|
|
||||||
protected void dumpValues(TraceObject obj) {
|
protected void dumpValues(TraceObject obj) {
|
||||||
System.err.println("Values of " + obj);
|
System.err.println("Values of " + obj);
|
||||||
for (TraceObjectValue val : obj.getValues()) {
|
for (TraceObjectValue val : obj.getValues(Lifespan.ALL)) {
|
||||||
System.err.println(" " + val.getEntryKey() + " = " + val.getValue());
|
System.err.println(" " + val.getEntryKey() + " = " + val.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,39 +98,39 @@ public class DBTraceTimeViewport implements TraceTimeViewport {
|
||||||
if (range == null) {
|
if (range == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try (LockHold hold = trace.lockRead()) {
|
/**
|
||||||
synchronized (ordered) {
|
* NB. Because occlusion.occluded can be expensive, we should only keep the lock long enough
|
||||||
for (Lifespan rng : ordered) {
|
* to copy the spans.
|
||||||
if (lifespan.contains(rng.lmax())) {
|
*/
|
||||||
return true;
|
for (Lifespan rng : getOrderedSpans()) {
|
||||||
}
|
if (lifespan.contains(rng.lmax())) {
|
||||||
if (occlusion.occluded(object, range, rng)) {
|
return true;
|
||||||
return false;
|
}
|
||||||
}
|
if (occlusion.occluded(object, range, rng)) {
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> AddressSet computeVisibleParts(AddressSetView set, Lifespan lifespan, T object,
|
public <T> AddressSet computeVisibleParts(AddressSetView set, Lifespan lifespan, T object,
|
||||||
Occlusion<T> occlusion) {
|
Occlusion<T> occlusion) {
|
||||||
|
List<Lifespan> spans;
|
||||||
try (LockHold hold = trace.lockRead()) {
|
try (LockHold hold = trace.lockRead()) {
|
||||||
if (!containsAnyUpper(lifespan)) {
|
if (!containsAnyUpper(lifespan)) {
|
||||||
return new AddressSet();
|
return new AddressSet();
|
||||||
}
|
}
|
||||||
AddressSet remains = new AddressSet(set);
|
spans = getOrderedSpans();
|
||||||
synchronized (ordered) {
|
}
|
||||||
for (Lifespan rng : ordered) {
|
AddressSet remains = new AddressSet(set);
|
||||||
if (lifespan.contains(rng.lmax())) {
|
for (Lifespan rng : spans) {
|
||||||
return remains;
|
if (lifespan.contains(rng.lmax())) {
|
||||||
}
|
return remains;
|
||||||
occlusion.remove(object, remains, rng);
|
}
|
||||||
if (remains.isEmpty()) {
|
occlusion.remove(object, remains, rng);
|
||||||
return remains;
|
if (remains.isEmpty()) {
|
||||||
}
|
return remains;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// This condition should have been detected by !containsAnyUpper
|
// This condition should have been detected by !containsAnyUpper
|
||||||
|
@ -308,73 +308,62 @@ public class DBTraceTimeViewport implements TraceTimeViewport {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Long> getOrderedSnaps() {
|
public List<Long> getOrderedSnaps() {
|
||||||
|
ArrayList<Long> result = new ArrayList<>();
|
||||||
try (LockHold hold = trace.lockRead()) {
|
try (LockHold hold = trace.lockRead()) {
|
||||||
synchronized (ordered) {
|
synchronized (ordered) {
|
||||||
return ordered
|
for (Lifespan span : ordered) {
|
||||||
.stream()
|
result.add(span.lmax());
|
||||||
.map(Lifespan::lmax)
|
}
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Long> getReversedSnaps() {
|
public List<Long> getReversedSnaps() {
|
||||||
|
ArrayList<Long> result = new ArrayList<>();
|
||||||
try (LockHold hold = trace.lockRead()) {
|
try (LockHold hold = trace.lockRead()) {
|
||||||
synchronized (ordered) {
|
synchronized (ordered) {
|
||||||
List<Long> reversed =
|
ListIterator<Lifespan> it = ordered.listIterator(ordered.size());
|
||||||
ordered.stream().map(Lifespan::lmax).collect(Collectors.toList());
|
while (it.hasPrevious()) {
|
||||||
Collections.reverse(reversed);
|
result.add(it.previous().lmax());
|
||||||
return reversed;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T getTop(Function<Long, T> func) {
|
public <T> T getTop(Function<Long, T> func) {
|
||||||
try (LockHold hold = trace.lockRead()) {
|
for (Lifespan rng : getOrderedSpans()) {
|
||||||
synchronized (ordered) {
|
T t = func.apply(rng.lmax());
|
||||||
for (Lifespan rng : ordered) {
|
if (t != null) {
|
||||||
T t = func.apply(rng.lmax());
|
return t;
|
||||||
if (t != null) {
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> Iterator<T> mergedIterator(Function<Long, Iterator<T>> iterFunc,
|
public <T> Iterator<T> mergedIterator(Function<Long, Iterator<T>> iterFunc,
|
||||||
Comparator<? super T> comparator) {
|
Comparator<? super T> comparator) {
|
||||||
List<Iterator<T>> iters;
|
if (!isForked()) {
|
||||||
try (LockHold hold = trace.lockRead()) {
|
return iterFunc.apply(snap);
|
||||||
synchronized (ordered) {
|
|
||||||
if (!isForked()) {
|
|
||||||
return iterFunc.apply(snap);
|
|
||||||
}
|
|
||||||
iters = ordered.stream()
|
|
||||||
.map(rng -> iterFunc.apply(rng.lmax()))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
List<Iterator<T>> iters = getOrderedSpans().stream()
|
||||||
|
.map(rng -> iterFunc.apply(rng.lmax()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
return new UniqIterator<>(new MergeSortingIterator<>(iters, comparator));
|
return new UniqIterator<>(new MergeSortingIterator<>(iters, comparator));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AddressSetView unionedAddresses(Function<Long, AddressSetView> viewFunc) {
|
public AddressSetView unionedAddresses(Function<Long, AddressSetView> viewFunc) {
|
||||||
List<AddressSetView> views;
|
if (!isForked()) {
|
||||||
try (LockHold hold = trace.lockRead()) {
|
return viewFunc.apply(snap);
|
||||||
synchronized (ordered) {
|
|
||||||
if (!isForked()) {
|
|
||||||
return viewFunc.apply(snap);
|
|
||||||
}
|
|
||||||
views = ordered.stream()
|
|
||||||
.map(rng -> viewFunc.apply(rng.lmax()))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
List<AddressSetView> views = getOrderedSpans().stream()
|
||||||
|
.map(rng -> viewFunc.apply(rng.lmax()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
return new UnionAddressSetView(views);
|
return new UnionAddressSetView(views);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,7 +118,7 @@ public class DBTraceAddressSnapRangePropertyMapTree<T, DR extends AbstractDBTrac
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected NodeType getType() {
|
protected NodeType getType() {
|
||||||
return NodeType.values()[(typeAndChildCount >> NODE_TYPE_SHIFT) & NODE_TYPE_MASK];
|
return NodeType.VALUES.get((typeAndChildCount >> NODE_TYPE_SHIFT) & NODE_TYPE_MASK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -26,12 +26,29 @@ import ghidra.util.LockHold;
|
||||||
import ghidra.util.datastruct.WeakValueHashMap;
|
import ghidra.util.datastruct.WeakValueHashMap;
|
||||||
|
|
||||||
public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
|
public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
|
||||||
|
// I think size should be about how many instructions may appear on screen at once.
|
||||||
|
// Double for good measure (in case windows are cloned, maximized, etc.)
|
||||||
|
private static final int REGION_CACHE_BY_ADDRESS_SIZE = 300;
|
||||||
|
|
||||||
|
// Size should be about how many distinct regions are involved in displayed instructions
|
||||||
|
// Probably only about 5, but cost of 30 is still small.
|
||||||
|
private static final int REGION_CACHE_BY_NAME_SIZE = 30;
|
||||||
|
|
||||||
// NB. Keep both per-region and force-full (per-space) block sets ready
|
// NB. Keep both per-region and force-full (per-space) block sets ready
|
||||||
private final Map<TraceMemoryRegion, DBTraceProgramViewMemoryRegionBlock> regionBlocks =
|
private final Map<TraceMemoryRegion, DBTraceProgramViewMemoryRegionBlock> regionBlocks =
|
||||||
new WeakValueHashMap<>();
|
new WeakValueHashMap<>();
|
||||||
private final Map<AddressSpace, DBTraceProgramViewMemorySpaceBlock> spaceBlocks =
|
private final Map<AddressSpace, DBTraceProgramViewMemorySpaceBlock> spaceBlocks =
|
||||||
new WeakValueHashMap<>();
|
new WeakValueHashMap<>();
|
||||||
|
private final Map<Address, TraceMemoryRegion> regionCacheByAddress = new LinkedHashMap<>() {
|
||||||
|
protected boolean removeEldestEntry(Map.Entry<Address, TraceMemoryRegion> eldest) {
|
||||||
|
return this.size() > REGION_CACHE_BY_ADDRESS_SIZE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private final Map<String, TraceMemoryRegion> regionCacheByName = new LinkedHashMap<>() {
|
||||||
|
protected boolean removeEldestEntry(Map.Entry<String, TraceMemoryRegion> eldest) {
|
||||||
|
return this.size() > REGION_CACHE_BY_NAME_SIZE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public DBTraceProgramViewMemory(DBTraceProgramView program) {
|
public DBTraceProgramViewMemory(DBTraceProgramView program) {
|
||||||
super(program);
|
super(program);
|
||||||
|
@ -90,8 +107,24 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
|
||||||
if (forceFullView) {
|
if (forceFullView) {
|
||||||
return getSpaceBlock(addr.getAddressSpace());
|
return getSpaceBlock(addr.getAddressSpace());
|
||||||
}
|
}
|
||||||
TraceMemoryRegion region = getTopRegion(s -> memoryManager.getRegionContaining(s, addr));
|
TraceMemoryRegion region = regionCacheByAddress.get(addr);
|
||||||
return region == null ? null : getRegionBlock(region);
|
if (region != null && !region.isDeleted()) {
|
||||||
|
/**
|
||||||
|
* TODO: This is assuming: 1) We never fork in non-scratch space. 2) Regions are not
|
||||||
|
* created in scratch space. These are convention, but weren't originally intended to be
|
||||||
|
* rules. This makes them rules.
|
||||||
|
*/
|
||||||
|
long s = program.viewport.getReversedSnaps().get(0);
|
||||||
|
if (region.getLifespan().contains(s)) {
|
||||||
|
return getRegionBlock(region);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
region = getTopRegion(s -> memoryManager.getRegionContaining(s, addr));
|
||||||
|
if (region != null) {
|
||||||
|
regionCacheByAddress.put(addr, region);
|
||||||
|
return getRegionBlock(region);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -100,9 +133,19 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
|
||||||
AddressSpace space = program.getAddressFactory().getAddressSpace(blockName);
|
AddressSpace space = program.getAddressFactory().getAddressSpace(blockName);
|
||||||
return space == null ? null : getSpaceBlock(space);
|
return space == null ? null : getSpaceBlock(space);
|
||||||
}
|
}
|
||||||
TraceMemoryRegion region =
|
TraceMemoryRegion region = regionCacheByName.get(blockName);
|
||||||
getTopRegion(s -> memoryManager.getLiveRegionByPath(s, blockName));
|
if (region != null && !region.isDeleted()) {
|
||||||
return region == null ? null : getRegionBlock(region);
|
long s = program.viewport.getReversedSnaps().get(0);
|
||||||
|
if (region.getLifespan().contains(s)) {
|
||||||
|
return getRegionBlock(region);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
region = getTopRegion(s -> memoryManager.getLiveRegionByPath(s, blockName));
|
||||||
|
if (region != null) {
|
||||||
|
regionCacheByName.put(blockName, region);
|
||||||
|
return getRegionBlock(region);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -19,26 +19,24 @@ import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.*;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import db.DBRecord;
|
import db.DBRecord;
|
||||||
import db.StringField;
|
import db.StringField;
|
||||||
import ghidra.dbg.target.TargetObject;
|
import ghidra.dbg.target.TargetObject;
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
import ghidra.dbg.util.*;
|
import ghidra.dbg.util.*;
|
||||||
import ghidra.program.model.address.AddressSpace;
|
|
||||||
import ghidra.trace.database.DBTrace;
|
import ghidra.trace.database.DBTrace;
|
||||||
import ghidra.trace.database.breakpoint.DBTraceObjectBreakpointLocation;
|
import ghidra.trace.database.breakpoint.DBTraceObjectBreakpointLocation;
|
||||||
import ghidra.trace.database.breakpoint.DBTraceObjectBreakpointSpec;
|
import ghidra.trace.database.breakpoint.DBTraceObjectBreakpointSpec;
|
||||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapSpace;
|
|
||||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery;
|
|
||||||
import ghidra.trace.database.memory.DBTraceObjectMemoryRegion;
|
import ghidra.trace.database.memory.DBTraceObjectMemoryRegion;
|
||||||
import ghidra.trace.database.memory.DBTraceObjectRegister;
|
import ghidra.trace.database.memory.DBTraceObjectRegister;
|
||||||
import ghidra.trace.database.module.*;
|
import ghidra.trace.database.module.*;
|
||||||
import ghidra.trace.database.stack.DBTraceObjectStack;
|
import ghidra.trace.database.stack.DBTraceObjectStack;
|
||||||
import ghidra.trace.database.stack.DBTraceObjectStackFrame;
|
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.InternalTraceObjectValue.ValueLifespanSetter;
|
||||||
|
import ghidra.trace.database.target.ValueSpace.SnapDimension;
|
||||||
import ghidra.trace.database.target.visitors.*;
|
import ghidra.trace.database.target.visitors.*;
|
||||||
import ghidra.trace.database.target.visitors.TreeTraversal.Visitor;
|
import ghidra.trace.database.target.visitors.TreeTraversal.Visitor;
|
||||||
import ghidra.trace.database.thread.DBTraceObjectThread;
|
import ghidra.trace.database.thread.DBTraceObjectThread;
|
||||||
|
@ -56,16 +54,18 @@ import ghidra.trace.model.target.*;
|
||||||
import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils;
|
import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils;
|
||||||
import ghidra.trace.model.thread.TraceObjectThread;
|
import ghidra.trace.model.thread.TraceObjectThread;
|
||||||
import ghidra.trace.util.TraceChangeRecord;
|
import ghidra.trace.util.TraceChangeRecord;
|
||||||
import ghidra.util.*;
|
import ghidra.util.LockHold;
|
||||||
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.database.*;
|
import ghidra.util.database.*;
|
||||||
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
|
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
|
||||||
import ghidra.util.database.annot.*;
|
import ghidra.util.database.annot.*;
|
||||||
import ghidra.util.database.spatial.rect.Rectangle2DDirection;
|
|
||||||
|
|
||||||
@DBAnnotatedObjectInfo(version = 0)
|
@DBAnnotatedObjectInfo(version = 0)
|
||||||
public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
protected static final String TABLE_NAME = "Objects";
|
protected static final String TABLE_NAME = "Objects";
|
||||||
|
|
||||||
|
private static final int VALUE_CACHE_SIZE = 50;
|
||||||
|
|
||||||
protected static <T extends TraceObjectInterface> //
|
protected static <T extends TraceObjectInterface> //
|
||||||
Map.Entry<Class<? extends T>, Function<DBTraceObject, ? extends T>> safeEntry(
|
Map.Entry<Class<? extends T>, Function<DBTraceObject, ? extends T>> safeEntry(
|
||||||
Class<T> cls, Function<DBTraceObject, ? extends T> ctor) {
|
Class<T> cls, Function<DBTraceObject, ? extends T> ctor) {
|
||||||
|
@ -119,6 +119,9 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
record CachedLifespanValues(Lifespan span, Set<InternalTraceObjectValue> values) {
|
||||||
|
}
|
||||||
|
|
||||||
// Canonical path
|
// Canonical path
|
||||||
static final String PATH_COLUMN_NAME = "Path";
|
static final String PATH_COLUMN_NAME = "Path";
|
||||||
|
|
||||||
|
@ -135,6 +138,19 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
|
|
||||||
private Map<Class<? extends TraceObjectInterface>, TraceObjectInterface> ifaces;
|
private Map<Class<? extends TraceObjectInterface>, TraceObjectInterface> ifaces;
|
||||||
|
|
||||||
|
private final Map<String, InternalTraceObjectValue> valueCache = new LinkedHashMap<>() {
|
||||||
|
protected boolean removeEldestEntry(Map.Entry<String, InternalTraceObjectValue> eldest) {
|
||||||
|
return size() > VALUE_CACHE_SIZE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private final Map<String, Long> nullCache = new LinkedHashMap<>() {
|
||||||
|
protected boolean removeEldestEntry(Map.Entry<String, Long> eldest) {
|
||||||
|
return size() > VALUE_CACHE_SIZE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private CachedLifespanValues cachedLifespanValues = null;
|
||||||
|
private MutableLifeSet cachedLife = null;
|
||||||
|
|
||||||
public DBTraceObject(DBTraceObjectManager manager, DBCachedObjectStore<?> store,
|
public DBTraceObject(DBTraceObjectManager manager, DBCachedObjectStore<?> store,
|
||||||
DBRecord record) {
|
DBRecord record) {
|
||||||
super(store, record);
|
super(store, record);
|
||||||
|
@ -199,12 +215,17 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LifeSet getLife() {
|
public LifeSet getLife() {
|
||||||
// TODO: This should really be cached
|
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
|
if (cachedLife != null) {
|
||||||
|
synchronized (cachedLife) {
|
||||||
|
return DefaultLifeSet.copyOf(cachedLife);
|
||||||
|
}
|
||||||
|
}
|
||||||
MutableLifeSet result = new DefaultLifeSet();
|
MutableLifeSet result = new DefaultLifeSet();
|
||||||
// NOTE: connected ranges should already be coalesced
|
// NOTE: connected ranges should already be coalesced
|
||||||
// No need to apply discreet domain
|
// No need to apply discreet domain
|
||||||
getCanonicalParents(Lifespan.ALL).forEach(v -> result.add(v.getLifespan()));
|
getCanonicalParents(Lifespan.ALL).forEach(v -> result.add(v.getLifespan()));
|
||||||
|
cachedLife = result;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -253,10 +274,10 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doRemoveTree(Lifespan span) {
|
protected void doRemoveTree(Lifespan span) {
|
||||||
for (DBTraceObjectValue parent : getParents()) {
|
for (InternalTraceObjectValue parent : getParents(span)) {
|
||||||
parent.doTruncateOrDeleteAndEmitLifeChange(span);
|
parent.doTruncateOrDeleteAndEmitLifeChange(span);
|
||||||
}
|
}
|
||||||
for (InternalTraceObjectValue value : getValues()) {
|
for (InternalTraceObjectValue value : getValues(span)) {
|
||||||
value.doTruncateOrDeleteAndEmitLifeChange(span);
|
value.doTruncateOrDeleteAndEmitLifeChange(span);
|
||||||
if (value.isCanonical()) {
|
if (value.isCanonical()) {
|
||||||
value.getChild().doRemoveTree(span);
|
value.getChild().doRemoveTree(span);
|
||||||
|
@ -275,26 +296,24 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
public TraceObjectValue getCanonicalParent(long snap) {
|
public TraceObjectValue getCanonicalParent(long snap) {
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
if (isRoot()) {
|
if (isRoot()) {
|
||||||
return manager.valueStore.getObjectAt(0);
|
return manager.getRootValue();
|
||||||
}
|
}
|
||||||
return getCanonicalParents(Lifespan.at(snap)).findAny().orElse(null);
|
return manager.valueMap
|
||||||
|
.reduce(TraceObjectValueQuery.canonicalParents(this, Lifespan.at(snap)))
|
||||||
|
.firstValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<? extends DBTraceObjectValue> getCanonicalParents(Lifespan lifespan) {
|
public Stream<? extends InternalTraceObjectValue> getCanonicalParents(Lifespan lifespan) {
|
||||||
// TODO: If this is invoked often, perhaps index
|
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
if (isRoot()) {
|
if (isRoot()) {
|
||||||
return Stream.of(manager.valueStore.getObjectAt(0));
|
return Stream.of(manager.getRootValue());
|
||||||
}
|
}
|
||||||
String canonicalKey = path.key();
|
List<InternalTraceObjectValue> list = List.copyOf(
|
||||||
TraceObjectKeyPath canonicalTail = path.parent();
|
manager.valueMap.reduce(TraceObjectValueQuery.canonicalParents(this, lifespan))
|
||||||
return manager.valuesByChild.getLazily(this)
|
.values());
|
||||||
.stream()
|
return list.stream();
|
||||||
.filter(v -> canonicalKey.equals(v.getEntryKey()))
|
|
||||||
.filter(v -> v.getLifespan().intersects(lifespan))
|
|
||||||
.filter(v -> canonicalTail.equals(v.getParent().getCanonicalPath()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,135 +348,53 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
return ifCls.cast(ifaces.get(ifCls));
|
return ifCls.cast(ifaces.get(ifCls));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Collection<? extends DBTraceObjectValue> doGetParents() {
|
protected Collection<? extends InternalTraceObjectValue> doGetParents(Lifespan lifespan) {
|
||||||
return manager.valuesByChild.get(this);
|
return List.copyOf(
|
||||||
|
manager.valueMap.reduce(TraceObjectValueQuery.parents(this, lifespan)).values());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<? extends DBTraceObjectValue> getParents() {
|
public Collection<? extends InternalTraceObjectValue> getParents(Lifespan lifespan) {
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
return doGetParents();
|
return doGetParents(lifespan);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void collectNonRangedValues(Collection<? super DBTraceObjectValue> result) {
|
|
||||||
for (DBTraceObjectValue val : manager.valuesByTriple
|
|
||||||
.tail(new PrimaryTriple(this, "", Long.MIN_VALUE), true)
|
|
||||||
.values()) {
|
|
||||||
if (val.getParent() != this) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
result.add(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void collectNonRangedAttributes(List<? super DBTraceObjectValue> result) {
|
|
||||||
for (DBTraceObjectValue val : manager.valuesByTriple
|
|
||||||
.sub(new PrimaryTriple(this, "", Long.MIN_VALUE), true,
|
|
||||||
new PrimaryTriple(this, "[", Long.MIN_VALUE), false)
|
|
||||||
.values()) {
|
|
||||||
result.add(val);
|
|
||||||
}
|
|
||||||
for (DBTraceObjectValue val : manager.valuesByTriple
|
|
||||||
.tail(new PrimaryTriple(this, "\\", Long.MIN_VALUE), true)
|
|
||||||
.values()) {
|
|
||||||
if (val.getParent() != this) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
result.add(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void collectNonRangedElements(List<? super DBTraceObjectValue> result) {
|
|
||||||
for (DBTraceObjectValue val : manager.valuesByTriple
|
|
||||||
.sub(new PrimaryTriple(this, "[", Long.MIN_VALUE), true,
|
|
||||||
new PrimaryTriple(this, "\\", Long.MIN_VALUE), false)
|
|
||||||
.values()) {
|
|
||||||
result.add(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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<? super DBTraceObjectAddressRangeValue> result) {
|
|
||||||
for (DBTraceAddressSnapRangePropertyMapSpace<DBTraceObjectAddressRangeValue, ?> space //
|
|
||||||
: manager.rangeValueMap.getActiveMemorySpaces()) {
|
|
||||||
for (DBTraceObjectAddressRangeValue val : space.values()) {
|
|
||||||
if (val.getParent() != this) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
result.add(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void collectRangedAttributes(
|
|
||||||
Collection<? super DBTraceObjectAddressRangeValue> result) {
|
|
||||||
for (DBTraceAddressSnapRangePropertyMapSpace<DBTraceObjectAddressRangeValue, ?> space //
|
|
||||||
: manager.rangeValueMap.getActiveMemorySpaces()) {
|
|
||||||
for (DBTraceObjectAddressRangeValue val : space.values()) {
|
|
||||||
if (val.getParent() != this) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!PathUtils.isName(val.getEntryKey())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
result.add(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void collectRangedElements(
|
|
||||||
Collection<? super DBTraceObjectAddressRangeValue> result) {
|
|
||||||
for (DBTraceAddressSnapRangePropertyMapSpace<DBTraceObjectAddressRangeValue, ?> space //
|
|
||||||
: manager.rangeValueMap.getActiveMemorySpaces()) {
|
|
||||||
for (DBTraceObjectAddressRangeValue val : space.values()) {
|
|
||||||
if (val.getParent() != this) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!PathUtils.isIndex(val.getEntryKey())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
result.add(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean doHasAnyRangedValues() {
|
|
||||||
for (DBTraceAddressSnapRangePropertyMapSpace<DBTraceObjectAddressRangeValue, ?> space //
|
|
||||||
: manager.rangeValueMap.getActiveMemorySpaces()) {
|
|
||||||
for (DBTraceObjectAddressRangeValue val : space.values()) {
|
|
||||||
if (val.getParent() == this) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Collection<? extends InternalTraceObjectValue> doGetValues() {
|
|
||||||
List<InternalTraceObjectValue> result = new ArrayList<>();
|
|
||||||
collectNonRangedValues(result);
|
|
||||||
collectRangedValues(result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean doHasAnyValues() {
|
protected boolean doHasAnyValues() {
|
||||||
return doHasAnyNonRangedValues() || doHasAnyRangedValues();
|
return !manager.valueMap.reduce(TraceObjectValueQuery.values(this, Lifespan.ALL))
|
||||||
|
.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Collection<? extends InternalTraceObjectValue> doGetValues(Lifespan lifespan) {
|
||||||
|
return manager.valueMap.reduce(TraceObjectValueQuery.values(this, lifespan)).values();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Collection<? extends InternalTraceObjectValue> cachedDoGetValues(Lifespan lifespan) {
|
||||||
|
if (Long.compareUnsigned(lifespan.lmax() - lifespan.lmin(), 10) > 0) {
|
||||||
|
return List.copyOf(doGetValues(lifespan));
|
||||||
|
}
|
||||||
|
if (cachedLifespanValues == null || !cachedLifespanValues.span.encloses(lifespan)) {
|
||||||
|
// Expand the query to take advantage of spatial locality (in the time dimension)
|
||||||
|
long min = lifespan.lmin() - 10;
|
||||||
|
if (min > lifespan.lmin()) {
|
||||||
|
min = Lifespan.ALL.lmin();
|
||||||
|
}
|
||||||
|
long max = lifespan.lmax() + 10;
|
||||||
|
if (max < lifespan.lmax()) {
|
||||||
|
max = Lifespan.ALL.lmax();
|
||||||
|
}
|
||||||
|
Lifespan expanded = Lifespan.span(min, max);
|
||||||
|
cachedLifespanValues =
|
||||||
|
new CachedLifespanValues(expanded, new HashSet<>(doGetValues(expanded)));
|
||||||
|
}
|
||||||
|
return cachedLifespanValues.values.stream()
|
||||||
|
.filter(v -> v.getLifespan().intersects(lifespan))
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean doHasAnyParents() {
|
protected boolean doHasAnyParents() {
|
||||||
return manager.valuesByChild.containsKey(this);
|
return !manager.valueMap.reduce(TraceObjectValueQuery.parents(this, Lifespan.ALL))
|
||||||
|
.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean doIsConnected() {
|
protected boolean doIsConnected() {
|
||||||
|
@ -465,42 +402,28 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<? extends InternalTraceObjectValue> getValues() {
|
public Collection<? extends InternalTraceObjectValue> getValues(Lifespan lifespan) {
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
return doGetValues();
|
return cachedDoGetValues(lifespan);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Collection<? extends InternalTraceObjectValue> doGetElements() {
|
|
||||||
List<InternalTraceObjectValue> result = new ArrayList<>();
|
|
||||||
collectNonRangedElements(result);
|
|
||||||
collectRangedElements(result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<? extends InternalTraceObjectValue> getElements() {
|
public Collection<? extends InternalTraceObjectValue> getElements(Lifespan lifespan) {
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
return getValues(lifespan).stream()
|
||||||
return doGetElements();
|
.filter(v -> PathUtils.isIndex(v.getEntryKey()))
|
||||||
}
|
.toList();
|
||||||
}
|
|
||||||
|
|
||||||
protected Collection<? extends InternalTraceObjectValue> doGetAttributes() {
|
|
||||||
List<InternalTraceObjectValue> result = new ArrayList<>();
|
|
||||||
collectNonRangedAttributes(result);
|
|
||||||
collectRangedAttributes(result);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<? extends InternalTraceObjectValue> getAttributes() {
|
public Collection<? extends InternalTraceObjectValue> getAttributes(Lifespan lifespan) {
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
return getValues(lifespan).stream()
|
||||||
return doGetAttributes();
|
.filter(v -> PathUtils.isName(v.getEntryKey()))
|
||||||
}
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doCheckConflicts(Lifespan span, String key, Object value) {
|
protected void doCheckConflicts(Lifespan span, String key, Object value) {
|
||||||
for (InternalTraceObjectValue val : doGetValues(span, key)) {
|
for (InternalTraceObjectValue val : doGetValues(span, key, true)) {
|
||||||
if (!Objects.equals(value, val.getValue())) {
|
if (!Objects.equals(value, val.getValue())) {
|
||||||
throw new DuplicateKeyException(key);
|
throw new DuplicateKeyException(key);
|
||||||
}
|
}
|
||||||
|
@ -510,7 +433,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
protected Lifespan doAdjust(Lifespan span, String key, Object value) {
|
protected Lifespan doAdjust(Lifespan span, String key, Object value) {
|
||||||
// Ordered by min, so I only need to consider the first conflict
|
// Ordered by min, so I only need to consider the first conflict
|
||||||
// If start is contained in an entry, assume the user means to overwrite it.
|
// If start is contained in an entry, assume the user means to overwrite it.
|
||||||
for (InternalTraceObjectValue val : doGetValues(span, key)) {
|
for (InternalTraceObjectValue val : doGetValues(span, key, true)) {
|
||||||
if (Objects.equals(value, val.getValue())) {
|
if (Objects.equals(value, val.getValue())) {
|
||||||
continue; // not a conflict
|
continue; // not a conflict
|
||||||
}
|
}
|
||||||
|
@ -523,154 +446,50 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
return span;
|
return span;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Could/should this return Stream instead?
|
|
||||||
protected Collection<? extends InternalTraceObjectValue> doGetValues(Lifespan span,
|
protected Collection<? extends InternalTraceObjectValue> doGetValues(Lifespan span,
|
||||||
String key) {
|
String key, boolean forward) {
|
||||||
return doGetValues(span.lmin(), span.lmax(), key);
|
return manager.valueMap
|
||||||
}
|
.reduce(TraceObjectValueQuery.values(this, key, key, span)
|
||||||
|
.starting(forward ? SnapDimension.FORWARD : SnapDimension.BACKWARD))
|
||||||
/**
|
.orderedValues();
|
||||||
* The implementation of {@link #getValues(Lifespan, String)}
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* This collects entries ordered by min snap
|
|
||||||
*
|
|
||||||
* @param lower the lower snap
|
|
||||||
* @param upper the upper snap
|
|
||||||
* @param key the key
|
|
||||||
* @return the collection of values
|
|
||||||
*/
|
|
||||||
protected Collection<? extends InternalTraceObjectValue> doGetValues(long lower, long upper,
|
|
||||||
String key) {
|
|
||||||
// Collect triplet-indexed values
|
|
||||||
Set<InternalTraceObjectValue> result = new LinkedHashSet<>();
|
|
||||||
PrimaryTriple min = new PrimaryTriple(this, key, lower);
|
|
||||||
PrimaryTriple max = new PrimaryTriple(this, key, upper);
|
|
||||||
DBTraceObjectValue floor = manager.valuesByTriple.floorValue(min);
|
|
||||||
if (floor != null && floor.getParent() == this && key.equals(floor.getEntryKey()) &&
|
|
||||||
floor.getLifespan().contains(lower)) {
|
|
||||||
result.add(floor);
|
|
||||||
}
|
|
||||||
for (DBTraceObjectValue val : manager.valuesByTriple.sub(min, true, max, true)
|
|
||||||
.values()) {
|
|
||||||
result.add(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect R*-Tree-indexed values
|
|
||||||
for (DBTraceAddressSnapRangePropertyMapSpace<DBTraceObjectAddressRangeValue, DBTraceObjectAddressRangeValue> space : manager.rangeValueMap
|
|
||||||
.getActiveMemorySpaces()) {
|
|
||||||
AddressSpace as = space.getAddressSpace();
|
|
||||||
|
|
||||||
for (DBTraceObjectAddressRangeValue val : manager.rangeValueMap
|
|
||||||
.reduce(TraceAddressSnapRangeQuery
|
|
||||||
.intersecting(as.getMinAddress(), as.getMaxAddress(), lower, upper))
|
|
||||||
.values()) {
|
|
||||||
if (val.getParent() != this) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!key.equals(val.getEntryKey())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
result.add(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.stream()
|
|
||||||
.sorted(Comparator.comparing(v -> v.getMinSnap()))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<? extends InternalTraceObjectValue> getValues(Lifespan span, String key) {
|
public Collection<? extends InternalTraceObjectValue> getValues(Lifespan span, String key) {
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
return doGetValues(span, key);
|
return doGetValues(span, key, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected DBTraceObjectValue doGetNonRangedValue(long snap, String key) {
|
|
||||||
DBTraceObjectValue floor =
|
|
||||||
manager.valuesByTriple.floorValue(new PrimaryTriple(this, key, snap));
|
|
||||||
if (floor == null || floor.getParent() != this || !key.equals(floor.getEntryKey()) ||
|
|
||||||
!floor.getLifespan().contains(snap)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return floor;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Stream<DBTraceObjectValue> doGetOrderedNonRangedValues(Lifespan span, String key,
|
|
||||||
boolean forward) {
|
|
||||||
DBCachedObjectIndex<PrimaryTriple, DBTraceObjectValue> sub = manager.valuesByTriple.sub(
|
|
||||||
new PrimaryTriple(this, key, span.lmin()), true,
|
|
||||||
new PrimaryTriple(this, key, span.lmax()), true);
|
|
||||||
Spliterator<DBTraceObjectValue> spliterator = (forward ? sub : sub.descending())
|
|
||||||
.values()
|
|
||||||
.spliterator();
|
|
||||||
return StreamSupport.stream(spliterator, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected DBTraceObjectAddressRangeValue doGetRangedValue(long snap, String key) {
|
|
||||||
for (DBTraceAddressSnapRangePropertyMapSpace<DBTraceObjectAddressRangeValue, //
|
|
||||||
DBTraceObjectAddressRangeValue> space : manager.rangeValueMap
|
|
||||||
.getActiveMemorySpaces()) {
|
|
||||||
AddressSpace as = space.getAddressSpace();
|
|
||||||
for (DBTraceObjectAddressRangeValue val : space
|
|
||||||
.reduce(TraceAddressSnapRangeQuery.atSnap(snap, as))
|
|
||||||
.values()) {
|
|
||||||
if (val.getParent() == this && key.equals(val.getEntryKey())) {
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Stream<DBTraceObjectAddressRangeValue> doGetOrderedRangedValues(Lifespan span,
|
|
||||||
String key, boolean forward) {
|
|
||||||
Rectangle2DDirection dir = forward
|
|
||||||
? Rectangle2DDirection.BOTTOMMOST
|
|
||||||
: Rectangle2DDirection.TOPMOST;
|
|
||||||
List<Stream<DBTraceObjectAddressRangeValue>> streams = manager.rangeValueMap
|
|
||||||
.getActiveMemorySpaces()
|
|
||||||
.stream()
|
|
||||||
.map(s -> StreamSupport.stream(s
|
|
||||||
.reduce(TraceAddressSnapRangeQuery.intersecting(span, s.getAddressSpace())
|
|
||||||
.starting(dir))
|
|
||||||
.orderedValues()
|
|
||||||
.spliterator(),
|
|
||||||
false).filter(v -> key.equals(v.getEntryKey()) && this == v.getParent()))
|
|
||||||
.toList();
|
|
||||||
Comparator<Long> order = forward ? Comparator.naturalOrder() : Comparator.reverseOrder();
|
|
||||||
Comparator<DBTraceObjectAddressRangeValue> comparator =
|
|
||||||
Comparator.comparing(v -> v.getMinSnap(), order);
|
|
||||||
return StreamUtils.merge(streams, comparator);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InternalTraceObjectValue getValue(long snap, String key) {
|
public InternalTraceObjectValue getValue(long snap, String key) {
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
DBTraceObjectValue nrVal = doGetNonRangedValue(snap, key);
|
InternalTraceObjectValue cached = valueCache.get(key);
|
||||||
if (nrVal != null) {
|
if (cached != null && !cached.isDeleted() && cached.getLifespan().contains(snap)) {
|
||||||
return nrVal;
|
return cached;
|
||||||
}
|
}
|
||||||
return doGetRangedValue(snap, key);
|
Long nullSnap = nullCache.get(key);
|
||||||
|
if (nullSnap != null && nullSnap.longValue() == snap) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
InternalTraceObjectValue found = manager.valueMap
|
||||||
|
.reduce(TraceObjectValueQuery.values(this, key, key, Lifespan.at(snap)))
|
||||||
|
.firstValue();
|
||||||
|
if (found == null) {
|
||||||
|
nullCache.put(key, snap);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
valueCache.put(key, found);
|
||||||
|
}
|
||||||
|
return found;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Stream<InternalTraceObjectValue> doGetOrderedValues(Lifespan span, String key,
|
|
||||||
boolean forward) {
|
|
||||||
Stream<DBTraceObjectValue> nrVals = doGetOrderedNonRangedValues(span, key, forward);
|
|
||||||
Stream<DBTraceObjectAddressRangeValue> rVals = doGetOrderedRangedValues(span, key, forward);
|
|
||||||
Comparator<Long> order = forward ? Comparator.naturalOrder() : Comparator.reverseOrder();
|
|
||||||
Comparator<InternalTraceObjectValue> comparator =
|
|
||||||
Comparator.comparing(v -> v.getMinSnap(), order);
|
|
||||||
return StreamUtils.merge(List.of(nrVals, rVals), comparator);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<? extends InternalTraceObjectValue> getOrderedValues(Lifespan span, String key,
|
public Stream<? extends InternalTraceObjectValue> getOrderedValues(Lifespan span, String key,
|
||||||
boolean forward) {
|
boolean forward) {
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
return doGetOrderedValues(span, key, forward);
|
return doGetValues(span, key, forward).stream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -762,6 +581,10 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
|
|
||||||
protected InternalTraceObjectValue doCreateValue(Lifespan lifespan, String key,
|
protected InternalTraceObjectValue doCreateValue(Lifespan lifespan, String key,
|
||||||
Object value) {
|
Object value) {
|
||||||
|
Long nullSnap = nullCache.get(key);
|
||||||
|
if (nullSnap != null && lifespan.contains(nullSnap)) {
|
||||||
|
nullCache.remove(key);
|
||||||
|
}
|
||||||
return manager.doCreateValue(lifespan, this, key, value);
|
return manager.doCreateValue(lifespan, this, key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -784,7 +607,8 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
@Override
|
@Override
|
||||||
protected Iterable<InternalTraceObjectValue> getIntersecting(Long lower,
|
protected Iterable<InternalTraceObjectValue> getIntersecting(Long lower,
|
||||||
Long upper) {
|
Long upper) {
|
||||||
return Collections.unmodifiableCollection(doGetValues(lower, upper, key));
|
return Collections.unmodifiableCollection(
|
||||||
|
doGetValues(Lifespan.span(lower, upper), key, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -930,10 +754,10 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doDeleteReferringValues() {
|
protected void doDeleteReferringValues() {
|
||||||
for (InternalTraceObjectValue child : getValues()) {
|
for (InternalTraceObjectValue child : getValues(Lifespan.ALL)) {
|
||||||
child.doDeleteAndEmit();
|
child.doDeleteAndEmit();
|
||||||
}
|
}
|
||||||
for (DBTraceObjectValue parent : getParents()) {
|
for (InternalTraceObjectValue parent : getParents(Lifespan.ALL)) {
|
||||||
parent.doDeleteAndEmit();
|
parent.doDeleteAndEmit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -957,7 +781,38 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Throwable t) {
|
catch (Throwable t) {
|
||||||
Msg.error(this, "Error while translating event " + rec + " for interface " + iface);
|
Msg.error(this,
|
||||||
|
"Error while translating event " + rec + " for interface " + iface + ":" + t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void notifyValueCreated(InternalTraceObjectValue value) {
|
||||||
|
if (cachedLifespanValues != null) {
|
||||||
|
if (cachedLifespanValues.span.intersects(value.getLifespan())) {
|
||||||
|
cachedLifespanValues.values.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void notifyValueDeleted(InternalTraceObjectValue value) {
|
||||||
|
if (cachedLifespanValues != null) {
|
||||||
|
cachedLifespanValues.values.remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void notifyParentValueCreated(InternalTraceObjectValue parent) {
|
||||||
|
if (cachedLife != null && parent.isCanonical()) {
|
||||||
|
synchronized (cachedLife) {
|
||||||
|
cachedLife.add(parent.getLifespan());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void notifyParentValueDeleted(InternalTraceObjectValue parent) {
|
||||||
|
if (cachedLife != null && parent.isCanonical()) {
|
||||||
|
synchronized (cachedLife) {
|
||||||
|
cachedLife.remove(parent.getLifespan());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,202 +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 db.DBRecord;
|
|
||||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree;
|
|
||||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData;
|
|
||||||
import ghidra.trace.database.target.DBTraceObjectValue.DBTraceObjectDBFieldCodec;
|
|
||||||
import ghidra.trace.model.Lifespan;
|
|
||||||
import ghidra.trace.model.Trace;
|
|
||||||
import ghidra.trace.model.target.TraceObjectKeyPath;
|
|
||||||
import ghidra.trace.model.target.TraceObjectValue;
|
|
||||||
import ghidra.trace.util.TraceAddressSpace;
|
|
||||||
import ghidra.util.LockHold;
|
|
||||||
import ghidra.util.database.DBCachedObjectStore;
|
|
||||||
import ghidra.util.database.DBObjectColumn;
|
|
||||||
import ghidra.util.database.annot.*;
|
|
||||||
|
|
||||||
@DBAnnotatedObjectInfo(version = 0)
|
|
||||||
public class DBTraceObjectAddressRangeValue
|
|
||||||
extends AbstractDBTraceAddressSnapRangePropertyMapData<DBTraceObjectAddressRangeValue>
|
|
||||||
implements InternalTraceObjectValue {
|
|
||||||
public static final String TABLE_NAME = "ObjectRangeValue";
|
|
||||||
|
|
||||||
static final String PARENT_COLUMN_NAME = "ValueParent";
|
|
||||||
static final String KEY_COLUMN_NAME = "ValueKey";
|
|
||||||
static final String TYPE_COLUMN_NAME = "IsAddress";
|
|
||||||
|
|
||||||
@DBAnnotatedColumn(PARENT_COLUMN_NAME)
|
|
||||||
static DBObjectColumn PARENT_COLUMN;
|
|
||||||
@DBAnnotatedColumn(KEY_COLUMN_NAME)
|
|
||||||
static DBObjectColumn KEY_COLUMN;
|
|
||||||
@DBAnnotatedColumn(TYPE_COLUMN_NAME)
|
|
||||||
static DBObjectColumn TYPE_COLUMN;
|
|
||||||
|
|
||||||
@DBAnnotatedField(
|
|
||||||
column = PARENT_COLUMN_NAME,
|
|
||||||
indexed = true,
|
|
||||||
codec = DBTraceObjectDBFieldCodec.class)
|
|
||||||
private DBTraceObject parent;
|
|
||||||
@DBAnnotatedField(column = KEY_COLUMN_NAME)
|
|
||||||
private String entryKey;
|
|
||||||
@DBAnnotatedField(column = TYPE_COLUMN_NAME)
|
|
||||||
private boolean isAddress;
|
|
||||||
|
|
||||||
protected final DBTraceObjectManager manager;
|
|
||||||
|
|
||||||
public DBTraceObjectAddressRangeValue(DBTraceObjectManager manager,
|
|
||||||
DBTraceAddressSnapRangePropertyMapTree<DBTraceObjectAddressRangeValue, ?> tree,
|
|
||||||
DBCachedObjectStore<?> store, DBRecord record) {
|
|
||||||
super(tree, store, record);
|
|
||||||
this.manager = manager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return getClass().getSimpleName() + ": parent=" + parent + ", key=" + entryKey +
|
|
||||||
", lifespan=" + getLifespan() + ", value=" + getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setRecordValue(DBTraceObjectAddressRangeValue value) {
|
|
||||||
// Nothing to do. I am the value
|
|
||||||
assert value == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TraceAddressSpace getTraceAddressSpace() {
|
|
||||||
return tree.getMapSpace();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected DBTraceObjectAddressRangeValue getRecordValue() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set(DBTraceObject parent, String key, boolean isAddress) {
|
|
||||||
this.parent = parent;
|
|
||||||
this.entryKey = key;
|
|
||||||
this.isAddress = isAddress;
|
|
||||||
update(PARENT_COLUMN, KEY_COLUMN, TYPE_COLUMN);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Trace getTrace() {
|
|
||||||
return manager.trace;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DBTraceObjectManager getManager() {
|
|
||||||
return manager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DBTraceObject getParent() {
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getEntryKey() {
|
|
||||||
return entryKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getValue() {
|
|
||||||
if (isAddress) {
|
|
||||||
return getRange().getMinAddress();
|
|
||||||
}
|
|
||||||
return getRange();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DBTraceObject getChild() {
|
|
||||||
throw new ClassCastException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isObject() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DBTraceObject getChildOrNull() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TraceObjectKeyPath getCanonicalPath() {
|
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
|
||||||
return parent.getCanonicalPath().extend(entryKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCanonical() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doSetLifespan(Lifespan lifespan) {
|
|
||||||
super.doSetLifespan(lifespan);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setMinSnap(long minSnap) {
|
|
||||||
try (LockHold hold = manager.trace.lockWrite()) {
|
|
||||||
setLifespan(Lifespan.span(minSnap, getY2()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getMinSnap() {
|
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
|
||||||
return getY1();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setMaxSnap(long maxSnap) {
|
|
||||||
try (LockHold hold = manager.trace.lockWrite()) {
|
|
||||||
setLifespan(Lifespan.span(getY1(), maxSnap));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getMaxSnap() {
|
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
|
||||||
return getY2();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doDelete() {
|
|
||||||
manager.rangeValueMap.deleteData(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void delete() {
|
|
||||||
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
|
||||||
doDeleteAndEmit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TraceObjectValue truncateOrDelete(Lifespan span) {
|
|
||||||
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
|
||||||
return doTruncateOrDeleteAndEmitLifeChange(span);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
/* ###
|
||||||
|
* 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.lang.reflect.Field;
|
||||||
|
|
||||||
|
import db.DBRecord;
|
||||||
|
import db.LongField;
|
||||||
|
import ghidra.util.database.DBAnnotatedObject;
|
||||||
|
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
|
||||||
|
|
||||||
|
public class DBTraceObjectDBFieldCodec<OV extends DBAnnotatedObject & InternalTraceObjectValue>
|
||||||
|
extends AbstractDBFieldCodec<DBTraceObject, OV, LongField> {
|
||||||
|
public DBTraceObjectDBFieldCodec(Class<OV> objectType, Field field, int column) {
|
||||||
|
super(DBTraceObject.class, objectType, LongField.class, field, column);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static long encode(DBTraceObject value) {
|
||||||
|
return value == null ? -1 : value.getKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static DBTraceObject decode(InternalTraceObjectValue ent, long enc) {
|
||||||
|
return enc == -1 ? null : ent.getManager().getObjectById(enc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void store(DBTraceObject value, LongField f) {
|
||||||
|
f.setLongValue(encode(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doStore(OV obj, DBRecord record)
|
||||||
|
throws IllegalArgumentException, IllegalAccessException {
|
||||||
|
record.setLongValue(column, encode(getValue(obj)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doLoad(OV obj, DBRecord record)
|
||||||
|
throws IllegalArgumentException, IllegalAccessException {
|
||||||
|
setValue(obj, decode(obj, record.getLongValue(column)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,8 +15,12 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.trace.database.target;
|
package ghidra.trace.database.target;
|
||||||
|
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.address.AddressRange;
|
||||||
|
import ghidra.trace.database.space.DBTraceSpaceKey.DefaultDBTraceSpaceKey;
|
||||||
import ghidra.trace.model.Lifespan;
|
import ghidra.trace.model.Lifespan;
|
||||||
import ghidra.trace.model.Lifespan.*;
|
import ghidra.trace.model.Lifespan.DefaultLifeSet;
|
||||||
|
import ghidra.trace.model.Lifespan.LifeSet;
|
||||||
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
||||||
import ghidra.trace.model.TraceUniqueObject;
|
import ghidra.trace.model.TraceUniqueObject;
|
||||||
import ghidra.trace.model.target.*;
|
import ghidra.trace.model.target.*;
|
||||||
|
@ -173,9 +177,14 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
|
||||||
|
|
||||||
static TraceAddressSpace spaceForValue(TraceObject object, long snap, String key) {
|
static TraceAddressSpace spaceForValue(TraceObject object, long snap, String key) {
|
||||||
TraceObjectValue val = object.getAttribute(snap, key);
|
TraceObjectValue val = object.getAttribute(snap, key);
|
||||||
if (val instanceof DBTraceObjectAddressRangeValue) {
|
if (val == null) {
|
||||||
DBTraceObjectAddressRangeValue addrVal = (DBTraceObjectAddressRangeValue) val;
|
return null;
|
||||||
return addrVal.getTraceAddressSpace();
|
}
|
||||||
|
if (val.getValue() instanceof Address address) {
|
||||||
|
return new DefaultDBTraceSpaceKey(null, address.getAddressSpace(), 0);
|
||||||
|
}
|
||||||
|
if (val.getValue() instanceof AddressRange range) {
|
||||||
|
return new DefaultDBTraceSpaceKey(null, range.getAddressSpace(), 0);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@ import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.apache.commons.collections4.IteratorUtils;
|
|
||||||
import org.jdom.JDOMException;
|
import org.jdom.JDOMException;
|
||||||
|
|
||||||
import db.*;
|
import db.*;
|
||||||
|
@ -38,13 +37,13 @@ import ghidra.program.model.lang.Language;
|
||||||
import ghidra.trace.database.DBTrace;
|
import ghidra.trace.database.DBTrace;
|
||||||
import ghidra.trace.database.DBTraceManager;
|
import ghidra.trace.database.DBTraceManager;
|
||||||
import ghidra.trace.database.breakpoint.DBTraceObjectBreakpointLocation;
|
import ghidra.trace.database.breakpoint.DBTraceObjectBreakpointLocation;
|
||||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMap;
|
|
||||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery;
|
|
||||||
import ghidra.trace.database.module.TraceObjectSection;
|
import ghidra.trace.database.module.TraceObjectSection;
|
||||||
import ghidra.trace.database.target.DBTraceObjectValue.PrimaryTriple;
|
import ghidra.trace.database.target.DBTraceObjectValueRStarTree.DBTraceObjectValueMap;
|
||||||
|
import ghidra.trace.database.target.ValueSpace.EntryKeyDimension;
|
||||||
import ghidra.trace.database.target.visitors.SuccessorsRelativeVisitor;
|
import ghidra.trace.database.target.visitors.SuccessorsRelativeVisitor;
|
||||||
import ghidra.trace.database.thread.DBTraceObjectThread;
|
import ghidra.trace.database.thread.DBTraceObjectThread;
|
||||||
import ghidra.trace.model.*;
|
import ghidra.trace.model.Lifespan;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
||||||
import ghidra.trace.model.breakpoint.*;
|
import ghidra.trace.model.breakpoint.*;
|
||||||
import ghidra.trace.model.memory.*;
|
import ghidra.trace.model.memory.*;
|
||||||
|
@ -69,6 +68,7 @@ import ghidra.util.exception.VersionException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager {
|
public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager {
|
||||||
|
private static final int OBJECTS_CONTAINING_CACHE_SIZE = 100;
|
||||||
|
|
||||||
public static class DBTraceObjectSchemaDBFieldCodec extends
|
public static class DBTraceObjectSchemaDBFieldCodec extends
|
||||||
AbstractDBFieldCodec<SchemaContext, DBTraceObjectSchemaEntry, StringField> {
|
AbstractDBFieldCodec<SchemaContext, DBTraceObjectSchemaEntry, StringField> {
|
||||||
|
@ -148,24 +148,33 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
record ObjectsContainingKey(long snap, Address address, String key,
|
||||||
|
Class<? extends TraceObjectInterface> iface) {
|
||||||
|
}
|
||||||
|
|
||||||
protected final ReadWriteLock lock;
|
protected final ReadWriteLock lock;
|
||||||
protected final DBTrace trace;
|
protected final DBTrace trace;
|
||||||
|
|
||||||
protected final DBCachedObjectStore<DBTraceObjectSchemaEntry> schemaStore;
|
protected final DBCachedObjectStore<DBTraceObjectSchemaEntry> schemaStore;
|
||||||
protected final DBCachedObjectStore<DBTraceObject> objectStore;
|
protected final DBCachedObjectStore<DBTraceObject> objectStore;
|
||||||
protected final DBCachedObjectStore<DBTraceObjectValue> valueStore;
|
protected final DBTraceObjectValueRStarTree valueTree;
|
||||||
|
protected final DBTraceObjectValueMap valueMap;
|
||||||
protected final DBTraceAddressSnapRangePropertyMap<DBTraceObjectAddressRangeValue, DBTraceObjectAddressRangeValue> rangeValueMap;
|
|
||||||
|
|
||||||
protected final DBCachedObjectIndex<TraceObjectKeyPath, DBTraceObject> objectsByPath;
|
protected final DBCachedObjectIndex<TraceObjectKeyPath, DBTraceObject> objectsByPath;
|
||||||
protected final DBCachedObjectIndex<PrimaryTriple, DBTraceObjectValue> valuesByTriple;
|
|
||||||
protected final DBCachedObjectIndex<DBTraceObject, DBTraceObjectValue> valuesByChild;
|
|
||||||
|
|
||||||
protected final Collection<TraceObject> objectsView;
|
protected final Collection<TraceObject> objectsView;
|
||||||
protected final Collection<TraceObjectValue> valuesView;
|
protected final Collection<TraceObjectValue> valuesView;
|
||||||
|
|
||||||
protected TargetObjectSchema rootSchema;
|
protected TargetObjectSchema rootSchema;
|
||||||
|
|
||||||
|
protected final Map<ObjectsContainingKey, Collection<?>> objectsContainingCache =
|
||||||
|
new LinkedHashMap<>() {
|
||||||
|
protected boolean removeEldestEntry(
|
||||||
|
Map.Entry<ObjectsContainingKey, Collection<?>> eldest) {
|
||||||
|
return size() > OBJECTS_CONTAINING_CACHE_SIZE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public DBTraceObjectManager(DBHandle dbh, DBOpenMode openMode, ReadWriteLock lock,
|
public DBTraceObjectManager(DBHandle dbh, DBOpenMode openMode, ReadWriteLock lock,
|
||||||
TaskMonitor monitor, Language baseLanguage, DBTrace trace)
|
TaskMonitor monitor, Language baseLanguage, DBTrace trace)
|
||||||
throws IOException, VersionException {
|
throws IOException, VersionException {
|
||||||
|
@ -178,33 +187,17 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
loadRootSchema();
|
loadRootSchema();
|
||||||
objectStore = factory.getOrCreateCachedStore(DBTraceObject.TABLE_NAME,
|
objectStore = factory.getOrCreateCachedStore(DBTraceObject.TABLE_NAME,
|
||||||
DBTraceObject.class, (s, r) -> new DBTraceObject(this, s, r), true);
|
DBTraceObject.class, (s, r) -> new DBTraceObject(this, s, r), true);
|
||||||
valueStore = factory.getOrCreateCachedStore(DBTraceObjectValue.TABLE_NAME,
|
|
||||||
DBTraceObjectValue.class, (s, r) -> new DBTraceObjectValue(this, s, r), true);
|
valueTree = new DBTraceObjectValueRStarTree(this, factory,
|
||||||
rangeValueMap = new DBTraceAddressSnapRangePropertyMap<>(
|
DBTraceObjectValueData.TABLE_NAME, ValueSpace.INSTANCE, DBTraceObjectValueData.class,
|
||||||
DBTraceObjectAddressRangeValue.TABLE_NAME, dbh, openMode, lock, monitor, baseLanguage,
|
DBTraceObjectValueNode.class, false, 50);
|
||||||
trace, null, DBTraceObjectAddressRangeValue.class,
|
valueMap = valueTree.asSpatialMap();
|
||||||
(t, s, r) -> new DBTraceObjectAddressRangeValue(this, t, s, r));
|
|
||||||
|
|
||||||
objectsByPath =
|
objectsByPath =
|
||||||
objectStore.getIndex(TraceObjectKeyPath.class, DBTraceObject.PATH_COLUMN);
|
objectStore.getIndex(TraceObjectKeyPath.class, DBTraceObject.PATH_COLUMN);
|
||||||
valuesByTriple =
|
|
||||||
valueStore.getIndex(PrimaryTriple.class, DBTraceObjectValue.TRIPLE_COLUMN);
|
|
||||||
valuesByChild =
|
|
||||||
valueStore.getIndex(DBTraceObject.class, DBTraceObjectValue.CHILD_COLUMN);
|
|
||||||
|
|
||||||
objectsView = Collections.unmodifiableCollection(objectStore.asMap().values());
|
objectsView = Collections.unmodifiableCollection(objectStore.asMap().values());
|
||||||
valuesView = new AbstractCollection<>() {
|
valuesView = Collections.unmodifiableCollection(valueMap.values());
|
||||||
@Override
|
|
||||||
public Iterator<TraceObjectValue> iterator() {
|
|
||||||
return IteratorUtils.chainedIterator(valueStore.asMap().values().iterator(),
|
|
||||||
rangeValueMap.values().iterator());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
return objectStore.getRecordCount() + rangeValueMap.size();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void loadRootSchema() {
|
protected void loadRootSchema() {
|
||||||
|
@ -225,8 +218,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
@Override
|
@Override
|
||||||
public void invalidateCache(boolean all) {
|
public void invalidateCache(boolean all) {
|
||||||
objectStore.invalidateCache();
|
objectStore.invalidateCache();
|
||||||
valueStore.invalidateCache();
|
valueTree.invalidateCache();
|
||||||
rangeValueMap.invalidateCache(all);
|
|
||||||
schemaStore.invalidateCache();
|
schemaStore.invalidateCache();
|
||||||
loadRootSchema();
|
loadRootSchema();
|
||||||
}
|
}
|
||||||
|
@ -281,24 +273,22 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
|
|
||||||
protected InternalTraceObjectValue doCreateValue(Lifespan lifespan,
|
protected InternalTraceObjectValue doCreateValue(Lifespan lifespan,
|
||||||
DBTraceObject parent, String key, Object value) {
|
DBTraceObject parent, String key, Object value) {
|
||||||
if (value instanceof AddressRange) {
|
InternalTraceObjectValue entry = valueTree.asSpatialMap()
|
||||||
DBTraceObjectAddressRangeValue entry = rangeValueMap
|
.put(new ImmutableValueShape(parent, value, key, lifespan), null);
|
||||||
.put(new ImmutableTraceAddressSnapRange((AddressRange) value, lifespan), null);
|
if (value instanceof DBTraceObject child) {
|
||||||
entry.set(parent, key, false);
|
child.notifyParentValueCreated(entry);
|
||||||
emitValueCreated(parent, entry);
|
|
||||||
return entry;
|
|
||||||
}
|
}
|
||||||
else if (value instanceof Address) {
|
else {
|
||||||
Address address = (Address) value;
|
entry.doSetPrimitive(value);
|
||||||
AddressRange singleton = new AddressRangeImpl(address, address);
|
|
||||||
DBTraceObjectAddressRangeValue entry = rangeValueMap
|
|
||||||
.put(new ImmutableTraceAddressSnapRange(singleton, lifespan), null);
|
|
||||||
entry.set(parent, key, true);
|
|
||||||
emitValueCreated(parent, entry);
|
|
||||||
return entry;
|
|
||||||
}
|
}
|
||||||
DBTraceObjectValue entry = valueStore.create();
|
|
||||||
entry.set(lifespan, parent, key, value);
|
if (parent != null) { // Root
|
||||||
|
parent.notifyValueCreated(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Perhaps a little drastic
|
||||||
|
invalidateObjectsContainingCache();
|
||||||
|
|
||||||
emitValueCreated(parent, entry);
|
emitValueCreated(parent, entry);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
@ -350,6 +340,12 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DBTraceObjectValueData getRootValue() {
|
||||||
|
try (LockHold hold = trace.lockRead()) {
|
||||||
|
return valueTree.getDataStore().getObjectAt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DBTraceObject getRootObject() {
|
public DBTraceObject getRootObject() {
|
||||||
return getObjectById(0);
|
return getObjectById(0);
|
||||||
|
@ -381,7 +377,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
public Stream<? extends TraceObjectValPath> getValuePaths(Lifespan span,
|
public Stream<? extends TraceObjectValPath> getValuePaths(Lifespan span,
|
||||||
PathPredicates predicates) {
|
PathPredicates predicates) {
|
||||||
try (LockHold hold = trace.lockRead()) {
|
try (LockHold hold = trace.lockRead()) {
|
||||||
DBTraceObjectValue rootVal = valueStore.getObjectAt(0);
|
DBTraceObjectValueData rootVal = getRootValue();
|
||||||
if (rootVal == null) {
|
if (rootVal == null) {
|
||||||
return Stream.of();
|
return Stream.of();
|
||||||
}
|
}
|
||||||
|
@ -401,14 +397,18 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<? extends TraceObjectValue> getValuesIntersecting(Lifespan span,
|
public Collection<? extends TraceObjectValue> getValuesIntersecting(Lifespan span,
|
||||||
AddressRange range) {
|
AddressRange range, String entryKey) {
|
||||||
return Collections.unmodifiableCollection(
|
return Collections
|
||||||
rangeValueMap.reduce(TraceAddressSnapRangeQuery.intersecting(range, span)).values());
|
.unmodifiableCollection(valueMap.reduce(TraceObjectValueQuery.intersecting(
|
||||||
|
entryKey != null ? entryKey : EntryKeyDimension.INSTANCE.absoluteMin(),
|
||||||
|
entryKey != null ? entryKey : EntryKeyDimension.INSTANCE.absoluteMax(),
|
||||||
|
span, range)).values());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<? extends TraceObjectValue> getValuesAt(long snap, Address address) {
|
public Collection<? extends TraceObjectValue> getValuesAt(long snap, Address address,
|
||||||
|
String entryKey) {
|
||||||
return Collections.unmodifiableCollection(
|
return Collections.unmodifiableCollection(
|
||||||
rangeValueMap.reduce(TraceAddressSnapRangeQuery.at(address, snap)).values());
|
valueMap.reduce(TraceObjectValueQuery.at(entryKey, snap, address)).values());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -445,8 +445,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
try (LockHold hold = trace.lockWrite()) {
|
try (LockHold hold = trace.lockWrite()) {
|
||||||
valueStore.deleteAll();
|
valueMap.clear();
|
||||||
rangeValueMap.clear();
|
|
||||||
objectStore.deleteAll();
|
objectStore.deleteAll();
|
||||||
schemaStore.deleteAll();
|
schemaStore.deleteAll();
|
||||||
rootSchema = null;
|
rootSchema = null;
|
||||||
|
@ -458,8 +457,16 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
object.emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.DELETED, null, object));
|
object.emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.DELETED, null, object));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doDeleteEdge(DBTraceObjectValue edge) {
|
protected void doDeleteEdge(DBTraceObjectValueData edge) {
|
||||||
valueStore.delete(edge);
|
valueTree.doDeleteEntry(edge);
|
||||||
|
|
||||||
|
// TODO: Perhaps a little drastic....
|
||||||
|
/**
|
||||||
|
* NB. An object in one of these queries had to have an edge. Deleting that object will also
|
||||||
|
* delete referring edges, so the cache will get invalidated. No need to repeat in
|
||||||
|
* doDeleteObject.
|
||||||
|
*/
|
||||||
|
invalidateObjectsContainingCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasSchema() {
|
public boolean hasSchema() {
|
||||||
|
@ -508,35 +515,51 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected <I extends TraceObjectInterface> Stream<I> doParentsWithKeyHaving(
|
protected <I extends TraceObjectInterface> Stream<I> doParentsHaving(
|
||||||
Stream<? extends TraceObjectValue> values, String key, Class<I> iface) {
|
Stream<? extends TraceObjectValue> values, Class<I> iface) {
|
||||||
return values.filter(v -> key.equals(v.getEntryKey()))
|
return values.map(v -> v.getParent())
|
||||||
.map(v -> v.getParent())
|
|
||||||
.map(o -> o.queryInterface(iface))
|
.map(o -> o.queryInterface(iface))
|
||||||
.filter(i -> i != null);
|
.filter(i -> i != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void invalidateObjectsContainingCache() {
|
||||||
|
synchronized (objectsContainingCache) {
|
||||||
|
objectsContainingCache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Collection<? extends TraceObjectInterface> doGetObjectsContaining(
|
||||||
|
ObjectsContainingKey key) {
|
||||||
|
return doParentsHaving(getValuesAt(key.snap, key.address, key.key).stream(), key.iface)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public <I extends TraceObjectInterface> Collection<I> getObjectsContaining(long snap,
|
public <I extends TraceObjectInterface> Collection<I> getObjectsContaining(long snap,
|
||||||
Address address, String key, Class<I> iface) {
|
Address address, String key, Class<I> iface) {
|
||||||
try (LockHold hold = trace.lockRead()) {
|
try (LockHold hold = trace.lockRead()) {
|
||||||
return doParentsWithKeyHaving(getValuesAt(snap, address).stream(), key,
|
synchronized (objectsContainingCache) {
|
||||||
iface).collect(Collectors.toSet());
|
return (Collection<I>) objectsContainingCache.computeIfAbsent(
|
||||||
|
new ObjectsContainingKey(snap, address, key, iface),
|
||||||
|
this::doGetObjectsContaining);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public <I extends TraceObjectInterface> I getObjectContaining(long snap, Address address,
|
public <I extends TraceObjectInterface> I getObjectContaining(long snap, Address address,
|
||||||
String key, Class<I> iface) {
|
String key, Class<I> iface) {
|
||||||
try (LockHold hold = trace.lockRead()) {
|
Collection<I> col = getObjectsContaining(snap, address, key, iface);
|
||||||
return doParentsWithKeyHaving(getValuesAt(snap, address).stream(), key,
|
if (col.isEmpty()) {
|
||||||
iface).findAny().orElse(null);
|
return null;
|
||||||
}
|
}
|
||||||
|
return col.iterator().next();
|
||||||
}
|
}
|
||||||
|
|
||||||
public <I extends TraceObjectInterface> Collection<I> getObjectsIntersecting(
|
public <I extends TraceObjectInterface> Collection<I> getObjectsIntersecting(
|
||||||
Lifespan lifespan, AddressRange range, String key, Class<I> iface) {
|
Lifespan lifespan, AddressRange range, String key, Class<I> iface) {
|
||||||
try (LockHold hold = trace.lockRead()) {
|
try (LockHold hold = trace.lockRead()) {
|
||||||
return doParentsWithKeyHaving(getValuesIntersecting(lifespan, range).stream(), key,
|
return doParentsHaving(getValuesIntersecting(lifespan, range, key).stream(), iface)
|
||||||
iface).collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -549,7 +572,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
|
|
||||||
public <I extends TraceObjectInterface> AddressSetView getObjectsAddressSet(long snap,
|
public <I extends TraceObjectInterface> AddressSetView getObjectsAddressSet(long snap,
|
||||||
String key, Class<I> ifaceCls, Predicate<? super I> predicate) {
|
String key, Class<I> ifaceCls, Predicate<? super I> predicate) {
|
||||||
return rangeValueMap.getAddressSetView(Lifespan.at(snap), v -> {
|
return valueMap.getAddressSetView(Lifespan.at(snap), v -> {
|
||||||
if (!key.equals(v.getEntryKey())) {
|
if (!key.equals(v.getEntryKey())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,20 +74,33 @@ public class DBTraceObjectValPath implements TraceObjectValPath {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DBTraceObjectValPath prepend(TraceObjectValue entry) {
|
public DBTraceObjectValPath prepend(TraceObjectValue entry) {
|
||||||
|
if (!entryList.isEmpty() && entry.getTrace() != entryList.get(0).getTrace()) {
|
||||||
|
throw new IllegalArgumentException("All values in path must be from the same trace");
|
||||||
|
}
|
||||||
|
if (!(entry instanceof InternalTraceObjectValue val)) {
|
||||||
|
throw new IllegalArgumentException("Value must be in the database");
|
||||||
|
}
|
||||||
InternalTraceObjectValue[] arr = new InternalTraceObjectValue[1 + entryList.size()];
|
InternalTraceObjectValue[] arr = new InternalTraceObjectValue[1 + entryList.size()];
|
||||||
arr[0] = (DBTraceObjectValue) entry;
|
arr[0] = val;
|
||||||
for (int i = 1; i < arr.length; i++) {
|
for (int i = 1; i < arr.length; i++) {
|
||||||
arr[i] = entryList.get(i - 1);
|
arr[i] = entryList.get(i - 1);
|
||||||
}
|
}
|
||||||
return new DBTraceObjectValPath(Collections.unmodifiableList(Arrays.asList(arr)));
|
return new DBTraceObjectValPath(Collections.unmodifiableList(Arrays.asList(arr)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public DBTraceObjectValPath append(TraceObjectValue entry) {
|
public DBTraceObjectValPath append(TraceObjectValue entry) {
|
||||||
|
if (!entryList.isEmpty() && entry.getTrace() != entryList.get(0).getTrace()) {
|
||||||
|
throw new IllegalArgumentException("All values in path must be from the same trace");
|
||||||
|
}
|
||||||
|
if (!(entry instanceof InternalTraceObjectValue val)) {
|
||||||
|
throw new IllegalArgumentException("Value must be in the database");
|
||||||
|
}
|
||||||
InternalTraceObjectValue[] arr = new InternalTraceObjectValue[1 + entryList.size()];
|
InternalTraceObjectValue[] arr = new InternalTraceObjectValue[1 + entryList.size()];
|
||||||
for (int i = 0; i < arr.length - 1; i++) {
|
for (int i = 0; i < arr.length - 1; i++) {
|
||||||
arr[i] = entryList.get(i);
|
arr[i] = entryList.get(i);
|
||||||
}
|
}
|
||||||
arr[arr.length - 1] = (InternalTraceObjectValue) entry;
|
arr[arr.length - 1] = val;
|
||||||
return new DBTraceObjectValPath(Collections.unmodifiableList(Arrays.asList(arr)));
|
return new DBTraceObjectValPath(Collections.unmodifiableList(Arrays.asList(arr)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,382 +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 java.io.IOException;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.ArrayUtils;
|
|
||||||
|
|
||||||
import db.*;
|
|
||||||
import ghidra.trace.database.target.visitors.TreeTraversal;
|
|
||||||
import ghidra.trace.database.target.visitors.TreeTraversal.Visitor;
|
|
||||||
import ghidra.trace.model.Lifespan;
|
|
||||||
import ghidra.trace.model.Trace;
|
|
||||||
import ghidra.trace.model.target.*;
|
|
||||||
import ghidra.util.LockHold;
|
|
||||||
import ghidra.util.database.*;
|
|
||||||
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
|
|
||||||
import ghidra.util.database.DBCachedObjectStoreFactory.VariantDBFieldCodec;
|
|
||||||
import ghidra.util.database.annot.*;
|
|
||||||
|
|
||||||
@DBAnnotatedObjectInfo(version = 0)
|
|
||||||
public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTraceObjectValue {
|
|
||||||
protected static final String TABLE_NAME = "ObjectValue";
|
|
||||||
|
|
||||||
protected static class PrimaryTriple {
|
|
||||||
private final DBTraceObject parent;
|
|
||||||
private final String key;
|
|
||||||
private final long minSnap;
|
|
||||||
|
|
||||||
protected PrimaryTriple(DBTraceObject parent, String key, long minSnap) {
|
|
||||||
this.parent = parent;
|
|
||||||
this.key = Objects.requireNonNull(key);
|
|
||||||
this.minSnap = minSnap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "<parent=" + parent + ",key=" + key + ",minSnap=" + minSnap + ">";
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrimaryTriple withMinSnap(long minSnap) {
|
|
||||||
return new PrimaryTriple(parent, key, minSnap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class PrimaryTripleDBFieldCodec
|
|
||||||
extends AbstractDBFieldCodec<PrimaryTriple, DBTraceObjectValue, BinaryField> {
|
|
||||||
static final Charset cs = Charset.forName("UTF-8");
|
|
||||||
|
|
||||||
public PrimaryTripleDBFieldCodec(Class<DBTraceObjectValue> objectType, Field field,
|
|
||||||
int column) {
|
|
||||||
super(PrimaryTriple.class, objectType, BinaryField.class, field, column);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static byte[] encode(PrimaryTriple value) {
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] keyBytes = value.key.getBytes(cs);
|
|
||||||
ByteBuffer buf = ByteBuffer.allocate(keyBytes.length + 1 + Long.BYTES * 2);
|
|
||||||
|
|
||||||
buf.putLong(DBTraceObjectDBFieldCodec.encode(value.parent) ^ Long.MIN_VALUE);
|
|
||||||
|
|
||||||
buf.put(keyBytes);
|
|
||||||
buf.put((byte) 0);
|
|
||||||
|
|
||||||
buf.putLong(value.minSnap ^ Long.MIN_VALUE);
|
|
||||||
|
|
||||||
return buf.array();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static PrimaryTriple decode(DBTraceObjectValue ent, byte[] enc) {
|
|
||||||
if (enc == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
ByteBuffer buf = ByteBuffer.wrap(enc);
|
|
||||||
|
|
||||||
DBTraceObject parent =
|
|
||||||
DBTraceObjectDBFieldCodec.decode(ent, buf.getLong() ^ Long.MIN_VALUE);
|
|
||||||
|
|
||||||
int nullPos = ArrayUtils.indexOf(enc, (byte) 0, buf.position());
|
|
||||||
assert nullPos != -1;
|
|
||||||
String key = new String(enc, buf.position(), nullPos - buf.position(), cs);
|
|
||||||
buf.position(nullPos + 1);
|
|
||||||
|
|
||||||
long minSnap = buf.getLong() ^ Long.MIN_VALUE;
|
|
||||||
|
|
||||||
return new PrimaryTriple(parent, key, minSnap);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void store(PrimaryTriple value, BinaryField f) {
|
|
||||||
f.setBinaryData(encode(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStore(DBTraceObjectValue obj, DBRecord record)
|
|
||||||
throws IllegalArgumentException, IllegalAccessException {
|
|
||||||
record.setBinaryData(column, encode(getValue(obj)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doLoad(DBTraceObjectValue obj, DBRecord record)
|
|
||||||
throws IllegalArgumentException, IllegalAccessException {
|
|
||||||
setValue(obj, decode(obj, record.getBinaryData(column)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DBTraceObjectDBFieldCodec<OV extends DBAnnotatedObject & InternalTraceObjectValue>
|
|
||||||
extends AbstractDBFieldCodec<DBTraceObject, OV, LongField> {
|
|
||||||
public DBTraceObjectDBFieldCodec(Class<OV> objectType, Field field,
|
|
||||||
int column) {
|
|
||||||
super(DBTraceObject.class, objectType, LongField.class, field, column);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static long encode(DBTraceObject value) {
|
|
||||||
return value == null ? -1 : value.getKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static DBTraceObject decode(InternalTraceObjectValue ent, long enc) {
|
|
||||||
return enc == -1 ? null : ent.getManager().getObjectById(enc);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void store(DBTraceObject value, LongField f) {
|
|
||||||
f.setLongValue(encode(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStore(OV obj, DBRecord record)
|
|
||||||
throws IllegalArgumentException, IllegalAccessException {
|
|
||||||
record.setLongValue(column, encode(getValue(obj)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doLoad(OV obj, DBRecord record)
|
|
||||||
throws IllegalArgumentException, IllegalAccessException {
|
|
||||||
setValue(obj, decode(obj, record.getLongValue(column)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static final String TRIPLE_COLUMN_NAME = "Triple";
|
|
||||||
static final String MAX_SNAP_COLUMN_NAME = "MaxSnap";
|
|
||||||
static final String CHILD_COLUMN_NAME = "Child";
|
|
||||||
static final String PRIMITIVE_COLUMN_NAME = "Primitive";
|
|
||||||
|
|
||||||
@DBAnnotatedColumn(TRIPLE_COLUMN_NAME)
|
|
||||||
static DBObjectColumn TRIPLE_COLUMN;
|
|
||||||
@DBAnnotatedColumn(MAX_SNAP_COLUMN_NAME)
|
|
||||||
static DBObjectColumn MAX_SNAP_COLUMN;
|
|
||||||
@DBAnnotatedColumn(CHILD_COLUMN_NAME)
|
|
||||||
static DBObjectColumn CHILD_COLUMN;
|
|
||||||
@DBAnnotatedColumn(PRIMITIVE_COLUMN_NAME)
|
|
||||||
static DBObjectColumn PRIMITIVE_COLUMN;
|
|
||||||
|
|
||||||
@DBAnnotatedField(
|
|
||||||
column = TRIPLE_COLUMN_NAME,
|
|
||||||
indexed = true,
|
|
||||||
codec = PrimaryTripleDBFieldCodec.class)
|
|
||||||
private PrimaryTriple triple;
|
|
||||||
@DBAnnotatedField(
|
|
||||||
column = MAX_SNAP_COLUMN_NAME)
|
|
||||||
private long maxSnap;
|
|
||||||
@DBAnnotatedField(
|
|
||||||
column = CHILD_COLUMN_NAME,
|
|
||||||
indexed = true,
|
|
||||||
codec = DBTraceObjectDBFieldCodec.class)
|
|
||||||
private DBTraceObject child;
|
|
||||||
@DBAnnotatedField(
|
|
||||||
column = PRIMITIVE_COLUMN_NAME,
|
|
||||||
codec = VariantDBFieldCodec.class)
|
|
||||||
private Object primitive;
|
|
||||||
|
|
||||||
protected final DBTraceObjectManager manager;
|
|
||||||
|
|
||||||
private Lifespan lifespan;
|
|
||||||
|
|
||||||
public DBTraceObjectValue(DBTraceObjectManager manager, DBCachedObjectStore<?> store,
|
|
||||||
DBRecord record) {
|
|
||||||
super(store, record);
|
|
||||||
this.manager = manager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void fresh(boolean created) throws IOException {
|
|
||||||
if (created) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lifespan = Lifespan.span(triple.minSnap, maxSnap);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void set(Lifespan lifespan, DBTraceObject parent, String key, Object value) {
|
|
||||||
this.triple = new PrimaryTriple(parent, key, lifespan.lmin());
|
|
||||||
this.maxSnap = lifespan.lmax();
|
|
||||||
this.lifespan = Lifespan.span(triple.minSnap, maxSnap);
|
|
||||||
if (value instanceof TraceObject) {
|
|
||||||
DBTraceObject child = manager.assertIsMine((TraceObject) value);
|
|
||||||
this.child = child;
|
|
||||||
this.primitive = null;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.primitive = manager.validatePrimitive(value);
|
|
||||||
this.child = null;
|
|
||||||
}
|
|
||||||
update(TRIPLE_COLUMN, MAX_SNAP_COLUMN, CHILD_COLUMN, PRIMITIVE_COLUMN);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return getClass().getSimpleName() + ": parent=" + triple.parent + ", key=" + triple.key +
|
|
||||||
", lifespan=" + getLifespan() + ", value=" + getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doSetLifespan(Lifespan lifespan) {
|
|
||||||
long minSnap = lifespan.lmin();
|
|
||||||
if (this.triple.minSnap != minSnap) {
|
|
||||||
this.triple = triple.withMinSnap(minSnap);
|
|
||||||
update(TRIPLE_COLUMN);
|
|
||||||
}
|
|
||||||
this.maxSnap = lifespan.lmax();
|
|
||||||
update(MAX_SNAP_COLUMN);
|
|
||||||
this.lifespan = Lifespan.span(minSnap, maxSnap);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Trace getTrace() {
|
|
||||||
return manager.trace;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DBTraceObjectManager getManager() {
|
|
||||||
return manager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DBTraceObject getParent() {
|
|
||||||
return triple == null ? null : triple.parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getEntryKey() {
|
|
||||||
return triple == null ? null : triple.key;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getValue() {
|
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
|
||||||
return child != null ? child : primitive;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DBTraceObject getChild() {
|
|
||||||
return (DBTraceObject) getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isObject() {
|
|
||||||
return child != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DBTraceObject getChildOrNull() {
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Lifespan getLifespan() {
|
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
|
||||||
return lifespan;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setMinSnap(long minSnap) {
|
|
||||||
try (LockHold hold = manager.trace.lockWrite()) {
|
|
||||||
setLifespan(Lifespan.span(minSnap, maxSnap));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getMinSnap() {
|
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
|
||||||
return triple.minSnap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setMaxSnap(long maxSnap) {
|
|
||||||
try (LockHold hold = manager.trace.lockWrite()) {
|
|
||||||
setLifespan(Lifespan.span(triple.minSnap, maxSnap));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getMaxSnap() {
|
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
|
||||||
return maxSnap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Stream<? extends TraceObjectValPath> doStreamVisitor(Lifespan span,
|
|
||||||
Visitor visitor) {
|
|
||||||
return TreeTraversal.INSTANCE.walkValue(visitor, this, span, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected TraceObjectKeyPath doGetCanonicalPath() {
|
|
||||||
if (triple == null || triple.parent == null) {
|
|
||||||
return TraceObjectKeyPath.of();
|
|
||||||
}
|
|
||||||
return triple.parent.getCanonicalPath().extend(triple.key);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean doIsCanonical() {
|
|
||||||
if (child == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (triple.parent == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return doGetCanonicalPath().equals(child.getCanonicalPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TraceObjectKeyPath getCanonicalPath() {
|
|
||||||
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
|
|
||||||
return doGetCanonicalPath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCanonical() {
|
|
||||||
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
|
|
||||||
return doIsCanonical();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doDelete() {
|
|
||||||
manager.doDeleteEdge(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void delete() {
|
|
||||||
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
|
||||||
if (triple.parent == null) {
|
|
||||||
throw new IllegalArgumentException("Cannot delete root value");
|
|
||||||
}
|
|
||||||
doDeleteAndEmit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InternalTraceObjectValue truncateOrDelete(Lifespan span) {
|
|
||||||
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
|
||||||
if (triple.parent == null) {
|
|
||||||
throw new IllegalArgumentException("Cannot truncate or delete root value");
|
|
||||||
}
|
|
||||||
return doTruncateOrDeleteAndEmitLifeChange(span);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,433 @@
|
||||||
|
/* ###
|
||||||
|
* 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.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import db.DBRecord;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.address.AddressRange;
|
||||||
|
import ghidra.trace.database.target.visitors.TreeTraversal;
|
||||||
|
import ghidra.trace.database.target.visitors.TreeTraversal.Visitor;
|
||||||
|
import ghidra.trace.model.Lifespan;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.target.*;
|
||||||
|
import ghidra.util.LockHold;
|
||||||
|
import ghidra.util.database.DBCachedObjectStore;
|
||||||
|
import ghidra.util.database.DBCachedObjectStoreFactory.*;
|
||||||
|
import ghidra.util.database.DBObjectColumn;
|
||||||
|
import ghidra.util.database.annot.*;
|
||||||
|
import ghidra.util.database.spatial.DBTreeDataRecord;
|
||||||
|
|
||||||
|
@DBAnnotatedObjectInfo(version = 1)
|
||||||
|
public class DBTraceObjectValueData
|
||||||
|
extends DBTreeDataRecord<ValueShape, ValueBox, InternalTraceObjectValue>
|
||||||
|
implements InternalTraceObjectValue, ValueShape {
|
||||||
|
static final String TABLE_NAME = "ObjectValue";
|
||||||
|
|
||||||
|
static final String PARENT_COLUMN_NAME = "Parent"; // R*-Tree parent
|
||||||
|
static final String OBJ_PARENT_COLUMN_NAME = "ObjParent"; // Object-Tree parent
|
||||||
|
static final String ENTRY_KEY_COLUMN_NAME = "EntryKey";
|
||||||
|
static final String MIN_SNAP_COLUMN_NAME = "MinSnap";
|
||||||
|
static final String MAX_SNAP_COLUMN_NAME = "MaxSnap";
|
||||||
|
static final String CHILD_COLUMN_NAME = "Child";
|
||||||
|
static final String PRIMITIVE_COLUMN_NAME = "Primitive";
|
||||||
|
|
||||||
|
@DBAnnotatedColumn(PARENT_COLUMN_NAME)
|
||||||
|
static DBObjectColumn PARENT_COLUMN;
|
||||||
|
@DBAnnotatedColumn(OBJ_PARENT_COLUMN_NAME)
|
||||||
|
static DBObjectColumn OBJ_PARENT_COLUMN;
|
||||||
|
@DBAnnotatedColumn(ENTRY_KEY_COLUMN_NAME)
|
||||||
|
static DBObjectColumn ENTRY_KEY_COLUMN;
|
||||||
|
@DBAnnotatedColumn(MIN_SNAP_COLUMN_NAME)
|
||||||
|
static DBObjectColumn MIN_SNAP_COLUMN;
|
||||||
|
@DBAnnotatedColumn(MAX_SNAP_COLUMN_NAME)
|
||||||
|
static DBObjectColumn MAX_SNAP_COLUMN;
|
||||||
|
@DBAnnotatedColumn(CHILD_COLUMN_NAME)
|
||||||
|
static DBObjectColumn CHILD_COLUMN;
|
||||||
|
@DBAnnotatedColumn(PRIMITIVE_COLUMN_NAME)
|
||||||
|
static DBObjectColumn PRIMITIVE_COLUMN;
|
||||||
|
|
||||||
|
@DBAnnotatedField(column = PARENT_COLUMN_NAME, indexed = true)
|
||||||
|
private long parentKey;
|
||||||
|
@DBAnnotatedField(column = OBJ_PARENT_COLUMN_NAME, codec = DBTraceObjectDBFieldCodec.class)
|
||||||
|
private DBTraceObject objParent;
|
||||||
|
@DBAnnotatedField(column = ENTRY_KEY_COLUMN_NAME)
|
||||||
|
private String entryKey;
|
||||||
|
@DBAnnotatedField(column = MIN_SNAP_COLUMN_NAME)
|
||||||
|
private long minSnap;
|
||||||
|
@DBAnnotatedField(column = MAX_SNAP_COLUMN_NAME)
|
||||||
|
private long maxSnap;
|
||||||
|
@DBAnnotatedField(
|
||||||
|
column = CHILD_COLUMN_NAME,
|
||||||
|
indexed = true,
|
||||||
|
codec = DBTraceObjectDBFieldCodec.class)
|
||||||
|
private DBTraceObject child;
|
||||||
|
@DBAnnotatedField(column = PRIMITIVE_COLUMN_NAME, codec = VariantDBFieldCodec.class)
|
||||||
|
private Object primitive;
|
||||||
|
|
||||||
|
protected final DBTraceObjectManager manager;
|
||||||
|
protected final DBTraceObjectValueRStarTree tree;
|
||||||
|
|
||||||
|
protected ValueBox bounds;
|
||||||
|
protected Lifespan lifespan;
|
||||||
|
protected Address address;
|
||||||
|
protected AddressRange range;
|
||||||
|
|
||||||
|
public DBTraceObjectValueData(DBTraceObjectManager manager, DBTraceObjectValueRStarTree tree,
|
||||||
|
DBCachedObjectStore<?> store, DBRecord record) {
|
||||||
|
super(store, record);
|
||||||
|
this.manager = manager;
|
||||||
|
this.tree = tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doSetPrimitive(Object primitive) {
|
||||||
|
if (primitive instanceof TraceObject) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
else if (primitive instanceof Address address) {
|
||||||
|
this.address = address;
|
||||||
|
this.range = null;
|
||||||
|
this.primitive = RecAddress.fromAddress(address);
|
||||||
|
}
|
||||||
|
else if (primitive instanceof AddressRange range) {
|
||||||
|
this.address = null;
|
||||||
|
this.range = range;
|
||||||
|
this.primitive = RecRange.fromRange(range);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.address = null;
|
||||||
|
this.range = null;
|
||||||
|
this.primitive = primitive;
|
||||||
|
}
|
||||||
|
update(PRIMITIVE_COLUMN);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected long getObjParentKey() {
|
||||||
|
return objParent == null ? -1 : objParent.getKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected long getObjChildKey() {
|
||||||
|
return child == null ? -1 : child.getKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAddressSpaceId() {
|
||||||
|
if (primitive instanceof RecAddress addr) {
|
||||||
|
return addr.spaceId();
|
||||||
|
}
|
||||||
|
if (primitive instanceof RecRange rng) {
|
||||||
|
return rng.spaceId();
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getMinAddressOffset() {
|
||||||
|
if (primitive instanceof RecAddress addr) {
|
||||||
|
return addr.offset();
|
||||||
|
}
|
||||||
|
if (primitive instanceof RecRange rng) {
|
||||||
|
return rng.min();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getMaxAddressOffset() {
|
||||||
|
if (primitive instanceof RecAddress addr) {
|
||||||
|
return addr.offset();
|
||||||
|
}
|
||||||
|
if (primitive instanceof RecRange rng) {
|
||||||
|
return rng.max();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateBounds() {
|
||||||
|
long objParentKey = getObjParentKey();
|
||||||
|
long objChildKey = getObjChildKey();
|
||||||
|
int spaceId = getAddressSpaceId();
|
||||||
|
bounds = new ImmutableValueBox(
|
||||||
|
new ValueTriple(objParentKey, objChildKey, entryKey, minSnap,
|
||||||
|
new RecAddress(spaceId, getMinAddressOffset())),
|
||||||
|
new ValueTriple(objParentKey, objChildKey, entryKey, maxSnap,
|
||||||
|
new RecAddress(spaceId, getMaxAddressOffset())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void fresh(boolean created) throws IOException {
|
||||||
|
super.fresh(created);
|
||||||
|
if (created) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateBounds();
|
||||||
|
lifespan = Lifespan.span(minSnap, maxSnap);
|
||||||
|
if (primitive instanceof RecAddress address) {
|
||||||
|
this.address = address.toAddress(manager.trace.getBaseAddressFactory());
|
||||||
|
this.range = null;
|
||||||
|
}
|
||||||
|
else if (primitive instanceof RecRange range) {
|
||||||
|
this.address = null;
|
||||||
|
this.range = range.toRange(manager.trace.getBaseAddressFactory());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.address = null;
|
||||||
|
this.range = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Trace getTrace() {
|
||||||
|
return manager.trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DBTraceObject getParent() {
|
||||||
|
return objParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEntryKey() {
|
||||||
|
return entryKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TraceObjectKeyPath doGetCanonicalPath() {
|
||||||
|
if (objParent == null) {
|
||||||
|
return TraceObjectKeyPath.of();
|
||||||
|
}
|
||||||
|
return objParent.getCanonicalPath().extend(entryKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean doIsCanonical() {
|
||||||
|
if (child == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (objParent == null) { // We're the root
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return doGetCanonicalPath().equals(child.getCanonicalPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TraceObjectKeyPath getCanonicalPath() {
|
||||||
|
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
|
||||||
|
return doGetCanonicalPath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCanonical() {
|
||||||
|
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
|
||||||
|
return doIsCanonical();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getValue() {
|
||||||
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
|
if (child != null) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
if (address != null) {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
if (range != null) {
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
return child != null ? child : primitive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DBTraceObject getChild() {
|
||||||
|
return (DBTraceObject) getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isObject() {
|
||||||
|
return child != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Lifespan getLifespan() {
|
||||||
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
|
return lifespan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMinSnap(long minSnap) {
|
||||||
|
try (LockHold hold = manager.trace.lockWrite()) {
|
||||||
|
setLifespan(Lifespan.span(minSnap, maxSnap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getMinSnap() {
|
||||||
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
|
return minSnap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMaxSnap(long maxSnap) {
|
||||||
|
try (LockHold hold = manager.trace.lockWrite()) {
|
||||||
|
setLifespan(Lifespan.span(minSnap, maxSnap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getMaxSnap() {
|
||||||
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
|
return maxSnap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete() {
|
||||||
|
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
||||||
|
if (objParent == null) {
|
||||||
|
throw new IllegalArgumentException("Cannot delete root value");
|
||||||
|
}
|
||||||
|
doDeleteAndEmit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TraceObjectValue truncateOrDelete(Lifespan span) {
|
||||||
|
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
||||||
|
if (objParent == null) {
|
||||||
|
throw new IllegalArgumentException("Cannot truncate or delete root value");
|
||||||
|
}
|
||||||
|
return doTruncateOrDeleteAndEmitLifeChange(span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean shapeEquals(ValueShape shape) {
|
||||||
|
if (objParent != shape.getParent()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(entryKey, shape.getEntryKey())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(lifespan, shape.getLifespan())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setRecordValue(InternalTraceObjectValue value) {
|
||||||
|
// Nothing. Entry is the value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected InternalTraceObjectValue getRecordValue() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueShape getShape() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueBox getBounds() {
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setShape(ValueShape shape) {
|
||||||
|
objParent = shape.getParent();
|
||||||
|
child = shape.getChild();
|
||||||
|
entryKey = shape.getEntryKey();
|
||||||
|
minSnap = shape.getLifespan().lmin();
|
||||||
|
maxSnap = shape.getLifespan().lmax();
|
||||||
|
// Space/Address/Range will be set by doSetPrimitive
|
||||||
|
update(OBJ_PARENT_COLUMN, CHILD_COLUMN, ENTRY_KEY_COLUMN, MIN_SNAP_COLUMN, MAX_SNAP_COLUMN);
|
||||||
|
|
||||||
|
lifespan = shape.getLifespan();
|
||||||
|
bounds = shape.getBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getParentKey() {
|
||||||
|
return parentKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setParentKey(long parentKey) {
|
||||||
|
this.parentKey = parentKey;
|
||||||
|
update(PARENT_COLUMN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return new ImmutableValueShape(getShape()).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DBTraceObjectManager getManager() {
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DBTraceObject getChildOrNull() {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doSetLifespan(Lifespan lifespan) {
|
||||||
|
if (minSnap == lifespan.lmin() && maxSnap == lifespan.lmax()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DBTraceObjectValueRStarTree tree = this.tree;
|
||||||
|
tree.doUnparentEntry(this);
|
||||||
|
objParent.notifyValueDeleted(this);
|
||||||
|
if (child != null) {
|
||||||
|
child.notifyParentValueDeleted(this);
|
||||||
|
}
|
||||||
|
minSnap = lifespan.lmin();
|
||||||
|
maxSnap = lifespan.lmax();
|
||||||
|
update(MIN_SNAP_COLUMN, MAX_SNAP_COLUMN);
|
||||||
|
this.lifespan = lifespan;
|
||||||
|
updateBounds();
|
||||||
|
tree.doInsertDataEntry(this);
|
||||||
|
objParent.notifyValueCreated(this);
|
||||||
|
if (child != null) {
|
||||||
|
child.notifyParentValueCreated(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doDelete() {
|
||||||
|
objParent.notifyValueDeleted(this);
|
||||||
|
if (child != null) {
|
||||||
|
child.notifyParentValueDeleted(this);
|
||||||
|
}
|
||||||
|
manager.doDeleteEdge(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Stream<? extends TraceObjectValPath> doStreamVisitor(Lifespan span,
|
||||||
|
Visitor visitor) {
|
||||||
|
return TreeTraversal.INSTANCE.walkValue(visitor, this, span, null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,286 @@
|
||||||
|
/* ###
|
||||||
|
* 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.Iterator;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import org.apache.commons.collections4.IteratorUtils;
|
||||||
|
|
||||||
|
import ghidra.program.model.address.*;
|
||||||
|
import ghidra.trace.database.target.ValueSpace.AddressDimension;
|
||||||
|
import ghidra.trace.database.target.ValueSpace.EntryKeyDimension;
|
||||||
|
import ghidra.trace.model.Lifespan;
|
||||||
|
import ghidra.util.*;
|
||||||
|
import ghidra.util.database.DBCachedObjectStoreFactory.RecAddress;
|
||||||
|
import ghidra.util.database.spatial.SpatialMap;
|
||||||
|
|
||||||
|
public class DBTraceObjectValueMapAddressSetView extends AbstractAddressSetView {
|
||||||
|
|
||||||
|
private final AddressFactory factory;
|
||||||
|
private final ReadWriteLock lock;
|
||||||
|
private final SpatialMap<ValueShape, InternalTraceObjectValue, TraceObjectValueQuery> map;
|
||||||
|
private final Predicate<? super InternalTraceObjectValue> predicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An address set view that unions all addresses where an entry satisfying the given predicate
|
||||||
|
* exists.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The caller may reduce the map given to this view. Reduction is preferable to using a
|
||||||
|
* predicate, where possible, because reduction benefits from the index.
|
||||||
|
*
|
||||||
|
* @param factory the trace's address factory
|
||||||
|
* @param lock the lock on the database
|
||||||
|
* @param map the map
|
||||||
|
* @param predicate a predicate to further filter entries
|
||||||
|
*/
|
||||||
|
public DBTraceObjectValueMapAddressSetView(AddressFactory factory, ReadWriteLock lock,
|
||||||
|
SpatialMap<ValueShape, InternalTraceObjectValue, TraceObjectValueQuery> map,
|
||||||
|
Predicate<? super InternalTraceObjectValue> predicate) {
|
||||||
|
this.factory = factory;
|
||||||
|
this.lock = lock;
|
||||||
|
this.map = map;
|
||||||
|
this.predicate = predicate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(Address addr) {
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
for (InternalTraceObjectValue value : map
|
||||||
|
.reduce(TraceObjectValueQuery.intersecting(Lifespan.ALL,
|
||||||
|
new AddressRangeImpl(addr, addr)))
|
||||||
|
.values()) {
|
||||||
|
if (predicate.test(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (NoSuchElementException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(Address start, Address end) {
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
return super.contains(start, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(AddressSetView rangeSet) {
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
return super.contains(rangeSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
for (InternalTraceObjectValue value : map.values()) {
|
||||||
|
if (predicate.test(value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Address getMinAddress() {
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
for (Entry<ValueShape, InternalTraceObjectValue> entry : map
|
||||||
|
.reduce(TraceObjectValueQuery.all().starting(AddressDimension.FORWARD))
|
||||||
|
.orderedEntries()) {
|
||||||
|
if (predicate.test(entry.getValue())) {
|
||||||
|
return entry.getKey().getMinAddress(factory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Address getMaxAddress() {
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
for (Entry<ValueShape, InternalTraceObjectValue> entry : map
|
||||||
|
.reduce(TraceObjectValueQuery.all().starting(AddressDimension.BACKWARD))
|
||||||
|
.orderedEntries()) {
|
||||||
|
if (predicate.test(entry.getValue())) {
|
||||||
|
return entry.getKey().getMaxAddress(factory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNumAddressRanges() {
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
return super.getNumAddressRanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AddressRangeIterator getAddressRanges() {
|
||||||
|
return doGetAddressRanges(AddressDimension.INSTANCE.absoluteMin(),
|
||||||
|
AddressDimension.INSTANCE.absoluteMax(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AddressRangeIterator getAddressRanges(boolean forward) {
|
||||||
|
return doGetAddressRanges(AddressDimension.INSTANCE.absoluteMin(),
|
||||||
|
AddressDimension.INSTANCE.absoluteMax(), forward);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AddressRangeIterator doGetAddressRanges(RecAddress start, RecAddress end,
|
||||||
|
boolean forward) {
|
||||||
|
Iterator<Entry<ValueShape, InternalTraceObjectValue>> mapIt = map
|
||||||
|
.reduce(TraceObjectValueQuery
|
||||||
|
.intersecting(EntryKeyDimension.INSTANCE.absoluteMin(),
|
||||||
|
EntryKeyDimension.INSTANCE.absoluteMax(), Lifespan.ALL, start, end)
|
||||||
|
.starting(forward ? AddressDimension.FORWARD : AddressDimension.BACKWARD))
|
||||||
|
.orderedEntries()
|
||||||
|
.iterator();
|
||||||
|
Iterator<Entry<ValueShape, InternalTraceObjectValue>> fltIt =
|
||||||
|
IteratorUtils.filteredIterator(mapIt, e -> predicate.test(e.getValue()));
|
||||||
|
Iterator<AddressRange> rawIt =
|
||||||
|
IteratorUtils.transformedIterator(fltIt, e -> e.getKey().getRange(factory));
|
||||||
|
return new UnionAddressRangeIterator(rawIt, forward);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Note the first range may be incomplete up when composed of connected entries, but it will at
|
||||||
|
* least include all the ranges ahead of the given start -- or behind the given start if forward
|
||||||
|
* is false. TODO: Fix that, just like {@link UnionAddressSetView}?
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AddressRangeIterator getAddressRanges(Address start, boolean forward) {
|
||||||
|
RecAddress min = forward
|
||||||
|
? RecAddress.fromAddress(start)
|
||||||
|
: AddressDimension.INSTANCE.absoluteMin();
|
||||||
|
RecAddress max = forward
|
||||||
|
? AddressDimension.INSTANCE.absoluteMax()
|
||||||
|
: RecAddress.fromAddress(start);
|
||||||
|
return doGetAddressRanges(min, max, forward);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getNumAddresses() {
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
return super.getNumAddresses();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean intersects(AddressSetView addrSet) {
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
return super.intersects(addrSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean intersects(Address start, Address end) {
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
return super.intersects(start, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AddressSet intersect(AddressSetView view) {
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
return super.intersect(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AddressSet intersectRange(Address start, Address end) {
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
return super.intersectRange(start, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AddressSet union(AddressSetView addrSet) {
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
return super.union(addrSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AddressSet subtract(AddressSetView addrSet) {
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
return super.subtract(addrSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AddressSet xor(AddressSetView addrSet) {
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
return super.xor(addrSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSameAddresses(AddressSetView view) {
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
return super.hasSameAddresses(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AddressRange getFirstRange() {
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
return super.getFirstRange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AddressRange getLastRange() {
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
return super.getLastRange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Note that adjacent or overlapping ranges may be omitted if they don't also contain the
|
||||||
|
* address.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AddressRange getRangeContaining(Address address) {
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
return super.getRangeContaining(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Address findFirstAddressInCommon(AddressSetView set) {
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
return super.findFirstAddressInCommon(set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,232 @@
|
||||||
|
/* ###
|
||||||
|
* 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.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import db.DBRecord;
|
||||||
|
import ghidra.util.database.DBCachedObjectStore;
|
||||||
|
import ghidra.util.database.DBCachedObjectStoreFactory.RecAddress;
|
||||||
|
import ghidra.util.database.DBObjectColumn;
|
||||||
|
import ghidra.util.database.annot.*;
|
||||||
|
import ghidra.util.database.spatial.DBTreeNodeRecord;
|
||||||
|
|
||||||
|
@DBAnnotatedObjectInfo(version = 0)
|
||||||
|
public class DBTraceObjectValueNode extends DBTreeNodeRecord<ValueBox> implements ValueBox {
|
||||||
|
protected static final byte NODE_TYPE_MASK = 3;
|
||||||
|
protected static final int NODE_TYPE_SHIFT = 6;
|
||||||
|
protected static final byte NODE_TYPE_CLEAR = (byte) ~(NODE_TYPE_MASK << NODE_TYPE_SHIFT);
|
||||||
|
|
||||||
|
protected static final byte CHILD_COUNT_MASK = 0x3f;
|
||||||
|
protected static final int CHILD_COUNT_SHIFT = 0;
|
||||||
|
protected static final byte CHILD_COUNT_CLEAR = (byte) ~(CHILD_COUNT_MASK << CHILD_COUNT_SHIFT);
|
||||||
|
|
||||||
|
// Please note the overloaded use of "parent" here:
|
||||||
|
static final String PARENT_COLUMN_NAME = "Parent"; // parent in the R*-Tree
|
||||||
|
static final String MIN_PARENT_KEY_COLUMN_NAME = "MinParentKey"; // parent in the object tree
|
||||||
|
static final String MAX_PARENT_KEY_COLUMN_NAME = "MaxParentKey"; // parent in the object tree
|
||||||
|
static final String MIN_CHILD_KEY_COLUMN_NAME = "MinChildKey";
|
||||||
|
static final String MAX_CHILD_KEY_COLUMN_NAME = "MaxChildKey";
|
||||||
|
static final String MIN_ENTRY_KEY_COLUMN_NAME = "MinEntryKey";
|
||||||
|
static final String MAX_ENTRY_KEY_COLUMN_NAME = "MaxEntryKey";
|
||||||
|
static final String MIN_SNAP_COLUMN_NAME = "MinSnap";
|
||||||
|
static final String MAX_SNAP_COLUMN_NAME = "MaxSnap";
|
||||||
|
static final String MIN_SPACE_COLUMN_NAME = "MinSpace";
|
||||||
|
static final String MAX_SPACE_COLUMN_NAME = "MaxSpace";
|
||||||
|
static final String MIN_ADDRESS_COLUMN_NAME = "MinAddress";
|
||||||
|
static final String MAX_ADDRESS_COLUMN_NAME = "MaxAddress";
|
||||||
|
static final String TYPE_AND_CHILD_COUNT_COLUMN_NAME = "Type/ChildCount";
|
||||||
|
static final String DATA_COUNT_COLUMN_NAME = "DataCount";
|
||||||
|
|
||||||
|
@DBAnnotatedColumn(PARENT_COLUMN_NAME)
|
||||||
|
static DBObjectColumn PARENT_COLUMN;
|
||||||
|
@DBAnnotatedColumn(MIN_PARENT_KEY_COLUMN_NAME)
|
||||||
|
static DBObjectColumn MIN_PARENT_KEY_COLUMN;
|
||||||
|
@DBAnnotatedColumn(MAX_PARENT_KEY_COLUMN_NAME)
|
||||||
|
static DBObjectColumn MAX_PARENT_KEY_COLUMN;
|
||||||
|
@DBAnnotatedColumn(MIN_CHILD_KEY_COLUMN_NAME)
|
||||||
|
static DBObjectColumn MIN_CHILD_KEY_COLUMN;
|
||||||
|
@DBAnnotatedColumn(MAX_CHILD_KEY_COLUMN_NAME)
|
||||||
|
static DBObjectColumn MAX_CHILD_KEY_COLUMN;
|
||||||
|
@DBAnnotatedColumn(MIN_ENTRY_KEY_COLUMN_NAME)
|
||||||
|
static DBObjectColumn MIN_ENTRY_KEY_COLUMN;
|
||||||
|
@DBAnnotatedColumn(MAX_ENTRY_KEY_COLUMN_NAME)
|
||||||
|
static DBObjectColumn MAX_ENTRY_KEY_COLUMN;
|
||||||
|
@DBAnnotatedColumn(MIN_SNAP_COLUMN_NAME)
|
||||||
|
static DBObjectColumn MIN_SNAP_COLUMN;
|
||||||
|
@DBAnnotatedColumn(MAX_SNAP_COLUMN_NAME)
|
||||||
|
static DBObjectColumn MAX_SNAP_COLUMN;
|
||||||
|
@DBAnnotatedColumn(MIN_SPACE_COLUMN_NAME)
|
||||||
|
static DBObjectColumn MIN_SPACE_COLUMN;
|
||||||
|
@DBAnnotatedColumn(MAX_SPACE_COLUMN_NAME)
|
||||||
|
static DBObjectColumn MAX_SPACE_COLUMN;
|
||||||
|
@DBAnnotatedColumn(MIN_ADDRESS_COLUMN_NAME)
|
||||||
|
static DBObjectColumn MIN_ADDRESS_COLUMN;
|
||||||
|
@DBAnnotatedColumn(MAX_ADDRESS_COLUMN_NAME)
|
||||||
|
static DBObjectColumn MAX_ADDRESS_COLUMN;
|
||||||
|
@DBAnnotatedColumn(TYPE_AND_CHILD_COUNT_COLUMN_NAME)
|
||||||
|
static DBObjectColumn TYPE_AND_CHILD_COUNT_COLUMN;
|
||||||
|
@DBAnnotatedColumn(DATA_COUNT_COLUMN_NAME)
|
||||||
|
static DBObjectColumn DATA_COUNT_COLUMN;
|
||||||
|
|
||||||
|
@DBAnnotatedField(column = PARENT_COLUMN_NAME, indexed = true)
|
||||||
|
private long parentKey;
|
||||||
|
@DBAnnotatedField(column = MIN_PARENT_KEY_COLUMN_NAME)
|
||||||
|
private long minObjParentKey;
|
||||||
|
@DBAnnotatedField(column = MAX_PARENT_KEY_COLUMN_NAME)
|
||||||
|
private long maxObjParentKey;
|
||||||
|
@DBAnnotatedField(column = MIN_CHILD_KEY_COLUMN_NAME)
|
||||||
|
private long minObjChildKey;
|
||||||
|
@DBAnnotatedField(column = MAX_CHILD_KEY_COLUMN_NAME)
|
||||||
|
private long maxObjChildKey;
|
||||||
|
@DBAnnotatedField(column = MIN_ENTRY_KEY_COLUMN_NAME)
|
||||||
|
private String minEntryKey;
|
||||||
|
@DBAnnotatedField(column = MAX_ENTRY_KEY_COLUMN_NAME)
|
||||||
|
private String maxEntryKey;
|
||||||
|
@DBAnnotatedField(column = MIN_SNAP_COLUMN_NAME)
|
||||||
|
private long minSnap;
|
||||||
|
@DBAnnotatedField(column = MAX_SNAP_COLUMN_NAME)
|
||||||
|
private long maxSnap;
|
||||||
|
@DBAnnotatedField(column = MIN_SPACE_COLUMN_NAME)
|
||||||
|
private int minSpace;
|
||||||
|
@DBAnnotatedField(column = MAX_SPACE_COLUMN_NAME)
|
||||||
|
private int maxSpace;
|
||||||
|
@DBAnnotatedField(column = MIN_ADDRESS_COLUMN_NAME)
|
||||||
|
private long minAddress;
|
||||||
|
@DBAnnotatedField(column = MAX_ADDRESS_COLUMN_NAME)
|
||||||
|
private long maxAddress;
|
||||||
|
@DBAnnotatedField(column = TYPE_AND_CHILD_COUNT_COLUMN_NAME)
|
||||||
|
private byte typeAndChildCount;
|
||||||
|
@DBAnnotatedField(column = DATA_COUNT_COLUMN_NAME)
|
||||||
|
private int dataCount;
|
||||||
|
|
||||||
|
protected final DBTraceObjectValueRStarTree tree;
|
||||||
|
|
||||||
|
private ValueTriple lCorner;
|
||||||
|
private ValueTriple uCorner;
|
||||||
|
|
||||||
|
public DBTraceObjectValueNode(DBTraceObjectValueRStarTree tree, DBCachedObjectStore<?> store,
|
||||||
|
DBRecord record) {
|
||||||
|
super(store, record);
|
||||||
|
this.tree = tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void fresh(boolean created) throws IOException {
|
||||||
|
super.fresh(created);
|
||||||
|
if (created) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lCorner = new ValueTriple(minObjParentKey, minObjChildKey, minEntryKey, minSnap,
|
||||||
|
new RecAddress(minSpace, minAddress));
|
||||||
|
uCorner = new ValueTriple(maxObjParentKey, maxObjChildKey, maxEntryKey, maxSnap,
|
||||||
|
new RecAddress(maxSpace, maxAddress));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueTriple lCorner() {
|
||||||
|
return Objects.requireNonNull(lCorner);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueTriple uCorner() {
|
||||||
|
return Objects.requireNonNull(uCorner);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected NodeType getType() {
|
||||||
|
return NodeType.VALUES.get((typeAndChildCount >> NODE_TYPE_SHIFT) & NODE_TYPE_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setType(NodeType type) {
|
||||||
|
typeAndChildCount =
|
||||||
|
(byte) (typeAndChildCount & NODE_TYPE_CLEAR | (type.ordinal() << NODE_TYPE_SHIFT));
|
||||||
|
update(TYPE_AND_CHILD_COUNT_COLUMN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getChildCount() {
|
||||||
|
return (typeAndChildCount >> CHILD_COUNT_SHIFT) & CHILD_COUNT_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setChildCount(int childCount) {
|
||||||
|
assert (childCount & CHILD_COUNT_MASK) == childCount;
|
||||||
|
typeAndChildCount =
|
||||||
|
(byte) (typeAndChildCount & CHILD_COUNT_CLEAR | (childCount << CHILD_COUNT_SHIFT));
|
||||||
|
update(TYPE_AND_CHILD_COUNT_COLUMN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getDataCount() {
|
||||||
|
return dataCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setDataCount(int dataCount) {
|
||||||
|
this.dataCount = dataCount;
|
||||||
|
update(DATA_COUNT_COLUMN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueBox getShape() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setShape(ValueBox shape) {
|
||||||
|
minObjParentKey = shape.lCorner().parentKey();
|
||||||
|
maxObjParentKey = shape.uCorner().parentKey();
|
||||||
|
minObjChildKey = shape.lCorner().childKey();
|
||||||
|
maxObjChildKey = shape.uCorner().childKey();
|
||||||
|
minEntryKey = shape.lCorner().entryKey();
|
||||||
|
maxEntryKey = shape.uCorner().entryKey();
|
||||||
|
minSnap = shape.lCorner().snap();
|
||||||
|
maxSnap = shape.uCorner().snap();
|
||||||
|
minSpace = shape.lCorner().address().spaceId();
|
||||||
|
maxSpace = shape.uCorner().address().spaceId();
|
||||||
|
minAddress = shape.lCorner().address().offset();
|
||||||
|
maxAddress = shape.uCorner().address().offset();
|
||||||
|
update(
|
||||||
|
MIN_PARENT_KEY_COLUMN, MAX_PARENT_KEY_COLUMN,
|
||||||
|
MIN_CHILD_KEY_COLUMN, MAX_CHILD_KEY_COLUMN,
|
||||||
|
MIN_ENTRY_KEY_COLUMN, MAX_ENTRY_KEY_COLUMN,
|
||||||
|
MIN_SNAP_COLUMN, MAX_SNAP_COLUMN,
|
||||||
|
MIN_SPACE_COLUMN, MAX_SPACE_COLUMN,
|
||||||
|
MIN_ADDRESS_COLUMN, MAX_ADDRESS_COLUMN);
|
||||||
|
|
||||||
|
lCorner = shape.lCorner();
|
||||||
|
uCorner = shape.uCorner();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueBox getBounds() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getParentKey() {
|
||||||
|
return parentKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setParentKey(long parentKey) {
|
||||||
|
this.parentKey = parentKey;
|
||||||
|
update(PARENT_COLUMN);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
/* ###
|
||||||
|
* 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.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import db.DBRecord;
|
||||||
|
import ghidra.program.model.address.AddressFactory;
|
||||||
|
import ghidra.program.model.address.AddressSetView;
|
||||||
|
import ghidra.trace.model.Lifespan;
|
||||||
|
import ghidra.util.database.*;
|
||||||
|
import ghidra.util.database.spatial.AbstractConstraintsTree;
|
||||||
|
import ghidra.util.database.spatial.hyper.AbstractHyperRStarTree;
|
||||||
|
import ghidra.util.database.spatial.hyper.EuclideanHyperSpace;
|
||||||
|
import ghidra.util.exception.VersionException;
|
||||||
|
|
||||||
|
public class DBTraceObjectValueRStarTree extends AbstractHyperRStarTree< //
|
||||||
|
ValueTriple, //
|
||||||
|
ValueShape, DBTraceObjectValueData, //
|
||||||
|
ValueBox, DBTraceObjectValueNode, //
|
||||||
|
InternalTraceObjectValue, TraceObjectValueQuery> {
|
||||||
|
|
||||||
|
public static class DBTraceObjectValueMap extends AsSpatialMap<ValueShape, //
|
||||||
|
DBTraceObjectValueData, ValueBox, InternalTraceObjectValue, TraceObjectValueQuery> {
|
||||||
|
|
||||||
|
private final AddressFactory factory;
|
||||||
|
private final ReadWriteLock lock;
|
||||||
|
|
||||||
|
public DBTraceObjectValueMap(AbstractConstraintsTree<ValueShape, DBTraceObjectValueData, //
|
||||||
|
ValueBox, ?, InternalTraceObjectValue, TraceObjectValueQuery> tree,
|
||||||
|
TraceObjectValueQuery query, AddressFactory factory, ReadWriteLock lock) {
|
||||||
|
super(tree, query);
|
||||||
|
this.factory = factory;
|
||||||
|
this.lock = lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DBTraceObjectValueMap reduce(TraceObjectValueQuery andQuery) {
|
||||||
|
return new DBTraceObjectValueMap(this.tree,
|
||||||
|
this.query == null ? andQuery : this.query.and(andQuery), this.factory, this.lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AddressSetView getAddressSetView(Lifespan at,
|
||||||
|
Predicate<? super InternalTraceObjectValue> predicate) {
|
||||||
|
return new DBTraceObjectValueMapAddressSetView(factory, lock, this, predicate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final DBTraceObjectManager manager;
|
||||||
|
protected final DBCachedObjectIndex<Long, DBTraceObjectValueNode> nodesByParent;
|
||||||
|
protected final DBCachedObjectIndex<Long, DBTraceObjectValueData> dataByParent;
|
||||||
|
|
||||||
|
public DBTraceObjectValueRStarTree(DBTraceObjectManager manager,
|
||||||
|
DBCachedObjectStoreFactory storeFactory, String tableName,
|
||||||
|
EuclideanHyperSpace<ValueTriple, ValueBox> space,
|
||||||
|
Class<DBTraceObjectValueData> dataType, Class<DBTraceObjectValueNode> nodeType,
|
||||||
|
boolean upgradeable, int maxChildren) throws VersionException, IOException {
|
||||||
|
super(storeFactory, tableName, space, dataType, nodeType, upgradeable, maxChildren);
|
||||||
|
this.manager = manager;
|
||||||
|
this.nodesByParent = nodeStore.getIndex(long.class, DBTraceObjectValueNode.PARENT_COLUMN);
|
||||||
|
this.dataByParent = dataStore.getIndex(long.class, DBTraceObjectValueNode.PARENT_COLUMN);
|
||||||
|
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DBCachedObjectStore<DBTraceObjectValueData> getDataStore() {
|
||||||
|
return dataStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doUnparentEntry(DBTraceObjectValueData data) {
|
||||||
|
super.doUnparentEntry(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doInsertDataEntry(DBTraceObjectValueData entry) {
|
||||||
|
super.doInsert(entry, new LevelInfo(leafLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doDeleteEntry(DBTraceObjectValueData data) {
|
||||||
|
super.doDeleteEntry(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected DBTraceObjectValueData createDataEntry(
|
||||||
|
DBCachedObjectStore<DBTraceObjectValueData> store, DBRecord record) {
|
||||||
|
return new DBTraceObjectValueData(manager, this, store, record);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected DBTraceObjectValueNode createNodeEntry(
|
||||||
|
DBCachedObjectStore<DBTraceObjectValueNode> store, DBRecord record) {
|
||||||
|
return new DBTraceObjectValueNode(this, store, record);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Collection<DBTraceObjectValueNode> getNodeChildrenOf(long parentKey) {
|
||||||
|
return nodesByParent.get(parentKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Collection<DBTraceObjectValueData> getDataChildrenOf(long parentKey) {
|
||||||
|
return dataByParent.get(parentKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DBTraceObjectValueMap asSpatialMap() {
|
||||||
|
return new DBTraceObjectValueMap(this, null, manager.trace.getBaseAddressFactory(),
|
||||||
|
manager.lock);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
/* ###
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
record ImmutableValueBox(ValueTriple lCorner, ValueTriple uCorner) implements ValueBox {
|
||||||
|
public ImmutableValueBox(ValueBox box) {
|
||||||
|
this(box.lCorner(), box.uCorner());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
/* ###
|
||||||
|
* 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 ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.address.AddressRange;
|
||||||
|
import ghidra.trace.model.Lifespan;
|
||||||
|
import ghidra.util.database.DBCachedObjectStoreFactory.RecAddress;
|
||||||
|
|
||||||
|
public record ImmutableValueShape(DBTraceObject parent, DBTraceObject child, String entryKey,
|
||||||
|
Lifespan lifespan, int addressSpaceId, long minAddressOffset, long maxAddressOffset)
|
||||||
|
implements ValueShape {
|
||||||
|
|
||||||
|
public static int getAddressSpaceId(Object value) {
|
||||||
|
if (value instanceof Address address) {
|
||||||
|
return address.getAddressSpace().getSpaceID();
|
||||||
|
}
|
||||||
|
if (value instanceof AddressRange range) {
|
||||||
|
return range.getAddressSpace().getSpaceID();
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getMinAddressOffset(Object value) {
|
||||||
|
if (value instanceof Address address) {
|
||||||
|
return address.getOffset();
|
||||||
|
}
|
||||||
|
if (value instanceof AddressRange range) {
|
||||||
|
return range.getMinAddress().getOffset();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getMaxAddressOffset(Object value) {
|
||||||
|
if (value instanceof Address address) {
|
||||||
|
return address.getOffset();
|
||||||
|
}
|
||||||
|
if (value instanceof AddressRange range) {
|
||||||
|
return range.getMaxAddress().getOffset();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableValueShape(DBTraceObject parent, Object value, String entryKey,
|
||||||
|
Lifespan lifespan) {
|
||||||
|
this(parent, value instanceof DBTraceObject child ? child : null, entryKey, lifespan,
|
||||||
|
getAddressSpaceId(value), getMinAddressOffset(value), getMaxAddressOffset(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableValueShape(ValueShape shape) {
|
||||||
|
this(shape.getParent(), shape.getChildOrNull(), shape.getEntryKey(), shape.getLifespan(),
|
||||||
|
shape.getAddressSpaceId(), shape.getMinAddressOffset(), shape.getMaxAddressOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueBox getBounds() {
|
||||||
|
long parentKey = parent == null ? -1 : parent.getKey();
|
||||||
|
long childKey = child == null ? -1 : child.getKey();
|
||||||
|
return new ImmutableValueBox(
|
||||||
|
new ValueTriple(parentKey, childKey, entryKey, lifespan.lmin(),
|
||||||
|
new RecAddress(addressSpaceId, minAddressOffset)),
|
||||||
|
new ValueTriple(parentKey, childKey, entryKey, lifespan.lmax(),
|
||||||
|
new RecAddress(addressSpaceId, maxAddressOffset)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DBTraceObject getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DBTraceObject getChild() {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DBTraceObject getChildOrNull() {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEntryKey() {
|
||||||
|
return entryKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Lifespan getLifespan() {
|
||||||
|
return lifespan;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAddressSpaceId() {
|
||||||
|
return addressSpaceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getMinAddressOffset() {
|
||||||
|
return minAddressOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getMaxAddressOffset() {
|
||||||
|
return maxAddressOffset;
|
||||||
|
}
|
||||||
|
}
|
|
@ -98,6 +98,8 @@ interface InternalTraceObjectValue extends TraceObjectValue {
|
||||||
protected abstract InternalTraceObjectValue create(Lifespan range, Object value);
|
protected abstract InternalTraceObjectValue create(Lifespan range, Object value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void doSetPrimitive(Object primitive);
|
||||||
|
|
||||||
DBTraceObjectManager getManager();
|
DBTraceObjectManager getManager();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -143,7 +145,7 @@ interface InternalTraceObjectValue extends TraceObjectValue {
|
||||||
protected Iterable<InternalTraceObjectValue> getIntersecting(Long lower,
|
protected Iterable<InternalTraceObjectValue> getIntersecting(Long lower,
|
||||||
Long upper) {
|
Long upper) {
|
||||||
Collection<InternalTraceObjectValue> col = Collections.unmodifiableCollection(
|
Collection<InternalTraceObjectValue> col = Collections.unmodifiableCollection(
|
||||||
getParent().doGetValues(lower, upper, getEntryKey()));
|
getParent().doGetValues(Lifespan.span(lower, upper), getEntryKey(), true));
|
||||||
return IterableUtils.filteredIterable(col, v -> v != keep);
|
return IterableUtils.filteredIterable(col, v -> v != keep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,182 @@
|
||||||
|
/* ###
|
||||||
|
* 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.Objects;
|
||||||
|
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.address.AddressRange;
|
||||||
|
import ghidra.trace.database.target.ValueSpace.*;
|
||||||
|
import ghidra.trace.model.Lifespan;
|
||||||
|
import ghidra.trace.model.target.TraceObjectKeyPath;
|
||||||
|
import ghidra.util.database.DBCachedObjectStoreFactory.RecAddress;
|
||||||
|
import ghidra.util.database.spatial.hyper.AbstractHyperBoxQuery;
|
||||||
|
import ghidra.util.database.spatial.hyper.HyperDirection;
|
||||||
|
|
||||||
|
public class TraceObjectValueQuery
|
||||||
|
extends AbstractHyperBoxQuery<ValueTriple, ValueShape, ValueBox, TraceObjectValueQuery> {
|
||||||
|
|
||||||
|
public TraceObjectValueQuery(ValueBox ls, ValueBox us, HyperDirection direction) {
|
||||||
|
super(ls, us, ls.space(), direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean testData(ValueShape shape) {
|
||||||
|
ValueBox bounds = shape.getBounds();
|
||||||
|
if (!ls.contains(bounds.lCorner())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!us.contains(bounds.uCorner())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TraceObjectValueQuery create(ValueBox ir1, ValueBox ir2,
|
||||||
|
HyperDirection newDirection) {
|
||||||
|
return new TraceObjectValueQuery(ir1, ir2, newDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TraceObjectValueQuery all() {
|
||||||
|
return AbstractHyperBoxQuery.intersecting(ValueSpace.FULL, HyperDirection.DEFAULT,
|
||||||
|
TraceObjectValueQuery::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TraceObjectValueQuery canonicalParents(DBTraceObject child, Lifespan lifespan) {
|
||||||
|
Objects.requireNonNull(child);
|
||||||
|
TraceObjectKeyPath path = child.getCanonicalPath();
|
||||||
|
String entryKey = path.isRoot() ? "" : path.key();
|
||||||
|
long childKey = child.getKey();
|
||||||
|
return AbstractHyperBoxQuery.intersecting(
|
||||||
|
new ImmutableValueBox(
|
||||||
|
new ValueTriple(
|
||||||
|
ParentKeyDimension.INSTANCE.absoluteMin(),
|
||||||
|
childKey, entryKey, lifespan.lmin(),
|
||||||
|
AddressDimension.INSTANCE.absoluteMin()),
|
||||||
|
new ValueTriple(
|
||||||
|
ParentKeyDimension.INSTANCE.absoluteMax(),
|
||||||
|
childKey, entryKey, lifespan.lmax(),
|
||||||
|
AddressDimension.INSTANCE.absoluteMax())),
|
||||||
|
HyperDirection.DEFAULT, TraceObjectValueQuery::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TraceObjectValueQuery parents(DBTraceObject child, Lifespan lifespan) {
|
||||||
|
Objects.requireNonNull(child);
|
||||||
|
long childKey = child.getKey();
|
||||||
|
return AbstractHyperBoxQuery.intersecting(
|
||||||
|
new ImmutableValueBox(
|
||||||
|
new ValueTriple(
|
||||||
|
ParentKeyDimension.INSTANCE.absoluteMin(),
|
||||||
|
childKey,
|
||||||
|
EntryKeyDimension.INSTANCE.absoluteMin(),
|
||||||
|
lifespan.lmin(),
|
||||||
|
AddressDimension.INSTANCE.absoluteMin()),
|
||||||
|
new ValueTriple(
|
||||||
|
ParentKeyDimension.INSTANCE.absoluteMax(),
|
||||||
|
childKey,
|
||||||
|
EntryKeyDimension.INSTANCE.absoluteMax(),
|
||||||
|
lifespan.lmax(),
|
||||||
|
AddressDimension.INSTANCE.absoluteMax())),
|
||||||
|
HyperDirection.DEFAULT, TraceObjectValueQuery::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TraceObjectValueQuery values(DBTraceObject parent, Lifespan lifespan) {
|
||||||
|
Objects.requireNonNull(parent);
|
||||||
|
long parentKey = parent.getKey();
|
||||||
|
return AbstractHyperBoxQuery.intersecting(
|
||||||
|
new ImmutableValueBox(
|
||||||
|
new ValueTriple(
|
||||||
|
parentKey,
|
||||||
|
ChildKeyDimension.INSTANCE.absoluteMin(),
|
||||||
|
EntryKeyDimension.INSTANCE.absoluteMin(),
|
||||||
|
lifespan.lmin(),
|
||||||
|
AddressDimension.INSTANCE.absoluteMin()),
|
||||||
|
new ValueTriple(
|
||||||
|
parentKey,
|
||||||
|
ChildKeyDimension.INSTANCE.absoluteMax(),
|
||||||
|
EntryKeyDimension.INSTANCE.absoluteMax(),
|
||||||
|
lifespan.lmax(),
|
||||||
|
AddressDimension.INSTANCE.absoluteMax())),
|
||||||
|
HyperDirection.DEFAULT, TraceObjectValueQuery::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TraceObjectValueQuery values(DBTraceObject parent, String minKey, String maxKey,
|
||||||
|
Lifespan lifespan) {
|
||||||
|
Objects.requireNonNull(parent);
|
||||||
|
long parentKey = parent.getKey();
|
||||||
|
return AbstractHyperBoxQuery.intersecting(
|
||||||
|
new ImmutableValueBox(
|
||||||
|
new ValueTriple(
|
||||||
|
parentKey,
|
||||||
|
ChildKeyDimension.INSTANCE.absoluteMin(),
|
||||||
|
minKey,
|
||||||
|
lifespan.lmin(),
|
||||||
|
AddressDimension.INSTANCE.absoluteMin()),
|
||||||
|
new ValueTriple(
|
||||||
|
parentKey,
|
||||||
|
ChildKeyDimension.INSTANCE.absoluteMax(),
|
||||||
|
maxKey,
|
||||||
|
lifespan.lmax(),
|
||||||
|
AddressDimension.INSTANCE.absoluteMax())),
|
||||||
|
HyperDirection.DEFAULT, TraceObjectValueQuery::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TraceObjectValueQuery intersecting(String minKey, String maxKey,
|
||||||
|
Lifespan lifespan, RecAddress minAddress, RecAddress maxAddress) {
|
||||||
|
return AbstractHyperBoxQuery.intersecting(
|
||||||
|
new ImmutableValueBox(
|
||||||
|
new ValueTriple(
|
||||||
|
ParentKeyDimension.INSTANCE.absoluteMin(),
|
||||||
|
ChildKeyDimension.INSTANCE.absoluteMin(),
|
||||||
|
minKey, lifespan.lmin(), minAddress),
|
||||||
|
new ValueTriple(
|
||||||
|
ParentKeyDimension.INSTANCE.absoluteMax(),
|
||||||
|
ChildKeyDimension.INSTANCE.absoluteMax(),
|
||||||
|
maxKey, lifespan.lmax(), maxAddress)),
|
||||||
|
HyperDirection.DEFAULT, TraceObjectValueQuery::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TraceObjectValueQuery intersecting(String minKey, String maxKey,
|
||||||
|
Lifespan lifespan, AddressRange range) {
|
||||||
|
return intersecting(minKey, maxKey, lifespan,
|
||||||
|
RecAddress.fromAddress(range.getMinAddress()),
|
||||||
|
RecAddress.fromAddress(range.getMaxAddress()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TraceObjectValueQuery intersecting(Lifespan lifespan, AddressRange range) {
|
||||||
|
return intersecting(EntryKeyDimension.INSTANCE.absoluteMin(),
|
||||||
|
EntryKeyDimension.INSTANCE.absoluteMax(), lifespan, range);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TraceObjectValueQuery at(String entryKey, long snap, Address address) {
|
||||||
|
return AbstractHyperBoxQuery.intersecting(
|
||||||
|
new ImmutableValueBox(
|
||||||
|
new ValueTriple(
|
||||||
|
ParentKeyDimension.INSTANCE.absoluteMin(),
|
||||||
|
ChildKeyDimension.INSTANCE.absoluteMin(),
|
||||||
|
entryKey,
|
||||||
|
snap,
|
||||||
|
RecAddress.fromAddress(address)),
|
||||||
|
new ValueTriple(
|
||||||
|
ParentKeyDimension.INSTANCE.absoluteMax(),
|
||||||
|
ChildKeyDimension.INSTANCE.absoluteMax(),
|
||||||
|
entryKey,
|
||||||
|
snap,
|
||||||
|
RecAddress.fromAddress(address))),
|
||||||
|
HyperDirection.DEFAULT, TraceObjectValueQuery::new);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/* ###
|
||||||
|
* 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 ghidra.util.database.spatial.hyper.HyperBox;
|
||||||
|
|
||||||
|
public interface ValueBox extends HyperBox<ValueTriple, ValueBox> {
|
||||||
|
@Override
|
||||||
|
default ValueBox immutable(ValueTriple lCorner, ValueTriple uCorner) {
|
||||||
|
return new ImmutableValueBox(lCorner, uCorner);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ValueBox getBounds() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ValueSpace space() {
|
||||||
|
return ValueSpace.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String description() {
|
||||||
|
return new ImmutableValueBox(this).toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
/* ###
|
||||||
|
* 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 ghidra.program.model.address.*;
|
||||||
|
import ghidra.trace.model.Lifespan;
|
||||||
|
import ghidra.util.database.spatial.BoundedShape;
|
||||||
|
|
||||||
|
public interface ValueShape extends BoundedShape<ValueBox> {
|
||||||
|
DBTraceObject getParent();
|
||||||
|
|
||||||
|
DBTraceObject getChild();
|
||||||
|
|
||||||
|
DBTraceObject getChildOrNull();
|
||||||
|
|
||||||
|
String getEntryKey();
|
||||||
|
|
||||||
|
Lifespan getLifespan();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the value is an address or range, the id of the address space
|
||||||
|
*
|
||||||
|
* @return the space id, or -1 for non-address value
|
||||||
|
*/
|
||||||
|
int getAddressSpaceId();
|
||||||
|
|
||||||
|
long getMinAddressOffset();
|
||||||
|
|
||||||
|
long getMaxAddressOffset();
|
||||||
|
|
||||||
|
default Address getMinAddress(AddressFactory factory) {
|
||||||
|
int spaceId = getAddressSpaceId();
|
||||||
|
if (spaceId == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
AddressSpace space = factory.getAddressSpace(spaceId);
|
||||||
|
return space.getAddress(getMinAddressOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
default Address getMaxAddress(AddressFactory factory) {
|
||||||
|
int spaceId = getAddressSpaceId();
|
||||||
|
if (spaceId == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
AddressSpace space = factory.getAddressSpace(spaceId);
|
||||||
|
return space.getAddress(getMaxAddressOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
default AddressRange getRange(AddressFactory factory) {
|
||||||
|
Address min = getMinAddress(factory);
|
||||||
|
if (min == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new AddressRangeImpl(min, getMaxAddress(factory));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,213 @@
|
||||||
|
/* ###
|
||||||
|
* 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.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import ghidra.util.database.DBCachedObjectStoreFactory.RecAddress;
|
||||||
|
import ghidra.util.database.spatial.hyper.*;
|
||||||
|
|
||||||
|
enum ValueSpace implements EuclideanHyperSpace<ValueTriple, ValueBox> {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
enum ParentKeyDimension implements ULongDimension<ValueTriple, ValueBox> {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
public static final HyperDirection FORWARD = new HyperDirection(0, true);
|
||||||
|
public static final HyperDirection BACKWARD = new HyperDirection(0, false);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long value(ValueTriple point) {
|
||||||
|
return point.parentKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ChildKeyDimension implements ULongDimension<ValueTriple, ValueBox> {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
public static final HyperDirection FORWARD = new HyperDirection(1, true);
|
||||||
|
public static final HyperDirection BACKWARD = new HyperDirection(1, false);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long value(ValueTriple point) {
|
||||||
|
return point.childKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EntryKeyDimension implements StringDimension<ValueTriple, ValueBox> {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
public static final HyperDirection FORWARD = new HyperDirection(2, true);
|
||||||
|
public static final HyperDirection BACKWARD = new HyperDirection(2, false);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String value(ValueTriple point) {
|
||||||
|
return point.entryKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SnapDimension implements LongDimension<ValueTriple, ValueBox> {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
public static final HyperDirection FORWARD = new HyperDirection(3, true);
|
||||||
|
public static final HyperDirection BACKWARD = new HyperDirection(3, false);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long value(ValueTriple point) {
|
||||||
|
return point.snap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AddressDimension implements Dimension<RecAddress, ValueTriple, ValueBox> {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
static final BigInteger MASK_32 = BigInteger.ONE.shiftLeft(64).subtract(BigInteger.ONE);
|
||||||
|
static final BigInteger MASK_64 = BigInteger.ONE.shiftLeft(64).subtract(BigInteger.ONE);
|
||||||
|
|
||||||
|
public static final HyperDirection FORWARD = new HyperDirection(4, true);
|
||||||
|
public static final HyperDirection BACKWARD = new HyperDirection(4, false);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RecAddress value(ValueTriple point) {
|
||||||
|
return point.address();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(RecAddress a, RecAddress b) {
|
||||||
|
int c = Integer.compareUnsigned(a.spaceId(), b.spaceId());
|
||||||
|
if (c != 0) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return Long.compareUnsigned(a.offset(), b.offset());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double distance(RecAddress a, RecAddress b) {
|
||||||
|
double result = b.spaceId() - a.spaceId();
|
||||||
|
result *= Math.pow(2, 64);
|
||||||
|
result += (b.offset() - a.offset());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BigInteger addrToBigInt(RecAddress a) {
|
||||||
|
return BigInteger.valueOf(a.spaceId())
|
||||||
|
.and(MASK_32)
|
||||||
|
.shiftLeft(64)
|
||||||
|
.add(BigInteger.valueOf(a.offset()).and(MASK_64));
|
||||||
|
}
|
||||||
|
|
||||||
|
static RecAddress bigIntToAddr(BigInteger i) {
|
||||||
|
return new RecAddress(i.shiftRight(64).intValue(), i.longValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RecAddress mid(RecAddress a, RecAddress b) {
|
||||||
|
BigInteger biA = addrToBigInt(a);
|
||||||
|
BigInteger biB = addrToBigInt(b);
|
||||||
|
BigInteger biMid = biA.add((biB.subtract(biA)).shiftRight(1));
|
||||||
|
return bigIntToAddr(biMid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final RecAddress MIN = new RecAddress(0, 0);
|
||||||
|
private static final RecAddress MAX = new RecAddress(-1, -1);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RecAddress absoluteMin() {
|
||||||
|
return MIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RecAddress absoluteMax() {
|
||||||
|
return MAX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final List<Dimension<?, ValueTriple, ValueBox>> DIMENSIONS = List.of(
|
||||||
|
ParentKeyDimension.INSTANCE,
|
||||||
|
ChildKeyDimension.INSTANCE,
|
||||||
|
EntryKeyDimension.INSTANCE,
|
||||||
|
SnapDimension.INSTANCE,
|
||||||
|
AddressDimension.INSTANCE);
|
||||||
|
|
||||||
|
static final ValueBox FULL = new ImmutableValueBox(
|
||||||
|
new ValueTriple(
|
||||||
|
ParentKeyDimension.INSTANCE.absoluteMin(),
|
||||||
|
ChildKeyDimension.INSTANCE.absoluteMin(),
|
||||||
|
EntryKeyDimension.INSTANCE.absoluteMin(),
|
||||||
|
SnapDimension.INSTANCE.absoluteMin(),
|
||||||
|
AddressDimension.INSTANCE.absoluteMin()),
|
||||||
|
new ValueTriple(
|
||||||
|
ParentKeyDimension.INSTANCE.absoluteMax(),
|
||||||
|
ChildKeyDimension.INSTANCE.absoluteMax(),
|
||||||
|
EntryKeyDimension.INSTANCE.absoluteMax(),
|
||||||
|
SnapDimension.INSTANCE.absoluteMax(),
|
||||||
|
AddressDimension.INSTANCE.absoluteMax()));
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Dimension<?, ValueTriple, ValueBox>> getDimensions() {
|
||||||
|
return DIMENSIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueBox getFull() {
|
||||||
|
return FULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueTriple boxCenter(ValueBox box) {
|
||||||
|
return new ValueTriple(
|
||||||
|
ParentKeyDimension.INSTANCE.boxMid(box),
|
||||||
|
ChildKeyDimension.INSTANCE.boxMid(box),
|
||||||
|
EntryKeyDimension.INSTANCE.boxMid(box),
|
||||||
|
SnapDimension.INSTANCE.boxMid(box),
|
||||||
|
AddressDimension.INSTANCE.boxMid(box));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueBox boxUnionBounds(ValueBox a, ValueBox b) {
|
||||||
|
ValueTriple lc = new ValueTriple(
|
||||||
|
ParentKeyDimension.INSTANCE.unionLower(a, b),
|
||||||
|
ChildKeyDimension.INSTANCE.unionLower(a, b),
|
||||||
|
EntryKeyDimension.INSTANCE.unionLower(a, b),
|
||||||
|
SnapDimension.INSTANCE.unionLower(a, b),
|
||||||
|
AddressDimension.INSTANCE.unionLower(a, b));
|
||||||
|
ValueTriple uc = new ValueTriple(
|
||||||
|
ParentKeyDimension.INSTANCE.unionUpper(a, b),
|
||||||
|
ChildKeyDimension.INSTANCE.unionUpper(a, b),
|
||||||
|
EntryKeyDimension.INSTANCE.unionUpper(a, b),
|
||||||
|
SnapDimension.INSTANCE.unionUpper(a, b),
|
||||||
|
AddressDimension.INSTANCE.unionUpper(a, b));
|
||||||
|
return new ImmutableValueBox(lc, uc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueBox boxIntersection(ValueBox a, ValueBox b) {
|
||||||
|
ValueTriple lc = new ValueTriple(
|
||||||
|
ParentKeyDimension.INSTANCE.intersectionLower(a, b),
|
||||||
|
ChildKeyDimension.INSTANCE.intersectionLower(a, b),
|
||||||
|
EntryKeyDimension.INSTANCE.intersectionLower(a, b),
|
||||||
|
SnapDimension.INSTANCE.intersectionLower(a, b),
|
||||||
|
AddressDimension.INSTANCE.intersectionLower(a, b));
|
||||||
|
ValueTriple uc = new ValueTriple(
|
||||||
|
ParentKeyDimension.INSTANCE.intersectionUpper(a, b),
|
||||||
|
ChildKeyDimension.INSTANCE.intersectionUpper(a, b),
|
||||||
|
EntryKeyDimension.INSTANCE.intersectionUpper(a, b),
|
||||||
|
SnapDimension.INSTANCE.intersectionUpper(a, b),
|
||||||
|
AddressDimension.INSTANCE.intersectionUpper(a, b));
|
||||||
|
return new ImmutableValueBox(lc, uc);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/* ###
|
||||||
|
* 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.Objects;
|
||||||
|
|
||||||
|
import ghidra.util.database.DBCachedObjectStoreFactory.RecAddress;
|
||||||
|
import ghidra.util.database.spatial.hyper.HyperPoint;
|
||||||
|
|
||||||
|
record ValueTriple(long parentKey, long childKey, String entryKey, long snap, RecAddress address)
|
||||||
|
implements HyperPoint {
|
||||||
|
public ValueTriple(long parentKey, long childKey, String entryKey, long snap,
|
||||||
|
RecAddress address) {
|
||||||
|
this.parentKey = parentKey;
|
||||||
|
this.childKey = childKey;
|
||||||
|
this.entryKey = entryKey;
|
||||||
|
this.snap = snap;
|
||||||
|
this.address = Objects.requireNonNull(address);
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,6 +50,6 @@ public enum AllPathsVisitor implements SpanIntersectingVisitor {
|
||||||
@Override
|
@Override
|
||||||
public Stream<? extends TraceObjectValue> continueValues(TraceObject object,
|
public Stream<? extends TraceObjectValue> continueValues(TraceObject object,
|
||||||
Lifespan span, TraceObjectValPath path) {
|
Lifespan span, TraceObjectValPath path) {
|
||||||
return object.getParents().stream().filter(v -> !path.contains(v));
|
return object.getParents(span).stream().filter(v -> !path.contains(v));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ public class AncestorsRelativeVisitor implements SpanIntersectingVisitor {
|
||||||
return Stream.empty();
|
return Stream.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
return object.getParents()
|
return object.getParents(span)
|
||||||
.stream()
|
.stream()
|
||||||
.filter(v -> PathPredicates.anyMatches(prevKeys, v.getEntryKey()));
|
.filter(v -> PathPredicates.anyMatches(prevKeys, v.getEntryKey()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,6 @@ public class AncestorsRootVisitor implements SpanIntersectingVisitor {
|
||||||
* Can't really filter the parent values by predicates here, since the predicates are not
|
* Can't really filter the parent values by predicates here, since the predicates are not
|
||||||
* matching relative paths, but canonical paths.
|
* matching relative paths, but canonical paths.
|
||||||
*/
|
*/
|
||||||
return object.getParents().stream().filter(v -> !path.contains(v));
|
return object.getParents(span).stream().filter(v -> !path.contains(v));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ public class CanonicalSuccessorsRelativeVisitor implements Visitor {
|
||||||
|
|
||||||
Stream<? extends TraceObjectValue> attrStream;
|
Stream<? extends TraceObjectValue> attrStream;
|
||||||
if (nextKeys.contains("")) {
|
if (nextKeys.contains("")) {
|
||||||
attrStream = object.getAttributes().stream().filter(TraceObjectValue::isCanonical);
|
attrStream = object.getAttributes(span).stream().filter(TraceObjectValue::isCanonical);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
attrStream = Stream.empty();
|
attrStream = Stream.empty();
|
||||||
|
@ -81,7 +81,7 @@ public class CanonicalSuccessorsRelativeVisitor implements Visitor {
|
||||||
|
|
||||||
Stream<? extends TraceObjectValue> elemStream;
|
Stream<? extends TraceObjectValue> elemStream;
|
||||||
if (nextKeys.contains("[]")) {
|
if (nextKeys.contains("[]")) {
|
||||||
elemStream = object.getElements().stream().filter(TraceObjectValue::isCanonical);
|
elemStream = object.getElements(span).stream().filter(TraceObjectValue::isCanonical);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
elemStream = Stream.empty();
|
elemStream = Stream.empty();
|
||||||
|
|
|
@ -61,9 +61,7 @@ public class SuccessorsRelativeVisitor implements SpanIntersectingVisitor {
|
||||||
|
|
||||||
Stream<? extends TraceObjectValue> attrStream;
|
Stream<? extends TraceObjectValue> attrStream;
|
||||||
if (nextKeys.contains("")) {
|
if (nextKeys.contains("")) {
|
||||||
attrStream = object.getAttributes()
|
attrStream = object.getAttributes(span).stream();
|
||||||
.stream()
|
|
||||||
.filter(v -> span.intersects(v.getLifespan()));
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
attrStream = Stream.empty();
|
attrStream = Stream.empty();
|
||||||
|
@ -71,9 +69,7 @@ public class SuccessorsRelativeVisitor implements SpanIntersectingVisitor {
|
||||||
|
|
||||||
Stream<? extends TraceObjectValue> elemStream;
|
Stream<? extends TraceObjectValue> elemStream;
|
||||||
if (nextKeys.contains("[]")) {
|
if (nextKeys.contains("[]")) {
|
||||||
elemStream = object.getElements()
|
elemStream = object.getElements(span).stream();
|
||||||
.stream()
|
|
||||||
.filter(v -> span.intersects(v.getLifespan()));
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
elemStream = Stream.empty();
|
elemStream = Stream.empty();
|
||||||
|
|
|
@ -395,6 +395,12 @@ public sealed interface Lifespan extends Span<Long, Lifespan>, Iterable<Long> {
|
||||||
* An interval tree implementing {@link MutableLifeSet}
|
* An interval tree implementing {@link MutableLifeSet}
|
||||||
*/
|
*/
|
||||||
public class DefaultLifeSet extends DefaultSpanSet<Long, Lifespan> implements MutableLifeSet {
|
public class DefaultLifeSet extends DefaultSpanSet<Long, Lifespan> implements MutableLifeSet {
|
||||||
|
public static DefaultLifeSet copyOf(LifeSet set) {
|
||||||
|
DefaultLifeSet copy = new DefaultLifeSet();
|
||||||
|
copy.addAll(set);
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
public DefaultLifeSet() {
|
public DefaultLifeSet() {
|
||||||
super(Lifespan.DOMAIN);
|
super(Lifespan.DOMAIN);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,28 +16,23 @@
|
||||||
package ghidra.trace.model;
|
package ghidra.trace.model;
|
||||||
|
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.util.TimedMsg;
|
|
||||||
|
|
||||||
public class TraceDomainObjectListener extends TypedEventDispatcher
|
public class TraceDomainObjectListener extends TypedEventDispatcher
|
||||||
implements DomainObjectListener {
|
implements DomainObjectListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void domainObjectChanged(DomainObjectChangedEvent ev) {
|
public void domainObjectChanged(DomainObjectChangedEvent ev) {
|
||||||
//TimedMsg.info(this, "Handing (" + this + "): " + ev);
|
|
||||||
if (restoredHandler != null && ev.containsEvent(DomainObject.DO_OBJECT_RESTORED)) {
|
if (restoredHandler != null && ev.containsEvent(DomainObject.DO_OBJECT_RESTORED)) {
|
||||||
for (DomainObjectChangeRecord rec : ev) {
|
for (DomainObjectChangeRecord rec : ev) {
|
||||||
if (rec.getEventType() == DomainObject.DO_OBJECT_RESTORED) {
|
if (rec.getEventType() == DomainObject.DO_OBJECT_RESTORED) {
|
||||||
restoredHandler.accept(rec);
|
restoredHandler.accept(rec);
|
||||||
TimedMsg.debug(this, " Done: OBJECT_RESTORED");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
}
|
}
|
||||||
//Map<String, Integer> CountsByType = new TreeMap<>();
|
|
||||||
for (DomainObjectChangeRecord rec : ev) {
|
for (DomainObjectChangeRecord rec : ev) {
|
||||||
handleChangeRecord(rec);
|
handleChangeRecord(rec);
|
||||||
}
|
}
|
||||||
//TimedMsg.info(this, " Done: " + CountsByType);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,18 +202,19 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
<I extends TraceObjectInterface> I queryInterface(Class<I> ifClass);
|
<I extends TraceObjectInterface> I queryInterface(Class<I> ifClass);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all values whose child is this object
|
* Get all values intersecting the given span and whose child is this object
|
||||||
*
|
*
|
||||||
|
* @param span the span
|
||||||
* @return the parent values
|
* @return the parent values
|
||||||
*/
|
*/
|
||||||
Collection<? extends TraceObjectValue> getParents();
|
Collection<? extends TraceObjectValue> getParents(Lifespan span);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all values (elements and attributes) of this object
|
* Get all values (elements and attributes) of this object intersecting the given span
|
||||||
*
|
*
|
||||||
* @return the values
|
* @return the values
|
||||||
*/
|
*/
|
||||||
Collection<? extends TraceObjectValue> getValues();
|
Collection<? extends TraceObjectValue> getValues(Lifespan span);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get values with the given key intersecting the given span
|
* Get values with the given key intersecting the given span
|
||||||
|
@ -236,18 +237,18 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
boolean forward);
|
boolean forward);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all elements of this object
|
* Get all elements of this object intersecting the given span
|
||||||
*
|
*
|
||||||
* @return the element values
|
* @return the element values
|
||||||
*/
|
*/
|
||||||
Collection<? extends TraceObjectValue> getElements();
|
Collection<? extends TraceObjectValue> getElements(Lifespan span);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all attributes of this object
|
* Get all attributes of this object intersecting the given span
|
||||||
*
|
*
|
||||||
* @return the attribute values
|
* @return the attribute values
|
||||||
*/
|
*/
|
||||||
Collection<? extends TraceObjectValue> getAttributes();
|
Collection<? extends TraceObjectValue> getAttributes(Lifespan span);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value for the given snap and key
|
* Get the value for the given snap and key
|
||||||
|
|
|
@ -139,10 +139,23 @@ public interface TraceObjectManager {
|
||||||
*
|
*
|
||||||
* @param span the span that desired values lifespans must intersect
|
* @param span the span that desired values lifespans must intersect
|
||||||
* @param range the range that desired address-ranged values must intersect
|
* @param range the range that desired address-ranged values must intersect
|
||||||
|
* @param entryKey the entry key if a single one should be matched, or null for any
|
||||||
* @return the collection of values
|
* @return the collection of values
|
||||||
*/
|
*/
|
||||||
Collection<? extends TraceObjectValue> getValuesIntersecting(Lifespan span,
|
Collection<? extends TraceObjectValue> getValuesIntersecting(Lifespan span,
|
||||||
AddressRange range);
|
AddressRange range, String entryKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all address-ranged values intersecting the given span and address range
|
||||||
|
*
|
||||||
|
* @param span the span that desired values lifespans must intersect
|
||||||
|
* @param range the range that desired address-ranged values must intersect
|
||||||
|
* @return the collection of values
|
||||||
|
*/
|
||||||
|
default Collection<? extends TraceObjectValue> getValuesIntersecting(Lifespan span,
|
||||||
|
AddressRange range) {
|
||||||
|
return getValuesIntersecting(span, range, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all interfaces of the given type in the database
|
* Get all interfaces of the given type in the database
|
||||||
|
|
|
@ -54,7 +54,7 @@ public enum TraceObjectInterfaceUtils {
|
||||||
public static void setLifespan(Class<? extends TraceObjectInterface> traceIf,
|
public static void setLifespan(Class<? extends TraceObjectInterface> traceIf,
|
||||||
TraceObject object, Lifespan lifespan) throws DuplicateNameException {
|
TraceObject object, Lifespan lifespan) throws DuplicateNameException {
|
||||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
for (TraceObjectValue val : object.getParents()) {
|
for (TraceObjectValue val : object.getParents(Lifespan.ALL)) {
|
||||||
if (val.isCanonical() && !val.isDeleted()) {
|
if (val.isCanonical() && !val.isDeleted()) {
|
||||||
val.setLifespan(lifespan, ConflictResolution.DENY);
|
val.setLifespan(lifespan, ConflictResolution.DENY);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,10 @@ package ghidra.trace.database.target;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -31,6 +33,7 @@ import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||||
import ghidra.dbg.target.schema.XmlSchemaContext;
|
import ghidra.dbg.target.schema.XmlSchemaContext;
|
||||||
import ghidra.dbg.util.PathPredicates;
|
import ghidra.dbg.util.PathPredicates;
|
||||||
import ghidra.dbg.util.PathUtils;
|
import ghidra.dbg.util.PathUtils;
|
||||||
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
||||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||||
import ghidra.trace.model.Lifespan;
|
import ghidra.trace.model.Lifespan;
|
||||||
|
@ -111,22 +114,22 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
TraceObjectKeyPath pathTargets = TraceObjectKeyPath.of("Targets");
|
TraceObjectKeyPath pathTargets = TraceObjectKeyPath.of("Targets");
|
||||||
targetContainer = manager.createObject(pathTargets);
|
targetContainer = manager.createObject(pathTargets);
|
||||||
root.setAttribute(Lifespan.nowOn(0), "Targets", targetContainer);
|
root.setAttribute(Lifespan.nowOn(0), "Targets", targetContainer);
|
||||||
dumpStore(manager.valueStore);
|
dumpStore(manager.valueTree.getDataStore());
|
||||||
|
|
||||||
for (int i = 0; i < targetCount; i++) {
|
for (int i = 0; i < targetCount; i++) {
|
||||||
Lifespan lifespan = Lifespan.nowOn(i);
|
Lifespan lifespan = Lifespan.nowOn(i);
|
||||||
TraceObject target = manager.createObject(pathTargets.index(i));
|
TraceObject target = manager.createObject(pathTargets.index(i));
|
||||||
target.setAttribute(Lifespan.ALL, "self", target);
|
target.setAttribute(Lifespan.ALL, "self", target);
|
||||||
dumpStore(manager.valueStore);
|
dumpStore(manager.valueTree.getDataStore());
|
||||||
targetContainer.setElement(lifespan, i, target);
|
targetContainer.setElement(lifespan, i, target);
|
||||||
dumpStore(manager.valueStore);
|
dumpStore(manager.valueTree.getDataStore());
|
||||||
targets.add(target);
|
targets.add(target);
|
||||||
root.setAttribute(lifespan, "curTarget", target);
|
root.setAttribute(lifespan, "curTarget", target);
|
||||||
dumpStore(manager.valueStore);
|
dumpStore(manager.valueTree.getDataStore());
|
||||||
}
|
}
|
||||||
|
|
||||||
root.setValue(Lifespan.ALL, "anAttribute", "A primitive string");
|
root.setValue(Lifespan.ALL, "anAttribute", "A primitive string");
|
||||||
dumpStore(manager.valueStore);
|
dumpStore(manager.valueTree.getDataStore());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,8 +271,8 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
TraceObjectValue rangeVal =
|
TraceObjectValue rangeVal =
|
||||||
root.setValue(Lifespan.nowOn(0), "a", b.range(0x1000, 0x1fff));
|
root.setValue(Lifespan.nowOn(0), "a", b.range(0x1000, 0x1fff));
|
||||||
|
|
||||||
assertTrue(root.getValues().contains(rangeVal));
|
assertTrue(root.getValues(Lifespan.at(0)).contains(rangeVal));
|
||||||
assertFalse(targetContainer.getValues().contains(rangeVal));
|
assertFalse(targetContainer.getValues(Lifespan.ALL).contains(rangeVal));
|
||||||
assertEquals(rangeVal, root.getValue(0, "a"));
|
assertEquals(rangeVal, root.getValue(0, "a"));
|
||||||
assertNull(root.getValue(0, "b"));
|
assertNull(root.getValue(0, "b"));
|
||||||
|
|
||||||
|
@ -292,10 +295,14 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
|
|
||||||
assertEquals(Set.of(rangeVal),
|
assertEquals(Set.of(rangeVal),
|
||||||
Set.copyOf(manager.getValuesIntersecting(Lifespan.ALL, b.range(0, -1))));
|
Set.copyOf(manager.getValuesIntersecting(Lifespan.ALL, b.range(0, -1))));
|
||||||
|
assertEquals(Set.of(rangeVal),
|
||||||
|
Set.copyOf(manager.getValuesIntersecting(Lifespan.ALL, b.range(0, -1), "a")));
|
||||||
assertEquals(Set.of(),
|
assertEquals(Set.of(),
|
||||||
Set.copyOf(manager.getValuesIntersecting(Lifespan.toNow(-1), b.range(0, -1))));
|
Set.copyOf(manager.getValuesIntersecting(Lifespan.toNow(-1), b.range(0, -1))));
|
||||||
assertEquals(Set.of(),
|
assertEquals(Set.of(),
|
||||||
Set.copyOf(manager.getValuesIntersecting(Lifespan.ALL, b.range(0, 0xfff))));
|
Set.copyOf(manager.getValuesIntersecting(Lifespan.ALL, b.range(0, 0xfff))));
|
||||||
|
assertEquals(Set.of(),
|
||||||
|
Set.copyOf(manager.getValuesIntersecting(Lifespan.ALL, b.range(0, -1), "b")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -444,32 +451,33 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
public void testGetParents() {
|
public void testGetParents() {
|
||||||
populateModel(3);
|
populateModel(3);
|
||||||
|
|
||||||
assertEquals(1, root.getParents().size());
|
assertEquals(1, root.getParents(Lifespan.ALL).size());
|
||||||
assertEquals(root, Unique.assertOne(targetContainer.getParents()).getParent());
|
assertEquals(root, Unique.assertOne(targetContainer.getParents(Lifespan.ALL)).getParent());
|
||||||
assertEquals(3, targets.get(0).getParents().size()); // curTarget, targetContainer, self
|
assertEquals(3, targets.get(0).getParents(Lifespan.ALL).size());
|
||||||
|
// curTarget, targetContainer, self
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetValues() {
|
public void testGetValues() {
|
||||||
populateModel(3);
|
populateModel(3);
|
||||||
|
|
||||||
assertEquals(3, targetContainer.getValues().size());
|
assertEquals(3, targetContainer.getValues(Lifespan.ALL).size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetElements() {
|
public void testGetElements() {
|
||||||
populateModel(3);
|
populateModel(3);
|
||||||
|
|
||||||
assertEquals(0, root.getElements().size());
|
assertEquals(0, root.getElements(Lifespan.ALL).size());
|
||||||
assertEquals(3, targetContainer.getElements().size());
|
assertEquals(3, targetContainer.getElements(Lifespan.ALL).size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetAttributes() {
|
public void testGetAttributes() {
|
||||||
populateModel(3);
|
populateModel(3);
|
||||||
|
|
||||||
assertEquals(5, root.getAttributes().size()); // Targets, curTarget(x3), string
|
assertEquals(5, root.getAttributes(Lifespan.ALL).size()); // Targets, curTarget(x3), string
|
||||||
assertEquals(0, targetContainer.getAttributes().size());
|
assertEquals(0, targetContainer.getAttributes(Lifespan.ALL).size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -610,7 +618,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
|
|
||||||
assertEquals(valA, root.setValue(Lifespan.span(-10, -1), "a", 1));
|
assertEquals(valA, root.setValue(Lifespan.span(-10, -1), "a", 1));
|
||||||
assertEquals(Lifespan.span(-10, 9), valA.getLifespan());
|
assertEquals(Lifespan.span(-10, 9), valA.getLifespan());
|
||||||
assertEquals(1, root.getValues().size());
|
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -624,7 +632,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
assertEquals(valA,
|
assertEquals(valA,
|
||||||
root.setValue(Lifespan.span(-10, -1), "a", b.range(0x1000, 0x1fff)));
|
root.setValue(Lifespan.span(-10, -1), "a", b.range(0x1000, 0x1fff)));
|
||||||
assertEquals(Lifespan.span(-10, 9), valA.getLifespan());
|
assertEquals(Lifespan.span(-10, 9), valA.getLifespan());
|
||||||
assertEquals(1, root.getValues().size());
|
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -636,7 +644,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
|
|
||||||
assertEquals(valA, root.setValue(Lifespan.span(10, 19), "a", 1));
|
assertEquals(valA, root.setValue(Lifespan.span(10, 19), "a", 1));
|
||||||
assertEquals(Lifespan.span(0, 19), valA.getLifespan());
|
assertEquals(Lifespan.span(0, 19), valA.getLifespan());
|
||||||
assertEquals(1, root.getValues().size());
|
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -648,7 +656,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
|
|
||||||
assertEquals(valA, root.setValue(Lifespan.span(-5, 4), "a", 1));
|
assertEquals(valA, root.setValue(Lifespan.span(-5, 4), "a", 1));
|
||||||
assertEquals(Lifespan.span(-5, 9), valA.getLifespan());
|
assertEquals(Lifespan.span(-5, 9), valA.getLifespan());
|
||||||
assertEquals(1, root.getValues().size());
|
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -660,7 +668,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
|
|
||||||
assertEquals(valA, root.setValue(Lifespan.span(5, 14), "a", 1));
|
assertEquals(valA, root.setValue(Lifespan.span(5, 14), "a", 1));
|
||||||
assertEquals(Lifespan.span(0, 14), valA.getLifespan());
|
assertEquals(Lifespan.span(0, 14), valA.getLifespan());
|
||||||
assertEquals(1, root.getValues().size());
|
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -672,7 +680,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
|
|
||||||
assertEquals(valA, root.setValue(Lifespan.span(0, 9), "a", 1));
|
assertEquals(valA, root.setValue(Lifespan.span(0, 9), "a", 1));
|
||||||
assertEquals(Lifespan.span(0, 9), valA.getLifespan());
|
assertEquals(Lifespan.span(0, 9), valA.getLifespan());
|
||||||
assertEquals(1, root.getValues().size());
|
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -684,11 +692,11 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
|
|
||||||
assertEquals(valA, root.setValue(Lifespan.at(5), "a", 1));
|
assertEquals(valA, root.setValue(Lifespan.at(5), "a", 1));
|
||||||
assertEquals(Lifespan.span(0, 9), valA.getLifespan());
|
assertEquals(Lifespan.span(0, 9), valA.getLifespan());
|
||||||
assertEquals(1, root.getValues().size());
|
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||||
|
|
||||||
assertEquals(valA, root.setValue(Lifespan.span(-5, 14), "a", 1));
|
assertEquals(valA, root.setValue(Lifespan.span(-5, 14), "a", 1));
|
||||||
assertEquals(Lifespan.span(-5, 14), valA.getLifespan());
|
assertEquals(Lifespan.span(-5, 14), valA.getLifespan());
|
||||||
assertEquals(1, root.getValues().size());
|
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -700,11 +708,11 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
|
|
||||||
assertEquals(valA, root.setValue(Lifespan.span(0, 5), "a", 1));
|
assertEquals(valA, root.setValue(Lifespan.span(0, 5), "a", 1));
|
||||||
assertEquals(Lifespan.span(0, 9), valA.getLifespan());
|
assertEquals(Lifespan.span(0, 9), valA.getLifespan());
|
||||||
assertEquals(1, root.getValues().size());
|
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||||
|
|
||||||
assertEquals(valA, root.setValue(Lifespan.span(0, 14), "a", 1));
|
assertEquals(valA, root.setValue(Lifespan.span(0, 14), "a", 1));
|
||||||
assertEquals(Lifespan.span(0, 14), valA.getLifespan());
|
assertEquals(Lifespan.span(0, 14), valA.getLifespan());
|
||||||
assertEquals(1, root.getValues().size());
|
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -716,11 +724,11 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
|
|
||||||
assertEquals(valA, root.setValue(Lifespan.span(5, 9), "a", 1));
|
assertEquals(valA, root.setValue(Lifespan.span(5, 9), "a", 1));
|
||||||
assertEquals(Lifespan.span(0, 9), valA.getLifespan());
|
assertEquals(Lifespan.span(0, 9), valA.getLifespan());
|
||||||
assertEquals(1, root.getValues().size());
|
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||||
|
|
||||||
assertEquals(valA, root.setValue(Lifespan.span(-5, 9), "a", 1));
|
assertEquals(valA, root.setValue(Lifespan.span(-5, 9), "a", 1));
|
||||||
assertEquals(Lifespan.span(-5, 9), valA.getLifespan());
|
assertEquals(Lifespan.span(-5, 9), valA.getLifespan());
|
||||||
assertEquals(1, root.getValues().size());
|
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -731,12 +739,12 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
TraceObjectValue valA = root.setValue(Lifespan.span(0, 9), "a", 1);
|
TraceObjectValue valA = root.setValue(Lifespan.span(0, 9), "a", 1);
|
||||||
TraceObjectValue valB = root.setValue(Lifespan.span(20, 29), "a", 1);
|
TraceObjectValue valB = root.setValue(Lifespan.span(20, 29), "a", 1);
|
||||||
assertNotSame(valA, valB);
|
assertNotSame(valA, valB);
|
||||||
assertEquals(2, root.getValues().size());
|
assertEquals(2, root.getValues(Lifespan.ALL).size());
|
||||||
|
|
||||||
assertEquals(valA, root.setValue(Lifespan.span(10, 19), "a", 1));
|
assertEquals(valA, root.setValue(Lifespan.span(10, 19), "a", 1));
|
||||||
assertEquals(Lifespan.span(0, 29), valA.getLifespan());
|
assertEquals(Lifespan.span(0, 29), valA.getLifespan());
|
||||||
assertTrue(valB.isDeleted());
|
assertTrue(valB.isDeleted());
|
||||||
assertEquals(1, root.getValues().size());
|
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -825,13 +833,13 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
try (Transaction tx = b.startTransaction()) {
|
try (Transaction tx = b.startTransaction()) {
|
||||||
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
||||||
assertNull(root.setValue(Lifespan.span(0, 9), "a", null));
|
assertNull(root.setValue(Lifespan.span(0, 9), "a", null));
|
||||||
assertEquals(0, root.getValues().size());
|
assertEquals(0, root.getValues(Lifespan.ALL).size());
|
||||||
|
|
||||||
assertNotNull(root.setValue(Lifespan.span(0, 9), "a", 1));
|
assertNotNull(root.setValue(Lifespan.span(0, 9), "a", 1));
|
||||||
assertEquals(1, root.getValues().size());
|
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||||
|
|
||||||
assertNull(root.setValue(Lifespan.at(5), "a", null));
|
assertNull(root.setValue(Lifespan.at(5), "a", null));
|
||||||
assertEquals(2, root.getValues().size());
|
assertEquals(2, root.getValues(Lifespan.ALL).size());
|
||||||
|
|
||||||
assertEquals(List.of(Lifespan.span(0, 4), Lifespan.span(6, 9)),
|
assertEquals(List.of(Lifespan.span(0, 4), Lifespan.span(6, 9)),
|
||||||
root.getOrderedValues(Lifespan.ALL, "a", true)
|
root.getOrderedValues(Lifespan.ALL, "a", true)
|
||||||
|
@ -845,10 +853,10 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
try (Transaction tx = b.startTransaction()) {
|
try (Transaction tx = b.startTransaction()) {
|
||||||
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
||||||
assertNotNull(root.setValue(Lifespan.span(0, 9), "a", 1));
|
assertNotNull(root.setValue(Lifespan.span(0, 9), "a", 1));
|
||||||
assertEquals(1, root.getValues().size());
|
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||||
|
|
||||||
assertNull(root.setValue(Lifespan.span(0, 9), "a", null));
|
assertNull(root.setValue(Lifespan.span(0, 9), "a", null));
|
||||||
assertEquals(0, root.getValues().size());
|
assertEquals(0, root.getValues(Lifespan.ALL).size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -879,7 +887,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
// Delete a leaf
|
// Delete a leaf
|
||||||
TraceObject t1 = targets.get(1);
|
TraceObject t1 = targets.get(1);
|
||||||
assertFalse(t1.isDeleted());
|
assertFalse(t1.isDeleted());
|
||||||
assertEquals(3, targetContainer.getValues().size());
|
assertEquals(3, targetContainer.getValues(Lifespan.ALL).size());
|
||||||
assertEquals(t1, Unique.assertOne(
|
assertEquals(t1, Unique.assertOne(
|
||||||
manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[1]"))));
|
manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[1]"))));
|
||||||
assertEquals(t1, t1.getAttribute(1, "self").getValue());
|
assertEquals(t1, t1.getAttribute(1, "self").getValue());
|
||||||
|
@ -890,8 +898,8 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTrue(t1.isDeleted());
|
assertTrue(t1.isDeleted());
|
||||||
assertTrue(t1.getParents().isEmpty());
|
assertTrue(t1.getParents(Lifespan.ALL).isEmpty());
|
||||||
assertEquals(2, targetContainer.getValues().size());
|
assertEquals(2, targetContainer.getValues(Lifespan.ALL).size());
|
||||||
assertEquals(0,
|
assertEquals(0,
|
||||||
manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[1]")).count());
|
manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[1]")).count());
|
||||||
assertNull(t1.getAttribute(2, "self"));
|
assertNull(t1.getAttribute(2, "self"));
|
||||||
|
@ -901,13 +909,14 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
TraceObject t0 = targets.get(0);
|
TraceObject t0 = targets.get(0);
|
||||||
assertEquals(2,
|
assertEquals(2,
|
||||||
manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[]")).count());
|
manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[]")).count());
|
||||||
assertTrue(t0.getParents().stream().anyMatch(v -> v.getParent() == targetContainer));
|
assertTrue(
|
||||||
assertEquals(2, targetContainer.getValues().size());
|
t0.getParents(Lifespan.ALL).stream().anyMatch(v -> v.getParent() == targetContainer));
|
||||||
|
assertEquals(2, targetContainer.getValues(Lifespan.ALL).size());
|
||||||
|
|
||||||
b.trace.undo();
|
b.trace.undo();
|
||||||
b.trace.redo();
|
b.trace.redo();
|
||||||
|
|
||||||
assertEquals(2, targetContainer.getValues().size());
|
assertEquals(2, targetContainer.getValues(Lifespan.ALL).size());
|
||||||
|
|
||||||
try (Transaction tx = b.startTransaction()) {
|
try (Transaction tx = b.startTransaction()) {
|
||||||
targetContainer.delete();
|
targetContainer.delete();
|
||||||
|
@ -916,7 +925,8 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
assertEquals(0,
|
assertEquals(0,
|
||||||
manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[]")).count());
|
manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[]")).count());
|
||||||
assertFalse(t0.isDeleted());
|
assertFalse(t0.isDeleted());
|
||||||
assertFalse(t0.getParents().stream().anyMatch(v -> v.getParent() == targetContainer));
|
assertFalse(
|
||||||
|
t0.getParents(Lifespan.ALL).stream().anyMatch(v -> v.getParent() == targetContainer));
|
||||||
// A little odd, but allows branch to be replaced and successors restored later
|
// A little odd, but allows branch to be replaced and successors restored later
|
||||||
assertEquals(t0, root.getValue(0, "curTarget").getValue());
|
assertEquals(t0, root.getValue(0, "curTarget").getValue());
|
||||||
}
|
}
|
||||||
|
@ -1093,4 +1103,76 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
assertTrue(hiddenOutside.getCanonicalParent(0).isHidden());
|
assertTrue(hiddenOutside.getCanonicalParent(0).isHidden());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String randomIdentifier(Random random, int length) {
|
||||||
|
StringBuilder sb = new StringBuilder(length);
|
||||||
|
while (sb.length() < length) {
|
||||||
|
char c = (char) random.nextInt();
|
||||||
|
boolean isValid = sb.isEmpty()
|
||||||
|
? Character.isJavaIdentifierStart(c)
|
||||||
|
: Character.isJavaIdentifierPart(c);
|
||||||
|
if (isValid) {
|
||||||
|
sb.append(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Address randomAddress(Random random) {
|
||||||
|
List<AddressSpace> spaces = Stream.of(b.trace.getBaseAddressFactory().getAllAddressSpaces())
|
||||||
|
.filter(s -> s.isMemorySpace() || s.isRegisterSpace())
|
||||||
|
.toList();
|
||||||
|
AddressSpace space = spaces.get(random.nextInt(spaces.size()));
|
||||||
|
while (true) {
|
||||||
|
BigInteger offset = new BigInteger(space.getSize(), random);
|
||||||
|
try {
|
||||||
|
return space.getAddress(offset.longValue());
|
||||||
|
}
|
||||||
|
catch (AddressOutOfBoundsException e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Lifespan randomLifespan(Random random) {
|
||||||
|
boolean isNowOn = random.nextInt(4) < 3;
|
||||||
|
if (isNowOn) {
|
||||||
|
return Lifespan.nowOn(random.nextLong(10000));
|
||||||
|
}
|
||||||
|
int length = random.nextInt(10000);
|
||||||
|
long start = random.nextLong(10000);
|
||||||
|
return Lifespan.span(start, start + length);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertSameResult(Collection<TraceObjectValue> values, Lifespan span,
|
||||||
|
AddressRange range) {
|
||||||
|
List<TraceObjectValue> expected = values.stream()
|
||||||
|
.filter(v -> v.getLifespan().intersects(span) && range.contains(v.castValue()))
|
||||||
|
.toList();
|
||||||
|
List<TraceObjectValue> actual =
|
||||||
|
List.copyOf(b.trace.getObjectManager().getValuesIntersecting(span, range));
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testManyAddressValuesAcrossSpaces() {
|
||||||
|
Random random = new Random();
|
||||||
|
List<TraceObjectValue> values = new ArrayList<>();
|
||||||
|
try (Transaction tx = b.startTransaction()) {
|
||||||
|
TraceObjectValue rootVal =
|
||||||
|
manager.createRootObject(ctx.getSchema(new SchemaName("Session")));
|
||||||
|
root = rootVal.getChild();
|
||||||
|
|
||||||
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
String key = randomIdentifier(random, 6);
|
||||||
|
Address addr = randomAddress(random);
|
||||||
|
Lifespan lifespan = randomLifespan(random);
|
||||||
|
|
||||||
|
values.add(root.setAttribute(lifespan, key, addr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.trace.getObjectManager().getValuesIntersecting(Lifespan.ALL, b.range(0, -1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
/* ###
|
||||||
|
* 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 static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import ghidra.trace.database.target.ValueSpace.AddressDimension;
|
||||||
|
import ghidra.trace.database.target.ValueSpace.EntryKeyDimension;
|
||||||
|
import ghidra.util.database.DBCachedObjectStoreFactory.RecAddress;
|
||||||
|
|
||||||
|
public class ValueSpaceTest {
|
||||||
|
@Test
|
||||||
|
public void testAddressDistance() {
|
||||||
|
AddressDimension dim = AddressDimension.INSTANCE;
|
||||||
|
|
||||||
|
assertEquals(0.0, dim.distance(new RecAddress(0, 0), new RecAddress(0, 0)), 0.0);
|
||||||
|
|
||||||
|
assertEquals(100.0, dim.distance(new RecAddress(0, 0), new RecAddress(0, 100)), 0.0);
|
||||||
|
assertEquals(-100.0, dim.distance(new RecAddress(0, 100), new RecAddress(0, 0)), 0.0);
|
||||||
|
assertEquals(Math.pow(2, 65), dim.distance(new RecAddress(0, 0), new RecAddress(2, 0)),
|
||||||
|
0.0);
|
||||||
|
assertEquals(-Math.pow(2, 65), dim.distance(new RecAddress(2, 0), new RecAddress(0, 0)),
|
||||||
|
0.0);
|
||||||
|
|
||||||
|
// 10000 instead of 100, because double precision will not detect just 100
|
||||||
|
assertEquals(Math.pow(2, 65) + 10000,
|
||||||
|
dim.distance(new RecAddress(0, 0), new RecAddress(2, 10000)), 0.0);
|
||||||
|
assertEquals(-Math.pow(2, 65) - 10000,
|
||||||
|
dim.distance(new RecAddress(2, 10000), new RecAddress(0, 0)), 0.0);
|
||||||
|
assertEquals(Math.pow(2, 65) - 10000,
|
||||||
|
dim.distance(new RecAddress(0, 10000), new RecAddress(2, 0)), 0.0);
|
||||||
|
assertEquals(-Math.pow(2, 65) + 10000,
|
||||||
|
dim.distance(new RecAddress(2, 0), new RecAddress(0, 10000)), 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddressMid() {
|
||||||
|
AddressDimension dim = AddressDimension.INSTANCE;
|
||||||
|
|
||||||
|
assertEquals(new RecAddress(0, 100),
|
||||||
|
dim.mid(new RecAddress(0, 100), new RecAddress(0, 100)));
|
||||||
|
|
||||||
|
assertEquals(new RecAddress(0, 50), dim.mid(new RecAddress(0, 0), new RecAddress(0, 100)));
|
||||||
|
assertEquals(new RecAddress(0, 50), dim.mid(new RecAddress(0, 100), new RecAddress(0, 0)));
|
||||||
|
|
||||||
|
assertEquals(new RecAddress(0, 49), dim.mid(new RecAddress(0, 0), new RecAddress(0, 99)));
|
||||||
|
assertEquals(new RecAddress(0, 49), dim.mid(new RecAddress(0, 99), new RecAddress(0, 0)));
|
||||||
|
|
||||||
|
assertEquals(new RecAddress(1, 0), dim.mid(new RecAddress(0, 0), new RecAddress(2, 0)));
|
||||||
|
assertEquals(new RecAddress(1, 0), dim.mid(new RecAddress(2, 0), new RecAddress(0, 0)));
|
||||||
|
|
||||||
|
assertEquals(new RecAddress(1, 50), dim.mid(new RecAddress(0, 0), new RecAddress(2, 100)));
|
||||||
|
assertEquals(new RecAddress(1, 50), dim.mid(new RecAddress(2, 100), new RecAddress(0, 0)));
|
||||||
|
assertEquals(new RecAddress(1, 50), dim.mid(new RecAddress(0, 100), new RecAddress(2, 0)));
|
||||||
|
assertEquals(new RecAddress(1, 50), dim.mid(new RecAddress(2, 0), new RecAddress(0, 100)));
|
||||||
|
|
||||||
|
assertEquals(new RecAddress(0, Long.MIN_VALUE),
|
||||||
|
dim.mid(new RecAddress(0, 0), new RecAddress(1, 0)));
|
||||||
|
assertEquals(new RecAddress(0, Long.MIN_VALUE),
|
||||||
|
dim.mid(new RecAddress(1, 0), new RecAddress(0, 0)));
|
||||||
|
|
||||||
|
assertEquals(new RecAddress(0, Long.MAX_VALUE),
|
||||||
|
dim.mid(new RecAddress(0, 0), new RecAddress(0, -1)));
|
||||||
|
assertEquals(new RecAddress(0, Long.MAX_VALUE),
|
||||||
|
dim.mid(new RecAddress(0, -1), new RecAddress(0, 0)));
|
||||||
|
|
||||||
|
assertEquals(new RecAddress(0, Long.MIN_VALUE + 50),
|
||||||
|
dim.mid(new RecAddress(0, 0), new RecAddress(1, 100)));
|
||||||
|
assertEquals(new RecAddress(0, Long.MIN_VALUE + 50),
|
||||||
|
dim.mid(new RecAddress(1, 100), new RecAddress(0, 0)));
|
||||||
|
assertEquals(new RecAddress(0, Long.MIN_VALUE + 50),
|
||||||
|
dim.mid(new RecAddress(0, 100), new RecAddress(1, 0)));
|
||||||
|
assertEquals(new RecAddress(0, Long.MIN_VALUE + 50),
|
||||||
|
dim.mid(new RecAddress(1, 0), new RecAddress(0, 100)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEntryKeyDistance() {
|
||||||
|
EntryKeyDimension dim = EntryKeyDimension.INSTANCE;
|
||||||
|
|
||||||
|
assertEquals(0.0, dim.distance("", ""), 0.0);
|
||||||
|
assertEquals(0.0, dim.distance("A", "A"), 0.0);
|
||||||
|
// null is not a valid key, but because it's the "absolute max", the tree may ask
|
||||||
|
assertEquals(0.0, dim.distance(null, null), 0.0);
|
||||||
|
|
||||||
|
assertEquals(25.0 * (Double.MAX_VALUE / 128), dim.distance("A", "Z"), 0.0);
|
||||||
|
assertEquals(-1.0 * (Double.MAX_VALUE / 128), dim.distance("B", "A"), 0.0);
|
||||||
|
assertEquals(1.0 * (Double.MAX_VALUE / 128), dim.distance("AA", "BA"), 0.0);
|
||||||
|
assertEquals(-1.0 * (Double.MAX_VALUE / 128), dim.distance("BA", "AA"), 0.0);
|
||||||
|
|
||||||
|
assertEquals(1.0 * (Double.MAX_VALUE / 128) + 1.0 * (Double.MAX_VALUE / 128 / 128),
|
||||||
|
dim.distance("AA", "BB"), 0.0);
|
||||||
|
assertEquals(-1.0 * (Double.MAX_VALUE / 128) - 1.0 * (Double.MAX_VALUE / 128 / 128),
|
||||||
|
dim.distance("BB", "AA"), 0.0);
|
||||||
|
assertEquals(1.0 * (Double.MAX_VALUE / 128) - 1.0 * (Double.MAX_VALUE / 128 / 128),
|
||||||
|
dim.distance("AB", "BA"), 0.0);
|
||||||
|
assertEquals(-1.0 * (Double.MAX_VALUE / 128) + 1.0 * (Double.MAX_VALUE / 128 / 128),
|
||||||
|
dim.distance("BA", "AB"), 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEntryKeyMid() {
|
||||||
|
EntryKeyDimension dim = EntryKeyDimension.INSTANCE;
|
||||||
|
|
||||||
|
assertEquals("A", dim.mid("A", "A"));
|
||||||
|
assertEquals(null, dim.mid(null, null));
|
||||||
|
|
||||||
|
assertEquals("@", dim.mid("", null));
|
||||||
|
assertEquals("@", dim.mid(null, ""));
|
||||||
|
|
||||||
|
assertEquals("M@", dim.mid("A", "Z"));
|
||||||
|
assertEquals("M@", dim.mid("Z", "A"));
|
||||||
|
assertEquals("M@", dim.mid("A\0", "Z\0"));
|
||||||
|
|
||||||
|
assertEquals("M", dim.mid("A", "Y"));
|
||||||
|
assertEquals("M", dim.mid("Y", "A"));
|
||||||
|
|
||||||
|
assertEquals("MA", dim.mid("AA", "YA"));
|
||||||
|
assertEquals("MA", dim.mid("YA", "AA"));
|
||||||
|
|
||||||
|
assertEquals("N\1", dim.mid("AA", "ZA"));
|
||||||
|
assertEquals("N\1@", dim.mid("AA", "ZB"));
|
||||||
|
assertEquals("N\1@", dim.mid("ZB", "AA"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import db.*;
|
import db.*;
|
||||||
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.database.annot.*;
|
import ghidra.util.database.annot.*;
|
||||||
import ghidra.util.database.annot.DBAnnotatedField.DefaultCodec;
|
import ghidra.util.database.annot.DBAnnotatedField.DefaultCodec;
|
||||||
|
@ -110,6 +111,32 @@ import ghidra.util.exception.VersionException;
|
||||||
*/
|
*/
|
||||||
public class DBCachedObjectStoreFactory {
|
public class DBCachedObjectStoreFactory {
|
||||||
|
|
||||||
|
public record RecAddress(int spaceId, long offset) {
|
||||||
|
public static RecAddress fromAddress(Address address) {
|
||||||
|
return new RecAddress(address.getAddressSpace().getSpaceID(), address.getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
public long offset() {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Address toAddress(AddressFactory factory) {
|
||||||
|
return factory.getAddressSpace(spaceId).getAddress(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record RecRange(int spaceId, long min, long max) {
|
||||||
|
public static RecRange fromRange(AddressRange range) {
|
||||||
|
return new RecRange(range.getAddressSpace().getSpaceID(),
|
||||||
|
range.getMinAddress().getOffset(), range.getMaxAddress().getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
public AddressRange toRange(AddressFactory factory) {
|
||||||
|
AddressSpace space = factory.getAddressSpace(spaceId);
|
||||||
|
return new AddressRangeImpl(space.getAddress(min), space.getAddress(max));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A codec for encoding alternative data types
|
* A codec for encoding alternative data types
|
||||||
*
|
*
|
||||||
|
@ -928,11 +955,41 @@ public class DBCachedObjectStoreFactory {
|
||||||
/** Codec for {@code String[]} */
|
/** Codec for {@code String[]} */
|
||||||
PrimitiveCodec<String[]> STRING_ARR =
|
PrimitiveCodec<String[]> STRING_ARR =
|
||||||
new ArrayObjectCodec<>(new LengthBoundCodec<>(STRING));
|
new ArrayObjectCodec<>(new LengthBoundCodec<>(STRING));
|
||||||
|
PrimitiveCodec<RecAddress> ADDRESS = new AbstractPrimitiveCodec<>(RecAddress.class) {
|
||||||
|
@Override
|
||||||
|
public RecAddress decode(ByteBuffer buffer) {
|
||||||
|
int spaceId = buffer.getInt();
|
||||||
|
long offset = buffer.getLong();
|
||||||
|
return new RecAddress(spaceId, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encode(ByteBuffer buffer, RecAddress value) {
|
||||||
|
buffer.putInt(value.spaceId);
|
||||||
|
buffer.putLong(value.offset);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PrimitiveCodec<RecRange> RANGE = new AbstractPrimitiveCodec<>(RecRange.class) {
|
||||||
|
@Override
|
||||||
|
public RecRange decode(ByteBuffer buffer) {
|
||||||
|
int spaceId = buffer.getInt();
|
||||||
|
long min = buffer.getLong();
|
||||||
|
long max = buffer.getLong();
|
||||||
|
return new RecRange(spaceId, min, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encode(ByteBuffer buffer, RecRange value) {
|
||||||
|
buffer.putInt(value.spaceId);
|
||||||
|
buffer.putLong(value.min);
|
||||||
|
buffer.putLong(value.max);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: No floats?
|
// TODO: No floats?
|
||||||
Map<Byte, PrimitiveCodec<?>> CODECS_BY_SELECTOR = Stream
|
Map<Byte, PrimitiveCodec<?>> CODECS_BY_SELECTOR = Stream
|
||||||
.of(BOOL, BYTE, CHAR, SHORT, INT, LONG, STRING, BOOL_ARR, BYTE_ARR, CHAR_ARR,
|
.of(BOOL, BYTE, CHAR, SHORT, INT, LONG, STRING, BOOL_ARR, BYTE_ARR, CHAR_ARR,
|
||||||
SHORT_ARR, INT_ARR, LONG_ARR, STRING_ARR)
|
SHORT_ARR, INT_ARR, LONG_ARR, STRING_ARR, ADDRESS, RANGE)
|
||||||
.collect(Collectors.toMap(c -> c.getSelector(), c -> c));
|
.collect(Collectors.toMap(c -> c.getSelector(), c -> c));
|
||||||
Map<Class<?>, PrimitiveCodec<?>> CODECS_BY_CLASS = CODECS_BY_SELECTOR.values()
|
Map<Class<?>, PrimitiveCodec<?>> CODECS_BY_CLASS = CODECS_BY_SELECTOR.values()
|
||||||
.stream()
|
.stream()
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.util.database.spatial;
|
package ghidra.util.database.spatial;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import db.DBRecord;
|
import db.DBRecord;
|
||||||
import ghidra.util.database.DBCachedObjectStore;
|
import ghidra.util.database.DBCachedObjectStore;
|
||||||
|
|
||||||
|
@ -39,6 +41,8 @@ public abstract class DBTreeNodeRecord<NS extends BoundingShape<NS>> extends DBT
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static final List<NodeType> VALUES = List.of(values());
|
||||||
|
|
||||||
private final boolean directory;
|
private final boolean directory;
|
||||||
private final boolean leafParent;
|
private final boolean leafParent;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,182 @@
|
||||||
|
/* ###
|
||||||
|
* 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.util.database.spatial.hyper;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
import ghidra.util.database.spatial.BoundedShape;
|
||||||
|
import ghidra.util.database.spatial.Query;
|
||||||
|
|
||||||
|
public abstract class AbstractHyperBoxQuery< //
|
||||||
|
P extends HyperPoint, //
|
||||||
|
DS extends BoundedShape<NS>, //
|
||||||
|
NS extends HyperBox<P, NS>, //
|
||||||
|
Q extends AbstractHyperBoxQuery<P, DS, NS, Q>> //
|
||||||
|
implements Query<DS, NS> {
|
||||||
|
|
||||||
|
public interface QueryFactory<NS extends HyperBox<?, NS>, Q extends AbstractHyperBoxQuery<?, ?, NS, Q>> {
|
||||||
|
Q create(NS ls, NS us, HyperDirection direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static <P extends HyperPoint, NS extends HyperBox<P, NS>, //
|
||||||
|
Q extends AbstractHyperBoxQuery<P, ?, NS, Q>> Q intersecting(NS shape,
|
||||||
|
HyperDirection direction, QueryFactory<NS, Q> factory) {
|
||||||
|
HyperBox<P, ?> full = shape.space().getFull();
|
||||||
|
NS ls = shape.immutable(full.lCorner(), shape.uCorner());
|
||||||
|
NS us = shape.immutable(shape.lCorner(), full.uCorner());
|
||||||
|
return factory.create(ls, us, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static <P extends HyperPoint, NS extends HyperBox<P, NS>, //
|
||||||
|
Q extends AbstractHyperBoxQuery<P, ?, NS, Q>> Q enclosing(NS shape,
|
||||||
|
HyperDirection direction, QueryFactory<NS, Q> factory) {
|
||||||
|
HyperBox<P, ?> full = shape.space().getFull();
|
||||||
|
NS ls = shape.immutable(full.lCorner(), shape.lCorner());
|
||||||
|
NS us = shape.immutable(shape.uCorner(), full.uCorner());
|
||||||
|
return factory.create(ls, us, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static <P extends HyperPoint, NS extends HyperBox<P, NS>, //
|
||||||
|
Q extends AbstractHyperBoxQuery<P, ?, NS, Q>> Q enclosed(NS shape,
|
||||||
|
HyperDirection direction, QueryFactory<NS, Q> factory) {
|
||||||
|
HyperBox<P, ?> full = shape.space().getFull();
|
||||||
|
NS ls = shape.immutable(shape.lCorner(), full.uCorner());
|
||||||
|
NS us = shape.immutable(full.lCorner(), shape.uCorner());
|
||||||
|
return factory.create(ls, us, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static <P extends HyperPoint, NS extends HyperBox<P, NS>, //
|
||||||
|
Q extends AbstractHyperBoxQuery<P, ?, NS, Q>> Q equalTo(NS shape,
|
||||||
|
HyperDirection direction, QueryFactory<NS, Q> factory) {
|
||||||
|
NS ls = shape.immutable(shape.lCorner(), shape.lCorner());
|
||||||
|
NS us = shape.immutable(shape.uCorner(), shape.uCorner());
|
||||||
|
return factory.create(ls, us, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final NS ls;
|
||||||
|
protected final NS us;
|
||||||
|
protected final EuclideanHyperSpace<P, NS> space;
|
||||||
|
protected final HyperDirection direction;
|
||||||
|
|
||||||
|
protected Comparator<NS> comparator;
|
||||||
|
|
||||||
|
public AbstractHyperBoxQuery(NS ls, NS us, EuclideanHyperSpace<P, NS> space,
|
||||||
|
HyperDirection direction) {
|
||||||
|
this.ls = ls;
|
||||||
|
this.us = us;
|
||||||
|
this.space = space;
|
||||||
|
this.direction = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean terminateEarlyData(DS shape) {
|
||||||
|
return terminateEarlyNode(shape.getBounds());
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> boolean dimTerminateEarlyNode(Dimension<T, P, NS> dim, NS shape) {
|
||||||
|
return direction.forward()
|
||||||
|
? dim.compare(dim.lower(shape), dim.upper(us)) > 0
|
||||||
|
: dim.compare(dim.upper(shape), dim.lower(ls)) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean terminateEarlyNode(NS shape) {
|
||||||
|
Dimension<?, P, NS> dim = space.getDimensions().get(direction.dimension());
|
||||||
|
return dimTerminateEarlyNode(dim, shape);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Comparator<NS> getBoundsComparator() {
|
||||||
|
if (comparator == null) {
|
||||||
|
Dimension<?, P, NS> dim = space.getDimensions().get(direction.dimension());
|
||||||
|
comparator = createBoundsComparator(dim);
|
||||||
|
}
|
||||||
|
return comparator;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <T> Comparator<NS> createBoundsComparator(Dimension<T, P, NS> dim) {
|
||||||
|
if (direction.forward()) {
|
||||||
|
return Comparator.comparing(dim::lower, dim::compare);
|
||||||
|
}
|
||||||
|
return Comparator.comparing(dim::upper, (a, b) -> dim.compare(b, a));
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> boolean isNone(Dimension<T, P, NS> dim, NS shape) {
|
||||||
|
if (dim.compare(dim.lower(shape), dim.upper(ls)) > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (dim.compare(dim.lower(shape), dim.upper(us)) > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (dim.compare(dim.upper(shape), dim.lower(us)) < 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (dim.compare(dim.upper(shape), dim.lower(ls)) < 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> boolean isSome(Dimension<T, P, NS> dim, NS shape) {
|
||||||
|
if (dim.compare(dim.lower(shape), dim.lower(ls)) < 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (dim.compare(dim.lower(shape), dim.lower(us)) < 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (dim.compare(dim.upper(shape), dim.upper(us)) > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (dim.compare(dim.upper(shape), dim.upper(ls)) > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueryInclusion testNode(NS shape) {
|
||||||
|
for (Dimension<?, P, NS> dim : space.getDimensions()) {
|
||||||
|
if (isNone(dim, shape)) {
|
||||||
|
return QueryInclusion.NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Dimension<?, P, NS> dim : space.getDimensions()) {
|
||||||
|
if (isSome(dim, shape)) {
|
||||||
|
return QueryInclusion.SOME;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QueryInclusion.ALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Q create(NS ir1, NS ir2, HyperDirection newDirection);
|
||||||
|
|
||||||
|
public Q and(Q query) {
|
||||||
|
NS ir1 = ls.intersection(query.ls);
|
||||||
|
NS ir2 = us.intersection(query.us);
|
||||||
|
return create(ir1, ir2, query.direction != null ? query.direction : this.direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HyperDirection getDirection() {
|
||||||
|
if (direction == null) {
|
||||||
|
return new HyperDirection(0, true);
|
||||||
|
}
|
||||||
|
return direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Q starting(HyperDirection newDirection) {
|
||||||
|
return create(ls, us, newDirection);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.util.database.spatial.hyper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import ghidra.util.database.DBCachedObjectStoreFactory;
|
||||||
|
import ghidra.util.database.spatial.*;
|
||||||
|
import ghidra.util.exception.VersionException;
|
||||||
|
|
||||||
|
public abstract class AbstractHyperRStarTree< //
|
||||||
|
P extends HyperPoint, //
|
||||||
|
DS extends BoundedShape<NS>, //
|
||||||
|
DR extends DBTreeDataRecord<DS, NS, T>, //
|
||||||
|
NS extends HyperBox<P, NS>, //
|
||||||
|
NR extends DBTreeNodeRecord<NS>, //
|
||||||
|
T, //
|
||||||
|
Q extends AbstractHyperBoxQuery<P, DS, NS, Q>> //
|
||||||
|
extends AbstractRStarConstraintsTree<DS, DR, NS, NR, T, Q> {
|
||||||
|
|
||||||
|
protected static class AsSpatialMap< //
|
||||||
|
DS extends BoundedShape<NS>, //
|
||||||
|
DR extends DBTreeDataRecord<DS, NS, T>, //
|
||||||
|
NS extends HyperBox<?, NS>, T, Q extends AbstractHyperBoxQuery<?, DS, NS, Q>>
|
||||||
|
extends AbstractConstraintsTreeSpatialMap<DS, DR, NS, T, Q> {
|
||||||
|
public AsSpatialMap(AbstractConstraintsTree<DS, DR, NS, ?, T, Q> tree, Q query) {
|
||||||
|
super(tree, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AsSpatialMap<DS, DR, NS, T, Q> reduce(Q andQuery) {
|
||||||
|
return new AsSpatialMap<>(this.tree,
|
||||||
|
this.query == null ? andQuery : this.query.and(andQuery));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final EuclideanHyperSpace<P, NS> space;
|
||||||
|
protected final List<Comparator<NS>> axes;
|
||||||
|
|
||||||
|
protected <V> Comparator<NS> dimComparator(Dimension<V, P, NS> dim) {
|
||||||
|
return Comparator.comparing(dim::lower, dim::compare);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractHyperRStarTree(DBCachedObjectStoreFactory storeFactory, String tableName,
|
||||||
|
EuclideanHyperSpace<P, NS> space, Class<DR> dataType, Class<NR> nodeType,
|
||||||
|
boolean upgradeable, int maxChildren) throws VersionException, IOException {
|
||||||
|
super(storeFactory, tableName, dataType, nodeType, upgradeable, maxChildren);
|
||||||
|
this.space = space;
|
||||||
|
this.axes = space.getDimensions().stream().map(this::dimComparator).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<Comparator<NS>> getSplitAxes() {
|
||||||
|
return axes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Comparator<NS> getDefaultBoundsComparator() {
|
||||||
|
return axes.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AbstractConstraintsTreeSpatialMap<DS, DR, NS, T, Q> asSpatialMap() {
|
||||||
|
return new AsSpatialMap<>(this, null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
/* ###
|
||||||
|
* 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.util.database.spatial.hyper;
|
||||||
|
|
||||||
|
public interface Dimension<T, P extends HyperPoint, B extends HyperBox<P, B>> {
|
||||||
|
T value(P point);
|
||||||
|
|
||||||
|
default T lower(B box) {
|
||||||
|
return value(box.lCorner());
|
||||||
|
}
|
||||||
|
|
||||||
|
default T upper(B box) {
|
||||||
|
return value(box.uCorner());
|
||||||
|
}
|
||||||
|
|
||||||
|
int compare(T a, T b);
|
||||||
|
|
||||||
|
double distance(T a, T b);
|
||||||
|
|
||||||
|
T mid(T a, T b);
|
||||||
|
|
||||||
|
default T boxMid(B box) {
|
||||||
|
return mid(lower(box), upper(box));
|
||||||
|
}
|
||||||
|
|
||||||
|
default T min(T a, T b) {
|
||||||
|
return compare(a, b) < 0 ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
default T max(T a, T b) {
|
||||||
|
return compare(a, b) > 0 ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
T absoluteMin();
|
||||||
|
|
||||||
|
T absoluteMax();
|
||||||
|
|
||||||
|
default double pointDistance(P a, P b) {
|
||||||
|
return distance(value(a), value(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean contains(B box, P point) {
|
||||||
|
T value = value(point);
|
||||||
|
if (compare(value, lower(box)) < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (compare(value, upper(box)) > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean intersect(B a, B b) {
|
||||||
|
if (compare(lower(a), upper(b)) > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (compare(upper(a), lower(b)) < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean encloses(B outer, B inner) {
|
||||||
|
if (compare(lower(outer), lower(inner)) > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (compare(upper(outer), upper(inner)) < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
default T intersectionLower(B a, B b) {
|
||||||
|
return max(lower(a), lower(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
default T intersectionUpper(B a, B b) {
|
||||||
|
return min(upper(a), upper(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
default T unionLower(B a, B b) {
|
||||||
|
return min(lower(a), lower(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
default T unionUpper(B a, B b) {
|
||||||
|
return max(upper(a), upper(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
default double measure(B box) {
|
||||||
|
return distance(upper(box), lower(box));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
/* ###
|
||||||
|
* 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.util.database.spatial.hyper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public interface EuclideanHyperSpace<P extends HyperPoint, B extends HyperBox<P, B>> {
|
||||||
|
List<Dimension<?, P, B>> getDimensions();
|
||||||
|
|
||||||
|
B getFull();
|
||||||
|
|
||||||
|
default boolean boxesEqual(B a, B b) {
|
||||||
|
for (Dimension<?, P, B> dim : getDimensions()) {
|
||||||
|
if (!Objects.equals(dim.lower(a), dim.lower(b))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(dim.upper(a), dim.upper(b))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
default Object[] collectBounds(B box) {
|
||||||
|
List<Dimension<?, P, B>> dims = getDimensions();
|
||||||
|
Object[] result = new Object[dims.size() * 2];
|
||||||
|
for (int i = 0; i < dims.size(); i++) {
|
||||||
|
Dimension<?, P, B> d = dims.get(i);
|
||||||
|
result[i * 2] = d.lower(box);
|
||||||
|
result[i * 2 + 1] = d.upper(box);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean boxContains(B box, P point) {
|
||||||
|
for (Dimension<?, P, B> dim : getDimensions()) {
|
||||||
|
if (!dim.contains(box, point)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
default double boxArea(B box) {
|
||||||
|
double result = 1;
|
||||||
|
for (Dimension<?, P, B> dim : getDimensions()) {
|
||||||
|
result *= 1 + dim.measure(box);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
default double boxMargin(B box) {
|
||||||
|
double result = 0;
|
||||||
|
for (Dimension<?, P, B> dim : getDimensions()) {
|
||||||
|
result += 1 + dim.measure(box);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
P boxCenter(B box);
|
||||||
|
|
||||||
|
default <T> double measureUnion(Dimension<T, P, B> dim, B a, B b) {
|
||||||
|
T unionLower = dim.unionLower(a, b);
|
||||||
|
T unionUpper = dim.unionUpper(a, b);
|
||||||
|
return dim.distance(unionUpper, unionLower);
|
||||||
|
}
|
||||||
|
|
||||||
|
default double computeAreaUnionBounds(B a, B b) {
|
||||||
|
double result = 1;
|
||||||
|
for (Dimension<?, P, B> dim : getDimensions()) {
|
||||||
|
result *= 1 + measureUnion(dim, a, b);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
default <T> double measureIntersection(Dimension<T, P, B> dim, B a, B b) {
|
||||||
|
T intLower = dim.intersectionLower(a, b);
|
||||||
|
T intUpper = dim.intersectionUpper(a, b);
|
||||||
|
if (dim.compare(intLower, intUpper) > 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return dim.distance(intUpper, intLower);
|
||||||
|
}
|
||||||
|
|
||||||
|
default double computeAreaIntersection(B a, B b) {
|
||||||
|
double result = 1;
|
||||||
|
for (Dimension<?, P, B> dim : getDimensions()) {
|
||||||
|
double measure = measureIntersection(dim, a, b);
|
||||||
|
if (measure == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
result *= 1 + measure;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
default double sqDistance(P a, P b) {
|
||||||
|
double result = 0;
|
||||||
|
for (Dimension<?, P, B> dim : getDimensions()) {
|
||||||
|
double dist = dim.pointDistance(a, b);
|
||||||
|
result += dist * dist;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
B boxUnionBounds(B a, B b);
|
||||||
|
|
||||||
|
B boxIntersection(B b, B shape);
|
||||||
|
|
||||||
|
default boolean boxEncloses(B outer, B inner) {
|
||||||
|
for (Dimension<?, P, B> dim : getDimensions()) {
|
||||||
|
if (!dim.encloses(outer, inner)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
/* ###
|
||||||
|
* 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.util.database.spatial.hyper;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import ghidra.util.database.spatial.BoundingShape;
|
||||||
|
|
||||||
|
public interface HyperBox<P extends HyperPoint, B extends HyperBox<P, B>> extends BoundingShape<B> {
|
||||||
|
EuclideanHyperSpace<P, B> space();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
default boolean doEquals(Object obj) {
|
||||||
|
if (!(obj instanceof HyperBox<?, ?> that)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.space() != that.space()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return space().boxesEqual((B) this, (B) that);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
default int doHashCode() {
|
||||||
|
return Objects.hash(space().collectBounds((B) this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
default boolean contains(P p) {
|
||||||
|
return space().boxContains((B) this, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
default double getArea() {
|
||||||
|
return space().boxArea((B) this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
default double getMargin() {
|
||||||
|
return space().boxMargin((B) this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
default P getCenter() {
|
||||||
|
return space().boxCenter((B) this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
default double computeAreaUnionBounds(B shape) {
|
||||||
|
return space().computeAreaUnionBounds((B) this, shape);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
default double computeAreaIntersection(B shape) {
|
||||||
|
return space().computeAreaIntersection((B) this, shape);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default double computeCentroidDistance(B shape) {
|
||||||
|
return space().sqDistance(this.getCenter(), shape.getCenter());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
default B unionBounds(B shape) {
|
||||||
|
return space().boxUnionBounds((B) this, shape);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
default boolean encloses(B shape) {
|
||||||
|
return space().boxEncloses((B) this, shape);
|
||||||
|
}
|
||||||
|
|
||||||
|
P lCorner();
|
||||||
|
|
||||||
|
P uCorner();
|
||||||
|
|
||||||
|
B immutable(P lCorner, P uCorner);
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
default B intersection(B shape) {
|
||||||
|
return space().boxIntersection((B) this, shape);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
/* ###
|
||||||
|
* 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.util.database.spatial.hyper;
|
||||||
|
|
||||||
|
public record HyperDirection(int dimension, boolean forward) {
|
||||||
|
public static final HyperDirection DEFAULT = new HyperDirection(0, true);
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
/* ###
|
||||||
|
* 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.util.database.spatial.hyper;
|
||||||
|
|
||||||
|
public interface HyperPoint {
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/* ###
|
||||||
|
* 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.util.database.spatial.hyper;
|
||||||
|
|
||||||
|
public interface LongDimension<P extends HyperPoint, B extends HyperBox<P, B>>
|
||||||
|
extends Dimension<Long, P, B> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int compare(Long a, Long b) {
|
||||||
|
return Long.compare(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default double distance(Long a, Long b) {
|
||||||
|
return a - b;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Long mid(Long a, Long b) {
|
||||||
|
return a + (b - a) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Long absoluteMin() {
|
||||||
|
return Long.MIN_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Long absoluteMax() {
|
||||||
|
return Long.MAX_VALUE;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
/* ###
|
||||||
|
* 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.util.database.spatial.hyper;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public interface StringDimension<P extends HyperPoint, B extends HyperBox<P, B>>
|
||||||
|
extends Dimension<String, P, B> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int compare(String a, String b) {
|
||||||
|
if (a == null && b == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// Treat null as the absolute max value
|
||||||
|
if (a == null) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (b == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return a.compareTo(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int charAt(String s, int i) {
|
||||||
|
if (s == null) {
|
||||||
|
if (i == 0) {
|
||||||
|
return 128;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (i < s.length()) {
|
||||||
|
return Math.min(127, s.charAt(i));
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lenStrings(String a, String b) {
|
||||||
|
if (a == null) {
|
||||||
|
return b.length();
|
||||||
|
}
|
||||||
|
if (b == null) {
|
||||||
|
return a.length();
|
||||||
|
}
|
||||||
|
return Math.max(a.length(), b.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default double distance(String a, String b) {
|
||||||
|
if (Objects.equals(a, b)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// TODO: May revisit the starting place value, to scale this dimension down in importance.
|
||||||
|
double result = 0;
|
||||||
|
double placeVal = Double.MAX_VALUE / 128;
|
||||||
|
int len = lenStrings(a, b);
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
int ca = charAt(a, i);
|
||||||
|
int cb = charAt(b, i);
|
||||||
|
double oldResult = result;
|
||||||
|
result += placeVal * (cb - ca);
|
||||||
|
if (oldResult == result) {
|
||||||
|
// Can't capture any more precision, so we're done
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (placeVal == Double.MIN_VALUE || placeVal == 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
placeVal /= 128;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BigInteger subtractExact(String a, String b) {
|
||||||
|
int len = lenStrings(a, b);
|
||||||
|
BigInteger result = BigInteger.ZERO;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
int ca = charAt(a, i);
|
||||||
|
int cb = charAt(b, i);
|
||||||
|
result = result.shiftLeft(7).add(BigInteger.valueOf(ca - cb));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String add(String a, BigInteger d, int len) {
|
||||||
|
char[] cb = new char[len];
|
||||||
|
boolean carry = false;
|
||||||
|
for (int i = len - 1; i >= 0; i--) {
|
||||||
|
int tc = charAt(a, i) + (d.intValue() % 128) + (carry ? 1 : 0);
|
||||||
|
d = d.shiftRight(7);
|
||||||
|
carry = tc >= 128;
|
||||||
|
cb[i] = (char) (tc % 128);
|
||||||
|
}
|
||||||
|
return new String(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String mid(String a, String b) {
|
||||||
|
if (Objects.equals(a, b)) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
if (a == null && b.isEmpty()) {
|
||||||
|
return "" + ((char) 64);
|
||||||
|
}
|
||||||
|
if (b == null && a.isEmpty()) {
|
||||||
|
return "" + ((char) 64);
|
||||||
|
}
|
||||||
|
if (a.compareTo(b) > 0) {
|
||||||
|
// I'll cheat a bit here to avoid carries with negative operand
|
||||||
|
String c = a;
|
||||||
|
a = b;
|
||||||
|
b = c;
|
||||||
|
}
|
||||||
|
BigInteger diff = subtractExact(b, a);
|
||||||
|
String maybeTrunc = add(a, diff.shiftRight(1), lenStrings(a, b));
|
||||||
|
if (diff.testBit(0)) {
|
||||||
|
return maybeTrunc + ((char) 64);
|
||||||
|
}
|
||||||
|
return maybeTrunc;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String absoluteMin() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String absoluteMax() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/* ###
|
||||||
|
* 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.util.database.spatial.hyper;
|
||||||
|
|
||||||
|
public interface ULongDimension<P extends HyperPoint, B extends HyperBox<P, B>>
|
||||||
|
extends Dimension<Long, P, B> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int compare(Long a, Long b) {
|
||||||
|
return Long.compareUnsigned(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default double distance(Long a, Long b) {
|
||||||
|
return a - b;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Long mid(Long a, Long b) {
|
||||||
|
return a + Long.divideUnsigned(b - a, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Long absoluteMin() {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Long absoluteMax() {
|
||||||
|
return -1L;
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ public interface Point2D<X, Y> {
|
||||||
default double computeDistance(Point2D<X, Y> point) {
|
default double computeDistance(Point2D<X, Y> point) {
|
||||||
double distX = getSpace().distX(getX(), point.getX());
|
double distX = getSpace().distX(getX(), point.getX());
|
||||||
double distY = getSpace().distY(getY(), point.getY());
|
double distY = getSpace().distY(getY(), point.getY());
|
||||||
|
// NB. Square root is unnecessary, if this is just for comparison
|
||||||
return distX * distX + distY * distY;
|
return distX * distX + distY * distY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -761,7 +761,7 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
Map.entry("[1]", Lifespan.nowOn(0)),
|
Map.entry("[1]", Lifespan.nowOn(0)),
|
||||||
Map.entry("[2]", Lifespan.span(0, 9)),
|
Map.entry("[2]", Lifespan.span(0, 9)),
|
||||||
Map.entry("[3]", Lifespan.nowOn(0))),
|
Map.entry("[3]", Lifespan.nowOn(0))),
|
||||||
object.getValues()
|
object.getValues(Lifespan.ALL)
|
||||||
.stream()
|
.stream()
|
||||||
.collect(Collectors.toMap(v -> v.getEntryKey(), v -> v.getLifespan())));
|
.collect(Collectors.toMap(v -> v.getEntryKey(), v -> v.getLifespan())));
|
||||||
}
|
}
|
||||||
|
|
|
@ -786,7 +786,7 @@ public class GdbCommandsTest extends AbstractGdbTraceRmiTest {
|
||||||
Map.entry("[1]", Lifespan.nowOn(0)),
|
Map.entry("[1]", Lifespan.nowOn(0)),
|
||||||
Map.entry("[2]", Lifespan.span(0, 9)),
|
Map.entry("[2]", Lifespan.span(0, 9)),
|
||||||
Map.entry("[3]", Lifespan.nowOn(0))),
|
Map.entry("[3]", Lifespan.nowOn(0))),
|
||||||
object.getValues()
|
object.getValues(Lifespan.ALL)
|
||||||
.stream()
|
.stream()
|
||||||
.collect(Collectors.toMap(v -> v.getEntryKey(), v -> v.getLifespan())));
|
.collect(Collectors.toMap(v -> v.getEntryKey(), v -> v.getLifespan())));
|
||||||
}
|
}
|
||||||
|
|
|
@ -810,7 +810,7 @@ public class LldbCommandsTest extends AbstractLldbTraceRmiTest {
|
||||||
Map.entry("[1]", Lifespan.nowOn(0)),
|
Map.entry("[1]", Lifespan.nowOn(0)),
|
||||||
Map.entry("[2]", Lifespan.span(0, 9)),
|
Map.entry("[2]", Lifespan.span(0, 9)),
|
||||||
Map.entry("[3]", Lifespan.nowOn(0))),
|
Map.entry("[3]", Lifespan.nowOn(0))),
|
||||||
object.getValues()
|
object.getValues(Lifespan.ALL)
|
||||||
.stream()
|
.stream()
|
||||||
.collect(Collectors.toMap(v -> v.getEntryKey(), v -> v.getLifespan())));
|
.collect(Collectors.toMap(v -> v.getEntryKey(), v -> v.getLifespan())));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue