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

This commit is contained in:
ghidra1 2024-01-20 11:07:28 -05:00
commit 7b621c8989
57 changed files with 1728 additions and 431 deletions

View File

@ -193,9 +193,8 @@ public class DBTraceDataTypeManager extends ProgramBasedDataTypeManagerDB
}
@Override
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,17 +527,15 @@ public class ConflictHandlerTest extends AbstractGhidraHeadedIntegrationTest {
EnumDataType e = new EnumDataType(subc.getCategoryPath(), "Enum", 2);
DataType resolvedEnum =
dtm.resolve(e,
DataTypeConflictHandler.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER);
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,
DataTypeConflictHandler.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER);
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

@ -98,7 +98,7 @@ class ArrayDB extends DataTypeDB implements Array {
lock.acquire();
try {
checkIsValid();
if ( displayName == null ) {
if (displayName == null) {
displayName = DataTypeUtilities.getDisplayName(this, false);
}
return displayName;
@ -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;
if (resolvedDataType == null) {
SourceArchive sourceArchive = dataType.getSourceArchive();
if (sourceArchive != null &&
sourceArchive.getArchiveType() == ArchiveType.BUILT_IN) {
resolvedDataType = resolveBuiltIn(dataType);
}
else if (sourceArchive == null || dataType.getUniversalID() == null) {
// if the dataType has no source or it has no ID (datatypes with no ID are
// always local i.e. pointers)
resolvedDataType = resolveDataTypeNoSource(dataType);
}
else if (!sourceArchive.getSourceArchiveID().equals(getUniversalID()) &&
sourceArchive.getArchiveType() == ArchiveType.PROGRAM) {
// dataTypes from a different program don't carry over their identity.
resolvedDataType = resolveDataTypeNoSource(dataType);
}
else {
resolvedDataType = resolveDataTypeWithSource(dataType);
}
cacheResolvedDataType(dataType, resolvedDataType);
if (resolvedDataType instanceof DataTypeDB) {
setCachedEquivalence((DataTypeDB) resolvedDataType, dataType);
}
}
// TODO: delayed pointer-resolve use of "undefined *" could cause unintended
// equivalence match. May need to use an internal reserved type instead.
SourceArchive sourceArchive = dataType.getSourceArchive();
if (sourceArchive != null && sourceArchive.getArchiveType() == ArchiveType.BUILT_IN) {
resolvedDataType = resolveBuiltIn(dataType, currentHandler);
}
else if (sourceArchive == null || dataType.getUniversalID() == null) {
// if the dataType has no source or it has no ID (datatypes with no ID are
// always local i.e. pointers)
resolvedDataType = resolveDataTypeNoSource(dataType, currentHandler);
}
else if (!sourceArchive.getSourceArchiveID().equals(getUniversalID()) &&
sourceArchive.getArchiveType() == ArchiveType.PROGRAM) {
// dataTypes from a different program don't carry over their identity.
resolvedDataType = resolveDataTypeNoSource(dataType, currentHandler);
}
else {
resolvedDataType = resolveDataTypeWithSource(dataType, currentHandler);
}
cacheResolvedDataType(dataType, resolvedDataType);
if (resolvedDataType instanceof DataTypeDB) {
setCachedEquivalence((DataTypeDB) resolvedDataType, dataType);
}
return resolvedDataType;
}
finally {
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,7 +4620,9 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
ResolvePair resolvePair = resolveQueue.pollFirst();
DataTypeDB resolvedDt = resolvePair.resolvedDt;
try {
resolvedDt.postPointerResolve(resolvePair.definitionDt, handler);
if (!resolvedDt.isDeleted()) {
resolvedDt.postPointerResolve(resolvePair.definitionDt, handler);
}
}
// TODO: catch exceptions if needed
finally {
@ -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) {

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
@ -143,7 +144,7 @@ class PointerDB extends DataTypeDB implements Pointer {
lock.acquire();
try {
checkIsValid();
if ( displayName == null ) {
if (displayName == null) {
// NOTE: Pointer display name only specifies length if null base type
DataType dt = getDataType();
if (dt == null) {
@ -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) {
public int compare(DataType dt1, DataType dt2) {
String name1 = dt1.getName();
String name2 = dt2.getName();
if (o1 instanceof DataType && o2 instanceof DataType) {
DataType dt1 = (DataType) o1;
DataType dt2 = (DataType) o2;
int nameCompare = DataTypeNameComparator.INSTANCE.compare(name1, name2);
if (nameCompare == 0) {
String name1 = dt1.getName();
String name2 = dt2.getName();
DataTypeManager dtm1 = dt1.getDataTypeManager();
String dtmName1 = dtm1 != null ? dtm1.getName() : null;
// if the names are the same, then sort by the path
int nameResult = name1.compareToIgnoreCase(name2);
if (nameResult != 0) {
return nameResult;
DataTypeManager dtm2 = dt2.getDataTypeManager();
String dtmName2 = dtm2 != null ? dtm2.getName() : null;
if (dtm1 == null) {
if (dtm2 != null) {
return -1;
}
}
if (dtm2 == null) {
return 1;
}
String dtmName1 = dt1.getDataTypeManager().getName();
String dtmName2 = dt2.getDataTypeManager().getName();
// Compare DataTypeManager names if datatypes have the same name
int compare = dtmName1.compareTo(dtmName2);
if (compare == 0) {
// 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;
// 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 dt1.getPathName().compareToIgnoreCase(dt2.getPathName());
return compare;
}
// 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;
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
*/
@ -71,7 +71,7 @@ public interface Structure extends Composite {
* or null if not found.
*/
public DataTypeComponent getComponentContaining(int offset);
/**
* Gets the first non-zero-length component that starts at the specified offset.
* Note that one or more components may share the same offset when a bit-field or zero-length
@ -102,7 +102,7 @@ public interface Structure extends Composite {
}
return null;
}
/**
* Get an ordered list of components that contain the byte at the specified offset.
* Unlike {@link #getComponentAt(int)} and {@link #getComponentContaining(int)} this method will
@ -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 (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());
if (!(obj instanceof TypeDef td)) {
return false;
}
return false;
if (isAutoNamed != td.isAutoNamed()) {
return false;
}
if (!isAutoNamed && !DataTypeUtilities.equalsIgnoreConflict(getName(), td.getName())) {
return false;
}
if (!hasSameTypeDefSettings(td)) {
return false;
}
DataType otherDataType = td.getDataType();
if (DataTypeUtilities.isSameDataType(dataType, otherDataType)) {
return true;
}
return dataType.isEquivalent(otherDataType);
}
@Override
@ -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);
}
}