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 360ce0ee97..0c3c1bf157 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 @@ -528,7 +528,7 @@ public class DataTypeMergeManager implements MergeResolver { } sb.append(dt.getDisplayName()); sb.append(", "); - if (info.index < 0) { + if (info.resultOrdinal < 0) { if (dt instanceof FunctionDefinition) { sb.append("return-type"); } @@ -539,16 +539,16 @@ public class DataTypeMergeManager implements MergeResolver { else { if (dt instanceof FunctionDefinition) { sb.append("param-"); - sb.append(info.index); + sb.append(info.resultOrdinal); } else if (dt instanceof Union) { sb.append("component-"); - sb.append(info.index); + sb.append(info.resultOrdinal); } else if (dt instanceof Structure) { Structure resultStruct = (Structure) info.ht.get(info.id); sb.append("component-"); - sb.append(info.index); + sb.append(info.resultOrdinal); if (!resultStruct.isPackingEnabled()) { sb.append(", offset-"); sb.append("0x"); @@ -556,8 +556,8 @@ public class DataTypeMergeManager implements MergeResolver { } } else { - sb.append("index-"); // unknown use case - sb.append(info.index); + sb.append("resultOrdinal-"); // unknown use case + sb.append(info.resultOrdinal); } } @@ -1254,36 +1254,19 @@ public class DataTypeMergeManager implements MergeResolver { // Add each of the defined components back in. DataTypeComponent[] comps = sourceDt.getDefinedComponents(); - // Component sequence must be flipped for components that share the same offset - // (i.e., when zero-length components exist) to ensure that the insert at offset - // does not alter the intended order. - List compList = new ArrayList<>(); - int prevOffset = -1; - int index = -1; - for (DataTypeComponent dtc : comps) { - int offset = dtc.getOffset(); - if (offset == prevOffset) { - compList.add(index, dtc); - } - else { - prevOffset = offset; - index = compList.size(); - compList.add(dtc); - } - } - comps = null; // prevent improper use - // Track dependency errors to avoid duplicate popups HashMap badIdDtMsgs = new HashMap<>(); - for (int i = 0; i < compList.size(); i++) { + for (int i = 0; i < comps.length; i++) { - DataTypeComponent sourceComp = compList.get(i); + DataTypeComponent sourceComp = comps[i]; + DataTypeComponent resultComp; DataType sourceCompDt = sourceComp.getDataType(); BitFieldDataType bfDt = null; String comment = sourceComp.getComment(); DataType resultCompDt = null; + boolean fixupRequired = false; if (sourceComp.isBitFieldComponent()) { // NOTE: primitive type will be used if unable to resolve base type @@ -1307,13 +1290,7 @@ public class DataTypeMergeManager implements MergeResolver { // Not added so should be in result if it wasn't deleted there. resultCompDt = dtms[RESULT].getDataType(sourceComponentID); } - if (resultCompDt == null) { - // Not added/resolved yet - // put an entry in the fixup list - fixUpList.add(new FixUpInfo(sourceDtID, sourceComponentID, - sourceComp.getOrdinal(), sourceComp, resolvedDataTypes)); - fixUpIDSet.add(sourceDtID); - } + fixupRequired = (resultCompDt == null); if (bfDt != null && (resultCompDt == null || !BitFieldDataType.isValidBaseDataType(resultCompDt))) { // use primitive type as fixup placeholder @@ -1335,7 +1312,8 @@ public class DataTypeMergeManager implements MergeResolver { if (packed) { if (bfDt != null) { try { - destStruct.addBitField(resultCompDt, bfDt.getDeclaredBitSize(), + resultComp = + destStruct.addBitField(resultCompDt, bfDt.getDeclaredBitSize(), sourceComp.getFieldName(), comment); } catch (InvalidDataTypeException e) { @@ -1345,7 +1323,8 @@ public class DataTypeMergeManager implements MergeResolver { comment = buildDataTypeFailureComment(sourceCompDt, e.getMessage(), sourceComp.getComment()); try { - destStruct.addBitField(primitiveBaseDt, bfDt.getDeclaredBitSize(), + resultComp = destStruct.addBitField(primitiveBaseDt, + bfDt.getDeclaredBitSize(), sourceComp.getFieldName(), comment); } catch (InvalidDataTypeException exc) { @@ -1356,13 +1335,15 @@ public class DataTypeMergeManager implements MergeResolver { else if (badMsg == null) { try { // If I have compDt, it should now be from result DTM. - destStruct.add(resultCompDt, length, sourceComp.getFieldName(), + resultComp = + destStruct.add(resultCompDt, length, sourceComp.getFieldName(), comment); } catch (IllegalArgumentException e) { comment = buildDataTypeFailureComment(sourceCompDt, e.getMessage(), comment); - destStruct.add(BadDataType.dataType, sourceComp.getLength(), + resultComp = + destStruct.add(BadDataType.dataType, sourceComp.getLength(), sourceComp.getFieldName(), comment); if (e.getCause() instanceof DataTypeDependencyException) { badIdDtMsgs.put(dtId, e.getMessage()); @@ -1373,14 +1354,15 @@ public class DataTypeMergeManager implements MergeResolver { else { // Preserve non-fixup ordinal with bad placeholder comment = buildDataTypeFailureComment(sourceCompDt, badMsg, comment); - destStruct.add(BadDataType.dataType, sourceComp.getLength(), + resultComp = destStruct.add(BadDataType.dataType, sourceComp.getLength(), sourceComp.getFieldName(), badMsg + "; " + comment); } } else if (bfDt != null) { // non-packed bitfield try { - destStruct.insertBitFieldAt(sourceComp.getOffset(), sourceComp.getLength(), + resultComp = destStruct + .insertBitFieldAt(sourceComp.getOffset(), sourceComp.getLength(), bfDt.getBitOffset(), resultCompDt, bfDt.getDeclaredBitSize(), sourceComp.getFieldName(), comment); } @@ -1391,7 +1373,8 @@ public class DataTypeMergeManager implements MergeResolver { comment = buildDataTypeFailureComment(sourceCompDt, e.getMessage(), sourceComp.getComment()); try { - destStruct.addBitField(primitiveBaseDt, bfDt.getDeclaredBitSize(), + resultComp = + destStruct.addBitField(primitiveBaseDt, bfDt.getDeclaredBitSize(), sourceComp.getFieldName(), comment); } catch (InvalidDataTypeException exc) { @@ -1404,9 +1387,9 @@ public class DataTypeMergeManager implements MergeResolver { if (badMsg == null) { try { // If not last component must constrain length to original component size - if (i < compList.size() - 1) { + if (i < (comps.length - 1)) { int offset = sourceComp.getOffset(); - DataTypeComponent nextDtc = compList.get(i + 1); + DataTypeComponent nextDtc = comps[i + 1]; int available = nextDtc.getOffset() - offset; if (length > available) { // The data type is too big, so adjust the component length to what will fit. @@ -1423,13 +1406,15 @@ public class DataTypeMergeManager implements MergeResolver { } } - destStruct.insertAtOffset(sourceComp.getOffset(), resultCompDt, length, + resultComp = destStruct.insertAtOffset(sourceComp.getOffset(), + resultCompDt, length, sourceComp.getFieldName(), comment); } catch (IllegalArgumentException e) { comment = buildDataTypeFailureComment(sourceCompDt, e.getMessage(), comment); - destStruct.insertAtOffset(sourceComp.getOffset(), BadDataType.dataType, + resultComp = destStruct.insertAtOffset(sourceComp.getOffset(), + BadDataType.dataType, length, sourceComp.getFieldName(), comment); if (e.getCause() instanceof DataTypeDependencyException) { @@ -1441,7 +1426,8 @@ public class DataTypeMergeManager implements MergeResolver { else { // Preserve non-fixup ordinal with bad placeholder comment = buildDataTypeFailureComment(sourceCompDt, badMsg, comment); - destStruct.insertAtOffset(sourceComp.getOffset(), BadDataType.dataType, + resultComp = + destStruct.insertAtOffset(sourceComp.getOffset(), BadDataType.dataType, sourceComp.getLength(), sourceComp.getFieldName(), comment); } } @@ -1450,15 +1436,22 @@ public class DataTypeMergeManager implements MergeResolver { // 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. - destStruct.add(BadDataType.dataType, sourceComp.getLength(), + 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. - destStruct.insertAtOffset(sourceComp.getOffset(), BadDataType.dataType, + resultComp = destStruct.insertAtOffset(sourceComp.getOffset(), BadDataType.dataType, sourceComp.getLength(), sourceComp.getFieldName(), comment); } + + 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)); + fixUpIDSet.add(sourceDtID); + } } if (!packed) { adjustStructureSize(destStruct, sourceDt.getLength()); @@ -2548,22 +2541,22 @@ public class DataTypeMergeManager implements MergeResolver { long lastChangeTime = fd.getLastChangeTime(); // Don't let the time change. try { if (dt != null) { - if (info.index < 0) { // -1 for return type + if (info.resultOrdinal < 0) { // -1 for return type fd.setReturnType(dt); } else { ParameterDefinition[] args = fd.getArguments(); - args[info.index].setDataType(dt); + args[info.resultOrdinal].setDataType(dt); } return true; } - if (info.index < 0) { // -1 for return type + if (info.resultOrdinal < 0) { // -1 for return type // nowhere to set error comment } else { ParameterDefinition[] args = fd.getArguments(); - ParameterDefinition arg = args[info.index]; + ParameterDefinition arg = args[info.resultOrdinal]; String comment = buildDataTypeFailureComment(info.componentDataType, null, arg.getComment()); arg.setComment(comment); @@ -2583,15 +2576,15 @@ 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.index; + int ordinal = info.resultOrdinal; - DataTypeComponent dtc = null; + DataTypeComponent dtc; if (ordinal >= 0 || ordinal < struct.getNumComponents()) { dtc = struct.getComponent(ordinal); } - - if (dtc == null) { - throw new AssertException("Expected bad datatype placeholder"); + else { + throw new AssertException( + "Expected fixup component at ordinal " + ordinal + " in " + struct.getPathName()); } long lastChangeTime = struct.getLastChangeTime(); // Don't let the time change. @@ -2680,12 +2673,16 @@ public class DataTypeMergeManager implements MergeResolver { private boolean fixUpNonPackedStructureComponent(FixUpInfo info, Structure struct, DataType dt) { - int ordinal = info.index; + int ordinal = info.resultOrdinal; - DataTypeComponent dtc = null; + 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()); + } long lastChangeTime = struct.getLastChangeTime(); // Don't let the time change. try { @@ -2740,35 +2737,13 @@ public class DataTypeMergeManager implements MergeResolver { } // handle non-bitfield component fixup - int offset = dtc.getOffset(); - int dtcLength = dtc.getLength(); int length = dt.getLength(); if (length <= 0) { - length = dtcLength; - } - int bytesNeeded = length - dtcLength; - if (bytesNeeded > 0) { - int nextOffset = offset + dtcLength; - DataTypeComponent nextDefinedDtc = - struct.getDefinedComponentAtOrAfterOffset(nextOffset); - if (nextDefinedDtc != null) { - int bytesAvailable = nextDefinedDtc.getOffset() - nextOffset; - if (bytesAvailable < bytesNeeded) { - // The data type is too big, so adjust the component length to what will fit. - length = dtcLength + bytesAvailable; - // Output a warning indicating the structure has a data type that doesn't fit. - String message = "Structure Merge: Not enough undefined bytes to fit " + - dt.getPathName() + " in structure " + struct.getPathName() + - " at offset 0x" + Integer.toHexString(offset) + "." + - "\nIt needs " + (bytesNeeded - bytesAvailable) + - " more byte(s) to be able to fit."; - Msg.warn(this, message); - } - } + length = dtc.getLength(); } try { - struct.replaceAtOffset(offset, dt, length, dtc.getFieldName(), - dtc.getComment()); + struct.replace(ordinal, dt, length, dtc.getFieldName(), dtc.getComment()); + return true; } catch (IllegalArgumentException e) { displayError(struct, e); @@ -2804,7 +2779,7 @@ public class DataTypeMergeManager implements MergeResolver { String loc; if (struct.isPackingEnabled()) { - loc = "ordinal " + info.index; + loc = "ordinal " + info.resultOrdinal; } else { loc = "offset 0x" + Integer.toHexString(info.offset); @@ -2818,7 +2793,7 @@ public class DataTypeMergeManager implements MergeResolver { DataType compDt = resolve(info.compID, info.getDataTypeManager(), info.ht); - int ordinal = info.index; + int ordinal = info.resultOrdinal; DataTypeComponent dtc = null; if (ordinal >= 0 && ordinal <= union.getNumComponents()) { @@ -2889,7 +2864,7 @@ public class DataTypeMergeManager implements MergeResolver { } Msg.warn(this, "Union Merge: Failed to resolve data type '" + info.componentDataType.getName() + - "' at ordinal " + info.index + " in " + union.getPathName()); + "' at ordinal " + info.resultOrdinal + " in " + union.getPathName()); return false; } @@ -2913,7 +2888,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.index + " in " + union.getPathName()); + info.resultOrdinal + " in " + union.getPathName()); unresolvedFixups.add(info); } } @@ -3447,13 +3422,18 @@ public class DataTypeMergeManager implements MergeResolver { */ private class FixUpInfo implements Comparable { - final long id; - final long compID; - final DataType componentDataType; - final int index; + final long id; // Souce datatype ID + 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; + final MyIdentityHashMap ht; - // component offset - for display/logging use only + // source component offset - for display/logging use only // only meaningful for non-packed structure // may not be unique (e.g., bitfields, 0-length components) int offset = -1; @@ -3468,16 +3448,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 index offset into non-packed structure, or ordinal into union or packed - * structure; or parameter/return ordinal; for other data types index is not used (specify -1). + * @param resultOrdinal the result ordinal into a structure/union; or + * parameter/return ordinal; or -1 for other cases where index is not used * @param resolvedDataTypes hashtable used for resolving the data type */ - FixUpInfo(long id, long compID, DataType componentDataType, int index, + FixUpInfo(long id, long compID, DataType componentDataType, int resultOrdinal, MyIdentityHashMap resolvedDataTypes) { this.id = id; this.compID = compID; this.componentDataType = componentDataType; - this.index = index; + this.resultOrdinal = resultOrdinal; this.ht = resolvedDataTypes; if (componentDataType instanceof BitFieldDataType) { @@ -3491,13 +3471,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 destOrdinal component ordinal within destination composite + * @param resultOrdinal component ordinal within result composite * @param sourceDtc associated composite datatype component * @param resolvedDataTypes hashtable used for resolving the data type */ - FixUpInfo(long id, long compID, int destOrdinal, DataTypeComponent sourceDtc, + FixUpInfo(long id, long compID, int resultOrdinal, DataTypeComponent sourceDtc, MyIdentityHashMap resolvedDataTypes) { - this(id, compID, getDataType(sourceDtc), destOrdinal, resolvedDataTypes); + this(id, compID, getDataType(sourceDtc), resultOrdinal, resolvedDataTypes); offset = sourceDtc.getOffset(); if (sourceDtc.isBitFieldComponent()) { BitFieldDataType bfDt = (BitFieldDataType) sourceDtc.getDataType(); @@ -3516,11 +3496,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 index + // Compare such that items are grouped by id and sort such that the greatest resultOrdinal // is first within that group. long c = id - o.id; if (c == 0) { - c = Integer.toUnsignedLong(o.index) - Integer.toUnsignedLong(index); + c = Integer.toUnsignedLong(o.resultOrdinal) - Integer.toUnsignedLong(resultOrdinal); } if (c == 0) { return 0; @@ -3546,7 +3526,8 @@ 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) + ",\noffset/index = " + index + ",\n" + bitInfo + "ht = " + + dtm.getDataType(compID) + ",\nresultOrdinal = " + resultOrdinal + ",\n" + bitInfo + + "ht = " + htStr + "\n"; } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/datatypes/DataTypeMerge8Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/datatypes/DataTypeMerge8Test.java index 73375c6f91..4ca35f2e23 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/datatypes/DataTypeMerge8Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/datatypes/DataTypeMerge8Test.java @@ -115,6 +115,8 @@ public class DataTypeMerge8Test extends AbstractDataTypeMergeTest { // choose MY for Bar conflict chooseOption(DataTypeMergeManager.OPTION_MY); + pressButtonByName(waitForWindow("Structure Update Failed"), "OK"); // expected dependency error on ABC + waitForCompletion(); FrontEndPlugin frontEndPlugin = getPlugin(frontEndTool, FrontEndPlugin.class); @@ -122,8 +124,9 @@ public class DataTypeMerge8Test extends AbstractDataTypeMergeTest { JLabel label = (JLabel) TestUtils.getInstanceField("label", logPanel); String statusText = label.getText(); String expectedText = - "Structure Merge: Not enough undefined bytes to fit /XYZ in structure " + - "/MISC/ABC at offset 0x4. It needs 3 more byte(s) to be able to fit."; + "Structure Update Failed: Some of your changes to ABC cannot be merged. " + + "Problem: Not enough undefined bytes to fit /XYZ in structure /MISC/ABC at " + + "offset 0x4. It needs 3 more byte(s) to be able to fit."; assertTrue("Wrong status text: " + statusText, statusText.contains(expectedText)); } } 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 12a0cae1d3..99049264df 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 @@ -367,4 +367,85 @@ public class DataTypeMergeFixupTest extends AbstractDataTypeMergeTest { //@formatter:on } + @Test + public void testNonPackedZeroLengthComponentFixup() throws Exception { + + // Goal is to fixup zero-length component at end of structure where its ordinal will + // be revised during the merge processing + + final CategoryPath rootPath = new CategoryPath("/"); + + mtf.initialize("notepad", new OriginalProgramModifierListener() { + + @Override + public void modifyOriginal(ProgramDB program) throws Exception { + DataTypeManager dtm = program.getDataTypeManager(); + + Union inner = new UnionDataType("inner"); + inner.add(DWordDataType.dataType); + inner = (Union) dtm.addDataType(inner, null); + + Structure other = new StructureDataType("other", 0); + other.add(WordDataType.dataType); + other = (Structure) dtm.addDataType(other, null); + + Structure outer = new StructureDataType("outer", 20, dtm); + outer.replaceAtOffset(0, other, -1, null, null); // prevent size change + outer = (Structure) dtm.addDataType(outer, null); + assertEquals(20, outer.getLength()); + } + + @Override + public void modifyLatest(ProgramDB program) throws Exception { + DataTypeManager dtm = program.getDataTypeManager(); + + // Increase size of other struct + Structure other = (Structure) dtm.getDataType(rootPath, "other"); + other.add(DWordDataType.dataType); + + // remove inner to trigger conflict with its modification + Union inner = (Union) dtm.getDataType(rootPath, "inner"); + dtm.remove(inner); + } + + @Override + public void modifyPrivate(ProgramDB program) throws Exception { + DataTypeManager dtm = program.getDataTypeManager(); + Structure outer = (Structure) dtm.getDataType(rootPath, "outer"); + Union inner = (Union) dtm.getDataType(rootPath, "inner"); + + // change inner to trigger conflict with its removal + inner.add(WordDataType.dataType); + + // Add zero-length array at end of struct + outer.insertAtOffset(20, new ArrayDataType(inner, 0), -1); + assertEquals(20, outer.getLength()); + } + }); + + executeMerge(); + + chooseOption(DataTypeMergeManager.OPTION_MY); // resolve inner conflict + + chooseOption(DataTypeMergeManager.OPTION_MY); // resolve outer conflict + + waitForCompletion(); + + DataTypeManager dtm = resultProgram.getDataTypeManager(); + + StructureInternal outer = (StructureInternal) dtm.getDataType(rootPath, "outer"); + assertNotNull(outer); + + //@formatter:off + assertEquals("/outer\n" + + "pack(disabled)\n" + + "Structure outer {\n" + + " 0 other 6 \"\"\n" + + " 20 inner[0] 0 \"\"\n" + + "}\n" + + "Length: 20 Alignment: 1\n", outer.toString()); + //@formatter:on + + } + } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/program/model/data/StructureDataTypeTest.java b/Ghidra/Features/Base/src/test/java/ghidra/program/model/data/StructureDataTypeTest.java index 683aba0566..9416bde01a 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/program/model/data/StructureDataTypeTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/program/model/data/StructureDataTypeTest.java @@ -338,6 +338,74 @@ public class StructureDataTypeTest extends AbstractGenericTest { assertEquals(ByteDataType.class, comps[2].getDataType().getClass()); } + @Test + public void testInsertAtSameOffset1() { + + DataType zeroDt = new ArrayDataType(ByteDataType.dataType, 0); + + struct = createStructure("Test", 100); + assertFalse(struct.isPackingEnabled()); + + struct.insertAtOffset(0, zeroDt, -1, "a", "comment a"); + struct.insertAtOffset(0, zeroDt, -1, "b", "comment b"); + struct.insertAtOffset(0, WordDataType.dataType, -1, "c", "comment c"); + + DataTypeComponent[] definedComponents = struct.getDefinedComponents(); + assertEquals("a", definedComponents[0].getFieldName()); + assertEquals("b", definedComponents[1].getFieldName()); + assertEquals("c", definedComponents[2].getFieldName()); + } + + @Test + public void testInsertAtSameOffset2() { + + DataType zeroDt = new ArrayDataType(ByteDataType.dataType, 0); + + struct = createStructure("Test", 100); + assertFalse(struct.isPackingEnabled()); + + struct.insertAtOffset(0, WordDataType.dataType, -1, "c", "comment c"); + struct.insertAtOffset(0, zeroDt, -1, "a", "comment a"); + struct.insertAtOffset(0, zeroDt, -1, "b", "comment b"); + + DataTypeComponent[] definedComponents = struct.getDefinedComponents(); + assertEquals("a", definedComponents[0].getFieldName()); + assertEquals("b", definedComponents[1].getFieldName()); + assertEquals("c", definedComponents[2].getFieldName()); + } + + @Test + public void testInsertAtEndOffset() { + + DataType zeroDt = new ArrayDataType(ByteDataType.dataType, 0); + + struct = createStructure("Test2", 100); + assertFalse(struct.isPackingEnabled()); + + struct.insertAtOffset(100, zeroDt, -1, "a", "comment a"); + struct.insertAtOffset(100, zeroDt, -1, "b", "comment b"); + + assertEquals("/Test2\n" + + "pack(disabled)\n" + + "Structure Test2 {\n" + + " 100 byte[0] 0 a \"comment a\"\n" + + " 100 byte[0] 0 b \"comment b\"\n" + + "}\n" + + "Length: 100 Alignment: 1\n", struct.toString()); + + // Insert non-zero-length component will increase struct size + struct.insertAtOffset(100, WordDataType.dataType, -1, "c", "comment c"); + + assertEquals("/Test2\n" + + "pack(disabled)\n" + + "Structure Test2 {\n" + + " 100 byte[0] 0 a \"comment a\"\n" + + " 100 byte[0] 0 b \"comment b\"\n" + + " 100 word 2 c \"comment c\"\n" + + "}\n" + + "Length: 102 Alignment: 1\n", struct.toString()); + } + // test inserting at offset 0 @Test public void testInsertAtOffset() { @@ -458,32 +526,18 @@ public class StructureDataTypeTest extends AbstractGenericTest { Array zeroArray = new ArrayDataType(FloatDataType.dataType, 0, -1); struct.insertAtOffset(2, zeroArray, -1); struct.insertAtOffset(2, FloatDataType.dataType, -1); - assertEquals(13, struct.getLength()); - - DataTypeComponent[] comps = struct.getDefinedComponents(); - - assertEquals(6, comps.length); - - assertEquals(0, comps[0].getOffset()); - assertEquals(0, comps[0].getOrdinal()); - assertEquals(ByteDataType.class, comps[0].getDataType().getClass()); - - assertEquals(2, comps[1].getOffset()); - assertEquals(2, comps[1].getOrdinal()); - assertEquals(FloatDataType.class, comps[1].getDataType().getClass()); - - assertEquals(6, comps[2].getOffset()); - assertEquals(3, comps[2].getOrdinal()); - assertTrue(zeroArray.isEquivalent(comps[2].getDataType())); - - assertEquals(6, comps[3].getOffset()); - assertEquals(4, comps[3].getOrdinal()); - assertEquals(WordDataType.class, comps[3].getDataType().getClass()); - - assertEquals(8, comps[4].getOffset()); - assertEquals(5, comps[4].getOrdinal()); - assertEquals(DWordDataType.class, comps[4].getDataType().getClass()); + assertEquals("/TestStruct\n" + + "pack(disabled)\n" + + "Structure TestStruct {\n" + + " 0 byte 1 field1 \"Comment1\"\n" + + " 2 float[0] 0 \"\"\n" + + " 2 float 4 \"\"\n" + + " 6 word 2 \"Comment2\"\n" + + " 8 dword 4 field3 \"\"\n" + + " 12 byte 1 field4 \"Comment4\"\n" + + "}\n" + + "Length: 13 Alignment: 1\n", struct.toString()); } @Test @@ -660,7 +714,15 @@ public class StructureDataTypeTest extends AbstractGenericTest { } @Test - public void testInsertBitFieldAtLittleEndian() throws Exception { + public void testInsertBitFieldAtLittleEndian2() throws Exception { + + Array zeroArray = new ArrayDataType(CharDataType.dataType, 0, -1); + struct.insertAtOffset(2, zeroArray, -1, "A", null); + struct.insertAtOffset(2, zeroArray, -1, "B", null); + struct.insertAtOffset(2, zeroArray, -1, "C", null); + struct.insertAtOffset(2, zeroArray, -1, "D", null); + struct.insertAtOffset(2, zeroArray, -1, "E", null); + struct.insertAtOffset(2, CharDataType.dataType, -1, "XXX", null); struct.insertBitFieldAt(2, 4, 0, IntegerDataType.dataType, 3, "bf1", "bf1Comment"); @@ -670,15 +732,21 @@ public class StructureDataTypeTest extends AbstractGenericTest { "Structure TestStruct {\n" + " 0 byte 1 field1 \"Comment1\"\n" + // " 1 undefined 1 \"\"\n" + + " 2 char[0] 0 A \"\"\n" + + " 2 char[0] 0 B \"\"\n" + + " 2 char[0] 0 C \"\"\n" + + " 2 char[0] 0 D \"\"\n" + + " 2 char[0] 0 E \"\"\n" + " 2 int:3(0) 1 bf1 \"bf1Comment\"\n" + // " 3 undefined 1 \"\"\n" + // " 4 undefined 1 \"\"\n" + -// " 5 undefined 1 \"\"\n" + - " 6 word 2 \"Comment2\"\n" + - " 8 dword 4 field3 \"\"\n" + - " 12 byte 1 field4 \"Comment4\"\n" + +// " 5 undefined 1 \"\"\n" + + " 6 char 1 XXX \"\"\n" + + " 7 word 2 \"Comment2\"\n" + + " 9 dword 4 field3 \"\"\n" + + " 13 byte 1 field4 \"Comment4\"\n" + "}\n" + - "Length: 13 Alignment: 1", struct); + "Length: 14 Alignment: 1", struct); //@formatter:on struct.insertBitFieldAt(2, 4, 3, IntegerDataType.dataType, 3, "bf2", "bf2Comment"); @@ -688,17 +756,23 @@ public class StructureDataTypeTest extends AbstractGenericTest { "pack(disabled)\n" + "Structure TestStruct {\n" + " 0 byte 1 field1 \"Comment1\"\n" + -// " 1 undefined 1 \"\"\n" + +// " 1 undefined 1 \"\"\n" + + " 2 char[0] 0 A \"\"\n" + + " 2 char[0] 0 B \"\"\n" + + " 2 char[0] 0 C \"\"\n" + + " 2 char[0] 0 D \"\"\n" + + " 2 char[0] 0 E \"\"\n" + " 2 int:3(0) 1 bf1 \"bf1Comment\"\n" + " 2 int:3(3) 1 bf2 \"bf2Comment\"\n" + // " 3 undefined 1 \"\"\n" + // " 4 undefined 1 \"\"\n" + // " 5 undefined 1 \"\"\n" + - " 6 word 2 \"Comment2\"\n" + - " 8 dword 4 field3 \"\"\n" + - " 12 byte 1 field4 \"Comment4\"\n" + + " 6 char 1 XXX \"\"\n" + + " 7 word 2 \"Comment2\"\n" + + " 9 dword 4 field3 \"\"\n" + + " 13 byte 1 field4 \"Comment4\"\n" + "}\n" + - "Length: 13 Alignment: 1", struct); + "Length: 14 Alignment: 1", struct); //@formatter:on struct.insertBitFieldAt(2, 4, 6, IntegerDataType.dataType, 15, "bf3", "bf3Comment"); @@ -708,16 +782,22 @@ public class StructureDataTypeTest extends AbstractGenericTest { "pack(disabled)\n" + "Structure TestStruct {\n" + " 0 byte 1 field1 \"Comment1\"\n" + -// " 1 undefined 1 \"\"\n" + +// " 1 undefined 1 \"\"\n" + + " 2 char[0] 0 A \"\"\n" + + " 2 char[0] 0 B \"\"\n" + + " 2 char[0] 0 C \"\"\n" + + " 2 char[0] 0 D \"\"\n" + + " 2 char[0] 0 E \"\"\n" + " 2 int:3(0) 1 bf1 \"bf1Comment\"\n" + " 2 int:3(3) 1 bf2 \"bf2Comment\"\n" + " 2 int:15(6) 3 bf3 \"bf3Comment\"\n" + // " 5 undefined 1 \"\"\n" + - " 6 word 2 \"Comment2\"\n" + - " 8 dword 4 field3 \"\"\n" + - " 12 byte 1 field4 \"Comment4\"\n" + + " 6 char 1 XXX \"\"\n" + + " 7 word 2 \"Comment2\"\n" + + " 9 dword 4 field3 \"\"\n" + + " 13 byte 1 field4 \"Comment4\"\n" + "}\n" + - "Length: 13 Alignment: 1", struct); + "Length: 14 Alignment: 1", struct); //@formatter:on try { @@ -737,15 +817,47 @@ public class StructureDataTypeTest extends AbstractGenericTest { "Structure TestStruct {\n" + " 0 byte 1 field1 \"Comment1\"\n" + // " 1 undefined 1 \"\"\n" + + " 2 char[0] 0 A \"\"\n" + + " 2 char[0] 0 B \"\"\n" + + " 2 char[0] 0 C \"\"\n" + + " 2 char[0] 0 D \"\"\n" + + " 2 char[0] 0 E \"\"\n" + " 2 int:3(0) 1 bf1 \"bf1Comment\"\n" + " 2 int:3(3) 1 bf2 \"bf2Comment\"\n" + " 2 int:15(6) 3 bf3 \"bf3Comment\"\n" + " 4 int:11(5) 2 bf4 \"bf4Comment\"\n" + - " 6 word 2 \"Comment2\"\n" + - " 8 dword 4 field3 \"\"\n" + - " 12 byte 1 field4 \"Comment4\"\n" + + " 6 char 1 XXX \"\"\n" + + " 7 word 2 \"Comment2\"\n" + + " 9 dword 4 field3 \"\"\n" + + " 13 byte 1 field4 \"Comment4\"\n" + "}\n" + - "Length: 13 Alignment: 1", struct); + "Length: 14 Alignment: 1", struct); + //@formatter:on + + struct.insertBitFieldAt(2, 4, 0, IntegerDataType.dataType, 0, "z", "zero bitfield"); + + //@formatter:off + CompositeTestUtils.assertExpectedComposite(this, "/TestStruct\n" + + "pack(disabled)\n" + + "Structure TestStruct {\n" + + " 0 byte 1 field1 \"Comment1\"\n" + +// " 1 undefined 1 \"\"\n" + + " 2 char[0] 0 A \"\"\n" + + " 2 char[0] 0 B \"\"\n" + + " 2 char[0] 0 C \"\"\n" + + " 2 char[0] 0 D \"\"\n" + + " 2 char[0] 0 E \"\"\n" + + " 2 int:0(0) 0 \"zero bitfield\"\n" + // field name discarded + " 2 int:3(0) 1 bf1 \"bf1Comment\"\n" + + " 2 int:3(3) 1 bf2 \"bf2Comment\"\n" + + " 2 int:15(6) 3 bf3 \"bf3Comment\"\n" + + " 4 int:11(5) 2 bf4 \"bf4Comment\"\n" + + " 6 char 1 XXX \"\"\n" + + " 7 word 2 \"Comment2\"\n" + + " 9 dword 4 field3 \"\"\n" + + " 13 byte 1 field4 \"Comment4\"\n" + + "}\n" + + "Length: 14 Alignment: 1", struct); //@formatter:on } @@ -839,6 +951,26 @@ public class StructureDataTypeTest extends AbstractGenericTest { "}\n" + "Length: 13 Alignment: 1", struct); //@formatter:on + + struct.insertBitFieldAt(2, 4, 31, IntegerDataType.dataType, 0, "z", "zero bitfield"); + + //@formatter:off + CompositeTestUtils.assertExpectedComposite(this, "/TestStruct\n" + + "pack(disabled)\n" + + "Structure TestStruct {\n" + + " 0 byte 1 field1 \"Comment1\"\n" + +// " 1 undefined 1 \"\"\n" + + " 2 int:0(7) 0 \"zero bitfield\"\n" + // field name discarded + " 2 int:3(5) 1 bf1 \"bf1Comment\"\n" + + " 2 int:3(2) 1 bf2 \"bf2Comment\"\n" + + " 2 int:15(3) 3 bf3 \"bf3Comment\"\n" + + " 4 int:11(0) 2 bf4 \"bf4Comment\"\n" + + " 6 word 2 \"Comment2\"\n" + + " 8 dword 4 field3 \"\"\n" + + " 12 byte 1 field4 \"Comment4\"\n" + + "}\n" + + "Length: 13 Alignment: 1", struct); + //@formatter:on } @Test diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/CompositeDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/CompositeDB.java index 66b2002171..74237059a0 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/CompositeDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/CompositeDB.java @@ -30,7 +30,7 @@ import ghidra.util.UniversalID; import ghidra.util.exception.AssertException; /** - * Database implementation for a structure or union. + * {@link CompositeDB} provides an abstract database implementation for a structure or union. */ abstract class CompositeDB extends DataTypeDB implements CompositeInternal { 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 32b2579f1d..5c14268e8e 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 @@ -1007,6 +1007,26 @@ class StructureDB extends CompositeDB implements StructureInternal { return index; } + /** + * Find insertion index such that the index falls after all zero-length components at the + * specified offset and before any bitfields at that offset. + * + * @param index any defined component index which contains offset. + * @param offset offset within structure. + * @return index of first non-zero-length defined checking in the forward direction. + */ + private int afterNonZeroComponentsAtOffset(int index, int offset) { + int maxIndex = components.size(); + while (index < maxIndex) { + DataTypeComponentDB dtc = components.get(index); + if (dtc.getOffset() != offset || dtc.getLength() != 0) { + break; + } + ++index; + } + return index; + } + /** * Identify defined-component index of the first non-zero-length component which contains the * specified offset. If only zero-length components exist, the last zero-length component which @@ -1322,14 +1342,20 @@ class StructureDB extends CompositeDB implements StructureInternal { structLength = offset; } + // Any component insert at an offset should be placed after any zero-length components + // at the same offset but before any non-zero-length component. + int index = Collections.binarySearch(components, Integer.valueOf(offset), OffsetComparator.INSTANCE); int additionalShift = 0; if (index >= 0) { index = backupToFirstComponentContainingOffset(index, offset); - DataTypeComponentDB dtc = components.get(index); - additionalShift = offset - dtc.getOffset(); + index = afterNonZeroComponentsAtOffset(index, offset); + if (index < components.size()) { + DataTypeComponentDB dtc = components.get(index); + additionalShift = offset - dtc.getOffset(); + } } else { index = -index - 1; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BitFieldDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BitFieldDataType.java index bcccdca19e..0c3c439890 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BitFieldDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BitFieldDataType.java @@ -184,6 +184,11 @@ public class BitFieldDataType extends AbstractDataType { * Get the packing storage size in bytes associated with this bit-field which may be * larger than the base type associated with the fields original definition. * Returned value is the same as {@link #getLength()}. + *

+ * NOTE: Bitfields with a bit-size of zero will report a storage size of 1, although + * {@link #isZeroLength()} will return true. This is consistent with other datatypes which + * support a zero-length {@link DataTypeComponent} such as a zero-element Array. + * * @return packing storage size in bytes */ public int getStorageSize() { @@ -331,6 +336,9 @@ public class BitFieldDataType extends AbstractDataType { } } + /** + * @see #getStorageSize() + */ @Override public int getLength() { return storageSize; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponent.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponent.java index dd1a63388e..33f83fc764 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponent.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponent.java @@ -192,7 +192,7 @@ public interface DataTypeComponent { if (dataType instanceof Array) { return true; } - // assumes undefined types will ultimately have a non-zero length + // assumes not-yet-defined types will ultimately have a non-zero length return !dataType.isNotYetDefined(); } return false; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponentImpl.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponentImpl.java index 4519b49a1b..0226062d05 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponentImpl.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponentImpl.java @@ -61,6 +61,9 @@ public class DataTypeComponentImpl implements InternalDataTypeComponent, Seriali this.fieldName = InternalDataTypeComponent.cleanupFieldName(fieldName); setDataType(dataType); setComment(comment); + if (isZeroBitFieldComponent()) { + this.length = 0; // previously stored as 1, force to 0 + } } /** diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Structure.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Structure.java index 4a28743832..132f68a040 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Structure.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Structure.java @@ -240,9 +240,16 @@ public interface Structure extends Composite { /** * Inserts a new datatype at the specified offset into this structure. Inserting a component * will cause any conflicting components to shift down to the extent necessary to avoid a - * conflict. + * conflict. The overall structure length will always increase when a non-zero-length + * component is inserted. NOTE: bitfields may share an offset with other bitfields and + * zero-length components. *

- * This method does not support bit-field insertions which must use the method + * Any component insert at an offset will be placed after any zero-length components + * at the same offset but before any non-zero-length components. The components which + * fall after the insertion point will have there ordinal incremented and offset + * adjusted as needed. + *

+ * This method will defer bit-field insertions to the method * {@link #insertBitFieldAt(int, int, int, DataType, int, String, String)}. * * @param offset the byte offset into the structure where the new datatype is to be inserted. @@ -389,7 +396,8 @@ public interface Structure extends Composite { /** * Replaces all components containing the specified byte offset with a new component using the * specified datatype, length, name and comment. If the offset corresponds to a bit-field - * more than one component may be consumed by this replacement. + * more than one component may be consumed by this replacement. In general, this method + * should not be used to replace bitfield components. *

* This method may not be used to replace a zero-length component since there may be any number * of zero-length components at the same offset. If the only defined component(s) at the specified 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 5ff037ffba..171fc77449 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 @@ -523,14 +523,20 @@ public class StructureDataType extends CompositeDataTypeImpl implements Structur structLength = offset; } + // Any component insert at an offset should be placed after any zero-length components + // at the same offset but before any non-zero-length component. + int index = Collections.binarySearch(components, Integer.valueOf(offset), OffsetComparator.INSTANCE); int additionalShift = 0; if (index >= 0) { index = backupToFirstComponentContainingOffset(index, offset); - DataTypeComponent dtc = components.get(index); - additionalShift = offset - dtc.getOffset(); + index = afterNonZeroComponentsAtOffset(index, offset); + if (index < components.size()) { + DataTypeComponentImpl dtc = components.get(index); + additionalShift = offset - dtc.getOffset(); + } } else { index = -index - 1; @@ -898,6 +904,26 @@ public class StructureDataType extends CompositeDataTypeImpl implements Structur return index; } + /** + * Find insertion index such that the index falls after all zero-length components at the + * specified offset and before any bitfields at that offset. + * + * @param index any defined component index which contains offset. + * @param offset offset within structure. + * @return index of first non-zero-length defined checking in the forward direction. + */ + private int afterNonZeroComponentsAtOffset(int index, int offset) { + int maxIndex = components.size(); + while (index < maxIndex) { + DataTypeComponentImpl dtc = components.get(index); + if (dtc.getOffset() != offset || dtc.getLength() != 0) { + break; + } + ++index; + } + return index; + } + /** * Identify defined-component index of the first non-zero-length component which contains the specified offset. * If only zero-length components exist, the last zero-length component which contains the offset will be returned. diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/database/data/StructureDBTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/database/data/StructureDBTest.java index 4a5e80f535..03adfc1d70 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/database/data/StructureDBTest.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/database/data/StructureDBTest.java @@ -364,6 +364,74 @@ public class StructureDBTest extends AbstractGenericTest { assertEquals(ByteDataType.class, comps[2].getDataType().getClass()); } + @Test + public void testInsertAtSameOffset1() { + + DataType zeroDt = new ArrayDataType(ByteDataType.dataType, 0); + + struct = createStructure("Test", 100); + assertFalse(struct.isPackingEnabled()); + + struct.insertAtOffset(0, zeroDt, -1, "a", "comment a"); + struct.insertAtOffset(0, zeroDt, -1, "b", "comment b"); + struct.insertAtOffset(0, WordDataType.dataType, -1, "c", "comment c"); + + DataTypeComponentDB[] definedComponents = struct.getDefinedComponents(); + assertEquals("a", definedComponents[0].getFieldName()); + assertEquals("b", definedComponents[1].getFieldName()); + assertEquals("c", definedComponents[2].getFieldName()); + } + + @Test + public void testInsertAtSameOffset2() { + + DataType zeroDt = new ArrayDataType(ByteDataType.dataType, 0); + + struct = createStructure("Test", 100); + assertFalse(struct.isPackingEnabled()); + + struct.insertAtOffset(0, WordDataType.dataType, -1, "c", "comment c"); + struct.insertAtOffset(0, zeroDt, -1, "a", "comment a"); + struct.insertAtOffset(0, zeroDt, -1, "b", "comment b"); + + DataTypeComponentDB[] definedComponents = struct.getDefinedComponents(); + assertEquals("a", definedComponents[0].getFieldName()); + assertEquals("b", definedComponents[1].getFieldName()); + assertEquals("c", definedComponents[2].getFieldName()); + } + + @Test + public void testInsertAtEndOffset() { + + DataType zeroDt = new ArrayDataType(ByteDataType.dataType, 0); + + struct = createStructure("Test2", 100); + assertFalse(struct.isPackingEnabled()); + + struct.insertAtOffset(100, zeroDt, -1, "a", "comment a"); + struct.insertAtOffset(100, zeroDt, -1, "b", "comment b"); + + assertEquals("/Test2\n" + + "pack(disabled)\n" + + "Structure Test2 {\n" + + " 100 byte[0] 0 a \"comment a\"\n" + + " 100 byte[0] 0 b \"comment b\"\n" + + "}\n" + + "Length: 100 Alignment: 1\n", struct.toString()); + + // Insert non-zero-length component will increase struct size + struct.insertAtOffset(100, WordDataType.dataType, -1, "c", "comment c"); + + assertEquals("/Test2\n" + + "pack(disabled)\n" + + "Structure Test2 {\n" + + " 100 byte[0] 0 a \"comment a\"\n" + + " 100 byte[0] 0 b \"comment b\"\n" + + " 100 word 2 c \"comment c\"\n" + + "}\n" + + "Length: 102 Alignment: 1\n", struct.toString()); + } + // test inserting at offset 0 @Test public void testInsertAtOffset() { @@ -485,32 +553,18 @@ public class StructureDBTest extends AbstractGenericTest { Array zeroArray = new ArrayDataType(FloatDataType.dataType, 0, -1); struct.insertAtOffset(2, zeroArray, -1); struct.insertAtOffset(2, FloatDataType.dataType, -1); - assertEquals(13, struct.getLength()); - - DataTypeComponent[] comps = struct.getDefinedComponents(); - - assertEquals(6, comps.length); - - assertEquals(0, comps[0].getOffset()); - assertEquals(0, comps[0].getOrdinal()); - assertEquals(ByteDataType.class, comps[0].getDataType().getClass()); - - assertEquals(2, comps[1].getOffset()); - assertEquals(2, comps[1].getOrdinal()); - assertEquals(FloatDataType.class, comps[1].getDataType().getClass()); - - assertEquals(6, comps[2].getOffset()); - assertEquals(3, comps[2].getOrdinal()); - assertTrue(zeroArray.isEquivalent(comps[2].getDataType())); - - assertEquals(6, comps[3].getOffset()); - assertEquals(4, comps[3].getOrdinal()); - assertEquals(WordDataType.class, comps[3].getDataType().getClass()); - - assertEquals(8, comps[4].getOffset()); - assertEquals(5, comps[4].getOrdinal()); - assertEquals(DWordDataType.class, comps[4].getDataType().getClass()); + assertEquals("/Test\n" + + "pack(disabled)\n" + + "Structure Test {\n" + + " 0 byte 1 field1 \"Comment1\"\n" + + " 2 float[0] 0 \"\"\n" + + " 2 float 4 \"\"\n" + + " 6 word 2 \"Comment2\"\n" + + " 8 dword 4 field3 \"\"\n" + + " 12 byte 1 field4 \"Comment4\"\n" + + "}\n" + + "Length: 13 Alignment: 1\n", struct.toString()); } @Test @@ -815,6 +869,154 @@ public class StructureDBTest extends AbstractGenericTest { //@formatter:on } + @Test + public void testInsertBitFieldAtLittleEndian2() throws Exception { + + Array zeroArray = new ArrayDataType(CharDataType.dataType, 0, -1); + struct.insertAtOffset(2, zeroArray, -1, "A", null); + struct.insertAtOffset(2, zeroArray, -1, "B", null); + struct.insertAtOffset(2, zeroArray, -1, "C", null); + struct.insertAtOffset(2, zeroArray, -1, "D", null); + struct.insertAtOffset(2, zeroArray, -1, "E", null); + struct.insertAtOffset(2, CharDataType.dataType, -1, "XXX", null); + + struct.insertBitFieldAt(2, 4, 0, IntegerDataType.dataType, 3, "bf1", "bf1Comment"); + + //@formatter:off + CompositeTestUtils.assertExpectedComposite(this, "/Test\n" + + "pack(disabled)\n" + + "Structure Test {\n" + + " 0 byte 1 field1 \"Comment1\"\n" + +// " 1 undefined 1 \"\"\n" + + " 2 char[0] 0 A \"\"\n" + + " 2 char[0] 0 B \"\"\n" + + " 2 char[0] 0 C \"\"\n" + + " 2 char[0] 0 D \"\"\n" + + " 2 char[0] 0 E \"\"\n" + + " 2 int:3(0) 1 bf1 \"bf1Comment\"\n" + +// " 3 undefined 1 \"\"\n" + +// " 4 undefined 1 \"\"\n" + +// " 5 undefined 1 \"\"\n" + + " 6 char 1 XXX \"\"\n" + + " 7 word 2 \"Comment2\"\n" + + " 9 dword 4 field3 \"\"\n" + + " 13 byte 1 field4 \"Comment4\"\n" + + "}\n" + + "Length: 14 Alignment: 1", struct); + //@formatter:on + + struct.insertBitFieldAt(2, 4, 3, IntegerDataType.dataType, 3, "bf2", "bf2Comment"); + + //@formatter:off + CompositeTestUtils.assertExpectedComposite(this, "/Test\n" + + "pack(disabled)\n" + + "Structure Test {\n" + + " 0 byte 1 field1 \"Comment1\"\n" + +// " 1 undefined 1 \"\"\n" + + " 2 char[0] 0 A \"\"\n" + + " 2 char[0] 0 B \"\"\n" + + " 2 char[0] 0 C \"\"\n" + + " 2 char[0] 0 D \"\"\n" + + " 2 char[0] 0 E \"\"\n" + + " 2 int:3(0) 1 bf1 \"bf1Comment\"\n" + + " 2 int:3(3) 1 bf2 \"bf2Comment\"\n" + +// " 3 undefined 1 \"\"\n" + +// " 4 undefined 1 \"\"\n" + +// " 5 undefined 1 \"\"\n" + + " 6 char 1 XXX \"\"\n" + + " 7 word 2 \"Comment2\"\n" + + " 9 dword 4 field3 \"\"\n" + + " 13 byte 1 field4 \"Comment4\"\n" + + "}\n" + + "Length: 14 Alignment: 1", struct); + //@formatter:on + + struct.insertBitFieldAt(2, 4, 6, IntegerDataType.dataType, 15, "bf3", "bf3Comment"); + + //@formatter:off + CompositeTestUtils.assertExpectedComposite(this, "/Test\n" + + "pack(disabled)\n" + + "Structure Test {\n" + + " 0 byte 1 field1 \"Comment1\"\n" + +// " 1 undefined 1 \"\"\n" + + " 2 char[0] 0 A \"\"\n" + + " 2 char[0] 0 B \"\"\n" + + " 2 char[0] 0 C \"\"\n" + + " 2 char[0] 0 D \"\"\n" + + " 2 char[0] 0 E \"\"\n" + + " 2 int:3(0) 1 bf1 \"bf1Comment\"\n" + + " 2 int:3(3) 1 bf2 \"bf2Comment\"\n" + + " 2 int:15(6) 3 bf3 \"bf3Comment\"\n" + +// " 5 undefined 1 \"\"\n" + + " 6 char 1 XXX \"\"\n" + + " 7 word 2 \"Comment2\"\n" + + " 9 dword 4 field3 \"\"\n" + + " 13 byte 1 field4 \"Comment4\"\n" + + "}\n" + + "Length: 14 Alignment: 1", struct); + //@formatter:on + + try { + struct.insertBitFieldAt(2, 4, 21, IntegerDataType.dataType, 12, "bf4", "bf4Comment"); + fail( + "expected - IllegalArgumentException: Bitfield does not fit within specified constraints"); + } + catch (IllegalArgumentException e) { + // expected + } + + struct.insertBitFieldAt(2, 4, 21, IntegerDataType.dataType, 11, "bf4", "bf4Comment"); + + //@formatter:off + CompositeTestUtils.assertExpectedComposite(this, "/Test\n" + + "pack(disabled)\n" + + "Structure Test {\n" + + " 0 byte 1 field1 \"Comment1\"\n" + +// " 1 undefined 1 \"\"\n" + + " 2 char[0] 0 A \"\"\n" + + " 2 char[0] 0 B \"\"\n" + + " 2 char[0] 0 C \"\"\n" + + " 2 char[0] 0 D \"\"\n" + + " 2 char[0] 0 E \"\"\n" + + " 2 int:3(0) 1 bf1 \"bf1Comment\"\n" + + " 2 int:3(3) 1 bf2 \"bf2Comment\"\n" + + " 2 int:15(6) 3 bf3 \"bf3Comment\"\n" + + " 4 int:11(5) 2 bf4 \"bf4Comment\"\n" + + " 6 char 1 XXX \"\"\n" + + " 7 word 2 \"Comment2\"\n" + + " 9 dword 4 field3 \"\"\n" + + " 13 byte 1 field4 \"Comment4\"\n" + + "}\n" + + "Length: 14 Alignment: 1", struct); + //@formatter:on + + struct.insertBitFieldAt(2, 4, 0, IntegerDataType.dataType, 0, "z", "zero bitfield"); + + //@formatter:off + CompositeTestUtils.assertExpectedComposite(this, "/Test\n" + + "pack(disabled)\n" + + "Structure Test {\n" + + " 0 byte 1 field1 \"Comment1\"\n" + +// " 1 undefined 1 \"\"\n" + + " 2 char[0] 0 A \"\"\n" + + " 2 char[0] 0 B \"\"\n" + + " 2 char[0] 0 C \"\"\n" + + " 2 char[0] 0 D \"\"\n" + + " 2 char[0] 0 E \"\"\n" + + " 2 int:0(0) 0 \"zero bitfield\"\n" + // field name discarded + " 2 int:3(0) 1 bf1 \"bf1Comment\"\n" + + " 2 int:3(3) 1 bf2 \"bf2Comment\"\n" + + " 2 int:15(6) 3 bf3 \"bf3Comment\"\n" + + " 4 int:11(5) 2 bf4 \"bf4Comment\"\n" + + " 6 char 1 XXX \"\"\n" + + " 7 word 2 \"Comment2\"\n" + + " 9 dword 4 field3 \"\"\n" + + " 13 byte 1 field4 \"Comment4\"\n" + + "}\n" + + "Length: 14 Alignment: 1", struct); + //@formatter:on + } + @Test public void testInsertBitFieldAtBigEndian() throws Exception {