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

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

View file

@ -193,9 +193,8 @@ public class DBTraceDataTypeManager extends ProgramBasedDataTypeManagerDB
}
@Override
protected void deleteDataTypeIDs(LinkedList<Long> deletedIds, TaskMonitor monitor)
throws CancelledException {
trace.getCodeManager().clearData(deletedIds, monitor);
protected void deleteDataTypeIDs(LinkedList<Long> deletedIds) {
trace.getCodeManager().clearData(deletedIds, TaskMonitor.DUMMY);
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.DefaultDataTypeArchiveService;
import ghidra.app.plugin.core.datamgr.util.DataTypeComparator;
import ghidra.app.services.DataTypeManagerService;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.DataTypeManagerChangeListener;
import ghidra.program.model.data.*;
import ghidra.util.HelpLocation;
// FIXME!! TESTING
@ -83,9 +81,8 @@ public class DefaultDataTypeManagerService extends DefaultDataTypeArchiveService
public List<DataType> getSortedDataTypeList() {
List<DataType> dataTypes =
builtInDataTypesManager.getDataTypes(BuiltInSourceArchive.INSTANCE);
dataTypes.sort(new DataTypeComparator());
dataTypes.sort(DataTypeComparator.INSTANCE);
return dataTypes;
// throw new UnsupportedOperationException();
}
@Override

View file

@ -19,7 +19,6 @@ import java.util.*;
import javax.swing.SwingUtilities;
import ghidra.app.plugin.core.datamgr.util.DataTypeComparator;
import ghidra.program.model.data.*;
import ghidra.util.task.*;
@ -32,7 +31,6 @@ import ghidra.util.task.*;
public class DataTypeIndexer {
private List<DataTypeManager> dataTypeManagers = new ArrayList<>();
private List<DataType> dataTypeList = Collections.emptyList();
private Comparator<DataType> dataTypeComparator = new DataTypeComparator();
private DataTypeIndexUpdateListener listener = new DataTypeIndexUpdateListener();
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
* 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.
*/
@ -128,16 +127,13 @@ public class DataTypeIndexer {
monitor.initialize(dataTypeManagers.size());
monitor.setMessage("Preparing to index data types...");
Iterator<DataTypeManager> iterator = dataTypeManagers.iterator();
while (iterator.hasNext()) {
DataTypeManager dataTypeManager = iterator.next();
for (DataTypeManager dataTypeManager : dataTypeManagers) {
monitor.setMessage("Searching " + dataTypeManager.getName());
dataTypeManager.getAllDataTypes(list);
monitor.incrementProgress(1);
}
Collections.sort(list, dataTypeComparator);
Collections.sort(list, DataTypeComparator.INSTANCE);
}
List<DataType> getList() {

View file

@ -50,8 +50,9 @@ public class DataTypeNode extends DataTypeTreeNode {
@Override
public int compareTo(GTreeNode node) {
if (node instanceof DataTypeNode) {
return super.compareTo(node);
if (node instanceof DataTypeNode other) {
return DataTypeNameComparator.INSTANCE.compare(dataType.getName(),
other.dataType.getName());
}
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 ghidra.program.model.data.DataType;
import ghidra.program.model.data.*;
/**
* 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.
* 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.
*/
public List<DataType> getSortedDataTypeList();

View file

@ -27,8 +27,7 @@ import org.junit.Test;
import generic.test.ConcurrentTestExceptionHandler;
import ghidra.program.model.data.*;
public class StructureEditorUnlockedCellEdit2Test
extends AbstractStructureEditorTest {
public class StructureEditorUnlockedCellEdit2Test extends AbstractStructureEditorTest {
@Test
public void testF2EditKey() throws Exception {
@ -764,7 +763,7 @@ public class StructureEditorUnlockedCellEdit2Test
enter();
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());
escape();
@ -778,9 +777,9 @@ public class StructureEditorUnlockedCellEdit2Test
DataType newDt = getDataType(22);
assertEquals("simpleStructure", newDt.getDisplayName());
assertEquals(29, newDt.getLength());
assertEquals(29, getLength(22));
assertEquals(350, model.getLength());
assertEquals(12, newDt.getLength());
assertEquals(12, getLength(22));
assertEquals(333, model.getLength());
}
@Override

View file

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

View file

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

View file

@ -402,12 +402,7 @@ public class CategoryTest extends AbstractGhidraHeadedIntegrationTest {
assertTrue(dts[i].isEquivalent(newdts[i]));
}
DataType[] d = s.getDataTypes();
Arrays.sort(d, new Comparator<DataType>() {
@Override
public int compare(DataType o1, DataType o2) {
return o1.getName().compareTo(o2.getName());
}
});
Arrays.sort(d, DataTypeComparator.INSTANCE);
assertEquals(dts.length, d.length);
assertTrue(newdts[0] == d[0]);
}
@ -852,7 +847,8 @@ public class CategoryTest extends AbstractGhidraHeadedIntegrationTest {
}
@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));
}
@ -877,12 +873,13 @@ public class CategoryTest extends AbstractGhidraHeadedIntegrationTest {
@Override
public void dataTypeRemoved(DataTypeManager dtm, DataTypePath path) {
events.add(new Event("DT Removed", path.getCategoryPath(), null,
path.getDataTypeName(), null));
events.add(new Event("DT Removed", path.getCategoryPath(), null, path.getDataTypeName(),
null));
}
@Override
public void dataTypeRenamed(DataTypeManager dtm, DataTypePath oldPath, DataTypePath newPath) {
public void dataTypeRenamed(DataTypeManager dtm, DataTypePath oldPath,
DataTypePath newPath) {
DataType dataType = dtm.getDataType(newPath);
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 java.util.ArrayList;
import org.junit.*;
import ghidra.program.database.ProgramBuilder;
@ -26,6 +28,8 @@ import ghidra.program.model.data.DataTypeConflictHandler.ConflictResult;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.util.UniversalID;
import ghidra.util.UniversalIdGenerator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* 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);
DataType resolvedEnum =
dtm.resolve(e,
DataType resolvedEnum = dtm.resolve(e,
DataTypeConflictHandler.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER);
assertTrue(e.isEquivalent(resolvedEnum));
assertEquals("/subc/Enum", resolvedEnum.getPathName());
e.add("xyz", 1);
resolvedEnum =
dtm.resolve(e,
resolvedEnum = dtm.resolve(e,
DataTypeConflictHandler.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER);
assertTrue(e.isEquivalent(resolvedEnum));
assertEquals("/subc/Enum.conflict", resolvedEnum.getPathName());
@ -597,9 +599,8 @@ public class ConflictHandlerTest extends AbstractGhidraHeadedIntegrationTest {
@Test
public void testResolveArrayConflict() {
DataType array1 =
new ArrayDataType(new TypedefDataType("size_t", UnsignedIntegerDataType.dataType), 2,
-1);
DataType array1 = new ArrayDataType(
new TypedefDataType("size_t", UnsignedIntegerDataType.dataType), 2, -1);
DataType array2 =
new ArrayDataType(new TypedefDataType("size_t", IntegerDataType.dataType), 2, -1);
@ -616,6 +617,203 @@ public class ConflictHandlerTest extends AbstractGhidraHeadedIntegrationTest {
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 final UniversalID id;
@ -626,36 +824,45 @@ public class ConflictHandlerTest extends AbstractGhidraHeadedIntegrationTest {
this.archiveName = archiveName;
}
@Override
public ArchiveType getArchiveType() {
return ArchiveType.FILE;
}
@Override
public String getDomainFileID() {
return null;
}
@Override
public long getLastSyncTime() {
return 0;
}
@Override
public String getName() {
return archiveName;
}
@Override
public UniversalID getSourceArchiveID() {
return id;
}
@Override
public boolean isDirty() {
return false;
}
@Override
public void setDirtyFlag(boolean dirty) {
}
@Override
public void setLastSyncTime(long time) {
}
@Override
public void setName(String name) {
}

View file

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

View file

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

View file

@ -223,27 +223,45 @@ class ArrayDB extends DataTypeDB implements Array {
}
@Override
public boolean isEquivalent(DataType dt) {
protected boolean isEquivalent(DataType dt, DataTypeConflictHandler handler) {
if (dt == this) {
return true;
}
if (!(dt instanceof Array)) {
return false;
}
Array array = (Array) dt;
if (getNumElements() != array.getNumElements()) {
return false;
}
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;
}
if (dataType instanceof Dynamic && getElementLength() != array.getElementLength()) {
return false;
}
return true;
}
@Override
public boolean isEquivalent(DataType dt) {
return isEquivalent(dt, null);
}
@Override
public void dataTypeReplaced(DataType oldDt, DataType newDt) {
lock.acquire();
@ -255,13 +273,37 @@ class ArrayDB extends DataTypeDB implements Array {
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);
newDt.addParent(this);
String myOldName = getOldName();
int oldLength = getLength();
int oldAlignment = getAlignment();
int oldElementLength = getElementLength();
record.setLongValue(ArrayDBAdapter.ARRAY_DT_ID_COL, dataMgr.getResolvedID(newDt));
if (newDt instanceof Dynamic || newDt instanceof FactoryDataType) {
@ -276,7 +318,7 @@ class ArrayDB extends DataTypeDB implements Array {
dataMgr.dbError(e);
}
refreshName();
if (!getName().equals(myOldName)) {
if (!oldDt.getName().equals(newDt.getName())) {
notifyNameChanged(myOldName);
}
if (getLength() != oldLength || oldElementLength != getElementLength()) {

View file

@ -46,8 +46,7 @@ abstract class ArrayDBAdapter {
* @throws CancelledException task cancelled
*/
static ArrayDBAdapter getAdapter(DBHandle handle, int openMode, String tablePrefix,
TaskMonitor monitor)
throws VersionException, IOException, CancelledException {
TaskMonitor monitor) throws VersionException, IOException, CancelledException {
if (openMode == DBConstants.CREATE) {
return new ArrayDBAdapterV1(handle, tablePrefix, true);
}
@ -71,8 +70,8 @@ abstract class ArrayDBAdapter {
}
private static ArrayDBAdapter upgrade(DBHandle handle, ArrayDBAdapter oldAdapter,
String tablePrefix,
TaskMonitor monitor) throws VersionException, IOException, CancelledException {
String tablePrefix, TaskMonitor monitor)
throws VersionException, IOException, CancelledException {
DBHandle tmpHandle = new DBHandle();
long id = tmpHandle.startTransaction();
@ -116,4 +115,10 @@ abstract class ArrayDBAdapter {
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());
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_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);
return rec;
}
@ -141,4 +142,9 @@ class ArrayDBAdapterV0 extends ArrayDBAdapter {
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);
}
@Override
public int getRecordCount() {
return table.getRecordCount();
}
}

View file

@ -52,7 +52,8 @@ public abstract class BuiltinDBAdapter {
* @return new record
* @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.
@ -92,4 +93,10 @@ public abstract class BuiltinDBAdapter {
* @throws IOException if IO error occurs
*/
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;
if (create) {
table = handle.createTable(tableName, V0_SCHEMA,
new int[] { V0_BUILT_IN_CAT_COL });
table = handle.createTable(tableName, V0_SCHEMA, new int[] { V0_BUILT_IN_CAT_COL });
}
else {
table = handle.getTable(tableName);
@ -87,7 +86,8 @@ class BuiltinDBAdapterV0 extends BuiltinDBAdapter {
}
@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();
if (tableKey <= 100) {
@ -108,4 +108,9 @@ class BuiltinDBAdapterV0 extends BuiltinDBAdapter {
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;
static final int COMPOSITE_LAST_CHANGE_TIME_COL =
CompositeDBAdapterV5V6.V5V6_COMPOSITE_LAST_CHANGE_TIME_COL;
static final int COMPOSITE_PACKING_COL =
CompositeDBAdapterV5V6.V5V6_COMPOSITE_PACK_COL;
static final int COMPOSITE_PACKING_COL = CompositeDBAdapterV5V6.V5V6_COMPOSITE_PACK_COL;
static final int COMPOSITE_MIN_ALIGN_COL = CompositeDBAdapterV5V6.V5V6_COMPOSITE_MIN_ALIGN_COL;
// 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.
* @throws IOException if the database can't be accessed.
*/
@Override
public abstract RecordIterator getRecords() throws IOException;
/**
@ -272,9 +272,10 @@ abstract class CompositeDBAdapter implements DBRecordAdapter {
throws IOException;
/**
* Get the number of composite records
* Get the number of composite datatype records
* @return total number of composite records
*/
@Override
public abstract int getRecordCount();
}

View file

@ -295,8 +295,15 @@ class DataTypeComponentDB implements InternalDataTypeComponent {
return myDt.getClass() == otherDt.getClass();
}
@Override
public boolean isEquivalent(DataTypeComponent dtc) {
static boolean isEquivalent(DataTypeComponent existingDtc, 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 otherDt = dtc.getDataType();
// 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 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
@ -413,8 +429,7 @@ class DataTypeComponentDB implements InternalDataTypeComponent {
// TODO: Need to check field name and throw DuplicateNameException
// name = checkFieldName(name);
record.setString(ComponentDBAdapter.COMPONENT_FIELD_NAME_COL, name);
record.setLongValue(ComponentDBAdapter.COMPONENT_DT_ID_COL,
dataMgr.getResolvedID(dt));
record.setLongValue(ComponentDBAdapter.COMPONENT_DT_ID_COL, dataMgr.getResolvedID(dt));
record.setString(ComponentDBAdapter.COMPONENT_COMMENT_COL, comment);
updateRecord(false);
}

View file

@ -585,4 +585,33 @@ abstract class DataTypeDB extends DatabaseObject implements DataType {
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.util.ErrorHandler;
import generic.jar.ResourceFile;
import generic.stl.Pair;
import ghidra.app.plugin.core.datamgr.archive.BuiltInSourceArchive;
import ghidra.docking.settings.*;
import ghidra.framework.Application;
@ -145,19 +146,21 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
private List<InvalidatedListener> invalidatedListeners = new ArrayList<>();
protected DataTypeManagerChangeListenerHandler defaultListener =
new DataTypeManagerChangeListenerHandler();
private NameComparator nameComparator = new NameComparator();
private Comparator<DataType> nameComparator = DataTypeComparator.INSTANCE;
private int creatingDataType = 0;
protected UniversalID universalID;
private Map<UniversalID, SourceArchive> sourceArchiveMap;
private LinkedList<Long> idsToDelete = new LinkedList<>();
private LinkedList<Pair<DataType, DataType>> typesToReplace = new LinkedList<>();
private List<DataType> favoritesList = new ArrayList<>();
private IdsToDataTypeMap idsToDataTypeMap = new IdsToDataTypeMap();
private ThreadLocal<EquivalenceCache> equivalenceCache = new ThreadLocal<>();
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;
@ -971,14 +974,10 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
if (sortedDataTypes == null) {
return;
}
// find and remove exact match from sortedDataTypes list
String name = dataTypePath.getDataTypeName();
DataType compareDataType = new TypedefDataType(name, DataType.DEFAULT);
try {
compareDataType.setCategoryPath(dataTypePath.getCategoryPath());
}
catch (DuplicateNameException e) {
// will not happen - compareDataType not in dataTypeManager
}
DataType compareDataType =
new TypedefDataType(dataTypePath.getCategoryPath(), name, DataType.DEFAULT, this);
int index = Collections.binarySearch(sortedDataTypes, compareDataType, nameComparator);
if (index >= 0) {
sortedDataTypes.remove(index);
@ -995,6 +994,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
sortedDataTypes.add(index, dataType);
}
else {
// NOTE: ideally, this should never happen and may indicate presence of duplicate
sortedDataTypes.set(index, dataType);
}
}
@ -1236,40 +1236,37 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
}
resolvedDataType = getCachedResolve(dataType);
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.
if (resolvedDataType == null) {
SourceArchive sourceArchive = dataType.getSourceArchive();
if (sourceArchive != null && sourceArchive.getArchiveType() == ArchiveType.BUILT_IN) {
resolvedDataType = resolveBuiltIn(dataType, currentHandler);
if (sourceArchive != null &&
sourceArchive.getArchiveType() == ArchiveType.BUILT_IN) {
resolvedDataType = resolveBuiltIn(dataType);
}
else if (sourceArchive == null || dataType.getUniversalID() == null) {
// if the dataType has no source or it has no ID (datatypes with no ID are
// always local i.e. pointers)
resolvedDataType = resolveDataTypeNoSource(dataType, currentHandler);
resolvedDataType = resolveDataTypeNoSource(dataType);
}
else if (!sourceArchive.getSourceArchiveID().equals(getUniversalID()) &&
sourceArchive.getArchiveType() == ArchiveType.PROGRAM) {
// dataTypes from a different program don't carry over their identity.
resolvedDataType = resolveDataTypeNoSource(dataType, currentHandler);
resolvedDataType = resolveDataTypeNoSource(dataType);
}
else {
resolvedDataType = resolveDataTypeWithSource(dataType, currentHandler);
resolvedDataType = resolveDataTypeWithSource(dataType);
}
cacheResolvedDataType(dataType, resolvedDataType);
if (resolvedDataType instanceof DataTypeDB) {
setCachedEquivalence((DataTypeDB) resolvedDataType, dataType);
}
return resolvedDataType;
}
}
finally {
try {
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 {
@ -1280,13 +1277,14 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
lock.release();
}
}
return resolvedDataType;
}
private DataType resolveBuiltIn(DataType dataType, DataTypeConflictHandler handler) {
private DataType resolveBuiltIn(DataType dataType) {
if (dataType instanceof Pointer) {
// 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
@ -1309,7 +1307,8 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
"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,
@ -1394,12 +1393,14 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
if (!(dataType instanceof Enum)) {
return false;
}
// TODO: implement doReplaceWith
existingDataType.replaceWith(dataType);
}
else if (existingDataType instanceof TypedefDB) {
if (!(dataType instanceof TypeDef)) {
return false;
}
// TODO: implement doReplaceWith
existingDataType.replaceWith(dataType);
}
else {
@ -1480,8 +1481,6 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
return category.getDataTypesByBaseName(dataType.getName());
}
// Handle pointers and arrays
DataType existingDataType = category.getDataType(dataType.getName());
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
// method is short circuited such that it will return true. So it is important
// to call the isEquivalent on the existing datatype and not the dataType.
if (existingDataType != null && existingDataType.isEquivalent(dataType)) {
if (existingDataType != null &&
DataTypeDB.isEquivalent(existingDataType, dataType, currentHandler)) {
return existingDataType;
}
List<DataType> relatedByName = findDataTypesSameLocation(dataType);
for (DataType candidate : relatedByName) {
if (candidate != existingDataType && candidate.isEquivalent(dataType)) {
if (candidate != existingDataType &&
DataTypeDB.isEquivalent(candidate, dataType, currentHandler)) {
return candidate;
}
}
@ -1571,18 +1572,16 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
*
* @param dataType the dataType for which to return an equivalent dataType in
* this manager
* @param handler Used to handle collisions with dataTypes with same path and
* name that is
* @return resolved datatype
*/
private DataType resolveDataTypeNoSource(DataType dataType, DataTypeConflictHandler handler) {
private DataType resolveDataTypeNoSource(DataType dataType) {
DataType existingDataType = findEquivalentDataTypeSameLocation(dataType);
if (existingDataType != null) {
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
* this manager
* @param handler Used to handle collisions with dataTypes with same path and
* name that is
* @return resolved datatype
*/
private DataType resolveDataTypeWithSource(DataType dataType, DataTypeConflictHandler handler) {
private DataType resolveDataTypeWithSource(DataType dataType) {
SourceArchive sourceArchive = dataType.getSourceArchive();
// Do we have that dataType already resolved and associated with the source archive?
DataType existingDataType = getDataType(sourceArchive, dataType.getUniversalID());
if (existingDataType != null) {
if (!existingDataType.isEquivalent(dataType) &&
handler.shouldUpdate(dataType, existingDataType)) {
if (!DataTypeDB.isEquivalent(existingDataType, dataType, currentHandler) &&
currentHandler.shouldUpdate(dataType, existingDataType)) {
existingDataType.replaceWith(dataType);
existingDataType.setLastChangeTime(dataType.getLastChangeTime());
}
@ -1614,9 +1611,9 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
// Avoid conflict handling for types with a source which matches
// this archive, although a conflict name may still be used.
// This can occur when a semi-mirrored archive instance is used
// such as the CompositeViewerDataTypeManager which uses the same
// Archive UniversalID as the edited datatype's source.
return createDataType(dataType, sourceArchive, handler);
// such as the CompositeViewerDataTypeManager or Program Merge
// which uses the same Archive UniversalID.
return createConflictDataType(dataType, sourceArchive);
}
// 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 resolveNoEquivalentFound(dataType, sourceArchive, handler);
return resolveNoEquivalentFound(dataType, sourceArchive);
}
/**
@ -1639,11 +1636,9 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
* using the specified conflict handler.
* @param dataType datatype being resolved
* @param sourceArchive source archive associated with new type (may be null)
* @param handler datatype conflict handler
* @return resolved datatype (may be existing or newly added datatype)
*/
private DataType resolveNoEquivalentFound(DataType dataType, SourceArchive sourceArchive,
DataTypeConflictHandler handler) {
private DataType resolveNoEquivalentFound(DataType dataType, SourceArchive sourceArchive) {
if (sourceArchive != null && sourceArchive.getArchiveType() == ArchiveType.PROGRAM) {
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)
DataType existingDataType = findDataTypeSameLocation(dataType);
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
// the conflictHandler to decide what to do.
ConflictResult result = handler.resolveConflict(dataType, existingDataType);
switch (result) {
ConflictResult conflictResult = currentHandler.resolveConflict(dataType, existingDataType);
switch (conflictResult) {
case REPLACE_EXISTING: // new type replaces old conflicted type
try {
@ -1668,7 +1664,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
}
renameToUnusedConflictName(existingDataType);
DataType newDataType =
createDataType(dataType, dataType.getName(), sourceArchive, handler);
createDataType(dataType, dataType.getName(), sourceArchive, currentHandler);
try {
replace(existingDataType, newDataType);
}
@ -1684,25 +1680,22 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
}
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
return existingDataType;
}
}
private DataType createDataType(DataType dataType, SourceArchive sourceArchive,
DataTypeConflictHandler handler) {
private DataType createConflictDataType(DataType dataType, SourceArchive sourceArchive) {
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
// manager depending upon the conflict handler - check again
DataType existingDataType = findEquivalentDataTypeSameLocation(dataType);
// If there is an equivalent datatype, remove the added type and return the existing
if (existingDataType != null && existingDataType != newDataType) {
removeInternal(newDataType, TaskMonitor.DUMMY);
return existingDataType;
// NOTE: queue conflict datatype for delayed check for equivalent type.
// This is not needed for Pointer or Array which will update if/when
// referenced type gets replaced.
if (!(newDataType instanceof Pointer) && !(newDataType instanceof Array)) {
conflictQueue.add(newDataType);
}
return newDataType;
}
@ -1748,14 +1741,14 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
monitor.checkCancelled();
resolve(dt, handler);
if (isResolveCacheOwner) {
flushResolveQueue(false);
processResolveQueue(false);
}
monitor.setProgress(++i);
}
}
finally {
if (isResolveCacheOwner) {
flushResolveQueue(true);
processResolveQueue(true);
}
if (isEquivalenceCacheOwner) {
clearEquivalenceCache();
@ -1865,6 +1858,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
}
replace(existingDt, replacementDt);
if (fixupName) {
try {
long lastChangeTime = replacementDt.getLastChangeTime();
@ -1897,22 +1891,44 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
private void replace(DataType existingDt, DataType replacementDt)
throws DataTypeDependencyException {
if (existingDt == replacementDt) {
if (!contains(existingDt)) {
return;
}
DataTypePath replacedDtPath = existingDt.getDataTypePath();
long replacedId = getID(existingDt);
UniversalID id = existingDt.getUniversalID();
idsToDataTypeMap.removeDataType(existingDt.getSourceArchive(), id);
if (replacementDt.dependsOn(existingDt)) {
throw new DataTypeDependencyException("Replace failed: " +
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 {
replaceDataTypeIDs(replacedId, getID(replacementDt));
@ -1929,13 +1945,20 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
private void replaceUsesInOtherDataTypes(DataType existingDt, DataType newDt) {
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()) {
dt.dataTypeReplaced(existingDt, newDt);
}
}
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();
for (DataType dt : new ArrayList<>(sortedDataTypes)) {
List<DataType> sortedDataTypesCopy = new ArrayList<>(sortedDataTypes);
for (DataType dt : sortedDataTypesCopy) {
dt.dataTypeReplaced(existingDt, newDt);
}
}
@ -2008,17 +2031,23 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
list.add(DataType.DEFAULT);
return;
}
// ignore .conflict in both name and result matches
lock.acquire();
try {
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);
if (index < 0) {
index = -index - 1;
}
// add all matches to list
while (index < sortedDataTypes.size()) {
DataType dt = sortedDataTypes.get(index);
if (!name.equals(dt.getName())) {
if (!name.equals(DataTypeUtilities.getNameWithoutConflict(dt, false))) {
break;
}
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).
*
* @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)) {
return false;
return;
}
LinkedList<Long> deletedIds = new LinkedList<>();
long id = getID(dataType);
if (id < 0) {
return false;
return;
}
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()) {
Long l = idsToDelete.removeFirst();
id = l.longValue();
long id = idsToDelete.removeFirst();
removeUseOfDataType(id);
deletedIds.addFirst(l);
deletedIds.addFirst(id);
}
for (Long l : deletedIds) {
deleteDataType(l.longValue());
// perform actual database updates (e.g., record removal, change notifications, etc.)
for (long id : deletedIds) {
deleteDataType(id);
}
try {
deleteDataTypeIDs(deletedIds, monitor);
}
catch (CancelledException e) {
return false;
}
return true;
// Remove all uses of datatypes external to datatype manager
deleteDataTypeIDs(deletedIds);
}
private void removeUseOfDataType(long id) {
@ -2267,11 +2291,15 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
public boolean remove(DataType dataType, TaskMonitor monitor) {
lock.acquire();
try {
return removeInternal(dataType, monitor);
if (contains(dataType)) {
removeInternal(dataType);
return true;
}
}
finally {
lock.release();
}
return false;
}
@Override
@ -2358,18 +2386,29 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
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) {
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.
* @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)
throws CancelledException;
abstract protected void deleteDataTypeIDs(LinkedList<Long> deletedIds);
private void notifyDeleted(long dataTypeID) {
DataType dataType = getDataType(dataTypeID);
@ -2377,9 +2416,16 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
return;
}
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();
}
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();
List<DataType> sortedDataTypesCopy = new ArrayList<>(sortedDataTypes);
for (DataType dt : sortedDataTypesCopy) {
@ -3036,6 +3082,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
if (name == null || name.length() == 0) {
throw new IllegalArgumentException("Data type must have a valid name");
}
DataType dataType = resolve(typedef.getDataType(), getDependencyConflictHandler());
boolean isAutoNamed = typedef.isAutoNamed();
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
*
@ -4325,10 +4348,208 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
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
* 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)}
* should be invoked.
*
@ -4356,7 +4577,42 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
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 {
if (resolveQueue != null) {
DataTypeConflictHandler handler = getDependencyConflictHandler();
@ -4364,8 +4620,10 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
ResolvePair resolvePair = resolveQueue.pollFirst();
DataTypeDB resolvedDt = resolvePair.resolvedDt;
try {
if (!resolvedDt.isDeleted()) {
resolvedDt.postPointerResolve(resolvePair.definitionDt, handler);
}
}
// TODO: catch exceptions if needed
finally {
resolvedDt.resolving = false;
@ -4373,6 +4631,9 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
}
}
}
if (deactivateCache) {
return processConflictQueue(dataType);
}
}
finally {
resolveQueue = null;
@ -4380,6 +4641,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
resolveCache = null;
}
}
return dataType;
}
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> {

View file

@ -16,7 +16,7 @@
package ghidra.program.database.data;
import java.util.*;
import java.util.regex.Pattern;
import java.util.regex.*;
import ghidra.app.util.NamespaceUtils;
import ghidra.app.util.SymbolPathParser;
@ -306,7 +306,49 @@ public class DataTypeUtilities {
*/
public static String getNameWithoutConflict(DataType dataType, boolean includeCategoryPath) {
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.model.data.*;
import ghidra.program.model.data.Enum;
import ghidra.program.model.data.DataTypeConflictHandler.ConflictResult;
import ghidra.program.model.mem.MemBuffer;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.scalar.Scalar;
@ -601,7 +602,7 @@ class EnumDB extends DataTypeDB implements Enum {
}
@Override
public boolean isEquivalent(DataType dt) {
protected boolean isEquivalent(DataType dt, DataTypeConflictHandler handler) {
if (dt == this) {
return true;
}
@ -610,15 +611,26 @@ class EnumDB extends DataTypeDB implements Enum {
}
Enum enumm = (Enum) dt;
if (!DataTypeUtilities.equalsIgnoreConflict(getName(), enumm.getName()) ||
getLength() != enumm.getLength() || getCount() != enumm.getCount()) {
if (!DataTypeUtilities.equalsIgnoreConflict(getName(), enumm.getName())) {
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 true;
return isEachValueEquivalent(enumm);
}
@Override
public boolean isEquivalent(DataType dt) {
return isEquivalent(dt, null);
}
private boolean isEachValueEquivalent(Enum enumm) {
@ -895,6 +907,8 @@ class EnumDB extends DataTypeDB implements Enum {
public int getMinimumPossibleLength() {
lock.acquire();
try {
checkIsValid();
initializeIfNeeded();
if (valueMap.isEmpty()) {
return 1;
}

View file

@ -54,8 +54,7 @@ abstract class EnumDBAdapter {
* @throws CancelledException if task cancelled
*/
static EnumDBAdapter getAdapter(DBHandle handle, int openMode, String tablePrefix,
TaskMonitor monitor)
throws VersionException, IOException, CancelledException {
TaskMonitor monitor) throws VersionException, IOException, CancelledException {
if (openMode == DBConstants.CREATE) {
return new EnumDBAdapterV1(handle, tablePrefix, true);
}
@ -103,8 +102,7 @@ abstract class EnumDBAdapter {
* @throws CancelledException if task cancelled
*/
private static EnumDBAdapter upgrade(DBHandle handle, EnumDBAdapter oldAdapter,
String tablePrefix,
TaskMonitor monitor)
String tablePrefix, TaskMonitor monitor)
throws VersionException, IOException, CancelledException {
DBHandle tmpHandle = new DBHandle();
@ -216,4 +214,10 @@ abstract class EnumDBAdapter {
abstract DBRecord getRecordWithIDs(UniversalID sourceID, UniversalID datatypeID)
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;
}
@Override
public int getRecordCount() {
return 0;
}
}

View file

@ -128,4 +128,9 @@ class EnumDBAdapterV0 extends EnumDBAdapter implements RecordTranslator {
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()),
V1_ENUM_UNIVERSAL_DT_ID_COL);
for (int i = 0; i < keys.length; i++) {
DBRecord record = enumTable.getRecord(keys[i]);
for (Field key : keys) {
DBRecord record = enumTable.getRecord(key);
if (record.getLongValue(V1_ENUM_SOURCE_ARCHIVE_ID_COL) == sourceID.getValue()) {
return record;
}
@ -148,4 +148,9 @@ class EnumDBAdapterV1 extends EnumDBAdapter {
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.program.database.DBObjectCache;
import ghidra.program.model.data.*;
import ghidra.program.model.data.DataTypeConflictHandler.ConflictResult;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.FunctionSignature;
@ -198,7 +199,7 @@ class FunctionDefinitionDB extends DataTypeDB implements FunctionDefinition {
}
finally {
if (isResolveCacheOwner) {
dataMgr.flushResolveQueue(true);
dataMgr.processResolveQueue(true);
}
lock.release();
}
@ -429,12 +430,12 @@ class FunctionDefinitionDB extends DataTypeDB implements FunctionDefinition {
}
@Override
public boolean isEquivalent(DataType dataType) {
protected boolean isEquivalent(DataType dataType, DataTypeConflictHandler handler) {
if (dataType == this) {
return true;
}
if (!(dataType instanceof FunctionDefinition)) {
if (!(dataType instanceof FunctionDefinition sig)) {
return false;
}
@ -452,7 +453,18 @@ class FunctionDefinitionDB extends DataTypeDB implements FunctionDefinition {
}
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 {
dataMgr.putCachedEquivalence(this, dataType, isEquivalent);
@ -461,7 +473,12 @@ class FunctionDefinitionDB extends DataTypeDB implements FunctionDefinition {
}
@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) {
return true;
}
@ -476,10 +493,10 @@ class FunctionDefinitionDB extends DataTypeDB implements FunctionDefinition {
(hasVarArgs() == signature.hasVarArgs()) &&
(hasNoReturn() == signature.hasNoReturn())) {
ParameterDefinition[] args = signature.getArguments();
ParameterDefinition[] thisArgs = this.getArguments();
ParameterDefinitionDB[] thisArgs = this.getArguments();
if (args.length == thisArgs.length) {
for (int i = 0; i < args.length; i++) {
if (!thisArgs[i].isEquivalent(args[i])) {
if (!thisArgs[i].isEquivalent(args[i], handler)) {
return false;
}
}
@ -489,6 +506,11 @@ class FunctionDefinitionDB extends DataTypeDB implements FunctionDefinition {
return false;
}
@Override
public boolean isEquivalentSignature(FunctionSignature signature) {
return isEquivalentSignature(signature, null);
}
@Override
protected void doSetCategoryPathRecord(long categoryID) throws IOException {
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);
}
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
public boolean isEquivalent(Variable otherVar) {
if (otherVar == null) {
@ -169,20 +193,6 @@ final class ParameterDefinitionDB implements ParameterDefinition {
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
public int compareTo(ParameterDefinition p) {
return getOrdinal() - p.getOrdinal();

View file

@ -126,7 +126,8 @@ class PointerDB extends DataTypeDB implements Pointer {
return this;
}
// 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
@ -284,7 +285,7 @@ class PointerDB extends DataTypeDB implements Pointer {
}
@Override
public boolean isEquivalent(DataType dt) {
protected boolean isEquivalent(DataType dt, DataTypeConflictHandler handler) {
if (dt == null) {
return false;
}
@ -322,7 +323,7 @@ class PointerDB extends DataTypeDB implements Pointer {
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
// reality the referenced type simply has multiple implementations 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);
try {
return getDataType().isEquivalent(otherDataType);
if (handler != null) {
handler = handler.getSubsequentHandler();
}
return DataTypeDB.isEquivalent(referencedDataType, otherDataType, handler);
}
finally {
isEquivalentActive.set(false);
}
}
@Override
public boolean isEquivalent(DataType dt) {
return isEquivalent(dt, null);
}
@Override
public void dataTypeReplaced(DataType oldDt, DataType newDt) {
if (newDt == this) {
@ -351,12 +360,36 @@ class PointerDB extends DataTypeDB implements Pointer {
}
lock.acquire();
try {
String myOldName = getOldName();
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);
newDt.addParent(this);
record.setLongValue(PointerDBAdapter.PTR_DT_ID_COL, dataMgr.getResolvedID(newDt));
refreshName();
if (!oldDt.getName().equals(newDt.getName())) {
notifyNameChanged(myOldName);
}

View file

@ -163,4 +163,10 @@ abstract class PointerDBAdapter implements RecordTranslator {
* @throws IOException if the database can't be accessed.
*/
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);
}
@Override
public int getRecordCount() {
return table.getRecordCount();
}
}

View file

@ -97,4 +97,9 @@ class PointerDBAdapterV1 extends PointerDBAdapter {
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) {
return rec;
}
@Override
public int getRecordCount() {
return table.getRecordCount();
}
}

View file

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

View file

@ -24,6 +24,7 @@ import ghidra.docking.settings.Settings;
import ghidra.program.database.DBObjectCache;
import ghidra.program.model.data.*;
import ghidra.program.model.data.AlignedStructurePacker.StructurePackResult;
import ghidra.program.model.data.DataTypeConflictHandler.ConflictResult;
import ghidra.program.model.mem.MemBuffer;
import ghidra.util.Msg;
import ghidra.util.exception.AssertException;
@ -1584,7 +1585,7 @@ class StructureDB extends CompositeDB implements StructureInternal {
}
finally {
if (isResolveCacheOwner) {
dataMgr.flushResolveQueue(true);
dataMgr.processResolveQueue(true);
}
lock.release();
}
@ -1941,11 +1942,11 @@ class StructureDB extends CompositeDB implements StructureInternal {
}
@Override
public boolean isEquivalent(DataType dataType) {
protected boolean isEquivalent(DataType dataType, DataTypeConflictHandler handler) {
if (dataType == this) {
return true;
}
if (!(dataType instanceof StructureInternal)) {
if (!(dataType instanceof StructureInternal struct)) {
return false;
}
@ -1964,7 +1965,14 @@ class StructureDB extends CompositeDB implements StructureInternal {
try {
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 packing = getStoredPackingValue();
if (packing != struct.getStoredPackingValue() ||
@ -1982,10 +1990,14 @@ class StructureDB extends CompositeDB implements StructureInternal {
if (otherDefinedComponents.length != myNumComps) { // safety check
return false;
}
if (handler != null) {
handler = handler.getSubsequentHandler();
//dataMgr.getPostResolve(this);
}
for (int i = 0; i < myNumComps; i++) {
DataTypeComponent myDtc = components.get(i);
DataTypeComponent otherDtc = otherDefinedComponents[i];
if (!myDtc.isEquivalent(otherDtc)) {
if (!DataTypeComponentDB.isEquivalent(myDtc, otherDtc, handler)) {
return false;
}
}
@ -1997,6 +2009,11 @@ class StructureDB extends CompositeDB implements StructureInternal {
return true;
}
@Override
public boolean isEquivalent(DataType dt) {
return isEquivalent(dt, null);
}
/**
* Adjust length of specified component (by index) by consuming available undefined
* 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.program.database.DBObjectCache;
import ghidra.program.model.data.*;
import ghidra.program.model.data.DataTypeConflictHandler.ConflictResult;
import ghidra.program.model.mem.MemBuffer;
import ghidra.util.UniversalID;
import ghidra.util.exception.DuplicateNameException;
@ -249,29 +250,54 @@ class TypedefDB extends DataTypeDB implements TypeDef {
}
@Override
public boolean isEquivalent(DataType obj) {
if (obj == this) {
protected boolean isEquivalent(DataType dt, DataTypeConflictHandler handler) {
if (dt == this) {
return true;
}
if (obj == null || !(obj instanceof TypeDef)) {
if (dt == null || !(dt instanceof TypeDef)) {
return false;
}
TypeDef td = (TypeDef) obj;
TypeDef td = (TypeDef) dt;
validate(lock);
boolean autoNamed = isAutoNamed();
if (autoNamed != td.isAutoNamed()) {
return false;
}
if (!autoNamed && !DataTypeUtilities.equalsIgnoreConflict(getName(), td.getName())) {
return false;
}
if (!hasSameTypeDefSettings(td)) {
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 {
if (isAutoNamed()) {
return; // ignore category change if auto-naming enabled
@ -387,6 +413,7 @@ class TypedefDB extends DataTypeDB implements TypeDef {
return getDataType().getTypeDefSettingsDefinitions();
}
@Override
protected Settings doGetDefaultSettings() {
DataTypeSettingsDB settings = new DataTypeSettingsDB(dataMgr, this, key);
settings.setLock(dataMgr instanceof BuiltInDataTypeManager);

View file

@ -60,8 +60,7 @@ abstract class TypedefDBAdapter {
* @throws CancelledException if task is cancelled
*/
static TypedefDBAdapter getAdapter(DBHandle handle, int openMode, String tablePrefix,
TaskMonitor monitor)
throws VersionException, IOException, CancelledException {
TaskMonitor monitor) throws VersionException, IOException, CancelledException {
try {
return new TypedefDBAdapterV2(handle, tablePrefix, openMode == DBConstants.CREATE);
}
@ -217,4 +216,10 @@ abstract class TypedefDBAdapter {
abstract DBRecord getRecordWithIDs(UniversalID sourceID, UniversalID datatypeID)
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;
}
@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 {
Field[] keys =
table.findRecords(new LongField(datatypeID.getValue()), V1_TYPEDEF_UNIVERSAL_DT_ID_COL);
for (int i = 0; i < keys.length; i++) {
DBRecord record = table.getRecord(keys[i]);
for (Field key : keys) {
DBRecord record = table.getRecord(key);
if (record.getLongValue(V1_TYPEDEF_SOURCE_ARCHIVE_ID_COL) == sourceID.getValue()) {
return translateRecord(record);
}
@ -137,4 +137,9 @@ class TypedefDBAdapterV1 extends TypedefDBAdapter implements RecordTranslator {
oldRec.getLongValue(V1_TYPEDEF_LAST_CHANGE_TIME_COL));
return rec;
}
@Override
public int getRecordCount() {
return table.getRecordCount();
}
}

View file

@ -138,8 +138,8 @@ class TypedefDBAdapterV2 extends TypedefDBAdapter {
Field[] keys =
table.findRecords(new LongField(datatypeID.getValue()), V2_TYPEDEF_UNIVERSAL_DT_ID_COL);
for (int i = 0; i < keys.length; i++) {
DBRecord record = table.getRecord(keys[i]);
for (Field key : keys) {
DBRecord record = table.getRecord(key);
if (record.getLongValue(V2_TYPEDEF_SOURCE_ARCHIVE_ID_COL) == sourceID.getValue()) {
return record;
}
@ -147,4 +147,9 @@ class TypedefDBAdapterV2 extends TypedefDBAdapter {
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.program.database.DBObjectCache;
import ghidra.program.model.data.*;
import ghidra.program.model.data.DataTypeConflictHandler.ConflictResult;
import ghidra.program.model.mem.MemBuffer;
import ghidra.util.Msg;
@ -312,7 +313,7 @@ class UnionDB extends CompositeDB implements UnionInternal {
}
finally {
if (isResolveCacheOwner) {
dataMgr.flushResolveQueue(true);
dataMgr.processResolveQueue(true);
}
lock.release();
}
@ -722,12 +723,11 @@ class UnionDB extends CompositeDB implements UnionInternal {
}
@Override
public boolean isEquivalent(DataType dataType) {
protected boolean isEquivalent(DataType dataType, DataTypeConflictHandler handler) {
if (dataType == this) {
return true;
}
if (!(dataType instanceof UnionInternal)) {
if (!(dataType instanceof UnionInternal union)) {
return false;
}
@ -746,7 +746,14 @@ class UnionDB extends CompositeDB implements UnionInternal {
try {
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() ||
getStoredMinimumAlignment() != union.getStoredMinimumAlignment()) {
// rely on component match instead of checking length
@ -758,8 +765,11 @@ class UnionDB extends CompositeDB implements UnionInternal {
if (myComps.length != otherComps.length) {
return false;
}
if (handler != null) {
handler = handler.getSubsequentHandler();
}
for (int i = 0; i < myComps.length; i++) {
if (!myComps[i].isEquivalent(otherComps[i])) {
if (!DataTypeComponentDB.isEquivalent(myComps[i], otherComps[i], handler)) {
return false;
}
}
@ -771,6 +781,11 @@ class UnionDB extends CompositeDB implements UnionInternal {
return true;
}
@Override
public boolean isEquivalent(DataType dt) {
return isEquivalent(dt, null);
}
private void shiftOrdinals(int ordinal, int deltaOrdinal) {
for (int i = ordinal; i < components.size(); i++) {
DataTypeComponentDB dtc = components.get(i);

View file

@ -33,7 +33,7 @@ public interface DBRecordAdapter {
public RecordIterator getRecords() throws IOException;
/**
* Get the number of records in table
* Get the number of function definition datatype records
* @return total record count
*/
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
* any conflict suffixes have been removed from both the given name and the data types
* 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:
* 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.

View file

@ -49,7 +49,7 @@ public interface Composite extends DataType {
/**
* 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.
* @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.
* <BR>Note: For an aligned structure the ordinal position will get adjusted
* 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.
* @return the componentDataType created.
* @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.
* <BR>Note: For an aligned structure the ordinal position will get adjusted
* 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 length the length to associate with the 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.
* <BR>Note: For an aligned structure the ordinal position will get adjusted
* 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 length the length to associate with the 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.
* <BR>Note: Removal of bitfields from a structure with packing disabled will
* 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
*/
public void delete(int ordinal) throws IndexOutOfBoundsException;

View file

@ -17,50 +17,49 @@ package ghidra.program.model.data;
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
public int compare(Object o1, Object o2) {
if (o1 instanceof DataType && o2 instanceof DataType) {
DataType dt1 = (DataType) o1;
DataType dt2 = (DataType) o2;
public int compare(DataType dt1, DataType dt2) {
String name1 = dt1.getName();
String name2 = dt2.getName();
// if the names are the same, then sort by the path
int nameResult = name1.compareToIgnoreCase(name2);
if (nameResult != 0) {
return nameResult;
}
int nameCompare = DataTypeNameComparator.INSTANCE.compare(name1, name2);
if (nameCompare == 0) {
String dtmName1 = dt1.getDataTypeManager().getName();
String dtmName2 = dt2.getDataTypeManager().getName();
DataTypeManager dtm1 = dt1.getDataTypeManager();
String dtmName1 = dtm1 != null ? dtm1.getName() : null;
// if they have the same name, and are in the same DTM, then compare paths
int dtmResult = dtmName1.compareToIgnoreCase(dtmName2);
if (dtmResult != 0) {
return dtmResult;
}
DataTypeManager dtm2 = dt2.getDataTypeManager();
String dtmName2 = dtm2 != null ? dtm2.getName() : null;
return dt1.getPathName().compareToIgnoreCase(dt2.getPathName());
}
// these cases are for lookups by string keys
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;
if (dtm1 == null) {
if (dtm2 != null) {
return -1;
}
}
if (dtm2 == null) {
return 1;
}
// Compare DataTypeManager names if datatypes have the same name
int compare = dtmName1.compareTo(dtmName2);
if (compare == 0) {
// Compare category paths if they have the same name and DTM
String catPath1 = dt1.getCategoryPath().getPath();
String catPath2 = dt2.getCategoryPath().getPath();
compare = catPath1.compareTo(catPath2);
}
return compare;
}
return nameCompare;
}
}

View file

@ -167,15 +167,19 @@ public interface DataTypeManager {
/**
* Begin searching at the root category for all data types with the
* given name. Places all the data types in this data type manager
* with the given name into the list.
* @param name name of the data type
* with the given name into the list. Presence of {@code .conflict}
* 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
*/
public void findDataTypes(String name, List<DataType> list);
/**
* 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 list list that will be populated with matching DataType objects
* @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
protected void deleteDataTypeIDs(LinkedList<Long> deletedIds, TaskMonitor monitor) {
protected void deleteDataTypeIDs(LinkedList<Long> deletedIds) {
// do nothing
}

View file

@ -36,7 +36,7 @@ public interface Structure extends Composite {
/**
* 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.
* @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
* 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
* enough to contain the "effective bit size" and corresponding bitOffset. The actual
* 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
* 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
*/
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
* 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
* 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
@ -368,7 +368,7 @@ public interface Structure extends Composite {
* NOTE: In general, it is not recommended that this method be used with non-packed
* 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
* 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

View file

@ -168,20 +168,28 @@ public class TypedefDataType extends GenericDataType implements TypeDef {
if (obj == this) {
return true;
}
if (obj instanceof TypeDef) {
TypeDef td = (TypeDef) obj;
if (!(obj instanceof TypeDef td)) {
return false;
}
if (isAutoNamed != td.isAutoNamed()) {
return false;
}
if (!isAutoNamed && !DataTypeUtilities.equalsIgnoreConflict(getName(), td.getName())) {
return false;
}
if (!hasSameTypeDefSettings(td)) {
return false;
}
return DataTypeUtilities.isSameOrEquivalentDataType(getDataType(), td.getDataType());
DataType otherDataType = td.getDataType();
if (DataTypeUtilities.isSameDataType(dataType, otherDataType)) {
return true;
}
return false;
return dataType.isEquivalent(otherDataType);
}
@Override
@ -235,8 +243,7 @@ public class TypedefDataType extends GenericDataType implements TypeDef {
}
TypedefDataType newTypedef =
new TypedefDataType(typedef.getCategoryPath(), typedef.getName(), typedef.getDataType(),
typedef.getUniversalID(),
typedef.getSourceArchive(), typedef.getLastChangeTime(),
typedef.getUniversalID(), typedef.getSourceArchive(), typedef.getLastChangeTime(),
typedef.getLastChangeTimeInSourceArchive(), dtm);
copyTypeDefSettings(typedef, newTypedef, false);
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
* default behavior for most compilers. Insertion behavior may not work as expected if
* 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 bitSize the declared bitfield size in bits. The effective bit size may be
* 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);
}
}