diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeUtilitiesTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeUtilitiesTest.java index 30f54dc4ba..90ac08ab20 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeUtilitiesTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeUtilitiesTest.java @@ -63,6 +63,52 @@ public class DataTypeUtilitiesTest extends AbstractGenericTest { } } + @Test + public void testIsSameKindDataType() { + + assertTrue( + DataTypeUtilities.isSameKindDataType(IntegerDataType.dataType, ShortDataType.dataType)); + assertFalse( + DataTypeUtilities.isSameKindDataType(FloatDataType.dataType, ShortDataType.dataType)); + + assertTrue( + DataTypeUtilities.isSameKindDataType(new PointerDataType(IntegerDataType.dataType), + new PointerDataType(ShortDataType.dataType))); + assertFalse( + DataTypeUtilities.isSameKindDataType(new PointerDataType(FloatDataType.dataType), + new PointerDataType(ShortDataType.dataType))); + + assertTrue( + DataTypeUtilities.isSameKindDataType(new StructureDataType("X", 10), + new StructureDataType("Y", 5))); + assertTrue( + DataTypeUtilities.isSameKindDataType(new UnionDataType("X"), new UnionDataType("Y"))); + assertFalse( + DataTypeUtilities.isSameKindDataType(new StructureDataType("X", 10), + new UnionDataType("Y"))); + + assertTrue( + DataTypeUtilities.isSameKindDataType( + new PointerDataType(new StructureDataType("X", 10)), + new PointerDataType(new StructureDataType("Y", 5)))); + assertTrue( + DataTypeUtilities.isSameKindDataType(new PointerDataType(new UnionDataType("X")), + new PointerDataType(new UnionDataType("Y")))); + assertFalse( + DataTypeUtilities.isSameKindDataType( + new PointerDataType(new StructureDataType("X", 10)), + new PointerDataType(new UnionDataType("Y")))); + + assertTrue( + DataTypeUtilities.isSameKindDataType( + new TypedefDataType("Foo", new PointerDataType(new StructureDataType("X", 10))), + new PointerDataType(new StructureDataType("Y", 5)))); + assertFalse( + DataTypeUtilities.isSameKindDataType( + new TypedefDataType("Foo", new PointerDataType(new StructureDataType("X", 10))), + new PointerDataType(new UnionDataType("Y")))); + } + @Test public void testEqualsIgnoreConflictviaManagedDataTypes() throws Exception { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/ConflictHandlerTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/ConflictHandlerTest.java index 45bd5f52af..2a68c9e4e2 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/ConflictHandlerTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/ConflictHandlerTest.java @@ -24,6 +24,8 @@ import ghidra.program.database.ProgramDB; import ghidra.program.model.data.*; import ghidra.program.model.data.DataTypeConflictHandler.ConflictResult; import ghidra.test.AbstractGhidraHeadedIntegrationTest; +import ghidra.util.UniversalID; +import ghidra.util.UniversalIdGenerator; /** * Tests for the {@link DataTypeConflictHandler conflict handler} stuff. @@ -153,7 +155,7 @@ public class ConflictHandlerTest extends AbstractGhidraHeadedIntegrationTest { } /** - * Tests the {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER RESORAAH} + * Tests the {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER} * conflict handler to ensure that adding a empty conflicting structure resolves to a previous * populated structure. */ @@ -168,7 +170,7 @@ public class ConflictHandlerTest extends AbstractGhidraHeadedIntegrationTest { } /** - * Tests the {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER RESORAAH} + * Tests the {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER} * conflict handler to ensure that adding a populated structure replaces an existing * 'empty' structure. 'Empty' means either 0 byte length or 1 byte length structs * as previous versions of Ghidra did not allow truly empty structs. @@ -227,7 +229,7 @@ public class ConflictHandlerTest extends AbstractGhidraHeadedIntegrationTest { } /** - * Tests the {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER RESORAAH} + * Tests the {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER} * conflict handler to ensure that adding a conflicting typedef to a conflicting stub structure * (when there is already a typedef to a populated structure) correctly uses the * existing populated structure and existing typedef to the populated structure. @@ -253,7 +255,7 @@ public class ConflictHandlerTest extends AbstractGhidraHeadedIntegrationTest { } /** - * Tests the {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER RESORAAH} + * Tests the {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER} * conflict handler to ensure that adding truly conflicting structures and typedefs * are treated as new data types and are renamed to a different name when added. */ @@ -283,7 +285,7 @@ public class ConflictHandlerTest extends AbstractGhidraHeadedIntegrationTest { } /** - * Tests the {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER RESORAAH} + * Tests the {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER} * conflict handler when adding a conflicting typedef impl that is referred to multiple * times during a single addDataType() call. *

@@ -328,7 +330,7 @@ public class ConflictHandlerTest extends AbstractGhidraHeadedIntegrationTest { } /** - * Tests the {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER RESORAAH} + * Tests the {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER} * conflict handler when adding a conflicting typedef impl (but equiv) that is referred to multiple * times during a single addDataType() call. *

@@ -368,7 +370,7 @@ public class ConflictHandlerTest extends AbstractGhidraHeadedIntegrationTest { } /** - * Tests the {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER RESORAAH} + * Tests the {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER} * conflict handler when adding a typedef to a populated when there is already a typedef * to a stub structure. */ @@ -397,7 +399,7 @@ public class ConflictHandlerTest extends AbstractGhidraHeadedIntegrationTest { /** * Tests the - * {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER RESORAAH} + * {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER} * conflict handler to be sure that, if all else is the same, the packed version is chosen * over the non-packed version. *

@@ -422,7 +424,7 @@ public class ConflictHandlerTest extends AbstractGhidraHeadedIntegrationTest { /** * Tests the - * {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER RESORAAH} + * {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER} * conflict handler to be sure that, if all else is the same, the packed version is chosen * over the non-packed version. *

@@ -447,7 +449,7 @@ public class ConflictHandlerTest extends AbstractGhidraHeadedIntegrationTest { /** * Tests the - * {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER RESORAAH} + * {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER} * conflict handler to be sure that, if all else is the same, the new non-packed version is * chosen over the existing non-packed version. *

@@ -476,7 +478,7 @@ public class ConflictHandlerTest extends AbstractGhidraHeadedIntegrationTest { /** * Tests the - * {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER RESORAAH} + * {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER} * conflict handler to be sure that, if all else is the same, the new non-packed version is * chosen over the existing packed version. *

@@ -505,7 +507,7 @@ public class ConflictHandlerTest extends AbstractGhidraHeadedIntegrationTest { /** * Tests the - * {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER RESORAAH} + * {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER} * conflict handler to be sure that, if all else is the same, the packed version is chosen * over the non-packed version. *

@@ -515,24 +517,147 @@ public class ConflictHandlerTest extends AbstractGhidraHeadedIntegrationTest { public void testResolveDataTypeNonStructConflict() throws Exception { DataTypeManager dtm = new StandAloneDataTypeManager("Test"); int id = dtm.startTransaction(""); - Category otherRoot = dataMgr.getRootCategory(); - Category subc = otherRoot.createCategory("subc"); + try { + Category otherRoot = dataMgr.getRootCategory(); + Category subc = otherRoot.createCategory("subc"); - EnumDataType e = new EnumDataType(subc.getCategoryPath(), "Enum", 2); + EnumDataType e = new EnumDataType(subc.getCategoryPath(), "Enum", 2); - DataType resolvedEnum = - dtm.resolve(e, DataTypeConflictHandler.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER); - assertTrue(e.isEquivalent(resolvedEnum)); - assertEquals("/subc/Enum", resolvedEnum.getPathName()); + DataType resolvedEnum = + dtm.resolve(e, + DataTypeConflictHandler.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER); + assertTrue(e.isEquivalent(resolvedEnum)); + assertEquals("/subc/Enum", resolvedEnum.getPathName()); - e.add("xyz", 1); + e.add("xyz", 1); - resolvedEnum = - dtm.resolve(e, DataTypeConflictHandler.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER); - assertTrue(e.isEquivalent(resolvedEnum)); - assertEquals("/subc/Enum.conflict", resolvedEnum.getPathName()); + resolvedEnum = + dtm.resolve(e, + DataTypeConflictHandler.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER); + assertTrue(e.isEquivalent(resolvedEnum)); + assertEquals("/subc/Enum.conflict", resolvedEnum.getPathName()); + } + finally { + dtm.endTransaction(id, true); + dtm.close(); + } + } + + /** + * Tests the + * {@link DataTypeConflictHandler#REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER} + * conflict handler to be sure that and empty local structure will be replaced by + * a structure with source. + *

+ * Success is the source version is chosen over the empty local version. + */ + @Test + public void testChooseStructWithSourceOverExistingEmptyStructures() throws Exception { + + Structure struct = new StructureDataType(root, "TestStruct", 0, dataMgr); + struct = (Structure) dataMgr.resolve(struct, null); + + SourceArchive source = new DummySourceArchive("Test"); + + Structure structWithSource = new StructureDataType(root, "TestStruct", 0, dataMgr); + structWithSource.setSourceArchive(source); + structWithSource.add(ByteDataType.dataType); + + structWithSource = (Structure) dataMgr.resolve(structWithSource, + DataTypeConflictHandler.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER); + + assertEquals("TestStruct", structWithSource.getName()); + assertTrue(struct == structWithSource); + SourceArchive sourceArchive = struct.getSourceArchive(); + assertEquals(source.getSourceArchiveID(), sourceArchive.getSourceArchiveID()); + assertEquals(source.getName(), sourceArchive.getName()); + } + + @Test + public void testResolvePointerConflict() { + + DataType ptr1 = + new PointerDataType(new TypedefDataType("size_t", UnsignedIntegerDataType.dataType)); + DataType ptr2 = + new PointerDataType(new TypedefDataType("size_t", IntegerDataType.dataType)); + + DataType ptr1resolved = dataMgr.resolve(ptr1, DataTypeConflictHandler.DEFAULT_HANDLER); + assertEquals("size_t *", ptr1resolved.getName()); + + DataType ptr2resolvedA = dataMgr.resolve(ptr2, DataTypeConflictHandler.KEEP_HANDLER); + assertTrue(ptr2resolvedA == ptr1resolved); + + DataType ptr2resolvedB = dataMgr.resolve(ptr2, DataTypeConflictHandler.DEFAULT_HANDLER); + assertEquals("size_t.conflict *", ptr2resolvedB.getName()); + + DataType ptr2resolvedC = dataMgr.resolve(ptr2, DataTypeConflictHandler.REPLACE_HANDLER); + assertTrue(ptr2resolvedC == ptr2resolvedB); + } + + @Test + public void testResolveArrayConflict() { + + DataType array1 = + new ArrayDataType(new TypedefDataType("size_t", UnsignedIntegerDataType.dataType), 2, + -1); + DataType array2 = + new ArrayDataType(new TypedefDataType("size_t", IntegerDataType.dataType), 2, -1); + + DataType array1resolved = dataMgr.resolve(array1, DataTypeConflictHandler.DEFAULT_HANDLER); + assertEquals("size_t[2]", array1resolved.getName()); + + DataType array2resolvedA = dataMgr.resolve(array2, DataTypeConflictHandler.KEEP_HANDLER); + assertTrue(array2resolvedA == array1resolved); + + DataType array2resolvedB = dataMgr.resolve(array2, DataTypeConflictHandler.DEFAULT_HANDLER); + assertEquals("size_t.conflict[2]", array2resolvedB.getName()); + + DataType array2resolvedC = dataMgr.resolve(array2, DataTypeConflictHandler.REPLACE_HANDLER); + assertTrue(array2resolvedC == array2resolvedB); + } + + private static class DummySourceArchive implements SourceArchive { + + private final UniversalID id; + private final String archiveName; + + public DummySourceArchive(String archiveName) { + this.id = UniversalIdGenerator.nextID(); + this.archiveName = archiveName; + } + + public ArchiveType getArchiveType() { + return ArchiveType.FILE; + } + + public String getDomainFileID() { + return null; + } + + public long getLastSyncTime() { + return 0; + } + + public String getName() { + return archiveName; + } + + public UniversalID getSourceArchiveID() { + return id; + } + + public boolean isDirty() { + return false; + } + + public void setDirtyFlag(boolean dirty) { + } + + public void setLastSyncTime(long time) { + } + + public void setName(String name) { + } - dtm.endTransaction(id, true); - dtm.close(); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/CategoryDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/CategoryDB.java index c2954f3926..023d56c265 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/CategoryDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/CategoryDB.java @@ -540,7 +540,7 @@ class CategoryDB extends DatabaseObject implements Category { return; } if (existing != null) { - ConflictResult result = mgr.resolveConflict(handler, movedDataType, existing); + ConflictResult result = handler.resolveConflict(movedDataType, existing); if (result == ConflictResult.REPLACE_EXISTING) { // replace existing dt with new dt. mgr.replaceDataType(existing, movedDataType, true); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeManagerDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeManagerDB.java index 9ef0243cd2..41eb5c283a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeManagerDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeManagerDB.java @@ -1049,16 +1049,15 @@ abstract public class DataTypeManagerDB implements DataTypeManager { else if (sourceArchive == null || dataType.getUniversalID() == null) { // if the dataType has no source or it has no ID (datatypes with no ID are // always local i.e. pointers) - resolvedDataType = resolveNoSourceDataType(dataType, currentHandler); + resolvedDataType = resolveDataTypeNoSource(dataType, currentHandler); } else if (!sourceArchive.getSourceArchiveID().equals(getUniversalID()) && sourceArchive.getArchiveType() == ArchiveType.PROGRAM) { // dataTypes from a different program don't carry over their identity. - resolvedDataType = resolveNoSourceDataType(dataType, currentHandler); + resolvedDataType = resolveDataTypeNoSource(dataType, currentHandler); } else { - resolvedDataType = - resolveDataTypeWithSource(dataType, sourceArchive, currentHandler); + resolvedDataType = resolveDataTypeWithSource(dataType, currentHandler); } cacheResolvedDataType(dataType, resolvedDataType); if (resolvedDataType instanceof DataTypeDB) { @@ -1083,6 +1082,12 @@ abstract public class DataTypeManagerDB implements DataTypeManager { } private DataType resolveBuiltIn(DataType dataType, DataTypeConflictHandler handler) { + + if (dataType instanceof Pointer) { + // treat built-in pointers like other datatypes without a source + return resolveDataTypeNoSource(dataType, currentHandler); + } + // can't do this check now because Pointers from the BuiltinDataTypeManager are // not instances of BuiltInDataType because the BuiltInDataTypeManger converts // pointers from BuiltIns to PointerDBs (Probably shouldn't, but the @@ -1094,7 +1099,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager { return existingDataType; } // oops a non-builtin dataType exists with the same name. Only option is to rename existing - String dtName = getUnusedConflictName(dataType.getCategoryPath(), dataType.getName()); + String dtName = getUnusedConflictName(dataType); try { existingDataType.setName(dtName); } @@ -1135,84 +1140,8 @@ abstract public class DataTypeManagerDB implements DataTypeManager { } } - /** - * Either finds an equivalent dataType with the same categoryPath and name (or - * conflict name) to the given dataType. Otherwise, it creates a new dataType in - * this archive equivalent to the given dataType. If a dataType exists with same - * path and name but is not equivalent, the handler will resolve the problem in - * one of 3 ways. 1) A new dataType will be created, but with a .conflict name - * 2) The existing dataType will be replaced by a resolved copy of the given - * dataType. 3) The existing dataType will be returned instead of a resolved - * version of the given dataType. - * - * @param dataType the dataType for which to return an equivalent dataType in - * this manager - * @param handler Used to handle collisions with dataTypes with same path and - * name that is - * @return resolved datatype - */ - private DataType resolveNoSourceDataType(DataType dataType, DataTypeConflictHandler handler) { - - DataType existingDataType = findEquivalentDataTypeSameLocation(dataType, handler); - if (existingDataType != null) { - return existingDataType; - } - existingDataType = getDataType(dataType.getCategoryPath(), dataType.getName()); - if (existingDataType == null) { - return createDataType(dataType, dataType.getName(), null, handler); - } - - // So we have a dataType with the same path and name, but not equivalent, so use - // the conflictHandler to decide what to do. - ConflictResult result = resolveConflict(handler, dataType, existingDataType); - switch (result) { - - case REPLACE_EXISTING: // new type replaces old conflicted type - try { - if (updateExistingDataType(existingDataType, dataType)) { - return existingDataType; - } - renameToUnusedConflictName(existingDataType); - DataType newDataType = - createDataType(dataType, dataType.getName(), null, handler); - try { - replace(existingDataType, newDataType); - } - catch (DataTypeDependencyException e) { - throw new IllegalArgumentException( - "Invalid datatype replacement: " + newDataType.getName(), e); - } - return newDataType; - } - catch (DataTypeDependencyException e) { - // new type refers to old type - fallthrough to RENAME_AND_ADD - // TODO: alternatively we could throw an exception - } - - case RENAME_AND_ADD: // default handler behavior - String dtName = - getUnusedConflictName(dataType.getCategoryPath(), dataType.getName()); - DataType newDataType = createDataType(dataType, dtName, null, handler); - - // resolving child data types could result in another copy of dataType in the - // manager depending upon the conflict handler - check again - existingDataType = findEquivalentDataTypeSameLocation(dataType, handler); - // If there is an equivalent datatype, remove the added type and return the existing - if (existingDataType != null && existingDataType != newDataType) { - removeInternal(newDataType, TaskMonitor.DUMMY); - return existingDataType; - } - return newDataType; - - case USE_EXISTING: // new type is discarded and old conflicted type is returned - return existingDataType; - } - return null; - } - private void renameToUnusedConflictName(DataType dataType) { - String dtName = dataType.getName(); - String name = getUnusedConflictName(dataType.getCategoryPath(), dtName); + String name = getUnusedConflictName(dataType); try { dataType.setName(name); } @@ -1232,15 +1161,14 @@ abstract public class DataTypeManagerDB implements DataTypeManager { * * @param existingDataType existing datatype * @param dataType new datatype + * @param sourceArchive source archive associated with new type (may be null). + * If not null the existingDataType will be updated with source info. * @return true if replacement approach was successful, else false * @throws DataTypeDependencyException if datatype contains dependency issues * during resolve process */ - private boolean updateExistingDataType(DataType existingDataType, DataType dataType) - throws DataTypeDependencyException { - - // TODO: this approach could be added to other DB datatypes to avoid - // unnecessary creation and removal. + private boolean updateExistingDataType(DataType existingDataType, DataType dataType, + SourceArchive sourceArchive) throws DataTypeDependencyException { try { if (existingDataType instanceof StructureDB) { @@ -1249,7 +1177,6 @@ abstract public class DataTypeManagerDB implements DataTypeManager { } StructureDB existingStruct = (StructureDB) existingDataType; existingStruct.doReplaceWith((StructureInternal) dataType, true); - return true; } else if (existingDataType instanceof UnionDB) { if (!(dataType instanceof UnionInternal)) { @@ -1257,8 +1184,37 @@ abstract public class DataTypeManagerDB implements DataTypeManager { } UnionDB existingUnion = (UnionDB) existingDataType; existingUnion.doReplaceWith((UnionInternal) dataType, true); - return true; } + else if (existingDataType instanceof FunctionDefinitionDB) { + if (!(dataType instanceof FunctionDefinition)) { + return false; + } + existingDataType.replaceWith(dataType); + } + else if (existingDataType instanceof EnumDB) { + if (!(dataType instanceof Enum)) { + return false; + } + existingDataType.replaceWith(dataType); + } + else if (existingDataType instanceof TypedefDB) { + if (!(dataType instanceof TypeDef)) { + return false; + } + existingDataType.replaceWith(dataType); + } + else { + return false; + } + + if (sourceArchive != null) { + existingDataType.setSourceArchive(sourceArchive); + ((DataTypeDB) existingDataType).setUniversalID(dataType.getUniversalID()); + long lastChangeTime = dataType.getLastChangeTime(); + existingDataType.setLastChangeTime(lastChangeTime); + existingDataType.setLastChangeTimeInSourceArchive(lastChangeTime); + } + return true; } catch (IOException e) { dbError(e); @@ -1266,6 +1222,21 @@ abstract public class DataTypeManagerDB implements DataTypeManager { return false; } + /** + * This method gets a ".conflict" name that is not currently used by any data + * types in the indicated category of the data type manager. + * @param dt datatype who name is used to establish non-conflict base name + * @return the unused conflict name or original name for datatypes whose name is automatic + */ + public String getUnusedConflictName(DataType dt) { + String name = dt.getName(); + if ((dt instanceof Array) || (dt instanceof Pointer) || (dt instanceof BuiltInDataType)) { + // name not used - anything will do + return name; + } + return getUnusedConflictName(dt.getCategoryPath(), name); + } + /** * This method gets a ".conflict" name that is not currently used by any data * types in the indicated category of the data type manager. @@ -1283,20 +1254,59 @@ abstract public class DataTypeManagerDB implements DataTypeManager { if (index > 0) { name = name.substring(0, index); } + // Name sequence: , .conflict, .conflict1, ... + String baseName = name + DataType.CONFLICT_SUFFIX; - String testName = baseName; + String testName = name; int count = 0; while (getDataType(path, testName) != null) { - count++; - testName = baseName + count; + String countSuffix = ""; + if (count != 0) { + countSuffix = Integer.toString(count); + } + testName = baseName + countSuffix; + ++count; } return testName; } - private boolean isEquivalentDataType(DataType addedDataType, DataType existingDataType, - DataTypeConflictHandler handler) { - return existingDataType.isEquivalent(addedDataType) || - handler.resolveConflict(addedDataType, existingDataType) == ConflictResult.USE_EXISTING; + private List findDataTypesSameLocation(DataType dataType) { + + Category category = getCategory(dataType.getCategoryPath()); + if (category == null) { + return List.of(); + } + + if (!(dataType instanceof Pointer) && !(dataType instanceof Array)) { + return category.getDataTypesByBaseName(dataType.getName()); + } + + // Handle pointers and arrays + + DataType existingDataType = category.getDataType(dataType.getName()); + + DataType baseDataType = DataTypeUtilities.getBaseDataType(dataType); + if (baseDataType == null) { + return existingDataType != null ? List.of(existingDataType) : List.of(); + } + + SourceArchive sourceArchive = baseDataType.getSourceArchive(); + if (sourceArchive != null && sourceArchive.getArchiveType() == ArchiveType.BUILT_IN) { + return existingDataType != null ? List.of(existingDataType) : List.of(); + } + + String baseTypeName = baseDataType.getName(); + String decorations = dataType.getName().substring(baseTypeName.length()); + + List list = new ArrayList<>(); + for (DataType existingBaseDt : category.getDataTypesByBaseName(baseTypeName)) { + String name = existingBaseDt.getName() + decorations; + DataType dt = category.getDataType(name); + if (dt != null) { + list.add(dt); + } + } + return list; } /** @@ -1304,75 +1314,188 @@ abstract public class DataTypeManagerDB implements DataTypeManager { * categoryPath and has either the same name or a conflict variation of that * name. * - * @param dataType the dataType for which to find an equivalent existing - * dataType + * @param dataType the dataType for which to find an equivalent existing dataType */ - private DataType findEquivalentDataTypeSameLocation(DataType dataType, - DataTypeConflictHandler handler) { + private DataType findEquivalentDataTypeSameLocation(DataType dataType) { - // first see if an exact match exists - String dtName = dataType.getName(); - - DataType existingDataType = getDataType(dataType.getCategoryPath(), dtName); + // Check exact name match + DataType existingDataType = getDataType(dataType.getCategoryPath(), dataType.getName()); // If the existing Data type is currently being resolved, its isEquivalent // method is short circuited such that it will return true. So it is important // to call the isEquivalent on the existing datatype and not the dataType. - if (existingDataType != null && isEquivalentDataType(dataType, existingDataType, handler)) { + if (existingDataType != null && existingDataType.isEquivalent(dataType)) { return existingDataType; } - Category category = getCategory(dataType.getCategoryPath()); - if (category == null) { - return null; - } - List relatedByName = category.getDataTypesByBaseName(dtName); - + List relatedByName = findDataTypesSameLocation(dataType); for (DataType candidate : relatedByName) { - if (candidate != existingDataType && - isEquivalentDataType(dataType, candidate, handler)) { + if (candidate != existingDataType && candidate.isEquivalent(dataType)) { return candidate; } } return null; } - private DataType resolveDataTypeWithSource(DataType dataType, SourceArchive sourceArchive, - DataTypeConflictHandler handler) { + private DataType findDataTypeSameLocation(DataType dataType) { + + // Check exact name match which is similar + DataType existingDataType = getDataType(dataType.getCategoryPath(), dataType.getName()); + if (existingDataType != null && + DataTypeUtilities.isSameKindDataType(dataType, existingDataType)) { + return existingDataType; + } + + // check all conflict types + List relatedByName = findDataTypesSameLocation(dataType); + for (DataType candidate : relatedByName) { + if (existingDataType == null) { + existingDataType = candidate; + } + if (DataTypeUtilities.isSameKindDataType(dataType, candidate)) { + return candidate; + } + } + + return existingDataType; + } + + /** + * Either finds an equivalent dataType with the same categoryPath and name (or + * conflict name) to the given dataType. Otherwise, it creates a new dataType in + * this archive equivalent to the given dataType. If a dataType exists with same + * path and name but is not equivalent, the handler will resolve the problem in + * one of 3 ways. 1) A new dataType will be created, but with a .conflict name + * 2) The existing dataType will be replaced by a resolved copy of the given + * dataType. 3) The existing dataType will be returned instead of a resolved + * version of the given dataType. + * + * @param dataType the dataType for which to return an equivalent dataType in + * this manager + * @param handler Used to handle collisions with dataTypes with same path and + * name that is + * @return resolved datatype + */ + private DataType resolveDataTypeNoSource(DataType dataType, DataTypeConflictHandler handler) { + + DataType existingDataType = findEquivalentDataTypeSameLocation(dataType); + if (existingDataType != null) { + return existingDataType; + } + + return resolveNoEquivalentFound(dataType, null, handler); + } + + /** + * Perform datatype resolution for types originating from a source archive (excludes + * programs and built-in datatypes). + * + * @param dataType the dataType for which to return an equivalent dataType in + * this manager + * @param handler Used to handle collisions with dataTypes with same path and + * name that is + * @return resolved datatype + */ + private DataType resolveDataTypeWithSource(DataType dataType, DataTypeConflictHandler handler) { + + SourceArchive sourceArchive = dataType.getSourceArchive(); + // Do we have that dataType already resolved and associated with the source archive? DataType existingDataType = getDataType(sourceArchive, dataType.getUniversalID()); if (existingDataType != null) { - if (!existingDataType.isEquivalent(dataType)) { - if (handler.shouldUpdate(dataType, existingDataType)) { - existingDataType.replaceWith(dataType); - existingDataType.setLastChangeTime(dataType.getLastChangeTime()); - } + if (!existingDataType.isEquivalent(dataType) && + handler.shouldUpdate(dataType, existingDataType)) { + existingDataType.replaceWith(dataType); + existingDataType.setLastChangeTime(dataType.getLastChangeTime()); } return existingDataType; } - // Do we have the same named data type in the same category already? - existingDataType = getDataType(dataType.getCategoryPath(), dataType.getName()); - if (existingDataType == null) { - // Don't have a data type with this path name, so can create it. - return createDataType(dataType, dataType.getName(), sourceArchive, handler); - } - // If we have the same path name and the existing data type is a local data type // and is equivalent to this one, then associate it with the source archive - if (isLocalSource(existingDataType) && - isEquivalentDataType(dataType, existingDataType, handler)) { - return replaceEquivalentLocalWithSourceDataType(dataType, sourceArchive, - existingDataType); + existingDataType = findEquivalentDataTypeSameLocation(dataType); + if (existingDataType != null) { + if (isLocalSource(existingDataType)) { + // If we have an equivalent local data type associate it with the source archive + replaceEquivalentLocalWithSourceDataType(dataType, sourceArchive, existingDataType); + } + return existingDataType; } - - // Otherwise, we need to create a new Data type associated with the archive - // and it will possibly have a conflict name. - String dtName = getUnusedConflictName(dataType.getCategoryPath(), dataType.getName()); - return createDataType(dataType, dtName, sourceArchive, handler); + + return resolveNoEquivalentFound(dataType, sourceArchive, handler); } - private DataType replaceEquivalentLocalWithSourceDataType(DataType dataType, + /** + * Complete datatype resolution after having attempted to find an existing equivalent type. + * An attempt is made to identify a conflicting datatype and determine a conflict resolution + * using the specified conflict handler. + * @param dataType datatype being resolved + * @param sourceArchive source archive associated with new type (may be null) + * @param handler datatype conflict handler + * @return resolved datatype (may be existing or newly added datatype) + */ + private DataType resolveNoEquivalentFound(DataType dataType, SourceArchive sourceArchive, + DataTypeConflictHandler handler) { + + // If not found, do we have the same named data type in the same category already? + // (preference is given to similar kind of datatype when checking existing conflict types) + DataType existingDataType = findDataTypeSameLocation(dataType); + if (existingDataType == null) { + return createDataType(dataType, handler); + } + + // So we have a dataType with the same path and name, but not equivalent, so use + // the conflictHandler to decide what to do. + ConflictResult result = handler.resolveConflict(dataType, existingDataType); + switch (result) { + + case REPLACE_EXISTING: // new type replaces old conflicted type + try { + if (updateExistingDataType(existingDataType, dataType, sourceArchive)) { + return existingDataType; + } + renameToUnusedConflictName(existingDataType); + DataType newDataType = + createDataType(dataType, dataType.getName(), sourceArchive, handler); + try { + replace(existingDataType, newDataType); + } + catch (DataTypeDependencyException e) { + throw new IllegalArgumentException( + "Invalid datatype replacement: " + newDataType.getName(), e); + } + return newDataType; + } + catch (DataTypeDependencyException e) { + // new type refers to old type - fallthrough to RENAME_AND_ADD + // TODO: alternatively we could throw an exception + } + + case RENAME_AND_ADD: // default handler behavior + return createDataType(dataType, handler); + + default: // USE_EXISTING - new type is discarded and old conflicted type is returned + return existingDataType; + } + } + + private DataType createDataType(DataType dataType, DataTypeConflictHandler handler) { + SourceArchive sourceArchive = dataType.getSourceArchive(); + String dtName = getUnusedConflictName(dataType); + DataType newDataType = createDataType(dataType, dtName, sourceArchive, handler); + + // resolving child data types could result in another copy of dataType in the + // manager depending upon the conflict handler - check again + DataType existingDataType = findEquivalentDataTypeSameLocation(dataType); + // If there is an equivalent datatype, remove the added type and return the existing + if (existingDataType != null && existingDataType != newDataType) { + removeInternal(newDataType, TaskMonitor.DUMMY); + return existingDataType; + } + return newDataType; + } + + private void replaceEquivalentLocalWithSourceDataType(DataType dataType, SourceArchive sourceArchive, DataType existingDataType) { // Since it's equivalent, set its source, ID, and replace its components. // TODO: Need a better way to do this. @@ -1383,7 +1506,6 @@ abstract public class DataTypeManagerDB implements DataTypeManager { existingDataType.setLastChangeTime(lastChangeTime); existingDataType.setLastChangeTimeInSourceArchive(lastChangeTime); dataTypeChanged(existingDataType, false); - return existingDataType; } private boolean isLocalSource(DataType dataType) { @@ -1986,8 +2108,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager { // Set the datatype's universal ID to a newly generated universal ID, // since we no longer want the source archive data type's universal ID. - if (dataType instanceof DataTypeDB) { - DataTypeDB dt = (DataTypeDB) dataType; + if (dataType instanceof DataTypeDB dt) { dt.setUniversalID(UniversalIdGenerator.nextID()); } @@ -2038,8 +2159,8 @@ abstract public class DataTypeManagerDB implements DataTypeManager { if (dataType == null) { return; } - if (dataType instanceof DataTypeDB) { - ((DataTypeDB) dataType).notifyDeleted(); + if (dataType instanceof DataTypeDB dt) { + dt.notifyDeleted(); } else { buildSortedDataTypeList(); @@ -2159,8 +2280,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager { } // otherwise, it probably belongs to this dataTypeManager, but it could a // leftover after an undo. So make sure it really is there. - if (dataType instanceof DataTypeDB) { - DataTypeDB dtDb = (DataTypeDB) dataType; + if (dataType instanceof DataTypeDB dtDb) { return dtCache.get(dtDb.getKey()) == dataType && !dtDb.isDeleted(); } return builtIn2IdMap.containsKey(dataType); @@ -2599,46 +2719,37 @@ abstract public class DataTypeManagerDB implements DataTypeManager { } DataType newDataType = null; - if (dt instanceof Array) { - Array array = (Array) dt; + if (dt instanceof Array array) { newDataType = createArray(array.getDataType(), array.getNumElements(), array.getElementLength(), cat, handler); } - else if (dt instanceof Pointer) { - Pointer ptr = (Pointer) dt; + else if (dt instanceof Pointer ptr) { int len = ptr.hasLanguageDependantLength() ? -1 : ptr.getLength(); newDataType = createPointer(ptr.getDataType(), cat, (byte) len, handler); } - else if (dt instanceof BuiltInDataType) { - BuiltInDataType builtInDataType = (BuiltInDataType) dt; + else if (dt instanceof BuiltInDataType builtInDataType) { newDataType = createBuiltIn(builtInDataType, cat); } - else if (dt instanceof StructureInternal) { - StructureInternal structure = (StructureInternal) dt; + else if (dt instanceof StructureInternal structure) { newDataType = createStructure(structure, name, cat, sourceArchiveIdValue, id.getValue()); } - else if (dt instanceof TypeDef) { - TypeDef typedef = (TypeDef) dt; + else if (dt instanceof TypeDef typedef) { newDataType = createTypeDef(typedef, name, cat, sourceArchiveIdValue, id.getValue()); } - else if (dt instanceof UnionInternal) { - UnionInternal union = (UnionInternal) dt; + else if (dt instanceof UnionInternal union) { newDataType = createUnion(union, name, cat, sourceArchiveIdValue, id.getValue()); } - else if (dt instanceof Enum) { - Enum enumm = (Enum) dt; + else if (dt instanceof Enum enumm) { newDataType = createEnum(enumm, name, cat, sourceArchiveIdValue, id.getValue()); } - else if (dt instanceof FunctionDefinition) { - FunctionDefinition funDef = (FunctionDefinition) dt; + else if (dt instanceof FunctionDefinition funDef) { newDataType = createFunctionDefinition(funDef, name, cat, sourceArchiveIdValue, id.getValue()); } - else if (dt instanceof MissingBuiltInDataType) { - MissingBuiltInDataType missingBuiltInDataType = (MissingBuiltInDataType) dt; + else if (dt instanceof MissingBuiltInDataType missingBuiltInDataType) { newDataType = createMissingBuiltIn(missingBuiltInDataType, cat); } else { @@ -3391,8 +3502,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager { @Override public Set getDataTypesContaining(DataType dataType) { - if (dataType instanceof DataTypeDB) { - DataTypeDB dataTypeDb = (DataTypeDB) dataType; + if (dataType instanceof DataTypeDB dataTypeDb) { if (dataTypeDb.getDataTypeManager() != this) { return Set.of(); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeUtilities.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeUtilities.java index 4010544b5b..1e5f2ab47e 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeUtilities.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeUtilities.java @@ -205,7 +205,7 @@ public class DataTypeUtilities { } /** - * Returns true if the two dataTypes have the same sourceArchive and the same UniversalID OR are + * Returns true if two dataTypes have the same sourceArchive and the same UniversalID OR are * equivalent * * @param dataType1 first data type (if invoked by DB object or manager, this argument must @@ -223,6 +223,79 @@ public class DataTypeUtilities { return dataType1.isEquivalent(dataType2); } + /** + * Determine if two dataTypes are the same kind of datatype without considering naming or + * component makeup. The use of Typedefs is ignored and stripped away for comparison. + * This method also ignores details about most built-in types, pointers and arrays + * (e.g., number of elements or size). Implementations of the following abstract classes + * will be treated as the same kind as another datatype which extends the same abstract + * class: + *

+ * Other uses of {@link BuiltInDataType} must match the specific implementation class. + * @param dataType1 first data type + * @param dataType2 second data type + * @return true if the two dataTypes are the same basic kind else false + */ + public static boolean isSameKindDataType(DataType dataType1, DataType dataType2) { + + while (true) { + if (dataType1 == dataType2) { + return true; + } + + // Ignore the use of typedefs - strip away + if (dataType1 instanceof TypeDef td1) { + dataType1 = td1.getBaseDataType(); + } + if (dataType2 instanceof TypeDef td2) { + dataType2 = td2.getBaseDataType(); + } + + if (dataType1 instanceof Pointer p1 && dataType2 instanceof Pointer p2) { + dataType1 = p1.getDataType(); + dataType2 = p2.getDataType(); + } + else if (dataType2 instanceof Array a1 && dataType2 instanceof Array a2) { + dataType1 = a1.getDataType(); + dataType2 = a2.getDataType(); + } + else if (dataType1 instanceof Enum) { + return dataType2 instanceof Enum; + } + else if (dataType1 instanceof Structure) { + return dataType2 instanceof Structure; + } + else if (dataType1 instanceof Union) { + return dataType2 instanceof Union; + } + else if (dataType1 instanceof BuiltInDataType dt1) { + return isSameKindBuiltInDataType(dt1, dataType2); + } + else { + return false; + } + } + } + + private static boolean isSameKindBuiltInDataType(BuiltInDataType dataType1, + DataType dataType2) { + if (dataType1 instanceof BuiltIn) { + // Same kind if both types share a common BuiltIn implementation + Class baseClass = dataType1.getClass().getSuperclass(); + Class superClass; + while ((superClass = baseClass.getSuperclass()) != BuiltIn.class) { + baseClass = superClass; + } + return baseClass.isAssignableFrom(dataType2.getClass()); + } + // Ensure built-in implementation class is the same + return dataType1.getClass().equals(dataType2.getClass()); + } + /** * Get the name of a data type with all conflict naming patterns removed. * diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SourceArchiveUpgradeMap.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SourceArchiveUpgradeMap.java index 179dbf3219..3b59b71c03 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SourceArchiveUpgradeMap.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SourceArchiveUpgradeMap.java @@ -90,54 +90,54 @@ public class SourceArchiveUpgradeMap { return new String[] { "short", "int", "long", "longlong", "wchar_t", "bool" }; } -} + private static class SourceArchiveImpl implements SourceArchive { -class SourceArchiveImpl implements SourceArchive { + private final UniversalID id; + private final String archiveName; - private final UniversalID id; - private final String archiveName; + public SourceArchiveImpl(UniversalID id, String archiveName) { + this.id = id; + this.archiveName = archiveName; + } - public SourceArchiveImpl(UniversalID id, String archiveName) { - this.id = id; - this.archiveName = archiveName; - } + public SourceArchiveImpl() { + id = DataTypeManager.LOCAL_ARCHIVE_UNIVERSAL_ID; + archiveName = ""; + } - public SourceArchiveImpl() { - id = DataTypeManager.LOCAL_ARCHIVE_UNIVERSAL_ID; - archiveName = ""; - } + public ArchiveType getArchiveType() { + return ArchiveType.FILE; + } - public ArchiveType getArchiveType() { - return ArchiveType.FILE; - } + public String getDomainFileID() { + return null; + } - public String getDomainFileID() { - return null; - } + public long getLastSyncTime() { + return 0; + } - public long getLastSyncTime() { - return 0; - } + public String getName() { + return archiveName; + } - public String getName() { - return archiveName; - } + public UniversalID getSourceArchiveID() { + return id; + } - public UniversalID getSourceArchiveID() { - return id; - } + public boolean isDirty() { + return false; + } - public boolean isDirty() { - return false; - } + public void setDirtyFlag(boolean dirty) { + } - public void setDirtyFlag(boolean dirty) { - } + public void setLastSyncTime(long time) { + } - public void setLastSyncTime(long time) { - } + public void setName(String name) { + } - public void setName(String name) { } }