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 3d8948fea7..7820dc173c 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 @@ -30,14 +30,58 @@ import ghidra.util.database.spatial.DBTreeNodeRecord; import ghidra.util.database.spatial.rect.*; import ghidra.util.exception.VersionException; -public class DBTraceAddressSnapRangePropertyMapTree> - extends Abstract2DRStarTree< // - Address, Long, // - TraceAddressSnapRange, DR, // - TraceAddressSnapRange, DBTraceAddressSnapRangePropertyMapNode, // - T, TraceAddressSnapRangeQuery> { +public class DBTraceAddressSnapRangePropertyMapTree> + extends Abstract2DRStarTree< + Address, Long, + TraceAddressSnapRange, DR, + TraceAddressSnapRange, DBTraceAddressSnapRangePropertyMapNode, + T, TraceAddressSnapRangeQuery> { - protected static final int MAX_CHILDREN = 50; + /** + * On My Machine (TM), I get the following performance when setting register values, which imply + * setting state to KNOWN, which is how that involves this tree: + * + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
{@code MAX_CHILDREN}Records / second
313,179
1013,104
1511,261
208,035
501,085
62768
+ *

+ * These were measured after setting 100,000 records total. Note {@link #MAX_CHILDREN} must be + * less than 64, because of {@link DBTraceAddressSnapRangePropertyMapNode#CHILD_COUNT_MASK}. + * There's definitely a tradeoff in storage, since a lower child count will increase the value + * of log_{childCount}(recordCount). Similarly, I expect a longer query time, but it's unclear + * what actual impact that will have given the caches. I have yet to measure that, but for now, + * I'll set this at 15 and see how things go. It was 50.... + */ + protected static final int MAX_CHILDREN = 15; @DBAnnotatedObjectInfo(version = 0) public static class DBTraceAddressSnapRangePropertyMapNode @@ -232,7 +276,8 @@ public class DBTraceAddressSnapRangePropertyMapTree> tree; + protected final DBTraceAddressSnapRangePropertyMapTree> tree; protected AddressRange range; protected Lifespan lifespan; @@ -384,7 +429,8 @@ public class DBTraceAddressSnapRangePropertyMapTree { + AbstractRectangle2DQuery { public static TraceAddressSnapRangeQuery at(Address address, long snap) { return intersecting(new ImmutableTraceAddressSnapRange(address, snap), null, @@ -615,7 +661,7 @@ public class DBTraceAddressSnapRangePropertyMapTree getMapSpace() { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java index 634b76283d..473e7ae1aa 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java @@ -66,6 +66,40 @@ import ghidra.util.task.TaskMonitor; public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager { private static final int OBJECTS_CONTAINING_CACHE_SIZE = 100; + /** + * Tuned On My Machine :) for 100,000 records + * + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
MAX_CHILDRENRecords/s
3(exceeded time limit)
108,371
128,660
158,155
507,531
+ *

+ * I'll naively set it to 12 until/unless we run into storage size constraints. + */ + private static final int MAX_CHILDREN = 12; + public static class DBTraceObjectSchemaDBFieldCodec extends AbstractDBFieldCodec { public DBTraceObjectSchemaDBFieldCodec(Class objectType, @@ -187,7 +221,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager valueTree = new DBTraceObjectValueRStarTree(this, factory, DBTraceObjectValueData.TABLE_NAME, ValueSpace.INSTANCE, DBTraceObjectValueData.class, - DBTraceObjectValueNode.class, false, 50); + DBTraceObjectValueNode.class, false, MAX_CHILDREN); valueMap = valueTree.asSpatialMap(); objectsByPath = objectStore.getIndex(KeyPath.class, DBTraceObject.PATH_COLUMN); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValueRStarTree.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValueRStarTree.java index 5281e6b715..80f3165c91 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValueRStarTree.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValueRStarTree.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. @@ -32,20 +32,20 @@ import ghidra.util.database.spatial.hyper.AbstractHyperRStarTree; import ghidra.util.database.spatial.hyper.EuclideanHyperSpace; import ghidra.util.exception.VersionException; -public class DBTraceObjectValueRStarTree extends AbstractHyperRStarTree< // - ValueTriple, // - ValueShape, DBTraceObjectValueData, // - ValueBox, DBTraceObjectValueNode, // - DBTraceObjectValueData, TraceObjectValueQuery> { +public class DBTraceObjectValueRStarTree extends AbstractHyperRStarTree< + ValueTriple, + ValueShape, DBTraceObjectValueData, + ValueBox, DBTraceObjectValueNode, + DBTraceObjectValueData, TraceObjectValueQuery> { - public static class DBTraceObjectValueMap extends AsSpatialMap { + public static class DBTraceObjectValueMap extends AsSpatialMap { private final AddressFactory factory; private final ReadWriteLock lock; - public DBTraceObjectValueMap(AbstractConstraintsTree tree, + public DBTraceObjectValueMap(AbstractConstraintsTree tree, TraceObjectValueQuery query, AddressFactory factory, ReadWriteLock lock) { super(tree, query); this.factory = factory; @@ -98,7 +98,7 @@ public class DBTraceObjectValueRStarTree extends AbstractHyperRStarTree< // } protected void doInsertDataEntry(DBTraceObjectValueData entry) { - super.doInsert(entry, new LevelInfo(leafLevel)); + super.doInsert(entry, new LevelInfo(0)); } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerMemoryTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerMemoryTest.java index da96af77ab..e6f4f9d5dc 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerMemoryTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerMemoryTest.java @@ -16,7 +16,9 @@ package ghidra.trace.database.memory; import static org.junit.Assert.*; +import static org.junit.Assume.assumeFalse; +import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; @@ -28,9 +30,15 @@ import db.DBHandle; import db.Transaction; import ghidra.framework.data.OpenMode; import ghidra.program.model.address.*; +import ghidra.program.model.lang.Register; +import ghidra.program.model.lang.RegisterValue; import ghidra.trace.database.DBTrace; +import ghidra.trace.model.Lifespan; import ghidra.trace.model.TraceAddressSnapRange; -import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.memory.*; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.model.thread.TraceThreadManager; +import ghidra.util.SystemUtilities; import ghidra.util.task.ConsoleTaskMonitor; import ghidra.util.task.TaskMonitor; @@ -987,4 +995,36 @@ public abstract class AbstractDBTraceMemoryManagerMemoryTest } } } + + /** + * Based on old issue: https://github.com/NationalSecurityAgency/ghidra/issues/2760 that came up + * again in another context. + * + * @throws Exception because + */ + @Test + public void testReplicateClassCastExceptionScenario() throws Exception { + final int TICKS = 100_000; + + assumeFalse(SystemUtilities.isInTestingBatchMode()); + TraceMemoryManager memory = b.trace.getMemoryManager(); + TraceThreadManager threads = b.trace.getThreadManager(); + + try (Transaction tx = b.startTransaction()) { + TraceThread th = threads.addThread("Threads[0]", Lifespan.nowOn(1)); + b.createObjectsFramesAndRegs(th, Lifespan.nowOn(0), b.host, 1); + TraceMemorySpace regspace = memory.getMemoryRegisterSpace(th, true); + Register pc = b.trace.getBaseLanguage().getProgramCounter(); + + long start = System.currentTimeMillis(); + // For each tick, write PC with a dummy value + for (int tick = 0; tick < TICKS; tick++) { + RegisterValue value = new RegisterValue(pc, BigInteger.valueOf(tick)); + regspace.setValue(tick, value); // CRASH HERE + } + long current = System.currentTimeMillis(); + double ticksPerSecond = 1000.0 * TICKS / (current - start); + System.err.println("%f/s".formatted(ticksPerSecond)); + } + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java index 96d6c214ca..7d33a83c76 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java @@ -16,6 +16,7 @@ package ghidra.trace.database.target; import static org.junit.Assert.*; +import static org.junit.Assume.assumeFalse; import java.io.File; import java.math.BigInteger; @@ -40,6 +41,8 @@ import ghidra.trace.model.target.path.PathFilter; import ghidra.trace.model.target.schema.SchemaContext; import ghidra.trace.model.target.schema.TraceObjectSchema.SchemaName; import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.model.thread.TraceThreadManager; +import ghidra.util.SystemUtilities; import ghidra.util.database.DBAnnotatedObject; import ghidra.util.database.DBCachedObjectStore; @@ -1162,4 +1165,33 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT regionText.getAttribute(0, "Range").getValue()); assertEquals("Range", regionText.getAttribute(0, "_range").getEntryKey()); } + + @Test + public void testReplicateClassCastExceptionScenario() throws Exception { + final int TICKS = 100_000; + + assumeFalse(SystemUtilities.isInTestingBatchMode()); + TraceThreadManager threads = b.trace.getThreadManager(); + + try (Transaction tx = b.startTransaction()) { + b.createRootObject(); + TraceThread th = threads.addThread("Targets[0].Threads[0]", Lifespan.nowOn(1)); + DBTraceObject objThread = (DBTraceObject) th.getObject(); + + long start = System.currentTimeMillis(); + // For each tick, write PC with a dummy value + for (int tick = 0; tick < TICKS; tick++) { + /** + * NOTE: This puts the manager out of sync, since it won't have the corresponding + * wrapper objects, but that shouldn't be any matter, as the point of this is to + * test the underlying R*-Tree. + */ + b.trace.getObjectManager() + .doCreateValueData(Lifespan.nowOn(tick), objThread, "_pc", b.addr(tick)); + } + long current = System.currentTimeMillis(); + double ticksPerSecond = 1000.0 * TICKS / (current - start); + System.err.println("%f/s".formatted(ticksPerSecond)); + } + } } 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 fad5b6b801..ab159d8642 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 @@ -31,13 +31,13 @@ import ghidra.util.database.spatial.Query.QueryInclusion; import ghidra.util.datastruct.FixedSizeHashMap; import ghidra.util.exception.VersionException; -public abstract class AbstractConstraintsTree< // - DS extends BoundedShape, // - DR extends DBTreeDataRecord, // - NS extends BoundingShape, // - NR extends DBTreeNodeRecord, // - T, // - Q extends Query> { +public abstract class AbstractConstraintsTree< + DS extends BoundedShape, + DR extends DBTreeDataRecord, + NS extends BoundingShape, + NR extends DBTreeNodeRecord, + T, + Q extends Query> { static final int MAX_CACHE_ENTRIES = 50; diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/AbstractConstraintsTreeSpatialMap.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/AbstractConstraintsTreeSpatialMap.java index c1b613f89e..7f0145224f 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/AbstractConstraintsTreeSpatialMap.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/AbstractConstraintsTreeSpatialMap.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. @@ -26,12 +26,12 @@ import ghidra.util.LockHold; import ghidra.util.database.DBSynchronizedIterator; import ghidra.util.database.spatial.DBTreeDataRecord.RecordEntry; -public abstract class AbstractConstraintsTreeSpatialMap< // - DS extends BoundedShape, // - DR extends DBTreeDataRecord, // - NS extends BoundingShape, // - T, // - Q extends Query> // +public abstract class AbstractConstraintsTreeSpatialMap< + DS extends BoundedShape, + DR extends DBTreeDataRecord, + NS extends BoundingShape, + T, + Q extends Query> implements SpatialMap { protected final AbstractConstraintsTree tree; 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 b924d05a9f..bc8d7015d6 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 @@ -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. @@ -39,13 +39,13 @@ import ghidra.util.exception.VersionException; * @param The type of value stored in a data entry * @param The type of supported queries */ -public abstract class AbstractRStarConstraintsTree< // - DS extends BoundedShape, // - DR extends DBTreeDataRecord, // - NS extends BoundingShape, // - NR extends DBTreeNodeRecord, // - T, // - Q extends Query> // +public abstract class AbstractRStarConstraintsTree< + DS extends BoundedShape, + DR extends DBTreeDataRecord, + NS extends BoundingShape, + NR extends DBTreeNodeRecord, + T, + Q extends Query> extends AbstractConstraintsTree { protected static final int MAX_LEVELS = 64; // Outlandish! But BitSet uses at least one word @@ -56,7 +56,7 @@ public abstract class AbstractRStarConstraintsTree< // protected static final double REINSERT_RATE = 0.3; // Taken from paper - protected static final int CHEAT_OVERLAP_COUNT = 32; // Take from paper. TODO Tune it + protected static final int CHEAT_OVERLAP_COUNT = 32; // Taken from paper protected class LeastAreaEnlargementThenLeastArea implements Comparable { @@ -131,16 +131,16 @@ public abstract class AbstractRStarConstraintsTree< // /** * The ChooseSubtree algorithm as defined in Section 4.1 of the paper. * - * @param dstLevel the level of the node to choose + * @param dstLevelFromTop the level of the node to choose * @param bounds the bounds of the object being inserted * @return the leaf node into which the object should be inserted */ - protected NR doChooseSubtree(int dstLevel, NS bounds) { + protected NR doChooseSubtree(int dstLevelFromTop, NS bounds) { // CS1 NR node = root; // CS2 - for (int i = 0; i < dstLevel; i++) { + for (int i = 0; i < dstLevelFromTop; i++) { assert !node.getType().isLeaf(); if (node.getType().isLeafParent()) { node = findChildByNearlyMinimumOverlapCost(node, bounds); @@ -314,10 +314,13 @@ public abstract class AbstractRStarConstraintsTree< // for (Comparator axis : getSplitAxes()) { children.sort(Comparator.comparing(DBTreeRecord::getBounds, axis)); - // Distributions as desribed in Section 4.2. - // In the paper, S is defined as "the sum of all margin-values of the different distributions" - // While the margin-value is defined as a sum. So just sum it all. No need to collect the groups, - // or even pair their values. + /** + * Distributions as described in Section 4.2. + * + * In the paper, S is defined as "the sum of all margin-values of the different + * distributions." While the margin-value is defined as a sum. So just sum it all. No + * need to collect the groups, or even pair their values. + */ // Compute each area, incrementally, and sum them. // ************X (M = 12) @@ -407,30 +410,67 @@ public abstract class AbstractRStarConstraintsTree< // return bestIndex + minChildren; } - protected static class LevelInfo { - int dstLevel; - long reinsertedLevels = 0; // MAX_LEVELS = 64 + protected static class ReInsertInfo { + long reinsertedLevelsFromBottom = 0; // MAX_LEVELS = 64 - public LevelInfo(int dstLevel) { - this.dstLevel = dstLevel; + public boolean checkAndSet(int levelFromBottom) { + long dstLevelMask = 1L << levelFromBottom; + if ((reinsertedLevelsFromBottom & dstLevelMask) != 0) { + return true; + } + reinsertedLevelsFromBottom |= dstLevelMask; + return false; + } + } + + protected static class LevelInfo { + final int dstLevelFromBottom; + final ReInsertInfo reInsertInfo; + + /** + * Create level information + * + * @param dstLevelFromBottom the parent level of the destination, counting from the bottom. + * NOTE: The leaf elements are at level 0. + */ + public LevelInfo(int dstLevelFromBottom) { + assert dstLevelFromBottom >= 0; + this.dstLevelFromBottom = dstLevelFromBottom; + this.reInsertInfo = new ReInsertInfo(); + } + + LevelInfo(int dstLevelFromBottom, ReInsertInfo reInsertInfo) { + this.dstLevelFromBottom = dstLevelFromBottom; + this.reInsertInfo = reInsertInfo; + } + + boolean makesSense(DBTreeRecord entry) { + if (dstLevelFromBottom == 0) { + // If the parent is level 0, i.e., a leaf, then we are a data record + return entry instanceof DBTreeDataRecord; + } + if (!(entry instanceof DBTreeNodeRecord node)) { + return false; + } + if (dstLevelFromBottom == 1) { + return node.getType() == NodeType.LEAF; + } + if (dstLevelFromBottom == 2) { + return node.getType() == NodeType.LEAF_PARENT; + } + return node.getType() == NodeType.DIRECTORY; + } + + public int getDstLevelFromTop(int leafLevel) { + return leafLevel - dstLevelFromBottom; } public boolean checkAndSetReinserted() { - if ((reinsertedLevels >> dstLevel & 0x1) != 0) { - return true; - } - reinsertedLevels |= (1 << dstLevel); - return false; + return reInsertInfo.checkAndSet(dstLevelFromBottom); } - public LevelInfo decLevel() { - dstLevel--; - return this; - } - - public void incDepth() { - dstLevel++; - reinsertedLevels <<= 1; + public LevelInfo setLevelTowardTop1() { + return new LevelInfo(dstLevelFromBottom + 1, reInsertInfo); } } @@ -441,14 +481,20 @@ public abstract class AbstractRStarConstraintsTree< // entry.setParentKey(-1); // TODO: Probably unnecessary, except error recovery? entry.setShape(shape); entry.setRecordValue(value); - doInsert(entry, new LevelInfo(leafLevel)); + doInsert(entry, new LevelInfo(0)); return entry; } - // NOTE: entry may actually be a node + /** + * Insert an entry (data or node) into the tree at the given level + * + * @param entry the entry to insert + */ protected void doInsert(DBTreeRecord entry, LevelInfo levelInfo) { + assert levelInfo.makesSense(entry); + // I1 - NR node = doChooseSubtree(levelInfo.dstLevel, entry.getBounds()); + NR node = doChooseSubtree(levelInfo.getDstLevelFromTop(leafLevel), entry.getBounds()); // I2 if (node.getType() == NodeType.LEAF) { @@ -468,7 +514,7 @@ public abstract class AbstractRStarConstraintsTree< // int newChildCount = node.getChildCount() + 1; node.setChildCount(newChildCount); - // I4 - I'm having integrity issues unless this comes before overflow treatments + // I4 - I'm having integrity issues unless this comes before overflow treatments if (newChildCount == 1) { assert node == root; node.setShape(entry.getBounds()); @@ -485,26 +531,14 @@ public abstract class AbstractRStarConstraintsTree< // split = doOverflowTreatment(node, levelInfo); } // NOTE: Depth should never increase more than once per insert - int savedLevel = levelInfo.dstLevel; for (NR propa = node, parent = getParentOf(propa); split != null; // propa = parent, // parent = getParentOf(propa), // - split = doOverflowTreatment(propa, levelInfo.decLevel())) { + split = doOverflowTreatment(propa, levelInfo = levelInfo.setLevelTowardTop1())) { if (parent == null) { assert propa == root; - assert levelInfo.dstLevel == 0; - root = nodeStore.create(); - root.setParentKey(-1); - cachedNodeChildren.put(root.getKey(), new ArrayList<>(maxChildren)); - root.setShape(propa.getShape().unionBounds(split.getShape())); - root.setType(propa.getType().getParentType()); - root.setChildCount(2); - root.setDataCount(propa.getDataCount() + split.getDataCount()); - doSetParentKey(propa, root.getKey(), cachedNodeChildren); - doSetParentKey(split, root.getKey(), cachedNodeChildren); - leafLevel++; - levelInfo.dstLevel = savedLevel; - levelInfo.incDepth(); + assert levelInfo.dstLevelFromBottom == leafLevel; + root = reRoot(propa, split); return; } newChildCount = parent.getChildCount() + 1; @@ -513,7 +547,31 @@ public abstract class AbstractRStarConstraintsTree< // break; } } - levelInfo.dstLevel = savedLevel; + } + + /** + * Create a new node with the two given nodes. + *

+ * One of the nodes, probably n1 must be the root node. The other must be the node which + * resulted from splitting the root node. + * + * @param n1 the current root node + * @param n2 the node resulting from splitting the current root node + * @return the parent node, which must become the new root node + */ + protected NR reRoot(NR n1, NR n2) { + NR newRoot = nodeStore.create(); + newRoot.setParentKey(-1); + long newRootKey = newRoot.getKey(); + cachedNodeChildren.put(newRootKey, new ArrayList<>(maxChildren)); + newRoot.setShape(n1.getShape().unionBounds(n2.getShape())); + newRoot.setType(n1.getType().getParentType()); + newRoot.setChildCount(2); + newRoot.setDataCount(n1.getDataCount() + n2.getDataCount()); + doSetParentKey(n1, newRootKey, cachedNodeChildren); + doSetParentKey(n2, newRootKey, cachedNodeChildren); + leafLevel++; + return newRoot; } protected NR doOverflowTreatment(NR n, LevelInfo levelInfo) {