diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/internal/RStarDiagnosticsPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/internal/RStarDiagnosticsPlugin.java new file mode 100644 index 0000000000..8eaf783aa7 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/internal/RStarDiagnosticsPlugin.java @@ -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(); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/internal/RStarPlotProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/internal/RStarPlotProvider.java new file mode 100644 index 0000000000..efd1526bf7 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/internal/RStarPlotProvider.java @@ -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 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); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/internal/RStarTreeProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/internal/RStarTreeProvider.java new file mode 100644 index 0000000000..a7055008b5 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/internal/RStarTreeProvider.java @@ -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 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 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, 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(); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerSnapshotTablePanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerSnapshotTablePanel.java index 939b5b391d..bbf4a1c9ae 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerSnapshotTablePanel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerSnapshotTablePanel.java @@ -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(); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceTimeViewport.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceTimeViewport.java index fad17ba9ba..71878993be 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceTimeViewport.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceTimeViewport.java @@ -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 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; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMapSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMapSpace.java index 27b71332b6..3252074ddc 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMapSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMapSpace.java @@ -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> +public class DBTraceAddressSnapRangePropertyMapSpace> implements DBTraceSpaceBased, SpatialMap, TraceAddressSnapRangePropertyMapSpace { @@ -45,7 +44,8 @@ public class DBTraceAddressSnapRangePropertyMapSpace tree; - protected final AbstractConstraintsTreeSpatialMap map; + protected final AbstractConstraintsTreeSpatialMap map; protected final AddressRangeImpl fullSpace; public DBTraceAddressSnapRangePropertyMapSpace(String tableName, DBTrace trace, @@ -205,4 +205,37 @@ public class DBTraceAddressSnapRangePropertyMapSpace> + getChildrenOf(DBTreeNodeRecord rec) { + return tree.internalGetChildrenOf(rec); + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMapTree.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMapTree.java index 05689db55a..0411587e7f 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMapTree.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMapTree.java @@ -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 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> + internalGetChildrenOf(DBTreeNodeRecord rec) { + if (!(rec instanceof DBTraceAddressSnapRangePropertyMapNode node)) { + return List.of(); + } + return getChildrenOf(node); + } + + @Override // expose for testing + protected Comparator + doChooseSplitAxis(List> children) { + return super.doChooseSplitAxis(children); + } + + @Override // expose for testing + protected int doChooseSplitIndex( + List> children, + Comparator axis) { + return super.doChooseSplitIndex(children, axis); + } + + @Override // expose for testing + protected Collection> + getChildrenOf(DBTraceAddressSnapRangePropertyMapNode parent) { + return super.getChildrenOf(parent); + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemorySpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemorySpace.java index 0b8047496d..7e9899a84d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemorySpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemorySpace.java @@ -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 stateMapSpace; + protected final DBTraceAddressSnapRangePropertyMapSpace stateMapSpace; protected final DBCachedObjectStore bufferStore; protected final DBCachedObjectStore blockStore; @@ -135,7 +140,9 @@ public class DBTraceMemorySpace var l = new Object() { boolean changed; }; - new AddressRangeMapSetter, TraceMemoryState>() { + new AddressRangeMapSetter, + TraceMemoryState>() { + @Override protected AddressRange getRange(Entry 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> 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> + getChildrenOf(DBTreeNodeRecord rec) { + return stateMapSpace.getChildrenOf(rec); + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ImmutableValueBox.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ImmutableValueBox.java index f00e6ff44b..60715f1dd5 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ImmutableValueBox.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ImmutableValueBox.java @@ -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()); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ValueSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ValueSpace.java index 861c9a7a3e..f0841a1292 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ValueSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ValueSpace.java @@ -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 { } @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; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceSnapshot.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceSnapshot.java index 290087089b..564acfdddd 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceSnapshot.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceSnapshot.java @@ -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); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceTimeManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceTimeManager.java index 6588ea8e38..4cf65d1f6b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceTimeManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceTimeManager.java @@ -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 snapshotStore; protected final DBCachedObjectIndex snapshotsBySchedule; + protected final DBCachedObjectStore forkStore; + protected final DBCachedObjectIndex 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 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 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); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/TraceSnapshot.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/TraceSnapshot.java index d691d4ed06..fd2575f65a 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/TraceSnapshot.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/TraceSnapshot.java @@ -108,6 +108,19 @@ public interface TraceSnapshot { */ String getScheduleString(); + /** + * Check whether this snapshot represents a fork + * + *

+ * 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 not considered a fork. + * + * @return true if a fork, false otherwise + */ + public boolean isFork(); + /** * Set the schedule from some previous snapshot to this one * diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/TraceTimeManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/TraceTimeManager.java index 498dab653d..e291f8c95a 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/TraceTimeManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/TraceTimeManager.java @@ -50,6 +50,22 @@ public interface TraceTimeManager { */ TraceSnapshot getMostRecentSnapshot(long snap); + /** + * Get the most recent fork snapshot key since a given key + * + *

+ * This searches the snapshots for one where {@link TraceSnapshot#isFork()} is true. Note that + * conventionally, negative snaps are scratch 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 * diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMapSpaceTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMapSpaceTest.java index 4033dc8fa6..eb18ad6ab0 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMapSpaceTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMapSpaceTest.java @@ -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> 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 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> 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 axis = obj.space1.tree.doChooseSplitAxis(entries); + MatcherAssert.assertThat(axis, Matchers.instanceOf(YAxis.class)); + + int index = obj.space1.tree.doChooseSplitIndex(entries, axis); + assertEquals(cut, index); + } + } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/DBCachedObjectStoreFactory.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/DBCachedObjectStoreFactory.java index 179ce06a4e..6477eed825 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/DBCachedObjectStoreFactory.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/DBCachedObjectStoreFactory.java @@ -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}: * - *

{@code
+ * 
  * interface MyDomainObject {
  * 	Person createPerson(String name, String address);
  * 
  * 	Person getPerson(long id);
  * 
- * 	Collection getPeopleNamed(String name);
+ * 	Collection<? extends Person> getPeopleNamed(String name);
  * }
  * 
  * public class DBMyDomainObject extends DBCachedDomainObjectAdapter implements MyDomainObject {
  * 	private final DBCachedObjectStoreFactory factory;
- * 	private final DBCachedObjectStore people;
- * 	private final DBCachedObjectIndex 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 getPeopleNamed(String name) {
+ * 	@Override
+ * 	public Collection<Person> getPeopleNamed(String name) {
  * 		// Locking details elided
  * 		return peopleByName.get(name);
  * 	}
  * }
- * }
+ *
* *

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

{@code
+	 * 
 	 * public interface MyContext {
 	 * 	// ...
 	 * }
@@ -168,21 +169,21 @@ public class DBCachedObjectStoreFactory {
 	 * 	MyContext getContext();
 	 * }
 	 * 
-	 * public static class MyDBFieldCodec extends
-	 * 		AbstractDBFieldCodec {
+	 * public static class MyDBFieldCodec<OT extends DBAnnotatedObject & ContextProvider> extends
+	 * 		AbstractDBFieldCodec<MyType, OT, BinaryField> {
 	 * 
-	 * 	public MyDBFieldCodec(Class 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();
 	 * 		// ...
 	 * 	}
 	 * 	// ...
 	 * }
-	 * }
+ *
* *

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

{@code
-	 * @DBAnnotatedObjectInfo(version = 1)
+	 * 
+	 * @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() {
 	 * 		// ...
 	 * 	}
 	 * }
-	 * }
+ *
* *

* 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 + public static abstract class AbstractDBFieldCodec implements DBFieldCodec { protected final Class valueType; + protected final Class boxedType; protected final Class objectType; protected final Class 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 boxedType = (Class) 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); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/AbstractConstraintsTree.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/AbstractConstraintsTree.java index ab159d8642..700034a5b9 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/AbstractConstraintsTree.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/AbstractConstraintsTree.java @@ -44,10 +44,10 @@ public abstract class AbstractConstraintsTree< protected final DBCachedObjectStore dataStore; protected final DBCachedObjectStore nodeStore; - protected final Map> cachedDataChildren = - new FixedSizeHashMap<>(MAX_CACHE_ENTRIES); - protected final Map> cachedNodeChildren = - new FixedSizeHashMap<>(MAX_CACHE_ENTRIES); + protected final Map> cachedDataChildren = Collections.synchronizedMap( + new FixedSizeHashMap<>(MAX_CACHE_ENTRIES)); + protected final Map> 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> 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". */ diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/AbstractRStarConstraintsTree.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/AbstractRStarConstraintsTree.java index bc8d7015d6..d2180a80d8 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/AbstractRStarConstraintsTree.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/AbstractRStarConstraintsTree.java @@ -90,6 +90,35 @@ public abstract class AbstractRStarConstraintsTree< } } + protected class LeastOverlap implements Comparable { + private final NR node; + private final double areaEnlargement; + private final double overlapEnlargement; + + public LeastOverlap(NR node, NS bounds, List 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.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 { private final DBTreeRecord 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 sorted = new PriorityQueue<>(n.getChildCount()); List 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 { + static final Comparator 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> children, Comparator 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> 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 boundsFirsts = new ArrayDeque<>(); Deque 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 { diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/BoundingShape.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/BoundingShape.java index 8eed8523ae..bbcd15dd56 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/BoundingShape.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/BoundingShape.java @@ -16,6 +16,7 @@ package ghidra.util.database.spatial; public interface BoundingShape> extends BoundedShape { + double getArea(); double getMargin(); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/AbstractHyperBoxQuery.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/AbstractHyperBoxQuery.java index 13ac4e8bd6..22ff8768e7 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/AbstractHyperBoxQuery.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/AbstractHyperBoxQuery.java @@ -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 extends HyperBox, // - Q extends AbstractHyperBoxQuery> // +public abstract class AbstractHyperBoxQuery< + P extends HyperPoint, + DS extends BoundedShape, + NS extends HyperBox, + Q extends AbstractHyperBoxQuery> implements Query { - public interface QueryFactory, Q extends AbstractHyperBoxQuery> { + public interface QueryFactory, + Q extends AbstractHyperBoxQuery> { Q create(NS ls, NS us, HyperDirection direction); } - protected static

, // - Q extends AbstractHyperBoxQuery> Q intersecting(NS shape, - HyperDirection direction, QueryFactory factory) { + protected static

, + Q extends AbstractHyperBoxQuery> Q intersecting(NS shape, + HyperDirection direction, QueryFactory factory) { HyperBox 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

, // - Q extends AbstractHyperBoxQuery> Q enclosing(NS shape, - HyperDirection direction, QueryFactory factory) { + protected static

, + Q extends AbstractHyperBoxQuery> Q enclosing(NS shape, + HyperDirection direction, QueryFactory factory) { HyperBox 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

, // - Q extends AbstractHyperBoxQuery> Q enclosed(NS shape, - HyperDirection direction, QueryFactory factory) { + protected static

, + Q extends AbstractHyperBoxQuery> Q enclosed(NS shape, + HyperDirection direction, QueryFactory factory) { HyperBox 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

, // - Q extends AbstractHyperBoxQuery> Q equalTo(NS shape, - HyperDirection direction, QueryFactory factory) { + protected static

, + Q extends AbstractHyperBoxQuery> Q equalTo(NS shape, + HyperDirection direction, QueryFactory factory) { NS ls = shape.immutable(shape.lCorner(), shape.lCorner()); NS us = shape.immutable(shape.uCorner(), shape.uCorner()); return factory.create(ls, us, direction); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/AbstractHyperRStarTree.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/AbstractHyperRStarTree.java index d8dfadf2e4..aea57b30d2 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/AbstractHyperRStarTree.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/AbstractHyperRStarTree.java @@ -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, // - DR extends DBTreeDataRecord, // - NS extends HyperBox, // - NR extends DBTreeNodeRecord, // - T, // - Q extends AbstractHyperBoxQuery> // +public abstract class AbstractHyperRStarTree< + P extends HyperPoint, + DS extends BoundedShape, + DR extends DBTreeDataRecord, + NS extends HyperBox, + NR extends DBTreeNodeRecord, + T, + Q extends AbstractHyperBoxQuery> extends AbstractRStarConstraintsTree { - protected static class AsSpatialMap< // - DS extends BoundedShape, // - DR extends DBTreeDataRecord, // - NS extends HyperBox, T, Q extends AbstractHyperBoxQuery> + protected static class AsSpatialMap< + DS extends BoundedShape, + DR extends DBTreeDataRecord, + NS extends HyperBox, T, Q extends AbstractHyperBoxQuery> extends AbstractConstraintsTreeSpatialMap { public AsSpatialMap(AbstractConstraintsTree tree, Q query) { super(tree, query); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/Dimension.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/Dimension.java index 26c2271468..a9b1218fe8 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/Dimension.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/Dimension.java @@ -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> { int compare(T a, T b); - double distance(T a, T b); + double distance(T upper, T lower); T mid(T a, T b); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/LongDimension.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/LongDimension.java index e04cc4cd3b..16e2095c12 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/LongDimension.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/LongDimension.java @@ -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

> } @Override - default double distance(Long a, Long b) { - return a - b; + default double distance(Long upper, Long lower) { + return upper - lower; } @Override diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/StringDimension.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/StringDimension.java index bf49232bbf..85f73a7926 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/StringDimension.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/StringDimension.java @@ -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

> } @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; diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/ULongDimension.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/ULongDimension.java index 456b966224..67d0b98af4 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/ULongDimension.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/ULongDimension.java @@ -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

> } @Override - default double distance(Long a, Long b) { - return a - b; + default double distance(Long upper, Long lower) { + return upper - lower; } @Override diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/rect/Abstract2DRStarTree.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/rect/Abstract2DRStarTree.java index bd7cb74c1c..c4be7287a5 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/rect/Abstract2DRStarTree.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/rect/Abstract2DRStarTree.java @@ -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, // - DR extends DBTreeDataRecord, // - NS extends Rectangle2D, // - NR extends DBTreeNodeRecord, // - T, // - Q extends AbstractRectangle2DQuery> // +public abstract class Abstract2DRStarTree< + X, Y, + DS extends BoundedShape, + DR extends DBTreeDataRecord, + NS extends Rectangle2D, + NR extends DBTreeNodeRecord, + T, + Q extends AbstractRectangle2DQuery> extends AbstractRStarConstraintsTree { - protected static class AsSpatialMap< // - DS extends BoundedShape, // - DR extends DBTreeDataRecord, // - NS extends Rectangle2D, T, Q extends AbstractRectangle2DQuery> + protected static class AsSpatialMap< + DS extends BoundedShape, + DR extends DBTreeDataRecord, + NS extends Rectangle2D, T, Q extends AbstractRectangle2DQuery> extends AbstractConstraintsTreeSpatialMap { public AsSpatialMap(AbstractConstraintsTree tree, Q query) { super(tree, query); @@ -52,23 +52,40 @@ public abstract class Abstract2DRStarTree< // protected final EuclideanSpace2D space; protected final List> axes; + // public for test access + public static class XAxis> implements Comparator { + private final EuclideanSpace2D space; + + public XAxis(EuclideanSpace2D 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> implements Comparator { + private final EuclideanSpace2D space; + + public YAxis(EuclideanSpace2D 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 space, Class dataType, Class nodeType, boolean upgradable, int maxChildren) throws VersionException, IOException { super(storeFactory, tableName, dataType, nodeType, upgradable, maxChildren); this.space = space; - - this.axes = List.of(new Comparator() { - @Override - public int compare(NS o1, NS o2) { - return space.compareX(o1.getX1(), o2.getX1()); - } - }, new Comparator() { - @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 diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/rect/AbstractRectangle2DQuery.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/rect/AbstractRectangle2DQuery.java index 324cb7df70..4d0eb39738 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/rect/AbstractRectangle2DQuery.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/rect/AbstractRectangle2DQuery.java @@ -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 extends Rectangle2D, // - Q extends AbstractRectangle2DQuery> // +public abstract class AbstractRectangle2DQuery< + X, Y, + DS extends BoundedShape, + NS extends Rectangle2D, + Q extends AbstractRectangle2DQuery> implements Query { - public interface QueryFactory, Q extends AbstractRectangle2DQuery> { + public interface QueryFactory, + Q extends AbstractRectangle2DQuery> { Q create(NS r1, NS r2, Rectangle2DDirection direction); } - protected static , Q extends AbstractRectangle2DQuery> Q intersecting( - NS rect, Rectangle2DDirection direction, QueryFactory factory) { + protected static , + Q extends AbstractRectangle2DQuery> Q intersecting( + NS rect, Rectangle2DDirection direction, QueryFactory factory) { Rectangle2D 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 , Q extends AbstractRectangle2DQuery> Q enclosing( - NS rect, Rectangle2DDirection direction, QueryFactory factory) { + protected static , + Q extends AbstractRectangle2DQuery> Q enclosing( + NS rect, Rectangle2DDirection direction, QueryFactory factory) { Rectangle2D 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 , Q extends AbstractRectangle2DQuery> Q enclosed( - NS rect, Rectangle2DDirection direction, QueryFactory factory) { + protected static , + Q extends AbstractRectangle2DQuery> Q enclosed( + NS rect, Rectangle2DDirection direction, QueryFactory factory) { Rectangle2D 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 , Q extends AbstractRectangle2DQuery> Q equalTo( - NS rect, Rectangle2DDirection direction, QueryFactory factory) { + protected static , + Q extends AbstractRectangle2DQuery> Q equalTo( + NS rect, Rectangle2DDirection direction, QueryFactory 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); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/utilities/util/DbgMsgTracer.java b/Ghidra/Debug/ProposedUtils/src/main/java/utilities/util/DbgMsgTracer.java new file mode 100644 index 0000000000..6a21167865 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/utilities/util/DbgMsgTracer.java @@ -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 INSTANCES = + ThreadLocal.withInitial(DbgMsgTracer::new); + + private final Deque 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; + } + } +}