mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-20 07:14:38 +08:00
GP-6550: Fix several issues with R*-Trees causing slowdowns in Trace.
This commit is contained in:
+93
@@ -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();
|
||||
}
|
||||
}
|
||||
+321
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+196
@@ -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();
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-31
@@ -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;
|
||||
}
|
||||
|
||||
+39
-6
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+97
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+45
-4
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -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());
|
||||
}
|
||||
|
||||
+5
-5
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+31
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+52
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+13
@@ -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
|
||||
*
|
||||
|
||||
+16
@@ -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
|
||||
*
|
||||
|
||||
+56
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-23
@@ -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<? extends Person> 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<DBPerson> people;
|
||||
* private final DBCachedObjectIndex<String, DBPerson> peopleByName;
|
||||
*
|
||||
* public DBMyDomainObject() { // Constructor parameters elided
|
||||
* // super() invocation elided
|
||||
@@ -71,7 +72,7 @@ import ghidra.util.exception.VersionException;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* @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
|
||||
* @Override
|
||||
* public Person getPerson(int id) {
|
||||
* // Locking details elided
|
||||
* return people.getAt(id);
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public Collection<Person> getPeopleNamed(String name) {
|
||||
* @Override
|
||||
* public Collection<Person> 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<OT extends DBAnnotatedObject & ContextProvider> extends
|
||||
* AbstractDBFieldCodec<MyType, OT, BinaryField> {
|
||||
*
|
||||
* public MyDBFieldCodec(Class<OT> objectType, Field field, int column) {
|
||||
* public MyDBFieldCodec(Class<OT> objectType, Field field, int column) {
|
||||
* super(MyType.class, objectType, BinaryField.class, field, column);
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* @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>
|
||||
* @DBAnnotatedObjectInfo(version = 1)
|
||||
* public static class SomeObject extends DBAnnotatedObject implements ContextProvider {
|
||||
* static final String MY_COLUMN_NAME = "My";
|
||||
*
|
||||
* @DBAnnotatedColumn(MY_COLUMN_NAME)
|
||||
* @DBAnnotatedColumn(MY_COLUMN_NAME)
|
||||
* static DBObjectColumn MY_COLUMN;
|
||||
*
|
||||
* @DBAnnotatedField(column = MY_COLUMN_NAME, codec = MyDBFieldCodec.class)
|
||||
* @DBAnnotatedField(column = MY_COLUMN_NAME, codec = MyDBFieldCodec.class)
|
||||
* private MyType my;
|
||||
*
|
||||
* // ...
|
||||
*
|
||||
* @Override
|
||||
* @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);
|
||||
|
||||
+10
-12
@@ -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".
|
||||
*/
|
||||
|
||||
+78
-53
@@ -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 {
|
||||
|
||||
+1
@@ -16,6 +16,7 @@
|
||||
package ghidra.util.database.spatial;
|
||||
|
||||
public interface BoundingShape<S extends BoundingShape<S>> extends BoundedShape<S> {
|
||||
|
||||
double getArea();
|
||||
|
||||
double getMargin();
|
||||
|
||||
+21
-20
@@ -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);
|
||||
|
||||
+14
-14
@@ -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);
|
||||
|
||||
+3
-3
@@ -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
-4
@@ -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
|
||||
|
||||
+8
-8
@@ -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
-4
@@ -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
|
||||
|
||||
+43
-26
@@ -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
|
||||
|
||||
+21
-16
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user