GP-3632 revised datatype resolve with improved conflict resolution. Added standard DataType name comparators with improved sort. Corrected dataTypeReplaced handling for pointers and arrays to avoid type duplication.

This commit is contained in:
ghidra1 2024-01-20 11:05:28 -05:00
parent e17a03e2d1
commit c15fd0e594
57 changed files with 1730 additions and 431 deletions

View file

@ -193,9 +193,8 @@ public class DBTraceDataTypeManager extends ProgramBasedDataTypeManagerDB
} }
@Override @Override
protected void deleteDataTypeIDs(LinkedList<Long> deletedIds, TaskMonitor monitor) protected void deleteDataTypeIDs(LinkedList<Long> deletedIds) {
throws CancelledException { trace.getCodeManager().clearData(deletedIds, TaskMonitor.DUMMY);
trace.getCodeManager().clearData(deletedIds, monitor);
trace.getSymbolManager().invalidateCache(false); trace.getSymbolManager().invalidateCache(false);
} }

View file

@ -22,10 +22,8 @@ import javax.swing.tree.TreePath;
import ghidra.app.plugin.core.datamgr.archive.BuiltInSourceArchive; import ghidra.app.plugin.core.datamgr.archive.BuiltInSourceArchive;
import ghidra.app.plugin.core.datamgr.archive.DefaultDataTypeArchiveService; import ghidra.app.plugin.core.datamgr.archive.DefaultDataTypeArchiveService;
import ghidra.app.plugin.core.datamgr.util.DataTypeComparator;
import ghidra.app.services.DataTypeManagerService; import ghidra.app.services.DataTypeManagerService;
import ghidra.program.model.data.DataType; import ghidra.program.model.data.*;
import ghidra.program.model.data.DataTypeManagerChangeListener;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
// FIXME!! TESTING // FIXME!! TESTING
@ -83,9 +81,8 @@ public class DefaultDataTypeManagerService extends DefaultDataTypeArchiveService
public List<DataType> getSortedDataTypeList() { public List<DataType> getSortedDataTypeList() {
List<DataType> dataTypes = List<DataType> dataTypes =
builtInDataTypesManager.getDataTypes(BuiltInSourceArchive.INSTANCE); builtInDataTypesManager.getDataTypes(BuiltInSourceArchive.INSTANCE);
dataTypes.sort(new DataTypeComparator()); dataTypes.sort(DataTypeComparator.INSTANCE);
return dataTypes; return dataTypes;
// throw new UnsupportedOperationException();
} }
@Override @Override

View file

@ -19,7 +19,6 @@ import java.util.*;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import ghidra.app.plugin.core.datamgr.util.DataTypeComparator;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.util.task.*; import ghidra.util.task.*;
@ -32,7 +31,6 @@ import ghidra.util.task.*;
public class DataTypeIndexer { public class DataTypeIndexer {
private List<DataTypeManager> dataTypeManagers = new ArrayList<>(); private List<DataTypeManager> dataTypeManagers = new ArrayList<>();
private List<DataType> dataTypeList = Collections.emptyList(); private List<DataType> dataTypeList = Collections.emptyList();
private Comparator<DataType> dataTypeComparator = new DataTypeComparator();
private DataTypeIndexUpdateListener listener = new DataTypeIndexUpdateListener(); private DataTypeIndexUpdateListener listener = new DataTypeIndexUpdateListener();
private volatile boolean isStale = true; private volatile boolean isStale = true;
@ -59,7 +57,8 @@ public class DataTypeIndexer {
/** /**
* Returns a sorted list of the data types open in the current tool. The sorting of the list * Returns a sorted list of the data types open in the current tool. The sorting of the list
* is done using the {@link DataTypeComparator}. * is done using the {@link DataTypeComparator} whose primary sort is based upon the
* {@link DataTypeNameComparator}.
* *
* @return a sorted list of the data types open in the current tool. * @return a sorted list of the data types open in the current tool.
*/ */
@ -128,16 +127,13 @@ public class DataTypeIndexer {
monitor.initialize(dataTypeManagers.size()); monitor.initialize(dataTypeManagers.size());
monitor.setMessage("Preparing to index data types..."); monitor.setMessage("Preparing to index data types...");
Iterator<DataTypeManager> iterator = dataTypeManagers.iterator(); for (DataTypeManager dataTypeManager : dataTypeManagers) {
while (iterator.hasNext()) {
DataTypeManager dataTypeManager = iterator.next();
monitor.setMessage("Searching " + dataTypeManager.getName()); monitor.setMessage("Searching " + dataTypeManager.getName());
dataTypeManager.getAllDataTypes(list); dataTypeManager.getAllDataTypes(list);
monitor.incrementProgress(1); monitor.incrementProgress(1);
} }
Collections.sort(list, dataTypeComparator); Collections.sort(list, DataTypeComparator.INSTANCE);
} }
List<DataType> getList() { List<DataType> getList() {

View file

@ -50,8 +50,9 @@ public class DataTypeNode extends DataTypeTreeNode {
@Override @Override
public int compareTo(GTreeNode node) { public int compareTo(GTreeNode node) {
if (node instanceof DataTypeNode) { if (node instanceof DataTypeNode other) {
return super.compareTo(node); return DataTypeNameComparator.INSTANCE.compare(dataType.getName(),
other.dataType.getName());
} }
return 1; // DataTypeNodes always come after ****everything else**** return 1; // DataTypeNodes always come after ****everything else****

View file

@ -1,52 +0,0 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr.util;
import ghidra.program.model.data.DataType;
import java.util.Comparator;
public class DataTypeComparator implements Comparator<DataType> {
public int compare(DataType dt1, DataType dt2) {
String name1 = dt1.getName();
String name2 = dt2.getName();
// TODO: should built-ins always come first in the list? (in case we have an 'a' named archive?)
// if the names are the same, then sort by the path
if ( name1.equalsIgnoreCase( name2 ) ) {
if ( !name1.equals( name2 ) ) {
// let equivalent names be sorted by case ('-' for lower-case first)
return -name1.compareTo( name2 );
}
String dtmName1 = dt1.getDataTypeManager().getName();
String dtmName2 = dt2.getDataTypeManager().getName();
// if they have the same name, and are in the same DTM, then compare paths
if ( dtmName1.equalsIgnoreCase( dtmName2 ) ) {
return dt1.getPathName().compareToIgnoreCase( dt2.getPathName() );
}
return dtmName1.compareToIgnoreCase( dtmName2 );
}
return name1.compareToIgnoreCase( name2 );
}
}

View file

@ -17,7 +17,7 @@ package ghidra.app.services;
import java.util.List; import java.util.List;
import ghidra.program.model.data.DataType; import ghidra.program.model.data.*;
/** /**
* Simplified datatype service interface to provide query capabilities * Simplified datatype service interface to provide query capabilities
@ -28,7 +28,9 @@ public interface DataTypeQueryService {
/** /**
* Gets the sorted list of all datatypes known by this service via it's owned DataTypeManagers. * Gets the sorted list of all datatypes known by this service via it's owned DataTypeManagers.
* This method can be called frequently, as the underlying data is indexed and only updated * This method can be called frequently, as the underlying data is indexed and only updated
* as changes are made. * as changes are made. The sorting of the list is done using the {@link DataTypeComparator}
* whose primary sort is based upon the {@link DataTypeNameComparator}.
*
* @return the sorted list of known data types. * @return the sorted list of known data types.
*/ */
public List<DataType> getSortedDataTypeList(); public List<DataType> getSortedDataTypeList();

View file

@ -27,8 +27,7 @@ import org.junit.Test;
import generic.test.ConcurrentTestExceptionHandler; import generic.test.ConcurrentTestExceptionHandler;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
public class StructureEditorUnlockedCellEdit2Test public class StructureEditorUnlockedCellEdit2Test extends AbstractStructureEditorTest {
extends AbstractStructureEditorTest {
@Test @Test
public void testF2EditKey() throws Exception { public void testF2EditKey() throws Exception {
@ -764,7 +763,7 @@ public class StructureEditorUnlockedCellEdit2Test
enter(); enter();
assertIsEditingField(rowNum, colNum); assertIsEditingField(rowNum, colNum);
assertEquals("simpleStructure doesn't fit within 4 bytes, need 29 bytes", assertEquals("simpleStructure doesn't fit within 4 bytes, need 12 bytes",
model.getStatus()); model.getStatus());
escape(); escape();
@ -778,9 +777,9 @@ public class StructureEditorUnlockedCellEdit2Test
DataType newDt = getDataType(22); DataType newDt = getDataType(22);
assertEquals("simpleStructure", newDt.getDisplayName()); assertEquals("simpleStructure", newDt.getDisplayName());
assertEquals(29, newDt.getLength()); assertEquals(12, newDt.getLength());
assertEquals(29, getLength(22)); assertEquals(12, getLength(22));
assertEquals(350, model.getLength()); assertEquals(333, model.getLength());
} }
@Override @Override

View file

@ -304,8 +304,8 @@ public class UnionEditorCellEditTest extends AbstractUnionEditorTest {
init(simpleUnion, pgmBbCat, false); init(simpleUnion, pgmBbCat, false);
startTransaction("addExternal"); startTransaction("addExternal");
ExternalLocation extLoc = program.getExternalManager().addExtFunction(Library.UNKNOWN, ExternalLocation extLoc = program.getExternalManager()
"extLabel", null, SourceType.USER_DEFINED); .addExtFunction(Library.UNKNOWN, "extLabel", null, SourceType.USER_DEFINED);
Function function = extLoc.createFunction(); Function function = extLoc.createFunction();
endTransaction(true); endTransaction(true);
@ -726,9 +726,9 @@ public class UnionEditorCellEditTest extends AbstractUnionEditorTest {
assertNotEditingField(); assertNotEditingField();
DataType newDt = getDataType(rowNum); DataType newDt = getDataType(rowNum);
assertEquals("simpleStructure", newDt.getName()); assertEquals("simpleStructure", newDt.getName());
assertEquals(29, newDt.getLength()); assertEquals(12, newDt.getLength());
assertEquals(29, getLength(rowNum)); assertEquals(12, getLength(rowNum));
assertEquals(29, model.getLength()); assertEquals(12, model.getLength());
} }
@Test @Test

View file

@ -60,7 +60,7 @@ public class FunctionSignatureParserTest extends AbstractGhidraHeadedIntegration
// with Tool-based service. // with Tool-based service.
dtList = new ArrayList<>(super.getSortedDataTypeList()); dtList = new ArrayList<>(super.getSortedDataTypeList());
program.getDataTypeManager().getAllDataTypes(dtList); program.getDataTypeManager().getAllDataTypes(dtList);
Collections.sort(dtList, new NameComparator()); Collections.sort(dtList, DataTypeComparator.INSTANCE);
return dtList; return dtList;
} }
@ -83,17 +83,6 @@ public class FunctionSignatureParserTest extends AbstractGhidraHeadedIntegration
parser = new FunctionSignatureParser(program.getDataTypeManager(), service); parser = new FunctionSignatureParser(program.getDataTypeManager(), service);
} }
private class NameComparator implements Comparator<DataType> {
@Override
public int compare(DataType d1, DataType d2) {
int c = d1.getName().compareTo(d2.getName());
if (c == 0) {
return d1.getCategoryPath().compareTo(d2.getCategoryPath());
}
return c;
}
}
@Test @Test
public void testSubstitute() { public void testSubstitute() {
assertEquals("barxxxbar", parser.substitute("barfoobar", "foo", "xxx")); assertEquals("barxxxbar", parser.substitute("barfoobar", "foo", "xxx"));
@ -391,16 +380,16 @@ public class FunctionSignatureParserTest extends AbstractGhidraHeadedIntegration
FunctionSignature f = fun("int", "Bob"); FunctionSignature f = fun("int", "Bob");
FunctionDefinitionDataType dt = FunctionDefinitionDataType dt =
parser.parse(f, "unsigned long[3] Foo(unsigned long long *, signed int[3], StructA*)"); parser.parse(f, "unsigned long[3] Foo(unsigned long long *, signed int[3], StructA*)");
assertTrue((new ArrayDataType(UnsignedLongDataType.dataType, 3, -1)).isEquivalent( assertTrue((new ArrayDataType(UnsignedLongDataType.dataType, 3, -1))
dt.getReturnType())); .isEquivalent(dt.getReturnType()));
assertEquals("Foo", dt.getName()); assertEquals("Foo", dt.getName());
ParameterDefinition[] args = dt.getArguments(); ParameterDefinition[] args = dt.getArguments();
assertEquals(3, args.length); assertEquals(3, args.length);
assertTrue((new PointerDataType(UnsignedLongLongDataType.dataType)).isEquivalent( assertTrue((new PointerDataType(UnsignedLongLongDataType.dataType))
args[0].getDataType())); .isEquivalent(args[0].getDataType()));
assertEquals("", args[0].getName()); assertEquals("", args[0].getName());
assertTrue((new ArrayDataType(IntegerDataType.dataType, 3, -1)).isEquivalent( assertTrue((new ArrayDataType(IntegerDataType.dataType, 3, -1))
args[1].getDataType())); .isEquivalent(args[1].getDataType()));
assertEquals("", args[1].getName()); assertEquals("", args[1].getName());
assertTrue(args[2].getDataType() instanceof Pointer); assertTrue(args[2].getDataType() instanceof Pointer);
assertEquals("", args[2].getName()); assertEquals("", args[2].getName());
@ -430,11 +419,11 @@ public class FunctionSignatureParserTest extends AbstractGhidraHeadedIntegration
"unsigned long[3] Bob(unsigned long long *foo, signed int[3] bar, StructA *s)"); "unsigned long[3] Bob(unsigned long long *foo, signed int[3] bar, StructA *s)");
ParameterDefinition[] args = dt.getArguments(); ParameterDefinition[] args = dt.getArguments();
assertEquals(3, args.length); assertEquals(3, args.length);
assertTrue((new PointerDataType(UnsignedLongLongDataType.dataType)).isEquivalent( assertTrue((new PointerDataType(UnsignedLongLongDataType.dataType))
args[0].getDataType())); .isEquivalent(args[0].getDataType()));
assertEquals("foo", args[0].getName()); assertEquals("foo", args[0].getName());
assertTrue((new ArrayDataType(IntegerDataType.dataType, 3, -1)).isEquivalent( assertTrue((new ArrayDataType(IntegerDataType.dataType, 3, -1))
args[1].getDataType())); .isEquivalent(args[1].getDataType()));
assertEquals("bar", args[1].getName()); assertEquals("bar", args[1].getName());
assertTrue(args[2].getDataType() instanceof Pointer); assertTrue(args[2].getDataType() instanceof Pointer);
assertEquals("s", args[2].getName()); assertEquals("s", args[2].getName());

View file

@ -402,12 +402,7 @@ public class CategoryTest extends AbstractGhidraHeadedIntegrationTest {
assertTrue(dts[i].isEquivalent(newdts[i])); assertTrue(dts[i].isEquivalent(newdts[i]));
} }
DataType[] d = s.getDataTypes(); DataType[] d = s.getDataTypes();
Arrays.sort(d, new Comparator<DataType>() { Arrays.sort(d, DataTypeComparator.INSTANCE);
@Override
public int compare(DataType o1, DataType o2) {
return o1.getName().compareTo(o2.getName());
}
});
assertEquals(dts.length, d.length); assertEquals(dts.length, d.length);
assertTrue(newdts[0] == d[0]); assertTrue(newdts[0] == d[0]);
} }
@ -852,7 +847,8 @@ public class CategoryTest extends AbstractGhidraHeadedIntegrationTest {
} }
@Override @Override
public void categoryRenamed(DataTypeManager dtm, CategoryPath oldPath, CategoryPath newPath) { public void categoryRenamed(DataTypeManager dtm, CategoryPath oldPath,
CategoryPath newPath) {
events.add(new Event("Cat Renamed", null, newPath, oldPath.getName(), null)); events.add(new Event("Cat Renamed", null, newPath, oldPath.getName(), null));
} }
@ -877,12 +873,13 @@ public class CategoryTest extends AbstractGhidraHeadedIntegrationTest {
@Override @Override
public void dataTypeRemoved(DataTypeManager dtm, DataTypePath path) { public void dataTypeRemoved(DataTypeManager dtm, DataTypePath path) {
events.add(new Event("DT Removed", path.getCategoryPath(), null, events.add(new Event("DT Removed", path.getCategoryPath(), null, path.getDataTypeName(),
path.getDataTypeName(), null)); null));
} }
@Override @Override
public void dataTypeRenamed(DataTypeManager dtm, DataTypePath oldPath, DataTypePath newPath) { public void dataTypeRenamed(DataTypeManager dtm, DataTypePath oldPath,
DataTypePath newPath) {
DataType dataType = dtm.getDataType(newPath); DataType dataType = dtm.getDataType(newPath);
events.add(new Event("DT Renamed", null, null, oldPath.getDataTypeName(), dataType)); events.add(new Event("DT Renamed", null, null, oldPath.getDataTypeName(), dataType));
} }

View file

@ -17,6 +17,8 @@ package ghidra.program.database.data;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.util.ArrayList;
import org.junit.*; import org.junit.*;
import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramBuilder;
@ -26,6 +28,8 @@ import ghidra.program.model.data.DataTypeConflictHandler.ConflictResult;
import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.util.UniversalID; import ghidra.util.UniversalID;
import ghidra.util.UniversalIdGenerator; import ghidra.util.UniversalIdGenerator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/** /**
* Tests for the {@link DataTypeConflictHandler conflict handler} stuff. * Tests for the {@link DataTypeConflictHandler conflict handler} stuff.
@ -523,17 +527,15 @@ public class ConflictHandlerTest extends AbstractGhidraHeadedIntegrationTest {
EnumDataType e = new EnumDataType(subc.getCategoryPath(), "Enum", 2); EnumDataType e = new EnumDataType(subc.getCategoryPath(), "Enum", 2);
DataType resolvedEnum = DataType resolvedEnum = dtm.resolve(e,
dtm.resolve(e, DataTypeConflictHandler.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER);
DataTypeConflictHandler.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER);
assertTrue(e.isEquivalent(resolvedEnum)); assertTrue(e.isEquivalent(resolvedEnum));
assertEquals("/subc/Enum", resolvedEnum.getPathName()); assertEquals("/subc/Enum", resolvedEnum.getPathName());
e.add("xyz", 1); e.add("xyz", 1);
resolvedEnum = resolvedEnum = dtm.resolve(e,
dtm.resolve(e, DataTypeConflictHandler.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER);
DataTypeConflictHandler.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER);
assertTrue(e.isEquivalent(resolvedEnum)); assertTrue(e.isEquivalent(resolvedEnum));
assertEquals("/subc/Enum.conflict", resolvedEnum.getPathName()); assertEquals("/subc/Enum.conflict", resolvedEnum.getPathName());
} }
@ -597,9 +599,8 @@ public class ConflictHandlerTest extends AbstractGhidraHeadedIntegrationTest {
@Test @Test
public void testResolveArrayConflict() { public void testResolveArrayConflict() {
DataType array1 = DataType array1 = new ArrayDataType(
new ArrayDataType(new TypedefDataType("size_t", UnsignedIntegerDataType.dataType), 2, new TypedefDataType("size_t", UnsignedIntegerDataType.dataType), 2, -1);
-1);
DataType array2 = DataType array2 =
new ArrayDataType(new TypedefDataType("size_t", IntegerDataType.dataType), 2, -1); new ArrayDataType(new TypedefDataType("size_t", IntegerDataType.dataType), 2, -1);
@ -616,6 +617,203 @@ public class ConflictHandlerTest extends AbstractGhidraHeadedIntegrationTest {
assertTrue(array2resolvedC == array2resolvedB); assertTrue(array2resolvedC == array2resolvedB);
} }
@Test
public void testResolveWithCircularDependency() {
Structure struct1 = new StructureDataType("s1", 0, dataMgr);
struct1.setPackingEnabled(true);
Structure struct2 = new StructureDataType("s2", 0, dataMgr);
struct2.setPackingEnabled(true);
struct1.add(new PointerDataType(struct2));
struct2.add(new PointerDataType(struct1));
Structure struct1a = (Structure) dataMgr.resolve(struct1,
DataTypeConflictHandler.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER);
Structure struct1b = (Structure) dataMgr.resolve(struct1,
DataTypeConflictHandler.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER);
assertTrue(struct1a == struct1b);
assertEquals(5, dataMgr.getDataTypeRecordCount());
}
@Test
public void testComplexResolveWithConflictReplacement() {
// FIXME: Add typedef to struct1 pointer used as struct2 component
// FIXME: Add add array of struct1 pointers used as struct1 component
Structure struct1a = new StructureDataType("s1", 0, dataMgr);
struct1a.setPackingEnabled(true);
Structure struct2a = new StructureDataType("s2", 0, dataMgr);
struct2a.add(ByteDataType.dataType);
struct2a.add(new PointerDataType(struct1a, dataMgr));
struct1a.add(new PointerDataType(struct1a, dataMgr));
struct1a.add(new PointerDataType(struct2a, dataMgr));
struct1a.add(new ArrayDataType(struct2a, 2, -1, dataMgr));
struct1a.add(new TypedefDataType("S2TD", struct2a));
struct1a = (Structure) dataMgr.resolve(struct1a,
DataTypeConflictHandler.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER);
Structure struct1b = new StructureDataType("s1", 0, dataMgr);
struct1b.setPackingEnabled(true);
Structure struct2b = new StructureDataType("s2", 0, dataMgr); // not-yet-defined - will get replaced by struct2a
struct1b.add(new PointerDataType(struct1b, dataMgr));
struct1b.add(new PointerDataType(struct2b, dataMgr));
struct1b.add(new ArrayDataType(struct2b, 2, -1, dataMgr));
struct1b.add(new TypedefDataType("S2TD", struct2b));
struct1b = (Structure) dataMgr.resolve(struct1b,
DataTypeConflictHandler.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER);
assertTrue(struct1a == struct1b);
assertNoConflict("s1");
assertNoConflict("s2");
}
@Test
public void testComplexResolveWithConflictReplacement2() {
// FIXME: Add typedef to struct1 pointer used as struct2 component
// FIXME: Add add array of struct1 pointers used as struct1 component
Structure struct1a = new StructureDataType("s1", 0, dataMgr);
struct1a.setPackingEnabled(true);
Structure struct2a = new StructureDataType("s2", 0, dataMgr); // not-yet-defined - will get replaced by struct2b
struct1a.add(new PointerDataType(struct1a, dataMgr));
struct1a.add(new PointerDataType(struct2a, dataMgr));
struct1a.add(new ArrayDataType(struct2a, 2, -1, dataMgr));
struct1a.add(new TypedefDataType("S2TD", struct2a));
struct1a = (Structure) dataMgr.resolve(struct1a,
DataTypeConflictHandler.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER);
System.out.println("-- After First Resolve --");
System.out.println(struct1a);
Pointer ptr2a = (Pointer) struct1a.getComponent(1).getDataType();
struct2a = (Structure) ptr2a.getDataType();
System.out.println(struct2a);
Structure struct1b = new StructureDataType("s1", 0, dataMgr);
struct1b.setPackingEnabled(true);
Structure struct2b = new StructureDataType("s2", 0, dataMgr);
struct2b.setPackingEnabled(true);
struct2b.add(ByteDataType.dataType);
struct2b.add(new PointerDataType(struct1b, dataMgr));
struct1b.add(new PointerDataType(struct1b, dataMgr));
struct1b.add(new PointerDataType(struct2b, dataMgr));
struct1b.add(new ArrayDataType(struct2b, 2, -1, dataMgr));
struct1b.add(new TypedefDataType("S2TD", struct2b));
struct1b = (Structure) dataMgr.resolve(struct1b,
DataTypeConflictHandler.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER);
System.out.println("-- After Second Resolve (original instances - s2 content replaced) --");
System.out.println(struct1a);
System.out.println(struct2a);
System.out.println("-- After Second Resolve (bad s1.conflict) --");
System.out.println(struct1b);
Pointer ptr2b = (Pointer) struct1b.getComponent(1).getDataType();
struct2b = (Structure) ptr2b.getDataType();
System.out.println(struct2b);
// struct1a should get eliminated and replaced by struct1b
assertTrue(struct1a == struct1b);
assertNoConflict("s1");
assertNoConflict("s2");
}
@Test
public void testDedupeAllConflicts() throws CancelledException {
Structure struct1a = new StructureDataType("s1", 0, dataMgr);
struct1a.setPackingEnabled(true);
struct1a.add(ByteDataType.dataType);
Structure s1 = (Structure) dataMgr.resolve(struct1a, null);
struct1a.add(ByteDataType.dataType);
Structure s2 = (Structure) dataMgr.resolve(struct1a, null);
struct1a.add(ByteDataType.dataType);
Structure s3 = (Structure) dataMgr.resolve(struct1a, null);
// force all conflicts to become equivalent
s1.deleteAll();
s2.deleteAll();
s3.deleteAll();
ArrayList<DataType> list = new ArrayList<>();
dataMgr.findDataTypes("s1", list);
assertEquals(3, list.size());
dataMgr.dedupeAllConflicts(TaskMonitor.DUMMY);
list.clear();
dataMgr.findDataTypes("s1", list);
assertEquals(1, list.size());
}
@Test
public void testDedupeConflicts() {
Structure struct1a = new StructureDataType("s1", 0, dataMgr);
struct1a.setPackingEnabled(true);
struct1a.add(ByteDataType.dataType);
Structure s1 = (Structure) dataMgr.resolve(struct1a, null);
struct1a.add(ByteDataType.dataType);
Structure s2 = (Structure) dataMgr.resolve(struct1a, null);
struct1a.add(ByteDataType.dataType);
Structure s3 = (Structure) dataMgr.resolve(struct1a, null);
// force two of the conflicts to become equivalent
s1.deleteAll();
// leave s2 unchanged
s3.deleteAll();
ArrayList<DataType> list = new ArrayList<>();
dataMgr.findDataTypes("s1", list);
assertEquals(3, list.size());
dataMgr.dedupeConflicts(s3);
assertTrue(s3.isDeleted());
list.clear();
dataMgr.findDataTypes("s1", list);
assertEquals(2, list.size());
}
private void assertNoConflict(String dtName) {
DataType dt1 = dataMgr.getDataType("/" + dtName);
assertNotNull("DataType not found: " + dtName, dt1);
DataType dt2 = dataMgr.getDataType("/" + dtName + ".conflict");
if (dt2 != null) {
System.out.println("Original type: " + dt1.toString());
System.out.println("Conflict type: " + dt2.toString());
if (dt1.isEquivalent(dt2)) {
System.out.println(dtName + " - TYPES ARE EQUIVALENT");
}
fail("DataType conflict found: " + dt2.getPathName());
}
}
private static class DummySourceArchive implements SourceArchive { private static class DummySourceArchive implements SourceArchive {
private final UniversalID id; private final UniversalID id;
@ -626,36 +824,45 @@ public class ConflictHandlerTest extends AbstractGhidraHeadedIntegrationTest {
this.archiveName = archiveName; this.archiveName = archiveName;
} }
@Override
public ArchiveType getArchiveType() { public ArchiveType getArchiveType() {
return ArchiveType.FILE; return ArchiveType.FILE;
} }
@Override
public String getDomainFileID() { public String getDomainFileID() {
return null; return null;
} }
@Override
public long getLastSyncTime() { public long getLastSyncTime() {
return 0; return 0;
} }
@Override
public String getName() { public String getName() {
return archiveName; return archiveName;
} }
@Override
public UniversalID getSourceArchiveID() { public UniversalID getSourceArchiveID() {
return id; return id;
} }
@Override
public boolean isDirty() { public boolean isDirty() {
return false; return false;
} }
@Override
public void setDirtyFlag(boolean dirty) { public void setDirtyFlag(boolean dirty) {
} }
@Override
public void setLastSyncTime(long time) { public void setLastSyncTime(long time) {
} }
@Override
public void setName(String name) { public void setName(String name) {
} }

View file

@ -21,9 +21,7 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import generic.test.AbstractGTest; import generic.test.AbstractGTest;
import ghidra.program.model.data.DataType; import ghidra.program.model.data.*;
import ghidra.program.model.data.DataTypeComparator;
import ghidra.program.model.data.StubDataType;
import ghidra.util.UniversalIdGenerator; import ghidra.util.UniversalIdGenerator;
public class DataTypeUtilsTest { public class DataTypeUtilsTest {
@ -48,7 +46,7 @@ public class DataTypeUtilsTest {
} }
// sort them how our data will be sorted // sort them how our data will be sorted
Collections.sort(data, new DataTypeComparator()); Collections.sort(data, DataTypeComparator.INSTANCE);
List<DataType> finalData = Collections.unmodifiableList(data); List<DataType> finalData = Collections.unmodifiableList(data);
// a // a

View file

@ -191,7 +191,7 @@ public class ProjectDataTypeManager extends StandAloneDataTypeManager
} }
@Override @Override
protected void deleteDataTypeIDs(LinkedList<Long> deletedIds, TaskMonitor monitor) { protected void deleteDataTypeIDs(LinkedList<Long> deletedIds) {
// do nothing // do nothing
} }

View file

@ -98,7 +98,7 @@ class ArrayDB extends DataTypeDB implements Array {
lock.acquire(); lock.acquire();
try { try {
checkIsValid(); checkIsValid();
if ( displayName == null ) { if (displayName == null) {
displayName = DataTypeUtilities.getDisplayName(this, false); displayName = DataTypeUtilities.getDisplayName(this, false);
} }
return displayName; return displayName;
@ -223,27 +223,45 @@ class ArrayDB extends DataTypeDB implements Array {
} }
@Override @Override
public boolean isEquivalent(DataType dt) { protected boolean isEquivalent(DataType dt, DataTypeConflictHandler handler) {
if (dt == this) { if (dt == this) {
return true; return true;
} }
if (!(dt instanceof Array)) { if (!(dt instanceof Array)) {
return false; return false;
} }
Array array = (Array) dt; Array array = (Array) dt;
if (getNumElements() != array.getNumElements()) { if (getNumElements() != array.getNumElements()) {
return false; return false;
} }
DataType dataType = getDataType(); DataType dataType = getDataType();
if (!dataType.isEquivalent(array.getDataType())) { DataType otherDataType = array.getDataType();
// if they contain datatypes that have same ids, then we are essentially equivalent.
if (DataTypeUtilities.isSameDataType(dataType, otherDataType)) {
return true;
}
if (handler != null) {
handler = handler.getSubsequentHandler();
}
if (!DataTypeDB.isEquivalent(dataType, otherDataType, handler)) {
return false; return false;
} }
if (dataType instanceof Dynamic && getElementLength() != array.getElementLength()) { if (dataType instanceof Dynamic && getElementLength() != array.getElementLength()) {
return false; return false;
} }
return true; return true;
} }
@Override
public boolean isEquivalent(DataType dt) {
return isEquivalent(dt, null);
}
@Override @Override
public void dataTypeReplaced(DataType oldDt, DataType newDt) { public void dataTypeReplaced(DataType oldDt, DataType newDt) {
lock.acquire(); lock.acquire();
@ -255,13 +273,37 @@ class ArrayDB extends DataTypeDB implements Array {
if (oldDt == getDataType()) { if (oldDt == getDataType()) {
int oldElementLength = getElementLength();
int newElementLength =
elementLength = newDt.getLength() < 0 ? oldElementLength : -1;
// check for existing pointer to newDt
ArrayDataType newArray =
new ArrayDataType(newDt, getNumElements(), newElementLength, dataMgr);
DataType existingArray =
dataMgr.getDataType(newDt.getCategoryPath(), newArray.getName());
if (existingArray != null) {
// avoid duplicate array - replace this array with existing one
dataMgr.addDataTypeToReplace(this, existingArray);
return;
}
if (!newDt.getCategoryPath().equals(oldDt.getCategoryPath())) {
// move this pointer to same category as newDt
try {
super.setCategoryPath(newDt.getCategoryPath());
}
catch (DuplicateNameException e) {
throw new RuntimeException(e); // already checked
}
}
oldDt.removeParent(this); oldDt.removeParent(this);
newDt.addParent(this); newDt.addParent(this);
String myOldName = getOldName(); String myOldName = getOldName();
int oldLength = getLength(); int oldLength = getLength();
int oldAlignment = getAlignment(); int oldAlignment = getAlignment();
int oldElementLength = getElementLength();
record.setLongValue(ArrayDBAdapter.ARRAY_DT_ID_COL, dataMgr.getResolvedID(newDt)); record.setLongValue(ArrayDBAdapter.ARRAY_DT_ID_COL, dataMgr.getResolvedID(newDt));
if (newDt instanceof Dynamic || newDt instanceof FactoryDataType) { if (newDt instanceof Dynamic || newDt instanceof FactoryDataType) {
@ -276,7 +318,7 @@ class ArrayDB extends DataTypeDB implements Array {
dataMgr.dbError(e); dataMgr.dbError(e);
} }
refreshName(); refreshName();
if (!getName().equals(myOldName)) { if (!oldDt.getName().equals(newDt.getName())) {
notifyNameChanged(myOldName); notifyNameChanged(myOldName);
} }
if (getLength() != oldLength || oldElementLength != getElementLength()) { if (getLength() != oldLength || oldElementLength != getElementLength()) {

View file

@ -46,8 +46,7 @@ abstract class ArrayDBAdapter {
* @throws CancelledException task cancelled * @throws CancelledException task cancelled
*/ */
static ArrayDBAdapter getAdapter(DBHandle handle, int openMode, String tablePrefix, static ArrayDBAdapter getAdapter(DBHandle handle, int openMode, String tablePrefix,
TaskMonitor monitor) TaskMonitor monitor) throws VersionException, IOException, CancelledException {
throws VersionException, IOException, CancelledException {
if (openMode == DBConstants.CREATE) { if (openMode == DBConstants.CREATE) {
return new ArrayDBAdapterV1(handle, tablePrefix, true); return new ArrayDBAdapterV1(handle, tablePrefix, true);
} }
@ -71,8 +70,8 @@ abstract class ArrayDBAdapter {
} }
private static ArrayDBAdapter upgrade(DBHandle handle, ArrayDBAdapter oldAdapter, private static ArrayDBAdapter upgrade(DBHandle handle, ArrayDBAdapter oldAdapter,
String tablePrefix, String tablePrefix, TaskMonitor monitor)
TaskMonitor monitor) throws VersionException, IOException, CancelledException { throws VersionException, IOException, CancelledException {
DBHandle tmpHandle = new DBHandle(); DBHandle tmpHandle = new DBHandle();
long id = tmpHandle.startTransaction(); long id = tmpHandle.startTransaction();
@ -116,4 +115,10 @@ abstract class ArrayDBAdapter {
abstract Field[] getRecordIdsInCategory(long categoryID) throws IOException; abstract Field[] getRecordIdsInCategory(long categoryID) throws IOException;
/**
* Get the number of array datatype records
* @return total number of composite records
*/
public abstract int getRecordCount();
} }

View file

@ -91,7 +91,8 @@ class ArrayDBAdapterV0 extends ArrayDBAdapter {
DBRecord rec = ArrayDBAdapter.SCHEMA.createRecord(oldRec.getKey()); DBRecord rec = ArrayDBAdapter.SCHEMA.createRecord(oldRec.getKey());
rec.setLongValue(ArrayDBAdapter.ARRAY_DT_ID_COL, oldRec.getLongValue(V0_ARRAY_DT_ID_COL)); rec.setLongValue(ArrayDBAdapter.ARRAY_DT_ID_COL, oldRec.getLongValue(V0_ARRAY_DT_ID_COL));
rec.setIntValue(ArrayDBAdapter.ARRAY_DIM_COL, oldRec.getIntValue(V0_ARRAY_DIM_COL)); rec.setIntValue(ArrayDBAdapter.ARRAY_DIM_COL, oldRec.getIntValue(V0_ARRAY_DIM_COL));
rec.setIntValue(ArrayDBAdapter.ARRAY_ELEMENT_LENGTH_COL, oldRec.getIntValue(V0_ARRAY_ELEMENT_LENGTH_COL)); rec.setIntValue(ArrayDBAdapter.ARRAY_ELEMENT_LENGTH_COL,
oldRec.getIntValue(V0_ARRAY_ELEMENT_LENGTH_COL));
rec.setLongValue(ArrayDBAdapter.ARRAY_CAT_COL, 0); rec.setLongValue(ArrayDBAdapter.ARRAY_CAT_COL, 0);
return rec; return rec;
} }
@ -141,4 +142,9 @@ class ArrayDBAdapterV0 extends ArrayDBAdapter {
return Field.EMPTY_ARRAY; return Field.EMPTY_ARRAY;
} }
@Override
public int getRecordCount() {
return table.getRecordCount();
}
} }

View file

@ -118,4 +118,8 @@ class ArrayDBAdapterV1 extends ArrayDBAdapter {
return table.findRecords(new LongField(categoryID), V1_ARRAY_CAT_COL); return table.findRecords(new LongField(categoryID), V1_ARRAY_CAT_COL);
} }
@Override
public int getRecordCount() {
return table.getRecordCount();
}
} }

View file

@ -52,7 +52,8 @@ public abstract class BuiltinDBAdapter {
* @return new record * @return new record
* @throws IOException if there was a problem accessing the database * @throws IOException if there was a problem accessing the database
*/ */
abstract DBRecord createRecord(String name, String className, long categoryID) throws IOException; abstract DBRecord createRecord(String name, String className, long categoryID)
throws IOException;
/** /**
* Gets the Built-in data type record with the indicated ID. * Gets the Built-in data type record with the indicated ID.
@ -92,4 +93,10 @@ public abstract class BuiltinDBAdapter {
* @throws IOException if IO error occurs * @throws IOException if IO error occurs
*/ */
abstract RecordIterator getRecords() throws IOException; abstract RecordIterator getRecords() throws IOException;
/**
* Get the number of built-in datatype records
* @return total number of composite records
*/
public abstract int getRecordCount();
} }

View file

@ -52,8 +52,7 @@ class BuiltinDBAdapterV0 extends BuiltinDBAdapter {
String tableName = tablePrefix + BUILT_IN_TABLE_NAME; String tableName = tablePrefix + BUILT_IN_TABLE_NAME;
if (create) { if (create) {
table = handle.createTable(tableName, V0_SCHEMA, table = handle.createTable(tableName, V0_SCHEMA, new int[] { V0_BUILT_IN_CAT_COL });
new int[] { V0_BUILT_IN_CAT_COL });
} }
else { else {
table = handle.getTable(tableName); table = handle.getTable(tableName);
@ -87,7 +86,8 @@ class BuiltinDBAdapterV0 extends BuiltinDBAdapter {
} }
@Override @Override
public DBRecord createRecord(String name, String className, long categoryID) throws IOException { public DBRecord createRecord(String name, String className, long categoryID)
throws IOException {
long tableKey = table.getKey(); long tableKey = table.getKey();
if (tableKey <= 100) { if (tableKey <= 100) {
@ -108,4 +108,9 @@ class BuiltinDBAdapterV0 extends BuiltinDBAdapter {
return table.iterator(); return table.iterator();
} }
@Override
public int getRecordCount() {
return table.getRecordCount();
}
} }

View file

@ -49,8 +49,7 @@ abstract class CompositeDBAdapter implements DBRecordAdapter {
CompositeDBAdapterV5V6.V5V6_COMPOSITE_SOURCE_SYNC_TIME_COL; CompositeDBAdapterV5V6.V5V6_COMPOSITE_SOURCE_SYNC_TIME_COL;
static final int COMPOSITE_LAST_CHANGE_TIME_COL = static final int COMPOSITE_LAST_CHANGE_TIME_COL =
CompositeDBAdapterV5V6.V5V6_COMPOSITE_LAST_CHANGE_TIME_COL; CompositeDBAdapterV5V6.V5V6_COMPOSITE_LAST_CHANGE_TIME_COL;
static final int COMPOSITE_PACKING_COL = static final int COMPOSITE_PACKING_COL = CompositeDBAdapterV5V6.V5V6_COMPOSITE_PACK_COL;
CompositeDBAdapterV5V6.V5V6_COMPOSITE_PACK_COL;
static final int COMPOSITE_MIN_ALIGN_COL = CompositeDBAdapterV5V6.V5V6_COMPOSITE_MIN_ALIGN_COL; static final int COMPOSITE_MIN_ALIGN_COL = CompositeDBAdapterV5V6.V5V6_COMPOSITE_MIN_ALIGN_COL;
// Stored Packing and Minimum Alignment values are consistent with CompositeInternal // Stored Packing and Minimum Alignment values are consistent with CompositeInternal
@ -217,6 +216,7 @@ abstract class CompositeDBAdapter implements DBRecordAdapter {
* @return the composite data type record iterator. * @return the composite data type record iterator.
* @throws IOException if the database can't be accessed. * @throws IOException if the database can't be accessed.
*/ */
@Override
public abstract RecordIterator getRecords() throws IOException; public abstract RecordIterator getRecords() throws IOException;
/** /**
@ -272,9 +272,10 @@ abstract class CompositeDBAdapter implements DBRecordAdapter {
throws IOException; throws IOException;
/** /**
* Get the number of composite records * Get the number of composite datatype records
* @return total number of composite records * @return total number of composite records
*/ */
@Override
public abstract int getRecordCount(); public abstract int getRecordCount();
} }

View file

@ -295,8 +295,15 @@ class DataTypeComponentDB implements InternalDataTypeComponent {
return myDt.getClass() == otherDt.getClass(); return myDt.getClass() == otherDt.getClass();
} }
@Override static boolean isEquivalent(DataTypeComponent existingDtc, DataTypeComponent dtc,
public boolean isEquivalent(DataTypeComponent dtc) { DataTypeConflictHandler handler) {
if (existingDtc instanceof DataTypeComponentDB existingDtcDB) {
return existingDtcDB.isEquivalent(dtc, handler);
}
return existingDtc.isEquivalent(dtc);
}
boolean isEquivalent(DataTypeComponent dtc, DataTypeConflictHandler handler) {
DataType myDt = getDataType(); DataType myDt = getDataType();
DataType otherDt = dtc.getDataType(); DataType otherDt = dtc.getDataType();
// SCR #11220 - this may fix the null pointer exception - not sure as it is hard // SCR #11220 - this may fix the null pointer exception - not sure as it is hard
@ -319,7 +326,16 @@ class DataTypeComponentDB implements InternalDataTypeComponent {
return false; return false;
} }
return DataTypeUtilities.isSameOrEquivalentDataType(myDt, otherDt); if (DataTypeUtilities.isSameDataType(myDt, otherDt)) {
return true;
}
return DataTypeDB.isEquivalent(myDt, otherDt, handler);
}
@Override
public boolean isEquivalent(DataTypeComponent dtc) {
return isEquivalent(dtc, null);
} }
@Override @Override
@ -413,8 +429,7 @@ class DataTypeComponentDB implements InternalDataTypeComponent {
// TODO: Need to check field name and throw DuplicateNameException // TODO: Need to check field name and throw DuplicateNameException
// name = checkFieldName(name); // name = checkFieldName(name);
record.setString(ComponentDBAdapter.COMPONENT_FIELD_NAME_COL, name); record.setString(ComponentDBAdapter.COMPONENT_FIELD_NAME_COL, name);
record.setLongValue(ComponentDBAdapter.COMPONENT_DT_ID_COL, record.setLongValue(ComponentDBAdapter.COMPONENT_DT_ID_COL, dataMgr.getResolvedID(dt));
dataMgr.getResolvedID(dt));
record.setString(ComponentDBAdapter.COMPONENT_COMMENT_COL, comment); record.setString(ComponentDBAdapter.COMPONENT_COMMENT_COL, comment);
updateRecord(false); updateRecord(false);
} }

View file

@ -585,4 +585,33 @@ abstract class DataTypeDB extends DatabaseObject implements DataType {
throw new DataTypeEncodeException("Encoding not supported", repr, this); throw new DataTypeEncodeException("Encoding not supported", repr, this);
} }
/**
* Perform equivalence check while resolving the specified dataType. If the specified conflict
* handler under a conflict situation indicates that the existing data type (i.e., this type)
* be used in place of the specified dataType this method will return true.
* @param dataType datatype being resolved
* @param handler resolve conflict handler (if null perform normal {@link #isEquivalent(DataType)}
* @return true if the specified dataType should be considered equivalent to this datatype.
*/
protected abstract boolean isEquivalent(DataType dataType, DataTypeConflictHandler handler);
/**
* If possible, perform equivalence check while resolving the specified dataType if the
* existingDataType is an instance of DataTypeDB. Otherwise, perform a normal
* isEquivalent operation. If the specified conflict
* handler under a conflict situation indicates that the existing data type (i.e., this type)
* be used in place of the specified dataType this method will return true.
* @param existingDataType existing datatype
* @param otherDataType datatype being resolved
* @param handler resolve conflict handler (if null perform normal {@link #isEquivalent(DataType)}
* @return true if the specified dataType should be considered equivalent to this datatype.
*/
static boolean isEquivalent(DataType existingDataType, DataType otherDataType,
DataTypeConflictHandler handler) {
if (existingDataType instanceof DataTypeDB existingDataTypeDB) {
return existingDataTypeDB.isEquivalent(otherDataType, handler);
}
return existingDataType.isEquivalent(otherDataType);
}
} }

View file

@ -28,6 +28,7 @@ import javax.help.UnsupportedOperationException;
import db.*; import db.*;
import db.util.ErrorHandler; import db.util.ErrorHandler;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import generic.stl.Pair;
import ghidra.app.plugin.core.datamgr.archive.BuiltInSourceArchive; import ghidra.app.plugin.core.datamgr.archive.BuiltInSourceArchive;
import ghidra.docking.settings.*; import ghidra.docking.settings.*;
import ghidra.framework.Application; import ghidra.framework.Application;
@ -145,19 +146,21 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
private List<InvalidatedListener> invalidatedListeners = new ArrayList<>(); private List<InvalidatedListener> invalidatedListeners = new ArrayList<>();
protected DataTypeManagerChangeListenerHandler defaultListener = protected DataTypeManagerChangeListenerHandler defaultListener =
new DataTypeManagerChangeListenerHandler(); new DataTypeManagerChangeListenerHandler();
private NameComparator nameComparator = new NameComparator(); private Comparator<DataType> nameComparator = DataTypeComparator.INSTANCE;
private int creatingDataType = 0; private int creatingDataType = 0;
protected UniversalID universalID; protected UniversalID universalID;
private Map<UniversalID, SourceArchive> sourceArchiveMap; private Map<UniversalID, SourceArchive> sourceArchiveMap;
private LinkedList<Long> idsToDelete = new LinkedList<>(); private LinkedList<Long> idsToDelete = new LinkedList<>();
private LinkedList<Pair<DataType, DataType>> typesToReplace = new LinkedList<>();
private List<DataType> favoritesList = new ArrayList<>(); private List<DataType> favoritesList = new ArrayList<>();
private IdsToDataTypeMap idsToDataTypeMap = new IdsToDataTypeMap(); private IdsToDataTypeMap idsToDataTypeMap = new IdsToDataTypeMap();
private ThreadLocal<EquivalenceCache> equivalenceCache = new ThreadLocal<>(); private ThreadLocal<EquivalenceCache> equivalenceCache = new ThreadLocal<>();
private IdentityHashMap<DataType, DataType> resolveCache; private IdentityHashMap<DataType, DataType> resolveCache;
private TreeSet<ResolvePair> resolveQueue; private TreeSet<ResolvePair> resolveQueue; // TODO: is TreeSet really needed?
private LinkedList<DataType> conflictQueue = new LinkedList<>();
private boolean isBulkRemoving; private boolean isBulkRemoving;
@ -971,14 +974,10 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
if (sortedDataTypes == null) { if (sortedDataTypes == null) {
return; return;
} }
// find and remove exact match from sortedDataTypes list
String name = dataTypePath.getDataTypeName(); String name = dataTypePath.getDataTypeName();
DataType compareDataType = new TypedefDataType(name, DataType.DEFAULT); DataType compareDataType =
try { new TypedefDataType(dataTypePath.getCategoryPath(), name, DataType.DEFAULT, this);
compareDataType.setCategoryPath(dataTypePath.getCategoryPath());
}
catch (DuplicateNameException e) {
// will not happen - compareDataType not in dataTypeManager
}
int index = Collections.binarySearch(sortedDataTypes, compareDataType, nameComparator); int index = Collections.binarySearch(sortedDataTypes, compareDataType, nameComparator);
if (index >= 0) { if (index >= 0) {
sortedDataTypes.remove(index); sortedDataTypes.remove(index);
@ -995,6 +994,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
sortedDataTypes.add(index, dataType); sortedDataTypes.add(index, dataType);
} }
else { else {
// NOTE: ideally, this should never happen and may indicate presence of duplicate
sortedDataTypes.set(index, dataType); sortedDataTypes.set(index, dataType);
} }
} }
@ -1236,40 +1236,37 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
} }
resolvedDataType = getCachedResolve(dataType); resolvedDataType = getCachedResolve(dataType);
if (resolvedDataType != null) { if (resolvedDataType == null) {
return resolvedDataType; SourceArchive sourceArchive = dataType.getSourceArchive();
if (sourceArchive != null &&
sourceArchive.getArchiveType() == ArchiveType.BUILT_IN) {
resolvedDataType = resolveBuiltIn(dataType);
}
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 = resolveDataTypeNoSource(dataType);
}
else if (!sourceArchive.getSourceArchiveID().equals(getUniversalID()) &&
sourceArchive.getArchiveType() == ArchiveType.PROGRAM) {
// dataTypes from a different program don't carry over their identity.
resolvedDataType = resolveDataTypeNoSource(dataType);
}
else {
resolvedDataType = resolveDataTypeWithSource(dataType);
}
cacheResolvedDataType(dataType, resolvedDataType);
if (resolvedDataType instanceof DataTypeDB) {
setCachedEquivalence((DataTypeDB) resolvedDataType, dataType);
}
} }
// TODO: delayed pointer-resolve use of "undefined *" could cause unintended
// equivalence match. May need to use an internal reserved type instead.
SourceArchive sourceArchive = dataType.getSourceArchive();
if (sourceArchive != null && sourceArchive.getArchiveType() == ArchiveType.BUILT_IN) {
resolvedDataType = resolveBuiltIn(dataType, currentHandler);
}
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 = 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 = resolveDataTypeNoSource(dataType, currentHandler);
}
else {
resolvedDataType = resolveDataTypeWithSource(dataType, currentHandler);
}
cacheResolvedDataType(dataType, resolvedDataType);
if (resolvedDataType instanceof DataTypeDB) {
setCachedEquivalence((DataTypeDB) resolvedDataType, dataType);
}
return resolvedDataType;
} }
finally { finally {
try { try {
if (isResolveCacheOwner) { if (isResolveCacheOwner) {
flushResolveQueue(true); // may throw exception - incomplete resolve // Process resolve queue and allowed resolvedDataType to be replaced
// during conflict processing
resolvedDataType = processResolveQueue(true, resolvedDataType);
} }
} }
finally { finally {
@ -1280,13 +1277,14 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
lock.release(); lock.release();
} }
} }
return resolvedDataType;
} }
private DataType resolveBuiltIn(DataType dataType, DataTypeConflictHandler handler) { private DataType resolveBuiltIn(DataType dataType) {
if (dataType instanceof Pointer) { if (dataType instanceof Pointer) {
// treat built-in pointers like other datatypes without a source // treat built-in pointers like other datatypes without a source
return resolveDataTypeNoSource(dataType, currentHandler); return resolveDataTypeNoSource(dataType);
} }
// can't do this check now because Pointers from the BuiltinDataTypeManager are // can't do this check now because Pointers from the BuiltinDataTypeManager are
@ -1309,7 +1307,8 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
"Failed to rename conflicting datatype: " + existingDataType.getPathName(), e); "Failed to rename conflicting datatype: " + existingDataType.getPathName(), e);
} }
} }
return createDataType(dataType, dataType.getName(), BuiltInSourceArchive.INSTANCE, handler); return createDataType(dataType, dataType.getName(), BuiltInSourceArchive.INSTANCE,
currentHandler);
} }
private DataType resolveBitFieldDataType(BitFieldDataType bitFieldDataType, private DataType resolveBitFieldDataType(BitFieldDataType bitFieldDataType,
@ -1394,12 +1393,14 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
if (!(dataType instanceof Enum)) { if (!(dataType instanceof Enum)) {
return false; return false;
} }
// TODO: implement doReplaceWith
existingDataType.replaceWith(dataType); existingDataType.replaceWith(dataType);
} }
else if (existingDataType instanceof TypedefDB) { else if (existingDataType instanceof TypedefDB) {
if (!(dataType instanceof TypeDef)) { if (!(dataType instanceof TypeDef)) {
return false; return false;
} }
// TODO: implement doReplaceWith
existingDataType.replaceWith(dataType); existingDataType.replaceWith(dataType);
} }
else { else {
@ -1480,8 +1481,6 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
return category.getDataTypesByBaseName(dataType.getName()); return category.getDataTypesByBaseName(dataType.getName());
} }
// Handle pointers and arrays
DataType existingDataType = category.getDataType(dataType.getName()); DataType existingDataType = category.getDataType(dataType.getName());
DataType baseDataType = DataTypeUtilities.getBaseDataType(dataType); DataType baseDataType = DataTypeUtilities.getBaseDataType(dataType);
@ -1523,13 +1522,15 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
// If the existing Data type is currently being resolved, its isEquivalent // 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 // 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. // to call the isEquivalent on the existing datatype and not the dataType.
if (existingDataType != null && existingDataType.isEquivalent(dataType)) { if (existingDataType != null &&
DataTypeDB.isEquivalent(existingDataType, dataType, currentHandler)) {
return existingDataType; return existingDataType;
} }
List<DataType> relatedByName = findDataTypesSameLocation(dataType); List<DataType> relatedByName = findDataTypesSameLocation(dataType);
for (DataType candidate : relatedByName) { for (DataType candidate : relatedByName) {
if (candidate != existingDataType && candidate.isEquivalent(dataType)) { if (candidate != existingDataType &&
DataTypeDB.isEquivalent(candidate, dataType, currentHandler)) {
return candidate; return candidate;
} }
} }
@ -1571,18 +1572,16 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
* *
* @param dataType the dataType for which to return an equivalent dataType in * @param dataType the dataType for which to return an equivalent dataType in
* this manager * this manager
* @param handler Used to handle collisions with dataTypes with same path and
* name that is
* @return resolved datatype * @return resolved datatype
*/ */
private DataType resolveDataTypeNoSource(DataType dataType, DataTypeConflictHandler handler) { private DataType resolveDataTypeNoSource(DataType dataType) {
DataType existingDataType = findEquivalentDataTypeSameLocation(dataType); DataType existingDataType = findEquivalentDataTypeSameLocation(dataType);
if (existingDataType != null) { if (existingDataType != null) {
return existingDataType; return existingDataType;
} }
return resolveNoEquivalentFound(dataType, null, handler); return resolveNoEquivalentFound(dataType, null);
} }
/** /**
@ -1591,19 +1590,17 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
* *
* @param dataType the dataType for which to return an equivalent dataType in * @param dataType the dataType for which to return an equivalent dataType in
* this manager * this manager
* @param handler Used to handle collisions with dataTypes with same path and
* name that is
* @return resolved datatype * @return resolved datatype
*/ */
private DataType resolveDataTypeWithSource(DataType dataType, DataTypeConflictHandler handler) { private DataType resolveDataTypeWithSource(DataType dataType) {
SourceArchive sourceArchive = dataType.getSourceArchive(); SourceArchive sourceArchive = dataType.getSourceArchive();
// Do we have that dataType already resolved and associated with the source archive? // Do we have that dataType already resolved and associated with the source archive?
DataType existingDataType = getDataType(sourceArchive, dataType.getUniversalID()); DataType existingDataType = getDataType(sourceArchive, dataType.getUniversalID());
if (existingDataType != null) { if (existingDataType != null) {
if (!existingDataType.isEquivalent(dataType) && if (!DataTypeDB.isEquivalent(existingDataType, dataType, currentHandler) &&
handler.shouldUpdate(dataType, existingDataType)) { currentHandler.shouldUpdate(dataType, existingDataType)) {
existingDataType.replaceWith(dataType); existingDataType.replaceWith(dataType);
existingDataType.setLastChangeTime(dataType.getLastChangeTime()); existingDataType.setLastChangeTime(dataType.getLastChangeTime());
} }
@ -1614,9 +1611,9 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
// Avoid conflict handling for types with a source which matches // Avoid conflict handling for types with a source which matches
// this archive, although a conflict name may still be used. // this archive, although a conflict name may still be used.
// This can occur when a semi-mirrored archive instance is used // This can occur when a semi-mirrored archive instance is used
// such as the CompositeViewerDataTypeManager which uses the same // such as the CompositeViewerDataTypeManager or Program Merge
// Archive UniversalID as the edited datatype's source. // which uses the same Archive UniversalID.
return createDataType(dataType, sourceArchive, handler); return createConflictDataType(dataType, sourceArchive);
} }
// If we have the same path name and the existing data type is a local data type // If we have the same path name and the existing data type is a local data type
@ -1630,7 +1627,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
return existingDataType; return existingDataType;
} }
return resolveNoEquivalentFound(dataType, sourceArchive, handler); return resolveNoEquivalentFound(dataType, sourceArchive);
} }
/** /**
@ -1639,11 +1636,9 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
* using the specified conflict handler. * using the specified conflict handler.
* @param dataType datatype being resolved * @param dataType datatype being resolved
* @param sourceArchive source archive associated with new type (may be null) * @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) * @return resolved datatype (may be existing or newly added datatype)
*/ */
private DataType resolveNoEquivalentFound(DataType dataType, SourceArchive sourceArchive, private DataType resolveNoEquivalentFound(DataType dataType, SourceArchive sourceArchive) {
DataTypeConflictHandler handler) {
if (sourceArchive != null && sourceArchive.getArchiveType() == ArchiveType.PROGRAM) { if (sourceArchive != null && sourceArchive.getArchiveType() == ArchiveType.PROGRAM) {
sourceArchive = null; // do not preserve program as a source archive sourceArchive = null; // do not preserve program as a source archive
@ -1653,13 +1648,14 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
// (preference is given to similar kind of datatype when checking existing conflict types) // (preference is given to similar kind of datatype when checking existing conflict types)
DataType existingDataType = findDataTypeSameLocation(dataType); DataType existingDataType = findDataTypeSameLocation(dataType);
if (existingDataType == null) { if (existingDataType == null) {
return createDataType(dataType, sourceArchive, handler); return createDataType(dataType, getUnusedConflictName(dataType), sourceArchive,
currentHandler);
} }
// So we have a dataType with the same path and name, but not equivalent, so use // So we have a dataType with the same path and name, but not equivalent, so use
// the conflictHandler to decide what to do. // the conflictHandler to decide what to do.
ConflictResult result = handler.resolveConflict(dataType, existingDataType); ConflictResult conflictResult = currentHandler.resolveConflict(dataType, existingDataType);
switch (result) { switch (conflictResult) {
case REPLACE_EXISTING: // new type replaces old conflicted type case REPLACE_EXISTING: // new type replaces old conflicted type
try { try {
@ -1668,7 +1664,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
} }
renameToUnusedConflictName(existingDataType); renameToUnusedConflictName(existingDataType);
DataType newDataType = DataType newDataType =
createDataType(dataType, dataType.getName(), sourceArchive, handler); createDataType(dataType, dataType.getName(), sourceArchive, currentHandler);
try { try {
replace(existingDataType, newDataType); replace(existingDataType, newDataType);
} }
@ -1684,25 +1680,22 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
} }
case RENAME_AND_ADD: // default handler behavior case RENAME_AND_ADD: // default handler behavior
return createDataType(dataType, sourceArchive, handler); return createConflictDataType(dataType, sourceArchive);
default: // USE_EXISTING - new type is discarded and old conflicted type is returned default: // USE_EXISTING - new type is discarded and old conflicted type is returned
return existingDataType; return existingDataType;
} }
} }
private DataType createDataType(DataType dataType, SourceArchive sourceArchive, private DataType createConflictDataType(DataType dataType, SourceArchive sourceArchive) {
DataTypeConflictHandler handler) {
String dtName = getUnusedConflictName(dataType); String dtName = getUnusedConflictName(dataType);
DataType newDataType = createDataType(dataType, dtName, sourceArchive, handler); DataType newDataType = createDataType(dataType, dtName, sourceArchive, currentHandler);
// resolving child data types could result in another copy of dataType in the // NOTE: queue conflict datatype for delayed check for equivalent type.
// manager depending upon the conflict handler - check again // This is not needed for Pointer or Array which will update if/when
DataType existingDataType = findEquivalentDataTypeSameLocation(dataType); // referenced type gets replaced.
// If there is an equivalent datatype, remove the added type and return the existing if (!(newDataType instanceof Pointer) && !(newDataType instanceof Array)) {
if (existingDataType != null && existingDataType != newDataType) { conflictQueue.add(newDataType);
removeInternal(newDataType, TaskMonitor.DUMMY);
return existingDataType;
} }
return newDataType; return newDataType;
} }
@ -1748,14 +1741,14 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
monitor.checkCancelled(); monitor.checkCancelled();
resolve(dt, handler); resolve(dt, handler);
if (isResolveCacheOwner) { if (isResolveCacheOwner) {
flushResolveQueue(false); processResolveQueue(false);
} }
monitor.setProgress(++i); monitor.setProgress(++i);
} }
} }
finally { finally {
if (isResolveCacheOwner) { if (isResolveCacheOwner) {
flushResolveQueue(true); processResolveQueue(true);
} }
if (isEquivalenceCacheOwner) { if (isEquivalenceCacheOwner) {
clearEquivalenceCache(); clearEquivalenceCache();
@ -1865,6 +1858,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
} }
replace(existingDt, replacementDt); replace(existingDt, replacementDt);
if (fixupName) { if (fixupName) {
try { try {
long lastChangeTime = replacementDt.getLastChangeTime(); long lastChangeTime = replacementDt.getLastChangeTime();
@ -1897,22 +1891,44 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
private void replace(DataType existingDt, DataType replacementDt) private void replace(DataType existingDt, DataType replacementDt)
throws DataTypeDependencyException { throws DataTypeDependencyException {
if (existingDt == replacementDt) {
if (!contains(existingDt)) {
return; return;
} }
DataTypePath replacedDtPath = existingDt.getDataTypePath();
long replacedId = getID(existingDt);
UniversalID id = existingDt.getUniversalID();
idsToDataTypeMap.removeDataType(existingDt.getSourceArchive(), id);
if (replacementDt.dependsOn(existingDt)) { if (replacementDt.dependsOn(existingDt)) {
throw new DataTypeDependencyException("Replace failed: " + throw new DataTypeDependencyException("Replace failed: " +
replacementDt.getDisplayName() + " depends on " + existingDt.getDisplayName()); replacementDt.getDisplayName() + " depends on " + existingDt.getDisplayName());
} }
replaceUsesInOtherDataTypes(existingDt, replacementDt); addDataTypeToReplace(existingDt, replacementDt);
replaceQueuedDataTypes();
}
private void replaceQueuedDataTypes() {
// collect all datatypes to be replaced and notify children which may also get queued
// for removal.
LinkedList<Pair<DataType, DataType>> dataTypeReplacements = new LinkedList<>();
while (!typesToReplace.isEmpty()) {
Pair<DataType, DataType> dataTypeReplacement = typesToReplace.removeFirst();
replaceUsesInOtherDataTypes(dataTypeReplacement.first, dataTypeReplacement.second);
dataTypeReplacements.addFirst(dataTypeReplacement);
}
// perform actual database updates (e.g., record updates, change notifications, etc.)
for (Pair<DataType, DataType> dataTypeReplacement : dataTypeReplacements) {
replaceDataType(dataTypeReplacement.first, dataTypeReplacement.second);
}
}
private void replaceDataType(DataType existingDt, DataType replacementDt) {
DataTypePath replacedDtPath = existingDt.getDataTypePath();
long replacedId = getID(existingDt);
UniversalID id = existingDt.getUniversalID();
idsToDataTypeMap.removeDataType(existingDt.getSourceArchive(), id);
try { try {
replaceDataTypeIDs(replacedId, getID(replacementDt)); replaceDataTypeIDs(replacedId, getID(replacementDt));
@ -1929,13 +1945,20 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
private void replaceUsesInOtherDataTypes(DataType existingDt, DataType newDt) { private void replaceUsesInOtherDataTypes(DataType existingDt, DataType newDt) {
if (existingDt instanceof DataTypeDB) { if (existingDt instanceof DataTypeDB) {
// Notify parents that a dependency has been replaced. For pointers and arrays
// it may require their subsequent category change or removal to avoid duplication.
for (DataType dt : existingDt.getParents()) { for (DataType dt : existingDt.getParents()) {
dt.dataTypeReplaced(existingDt, newDt); dt.dataTypeReplaced(existingDt, newDt);
} }
} }
else { else {
// Since we do not track parents of non-DB types we must assume that all data types
// must be modified. Use of the sortedDataTypes list is the simplest way to do this.
// A copy of the list must be used since it will changed if other types get removed
// in the process.
buildSortedDataTypeList(); buildSortedDataTypeList();
for (DataType dt : new ArrayList<>(sortedDataTypes)) { List<DataType> sortedDataTypesCopy = new ArrayList<>(sortedDataTypes);
for (DataType dt : sortedDataTypesCopy) {
dt.dataTypeReplaced(existingDt, newDt); dt.dataTypeReplaced(existingDt, newDt);
} }
} }
@ -2008,17 +2031,23 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
list.add(DataType.DEFAULT); list.add(DataType.DEFAULT);
return; return;
} }
// ignore .conflict in both name and result matches
lock.acquire(); lock.acquire();
try { try {
buildSortedDataTypeList(); buildSortedDataTypeList();
DataType compareDataType = new TypedefDataType(name, DataType.DEFAULT); // Use exemplar datatype in root category without .conflict to position at start
// of possible matches
name = DataTypeUtilities.getNameWithoutConflict(name);
DataType compareDataType =
new TypedefDataType(CategoryPath.ROOT, name, DataType.DEFAULT, this);
int index = Collections.binarySearch(sortedDataTypes, compareDataType, nameComparator); int index = Collections.binarySearch(sortedDataTypes, compareDataType, nameComparator);
if (index < 0) { if (index < 0) {
index = -index - 1; index = -index - 1;
} }
// add all matches to list
while (index < sortedDataTypes.size()) { while (index < sortedDataTypes.size()) {
DataType dt = sortedDataTypes.get(index); DataType dt = sortedDataTypes.get(index);
if (!name.equals(dt.getName())) { if (!name.equals(DataTypeUtilities.getNameWithoutConflict(dt, false))) {
break; break;
} }
list.add(dt); list.add(dt);
@ -2206,43 +2235,38 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
* Remove the given datatype from this manager (assumes the lock has already been acquired). * Remove the given datatype from this manager (assumes the lock has already been acquired).
* *
* @param dataType the dataType to be removed * @param dataType the dataType to be removed
* @param monitor the task monitor
*/ */
private boolean removeInternal(DataType dataType, TaskMonitor monitor) { private void removeInternal(DataType dataType) {
if (!contains(dataType)) { if (!contains(dataType)) {
return false; return;
} }
LinkedList<Long> deletedIds = new LinkedList<>();
long id = getID(dataType); long id = getID(dataType);
if (id < 0) { if (id < 0) {
return false; return;
} }
idsToDelete.add(Long.valueOf(id)); idsToDelete.add(Long.valueOf(id));
removeQueuedDataTypes();
}
private void removeQueuedDataTypes() {
// collect all datatype to be removed and notify children which may also get queued
// for removal.
LinkedList<Long> deletedIds = new LinkedList<>();
while (!idsToDelete.isEmpty()) { while (!idsToDelete.isEmpty()) {
Long l = idsToDelete.removeFirst(); long id = idsToDelete.removeFirst();
id = l.longValue();
removeUseOfDataType(id); removeUseOfDataType(id);
deletedIds.addFirst(id);
deletedIds.addFirst(l);
} }
for (Long l : deletedIds) { // perform actual database updates (e.g., record removal, change notifications, etc.)
deleteDataType(l.longValue()); for (long id : deletedIds) {
deleteDataType(id);
} }
try { // Remove all uses of datatypes external to datatype manager
deleteDataTypeIDs(deletedIds, monitor); deleteDataTypeIDs(deletedIds);
}
catch (CancelledException e) {
return false;
}
return true;
} }
private void removeUseOfDataType(long id) { private void removeUseOfDataType(long id) {
@ -2267,11 +2291,15 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
public boolean remove(DataType dataType, TaskMonitor monitor) { public boolean remove(DataType dataType, TaskMonitor monitor) {
lock.acquire(); lock.acquire();
try { try {
return removeInternal(dataType, monitor); if (contains(dataType)) {
removeInternal(dataType);
return true;
}
} }
finally { finally {
lock.release(); lock.release();
} }
return false;
} }
@Override @Override
@ -2358,18 +2386,29 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
return (sourceArchive.equals(dtm.getLocalSourceArchive())); return (sourceArchive.equals(dtm.getLocalSourceArchive()));
} }
/**
* Queue a datatype to deleted in response to another datatype being deleted.
* @param id datatype ID to be removed
*/
protected void addDataTypeToDelete(long id) { protected void addDataTypeToDelete(long id) {
idsToDelete.add(Long.valueOf(id)); idsToDelete.add(Long.valueOf(id));
} }
/**
* Queue a datatype to be replaced by another datatype in response to its referenced
* datatype being replaced.
* @param oldDataType datatype to be replaced
* @param replacementDataType datatype which is the replacement
*/
protected void addDataTypeToReplace(DataType oldDataType, DataType replacementDataType) {
typesToReplace.add(new Pair<>(oldDataType, replacementDataType));
}
/** /**
* Delete all datatype uses external to the datatype manager if applicable. * Delete all datatype uses external to the datatype manager if applicable.
* @param deletedIds old datatype IDs which were deleted * @param deletedIds old datatype IDs which were deleted
* @param monitor task monitor
* @throws CancelledException if operation cancelled
*/ */
abstract protected void deleteDataTypeIDs(LinkedList<Long> deletedIds, TaskMonitor monitor) abstract protected void deleteDataTypeIDs(LinkedList<Long> deletedIds);
throws CancelledException;
private void notifyDeleted(long dataTypeID) { private void notifyDeleted(long dataTypeID) {
DataType dataType = getDataType(dataTypeID); DataType dataType = getDataType(dataTypeID);
@ -2377,9 +2416,16 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
return; return;
} }
if (dataType instanceof DataTypeDB dt) { if (dataType instanceof DataTypeDB dt) {
// Notify datatype that it has been deleted which in-turn will notify all of its
// parents. Parent datatype which are no longer valid (i.e., pointers, arrays)
// may invoke addDataTypeToDelete method to schedule their subsequent removal.
dt.notifyDeleted(); dt.notifyDeleted();
} }
else { else {
// Since we do not track parents of non-DB types we must assume that all data types
// must be modified. Use of the sortedDataTypes list is the simplest way to do this.
// A copy of the list must be used since it will changed if other types get removed
// in the process.
buildSortedDataTypeList(); buildSortedDataTypeList();
List<DataType> sortedDataTypesCopy = new ArrayList<>(sortedDataTypes); List<DataType> sortedDataTypesCopy = new ArrayList<>(sortedDataTypes);
for (DataType dt : sortedDataTypesCopy) { for (DataType dt : sortedDataTypesCopy) {
@ -3036,6 +3082,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
if (name == null || name.length() == 0) { if (name == null || name.length() == 0) {
throw new IllegalArgumentException("Data type must have a valid name"); throw new IllegalArgumentException("Data type must have a valid name");
} }
DataType dataType = resolve(typedef.getDataType(), getDependencyConflictHandler()); DataType dataType = resolve(typedef.getDataType(), getDependencyConflictHandler());
boolean isAutoNamed = typedef.isAutoNamed(); boolean isAutoNamed = typedef.isAutoNamed();
short flags = 0; short flags = 0;
@ -3386,30 +3433,6 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
} }
} }
private class NameComparator implements Comparator<DataType> {
/**
* Compares its two arguments for order. Returns a negative integer, zero, or a
* positive integer as the first argument is less than, equal to, or greater
* than the second.
* <p>
*
* @param d1 the first datatype to be compared
* @param d2 the second datatype to be compared
* @return a negative integer, zero, or a positive integer as the first argument
* is less than, equal to, or greater than the second
* @throws ClassCastException if the arguments' types prevent them from being
* compared by this Comparator
*/
@Override
public int compare(DataType d1, DataType d2) {
int c = d1.getName().compareTo(d2.getName());
if (c == 0) {
return d1.getCategoryPath().compareTo(d2.getCategoryPath());
}
return c;
}
}
/** /**
* Handles IOExceptions * Handles IOExceptions
* *
@ -4325,10 +4348,208 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
return GraphAlgorithms.getVerticesInPostOrder(graph, GraphNavigator.topDownNavigator()); return GraphAlgorithms.getVerticesInPostOrder(graph, GraphNavigator.topDownNavigator());
} }
/**
* De-duplicate equivalent conflict datatypes which share a common base data type name and
* are found to be equivalent.
*
* @param dataType data type whose related conflict types should be de-duplicated
* @return true if one or more datatypes were de-duplicted or dde-conflicted, else false
*/
public boolean dedupeConflicts(DataType dataType) {
if (!(dataType instanceof DataTypeDB)) {
return false;
}
lock.acquire();
try {
if (dataType instanceof Pointer || dataType instanceof Array) {
dataType = DataTypeUtilities.getBaseDataType(dataType);
}
if (!contains(dataType)) {
return false;
}
List<DataType> relatedByName = findDataTypesSameLocation(dataType);
if (relatedByName.size() < 2) {
return false;
}
Collections.sort(relatedByName, DataTypeComparator.INSTANCE);
boolean success = false;
for (int n = relatedByName.size() - 1; n != 0; n--) {
DataType targetDt = relatedByName.get(n);
for (int m = 0; m < n; m++) {
DataType candidate = relatedByName.get(m);
if (candidate.isEquivalent(targetDt)) {
try {
replace(targetDt, candidate);
success = true;
break;
}
catch (DataTypeDependencyException e) {
throw new AssertionError("Unexpected condition", e);
}
}
}
}
return success;
}
finally {
lock.release();
}
}
private record DedupedConflicts(int processCnt, int replaceCnt) {
}
private DedupedConflicts doDedupeConflicts(DataType dataType) {
List<DataType> relatedByName = findDataTypesSameLocation(dataType);
if (relatedByName.size() < 2) {
return new DedupedConflicts(1, 0);
}
Collections.sort(relatedByName, DataTypeComparator.INSTANCE);
int replaceCnt = 0;
for (int n = relatedByName.size() - 1; n != 0; n--) {
DataType targetDt = relatedByName.get(n);
for (int m = 0; m < n; m++) {
DataType candidate = relatedByName.get(m);
if (candidate.isEquivalent(targetDt)) {
try {
replace(targetDt, candidate);
++replaceCnt;
break;
}
catch (DataTypeDependencyException e) {
throw new AssertionError("Unexpected condition", e);
}
}
}
}
return new DedupedConflicts(relatedByName.size(), replaceCnt);
}
/**
* De-duplicate equivalent conflict datatypes which share a common base data type name and
* are found to be equivalent.
*
* @param monitor task monitor
* @throws CancelledException if task is cancelled
*/
public void dedupeAllConflicts(TaskMonitor monitor) throws CancelledException {
lock.acquire();
try {
List<DataType> conflictList = new ArrayList<>();
int total = popuplateConflictList(conflictList, getRootCategory());
monitor.initialize(total);
int processed = 0;
int replacements = 0;
for (DataType conflictDt : conflictList) {
monitor.checkCancelled();
DedupedConflicts result = doDedupeConflicts(conflictDt);
processed += result.processCnt;
replacements += result.replaceCnt;
monitor.setProgress(processed);
}
Msg.info(this, "Evaluated " + processed + " conflict types, replaced " + replacements +
" base type conflicts");
}
finally {
lock.release();
}
}
private int popuplateConflictList(List<DataType> conflictList, Category category) {
int count = 0;
for (Category childCategory : category.getCategories()) {
count += popuplateConflictList(conflictList, childCategory);
}
DataType[] dataTypes = category.getDataTypes();
Arrays.sort(dataTypes, DataTypeComparator.INSTANCE);
String lastBaseName = null;
boolean lastHadConflict = false;
for (DataType dt : dataTypes) {
if (dt instanceof Pointer || dt instanceof Array) {
continue;
}
boolean isConflict = dt.getName().contains(DataType.CONFLICT_SUFFIX);
String name = DataTypeUtilities.getNameWithoutConflict(dt, false);
if (!name.equals(lastBaseName)) {
// base name changed
lastBaseName = name;
lastHadConflict = false;
if (isConflict) {
// non-conflict type is not present
conflictList.add(dt);
lastHadConflict = true;
++count;
}
}
else if (isConflict) {
if (!lastHadConflict) {
// account for non-conflict type in count
conflictList.add(dt);
lastHadConflict = true;
++count;
}
++count;
}
}
return count;
}
/**
* Process the conflict queue of newly created conflict datatypes. Processing performs one
* last attempt at locating an equivalent datatype
*
* @param dataType final resolved data type.
* If a newly created conflict type and this method and is able to replace with an equivalent
* data type the replacement datatype will be returned, otherwise the specified {@code dataType}
* instance will be returned.
* @return final resolved data type
*/
private DataType processConflictQueue(DataType dataType) {
while (!conflictQueue.isEmpty()) {
// Process last conflict first (LIFO) to ensure conflicts with larger
// numbers are discarded first if applicable - although unlikely to occur
// during the same resolve-cycle.
DataType dt = conflictQueue.removeLast();
List<DataType> relatedByName = findDataTypesSameLocation(dt);
for (DataType candidate : relatedByName) {
if (candidate != dt && DataTypeDB.isEquivalent(candidate, dt,
DataTypeConflictHandler.DEFAULT_HANDLER)) {
try {
replace(dt, candidate);
if (dt == dataType) {
// switch final type
dataType = candidate;
}
break;
}
catch (DataTypeDependencyException e) {
// ignore - try next if available
}
}
}
}
return dataType;
}
/** /**
* Activate resolveCache and associated resolveQueue if not already active. If * Activate resolveCache and associated resolveQueue if not already active. If
* this method returns true caller is responsible for flushing resolveQueue and * this method returns true caller is responsible for flushing resolveQueue and
* invoking {@link #flushResolveQueue(boolean)} when resolve complete. * invoking {@link #processResolveQueue(boolean)} when resolve complete.
* For each completed resolve {@link #cacheResolvedDataType(DataType, DataType)} * For each completed resolve {@link #cacheResolvedDataType(DataType, DataType)}
* should be invoked. * should be invoked.
* *
@ -4356,7 +4577,42 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
resolveQueue.add(new ResolvePair(resolvedDt, definitionDt)); resolveQueue.add(new ResolvePair(resolvedDt, definitionDt));
} }
void flushResolveQueue(boolean deactivateCache) { /**
* Process resolve queue, which includes:
* <ul>
* <li>Process any deferred pointer resolves in {@code resolveQueue}</li>
* <li>If deactivating cache {@code deactivateCache==true} the conflict queue will be
* processed</li>
* <li>If deactivating cache {@code deactivateCache==true} the {@code resolveCache} will be
* disposed.</li>
* </ul>
* @param deactivateCache true if caller is {@code resolveCache} owner as determined by call
* to {@link #activateResolveCache()}) at start of resolve cycle, else false.
*/
void processResolveQueue(boolean deactivateCache) {
processResolveQueue(deactivateCache, null);
}
/**
* Process resolve queue, which includes:
* <ul>
* <li>Process any deferred pointer resolves in {@code resolveQueue}</li>
* <li>If deactivating cache {@code deactivateCache==true} the conflict queue will be
* processed</li>
* <li>If deactivating cache {@code deactivateCache==true} the {@code resolveCache} will be
* disposed.</li>
* </ul>
*
* @param deactivateCache true if caller is {@code resolveCache} owner as determined by call
* to {@link #activateResolveCache()}) at start of resolve cycle, else false.
* @param dataType final resolved data type. If {@code deactivateCache==true} and this type
* is a newly created conflict type and this method and is able to replace with an equivalent
* data type the replacement datatype will be returned, otherwise the specified {@code dataType}
* instance will be returned.
* @return final resolved data type
*/
private DataType processResolveQueue(boolean deactivateCache, DataType dataType) {
try { try {
if (resolveQueue != null) { if (resolveQueue != null) {
DataTypeConflictHandler handler = getDependencyConflictHandler(); DataTypeConflictHandler handler = getDependencyConflictHandler();
@ -4364,7 +4620,9 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
ResolvePair resolvePair = resolveQueue.pollFirst(); ResolvePair resolvePair = resolveQueue.pollFirst();
DataTypeDB resolvedDt = resolvePair.resolvedDt; DataTypeDB resolvedDt = resolvePair.resolvedDt;
try { try {
resolvedDt.postPointerResolve(resolvePair.definitionDt, handler); if (!resolvedDt.isDeleted()) {
resolvedDt.postPointerResolve(resolvePair.definitionDt, handler);
}
} }
// TODO: catch exceptions if needed // TODO: catch exceptions if needed
finally { finally {
@ -4373,6 +4631,9 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
} }
} }
} }
if (deactivateCache) {
return processConflictQueue(dataType);
}
} }
finally { finally {
resolveQueue = null; resolveQueue = null;
@ -4380,6 +4641,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
resolveCache = null; resolveCache = null;
} }
} }
return dataType;
} }
private DataType getCachedResolve(DataType dt) { private DataType getCachedResolve(DataType dt) {
@ -4639,6 +4901,26 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
} }
} }
/**
* Diagnostic method to determine actual number of datatype records which exist. This
* may differ from the total number of datatypes reported via {@link DataTypeManager#getAllDataTypes()}
* due to the manner in which datatypes are held in-memory (i.e., name-based indexed) if
* duplicate datatype names exist within a category.
* @return total number of datatype records.
*/
int getDataTypeRecordCount() {
lock.acquire();
try {
return builtinAdapter.getRecordCount() + compositeAdapter.getRecordCount() +
arrayAdapter.getRecordCount() + pointerAdapter.getRecordCount() +
typedefAdapter.getRecordCount() + functionDefAdapter.getRecordCount() +
enumAdapter.getRecordCount();
}
finally {
lock.release();
}
}
} }
class CategoryCache extends FixedSizeHashMap<String, Category> { class CategoryCache extends FixedSizeHashMap<String, Category> {

View file

@ -16,7 +16,7 @@
package ghidra.program.database.data; package ghidra.program.database.data;
import java.util.*; import java.util.*;
import java.util.regex.Pattern; import java.util.regex.*;
import ghidra.app.util.NamespaceUtils; import ghidra.app.util.NamespaceUtils;
import ghidra.app.util.SymbolPathParser; import ghidra.app.util.SymbolPathParser;
@ -306,7 +306,49 @@ public class DataTypeUtilities {
*/ */
public static String getNameWithoutConflict(DataType dataType, boolean includeCategoryPath) { public static String getNameWithoutConflict(DataType dataType, boolean includeCategoryPath) {
String name = includeCategoryPath ? dataType.getPathName() : dataType.getName(); String name = includeCategoryPath ? dataType.getPathName() : dataType.getName();
return DATATYPE_CONFLICT_PATTERN.matcher(name).replaceAll(""); return getNameWithoutConflict(name);
}
/**
* Get the name of a data type with all conflict naming patterns removed.
*
* @param dataTypeName data type name with optional category path included
* @return name with optional category path included
*/
public static String getNameWithoutConflict(String dataTypeName) {
return DATATYPE_CONFLICT_PATTERN.matcher(dataTypeName).replaceAll("");
}
/**
* Get the conflict value string associated with a conflict datatype name.
*
* @param dataType datatype to be checked
* @return conflict value string. Will be null if name is not a conflict name, or
* empty string if conflict has no number. Otherwise a decimal value string will be returned.
*/
public static String getConflictString(DataType dataType) {
return getConflictString(dataType.getName());
}
/**
* Get the conflict value string associated with a conflict datatype name.
*
* @param dataTypeName datatype name to be checked
* @return conflict value string. Will be one of the following:
* <ol>
* <li>A null value if not a conflict name,</li>
* <li>an empty string if conflict name without a number, or</li>
* <li>a decimal string value which corresponds to the conflict number in the name.</li>
* </ol>
*/
public static String getConflictString(String dataTypeName) {
Matcher matcher = DATATYPE_CONFLICT_PATTERN.matcher(dataTypeName);
if (matcher.find()) {
MatchResult matchResult = matcher.toMatchResult();
return dataTypeName.substring(matchResult.start() + DataType.CONFLICT_SUFFIX.length(),
matchResult.end());
}
return null;
} }
/** /**

View file

@ -30,6 +30,7 @@ import ghidra.docking.settings.SettingsDefinition;
import ghidra.program.database.DBObjectCache; import ghidra.program.database.DBObjectCache;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.program.model.data.Enum; import ghidra.program.model.data.Enum;
import ghidra.program.model.data.DataTypeConflictHandler.ConflictResult;
import ghidra.program.model.mem.MemBuffer; import ghidra.program.model.mem.MemBuffer;
import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.scalar.Scalar; import ghidra.program.model.scalar.Scalar;
@ -601,7 +602,7 @@ class EnumDB extends DataTypeDB implements Enum {
} }
@Override @Override
public boolean isEquivalent(DataType dt) { protected boolean isEquivalent(DataType dt, DataTypeConflictHandler handler) {
if (dt == this) { if (dt == this) {
return true; return true;
} }
@ -610,15 +611,26 @@ class EnumDB extends DataTypeDB implements Enum {
} }
Enum enumm = (Enum) dt; Enum enumm = (Enum) dt;
if (!DataTypeUtilities.equalsIgnoreConflict(getName(), enumm.getName()) || if (!DataTypeUtilities.equalsIgnoreConflict(getName(), enumm.getName())) {
getLength() != enumm.getLength() || getCount() != enumm.getCount()) {
return false; return false;
} }
if (!isEachValueEquivalent(enumm)) { if (handler != null &&
ConflictResult.USE_EXISTING == handler.resolveConflict(enumm, this)) {
// treat this type as equivalent if existing type will be used
return true;
}
if (getLength() != enumm.getLength() || getCount() != enumm.getCount()) {
return false; return false;
} }
return true;
return isEachValueEquivalent(enumm);
}
@Override
public boolean isEquivalent(DataType dt) {
return isEquivalent(dt, null);
} }
private boolean isEachValueEquivalent(Enum enumm) { private boolean isEachValueEquivalent(Enum enumm) {
@ -895,6 +907,8 @@ class EnumDB extends DataTypeDB implements Enum {
public int getMinimumPossibleLength() { public int getMinimumPossibleLength() {
lock.acquire(); lock.acquire();
try { try {
checkIsValid();
initializeIfNeeded();
if (valueMap.isEmpty()) { if (valueMap.isEmpty()) {
return 1; return 1;
} }

View file

@ -54,8 +54,7 @@ abstract class EnumDBAdapter {
* @throws CancelledException if task cancelled * @throws CancelledException if task cancelled
*/ */
static EnumDBAdapter getAdapter(DBHandle handle, int openMode, String tablePrefix, static EnumDBAdapter getAdapter(DBHandle handle, int openMode, String tablePrefix,
TaskMonitor monitor) TaskMonitor monitor) throws VersionException, IOException, CancelledException {
throws VersionException, IOException, CancelledException {
if (openMode == DBConstants.CREATE) { if (openMode == DBConstants.CREATE) {
return new EnumDBAdapterV1(handle, tablePrefix, true); return new EnumDBAdapterV1(handle, tablePrefix, true);
} }
@ -103,8 +102,7 @@ abstract class EnumDBAdapter {
* @throws CancelledException if task cancelled * @throws CancelledException if task cancelled
*/ */
private static EnumDBAdapter upgrade(DBHandle handle, EnumDBAdapter oldAdapter, private static EnumDBAdapter upgrade(DBHandle handle, EnumDBAdapter oldAdapter,
String tablePrefix, String tablePrefix, TaskMonitor monitor)
TaskMonitor monitor)
throws VersionException, IOException, CancelledException { throws VersionException, IOException, CancelledException {
DBHandle tmpHandle = new DBHandle(); DBHandle tmpHandle = new DBHandle();
@ -216,4 +214,10 @@ abstract class EnumDBAdapter {
abstract DBRecord getRecordWithIDs(UniversalID sourceID, UniversalID datatypeID) abstract DBRecord getRecordWithIDs(UniversalID sourceID, UniversalID datatypeID)
throws IOException; throws IOException;
/**
* Get the number of enum datatype records
* @return total number of composite records
*/
public abstract int getRecordCount();
} }

View file

@ -79,4 +79,8 @@ class EnumDBAdapterNoTable extends EnumDBAdapter {
return null; return null;
} }
@Override
public int getRecordCount() {
return 0;
}
} }

View file

@ -128,4 +128,9 @@ class EnumDBAdapterV0 extends EnumDBAdapter implements RecordTranslator {
return null; return null;
} }
@Override
public int getRecordCount() {
return enumTable.getRecordCount();
}
} }

View file

@ -139,8 +139,8 @@ class EnumDBAdapterV1 extends EnumDBAdapter {
Field[] keys = enumTable.findRecords(new LongField(datatypeID.getValue()), Field[] keys = enumTable.findRecords(new LongField(datatypeID.getValue()),
V1_ENUM_UNIVERSAL_DT_ID_COL); V1_ENUM_UNIVERSAL_DT_ID_COL);
for (int i = 0; i < keys.length; i++) { for (Field key : keys) {
DBRecord record = enumTable.getRecord(keys[i]); DBRecord record = enumTable.getRecord(key);
if (record.getLongValue(V1_ENUM_SOURCE_ARCHIVE_ID_COL) == sourceID.getValue()) { if (record.getLongValue(V1_ENUM_SOURCE_ARCHIVE_ID_COL) == sourceID.getValue()) {
return record; return record;
} }
@ -148,4 +148,9 @@ class EnumDBAdapterV1 extends EnumDBAdapter {
return null; return null;
} }
@Override
public int getRecordCount() {
return enumTable.getRecordCount();
}
} }

View file

@ -25,6 +25,7 @@ import ghidra.docking.settings.Settings;
import ghidra.docking.settings.SettingsImpl; import ghidra.docking.settings.SettingsImpl;
import ghidra.program.database.DBObjectCache; import ghidra.program.database.DBObjectCache;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.program.model.data.DataTypeConflictHandler.ConflictResult;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.FunctionSignature; import ghidra.program.model.listing.FunctionSignature;
@ -198,7 +199,7 @@ class FunctionDefinitionDB extends DataTypeDB implements FunctionDefinition {
} }
finally { finally {
if (isResolveCacheOwner) { if (isResolveCacheOwner) {
dataMgr.flushResolveQueue(true); dataMgr.processResolveQueue(true);
} }
lock.release(); lock.release();
} }
@ -429,12 +430,12 @@ class FunctionDefinitionDB extends DataTypeDB implements FunctionDefinition {
} }
@Override @Override
public boolean isEquivalent(DataType dataType) { protected boolean isEquivalent(DataType dataType, DataTypeConflictHandler handler) {
if (dataType == this) { if (dataType == this) {
return true; return true;
} }
if (!(dataType instanceof FunctionDefinition)) { if (!(dataType instanceof FunctionDefinition sig)) {
return false; return false;
} }
@ -452,7 +453,18 @@ class FunctionDefinitionDB extends DataTypeDB implements FunctionDefinition {
} }
try { try {
isEquivalent = isEquivalentSignature((FunctionSignature) dataType);
if (handler != null &&
ConflictResult.USE_EXISTING == handler.resolveConflict(sig, this)) {
// treat this type as equivalent if existing type will be used
isEquivalent = true;
}
else {
if (handler != null) {
handler = handler.getSubsequentHandler();
}
isEquivalent = isEquivalentSignature(sig, handler);
}
} }
finally { finally {
dataMgr.putCachedEquivalence(this, dataType, isEquivalent); dataMgr.putCachedEquivalence(this, dataType, isEquivalent);
@ -461,7 +473,12 @@ class FunctionDefinitionDB extends DataTypeDB implements FunctionDefinition {
} }
@Override @Override
public boolean isEquivalentSignature(FunctionSignature signature) { public boolean isEquivalent(DataType dt) {
return isEquivalent(dt, null);
}
private boolean isEquivalentSignature(FunctionSignature signature,
DataTypeConflictHandler handler) {
if (signature == this) { if (signature == this) {
return true; return true;
} }
@ -476,10 +493,10 @@ class FunctionDefinitionDB extends DataTypeDB implements FunctionDefinition {
(hasVarArgs() == signature.hasVarArgs()) && (hasVarArgs() == signature.hasVarArgs()) &&
(hasNoReturn() == signature.hasNoReturn())) { (hasNoReturn() == signature.hasNoReturn())) {
ParameterDefinition[] args = signature.getArguments(); ParameterDefinition[] args = signature.getArguments();
ParameterDefinition[] thisArgs = this.getArguments(); ParameterDefinitionDB[] thisArgs = this.getArguments();
if (args.length == thisArgs.length) { if (args.length == thisArgs.length) {
for (int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
if (!thisArgs[i].isEquivalent(args[i])) { if (!thisArgs[i].isEquivalent(args[i], handler)) {
return false; return false;
} }
} }
@ -489,6 +506,11 @@ class FunctionDefinitionDB extends DataTypeDB implements FunctionDefinition {
return false; return false;
} }
@Override
public boolean isEquivalentSignature(FunctionSignature signature) {
return isEquivalentSignature(signature, null);
}
@Override @Override
protected void doSetCategoryPathRecord(long categoryID) throws IOException { protected void doSetCategoryPathRecord(long categoryID) throws IOException {
record.setLongValue(FunctionDefinitionDBAdapter.FUNCTION_DEF_CAT_ID_COL, categoryID); record.setLongValue(FunctionDefinitionDBAdapter.FUNCTION_DEF_CAT_ID_COL, categoryID);

View file

@ -152,6 +152,30 @@ final class ParameterDefinitionDB implements ParameterDefinition {
return record.getIntValue(FunctionParameterAdapter.PARAMETER_ORDINAL_COL); return record.getIntValue(FunctionParameterAdapter.PARAMETER_ORDINAL_COL);
} }
boolean isEquivalent(ParameterDefinition parm, DataTypeConflictHandler handler) {
if (parm == null) {
return false;
}
if (getOrdinal() != parm.getOrdinal()) {
return false;
}
DataType dataType = getDataType();
DataType otherDataType = parm.getDataType();
// if they contain datatypes that have same ids, then we are essentially equivalent.
if (DataTypeUtilities.isSameDataType(dataType, otherDataType)) {
return true;
}
return DataTypeDB.isEquivalent(dataType, otherDataType, handler);
}
@Override
public boolean isEquivalent(ParameterDefinition parm) {
return isEquivalent(parm, null);
}
@Override @Override
public boolean isEquivalent(Variable otherVar) { public boolean isEquivalent(Variable otherVar) {
if (otherVar == null) { if (otherVar == null) {
@ -169,20 +193,6 @@ final class ParameterDefinitionDB implements ParameterDefinition {
return true; return true;
} }
@Override
public boolean isEquivalent(ParameterDefinition parm) {
if (parm == null) {
return false;
}
if (getOrdinal() != parm.getOrdinal()) {
return false;
}
if (!DataTypeUtilities.isSameOrEquivalentDataType(getDataType(), parm.getDataType())) {
return false;
}
return true;
}
@Override @Override
public int compareTo(ParameterDefinition p) { public int compareTo(ParameterDefinition p) {
return getOrdinal() - p.getOrdinal(); return getOrdinal() - p.getOrdinal();

View file

@ -126,7 +126,8 @@ class PointerDB extends DataTypeDB implements Pointer {
return this; return this;
} }
// don't clone referenced data-type to avoid potential circular reference // don't clone referenced data-type to avoid potential circular reference
return new PointerDataType(getDataType(), hasLanguageDependantLength() ? -1 : getLength(), dtm); return new PointerDataType(getDataType(), hasLanguageDependantLength() ? -1 : getLength(),
dtm);
} }
@Override @Override
@ -143,7 +144,7 @@ class PointerDB extends DataTypeDB implements Pointer {
lock.acquire(); lock.acquire();
try { try {
checkIsValid(); checkIsValid();
if ( displayName == null ) { if (displayName == null) {
// NOTE: Pointer display name only specifies length if null base type // NOTE: Pointer display name only specifies length if null base type
DataType dt = getDataType(); DataType dt = getDataType();
if (dt == null) { if (dt == null) {
@ -284,7 +285,7 @@ class PointerDB extends DataTypeDB implements Pointer {
} }
@Override @Override
public boolean isEquivalent(DataType dt) { protected boolean isEquivalent(DataType dt, DataTypeConflictHandler handler) {
if (dt == null) { if (dt == null) {
return false; return false;
} }
@ -322,7 +323,7 @@ class PointerDB extends DataTypeDB implements Pointer {
return false; return false;
} }
// TODO: The pointer deep-dive equivalence checking on the referenced datatype can // NOTE: The pointer deep-dive equivalence checking on the referenced datatype can
// cause types containing pointers (composites, functions) to conflict when in // cause types containing pointers (composites, functions) to conflict when in
// reality the referenced type simply has multiple implementations which differ. // reality the referenced type simply has multiple implementations which differ.
// Although without doing this Ghidra may fail to resolve dependencies which differ // Although without doing this Ghidra may fail to resolve dependencies which differ
@ -337,13 +338,21 @@ class PointerDB extends DataTypeDB implements Pointer {
isEquivalentActive.set(true); isEquivalentActive.set(true);
try { try {
return getDataType().isEquivalent(otherDataType); if (handler != null) {
handler = handler.getSubsequentHandler();
}
return DataTypeDB.isEquivalent(referencedDataType, otherDataType, handler);
} }
finally { finally {
isEquivalentActive.set(false); isEquivalentActive.set(false);
} }
} }
@Override
public boolean isEquivalent(DataType dt) {
return isEquivalent(dt, null);
}
@Override @Override
public void dataTypeReplaced(DataType oldDt, DataType newDt) { public void dataTypeReplaced(DataType oldDt, DataType newDt) {
if (newDt == this) { if (newDt == this) {
@ -351,12 +360,36 @@ class PointerDB extends DataTypeDB implements Pointer {
} }
lock.acquire(); lock.acquire();
try { try {
String myOldName = getOldName();
if (checkIsValid() && getDataType() == oldDt) { if (checkIsValid() && getDataType() == oldDt) {
// check for existing pointer to newDt
PointerDataType newPtr = new PointerDataType(newDt,
hasLanguageDependantLength() ? -1 : getLength(), dataMgr);
DataType existingPtr =
dataMgr.getDataType(newDt.getCategoryPath(), newPtr.getName());
if (existingPtr != null) {
// avoid duplicate pointer - replace this pointer with existing one
dataMgr.addDataTypeToReplace(this, existingPtr);
return;
}
if (!newDt.getCategoryPath().equals(oldDt.getCategoryPath())) {
// move this pointer to same category as newDt
try {
super.setCategoryPath(newDt.getCategoryPath());
}
catch (DuplicateNameException e) {
throw new RuntimeException(e); // already checked
}
}
String myOldName = getOldName();
oldDt.removeParent(this); oldDt.removeParent(this);
newDt.addParent(this); newDt.addParent(this);
record.setLongValue(PointerDBAdapter.PTR_DT_ID_COL, dataMgr.getResolvedID(newDt)); record.setLongValue(PointerDBAdapter.PTR_DT_ID_COL, dataMgr.getResolvedID(newDt));
refreshName(); refreshName();
if (!oldDt.getName().equals(newDt.getName())) { if (!oldDt.getName().equals(newDt.getName())) {
notifyNameChanged(myOldName); notifyNameChanged(myOldName);
} }

View file

@ -163,4 +163,10 @@ abstract class PointerDBAdapter implements RecordTranslator {
* @throws IOException if the database can't be accessed. * @throws IOException if the database can't be accessed.
*/ */
abstract Field[] getRecordIdsInCategory(long categoryID) throws IOException; abstract Field[] getRecordIdsInCategory(long categoryID) throws IOException;
/**
* Get the number of pointer datatype records
* @return total number of composite records
*/
public abstract int getRecordCount();
} }

View file

@ -96,4 +96,9 @@ class PointerDBAdapterV0 extends PointerDBAdapter {
handle.deleteTable(POINTER_TABLE_NAME); handle.deleteTable(POINTER_TABLE_NAME);
} }
@Override
public int getRecordCount() {
return table.getRecordCount();
}
} }

View file

@ -97,4 +97,9 @@ class PointerDBAdapterV1 extends PointerDBAdapter {
handle.deleteTable(POINTER_TABLE_NAME); handle.deleteTable(POINTER_TABLE_NAME);
} }
@Override
public int getRecordCount() {
return table.getRecordCount();
}
} }

View file

@ -102,4 +102,9 @@ class PointerDBAdapterV2 extends PointerDBAdapter {
public DBRecord translateRecord(DBRecord rec) { public DBRecord translateRecord(DBRecord rec) {
return rec; return rec;
} }
@Override
public int getRecordCount() {
return table.getRecordCount();
}
} }

View file

@ -16,7 +16,6 @@
package ghidra.program.database.data; package ghidra.program.database.data;
import java.io.IOException; import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import db.*; import db.*;
@ -252,18 +251,21 @@ public class ProgramDataTypeManager extends ProgramBasedDataTypeManagerDB implem
} }
@Override @Override
protected void deleteDataTypeIDs(LinkedList<Long> deletedIds, TaskMonitor monitor) protected void deleteDataTypeIDs(LinkedList<Long> deletedIds) {
throws CancelledException {
// TODO: SymbolManager/FunctionManager do not appear to handle datatype removal update. // TODO: SymbolManager/FunctionManager do not appear to handle datatype removal update.
// Suspect it handles indirectly through detection of deleted datatype. Old deleted ID // Suspect it handles indirectly through detection of deleted datatype. Old deleted ID
// use could be an issue. // use could be an issue.
long[] ids = new long[deletedIds.size()]; long[] ids = new long[deletedIds.size()];
Iterator<Long> it = deletedIds.iterator();
int i = 0; int i = 0;
while (it.hasNext()) { for (Long deletedId : deletedIds) {
ids[i++] = it.next().longValue(); ids[i++] = deletedId.longValue();
}
try {
program.getCodeManager().clearData(ids, TaskMonitor.DUMMY);
}
catch (CancelledException e) {
// won't happen
} }
program.getCodeManager().clearData(ids, monitor);
program.getFunctionManager().invalidateCache(false); program.getFunctionManager().invalidateCache(false);
} }

View file

@ -24,6 +24,7 @@ import ghidra.docking.settings.Settings;
import ghidra.program.database.DBObjectCache; import ghidra.program.database.DBObjectCache;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.program.model.data.AlignedStructurePacker.StructurePackResult; import ghidra.program.model.data.AlignedStructurePacker.StructurePackResult;
import ghidra.program.model.data.DataTypeConflictHandler.ConflictResult;
import ghidra.program.model.mem.MemBuffer; import ghidra.program.model.mem.MemBuffer;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.exception.AssertException; import ghidra.util.exception.AssertException;
@ -1584,7 +1585,7 @@ class StructureDB extends CompositeDB implements StructureInternal {
} }
finally { finally {
if (isResolveCacheOwner) { if (isResolveCacheOwner) {
dataMgr.flushResolveQueue(true); dataMgr.processResolveQueue(true);
} }
lock.release(); lock.release();
} }
@ -1941,11 +1942,11 @@ class StructureDB extends CompositeDB implements StructureInternal {
} }
@Override @Override
public boolean isEquivalent(DataType dataType) { protected boolean isEquivalent(DataType dataType, DataTypeConflictHandler handler) {
if (dataType == this) { if (dataType == this) {
return true; return true;
} }
if (!(dataType instanceof StructureInternal)) { if (!(dataType instanceof StructureInternal struct)) {
return false; return false;
} }
@ -1964,7 +1965,14 @@ class StructureDB extends CompositeDB implements StructureInternal {
try { try {
isEquivalent = false; isEquivalent = false;
StructureInternal struct = (StructureInternal) dataType;
if (handler != null &&
ConflictResult.USE_EXISTING == handler.resolveConflict(struct, this)) {
// treat this type as equivalent if existing type will be used
isEquivalent = true;
return true;
}
int otherLength = struct.isZeroLength() ? 0 : struct.getLength(); int otherLength = struct.isZeroLength() ? 0 : struct.getLength();
int packing = getStoredPackingValue(); int packing = getStoredPackingValue();
if (packing != struct.getStoredPackingValue() || if (packing != struct.getStoredPackingValue() ||
@ -1982,10 +1990,14 @@ class StructureDB extends CompositeDB implements StructureInternal {
if (otherDefinedComponents.length != myNumComps) { // safety check if (otherDefinedComponents.length != myNumComps) { // safety check
return false; return false;
} }
if (handler != null) {
handler = handler.getSubsequentHandler();
//dataMgr.getPostResolve(this);
}
for (int i = 0; i < myNumComps; i++) { for (int i = 0; i < myNumComps; i++) {
DataTypeComponent myDtc = components.get(i); DataTypeComponent myDtc = components.get(i);
DataTypeComponent otherDtc = otherDefinedComponents[i]; DataTypeComponent otherDtc = otherDefinedComponents[i];
if (!myDtc.isEquivalent(otherDtc)) { if (!DataTypeComponentDB.isEquivalent(myDtc, otherDtc, handler)) {
return false; return false;
} }
} }
@ -1997,6 +2009,11 @@ class StructureDB extends CompositeDB implements StructureInternal {
return true; return true;
} }
@Override
public boolean isEquivalent(DataType dt) {
return isEquivalent(dt, null);
}
/** /**
* Adjust length of specified component (by index) by consuming available undefined * Adjust length of specified component (by index) by consuming available undefined
* bytes upto the specified number of bytes (numBytes). The associated component record will * bytes upto the specified number of bytes (numBytes). The associated component record will

View file

@ -22,6 +22,7 @@ import ghidra.docking.settings.Settings;
import ghidra.docking.settings.SettingsDefinition; import ghidra.docking.settings.SettingsDefinition;
import ghidra.program.database.DBObjectCache; import ghidra.program.database.DBObjectCache;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.program.model.data.DataTypeConflictHandler.ConflictResult;
import ghidra.program.model.mem.MemBuffer; import ghidra.program.model.mem.MemBuffer;
import ghidra.util.UniversalID; import ghidra.util.UniversalID;
import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.DuplicateNameException;
@ -249,29 +250,54 @@ class TypedefDB extends DataTypeDB implements TypeDef {
} }
@Override @Override
public boolean isEquivalent(DataType obj) { protected boolean isEquivalent(DataType dt, DataTypeConflictHandler handler) {
if (obj == this) { if (dt == this) {
return true; return true;
} }
if (obj == null || !(obj instanceof TypeDef)) { if (dt == null || !(dt instanceof TypeDef)) {
return false; return false;
} }
TypeDef td = (TypeDef) obj; TypeDef td = (TypeDef) dt;
validate(lock); validate(lock);
boolean autoNamed = isAutoNamed(); boolean autoNamed = isAutoNamed();
if (autoNamed != td.isAutoNamed()) { if (autoNamed != td.isAutoNamed()) {
return false; return false;
} }
if (!autoNamed && !DataTypeUtilities.equalsIgnoreConflict(getName(), td.getName())) { if (!autoNamed && !DataTypeUtilities.equalsIgnoreConflict(getName(), td.getName())) {
return false; return false;
} }
if (!hasSameTypeDefSettings(td)) { if (!hasSameTypeDefSettings(td)) {
return false; return false;
} }
return DataTypeUtilities.isSameOrEquivalentDataType(getDataType(), td.getDataType());
if (handler != null && ConflictResult.USE_EXISTING == handler.resolveConflict(td, this)) {
// treat this type as equivalent if existing type will be used
return true;
}
// TODO: add pointer-post-resolve logic with resolving bypass (similar to StructureDB components)
DataType dataType = getDataType();
DataType otherDataType = td.getDataType();
if (DataTypeUtilities.isSameDataType(dataType, otherDataType)) {
return true;
}
if (handler != null) {
handler = handler.getSubsequentHandler();
}
return DataTypeDB.isEquivalent(dataType, otherDataType, handler);
} }
@Override
public boolean isEquivalent(DataType dt) {
return isEquivalent(dt, null);
}
@Override
public void setCategoryPath(CategoryPath path) throws DuplicateNameException { public void setCategoryPath(CategoryPath path) throws DuplicateNameException {
if (isAutoNamed()) { if (isAutoNamed()) {
return; // ignore category change if auto-naming enabled return; // ignore category change if auto-naming enabled
@ -387,6 +413,7 @@ class TypedefDB extends DataTypeDB implements TypeDef {
return getDataType().getTypeDefSettingsDefinitions(); return getDataType().getTypeDefSettingsDefinitions();
} }
@Override
protected Settings doGetDefaultSettings() { protected Settings doGetDefaultSettings() {
DataTypeSettingsDB settings = new DataTypeSettingsDB(dataMgr, this, key); DataTypeSettingsDB settings = new DataTypeSettingsDB(dataMgr, this, key);
settings.setLock(dataMgr instanceof BuiltInDataTypeManager); settings.setLock(dataMgr instanceof BuiltInDataTypeManager);

View file

@ -60,8 +60,7 @@ abstract class TypedefDBAdapter {
* @throws CancelledException if task is cancelled * @throws CancelledException if task is cancelled
*/ */
static TypedefDBAdapter getAdapter(DBHandle handle, int openMode, String tablePrefix, static TypedefDBAdapter getAdapter(DBHandle handle, int openMode, String tablePrefix,
TaskMonitor monitor) TaskMonitor monitor) throws VersionException, IOException, CancelledException {
throws VersionException, IOException, CancelledException {
try { try {
return new TypedefDBAdapterV2(handle, tablePrefix, openMode == DBConstants.CREATE); return new TypedefDBAdapterV2(handle, tablePrefix, openMode == DBConstants.CREATE);
} }
@ -217,4 +216,10 @@ abstract class TypedefDBAdapter {
abstract DBRecord getRecordWithIDs(UniversalID sourceID, UniversalID datatypeID) abstract DBRecord getRecordWithIDs(UniversalID sourceID, UniversalID datatypeID)
throws IOException; throws IOException;
/**
* Get the number of typedef datatype records
* @return total number of composite records
*/
public abstract int getRecordCount();
} }

View file

@ -123,4 +123,8 @@ class TypedefDBAdapterV0 extends TypedefDBAdapter implements RecordTranslator {
return null; return null;
} }
@Override
public int getRecordCount() {
return table.getRecordCount();
}
} }

View file

@ -108,8 +108,8 @@ class TypedefDBAdapterV1 extends TypedefDBAdapter implements RecordTranslator {
DBRecord getRecordWithIDs(UniversalID sourceID, UniversalID datatypeID) throws IOException { DBRecord getRecordWithIDs(UniversalID sourceID, UniversalID datatypeID) throws IOException {
Field[] keys = Field[] keys =
table.findRecords(new LongField(datatypeID.getValue()), V1_TYPEDEF_UNIVERSAL_DT_ID_COL); table.findRecords(new LongField(datatypeID.getValue()), V1_TYPEDEF_UNIVERSAL_DT_ID_COL);
for (int i = 0; i < keys.length; i++) { for (Field key : keys) {
DBRecord record = table.getRecord(keys[i]); DBRecord record = table.getRecord(key);
if (record.getLongValue(V1_TYPEDEF_SOURCE_ARCHIVE_ID_COL) == sourceID.getValue()) { if (record.getLongValue(V1_TYPEDEF_SOURCE_ARCHIVE_ID_COL) == sourceID.getValue()) {
return translateRecord(record); return translateRecord(record);
} }
@ -137,4 +137,9 @@ class TypedefDBAdapterV1 extends TypedefDBAdapter implements RecordTranslator {
oldRec.getLongValue(V1_TYPEDEF_LAST_CHANGE_TIME_COL)); oldRec.getLongValue(V1_TYPEDEF_LAST_CHANGE_TIME_COL));
return rec; return rec;
} }
@Override
public int getRecordCount() {
return table.getRecordCount();
}
} }

View file

@ -138,8 +138,8 @@ class TypedefDBAdapterV2 extends TypedefDBAdapter {
Field[] keys = Field[] keys =
table.findRecords(new LongField(datatypeID.getValue()), V2_TYPEDEF_UNIVERSAL_DT_ID_COL); table.findRecords(new LongField(datatypeID.getValue()), V2_TYPEDEF_UNIVERSAL_DT_ID_COL);
for (int i = 0; i < keys.length; i++) { for (Field key : keys) {
DBRecord record = table.getRecord(keys[i]); DBRecord record = table.getRecord(key);
if (record.getLongValue(V2_TYPEDEF_SOURCE_ARCHIVE_ID_COL) == sourceID.getValue()) { if (record.getLongValue(V2_TYPEDEF_SOURCE_ARCHIVE_ID_COL) == sourceID.getValue()) {
return record; return record;
} }
@ -147,4 +147,9 @@ class TypedefDBAdapterV2 extends TypedefDBAdapter {
return null; return null;
} }
@Override
public int getRecordCount() {
return table.getRecordCount();
}
} }

View file

@ -23,6 +23,7 @@ import db.Field;
import ghidra.docking.settings.Settings; import ghidra.docking.settings.Settings;
import ghidra.program.database.DBObjectCache; import ghidra.program.database.DBObjectCache;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.program.model.data.DataTypeConflictHandler.ConflictResult;
import ghidra.program.model.mem.MemBuffer; import ghidra.program.model.mem.MemBuffer;
import ghidra.util.Msg; import ghidra.util.Msg;
@ -312,7 +313,7 @@ class UnionDB extends CompositeDB implements UnionInternal {
} }
finally { finally {
if (isResolveCacheOwner) { if (isResolveCacheOwner) {
dataMgr.flushResolveQueue(true); dataMgr.processResolveQueue(true);
} }
lock.release(); lock.release();
} }
@ -722,12 +723,11 @@ class UnionDB extends CompositeDB implements UnionInternal {
} }
@Override @Override
public boolean isEquivalent(DataType dataType) { protected boolean isEquivalent(DataType dataType, DataTypeConflictHandler handler) {
if (dataType == this) { if (dataType == this) {
return true; return true;
} }
if (!(dataType instanceof UnionInternal)) { if (!(dataType instanceof UnionInternal union)) {
return false; return false;
} }
@ -746,7 +746,14 @@ class UnionDB extends CompositeDB implements UnionInternal {
try { try {
isEquivalent = false; isEquivalent = false;
UnionInternal union = (UnionInternal) dataType;
if (handler != null &&
ConflictResult.USE_EXISTING == handler.resolveConflict(union, this)) {
// treat this type as equivalent if existing type will be used
isEquivalent = true;
return true;
}
if (getStoredPackingValue() != union.getStoredPackingValue() || if (getStoredPackingValue() != union.getStoredPackingValue() ||
getStoredMinimumAlignment() != union.getStoredMinimumAlignment()) { getStoredMinimumAlignment() != union.getStoredMinimumAlignment()) {
// rely on component match instead of checking length // rely on component match instead of checking length
@ -758,8 +765,11 @@ class UnionDB extends CompositeDB implements UnionInternal {
if (myComps.length != otherComps.length) { if (myComps.length != otherComps.length) {
return false; return false;
} }
if (handler != null) {
handler = handler.getSubsequentHandler();
}
for (int i = 0; i < myComps.length; i++) { for (int i = 0; i < myComps.length; i++) {
if (!myComps[i].isEquivalent(otherComps[i])) { if (!DataTypeComponentDB.isEquivalent(myComps[i], otherComps[i], handler)) {
return false; return false;
} }
} }
@ -771,6 +781,11 @@ class UnionDB extends CompositeDB implements UnionInternal {
return true; return true;
} }
@Override
public boolean isEquivalent(DataType dt) {
return isEquivalent(dt, null);
}
private void shiftOrdinals(int ordinal, int deltaOrdinal) { private void shiftOrdinals(int ordinal, int deltaOrdinal) {
for (int i = ordinal; i < components.size(); i++) { for (int i = ordinal; i < components.size(); i++) {
DataTypeComponentDB dtc = components.get(i); DataTypeComponentDB dtc = components.get(i);

View file

@ -33,7 +33,7 @@ public interface DBRecordAdapter {
public RecordIterator getRecords() throws IOException; public RecordIterator getRecords() throws IOException;
/** /**
* Get the number of records in table * Get the number of function definition datatype records
* @return total record count * @return total record count
*/ */
public int getRecordCount(); public int getRecordCount();

View file

@ -59,6 +59,9 @@ public interface Category extends Comparable<Category> {
* occurs. In other words, finds all data types whose name matches the given name once * occurs. In other words, finds all data types whose name matches the given name once
* any conflict suffixes have been removed from both the given name and the data types * any conflict suffixes have been removed from both the given name and the data types
* that are being scanned. * that are being scanned.
* <br>
* NOTE: The {@code name} provided must not contain array or pointer decorations.
*
* @param name the name for which to get conflict related data types in this category. Note: * @param name the name for which to get conflict related data types in this category. Note:
* the name that is passed in will be normalized to its base name, so you may pass in names * the name that is passed in will be normalized to its base name, so you may pass in names
* with .conflict appended as a convenience. * with .conflict appended as a convenience.

View file

@ -49,7 +49,7 @@ public interface Composite extends DataType {
/** /**
* Returns the component of this data type with the indicated ordinal. * Returns the component of this data type with the indicated ordinal.
* @param ordinal the component's ordinal (zero based). * @param ordinal the component's ordinal (numbering starts at 0).
* @return the data type component. * @return the data type component.
* @throws IndexOutOfBoundsException if the ordinal is out of bounds * @throws IndexOutOfBoundsException if the ordinal is out of bounds
*/ */
@ -155,7 +155,7 @@ public interface Composite extends DataType {
* Inserts a new datatype at the specified ordinal position in this composite. * Inserts a new datatype at the specified ordinal position in this composite.
* <BR>Note: For an aligned structure the ordinal position will get adjusted * <BR>Note: For an aligned structure the ordinal position will get adjusted
* automatically to provide the proper alignment. * automatically to provide the proper alignment.
* @param ordinal the ordinal where the new datatype is to be inserted. * @param ordinal the ordinal where the new datatype is to be inserted (numbering starts at 0).
* @param dataType the datatype to insert. * @param dataType the datatype to insert.
* @return the componentDataType created. * @return the componentDataType created.
* @throws IllegalArgumentException if the specified data type is not * @throws IllegalArgumentException if the specified data type is not
@ -171,7 +171,7 @@ public interface Composite extends DataType {
* Inserts a new datatype at the specified ordinal position in this composite. * Inserts a new datatype at the specified ordinal position in this composite.
* <BR>Note: For an aligned structure the ordinal position will get adjusted * <BR>Note: For an aligned structure the ordinal position will get adjusted
* automatically to provide the proper alignment. * automatically to provide the proper alignment.
* @param ordinal the ordinal where the new datatype is to be inserted. * @param ordinal the ordinal where the new datatype is to be inserted (numbering starts at 0).
* @param dataType the datatype to insert. * @param dataType the datatype to insert.
* @param length the length to associate with the datatype. * @param length the length to associate with the datatype.
* For fixed length types a length &lt;= 0 will use the length of the resolved dataType. * For fixed length types a length &lt;= 0 will use the length of the resolved dataType.
@ -190,7 +190,7 @@ public interface Composite extends DataType {
* Inserts a new datatype at the specified ordinal position in this composite. * Inserts a new datatype at the specified ordinal position in this composite.
* <BR>Note: For an aligned structure the ordinal position will get adjusted * <BR>Note: For an aligned structure the ordinal position will get adjusted
* automatically to provide the proper alignment. * automatically to provide the proper alignment.
* @param ordinal the ordinal where the new datatype is to be inserted. * @param ordinal the ordinal where the new datatype is to be inserted (numbering starts at 0).
* @param dataType the datatype to insert. * @param dataType the datatype to insert.
* @param length the length to associate with the datatype. * @param length the length to associate with the datatype.
* For fixed length types a length &lt;= 0 will use the length of the resolved dataType. * For fixed length types a length &lt;= 0 will use the length of the resolved dataType.
@ -211,7 +211,7 @@ public interface Composite extends DataType {
* Deletes the component at the given ordinal position. * Deletes the component at the given ordinal position.
* <BR>Note: Removal of bitfields from a structure with packing disabled will * <BR>Note: Removal of bitfields from a structure with packing disabled will
* not shift other components causing vacated bytes to revert to undefined filler. * not shift other components causing vacated bytes to revert to undefined filler.
* @param ordinal the ordinal of the component to be deleted. * @param ordinal the ordinal of the component to be deleted (numbering starts at 0).
* @throws IndexOutOfBoundsException if component ordinal is out of bounds * @throws IndexOutOfBoundsException if component ordinal is out of bounds
*/ */
public void delete(int ordinal) throws IndexOutOfBoundsException; public void delete(int ordinal) throws IndexOutOfBoundsException;

View file

@ -17,50 +17,49 @@ package ghidra.program.model.data;
import java.util.Comparator; import java.util.Comparator;
public class DataTypeComparator implements Comparator<Object> { /**
* {@link DataTypeComparator} provides the preferred named-based comparison of {@link DataType}
* which utilizes the {@link DataTypeNameComparator} for a primary {@link DataType#getName() name}
* comparison followed by sub-ordering on {@link DataTypeManager} name and {@link CategoryPath}.
*/
public class DataTypeComparator implements Comparator<DataType> {
public static DataTypeComparator INSTANCE = new DataTypeComparator();
@Override @Override
public int compare(Object o1, Object o2) { public int compare(DataType dt1, DataType dt2) {
String name1 = dt1.getName();
String name2 = dt2.getName();
if (o1 instanceof DataType && o2 instanceof DataType) { int nameCompare = DataTypeNameComparator.INSTANCE.compare(name1, name2);
DataType dt1 = (DataType) o1; if (nameCompare == 0) {
DataType dt2 = (DataType) o2;
String name1 = dt1.getName(); DataTypeManager dtm1 = dt1.getDataTypeManager();
String name2 = dt2.getName(); String dtmName1 = dtm1 != null ? dtm1.getName() : null;
// if the names are the same, then sort by the path DataTypeManager dtm2 = dt2.getDataTypeManager();
int nameResult = name1.compareToIgnoreCase(name2); String dtmName2 = dtm2 != null ? dtm2.getName() : null;
if (nameResult != 0) {
return nameResult; if (dtm1 == null) {
if (dtm2 != null) {
return -1;
}
}
if (dtm2 == null) {
return 1;
} }
String dtmName1 = dt1.getDataTypeManager().getName(); // Compare DataTypeManager names if datatypes have the same name
String dtmName2 = dt2.getDataTypeManager().getName(); int compare = dtmName1.compareTo(dtmName2);
if (compare == 0) {
// if they have the same name, and are in the same DTM, then compare paths // Compare category paths if they have the same name and DTM
int dtmResult = dtmName1.compareToIgnoreCase(dtmName2); String catPath1 = dt1.getCategoryPath().getPath();
if (dtmResult != 0) { String catPath2 = dt2.getCategoryPath().getPath();
return dtmResult; compare = catPath1.compareTo(catPath2);
} }
return compare;
return dt1.getPathName().compareToIgnoreCase(dt2.getPathName());
} }
// these cases are for lookups by string keys return nameCompare;
else if (o1 instanceof String && o2 instanceof DataType) {
DataType dt2 = (DataType) o2;
String name2 = dt2.getName();
return ((String) o1).compareToIgnoreCase(name2);
}
else if (o1 instanceof DataType && o2 instanceof String) {
DataType dt1 = (DataType) o1;
String name1 = dt1.getName();
return name1.compareToIgnoreCase(((String) o2));
}
return 0;
} }
} }

View file

@ -167,15 +167,19 @@ public interface DataTypeManager {
/** /**
* Begin searching at the root category for all data types with the * Begin searching at the root category for all data types with the
* given name. Places all the data types in this data type manager * given name. Places all the data types in this data type manager
* with the given name into the list. * with the given name into the list. Presence of {@code .conflict}
* @param name name of the data type * extension will be ignored for both specified name and returned
* results.
* @param name name of the data type (wildcards are not supported and will be treated
* as explicit search characters)
* @param list list that will be populated with matching DataType objects * @param list list that will be populated with matching DataType objects
*/ */
public void findDataTypes(String name, List<DataType> list); public void findDataTypes(String name, List<DataType> list);
/** /**
* Begin searching at the root category for all data types with names * Begin searching at the root category for all data types with names
* that match the given name that may contain wildcards. * that match the given name that may contain wildcards using familiar globbing
* characters '*' and '?'.
* @param name name to match; may contain wildcards * @param name name to match; may contain wildcards
* @param list list that will be populated with matching DataType objects * @param list list that will be populated with matching DataType objects
* @param caseSensitive true if the match is case sensitive * @param caseSensitive true if the match is case sensitive

View file

@ -0,0 +1,102 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.program.model.data;
import java.util.*;
import ghidra.program.database.data.DataTypeUtilities;
/**
* {@link DataTypeNameComparator} provides the preferred named-based comparison of {@link DataType}
* which handles both some degree of case-insensity as well as proper grouping and ordering of
* conflict datatypes.
*/
public class DataTypeNameComparator implements Comparator<String> {
public static final DataTypeNameComparator INSTANCE = new DataTypeNameComparator();
@Override
public int compare(String dt1Name, String dt2Name) {
String name1 = DataTypeUtilities.getNameWithoutConflict(dt1Name);
String name2 = DataTypeUtilities.getNameWithoutConflict(dt2Name);
int len1 = name1.length();
int len2 = name2.length();
int len = Math.min(len1, len2); // overlapping length
int baseNameLen = len; // Length of overlapping portion of base-name (no decorations)
// Case-insensitive compare of significant overlapping portion of name
int baseCaseCompare = 0;
for (int i = 0; i < len; i++) {
char c1 = name1.charAt(i);
char c2 = name2.charAt(i);
char lc1 = Character.toLowerCase(c1);
char lc2 = Character.toLowerCase(c2);
// first space treated as end of base-name
if (lc1 == ' ') {
if (lc2 == ' ') {
baseNameLen = i;
break;
}
return -1;
}
if (lc2 == ' ') {
return 1;
}
if (lc1 != lc2) {
return lc1 - lc2;
}
if (baseCaseCompare == 0) {
baseCaseCompare = c1 - c2;
}
}
if (len1 > baseNameLen && name1.charAt(baseNameLen) != ' ') {
return 1; // first name has longer base-name
}
if (len2 > baseNameLen && name2.charAt(baseNameLen) != ' ') {
return -1; // second name has longer base-name
}
if (baseCaseCompare != 0) {
return baseCaseCompare;
}
// Same base-name, order by conflict
int conflict1 = getConflictValue(dt1Name);
int conflict2 = getConflictValue(dt2Name);
if (conflict1 != conflict2) {
return conflict1 - conflict2;
}
return name1.compareTo(name2);
}
private int getConflictValue(String dtName) {
String conflict = DataTypeUtilities.getConflictString(dtName);
if (conflict == null) {
return -1;
}
if (conflict.length() == 0) {
return 0;
}
return Integer.parseInt(conflict);
}
}

View file

@ -0,0 +1,70 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.program.model.data;
import java.util.Comparator;
/**
* {@link DataTypeObjectComparator} provides the preferred named-based comparison of data types
* using the {@link DataTypeNameComparator} allowing a mix of {@link DataType} and/or {@link String}
* names to be compared.
*/
public class DataTypeObjectComparator implements Comparator<Object> {
public static DataTypeObjectComparator INSTANCE = new DataTypeObjectComparator();
/**
* Compare two data type names
* @param o1 the first {@link DataType} or {@link String} name to be compared.
* @param o2 the second {@link DataType} or {@link String} name to be compared.
* @return a negative integer, zero, or a positive integer as the
* first argument is less than, equal to, or greater than the
* second.
* @throws IllegalArgumentException if object types other than {@link DataType} or
* {@link String} are compared.
*/
@Override
public int compare(Object o1, Object o2) {
String name1, name2;
if (o1 instanceof DataType && o2 instanceof DataType) {
DataType dt1 = (DataType) o1;
name1 = dt1.getName();
DataType dt2 = (DataType) o2;
name2 = dt2.getName();
}
// these cases are for lookups by string keys
else if (o1 instanceof String && o2 instanceof DataType) {
name1 = (String) o1;
DataType dt2 = (DataType) o2;
name2 = dt2.getName();
}
else if (o1 instanceof DataType && o2 instanceof String) {
DataType dt1 = (DataType) o1;
name1 = dt1.getName();
name2 = (String) o2;
}
else if (o1 instanceof String && o2 instanceof String) {
name1 = (String) o1;
name2 = (String) o2;
}
else {
throw new IllegalArgumentException("Unsupported comparison " +
o1.getClass().getSimpleName() + " / " + o2.getClass().getSimpleName());
}
return DataTypeNameComparator.INSTANCE.compare(name1, name2);
}
}

View file

@ -875,7 +875,7 @@ public class StandAloneDataTypeManager extends DataTypeManagerDB implements Clos
} }
@Override @Override
protected void deleteDataTypeIDs(LinkedList<Long> deletedIds, TaskMonitor monitor) { protected void deleteDataTypeIDs(LinkedList<Long> deletedIds) {
// do nothing // do nothing
} }

View file

@ -36,7 +36,7 @@ public interface Structure extends Composite {
/** /**
* Returns the component of this structure with the indicated ordinal. * Returns the component of this structure with the indicated ordinal.
* *
* @param ordinal the ordinal of the component requested. * @param ordinal the ordinal of the component requested (numbering starts at 0).
* @return the data type component. * @return the data type component.
* @throws IndexOutOfBoundsException if the ordinal is out of bounds * @throws IndexOutOfBoundsException if the ordinal is out of bounds
*/ */
@ -151,7 +151,7 @@ public interface Structure extends Composite {
* with bit-7 (msb) of the first byte for big-endian. This is the default behavior for most * with bit-7 (msb) of the first byte for big-endian. This is the default behavior for most
* compilers. Insertion behavior may not work as expected if packing rules differ from this. * compilers. Insertion behavior may not work as expected if packing rules differ from this.
* *
* @param ordinal the ordinal of the component to be inserted. * @param ordinal the ordinal of the component to be inserted (numbering starts at 0).
* @param byteWidth the storage allocation unit width which contains the bitfield. Must be large * @param byteWidth the storage allocation unit width which contains the bitfield. Must be large
* enough to contain the "effective bit size" and corresponding bitOffset. The actual * enough to contain the "effective bit size" and corresponding bitOffset. The actual
* component size used will be recomputed during insertion. * component size used will be recomputed during insertion.
@ -305,7 +305,7 @@ public interface Structure extends Composite {
* which may not result in such undefined components. In the case of a packed structure * which may not result in such undefined components. In the case of a packed structure
* clearing is always completed without backfill. * clearing is always completed without backfill.
* *
* @param ordinal the ordinal of the component to clear. * @param ordinal the ordinal of the component to clear (numbering starts at 0).
* @throws IndexOutOfBoundsException if component ordinal is out of bounds * @throws IndexOutOfBoundsException if component ordinal is out of bounds
*/ */
public void clearComponent(int ordinal) throws IndexOutOfBoundsException; public void clearComponent(int ordinal) throws IndexOutOfBoundsException;
@ -331,7 +331,7 @@ public interface Structure extends Composite {
* NOTE: In general, it is not recommended that this method be used with non-packed * NOTE: In general, it is not recommended that this method be used with non-packed
* structures where the replaced component is a bit-field. * structures where the replaced component is a bit-field.
* *
* @param ordinal the ordinal of the component to be replaced. * @param ordinal the ordinal of the component to be replaced (numbering starts at 0).
* @param dataType the datatype to insert. If {@link DataType#DEFAULT} is specified for a packed * @param dataType the datatype to insert. If {@link DataType#DEFAULT} is specified for a packed
* structure an {@link Undefined1DataType} will be used in its place. If {@link DataType#DEFAULT} * structure an {@link Undefined1DataType} will be used in its place. If {@link DataType#DEFAULT}
* is specified for a non-packed structure this is equivelant to {@link #clearComponent(int)}, ignoring * is specified for a non-packed structure this is equivelant to {@link #clearComponent(int)}, ignoring
@ -368,7 +368,7 @@ public interface Structure extends Composite {
* NOTE: In general, it is not recommended that this method be used with non-packed * NOTE: In general, it is not recommended that this method be used with non-packed
* structures where the replaced component is a bit-field. * structures where the replaced component is a bit-field.
* *
* @param ordinal the ordinal of the component to be replaced. * @param ordinal the ordinal of the component to be replaced (numbering starts at 0).
* @param dataType the datatype to insert. If {@link DataType#DEFAULT} is specified for a packed * @param dataType the datatype to insert. If {@link DataType#DEFAULT} is specified for a packed
* structure an {@link Undefined1DataType} will be used in its place. If {@link DataType#DEFAULT} * structure an {@link Undefined1DataType} will be used in its place. If {@link DataType#DEFAULT}
* is specified for a non-packed structure this is equivelant to {@link #clearComponent(int)}, ignoring * is specified for a non-packed structure this is equivelant to {@link #clearComponent(int)}, ignoring

View file

@ -168,20 +168,28 @@ public class TypedefDataType extends GenericDataType implements TypeDef {
if (obj == this) { if (obj == this) {
return true; return true;
} }
if (obj instanceof TypeDef) { if (!(obj instanceof TypeDef td)) {
TypeDef td = (TypeDef) obj; return false;
if (isAutoNamed != td.isAutoNamed()) {
return false;
}
if (!isAutoNamed && !DataTypeUtilities.equalsIgnoreConflict(getName(), td.getName())) {
return false;
}
if (!hasSameTypeDefSettings(td)) {
return false;
}
return DataTypeUtilities.isSameOrEquivalentDataType(getDataType(), td.getDataType());
} }
return false;
if (isAutoNamed != td.isAutoNamed()) {
return false;
}
if (!isAutoNamed && !DataTypeUtilities.equalsIgnoreConflict(getName(), td.getName())) {
return false;
}
if (!hasSameTypeDefSettings(td)) {
return false;
}
DataType otherDataType = td.getDataType();
if (DataTypeUtilities.isSameDataType(dataType, otherDataType)) {
return true;
}
return dataType.isEquivalent(otherDataType);
} }
@Override @Override
@ -235,8 +243,7 @@ public class TypedefDataType extends GenericDataType implements TypeDef {
} }
TypedefDataType newTypedef = TypedefDataType newTypedef =
new TypedefDataType(typedef.getCategoryPath(), typedef.getName(), typedef.getDataType(), new TypedefDataType(typedef.getCategoryPath(), typedef.getName(), typedef.getDataType(),
typedef.getUniversalID(), typedef.getUniversalID(), typedef.getSourceArchive(), typedef.getLastChangeTime(),
typedef.getSourceArchive(), typedef.getLastChangeTime(),
typedef.getLastChangeTimeInSourceArchive(), dtm); typedef.getLastChangeTimeInSourceArchive(), dtm);
copyTypeDefSettings(typedef, newTypedef, false); copyTypeDefSettings(typedef, newTypedef, false);
newTypedef.isAutoNamed = typedef.isAutoNamed(); newTypedef.isAutoNamed = typedef.isAutoNamed();

View file

@ -33,7 +33,7 @@ public interface Union extends Composite {
* for little-endian, and with bit-7 (msb) of the first byte for big-endian. This is the * for little-endian, and with bit-7 (msb) of the first byte for big-endian. This is the
* default behavior for most compilers. Insertion behavior may not work as expected if * default behavior for most compilers. Insertion behavior may not work as expected if
* packing rules differ from this. * packing rules differ from this.
* @param ordinal the ordinal where the new datatype is to be inserted. * @param ordinal the ordinal where the new datatype is to be inserted (numbering starts at 0).
* @param baseDataType the bitfield base datatype (certain restrictions apply). * @param baseDataType the bitfield base datatype (certain restrictions apply).
* @param bitSize the declared bitfield size in bits. The effective bit size may be * @param bitSize the declared bitfield size in bits. The effective bit size may be
* adjusted based upon the specified baseDataType. * adjusted based upon the specified baseDataType.

View file

@ -0,0 +1,265 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.program.database.data;
import static org.junit.Assert.*;
import java.util.Iterator;
import org.junit.Before;
import org.junit.Test;
import generic.test.AbstractGenericTest;
import ghidra.program.model.data.*;
public class DataTypeDBReplaceTest extends AbstractGenericTest {
private DataTypeManagerDB dataMgr;
@Before
public void setUp() throws Exception {
dataMgr = new StandAloneDataTypeManager("dummyDTM");
dataMgr.startTransaction("Test");
}
@Test
public void testReplaceHandlingPointers() throws Exception {
Structure s1 = new StructureDataType(new CategoryPath("/P1"), "MyStruct1", 0, dataMgr);
s1.add(ByteDataType.dataType);
Structure sDb1 = (Structure) dataMgr.resolve(s1, null);
Structure s2 = new StructureDataType(new CategoryPath("/P2"), "MyStruct2", 0, dataMgr);
s2.add(new PointerDataType(s1)); // MyStruct1*
Structure sDb2 = (Structure) dataMgr.resolve(s2, null);
DataType ptrDb1 = sDb2.getComponent(0).getDataType(); // MyStruct1*
Structure s3 = new StructureDataType(new CategoryPath("/P3"), "MyStruct3", 0, dataMgr);
s3.add(new PointerDataType(new PointerDataType(s2))); // MyStruct2**
Structure sDb3 = (Structure) dataMgr.resolve(s3, null);
Pointer ptrDb2a = (Pointer) sDb3.getComponent(0).getDataType(); // MyStruct2**
Pointer ptrDb2b = (Pointer) ptrDb2a.getDataType(); // MyStruct2*
assertTrue(sDb3.isEquivalent(s3));
assertEquals(8, getDataTypeCount()); // include "undefined" type used during resolve
dataMgr.replaceDataType(sDb2, sDb1, false);
System.out.println("---");
assertTrue("Expected MyStruct2* to be replaced by MyStruct1*", ptrDb2b.isDeleted());
assertFalse("Expected /P2/MyStruct2** to be moved/transformed to /P1/MyStruct1**",
ptrDb2a.isDeleted());
// Pointer instance should have changed category as well as using MyStruct1*
Pointer ptrPtr = (Pointer) sDb3.getComponent(0).getDataType(); // MyStruct1**
assertTrue(ptrDb2a == ptrPtr);
assertEquals("/P1/MyStruct1 * *", ptrPtr.getPathName());
// Existing MyStruct1* pointer instance should be used and MyStruct2* removed
Pointer ptr = (Pointer) ptrPtr.getDataType(); // MyStruct1*
assertTrue(ptrDb1 == ptr);
assertEquals("/P1/MyStruct1 *", ptr.getPathName());
assertEquals(6, getDataTypeCount()); // include "undefined" type used during resolve
}
@Test
public void testReplaceHandlingArrays() throws Exception {
Structure s1 = new StructureDataType(new CategoryPath("/P1"), "MyStruct1", 0, dataMgr);
s1.add(ByteDataType.dataType);
Structure sDb1 = (Structure) dataMgr.resolve(s1, null);
Structure s2 = new StructureDataType(new CategoryPath("/P2"), "MyStruct2", 0, dataMgr);
s2.add(WordDataType.dataType);
Structure sDb2 = (Structure) dataMgr.resolve(s2, null);
Structure s3 = new StructureDataType(new CategoryPath("/P3"), "MyStruct3", 0, dataMgr);
s3.add(new ArrayDataType(s1, 3, -1)); // MyStruct1[3]
s3.add(new ArrayDataType(s2, 2, -1)); // MyStruct2[2]
s3.add(new ArrayDataType(new ArrayDataType(s2, 3, -1), 2, -1)); // MyStruct2[2][3]
Structure sDb3 = (Structure) dataMgr.resolve(s3, null);
Array aDb1_3 = (Array) sDb3.getComponent(0).getDataType(); // 0: MyStruct1[3]
Array aDb2_2 = (Array) sDb3.getComponent(1).getDataType(); // 1: MyStruct2[2]
Array aDb2_3_2 = (Array) sDb3.getComponent(2).getDataType(); // 2: MyStruct2[2][3]
Array aDb2_3 = (Array) aDb2_3_2.getDataType(); // MyStruct2[3]
//@formatter:off
assertEquals("/P3/MyStruct3\n" +
"pack(disabled)\n" +
"Structure MyStruct3 {\n" +
" 0 MyStruct1[3] 3 \"\"\n" +
" 3 MyStruct2[2] 4 \"\"\n" +
" 7 MyStruct2[2][3] 12 \"\"\n" +
"}\n" +
"Size = 19 Actual Alignment = 1\n", sDb3.toString());
//@formatter:on
assertTrue(sDb3.isEquivalent(s3));
assertEquals(9, getDataTypeCount()); // include "undefined" type used during resolve
dataMgr.replaceDataType(sDb2, sDb1, false);
System.out.println("---");
assertFalse("Expected no change", aDb1_3.isDeleted());
assertFalse("Expected MyStruct2[2] to be moved/transformed to MyStruct1[2]",
aDb2_2.isDeleted());
assertFalse("Expected MyStruct2[3][2] to be moved/transformed to MyStruct1[3][2]",
aDb2_3_2.isDeleted());
assertTrue("Expected MyStruct2[3] to be replaced by MyStruct1[3]", aDb2_3.isDeleted());
DataTypeComponent[] definedComponents = sDb3.getDefinedComponents();
assertEquals(3, definedComponents.length);
// Array instance should have changed category as well as using MyStruct1
Array a1 = (Array) definedComponents[1].getDataType(); // MyStruct1[2]
assertTrue(aDb2_2 == a1);
assertEquals("/P1/MyStruct1[2]", a1.getPathName());
// Array instance should have changed category as well as using MyStruct1[3]
Array a1a = (Array) definedComponents[2].getDataType(); // MyStruct1[3][2]
assertTrue(aDb2_3_2 == a1a);
assertEquals("/P1/MyStruct1[2][3]", a1a.getPathName());
// Existing MyStruct1[3] array instance should be used and MyStruct2[3] removed
Array a1b = (Array) a1a.getDataType(); // MyStruct1[3]
assertTrue(aDb1_3 == a1b);
assertEquals("/P1/MyStruct1[3]", a1b.getPathName());
// Component placements should not change but sizes will
//@formatter:off
assertEquals("/P3/MyStruct3\n" +
"pack(disabled)\n" +
"Structure MyStruct3 {\n" +
" 0 MyStruct1[3] 3 \"\"\n" +
" 3 MyStruct1[2] 2 \"\"\n" +
" 7 MyStruct1[2][3] 6 \"\"\n" +
"}\n" +
"Size = 19 Actual Alignment = 1\n", sDb3.toString());
//@formatter:on
assertEquals(7, getDataTypeCount()); // include "undefined" type used during resolve
}
@Test
public void testReplaceHandlingArraysPacked() throws Exception {
Structure s1 = new StructureDataType(new CategoryPath("/P1"), "MyStruct1", 0, dataMgr);
s1.setPackingEnabled(true);
s1.add(ByteDataType.dataType);
Structure sDb1 = (Structure) dataMgr.resolve(s1, null);
Structure s2 = new StructureDataType(new CategoryPath("/P2"), "MyStruct2", 0, dataMgr);
s2.setPackingEnabled(true);
s2.add(WordDataType.dataType);
Structure sDb2 = (Structure) dataMgr.resolve(s2, null);
Structure s3 = new StructureDataType(new CategoryPath("/P3"), "MyStruct3", 0, dataMgr);
s3.setPackingEnabled(true);
s3.add(new ArrayDataType(s1, 3, -1)); // MyStruct1[3]
s3.add(new ArrayDataType(s2, 2, -1)); // MyStruct2[2]
s3.add(new ArrayDataType(new ArrayDataType(s2, 3, -1), 2, -1)); // MyStruct2[2][3]
Structure sDb3 = (Structure) dataMgr.resolve(s3, null);
Array aDb1_3 = (Array) sDb3.getComponent(0).getDataType(); // 0: MyStruct1[3]
Array aDb2_2 = (Array) sDb3.getComponent(1).getDataType(); // 1: MyStruct2[2]
Array aDb2_3_2 = (Array) sDb3.getComponent(2).getDataType(); // 2: MyStruct2[2][3]
Array aDb2_3 = (Array) aDb2_3_2.getDataType(); // MyStruct2[3]
//@formatter:off
assertEquals("/P3/MyStruct3\n" +
"pack()\n" +
"Structure MyStruct3 {\n" +
" 0 MyStruct1[3] 3 \"\"\n" +
" 4 MyStruct2[2] 4 \"\"\n" +
" 8 MyStruct2[2][3] 12 \"\"\n" +
"}\n" +
"Size = 20 Actual Alignment = 2\n", sDb3.toString());
//@formatter:on
assertTrue(sDb3.isEquivalent(s3));
assertEquals(9, getDataTypeCount()); // include "undefined" type used during resolve
dataMgr.replaceDataType(sDb2, sDb1, false);
assertFalse("Expected no change", aDb1_3.isDeleted());
assertFalse("Expected MyStruct2[2] to be moved/transformed to MyStruct1[2]",
aDb2_2.isDeleted());
assertFalse("Expected MyStruct2[3][2] to be moved/transformed to MyStruct1[3][2]",
aDb2_3_2.isDeleted());
assertTrue("Expected MyStruct2[3] to be replaced by MyStruct1[3]", aDb2_3.isDeleted());
DataTypeComponent[] definedComponents = sDb3.getDefinedComponents();
assertEquals(3, definedComponents.length);
// Array instance should have changed category as well as using MyStruct1
Array a1 = (Array) definedComponents[1].getDataType(); // MyStruct1[2]
assertTrue(aDb2_2 == a1);
assertEquals("/P1/MyStruct1[2]", a1.getPathName());
// Array instance should have changed category as well as using MyStruct1[3]
Array a1a = (Array) definedComponents[2].getDataType(); // MyStruct1[3][2]
assertTrue(aDb2_3_2 == a1a);
assertEquals("/P1/MyStruct1[2][3]", a1a.getPathName());
// Existing MyStruct1[3] array instance should be used and MyStruct2[3] removed
Array a1b = (Array) a1a.getDataType(); // MyStruct1[3]
assertTrue(aDb1_3 == a1b);
assertEquals("/P1/MyStruct1[3]", a1b.getPathName());
// Structure should get repacked
//@formatter:off
assertEquals("/P3/MyStruct3\n" +
"pack()\n" +
"Structure MyStruct3 {\n" +
" 0 MyStruct1[3] 3 \"\"\n" +
" 3 MyStruct1[2] 2 \"\"\n" +
" 5 MyStruct1[2][3] 6 \"\"\n" +
"}\n" +
"Size = 11 Actual Alignment = 1\n", sDb3.toString());
//@formatter:on
assertEquals(7, getDataTypeCount()); // include "undefined" type used during resolve
}
private int getDataTypeCount() {
// NOTE: the DataTypeManager.getAllDataTypes() method will not properly detect duplicate
// datatypes if they occur due to the category-based collection which use named-based
// maps.
int cnt = 0;
Iterator<DataType> allDataTypes = dataMgr.getAllDataTypes();
while (allDataTypes.hasNext()) {
allDataTypes.next();
++cnt;
}
// Compare count with actual record count to ensure both proper maps updates and
// potential datatype duplication not reflected in count above.
assertEquals("Incomplete datatype manager update", cnt, dataMgr.getDataTypeRecordCount());
return cnt;
}
}

View file

@ -0,0 +1,80 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.program.model.data;
import java.util.Arrays;
import org.junit.Test;
import generic.test.AbstractGTest;
public class DataTypeNameComparatorTest extends AbstractGTest {
@Test
public void testDataTypeNameSort() {
String[] names = new String[] {
//@formatter:off
"int",
"INT",
"int *",
"INT *",
"int [2]",
"int * *",
"INT_PTR",
"s1 *",
"S1 *",
"S1.conflict",
"s1",
"S1",
"S1.conflict1",
"S1.conflict10",
"S1.conflict2",
"s1.conflict *",
"s1.conflict2 *",
"s1.conflict10"
//@formatter:on
};
String[] sortedNames = new String[] {
//@formatter:off
"INT",
"INT *",
"int",
"int *",
"int * *",
"int [2]",
"INT_PTR",
"S1",
"S1 *",
"S1.conflict",
"S1.conflict1",
"S1.conflict2",
"S1.conflict10",
"s1",
"s1 *",
"s1.conflict *",
"s1.conflict2 *",
"s1.conflict10"
//@formatter:on
};
Arrays.sort(names, DataTypeNameComparator.INSTANCE);
assertArraysEqualOrdered("Incorrect datatype name sort order", sortedNames, names);
}
}