From 6c6f274d91fa4784fa9c7cdff17e85d1fbd2f1c7 Mon Sep 17 00:00:00 2001 From: ghidra1 Date: Tue, 22 Sep 2026 17:24:55 -0400 Subject: [PATCH] GP-7307 Corrected data type merge conflict fixup --- .../merge/datatypes/DataTypeMergeManager.java | 151 +++++++++++------- .../core/stackeditor/StackFrameDataType.java | 6 + .../merge/datatypes/DataTypeMerge4Test.java | 66 ++++++++ .../datatypes/DataTypeMergeFixupTest.java | 101 ++++++++++++ .../program/database/data/StructureDB.java | 12 ++ .../ghidra/program/database/data/UnionDB.java | 13 +- .../ghidra/program/model/data/Composite.java | 11 ++ .../program/model/data/StructureDataType.java | 8 + .../program/model/data/UnionDataType.java | 12 +- 9 files changed, 315 insertions(+), 65 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/datatypes/DataTypeMergeManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/datatypes/DataTypeMergeManager.java index 05d6ca7113..472dfbb50f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/datatypes/DataTypeMergeManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/datatypes/DataTypeMergeManager.java @@ -529,7 +529,7 @@ public class DataTypeMergeManager implements MergeResolver { } sb.append(dt.getDisplayName()); sb.append(", "); - if (info.resultOrdinal < 0) { + if (info.resultIndex < 0) { if (dt instanceof FunctionDefinition) { sb.append("return-type"); } @@ -540,25 +540,25 @@ public class DataTypeMergeManager implements MergeResolver { else { if (dt instanceof FunctionDefinition) { sb.append("param-"); - sb.append(info.resultOrdinal); + sb.append(info.resultIndex); } else if (dt instanceof Union) { sb.append("component-"); - sb.append(info.resultOrdinal); + sb.append(info.resultIndex); } else if (dt instanceof Structure) { Structure resultStruct = (Structure) info.ht.get(info.id); - sb.append("component-"); - sb.append(info.resultOrdinal); + sb.append("defined-component-"); + sb.append(info.resultIndex); if (!resultStruct.isPackingEnabled()) { - sb.append(", offset-"); + sb.append(", source-offset-"); sb.append("0x"); sb.append(Integer.toHexString(info.offset)); } } else { - sb.append("resultOrdinal-"); // unknown use case - sb.append(info.resultOrdinal); + sb.append("resultIndex-"); // unknown use case + sb.append(info.resultIndex); } } @@ -1234,6 +1234,38 @@ public class DataTypeMergeManager implements MergeResolver { return nextOrdinal; } + /** + * Get the index of a defined component in the structure's defined-component sequence. + * Unlike its ordinal, this index is unaffected by changes to implicit undefined bytes. + * @param struct destination structure + * @param component newly inserted defined component + * @return index in the destination's defined-component sequence + */ + private static int getDefinedComponentIndex(Structure struct, DataTypeComponent component) { + if (component.getDataType() == DataType.DEFAULT) { + throw new IllegalArgumentException("Expected defined component"); + } + int ordinal = component.getOrdinal(); + int low = 0; + int high = struct.getNumDefinedComponents() - 1; + // Defined components have unique, increasing ordinals even when offsets overlap. + while (low <= high) { + int mid = low + (high - low) / 2; + int midOrdinal = struct.getDefinedComponent(mid).getOrdinal(); + if (midOrdinal < ordinal) { + low = mid + 1; + } + else if (midOrdinal > ordinal) { + high = mid - 1; + } + else { + return mid; + } + } + throw new AssertException( + "Expected defined component at ordinal " + ordinal + " in " + struct.getPathName()); + } + private void updateStructure(long sourceDtID, Structure sourceDt, Structure destStruct, MyIdentityHashMap resolvedDataTypes) { @@ -1434,15 +1466,14 @@ public class DataTypeMergeManager implements MergeResolver { } } else if (packed) { - // Add fixup placeholder to prevent the ordinal values and component sizes from - // changing. Nothing we can do about packing which may be affected. - // These should get fixed-up later. + // Preserve a defined component for later fixup. Packing and dependency size + // changes may alter its offset and ordinal, but not its defined-component index. resultComp = destStruct.add(BadDataType.dataType, sourceComp.getLength(), sourceComp.getFieldName(), comment); } else { - // Add fixup placeholder to prevent the ordinal values and component sizes from - // changing. These should get fixed-up later. + // Preserve a defined component and its reserved space for later fixup. + // Changes to preceding undefined bytes may alter its ordinal. resultComp = destStruct.insertAtOffset(sourceComp.getOffset(), BadDataType.dataType, sourceComp.getLength(), sourceComp.getFieldName(), comment); } @@ -1450,7 +1481,7 @@ public class DataTypeMergeManager implements MergeResolver { if (fixupRequired) { // Component datatype has not been added/resolved yet, put an entry in the fixup list fixUpList.add(new FixUpInfo(sourceDtID, sourceComponentID, - resultComp.getOrdinal(), sourceComp, resolvedDataTypes)); + getDefinedComponentIndex(destStruct, resultComp), sourceComp, resolvedDataTypes)); fixUpIDSet.add(sourceDtID); } } @@ -1607,7 +1638,7 @@ public class DataTypeMergeManager implements MergeResolver { } } else { - // Add fixup placeholder to prevent the ordinal values and component sizes from + // Add fixup placeholder to prevent the ordinal values and component sizes from // changing. Nothing we can do about packing which may be affected. // These should get fixed-up later. destUnion.add(BadDataType.dataType, sourceComp.getLength(), @@ -2470,7 +2501,7 @@ public class DataTypeMergeManager implements MergeResolver { /** * NOTE: It is important that fixups for a Structure are processed bottom-up - * (i.e., last-ordinal to first-ordinal) to ensure that subsequent fixup ordinal + * (i.e., last defined-component index to first) to ensure that subsequent fixup * indexes remain valid even after components are removed as a result of processing * a Structure fixup. */ @@ -2542,22 +2573,22 @@ public class DataTypeMergeManager implements MergeResolver { long lastChangeTime = fd.getLastChangeTime(); // Don't let the time change. try { if (dt != null) { - if (info.resultOrdinal < 0) { // -1 for return type + if (info.resultIndex < 0) { // -1 for return type fd.setReturnType(dt); } else { ParameterDefinition[] args = fd.getArguments(); - args[info.resultOrdinal].setDataType(dt); + args[info.resultIndex].setDataType(dt); } return true; } - if (info.resultOrdinal < 0) { // -1 for return type + if (info.resultIndex < 0) { // -1 for return type // nowhere to set error comment } else { ParameterDefinition[] args = fd.getArguments(); - ParameterDefinition arg = args[info.resultOrdinal]; + ParameterDefinition arg = args[info.resultIndex]; String comment = buildDataTypeFailureComment(info.componentDataType, null, arg.getComment()); arg.setComment(comment); @@ -2577,16 +2608,8 @@ public class DataTypeMergeManager implements MergeResolver { * @return false if component not found, else true */ private boolean fixUpPackedStructureComponent(FixUpInfo info, Structure struct, DataType dt) { - int ordinal = info.resultOrdinal; - - DataTypeComponent dtc; - if (ordinal >= 0 || ordinal < struct.getNumComponents()) { - dtc = struct.getComponent(ordinal); - } - else { - throw new AssertException( - "Expected fixup component at ordinal " + ordinal + " in " + struct.getPathName()); - } + DataTypeComponent dtc = getStructureFixupComponent(info, struct); + int ordinal = dtc.getOrdinal(); long lastChangeTime = struct.getLastChangeTime(); // Don't let the time change. try { @@ -2674,16 +2697,8 @@ public class DataTypeMergeManager implements MergeResolver { private boolean fixUpNonPackedStructureComponent(FixUpInfo info, Structure struct, DataType dt) { - int ordinal = info.resultOrdinal; - - DataTypeComponent dtc; - if (ordinal >= 0 || ordinal < struct.getNumComponents()) { - dtc = struct.getComponent(ordinal); - } - else { - throw new AssertException( - "Expected fixup component at ordinal " + ordinal + " in " + struct.getPathName()); - } + DataTypeComponent dtc = getStructureFixupComponent(info, struct); + int ordinal = dtc.getOrdinal(); long lastChangeTime = struct.getLastChangeTime(); // Don't let the time change. try { @@ -2760,6 +2775,21 @@ public class DataTypeMergeManager implements MergeResolver { return true; } + /** + * Locate a fixup after dependency resolution, which may have changed the structure's layout. + * Do not retain component instances or ordinals across fixups or packing changes. + * @param info fixup identifying a defined component + * @param struct current result structure + * @return current component to fix up + */ + private static DataTypeComponent getStructureFixupComponent(FixUpInfo info, Structure struct) { + if (info.resultIndex < 0 || info.resultIndex >= struct.getNumDefinedComponents()) { + throw new AssertException("Expected fixup component at defined-component index " + + info.resultIndex + " in " + struct.getPathName()); + } + return struct.getDefinedComponent(info.resultIndex); + } + /** * Fix up the structure using the fix up info for a component. * @param info fixup info @@ -2780,10 +2810,11 @@ public class DataTypeMergeManager implements MergeResolver { String loc; if (struct.isPackingEnabled()) { - loc = "ordinal " + info.resultOrdinal; + loc = "defined-component index " + info.resultIndex; } else { - loc = "offset 0x" + Integer.toHexString(info.offset); + loc = "defined-component index " + info.resultIndex + " (source offset 0x" + + Integer.toHexString(info.offset) + ")"; } Msg.warn(this, "Structure Merge: Failed to resolve data type '" + info.componentDataType.getName() + "' at " + loc + " in " + struct.getPathName()); @@ -2794,7 +2825,7 @@ public class DataTypeMergeManager implements MergeResolver { DataType compDt = resolve(info.compID, info.getDataTypeManager(), info.ht); - int ordinal = info.resultOrdinal; + int ordinal = info.resultIndex; DataTypeComponent dtc = null; if (ordinal >= 0 && ordinal <= union.getNumComponents()) { @@ -2865,7 +2896,7 @@ public class DataTypeMergeManager implements MergeResolver { } Msg.warn(this, "Union Merge: Failed to resolve data type '" + info.componentDataType.getName() + - "' at ordinal " + info.resultOrdinal + " in " + union.getPathName()); + "' at ordinal " + info.resultIndex + " in " + union.getPathName()); return false; } @@ -2889,7 +2920,7 @@ public class DataTypeMergeManager implements MergeResolver { FixUpInfo info = fixUpList.get(i); // assume info applies to union if (!fixUpUnionComponent(union, info)) { Msg.warn(this, "Union Merge: Failed to apply data type at ordinal " + - info.resultOrdinal + " in " + union.getPathName()); + info.resultIndex + " in " + union.getPathName()); unresolvedFixups.add(info); } } @@ -3427,10 +3458,12 @@ public class DataTypeMergeManager implements MergeResolver { final long compID; // Source datatype ID for component final DataType componentDataType; // Source component datatype - // Result ordinal when id represents a container such as a structure/union or - // function definition. In such cases, 'compID' corresponds to the component datatype. - // A -1 may be used when not applicable. - final int resultOrdinal; + // Result defined-component index for a structure, component ordinal for a union, + // or parameter index for a function definition. A -1 denotes a return type or + // a dependency without a component index. Structure indexes exclude implicit + // undefined bytes and survive layout changes, but must be regenerated when the + // selected definition is rebuilt (see removeFixUps). + final int resultIndex; final MyIdentityHashMap ht; @@ -3449,16 +3482,16 @@ public class DataTypeMergeManager implements MergeResolver { * @param id source data type ID needing to be fixed up * @param compID source datatype ID of either param/component or bitfield base type * @param componentDataType source component/dependency datatype - * @param resultOrdinal the result ordinal into a structure/union; or - * parameter/return ordinal; or -1 for other cases where index is not used + * @param resultIndex result defined-component index for a structure, union component + * ordinal, function parameter index, or -1 for a return type/other dependency * @param resolvedDataTypes hashtable used for resolving the data type */ - FixUpInfo(long id, long compID, DataType componentDataType, int resultOrdinal, + FixUpInfo(long id, long compID, DataType componentDataType, int resultIndex, MyIdentityHashMap resolvedDataTypes) { this.id = id; this.compID = compID; this.componentDataType = componentDataType; - this.resultOrdinal = resultOrdinal; + this.resultIndex = resultIndex; this.ht = resolvedDataTypes; if (componentDataType instanceof BitFieldDataType) { @@ -3472,13 +3505,13 @@ public class DataTypeMergeManager implements MergeResolver { * or components were resolved. * @param id id of data type needing to be fixed up * @param compID datatype ID of either param/component or bitfield base type - * @param resultOrdinal component ordinal within result composite + * @param resultIndex defined-component index within a result structure or ordinal in a union * @param sourceDtc associated composite datatype component * @param resolvedDataTypes hashtable used for resolving the data type */ - FixUpInfo(long id, long compID, int resultOrdinal, DataTypeComponent sourceDtc, + FixUpInfo(long id, long compID, int resultIndex, DataTypeComponent sourceDtc, MyIdentityHashMap resolvedDataTypes) { - this(id, compID, getDataType(sourceDtc), resultOrdinal, resolvedDataTypes); + this(id, compID, getDataType(sourceDtc), resultIndex, resolvedDataTypes); offset = sourceDtc.getOffset(); if (sourceDtc.isBitFieldComponent()) { BitFieldDataType bfDt = (BitFieldDataType) sourceDtc.getDataType(); @@ -3497,11 +3530,11 @@ public class DataTypeMergeManager implements MergeResolver { @Override public int compareTo(FixUpInfo o) { - // Compare such that items are grouped by id and sort such that the greatest resultOrdinal + // Compare such that items are grouped by id and sort such that the greatest resultIndex // is first within that group. long c = id - o.id; if (c == 0) { - c = Integer.toUnsignedLong(o.resultOrdinal) - Integer.toUnsignedLong(resultOrdinal); + c = Integer.toUnsignedLong(o.resultIndex) - Integer.toUnsignedLong(resultIndex); } if (c == 0) { return 0; @@ -3527,7 +3560,7 @@ public class DataTypeMergeManager implements MergeResolver { } return "\n" + "ID = " + Long.toHexString(id) + ",\ndt = " + dtm.getDataType(id) + ",\ncomponent ID = " + Long.toHexString(compID) + ",\ncomponent dt = " + - dtm.getDataType(compID) + ",\nresultOrdinal = " + resultOrdinal + ",\n" + bitInfo + + dtm.getDataType(compID) + ",\nresultIndex = " + resultIndex + ",\n" + bitInfo + "ht = " + htStr + "\n"; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackFrameDataType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackFrameDataType.java index 4e2746bbde..cb6f2b47b3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackFrameDataType.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackFrameDataType.java @@ -1098,6 +1098,12 @@ class StackFrameDataType implements Structure { return dtc != null ? new StackComponentWrapper(dtc) : null; } + @Override + public DataTypeComponent getDefinedComponent(int index) throws IndexOutOfBoundsException { + DataTypeComponent dtc = wrappedStruct.getDefinedComponent(index); + return new StackComponentWrapper(dtc); + } + @Override public StackComponentWrapper getComponent(int ordinal) throws IndexOutOfBoundsException { DataTypeComponent dtc = wrappedStruct.getComponent(ordinal); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/datatypes/DataTypeMerge4Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/datatypes/DataTypeMerge4Test.java index d030a52822..0e7a169719 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/datatypes/DataTypeMerge4Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/datatypes/DataTypeMerge4Test.java @@ -1169,6 +1169,72 @@ public class DataTypeMerge4Test extends AbstractDataTypeMergeTest { checkConflictCount(0); } + @Test + public void testMultiEdits14() throws Exception { + + mtf.initialize("notepad2", new ProgramModifierListener() { + @Override + public void modifyLatest(ProgramDB program) { + DataTypeManager dtm = program.getDataTypeManager(); + Structure bar = (Structure) dtm.getDataType(new CategoryPath("/MISC"), "Bar"); + + // Create a conflict while retaining Bar's original six-byte length. + assertEquals(6, bar.getLength()); + bar.getComponent(0).setComment("Latest Bar component"); + + int junk = 0; + } + + @Override + public void modifyPrivate(ProgramDB program) { + DataTypeManager dtm = program.getDataTypeManager(); + CategoryPath path = new CategoryPath("/MISC"); + Structure bar = (Structure) dtm.getDataType(path, "Bar"); + bar.delete(1); // Remove the pointer, shrinking Bar from six bytes to two. + assertEquals(2, bar.getLength()); + + // Allocate the container before its target so that the merge records a fixup + // for the as-yet-unresolved target when processing the added container. + Structure container = (Structure) dtm.addDataType( + new StructureDataType(path, "FixupContainer", 0), + DataTypeConflictHandler.DEFAULT_HANDLER); + Structure target = new StructureDataType(path, "FixupTarget", 0); + target.add(ByteDataType.dataType); + target = (Structure) dtm.addDataType(target, DataTypeConflictHandler.DEFAULT_HANDLER); + + container.add(bar, "bar", null); + container.growStructure(4); + container.add(target, "target", null); + assertFalse(container.isPackingEnabled()); + assertEquals(6, container.getComponent(5).getOffset()); + + int junk = 0; + } + }); + + executeMerge(); + DataTypeManager dtm = resultProgram.getDataTypeManager(); + + // The added container initially uses Latest's six-byte Bar, so the placeholder + // at offset 6 has ordinal 1. Choosing MY Bar shrinks that component to two bytes, + // introducing four undefined components before the placeholder. Its ordinal is + // now 5, but FixUpInfo still records 1. The merge must not use that stale ordinal. + chooseOption(DataTypeMergeManager.OPTION_MY); + waitForCompletion(); + + Structure bar = (Structure) dtm.getDataType(new CategoryPath("/MISC"), "Bar"); + Structure container = + (Structure) dtm.getDataType(new CategoryPath("/MISC"), "FixupContainer"); + DataType target = dtm.getDataType(new CategoryPath("/MISC"), "FixupTarget"); + assertEquals(2, bar.getLength()); + assertEquals(7, container.getLength()); + assertEquals(2, container.getNumDefinedComponents()); + assertEquals(bar, container.getComponent(0).getDataType()); + assertEquals(6, container.getComponent(5).getOffset()); + assertEquals(target, container.getComponent(5).getDataType()); + checkConflictCount(0); + } + @Test public void testDeletedBaseTypeDef() throws Exception { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/datatypes/DataTypeMergeFixupTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/datatypes/DataTypeMergeFixupTest.java index 99049264df..bb23ee93e4 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/datatypes/DataTypeMergeFixupTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/datatypes/DataTypeMergeFixupTest.java @@ -28,6 +28,107 @@ import ghidra.program.model.data.*; */ public class DataTypeMergeFixupTest extends AbstractDataTypeMergeTest { + @Test + public void testDefinedComponentFixupAfterResize() throws Exception { + checkDefinedComponentFixupAfterLayoutChange(false, false); + } + + @Test + public void testDefinedComponentFixupAfterEnablingPacking() throws Exception { + checkDefinedComponentFixupAfterLayoutChange(false, true); + } + + @Test + public void testDefinedComponentFixupAfterDisablingPacking() throws Exception { + checkDefinedComponentFixupAfterLayoutChange(true, false); + } + + private void checkDefinedComponentFixupAfterLayoutChange(boolean initiallyPacked, + boolean finallyPacked) throws Exception { + CategoryPath path = new CategoryPath("/MISC"); + mtf.initialize("notepad2", new OriginalProgramModifierListener() { + @Override + public void modifyOriginal(ProgramDB program) throws Exception { + // Ensure the zero-length type is already resolved when the container is added. + program.getDataTypeManager().addDataType(new ArrayDataType(ByteDataType.dataType, 0), + DataTypeConflictHandler.DEFAULT_HANDLER); + } + + @Override + public void modifyLatest(ProgramDB program) throws Exception { + Structure bar = (Structure) program.getDataTypeManager().getDataType(path, "Bar"); + bar.getComponent(0).setComment("Latest Bar component"); + } + + @Override + public void modifyPrivate(ProgramDB program) throws Exception { + DataTypeManager dtm = program.getDataTypeManager(); + Structure bar = (Structure) dtm.getDataType(path, "Bar"); + bar.delete(1); + assertEquals(2, bar.getLength()); + + // Allocate the container first so its target components require deferred fixups. + Structure container = (Structure) dtm.addDataType( + new StructureDataType(path, "LayoutContainer", 0), + DataTypeConflictHandler.DEFAULT_HANDLER); + Structure target = new StructureDataType(path, "LayoutTarget", 0); + target.add(ByteDataType.dataType); + target = (Structure) dtm.addDataType(target, DataTypeConflictHandler.DEFAULT_HANDLER); + + container.add(bar, "bar", null); + container.growStructure(4); + container.insertBitFieldAt(6, 1, 0, ByteDataType.dataType, 3, "bits1", null); + container.insertBitFieldAt(6, 1, 3, ByteDataType.dataType, 2, "bits2", null); + container.add(new ArrayDataType(ByteDataType.dataType, 0), "zero1", null); + container.add(new ArrayDataType(ByteDataType.dataType, 0), "zero2", null); + container.add(target, "target1", null); + container.add(target, "target2", null); + container.setPackingEnabled(initiallyPacked); + } + }); + + executeMerge(); + waitForPrompting(); // Wait until additions are complete and the Bar conflict is displayed. + DataTypeManager dtm = resultProgram.getDataTypeManager(); + Structure container = (Structure) dtm.getDataType(path, "LayoutContainer"); + assertNotNull(container); + DataTypeComponent[] components = container.getDefinedComponents(); + assertEquals(7, components.length); + assertEquals(BadDataType.dataType, components[5].getDataType()); + assertEquals(BadDataType.dataType, components[6].getDataType()); + + // The merge is paused at the Bar conflict, after the container fixups were recorded. + // Exercise a packing transition without replacing its defined-component sequence. + int tx = resultProgram.startTransaction("Change packing with pending fixups"); + try { + container.setPackingEnabled(finallyPacked); + } + finally { + resultProgram.endTransaction(tx, true); + } + chooseOption(DataTypeMergeManager.OPTION_MY); + waitForCompletion(); + + container = (Structure) dtm.getDataType(path, "LayoutContainer"); + components = container.getDefinedComponents(); + assertEquals(finallyPacked, container.isPackingEnabled()); + assertEquals(7, components.length); + assertEquals(2, components[0].getLength()); + assertTrue(components[1].isBitFieldComponent()); + assertTrue(components[2].isBitFieldComponent()); + assertEquals(components[1].getOffset(), components[2].getOffset()); + assertEquals(0, components[3].getLength()); + assertEquals(0, components[4].getLength()); + assertEquals(components[3].getOffset(), components[4].getOffset()); + DataType target = dtm.getDataType(path, "LayoutTarget"); + assertNotNull(target); + assertEquals("target1", components[5].getFieldName()); + assertEquals(target, components[5].getDataType()); + assertEquals("target2", components[6].getFieldName()); + assertEquals(target, components[6].getDataType()); + checkConflictCount(0); + } + private void setupRemoveInnerVsAddOuterContainingChangedInner() throws Exception { final CategoryPath rootPath = new CategoryPath("/"); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/StructureDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/StructureDB.java index 73c8456871..dec67f9f1a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/StructureDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/StructureDB.java @@ -746,10 +746,22 @@ class StructureDB extends CompositeDB implements StructureInternal { @Override public int getNumDefinedComponents() { try (Closeable c = lock.read()) { + refreshIfNeeded(); return components.size(); } } + @Override + public DataTypeComponentDB getDefinedComponent(int index) throws IndexOutOfBoundsException { + try (Closeable c = lock.read()) { + refreshIfNeeded(); + if (index < 0 || index >= components.size()) { + throw new IndexOutOfBoundsException(index); + } + return components.get(index); + } + } + @Override public DataTypeComponentDB getComponent(int ordinal) { try (Closeable c = lock.read()) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/UnionDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/UnionDB.java index 815b46c695..c20f79aa60 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/UnionDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/UnionDB.java @@ -389,16 +389,21 @@ class UnionDB extends CompositeDB implements UnionInternal { } @Override - public DataTypeComponentDB getComponent(int ordinal) { + public DataTypeComponentDB getDefinedComponent(int index) throws IndexOutOfBoundsException { try (Closeable c = lock.read()) { refreshIfNeeded(); - if (ordinal < 0 || ordinal >= components.size()) { - return null; + if (index < 0 || index >= components.size()) { + throw new IndexOutOfBoundsException(index); } - return components.get(ordinal); + return components.get(index); } } + @Override + public DataTypeComponentDB getComponent(int ordinal) { + return getDefinedComponent(ordinal); + } + @Override public DataTypeComponentDB[] getComponents() { try (Closeable c = lock.read()) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Composite.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Composite.java index ec104df709..ea449abac1 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Composite.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Composite.java @@ -47,6 +47,17 @@ public interface Composite extends DataType { */ public abstract int getNumDefinedComponents(); + /** + * Returns the defined component of this data type at the specified index + * within the ordered sequence of {@link #getDefinedComponents() defined components}. + * See {@link #getNumDefinedComponents()}. + * @param index the index of the defined component (numbering starts at 0). + * @return the data type component. + * @throws IndexOutOfBoundsException if the index is out of bounds + */ + public abstract DataTypeComponent getDefinedComponent(int index) + throws IndexOutOfBoundsException; + /** * Returns the component of this data type with the indicated ordinal. * @param ordinal the component's ordinal (numbering starts at 0). diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StructureDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StructureDataType.java index 94193ea48d..3c59391b26 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StructureDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StructureDataType.java @@ -448,6 +448,14 @@ public class StructureDataType extends CompositeDataTypeImpl implements Structur dtc.setOrdinal(dtc.getOrdinal() + deltaOrdinal); } + @Override + public DataTypeComponentImpl getDefinedComponent(int index) throws IndexOutOfBoundsException { + if (index < 0 || index >= components.size()) { + throw new IndexOutOfBoundsException(index); + } + return components.get(index); + } + @Override public DataTypeComponentImpl getComponent(int ordinal) { if (ordinal < 0 || ordinal >= numComponents) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/UnionDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/UnionDataType.java index 7d1192c4c7..5234ec1589 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/UnionDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/UnionDataType.java @@ -104,8 +104,16 @@ public class UnionDataType extends CompositeDataTypeImpl implements UnionInterna } @Override - public DataTypeComponent getComponent(int ordinal) { - return components.get(ordinal); + public DataTypeComponentImpl getDefinedComponent(int index) throws IndexOutOfBoundsException { + if (index < 0 || index >= components.size()) { + throw new IndexOutOfBoundsException(index); + } + return components.get(index); + } + + @Override + public DataTypeComponentImpl getComponent(int ordinal) throws IndexOutOfBoundsException { + return getDefinedComponent(ordinal); } @Override