Merge remote-tracking branch 'origin/GP-3632_ghidra1_DataTypeResolve--SQUASHED'

This commit is contained in:
ghidra1 2024-01-20 11:07:28 -05:00
commit 7b621c8989
57 changed files with 1728 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,16 +527,14 @@ 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;
}
// 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(); SourceArchive sourceArchive = dataType.getSourceArchive();
if (sourceArchive != null && sourceArchive.getArchiveType() == ArchiveType.BUILT_IN) { if (sourceArchive != null &&
resolvedDataType = resolveBuiltIn(dataType, currentHandler); sourceArchive.getArchiveType() == ArchiveType.BUILT_IN) {
resolvedDataType = resolveBuiltIn(dataType);
} }
else if (sourceArchive == null || dataType.getUniversalID() == null) { else if (sourceArchive == null || dataType.getUniversalID() == null) {
// if the dataType has no source or it has no ID (datatypes with no ID are // if the dataType has no source or it has no ID (datatypes with no ID are
// always local i.e. pointers) // always local i.e. pointers)
resolvedDataType = resolveDataTypeNoSource(dataType, currentHandler); resolvedDataType = resolveDataTypeNoSource(dataType);
} }
else if (!sourceArchive.getSourceArchiveID().equals(getUniversalID()) && else if (!sourceArchive.getSourceArchiveID().equals(getUniversalID()) &&
sourceArchive.getArchiveType() == ArchiveType.PROGRAM) { sourceArchive.getArchiveType() == ArchiveType.PROGRAM) {
// dataTypes from a different program don't carry over their identity. // dataTypes from a different program don't carry over their identity.
resolvedDataType = resolveDataTypeNoSource(dataType, currentHandler); resolvedDataType = resolveDataTypeNoSource(dataType);
} }
else { else {
resolvedDataType = resolveDataTypeWithSource(dataType, currentHandler); resolvedDataType = resolveDataTypeWithSource(dataType);
} }
cacheResolvedDataType(dataType, resolvedDataType); cacheResolvedDataType(dataType, resolvedDataType);
if (resolvedDataType instanceof DataTypeDB) { if (resolvedDataType instanceof DataTypeDB) {
setCachedEquivalence((DataTypeDB) resolvedDataType, dataType); 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,8 +4620,10 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
ResolvePair resolvePair = resolveQueue.pollFirst(); ResolvePair resolvePair = resolveQueue.pollFirst();
DataTypeDB resolvedDt = resolvePair.resolvedDt; DataTypeDB resolvedDt = resolvePair.resolvedDt;
try { try {
if (!resolvedDt.isDeleted()) {
resolvedDt.postPointerResolve(resolvePair.definitionDt, handler); resolvedDt.postPointerResolve(resolvePair.definitionDt, handler);
} }
}
// TODO: catch exceptions if needed // TODO: catch exceptions if needed
finally { finally {
resolvedDt.resolving = false; resolvedDt.resolving = false;
@ -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) {

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) {
if (o1 instanceof DataType && o2 instanceof DataType) {
DataType dt1 = (DataType) o1;
DataType dt2 = (DataType) o2;
String name1 = dt1.getName(); String name1 = dt1.getName();
String name2 = dt2.getName(); String name2 = dt2.getName();
// if the names are the same, then sort by the path int nameCompare = DataTypeNameComparator.INSTANCE.compare(name1, name2);
int nameResult = name1.compareToIgnoreCase(name2); if (nameCompare == 0) {
if (nameResult != 0) {
return nameResult; DataTypeManager dtm1 = dt1.getDataTypeManager();
String dtmName1 = dtm1 != null ? dtm1.getName() : null;
DataTypeManager dtm2 = dt2.getDataTypeManager();
String dtmName2 = dtm2 != null ? dtm2.getName() : null;
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()) { if (isAutoNamed != td.isAutoNamed()) {
return false; return false;
} }
if (!isAutoNamed && !DataTypeUtilities.equalsIgnoreConflict(getName(), td.getName())) { if (!isAutoNamed && !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());
DataType otherDataType = td.getDataType();
if (DataTypeUtilities.isSameDataType(dataType, otherDataType)) {
return true;
} }
return false;
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);
}
}