Merge remote-tracking branch 'origin/GP-6550_Dan_optimizeDBTrace--SQUASHED'

This commit is contained in:
Ryan Kurtz
2026-03-25 13:30:07 -04:00
28 changed files with 1271 additions and 234 deletions
@@ -0,0 +1,93 @@
/* ###
* 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.app.plugin.core.debug.gui.internal;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.trace.database.memory.DBTraceMemoryManager;
import ghidra.trace.database.memory.DBTraceMemorySpace;
import ghidra.trace.model.Trace;
@PluginInfo(
shortDescription = "Plot R*-Trees",
description = "Plot R*-Trees in Trace Databases",
category = PluginCategoryNames.DIAGNOSTIC,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.STABLE,
eventsConsumed = {
TraceActivatedPluginEvent.class,
})
public class RStarDiagnosticsPlugin extends Plugin {
static final int INITIAL_DEPTH = 3;
static final int MIN_DEPTH = 1;
protected final RStarPlotProvider plotProvider;
protected final RStarTreeProvider treeProvider;
protected DebuggerCoordinates current;
protected DBTraceMemorySpace space;
public RStarDiagnosticsPlugin(PluginTool tool) {
super(tool);
plotProvider = new RStarPlotProvider(this);
treeProvider = new RStarTreeProvider(this);
}
@Override
protected void init() {
super.init();
tool.addComponentProvider(plotProvider, true);
tool.addComponentProvider(treeProvider, true);
}
@Override
protected void dispose() {
tool.removeComponentProvider(plotProvider);
super.dispose();
}
@Override
public void processEvent(PluginEvent event) {
super.processEvent(event);
if (event instanceof TraceActivatedPluginEvent ev) {
coordinatesActivated(ev.getActiveCoordinates());
}
}
protected DBTraceMemorySpace computeSpace() {
Trace trace = current.getTrace();
if (trace == null) {
return null;
}
if (!(trace.getMemoryManager() instanceof DBTraceMemoryManager mem)) {
return null;
}
return mem.getMemorySpace(trace.getBaseAddressFactory().getDefaultAddressSpace(), false);
}
protected void coordinatesActivated(DebuggerCoordinates current) {
this.current = current;
this.space = computeSpace();
if (space == null) {
plotProvider.bounds = null;
}
plotProvider.component.repaint();
treeProvider.refresh();
}
}
@@ -0,0 +1,321 @@
/* ###
* 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.app.plugin.core.debug.gui.internal;
import java.awt.*;
import java.awt.event.*;
import java.util.Arrays;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.tree.TreePath;
import docking.action.builder.ActionBuilder;
import generic.theme.GIcon;
import ghidra.app.plugin.core.debug.gui.internal.RStarTreeProvider.HasShape;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.program.model.address.AddressSpace;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.Painter;
import ghidra.trace.model.*;
import ghidra.util.database.spatial.DBTreeDataRecord;
public class RStarPlotProvider extends ComponentProviderAdapter {
private final RStarDiagnosticsPlugin plugin;
final JComponent component = new JPanel(true) {
@Override
public void paint(Graphics g) {
super.paint(g);
RStarPlotProvider.this.doPaint(g);
}
};
private int depth = RStarDiagnosticsPlugin.INITIAL_DEPTH;
TraceAddressSnapRange bounds;
private int mouseX;
private int mouseY;
private boolean didDrag;
private int begDragX;
private int begDragY;
private int endDragX;
private int endDragY;
public RStarPlotProvider(RStarDiagnosticsPlugin plugin) {
super(plugin.getTool(), "R*-Tree Diagnostic Plot", plugin.getName());
this.plugin = plugin;
updateSubtitle();
component.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
component.repaint();
}
@Override
public void componentShown(ComponentEvent e) {
component.repaint();
}
});
component.addMouseWheelListener(new MouseWheelListener() {
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (plugin.space == null) {
return;
}
int maxDepth = plugin.space.getDepth();
int newDepth =
Math.min(
Math.max(depth + e.getWheelRotation(), RStarDiagnosticsPlugin.MIN_DEPTH),
maxDepth);
if (newDepth == depth) {
return;
}
depth = newDepth;
updateSubtitle();
component.repaint();
}
});
component.addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
if (plugin.space == null) {
return;
}
OffsetSnap offsetSnap = xyToOffsetSnap(e.getX(), e.getY());
if (offsetSnap == null) {
return;
}
tool.setStatusInfo(offsetSnap.toString());
}
@Override
public void mouseDragged(MouseEvent e) {
didDrag = true;
}
});
component.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == 1) {
mouseX = e.getX();
mouseY = e.getY();
component.repaint();
}
else if (e.getButton() == 3) {
if (plugin.space == null) {
bounds = null;
}
else {
bounds = plugin.space.getRootBounds();
}
component.repaint();
}
}
@Override
public void mousePressed(MouseEvent e) {
didDrag = false;
begDragX = e.getX();
begDragY = e.getY();
}
@Override
public void mouseReleased(MouseEvent e) {
if (!didDrag) {
return;
}
endDragX = e.getX();
endDragY = e.getY();
if (endDragX == begDragX || endDragY == begDragY) {
return;
}
if (endDragX < begDragX) {
int t = endDragX;
endDragX = begDragX;
begDragX = t;
}
if (endDragY < begDragY) {
int t = endDragY;
endDragY = begDragY;
begDragY = t;
}
Trace trace = plugin.current.getTrace();
if (trace == null) {
return;
}
AddressSpace addrSpace = trace.getBaseAddressFactory().getDefaultAddressSpace();
OffsetSnap beg = xyToOffsetSnap(begDragX, begDragY);
OffsetSnap end = xyToOffsetSnap(endDragX, endDragY);
bounds =
new ImmutableTraceAddressSnapRange(addrSpace.getAddress(beg.offset),
addrSpace.getAddress(end.offset), beg.snap, end.snap);
component.repaint();
}
});
new ActionBuilder("Zoom Out", plugin.getName())
.toolBarIcon(new GIcon("icon.debugger.breakpoint.timeline.zoom_out_max"))
.onAction(ctx -> {
if (plugin.space == null) {
bounds = null;
}
else {
bounds = plugin.space.getRootBounds();
}
})
.buildAndInstallLocal(this);
}
record OffsetSnap(long offset, long snap) {
@Override
public final String toString() {
return "Addr:%08x,Snap:%d".formatted(offset, snap);
}
}
protected OffsetSnap xyToOffsetSnap(int x, int y) {
if (bounds == null) {
return null;
}
double scaleX =
1.0 * (bounds.getX2().getOffset() - bounds.getX1().getOffset()) /
component.getWidth();
double scaleY = 1.0 * (bounds.getY2() - bounds.getY1()) / component.getHeight();
long offset = bounds.getX1().getOffset() + (long) (scaleX * x);
long snap = bounds.getY1() + (long) (scaleY * y);
return new OffsetSnap(offset, snap);
}
protected void updateSubtitle() {
if (plugin.space == null) {
setSubTitle("");
return;
}
setSubTitle("Depth=%d,Max=%d".formatted(depth, plugin.space.getDepth()));
}
@Override
public JComponent getComponent() {
return component;
}
private abstract class MyPainter implements Painter {
private final double xScale;
private final double yScale;
private final long xShift;
private final long yShift;
public MyPainter(Rectangle rect, TraceAddressSnapRange bounds) {
xScale = 1.0 * rect.width / (bounds.getX2().getOffset() - bounds.getX1().getOffset());
yScale = 1.0 * rect.height / (bounds.getY2() - bounds.getY1());
xShift = bounds.getX1().getOffset();
yShift = bounds.getY1();
}
@Override
public void paint(TraceAddressSnapRange shape, int depth) {
int x = (int) ((shape.getX1().getOffset() - xShift) * xScale);
int y = (int) ((shape.getY1() - yShift) * yScale);
int width = Math.max(1,
(int) ((shape.getX2().getOffset() - shape.getX1().getOffset()) * xScale));
int height = Math.max(1, (int) ((shape.getY2() - shape.getY1()) * yScale));
paintRect(x, y, width, height, shape, depth);
}
protected abstract void paintRect(int x, int y, int width, int height,
TraceAddressSnapRange shape, int depth);
}
void doPaint(Graphics _g) {
if (!(_g instanceof Graphics2D g)) {
return;
}
Rectangle r = new Rectangle(0, 0, component.getWidth(), component.getHeight());
g.clearRect(r.x, r.y, r.width, r.height);
if (plugin.space == null) {
return;
}
if (bounds == null) {
bounds = plugin.space.getRootBounds();
}
var fillPainter = new MyPainter(r, bounds) {
TraceAddressSnapRange result;
@Override
protected void paintRect(int x, int y, int width, int height,
TraceAddressSnapRange shape, int depth) {
boolean select = x <= mouseX && mouseX <= x + width &&
y <= mouseY && mouseY <= y + height;
float brightness = (float) Math.pow(0.5, depth);
if (select) {
result = shape;
g.setColor(shape instanceof DBTreeDataRecord<?, ?, ?>
? new Color(0, brightness, 0)
: new Color(0, brightness, brightness));
}
else {
g.setColor(shape instanceof DBTreeDataRecord<?, ?, ?>
? new Color(brightness, brightness, 0)
: new Color(brightness, brightness, brightness));
}
g.fillRect(x, y, width, height);
}
};
var linePainter = new MyPainter(r, bounds) {
@Override
protected void paintRect(int x, int y, int width, int height,
TraceAddressSnapRange shape, int depth) {
g.setColor(shape instanceof DBTreeDataRecord<?, ?, ?>
? Color.RED
: Color.BLACK);
g.drawRect(x, y, width, height);
}
};
TreePath[] selArr = plugin.treeProvider.tree.getSelectionPaths();
if (selArr == null || selArr.length == 0) {
plugin.space.paint(fillPainter, depth);
plugin.space.paint(linePainter, depth);
if (fillPainter.result != null) {
plugin.treeProvider.select(fillPainter.result);
}
}
List<TreePath> selection = Arrays.asList(selArr);
selection.sort((p1, p2) -> {
return p1.getPathCount() - p2.getPathCount();
});
g.setColor(Color.CYAN);
for (TreePath p : selection) {
if (!(p.getLastPathComponent() instanceof HasShape node)) {
continue;
}
fillPainter.paint(node.getShape(), p.getPathCount() - 1);
}
for (TreePath p : selection) {
if (!(p.getLastPathComponent() instanceof HasShape node)) {
continue;
}
linePainter.paint(node.getShape(), p.getPathCount() - 1);
}
}
}
@@ -0,0 +1,196 @@
/* ###
* 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.app.plugin.core.debug.gui.internal;
import java.awt.BorderLayout;
import java.util.*;
import javax.swing.*;
import docking.widgets.tree.*;
import generic.theme.GIcon;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.util.database.spatial.*;
public class RStarTreeProvider extends ComponentProviderAdapter {
private final RStarDiagnosticsPlugin plugin;
interface HasShape {
TraceAddressSnapRange getShape();
}
class RootRStarNode extends GTreeLazyNode implements HasShape {
static final GIcon ICON = new GIcon("blargh");
@Override
public TraceAddressSnapRange getShape() {
if (plugin == null || plugin.space == null) {
return null;
}
return plugin.space.getRootBounds();
}
@Override
public String getName() {
TraceAddressSnapRange root = getShape();
if (root == null) {
return "No Shape";
}
return root.toString();
}
@Override
public Icon getIcon(boolean expanded) {
return ICON;
}
@Override
public String getToolTip() {
return null;
}
@Override
public boolean isLeaf() {
return false;
}
@Override
protected List<GTreeNode> generateChildren() {
if (!(getShape() instanceof DBTreeNodeRecord<?> rec)) {
return List.of();
}
return plugin.space.getChildrenOf(rec).stream().map(n -> nodeFor(n)).toList();
}
}
class NodeRStarNode extends GTreeLazyNode implements HasShape {
static final GIcon ICON = new GIcon("blargh");
final DBTreeNodeRecord<?> rec;
public NodeRStarNode(DBTreeNodeRecord<?> rec) {
this.rec = rec;
}
@Override
public TraceAddressSnapRange getShape() {
return (TraceAddressSnapRange) rec.getShape();
}
@Override
public String getName() {
return rec.toString();
}
@Override
public Icon getIcon(boolean expanded) {
return ICON;
}
@Override
public String getToolTip() {
return null;
}
@Override
public boolean isLeaf() {
return false;
}
@Override
protected List<GTreeNode> generateChildren() {
return plugin.space.getChildrenOf(rec).stream().map(n -> nodeFor(n)).toList();
}
}
class DataRStarNode extends GTreeNode implements HasShape {
static final GIcon ICON = new GIcon("blargh");
final DBTreeDataRecord<?, ?, ?> rec;
public DataRStarNode(DBTreeDataRecord<?, ?, ?> rec) {
this.rec = rec;
}
@Override
public TraceAddressSnapRange getShape() {
return (TraceAddressSnapRange) rec.getShape();
}
@Override
public String getName() {
return rec.toString();
}
@Override
public Icon getIcon(boolean expanded) {
return ICON;
}
@Override
public String getToolTip() {
return null;
}
@Override
public boolean isLeaf() {
return true;
}
}
final Map<DBTreeRecord<?, ?>, GTreeNode> nodes = new HashMap<>();
private GTreeNode nodeFor(DBTreeRecord<?, ?> rec) {
if (rec == plugin.space.getRootBounds()) {
return root;
}
return nodes.computeIfAbsent(rec, r -> switch (rec) {
case DBTreeNodeRecord<?> n -> new NodeRStarNode(n);
case DBTreeDataRecord<?, ?, ?> d -> new DataRStarNode(d);
default -> throw new AssertionError();
});
}
final RootRStarNode root = new RootRStarNode();
final GTree tree = new GTree(root);
final JPanel panel = new JPanel(new BorderLayout());
public RStarTreeProvider(RStarDiagnosticsPlugin plugin) {
super(plugin.getTool(), "R*-Tree Diagnostic Tree", plugin.getName());
this.plugin = plugin;
this.panel.add(tree);
tree.addGTreeSelectionListener(e -> {
plugin.plotProvider.component.repaint();
});
}
@Override
public JComponent getComponent() {
return panel;
}
public void select(TraceAddressSnapRange shape) {
if (!(shape instanceof DBTreeRecord<?, ?> rec)) {
return;
}
tree.getSelectionModel().setSelectionPath(nodeFor(rec).getTreePath());
}
void refresh() {
root.unloadChildren();
nodes.clear();
}
}
@@ -471,10 +471,11 @@ public class DebuggerSnapshotTablePanel extends JPanel {
}
public void setCurrent(DebuggerCoordinates coords) {
assert coords.getTrace() == currentTrace;
boolean fire = coords.getViewSnap() != current.getViewSnap();
current = coords;
if (fire) {
snapshotTableModel.fireTableDataChanged();
snapshotTable.repaint();
}
}
@@ -27,7 +27,6 @@ import ghidra.trace.model.Lifespan.MutableLifeSet;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.*;
import ghidra.util.datastruct.ListenerSet;
@@ -161,31 +160,6 @@ public class DBTraceTimeViewport implements TraceTimeViewport {
return true;
}
protected static TraceSnapshot locateMostRecentFork(TraceTimeManager timeManager, long from) {
while (true) {
TraceSnapshot prev = timeManager.getMostRecentSnapshot(from);
if (prev == null) {
return null;
}
TraceSchedule prevSched = prev.getSchedule();
long prevKey = prev.getKey();
if (prevSched == null) {
if (prevKey == Long.MIN_VALUE) {
return null;
}
from = prevKey - 1;
continue;
}
long forkedSnap = prevSched.getSnap();
if (forkedSnap == prevKey - 1) {
// Schedule is notational without forking
from--;
continue;
}
return prev;
}
}
/**
* Construct the ranges (set and ordered)
*
@@ -200,14 +174,11 @@ public class DBTraceTimeViewport implements TraceTimeViewport {
protected static void collectForkRanges(TraceTimeManager timeManager, long curSnap,
MutableLifeSet spanSet, List<Lifespan> ordered) {
while (true) {
TraceSnapshot fork = locateMostRecentFork(timeManager, curSnap);
long prevSnap = fork == null ? Long.MIN_VALUE : fork.getKey();
if (curSnap >= 0 && prevSnap < 0) {
prevSnap = 0;
}
long prevSnap = timeManager.getMostRecentFork(curSnap);
if (!addSnapRange(prevSnap, curSnap, spanSet, ordered)) {
return;
}
TraceSnapshot fork = timeManager.getSnapshot(prevSnap, false);
if (fork == null) {
return;
}
@@ -25,18 +25,17 @@ import ghidra.lifecycle.Internal;
import ghidra.program.model.address.*;
import ghidra.trace.database.DBTrace;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMap.DBTraceAddressSnapRangePropertyMapDataFactory;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.*;
import ghidra.trace.database.space.DBTraceSpaceBased;
import ghidra.trace.model.*;
import ghidra.trace.model.map.TraceAddressSnapRangePropertyMapSpace;
import ghidra.util.LockHold;
import ghidra.util.database.*;
import ghidra.util.database.spatial.AbstractConstraintsTreeSpatialMap;
import ghidra.util.database.spatial.SpatialMap;
import ghidra.util.database.spatial.*;
import ghidra.util.exception.VersionException;
public class DBTraceAddressSnapRangePropertyMapSpace<T, DR extends AbstractDBTraceAddressSnapRangePropertyMapData<T>>
public class DBTraceAddressSnapRangePropertyMapSpace<T,
DR extends AbstractDBTraceAddressSnapRangePropertyMapData<T>>
implements DBTraceSpaceBased,
SpatialMap<TraceAddressSnapRange, T, TraceAddressSnapRangeQuery>,
TraceAddressSnapRangePropertyMapSpace<T> {
@@ -45,7 +44,8 @@ public class DBTraceAddressSnapRangePropertyMapSpace<T, DR extends AbstractDBTra
protected final AddressSpace space;
protected final ReadWriteLock lock;
protected final DBTraceAddressSnapRangePropertyMapTree<T, DR> tree;
protected final AbstractConstraintsTreeSpatialMap<TraceAddressSnapRange, DR, TraceAddressSnapRange, T, TraceAddressSnapRangeQuery> map;
protected final AbstractConstraintsTreeSpatialMap<TraceAddressSnapRange, DR,
TraceAddressSnapRange, T, TraceAddressSnapRangeQuery> map;
protected final AddressRangeImpl fullSpace;
public DBTraceAddressSnapRangePropertyMapSpace(String tableName, DBTrace trace,
@@ -205,4 +205,37 @@ public class DBTraceAddressSnapRangePropertyMapSpace<T, DR extends AbstractDBTra
public void checkIntegrity() {
tree.checkIntegrity();
}
/**
* For developers and testers.
*/
@Internal
public void paint(Painter painter, int depth) {
tree.paint(painter, depth);
}
/**
* For developers and testers.
*/
@Internal
public int getDepth() {
return tree.getDepth();
}
/**
* For developers and testers.
*/
@Internal
public TraceAddressSnapRange getRootBounds() {
return tree.getRootBounds();
}
/**
* For developers and testers.
*/
@Internal
public Collection<? extends DBTreeRecord<?, ? extends TraceAddressSnapRange>>
getChildrenOf(DBTreeNodeRecord<?> rec) {
return tree.internalGetChildrenOf(rec);
}
}
@@ -19,14 +19,15 @@ import java.io.IOException;
import java.util.*;
import db.DBRecord;
import ghidra.lifecycle.Internal;
import ghidra.program.model.address.*;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMap.DBTraceAddressSnapRangePropertyMapDataFactory;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.*;
import ghidra.trace.model.*;
import ghidra.util.database.*;
import ghidra.util.database.annot.*;
import ghidra.util.database.spatial.DBTreeDataRecord;
import ghidra.util.database.spatial.DBTreeNodeRecord;
import ghidra.util.database.spatial.*;
import ghidra.util.database.spatial.Query.QueryInclusion;
import ghidra.util.database.spatial.rect.*;
import ghidra.util.exception.VersionException;
@@ -666,4 +667,98 @@ public class DBTraceAddressSnapRangePropertyMapTree<T,
public DBTraceAddressSnapRangePropertyMapSpace<T, DR> getMapSpace() {
return mapSpace;
}
public interface Painter {
void paint(TraceAddressSnapRange shape, int depth);
}
/**
* For developers and testers.
*/
@Internal
public void paint(Painter painter, int depth) {
var visitor = new TreeRecordVisitor() {
int depth = 0;
int level = 0;
@Override
protected VisitResult beginNode(DBTraceAddressSnapRangePropertyMapNode parent,
DBTraceAddressSnapRangePropertyMapNode n, QueryInclusion inclusion) {
if (level == depth) {
painter.paint(n, depth);
}
if (level > depth) {
return VisitResult.NEXT;
}
level++;
return VisitResult.DESCEND;
}
@Override
protected VisitResult endNode(DBTraceAddressSnapRangePropertyMapNode parent,
DBTraceAddressSnapRangePropertyMapNode n, QueryInclusion inclusion) {
level--;
return super.endNode(parent, n, inclusion);
}
@Override
protected VisitResult visitData(DBTraceAddressSnapRangePropertyMapNode parent, DR d,
boolean included) {
painter.paint(d, depth);
return VisitResult.NEXT;
}
};
for (int i = 0; i < depth; i++) {
visitor.depth = i;
visit(null, visitor, false);
}
}
/**
* For developers and testers.
*/
@Internal
public int getDepth() {
return leafLevel + 2;
}
/**
* For developers and testers.
*/
@Internal
public TraceAddressSnapRange getRootBounds() {
return root;
}
/**
* For developers and testers.
*/
@Internal
public Collection<? extends DBTreeRecord<?, ? extends TraceAddressSnapRange>>
internalGetChildrenOf(DBTreeNodeRecord<?> rec) {
if (!(rec instanceof DBTraceAddressSnapRangePropertyMapNode node)) {
return List.of();
}
return getChildrenOf(node);
}
@Override // expose for testing
protected Comparator<TraceAddressSnapRange>
doChooseSplitAxis(List<DBTreeRecord<?, ? extends TraceAddressSnapRange>> children) {
return super.doChooseSplitAxis(children);
}
@Override // expose for testing
protected int doChooseSplitIndex(
List<DBTreeRecord<?, ? extends TraceAddressSnapRange>> children,
Comparator<TraceAddressSnapRange> axis) {
return super.doChooseSplitIndex(children, axis);
}
@Override // expose for testing
protected Collection<? extends DBTreeRecord<?, ? extends TraceAddressSnapRange>>
getChildrenOf(DBTraceAddressSnapRangePropertyMapNode parent) {
return super.getChildrenOf(parent);
}
}
@@ -24,6 +24,7 @@ import java.util.concurrent.locks.ReadWriteLock;
import java.util.function.Predicate;
import db.DBHandle;
import ghidra.lifecycle.Internal;
import ghidra.program.model.address.*;
import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.database.DBTrace;
@@ -32,6 +33,7 @@ import ghidra.trace.database.DBTraceUtils.AddressRangeMapSetter;
import ghidra.trace.database.DBTraceUtils.OffsetSnap;
import ghidra.trace.database.listing.DBTraceCodeSpace;
import ghidra.trace.database.map.*;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.Painter;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery;
import ghidra.trace.database.space.AbstractDBTraceSpaceBasedManager.DBTraceSpaceEntry;
import ghidra.trace.database.space.DBTraceSpaceBased;
@@ -43,6 +45,8 @@ import ghidra.trace.util.TraceEvents;
import ghidra.util.*;
import ghidra.util.AddressIteratorAdapter;
import ghidra.util.database.*;
import ghidra.util.database.spatial.DBTreeNodeRecord;
import ghidra.util.database.spatial.DBTreeRecord;
import ghidra.util.database.spatial.rect.Rectangle2DDirection;
import ghidra.util.datastruct.FixedSizeHashMap;
import ghidra.util.exception.VersionException;
@@ -66,7 +70,8 @@ public class DBTraceMemorySpace
protected final ReadWriteLock lock;
protected final DBTrace trace;
protected final DBTraceAddressSnapRangePropertyMapSpace<TraceMemoryState, DBTraceMemoryStateEntry> stateMapSpace;
protected final DBTraceAddressSnapRangePropertyMapSpace<TraceMemoryState,
DBTraceMemoryStateEntry> stateMapSpace;
protected final DBCachedObjectStore<DBTraceMemoryBufferEntry> bufferStore;
protected final DBCachedObjectStore<DBTraceMemoryBlockEntry> blockStore;
@@ -135,7 +140,9 @@ public class DBTraceMemorySpace
var l = new Object() {
boolean changed;
};
new AddressRangeMapSetter<Entry<TraceAddressSnapRange, TraceMemoryState>, TraceMemoryState>() {
new AddressRangeMapSetter<Entry<TraceAddressSnapRange, TraceMemoryState>,
TraceMemoryState>() {
@Override
protected AddressRange getRange(Entry<TraceAddressSnapRange, TraceMemoryState> entry) {
return entry.getKey().getRange();
@@ -316,6 +323,7 @@ public class DBTraceMemorySpace
if (predicate.test(entry.getValue())) {
result.add(foundRange);
}
break;
}
}
return result;
@@ -324,7 +332,7 @@ public class DBTraceMemorySpace
protected Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> doGetStates(Lifespan span,
AddressRange range) {
// TODO: A better way to handle memory-mapped registers?
// LATER: A better way to handle memory-mapped registers?
if (getAddressSpace().isRegisterSpace() && !range.getAddressSpace().isRegisterSpace()) {
return trace.getMemoryManager().doGetStates(span, range);
}
@@ -603,11 +611,11 @@ public class DBTraceMemorySpace
@Override
public int getViewBytes(long snap, Address start, ByteBuffer buf) {
assertInSpace(start);
AddressRange toRead;
int len = truncateLen(buf.remaining(), start);
if (len == 0) {
return 0;
}
AddressRange toRead;
try {
toRead = new AddressRangeImpl(start, len);
}
@@ -912,4 +920,37 @@ public class DBTraceMemorySpace
blockCacheMostRecent.clear();
}
}
/**
* For developers and testers.
*/
@Internal
public void paint(Painter painter, int depth) {
stateMapSpace.paint(painter, depth);
}
/**
* For developers and testers.
*/
@Internal
public int getDepth() {
return stateMapSpace.getDepth();
}
/**
* For developers and testers.
*/
@Internal
public TraceAddressSnapRange getRootBounds() {
return stateMapSpace.getRootBounds();
}
/**
* For developers and testers.
*/
@Internal
public Collection<? extends DBTreeRecord<?, ? extends TraceAddressSnapRange>>
getChildrenOf(DBTreeNodeRecord<?> rec) {
return stateMapSpace.getChildrenOf(rec);
}
}
@@ -4,9 +4,9 @@
* 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.
@@ -16,6 +16,10 @@
package ghidra.trace.database.target;
record ImmutableValueBox(ValueTriple lCorner, ValueTriple uCorner) implements ValueBox {
ImmutableValueBox {
assert lCorner.snap() <= uCorner.snap();
}
public ImmutableValueBox(ValueBox box) {
this(box.lCorner(), box.uCorner());
}
@@ -4,9 +4,9 @@
* 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.
@@ -96,10 +96,10 @@ enum ValueSpace implements EuclideanHyperSpace<ValueTriple, ValueBox> {
}
@Override
public double distance(RecAddress a, RecAddress b) {
double result = b.spaceId() - a.spaceId();
public double distance(RecAddress upper, RecAddress lower) {
double result = upper.spaceId() - lower.spaceId();
result *= Math.pow(2, 64);
result += (b.offset() - a.offset());
result += (upper.offset() - lower.offset());
return result;
}
@@ -19,6 +19,7 @@ import java.io.IOException;
import db.DBRecord;
import ghidra.trace.database.target.DBTraceObject;
import ghidra.trace.database.time.DBTraceTimeManager.DBTraceFork;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectValue;
@@ -68,6 +69,7 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot
private TraceThread eventThread;
private TraceSchedule schedule;
private boolean isFork;
public DBTraceSnapshot(DBTraceTimeManager manager, DBCachedObjectStore<?> store,
DBRecord record) {
@@ -80,6 +82,7 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot
if (created) {
threadKey = -1;
scheduleStr = "";
isFork = false;
}
else {
eventThread = manager.threadManager.getThread(threadKey);
@@ -92,9 +95,20 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot
// Leave as null (or previous value?)
}
}
isFork = computeIsFork();
}
}
protected boolean computeIsFork() {
if (key == Long.MIN_VALUE) {
return false;
}
if (schedule == null) {
return false;
}
return schedule.getSnap() != key - 1;
}
@Override
public String toString() {
return String.format(
@@ -201,12 +215,29 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot
}
}
@Override
public boolean isFork() {
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
return isFork;
}
}
@Override
public void setSchedule(TraceSchedule schedule) {
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
this.schedule = schedule;
this.scheduleStr = schedule == null ? "" : schedule.toString(TimeRadix.DEC);
this.isFork = computeIsFork();
update(SCHEDULE_COLUMN);
DBTraceFork foundFork = manager.forksBySnap.getOne(key);
if (foundFork != null && !isFork) {
manager.forkStore.delete(foundFork);
}
else if (foundFork == null && isFork) {
manager.forkStore.create().set(key);
}
manager.notifySnapshotChanged(this);
}
}
@@ -21,6 +21,7 @@ import java.util.Map.Entry;
import java.util.concurrent.locks.ReadWriteLock;
import db.DBHandle;
import db.DBRecord;
import ghidra.framework.data.OpenMode;
import ghidra.trace.database.DBTrace;
import ghidra.trace.database.DBTraceManager;
@@ -36,11 +37,34 @@ import ghidra.trace.util.TraceChangeRecord;
import ghidra.trace.util.TraceEvents;
import ghidra.util.LockHold;
import ghidra.util.database.*;
import ghidra.util.database.annot.*;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
public class DBTraceTimeManager implements TraceTimeManager, DBTraceManager {
@DBAnnotatedObjectInfo(version = 0)
public static class DBTraceFork extends DBAnnotatedObject {
protected static final String TABLE_NAME = "Forks";
protected static final String SNAP_COLUMN_NAME = "Snap";
@DBAnnotatedColumn(SNAP_COLUMN_NAME)
static DBObjectColumn SNAP_COLUMN;
@DBAnnotatedField(column = SNAP_COLUMN_NAME, indexed = true)
long snap;
public DBTraceFork(DBCachedObjectStore<?> store, DBRecord record) {
super(store, record);
}
protected void set(long snap) {
this.snap = snap;
update(SNAP_COLUMN);
}
}
protected final ReadWriteLock lock;
protected final DBTrace trace;
protected final DBTraceThreadManager threadManager;
@@ -48,6 +72,9 @@ public class DBTraceTimeManager implements TraceTimeManager, DBTraceManager {
protected final DBCachedObjectStore<DBTraceSnapshot> snapshotStore;
protected final DBCachedObjectIndex<String, DBTraceSnapshot> snapshotsBySchedule;
protected final DBCachedObjectStore<DBTraceFork> forkStore;
protected final DBCachedObjectIndex<Long, DBTraceFork> forksBySnap;
public DBTraceTimeManager(DBHandle dbh, OpenMode openMode, ReadWriteLock lock,
TaskMonitor monitor, DBTrace trace, DBTraceThreadManager threadManager)
throws VersionException, IOException {
@@ -60,6 +87,10 @@ public class DBTraceTimeManager implements TraceTimeManager, DBTraceManager {
snapshotStore = factory.getOrCreateCachedStore(DBTraceSnapshot.TABLE_NAME,
DBTraceSnapshot.class, (s, r) -> new DBTraceSnapshot(this, s, r), true);
snapshotsBySchedule = snapshotStore.getIndex(String.class, DBTraceSnapshot.SCHEDULE_COLUMN);
forkStore = factory.getOrCreateCachedStore(DBTraceFork.TABLE_NAME, DBTraceFork.class,
DBTraceFork::new, true);
forksBySnap = forkStore.getIndex(long.class, DBTraceFork.SNAP_COLUMN);
}
@Override
@@ -131,6 +162,23 @@ public class DBTraceTimeManager implements TraceTimeManager, DBTraceManager {
}
}
@Override
public long getMostRecentFork(long snap) {
try (LockHold hold = LockHold.lock(lock.readLock())) {
Entry<Long, DBTraceFork> foundFork = forksBySnap.floorEntry(snap);
if (foundFork == null) {
if (snap < 0) {
return Long.MIN_VALUE;
}
return 0;
}
if (foundFork.getKey() < 0 && snap >= 0) {
return 0;
}
return foundFork.getValue().snap;
}
}
@Override
public Collection<? extends TraceSnapshot> getSnapshotsWithSchedule(TraceSchedule schedule) {
return snapshotsBySchedule.get(schedule.toString());
@@ -269,7 +317,11 @@ public class DBTraceTimeManager implements TraceTimeManager, DBTraceManager {
public void deleteSnapshot(DBTraceSnapshot snapshot) {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
DBTraceFork foundFork = forksBySnap.getOne(snapshot.getKey());
snapshotStore.delete(snapshot);
if (foundFork != null) {
forkStore.delete(foundFork);
}
notifySnapshotDeleted(snapshot);
}
}
@@ -108,6 +108,19 @@ public interface TraceSnapshot {
*/
String getScheduleString();
/**
* Check whether this snapshot represents a fork
*
* <p>
* A snapshot is a fork if the snap immediately preceding it does not actually represents its
* immediately preceding snapshot in time. This is the case if the snapshot has a schedule whose
* initial snapshot is not the one immediately preceding it. NOTE: The (scratch) snapshot with
* the minimum key is <em>not</em> considered a fork.
*
* @return true if a fork, false otherwise
*/
public boolean isFork();
/**
* Set the schedule from some previous snapshot to this one
*
@@ -50,6 +50,22 @@ public interface TraceTimeManager {
*/
TraceSnapshot getMostRecentSnapshot(long snap);
/**
* Get the most recent fork snapshot key since a given key
*
* <p>
* This searches the snapshots for one where {@link TraceSnapshot#isFork()} is true. Note that
* conventionally, negative snaps are <em>scratch</em> space. If a non-negative snap is given,
* then the return fork snap must also be non-negative, i.e., if no non-negative fork snapshot
* is found, this will return 0, the initial snapshot. If a negative snap is given, then the
* return fork snap must also be negative, i.e., if no negative fork snapshot is found, this
* will return {@value Long#MIN_VALUE}, even if that snapshot does not actually exist.
*
* @param snap the snapshot key
* @return the fork snapshot key
*/
long getMostRecentFork(long snap);
/**
* Get all snapshots with the given schedule
*
@@ -26,6 +26,8 @@ import java.util.*;
import java.util.Map.Entry;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.*;
import db.*;
@@ -42,7 +44,10 @@ import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.util.LockHold;
import ghidra.util.database.*;
import ghidra.util.database.annot.*;
import ghidra.util.database.spatial.DBTreeRecord;
import ghidra.util.database.spatial.SpatialMap;
import ghidra.util.database.spatial.rect.Abstract2DRStarTree.XAxis;
import ghidra.util.database.spatial.rect.Abstract2DRStarTree.YAxis;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.ConsoleTaskMonitor;
@@ -411,4 +416,55 @@ public class DBTraceAddressSnapRangePropertyMapSpaceTest
assertEquals(ent(0x1000, 5, entry1), obj.space1.firstEntry());
}
@Test
public void testSplitChoiceLinear() throws Exception {
int total = DBTraceAddressSnapRangePropertyMapTree.MAX_CHILDREN + 1;
try (Transaction tx = obj.openTransaction("Create entries")) {
List<DBTreeRecord<?, ? extends TraceAddressSnapRange>> entries = new ArrayList<>();
for (int i = 0; i < total; i++) {
entries.add(
obj.space1.put(new ImmutableTraceAddressSnapRange(addr(0x1000 + i), 5), null));
}
/**
* NOTE: Because we're using MAX+1, the internal tree will already have split by this
* time. That shouldn't matter. We must use MAX+1, because this choose-axis function
* assumes that number of children given in the list.
*/
Comparator<TraceAddressSnapRange> axis = obj.space1.tree.doChooseSplitAxis(entries);
MatcherAssert.assertThat(axis, Matchers.instanceOf(XAxis.class));
int index = obj.space1.tree.doChooseSplitIndex(entries, axis);
assertEquals(total / 2, index);
}
}
@Test
public void testSplitChoiceTwoLines() throws Exception {
int total = DBTraceAddressSnapRangePropertyMapTree.MAX_CHILDREN + 1;
int cut = total / 2;
try (Transaction tx = obj.openTransaction("Create entries")) {
List<DBTreeRecord<?, ? extends TraceAddressSnapRange>> entries = new ArrayList<>();
for (int i = 0; i < total / 2; i++) {
entries.add(obj.space1
.put(new ImmutableTraceAddressSnapRange(addr(0x1000 + i), 0), null));
}
for (int i = cut; i < total; i++) {
entries.add(obj.space1
.put(new ImmutableTraceAddressSnapRange(addr(0x1000 - cut + i), 10), null));
}
/**
* NOTE: Because we're using MAX+1, the internal tree will already have split by this
* time. That shouldn't matter. We must use MAX+1, because this choose-axis function
* assumes that number of children given in the list.
*/
Comparator<TraceAddressSnapRange> axis = obj.space1.tree.doChooseSplitAxis(entries);
MatcherAssert.assertThat(axis, Matchers.instanceOf(YAxis.class));
int index = obj.space1.tree.doChooseSplitIndex(entries, axis);
assertEquals(cut, index);
}
}
}
@@ -16,6 +16,7 @@
package ghidra.util.database;
import java.io.IOException;
import java.lang.invoke.MethodType;
import java.lang.reflect.*;
import java.lang.reflect.Field;
import java.nio.*;
@@ -41,19 +42,19 @@ import ghidra.util.exception.VersionException;
* See {@link DBAnnotatedObject} for more documentation, including an example object definition. To
* create a store, e.g., for {@code Person}:
*
* <pre>{@code
* <pre>
* interface MyDomainObject {
* Person createPerson(String name, String address);
*
* Person getPerson(long id);
*
* Collection<? extends Person> getPeopleNamed(String name);
* Collection&lt;? extends Person&gt; getPeopleNamed(String name);
* }
*
* public class DBMyDomainObject extends DBCachedDomainObjectAdapter implements MyDomainObject {
* private final DBCachedObjectStoreFactory factory;
* private final DBCachedObjectStore<DBPerson> people;
* private final DBCachedObjectIndex<String, DBPerson> peopleByName;
* private final DBCachedObjectStore&lt;DBPerson&gt; people;
* private final DBCachedObjectIndex&lt;String, DBPerson&gt; peopleByName;
*
* public DBMyDomainObject() { // Constructor parameters elided
* // super() invocation elided
@@ -71,7 +72,7 @@ import ghidra.util.exception.VersionException;
* }
* }
*
* @Override
* &#64;Override
* public Person createPerson(String name, String address) {
* // Locking details elided
* DBPerson person = people.create();
@@ -79,19 +80,19 @@ import ghidra.util.exception.VersionException;
* return person;
* }
*
* @Override
* &#64;Override
* public Person getPerson(int id) {
* // Locking details elided
* return people.getAt(id);
* }
*
* @Override
* public Collection<Person> getPeopleNamed(String name) {
* &#64;Override
* public Collection&lt;Person&gt; getPeopleNamed(String name) {
* // Locking details elided
* return peopleByName.get(name);
* }
* }
* }</pre>
* </pre>
*
* <p>
* The factory manages tables on behalf of the domain object, so it is typically the first thing
@@ -159,7 +160,7 @@ public class DBCachedObjectStoreFactory {
* {@link DBAnnotatedObject} is sufficient. If context is required, then additional interfaces
* can be required via type intersection:
*
* <pre>{@code
* <pre>
* public interface MyContext {
* // ...
* }
@@ -168,21 +169,21 @@ public class DBCachedObjectStoreFactory {
* MyContext getContext();
* }
*
* public static class MyDBFieldCodec<OT extends DBAnnotatedObject & ContextProvider> extends
* AbstractDBFieldCodec<MyType, OT, BinaryField> {
* public static class MyDBFieldCodec&lt;OT extends DBAnnotatedObject & ContextProvider&gt; extends
* AbstractDBFieldCodec&lt;MyType, OT, BinaryField&gt; {
*
* public MyDBFieldCodec(Class<OT> objectType, Field field, int column) {
* public MyDBFieldCodec(Class&lt;OT&gt; objectType, Field field, int column) {
* super(MyType.class, objectType, BinaryField.class, field, column);
* }
*
* @Override
* &#64;Override
* protected void doStore(OT obj, DBRecord record) {
* MyContext ctx = obj.getContext();
* // ...
* }
* // ...
* }
* }</pre>
* </pre>
*
* <p>
* Note that this implementation uses {@link AbstractDBFieldCodec}, which is highly recommended.
@@ -194,25 +195,25 @@ public class DBCachedObjectStoreFactory {
* {@link BinaryField}. See {@link ByteDBFieldCodec} for the simplest example with actual
* encoding and decoding implementations. To use the example codec in an object:
*
* <pre>{@code
* @DBAnnotatedObjectInfo(version = 1)
* <pre>
* &#64;DBAnnotatedObjectInfo(version = 1)
* public static class SomeObject extends DBAnnotatedObject implements ContextProvider {
* static final String MY_COLUMN_NAME = "My";
*
* @DBAnnotatedColumn(MY_COLUMN_NAME)
* &#64;DBAnnotatedColumn(MY_COLUMN_NAME)
* static DBObjectColumn MY_COLUMN;
*
* @DBAnnotatedField(column = MY_COLUMN_NAME, codec = MyDBFieldCodec.class)
* &#64;DBAnnotatedField(column = MY_COLUMN_NAME, codec = MyDBFieldCodec.class)
* private MyType my;
*
* // ...
*
* @Override
* &#64;Override
* public MyContext getContext() {
* // ...
* }
* }
* }</pre>
* </pre>
*
* <p>
* Notice that {@code SomeObject} must implement {@code ContextProvider}. This restriction is
@@ -307,9 +308,11 @@ public class DBCachedObjectStoreFactory {
* This reduces the implementation burden to {@link #doLoad(DBAnnotatedObject, DBRecord)},
* {@link #doStore(DBAnnotatedObject, DBRecord)}, and {@link #store(Object, db.Field)}.
*/
public static abstract class AbstractDBFieldCodec<VT, OT extends DBAnnotatedObject, FT extends db.Field>
public static abstract class AbstractDBFieldCodec<VT, OT extends DBAnnotatedObject,
FT extends db.Field>
implements DBFieldCodec<VT, OT, FT> {
protected final Class<VT> valueType;
protected final Class<VT> boxedType;
protected final Class<OT> objectType;
protected final Class<FT> fieldType;
protected final Field field;
@@ -335,6 +338,9 @@ public class DBCachedObjectStoreFactory {
"Given field does not have the given type: " + valueType + " != " + field);
}
this.valueType = valueType;
@SuppressWarnings("unchecked")
Class<VT> boxedType = (Class<VT>) MethodType.methodType(valueType).wrap().returnType();
this.boxedType = boxedType;
this.objectType = objectType;
this.fieldType = fieldType;
this.field = field;
@@ -379,7 +385,7 @@ public class DBCachedObjectStoreFactory {
@Override
public VT getValue(OT obj) {
try {
return valueType.cast(field.get(obj));
return boxedType.cast(field.get(obj));
}
catch (IllegalArgumentException | IllegalAccessException e) {
throw new AssertionError(e);
@@ -44,10 +44,10 @@ public abstract class AbstractConstraintsTree<
protected final DBCachedObjectStore<DR> dataStore;
protected final DBCachedObjectStore<NR> nodeStore;
protected final Map<Long, Collection<DR>> cachedDataChildren =
new FixedSizeHashMap<>(MAX_CACHE_ENTRIES);
protected final Map<Long, Collection<NR>> cachedNodeChildren =
new FixedSizeHashMap<>(MAX_CACHE_ENTRIES);
protected final Map<Long, Collection<DR>> cachedDataChildren = Collections.synchronizedMap(
new FixedSizeHashMap<>(MAX_CACHE_ENTRIES));
protected final Map<Long, Collection<NR>> cachedNodeChildren = Collections.synchronizedMap(
new FixedSizeHashMap<>(MAX_CACHE_ENTRIES));
protected NR root;
protected int leafLevel;
@@ -314,7 +314,10 @@ public abstract class AbstractConstraintsTree<
}
private void descend(NR nr) {
queue.addAll(getChildrenOf(nr));
List<? extends DBTreeRecord<?, ? extends NS>> passed = getChildrenOf(nr).stream()
.filter(c -> query.testNode(c.getBounds()) != QueryInclusion.NONE)
.toList();
queue.addAll(passed);
}
private DR findNext() {
@@ -323,9 +326,6 @@ public abstract class AbstractConstraintsTree<
if (rec == null) {
return null;
}
if (query != null && query.terminateEarlyNode(rec.getBounds())) {
return null;
}
if (rec instanceof DBTreeDataRecord) {
@SuppressWarnings("unchecked")
DR dr = (DR) rec;
@@ -337,9 +337,7 @@ public abstract class AbstractConstraintsTree<
assert rec instanceof DBTreeNodeRecord;
@SuppressWarnings("unchecked")
NR nr = (NR) rec;
if (query != null && query.testNode(nr.getShape()) != QueryInclusion.NONE) {
descend(nr);
}
descend(nr);
continue;
}
}
@@ -562,7 +560,7 @@ public abstract class AbstractConstraintsTree<
protected void doRecomputeBounds(NR node) {
/*
* TODO: There may be optimizations here, esp. if no bound of the removed node is on the
* LATER: There may be optimizations here, esp. if no bound of the removed node is on the
* edge of the parent. Furthermore, since an implementation may index on those bounds, there
* may be a fast way to discover the "next child in".
*/
@@ -90,6 +90,35 @@ public abstract class AbstractRStarConstraintsTree<
}
}
protected class LeastOverlap implements Comparable<LeastOverlap> {
private final NR node;
private final double areaEnlargement;
private final double overlapEnlargement;
public LeastOverlap(NR node, NS bounds, List<NR> all, double areaEnlargement) {
this.node = node;
this.areaEnlargement = areaEnlargement;
double overlap = computeOverlap(node.getShape(), all, node);
overlapEnlargement =
computeOverlap(node.getShape().unionBounds(bounds), all, node) - overlap;
}
@Override
public int compareTo(AbstractRStarConstraintsTree<DS, DR, NS, NR, T, Q>.LeastOverlap o) {
int result;
result = Double.compare(this.overlapEnlargement, o.overlapEnlargement);
if (result != 0) {
return result;
}
result = Double.compare(this.areaEnlargement, o.areaEnlargement);
if (result != 0) {
return result;
}
return 0;
}
}
protected class LeastDistanceFromCenterToPoint
implements Comparable<LeastDistanceFromCenterToPoint> {
private final DBTreeRecord<?, ? extends NS> record;
@@ -140,15 +169,15 @@ public abstract class AbstractRStarConstraintsTree<
NR node = root;
// CS2
/**
* The paper recommends we only apply the nearly-minimal overlap reduction on the leaf
* parent. I think this is just for cost savings. I get better search performance when I
* apply it to all levels. Insertion is a little slower, though. We can explore reverting
* this, if other tweaks work.
*/
for (int i = 0; i < dstLevelFromTop; i++) {
assert !node.getType().isLeaf();
if (node.getType().isLeafParent()) {
node = findChildByNearlyMinimumOverlapCost(node, bounds);
}
else {
assert node.getType().isDirectory();
node = findChildByMinimumEnlargementCost(node, bounds);
}
node = findChildByNearlyMinimumOverlapCost(node, bounds);
// CS3 (by virtue of setting node, and the while loop
}
return node;
@@ -163,21 +192,15 @@ public abstract class AbstractRStarConstraintsTree<
* @return
*/
protected NR findChildByMinimumEnlargementCost(NR n, NS bounds) {
assert !n.getType().isLeafParent() && n.getType().isDirectory();
NR bestChild = null;
double bestAreaEnlargement = 0;
LeastAreaEnlargementThenLeastArea least = null;
for (NR child : getNodeChildrenOf(n)) {
double candidateAreaEnlargement =
child.getShape().computeAreaUnionBounds(bounds) - child.getShape().getArea();
if (bestChild == null || candidateAreaEnlargement < bestAreaEnlargement ||
(candidateAreaEnlargement == bestAreaEnlargement &&
child.getShape().getArea() < bestChild.getShape().getArea())) {
bestChild = child;
bestAreaEnlargement = candidateAreaEnlargement;
LeastAreaEnlargementThenLeastArea candidate =
new LeastAreaEnlargementThenLeastArea(child, bounds);
if (least == null || candidate.compareTo(least) < 0) {
least = candidate;
}
}
assert bestChild != null;
return bestChild;
return least.node;
}
/**
@@ -192,7 +215,6 @@ public abstract class AbstractRStarConstraintsTree<
* @return the leaf node into which the object should be inserted
*/
protected NR findChildByNearlyMinimumOverlapCost(NR n, NS bounds) {
assert n.getType().isLeafParent();
PriorityQueue<LeastAreaEnlargementThenLeastArea> sorted =
new PriorityQueue<>(n.getChildCount());
List<NR> children = new ArrayList<>(getNodeChildrenOf(n));
@@ -200,29 +222,20 @@ public abstract class AbstractRStarConstraintsTree<
assert leaf.getType().isLeaf();
sorted.offer(new LeastAreaEnlargementThenLeastArea(leaf, bounds));
}
NR bestLeaf = null;
double bestOverlapEnlargement = 0;
double bestAreaEnlargement = 0;
LeastOverlap least = null;
for (int i = 0; i < CHEAT_OVERLAP_COUNT; i++) {
LeastAreaEnlargementThenLeastArea measure = sorted.poll();
if (measure == null) {
break;
}
double candidateOverlap =
computeOverlap(measure.node.getShape(), children, measure.node);
double candidateOverlapEnlargement =
computeOverlap(measure.node.getShape().unionBounds(bounds), children,
measure.node) - candidateOverlap;
if (bestLeaf == null || candidateOverlapEnlargement < bestOverlapEnlargement ||
(candidateOverlapEnlargement == bestOverlapEnlargement &&
measure.areaEnlargement < bestAreaEnlargement)) {
bestLeaf = measure.node;
bestOverlapEnlargement = candidateOverlapEnlargement;
bestAreaEnlargement = measure.areaEnlargement;
LeastOverlap candidate =
new LeastOverlap(measure.node, bounds, children, measure.areaEnlargement);
if (least == null || candidate.compareTo(least) < 0) {
least = candidate;
}
}
assert bestLeaf != null;
return bestLeaf;
return least.node;
}
/**
@@ -358,6 +371,19 @@ public abstract class AbstractRStarConstraintsTree<
return bestAxis;
}
record IndexScore(int index, double overlap, double areaSum, double areaDiff)
implements Comparable<IndexScore> {
static final Comparator<IndexScore> COMPARATOR = Comparator
.comparing(IndexScore::overlap)
.thenComparing(IndexScore::areaSum)
.thenComparing(IndexScore::areaDiff);
@Override
public int compareTo(IndexScore o) {
return COMPARATOR.compare(this, o);
}
}
protected int doChooseSplitIndex(List<DBTreeRecord<?, ? extends NS>> children,
Comparator<NS> axis) {
children.sort(Comparator.comparing(DBTreeRecord::getBounds, axis));
@@ -368,21 +394,20 @@ public abstract class AbstractRStarConstraintsTree<
// mmm-------mmm (m = 3)
// 8 distributions : 12 - 2*3 + 2
List<DBTreeRecord<?, ? extends NS>> reversed = children.reversed();
NS boundsFirstKChildren =
unionStream(children.subList(0, minChildren).stream().map(DBTreeRecord::getBounds));
NS boundsLastKChildren =
unionStream(children.subList(maxChildren + 1 - minChildren, maxChildren + 1)
.stream()
.map(DBTreeRecord::getBounds));
unionStream(reversed.subList(0, minChildren).stream().map(DBTreeRecord::getBounds));
int maxK = maxChildren + 1 - minChildren * 2;
Deque<NS> boundsFirsts = new ArrayDeque<>();
Deque<NS> boundsSeconds = new ArrayDeque<>();
boundsFirsts.addLast(boundsFirstKChildren);
boundsSeconds.addFirst(boundsLastKChildren);
for (int k = 0; k <= maxK; k++) {
NS forFirst = children.get(minChildren + k).getBounds();
NS forSecond = children.get(maxChildren - minChildren - k).getBounds();
for (int k = 1; k <= maxK; k++) { // i=0 case already done
NS forFirst = children.get(minChildren - 1 + k).getBounds();
NS forSecond = reversed.get(minChildren - 1 + k).getBounds();
boundsFirstKChildren = boundsFirstKChildren.unionBounds(forFirst);
boundsLastKChildren = boundsLastKChildren.unionBounds(forSecond);
@@ -392,22 +417,22 @@ public abstract class AbstractRStarConstraintsTree<
}
// CSI1
double bestOverlapValue = Double.MAX_VALUE;
double bestAreaValue = Double.MAX_VALUE;
int bestIndex = -1;
IndexScore best = null;
for (int k = 0; k <= maxK; k++) {
NS boundsFirstGroup = boundsFirsts.removeFirst();
NS boundsSecondGroup = boundsSeconds.removeFirst();
double overlapValue = boundsFirstGroup.computeAreaIntersection(boundsSecondGroup);
double areaValue = boundsFirstGroup.getArea() + boundsSecondGroup.getArea();
if (bestIndex == -1 || overlapValue < bestOverlapValue ||
(overlapValue == bestOverlapValue && areaValue < bestAreaValue)) {
bestIndex = k;
bestOverlapValue = overlapValue;
double areaFirst = boundsFirstGroup.getArea();
double areaSecond = boundsSecondGroup.getArea();
IndexScore candidate = new IndexScore(k,
boundsFirstGroup.computeAreaIntersection(boundsSecondGroup),
areaFirst + areaSecond,
Math.abs(areaFirst - areaSecond));
if (best == null || candidate.compareTo(best) < 0) {
best = candidate;
}
}
assert bestIndex != -1;
return bestIndex + minChildren;
return best.index + minChildren;
}
protected static class ReInsertInfo {
@@ -16,6 +16,7 @@
package ghidra.util.database.spatial;
public interface BoundingShape<S extends BoundingShape<S>> extends BoundedShape<S> {
double getArea();
double getMargin();
@@ -4,9 +4,9 @@
* 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.
@@ -20,47 +20,48 @@ 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>> //
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>> {
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) {
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) {
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) {
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) {
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);
@@ -4,9 +4,9 @@
* 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.
@@ -23,20 +23,20 @@ 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>> //
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>>
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);
@@ -4,9 +4,9 @@
* 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.
@@ -28,7 +28,7 @@ public interface Dimension<T, P extends HyperPoint, B extends HyperBox<P, B>> {
int compare(T a, T b);
double distance(T a, T b);
double distance(T upper, T lower);
T mid(T a, T b);
@@ -4,9 +4,9 @@
* 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.
@@ -24,8 +24,8 @@ public interface LongDimension<P extends HyperPoint, B extends HyperBox<P, B>>
}
@Override
default double distance(Long a, Long b) {
return a - b;
default double distance(Long upper, Long lower) {
return upper - lower;
}
@Override
@@ -4,9 +4,9 @@
* 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.
@@ -60,19 +60,19 @@ public interface StringDimension<P extends HyperPoint, B extends HyperBox<P, B>>
}
@Override
default double distance(String a, String b) {
if (Objects.equals(a, b)) {
default double distance(String upper, String lower) {
if (Objects.equals(upper, lower)) {
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);
int len = lenStrings(upper, lower);
for (int i = 0; i < len; i++) {
int ca = charAt(a, i);
int cb = charAt(b, i);
int cu = charAt(upper, i);
int cl = charAt(lower, i);
double oldResult = result;
result += placeVal * (cb - ca);
result += placeVal * (cu - cl);
if (oldResult == result) {
// Can't capture any more precision, so we're done
return result;
@@ -4,9 +4,9 @@
* 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.
@@ -24,8 +24,8 @@ public interface ULongDimension<P extends HyperPoint, B extends HyperBox<P, B>>
}
@Override
default double distance(Long a, Long b) {
return a - b;
default double distance(Long upper, Long lower) {
return upper - lower;
}
@Override
@@ -4,9 +4,9 @@
* 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.
@@ -23,20 +23,20 @@ import ghidra.util.database.DBCachedObjectStoreFactory;
import ghidra.util.database.spatial.*;
import ghidra.util.exception.VersionException;
public abstract class Abstract2DRStarTree< //
X, Y, //
DS extends BoundedShape<NS>, //
DR extends DBTreeDataRecord<DS, NS, T>, //
NS extends Rectangle2D<X, Y, NS>, //
NR extends DBTreeNodeRecord<NS>, //
T, //
Q extends AbstractRectangle2DQuery<X, Y, DS, NS, Q>> //
public abstract class Abstract2DRStarTree<
X, Y,
DS extends BoundedShape<NS>,
DR extends DBTreeDataRecord<DS, NS, T>,
NS extends Rectangle2D<X, Y, NS>,
NR extends DBTreeNodeRecord<NS>,
T,
Q extends AbstractRectangle2DQuery<X, Y, 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 Rectangle2D<?, ?, NS>, T, Q extends AbstractRectangle2DQuery<?, ?, DS, NS, Q>>
protected static class AsSpatialMap<
DS extends BoundedShape<NS>,
DR extends DBTreeDataRecord<DS, NS, T>,
NS extends Rectangle2D<?, ?, NS>, T, Q extends AbstractRectangle2DQuery<?, ?, DS, NS, Q>>
extends AbstractConstraintsTreeSpatialMap<DS, DR, NS, T, Q> {
public AsSpatialMap(AbstractConstraintsTree<DS, DR, NS, ?, T, Q> tree, Q query) {
super(tree, query);
@@ -52,23 +52,40 @@ public abstract class Abstract2DRStarTree< //
protected final EuclideanSpace2D<X, Y> space;
protected final List<Comparator<NS>> axes;
// public for test access
public static class XAxis<X, Y, NS extends Rectangle2D<X, Y, NS>> implements Comparator<NS> {
private final EuclideanSpace2D<X, Y> space;
public XAxis(EuclideanSpace2D<X, Y> space) {
this.space = space;
}
@Override
public int compare(NS o1, NS o2) {
return space.compareX(o1.getX1(), o2.getX1());
}
}
// public for test access
public static class YAxis<X, Y, NS extends Rectangle2D<X, Y, NS>> implements Comparator<NS> {
private final EuclideanSpace2D<X, Y> space;
public YAxis(EuclideanSpace2D<X, Y> space) {
this.space = space;
}
@Override
public int compare(NS o1, NS o2) {
return space.compareY(o1.getY1(), o2.getY1());
}
}
public Abstract2DRStarTree(DBCachedObjectStoreFactory storeFactory, String tableName,
EuclideanSpace2D<X, Y> space, Class<DR> dataType, Class<NR> nodeType,
boolean upgradable, int maxChildren) throws VersionException, IOException {
super(storeFactory, tableName, dataType, nodeType, upgradable, maxChildren);
this.space = space;
this.axes = List.of(new Comparator<NS>() {
@Override
public int compare(NS o1, NS o2) {
return space.compareX(o1.getX1(), o2.getX1());
}
}, new Comparator<NS>() {
@Override
public int compare(NS o1, NS o2) {
return space.compareY(o1.getY1(), o2.getY1());
}
});
this.axes = List.of(new XAxis<>(space), new YAxis<>(space));
}
@Override
@@ -4,9 +4,9 @@
* 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.
@@ -20,43 +20,48 @@ import java.util.Comparator;
import ghidra.util.database.spatial.BoundedShape;
import ghidra.util.database.spatial.Query;
public abstract class AbstractRectangle2DQuery< //
X, Y, //
DS extends BoundedShape<NS>, //
NS extends Rectangle2D<X, Y, NS>, //
Q extends AbstractRectangle2DQuery<X, Y, DS, NS, Q>> //
public abstract class AbstractRectangle2DQuery<
X, Y,
DS extends BoundedShape<NS>,
NS extends Rectangle2D<X, Y, NS>,
Q extends AbstractRectangle2DQuery<X, Y, DS, NS, Q>>
implements Query<DS, NS> {
public interface QueryFactory<NS extends Rectangle2D<?, ?, NS>, Q extends AbstractRectangle2DQuery<?, ?, ?, NS, Q>> {
public interface QueryFactory<NS extends Rectangle2D<?, ?, NS>,
Q extends AbstractRectangle2DQuery<?, ?, ?, NS, Q>> {
Q create(NS r1, NS r2, Rectangle2DDirection direction);
}
protected static <X, Y, NS extends Rectangle2D<X, Y, NS>, Q extends AbstractRectangle2DQuery<X, Y, ?, NS, Q>> Q intersecting(
NS rect, Rectangle2DDirection direction, QueryFactory<NS, Q> factory) {
protected static <X, Y, NS extends Rectangle2D<X, Y, NS>,
Q extends AbstractRectangle2DQuery<X, Y, ?, NS, Q>> Q intersecting(
NS rect, Rectangle2DDirection direction, QueryFactory<NS, Q> factory) {
Rectangle2D<X, Y, ?> full = rect.getSpace().getFull();
NS r1 = rect.immutable(full.getX1(), rect.getX2(), full.getY1(), rect.getY2());
NS r2 = rect.immutable(rect.getX1(), full.getX2(), rect.getY1(), full.getY2());
return factory.create(r1, r2, direction);
}
protected static <X, Y, NS extends Rectangle2D<X, Y, NS>, Q extends AbstractRectangle2DQuery<X, Y, ?, NS, Q>> Q enclosing(
NS rect, Rectangle2DDirection direction, QueryFactory<NS, Q> factory) {
protected static <X, Y, NS extends Rectangle2D<X, Y, NS>,
Q extends AbstractRectangle2DQuery<X, Y, ?, NS, Q>> Q enclosing(
NS rect, Rectangle2DDirection direction, QueryFactory<NS, Q> factory) {
Rectangle2D<X, Y, ?> full = rect.getSpace().getFull();
NS r1 = rect.immutable(full.getX1(), rect.getX1(), full.getY1(), rect.getY1());
NS r2 = rect.immutable(rect.getX2(), full.getX2(), rect.getY2(), full.getY2());
return factory.create(r1, r2, direction);
}
protected static <X, Y, NS extends Rectangle2D<X, Y, NS>, Q extends AbstractRectangle2DQuery<X, Y, ?, NS, Q>> Q enclosed(
NS rect, Rectangle2DDirection direction, QueryFactory<NS, Q> factory) {
protected static <X, Y, NS extends Rectangle2D<X, Y, NS>,
Q extends AbstractRectangle2DQuery<X, Y, ?, NS, Q>> Q enclosed(
NS rect, Rectangle2DDirection direction, QueryFactory<NS, Q> factory) {
Rectangle2D<X, Y, ?> full = rect.getSpace().getFull();
NS r1 = rect.immutable(rect.getX1(), full.getX2(), rect.getY1(), full.getY2());
NS r2 = rect.immutable(full.getX1(), rect.getX2(), full.getY1(), rect.getY2());
return factory.create(r1, r2, direction);
}
protected static <X, Y, NS extends Rectangle2D<X, Y, NS>, Q extends AbstractRectangle2DQuery<X, Y, ?, NS, Q>> Q equalTo(
NS rect, Rectangle2DDirection direction, QueryFactory<NS, Q> factory) {
protected static <X, Y, NS extends Rectangle2D<X, Y, NS>,
Q extends AbstractRectangle2DQuery<X, Y, ?, NS, Q>> Q equalTo(
NS rect, Rectangle2DDirection direction, QueryFactory<NS, Q> factory) {
NS r1 = rect.immutable(rect.getX1(), rect.getX1(), rect.getY1(), rect.getY1());
NS r2 = rect.immutable(rect.getX2(), rect.getX2(), rect.getY2(), rect.getY2());
return factory.create(r1, r2, direction);
@@ -0,0 +1,61 @@
/* ###
* 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 utilities.util;
import java.util.Deque;
import java.util.LinkedList;
import java.util.stream.Collectors;
import ghidra.util.Msg;
public class DbgMsgTracer {
private static final ThreadLocal<DbgMsgTracer> INSTANCES =
ThreadLocal.withInitial(DbgMsgTracer::new);
private final Deque<CallRec> stack = new LinkedList<>();
public static CallRec rec(Object obj, String name) {
DbgMsgTracer tracer = INSTANCES.get();
CallRec rec = new CallRec(tracer, obj, name, System.currentTimeMillis());
tracer.stack.push(rec);
tracer.doMsg(obj, "%d: (ENTER)".formatted(rec.start));
return rec;
}
String prefixStack() {
return stack.reversed().stream().map(CallRec::name).collect(Collectors.joining(" > "));
}
public static void msg(Object obj, String message) {
INSTANCES.get().doMsg(obj, message);
}
private void doMsg(Object obj, String message) {
Msg.info(obj, "%s %s".formatted(prefixStack(), message));
}
public record CallRec(DbgMsgTracer tracer, Object obj, String name, long start)
implements AutoCloseable {
@Override
public void close() {
long stop = System.currentTimeMillis();
long elapsedMs = stop - start;
tracer.doMsg(obj, "%d: (EXITED) after %f s".formatted(stop, elapsedMs / 1000.0));
CallRec popped = tracer.stack.pop();
assert popped == this;
}
}
}