From d7fc209657b7b1ef04b17d06ccc7df94677f42c9 Mon Sep 17 00:00:00 2001 From: ghidra1 Date: Fri, 6 May 2022 15:11:39 -0400 Subject: [PATCH] GP-1994 refined datatype search and add ability to specify a program's preferred root-namespace category node --- .../database/program/DBTraceProgramView.java | 15 +- .../program/DBTraceProgramViewRegisters.java | 11 + .../app/util/demangler/DemangledDataType.java | 3 + .../data/DataTypeUtilitiesFindTest.java | 201 ++++++++++ .../framework/data/DomainObjectAdapter.java | 8 +- .../framework/data/DomainObjectAdapterDB.java | 69 ---- .../ghidra/program/database/ProgramDB.java | 76 +++- .../database/data/DataTypeUtilities.java | 366 +++++++++++++++--- .../ghidra/program/model/listing/Program.java | 35 ++ .../model/listing/VariableUtilities.java | 84 ++-- .../program/model/symbol/Namespace.java | 34 +- .../program/model/ProgramTestDouble.java | 11 + 12 files changed, 734 insertions(+), 179 deletions(-) create mode 100644 Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/DataTypeUtilitiesFindTest.java diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramView.java index 47a517649d..54b5159f9f 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramView.java @@ -46,7 +46,8 @@ import ghidra.program.util.ChangeManager; import ghidra.program.util.ProgramChangeRecord; import ghidra.trace.database.DBTrace; import ghidra.trace.database.listing.*; -import ghidra.trace.database.memory.*; +import ghidra.trace.database.memory.DBTraceMemoryRegisterSpace; +import ghidra.trace.database.memory.DBTraceMemorySpace; import ghidra.trace.database.symbol.DBTraceFunctionSymbolView; import ghidra.trace.model.Trace.*; import ghidra.trace.model.TraceAddressSnapRange; @@ -1071,6 +1072,18 @@ public class DBTraceProgramView implements TraceProgramView { throw new UnsupportedOperationException(); } + @Override + public CategoryPath getPreferredRootNamespaceCategoryPath() { + // TODO: not yet implemented + return null; + } + + @Override + public void setPreferredRootNamespaceCategoryPath(String categoryPath) { + // TODO: not yet implemented + throw new UnsupportedOperationException(); + } + @Override public String getExecutablePath() { return trace.getExecutablePath(); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegisters.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegisters.java index 0856f1801e..129e0662d0 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegisters.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegisters.java @@ -26,6 +26,7 @@ import ghidra.framework.store.LockException; import ghidra.program.database.IntRangeMap; import ghidra.program.database.map.AddressMap; import ghidra.program.model.address.*; +import ghidra.program.model.data.CategoryPath; import ghidra.program.model.lang.*; import ghidra.program.model.listing.*; import ghidra.program.model.pcode.Varnode; @@ -141,6 +142,16 @@ public class DBTraceProgramViewRegisters implements TraceProgramView { view.setCompiler(compiler); } + @Override + public CategoryPath getPreferredRootNamespaceCategoryPath() { + return view.getPreferredRootNamespaceCategoryPath(); + } + + @Override + public void setPreferredRootNamespaceCategoryPath(String categoryPath) { + view.setPreferredRootNamespaceCategoryPath(categoryPath); + } + @Override public String getExecutablePath() { return view.getExecutablePath(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledDataType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledDataType.java index f21fd5ae24..02164b0173 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledDataType.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledDataType.java @@ -342,6 +342,9 @@ public class DemangledDataType extends DemangledType { static DataType findDataType(DataTypeManager dataTypeManager, Demangled namespace, String dtName) { + // TODO: add support for use of Program.getPreferredRootNamespaceCategoryPath when + // searching for datatypes + List list = new ArrayList<>(); dataTypeManager.findDataTypes(dtName, list); if (list.isEmpty()) { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/DataTypeUtilitiesFindTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/DataTypeUtilitiesFindTest.java new file mode 100644 index 0000000000..98ef202ad1 --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/DataTypeUtilitiesFindTest.java @@ -0,0 +1,201 @@ +/* ### + * 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 org.junit.*; + +import ghidra.program.database.ProgramBuilder; +import ghidra.program.database.ProgramDB; +import ghidra.program.model.data.*; +import ghidra.program.model.listing.GhidraClass; +import ghidra.program.model.symbol.*; +import ghidra.test.AbstractGhidraHeadedIntegrationTest; + +public class DataTypeUtilitiesFindTest extends AbstractGhidraHeadedIntegrationTest { + private ProgramDB program; + private DataTypeManagerDB dataMgr; + private SymbolTable symTab; + + private GhidraClass a; + private Namespace ab; + private GhidraClass aba; + private Namespace abab; + private GhidraClass ababa; + + @Before + public void setUp() throws Exception { + program = createDefaultProgram(testName.getMethodName(), ProgramBuilder._TOY, this); + dataMgr = program.getDataTypeManager(); + symTab = program.getSymbolTable(); + program.startTransaction("Test"); + + a = symTab.createClass(program.getGlobalNamespace(), "A", SourceType.USER_DEFINED); + ab = symTab.createNameSpace(a, "B", SourceType.USER_DEFINED); // A::B + aba = symTab.createClass(ab, "A", SourceType.USER_DEFINED); // A::B::A + abab = symTab.createNameSpace(aba, "B", SourceType.USER_DEFINED); // A::B::A::B + ababa = symTab.createClass(abab, "A", SourceType.USER_DEFINED); // A::B::A::B::A + + StructureDataType structA = new StructureDataType("A", 0); + + dataMgr.resolve(structA, null); + + CategoryPath cp = new CategoryPath("/x/A"); + structA.setCategoryPath(cp); + dataMgr.resolve(structA, null); + + cp = new CategoryPath("/x/A/B"); + structA.setCategoryPath(cp); + dataMgr.resolve(structA, null); + + cp = new CategoryPath("/x/A/B/A"); + structA.setCategoryPath(cp); + dataMgr.resolve(structA, null); + + cp = new CategoryPath("/x/A/B/A/B"); + structA.setCategoryPath(cp); + dataMgr.resolve(structA, null); + + cp = new CategoryPath("/x/A/B/A/B/A"); + structA.setCategoryPath(cp); + dataMgr.resolve(structA, null); + + cp = new CategoryPath("/y/A"); + structA.setCategoryPath(cp); + dataMgr.resolve(structA, null); + + cp = new CategoryPath("/y/A/B"); + structA.setCategoryPath(cp); + dataMgr.resolve(structA, null); + + cp = new CategoryPath("/y/A/B/A"); + structA.setCategoryPath(cp); + dataMgr.resolve(structA, null); + + cp = new CategoryPath("/y/A/B/A/B"); + structA.setCategoryPath(cp); + // omit struct fro category + + cp = new CategoryPath("/y/A/B/A/B/A"); + structA.setCategoryPath(cp); + dataMgr.resolve(structA, null); + + } + + @After + public void tearDown() throws Exception { + program.release(this); + } + + private void assertPath(DataType dt, String path) { + assertNotNull(dt); + assertEquals(path, dt.getPathName()); + } + + @Test + public void testFindDataType() { + + DataType dt = DataTypeUtilities.findDataType(dataMgr, program.getGlobalNamespace(), "A", + Structure.class); + assertPath(dt, "/A"); + + dt = DataTypeUtilities.findDataType(dataMgr, null, "A", null); + assertPath(dt, "/A"); + + dt = DataTypeUtilities.findDataType(dataMgr, ab, "A", Structure.class); + assertPath(dt, "/x/A/B/A"); + + dt = DataTypeUtilities.findDataType(dataMgr, aba, "A", Structure.class); + assertPath(dt, "/x/A/B/A/A"); + + program.setPreferredRootNamespaceCategoryPath("/y"); + + dt = DataTypeUtilities.findDataType(dataMgr, program.getGlobalNamespace(), "A", + Structure.class); + assertPath(dt, "/A"); + + dt = DataTypeUtilities.findDataType(dataMgr, null, "A", null); + assertPath(dt, "/A"); + + dt = DataTypeUtilities.findDataType(dataMgr, ab, "A", Structure.class); + assertPath(dt, "/y/A/B/A"); + + dt = DataTypeUtilities.findDataType(dataMgr, aba, "A", Structure.class); + assertPath(dt, "/y/A/B/A/A"); + + } + + @Test + public void findExistingClassStruct() { + + // NOTE: search gives preference to class structure found in parent-namespace + + DataType dt = DataTypeUtilities.findExistingClassStruct(dataMgr, a); + assertPath(dt, "/A"); + + dt = DataTypeUtilities.findExistingClassStruct(dataMgr, aba); // A::B::A + assertPath(dt, "/x/A/B/A"); + + dt = DataTypeUtilities.findExistingClassStruct(dataMgr, ababa); // A::B::A::B::A + assertPath(dt, "/x/A/B/A/B/A"); + + program.setPreferredRootNamespaceCategoryPath("/y"); + + dt = DataTypeUtilities.findExistingClassStruct(dataMgr, a); + assertPath(dt, "/y/A/A"); // not found in parent /y + + dt = DataTypeUtilities.findExistingClassStruct(dataMgr, aba); // A::B::A + assertPath(dt, "/y/A/B/A"); + + dt = DataTypeUtilities.findExistingClassStruct(dataMgr, ababa); // A::B::A::B::A + assertPath(dt, "/y/A/B/A/B/A/A"); // not found in parent /y/A/B/A/B + + } + + @Test + public void findNamespaceQualifiedDataType() { + + DataType dt = + DataTypeUtilities.findNamespaceQualifiedDataType(dataMgr, "A", Structure.class); + assertPath(dt, "/A"); + + dt = DataTypeUtilities.findNamespaceQualifiedDataType(dataMgr, "A", null); + assertPath(dt, "/A"); + + dt = DataTypeUtilities.findNamespaceQualifiedDataType(dataMgr, "A::A", Structure.class); + assertPath(dt, "/x/A/A"); + + dt = DataTypeUtilities.findNamespaceQualifiedDataType(dataMgr, "A::B::A", Structure.class); + assertPath(dt, "/x/A/B/A"); + + program.setPreferredRootNamespaceCategoryPath("/y"); + + dt = DataTypeUtilities.findNamespaceQualifiedDataType(dataMgr, "A", Structure.class); + assertPath(dt, "/A"); + + dt = DataTypeUtilities.findNamespaceQualifiedDataType(dataMgr, "A", null); + assertPath(dt, "/A"); + + dt = DataTypeUtilities.findNamespaceQualifiedDataType(dataMgr, "A::A", Structure.class); + assertPath(dt, "/y/A/A"); + + dt = DataTypeUtilities.findNamespaceQualifiedDataType(dataMgr, "A::B::A", Structure.class); + assertPath(dt, "/y/A/B/A"); + + } + +} diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapter.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapter.java index 8e039a42e5..adc0c0d95c 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapter.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapter.java @@ -60,8 +60,12 @@ public abstract class DomainObjectAdapter implements DomainObject { private ArrayList consumers; protected Map metadata = new LinkedHashMap(); - // A flag indicating whether the domain object has changed. Any methods of this domain object - // which cause its state to change must set this flag to true + // FIXME: (see GP-2003) "changed" flag is improperly manipulated by various methods. + // In general, comitted transactions will trigger all valid cases of setting flag to true, + // there may be a few cases where setting it to false may be appropriate. Without a transation + // it's unclear why it should ever need to get set true. + + // A flag indicating whether the domain object has changed. protected boolean changed = false; // a flag indicating that this object is temporary diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java index 29640be646..6e02f4d909 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java @@ -214,9 +214,6 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter return changeSet; } - /** - * @see db.util.ErrorHandler#dbError(java.io.IOException) - */ @Override public void dbError(IOException e) { fatalErrorOccurred = true; @@ -238,9 +235,6 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter return names; } - /** - * @see ghidra.framework.model.DomainObject#getOptions(java.lang.String) - */ @Override public Options getOptions(String propertyListName) { return new SubOptions(options, propertyListName, propertyListName + Options.DELIMITER); @@ -259,25 +253,16 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter options.performAlterations(propertyAlterations); } - /** - * @see ghidra.framework.model.DomainObject#canLock() - */ @Override public boolean canLock() { return transactionMgr.getCurrentTransaction() == null && !closed; } - /** - * @see ghidra.framework.model.DomainObject#isLocked() - */ @Override public boolean isLocked() { return transactionMgr.isLocked(); } - /** - * @see ghidra.framework.model.DomainObject#lock(String) - */ @Override public boolean lock(String reason) { return transactionMgr.lock(reason); @@ -308,17 +293,11 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter return transactionMgr.lockForSnapshot(this, hasProgress, title); } - /** - * @see ghidra.framework.model.DomainObject#forceLock(boolean, String) - */ @Override public void forceLock(boolean rollback, String reason) { transactionMgr.forceLock(rollback, reason); } - /** - * @see ghidra.framework.model.DomainObject#unlock() - */ @Override public void unlock() { transactionMgr.unlock(); @@ -336,9 +315,6 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter return startTransaction(description, null); } - /** - * @see ghidra.framework.model.UndoableDomainObject#startTransaction(java.lang.String) - */ @Override public int startTransaction(String description, AbortedTransactionListener listener) { int id = -1; @@ -359,9 +335,6 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter return id; } - /** - * @see ghidra.framework.model.UndoableDomainObject#endTransaction(int, boolean) - */ @Override public void endTransaction(int transactionID, boolean commit) { transactionMgr.endTransaction(this, transactionID, commit, true); @@ -395,65 +368,41 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter return transactionMgr.getUndoStackDepth(); } - /** - * @see ghidra.framework.model.Undoable#canRedo() - */ @Override public boolean canRedo() { return transactionMgr.canRedo(); } - /** - * @see ghidra.framework.model.Undoable#canUndo() - */ @Override public boolean canUndo() { return transactionMgr.canUndo(); } - /** - * @see ghidra.framework.model.Undoable#getRedoName() - */ @Override public String getRedoName() { return transactionMgr.getRedoName(); } - /** - * @see ghidra.framework.model.Undoable#getUndoName() - */ @Override public String getUndoName() { return transactionMgr.getUndoName(); } - /** - * @see ghidra.framework.model.UndoableDomainObject#getCurrentTransaction() - */ @Override public Transaction getCurrentTransaction() { return transactionMgr.getCurrentTransaction(); } - /** - * @see ghidra.framework.model.Undoable#redo() - */ @Override public void redo() throws IOException { transactionMgr.redo(); } - /** - * @see ghidra.framework.model.Undoable#undo() - */ @Override public void undo() throws IOException { transactionMgr.undo(); } - /** - * @see ghidra.framework.model.DomainObject#isChanged() - */ @Override public boolean isChanged() { if (dbh == null) { @@ -484,9 +433,6 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter return true; } - /** - * @see ghidra.framework.model.Undoable#clearUndo() - */ @Override public void clearUndo() { clearUndo(true); @@ -500,9 +446,6 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter options.clearCache(); } - /** - * @see ghidra.framework.model.DomainObject#canSave() - */ @Override public synchronized boolean canSave() { DomainFile df = getDomainFile(); @@ -512,9 +455,6 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter return dbh.canUpdate(); } - /** - * @see ghidra.framework.model.DomainObject#save(java.lang.String, ghidra.util.task.TaskMonitor) - */ @Override public void save(String comment, TaskMonitor monitor) throws IOException, CancelledException { if (!canSave()) { @@ -554,9 +494,6 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter } } - /** - * @see ghidra.framework.model.DomainObject#saveToPackedFile(java.io.File, ghidra.util.task.TaskMonitor) - */ @Override public void saveToPackedFile(File outputFile, TaskMonitor monitor) throws IOException, CancelledException { @@ -620,17 +557,11 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter } } - /** - * @see ghidra.framework.model.DomainObject#isClosed() - */ @Override public boolean isClosed() { return closed; } - /** - * @see ghidra.framework.model.UndoableDomainObject#hasTerminatedTransaction() - */ @Override public boolean hasTerminatedTransaction() { return transactionMgr.hasTerminatedTransaction(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java index f5445da470..2832ae5d8a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java @@ -19,6 +19,8 @@ import java.io.IOException; import java.math.BigInteger; import java.util.*; +import org.apache.commons.lang3.StringUtils; + import db.DBConstants; import db.DBHandle; import ghidra.app.plugin.processors.sleigh.SleighLanguage; @@ -46,6 +48,7 @@ import ghidra.program.database.reloc.RelocationManager; import ghidra.program.database.symbol.*; import ghidra.program.database.util.AddressSetPropertyMapDB; import ghidra.program.model.address.*; +import ghidra.program.model.data.CategoryPath; import ghidra.program.model.lang.*; import ghidra.program.model.listing.*; import ghidra.program.model.mem.MemoryBlock; @@ -208,6 +211,11 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM private Map addrSetPropertyMap = new HashMap<>(); private Map intRangePropertyMap = new HashMap<>(); + // cached program information properties + private static final String PREFERRED_ROOT_NAMESPACE_CATEGORY_PROPERTY_PATHNAME = + PROGRAM_INFO + "." + PREFERRED_ROOT_NAMESPACE_CATEGORY_PROPERTY; + private CategoryPath preferredRootNamespaceCategory; // value of PREFERRED_ROOT_NAMESPACE_CATEGORY_PROPERTY + /** * Constructs a new ProgramDB * @param name the name of the program @@ -247,7 +255,7 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM listing = new ListingDB(); changeSet = new ProgramDBChangeSet(addrMap, NUM_UNDOS); initManagers(CREATE, TaskMonitor.DUMMY); - propertiesCreate(); + createProgramInformationOptions(); programUserData = new ProgramUserDataDB(this); endTransaction(id, true); clearUndo(false); @@ -369,7 +377,8 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM changed = true; } - propertiesRestore(); + registerProgramInformationOptions(); + restoreProgramInformationOptions(); recordChanges = true; endTransaction(id, true); clearUndo(false); @@ -433,25 +442,59 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM recordChanges = true; } - private void propertiesRestore() { + private void registerProgramInformationOptions() { Options pl = getOptions(PROGRAM_INFO); - boolean origChangeState = changed; pl.registerOption(EXECUTABLE_PATH, UNKNOWN, null, "Original import path of program image"); pl.registerOption(EXECUTABLE_FORMAT, UNKNOWN, null, "Original program image format"); pl.registerOption(CREATED_WITH_GHIDRA_VERSION, "3.0 or earlier", null, "Version of Ghidra used to create this program."); pl.registerOption(DATE_CREATED, JANUARY_1_1970, null, "Date this program was created"); - changed = origChangeState; + pl.registerOption(PREFERRED_ROOT_NAMESPACE_CATEGORY_PROPERTY, "", null, + "Preferred data type category path of root namespace"); } - private void propertiesCreate() { + private void createProgramInformationOptions() { + registerProgramInformationOptions(); Options pl = getOptions(PROGRAM_INFO); - boolean origChangeState = changed; pl.setString(EXECUTABLE_PATH, UNKNOWN); pl.setString(EXECUTABLE_FORMAT, UNKNOWN); pl.setString(CREATED_WITH_GHIDRA_VERSION, Application.getApplicationVersion()); pl.setDate(DATE_CREATED, new Date()); - changed = origChangeState; + } + + private void restoreProgramInformationOptions() { + Options pl = getOptions(PROGRAM_INFO); + updatePreferredRootNamespaceCategory( + pl.getString(PREFERRED_ROOT_NAMESPACE_CATEGORY_PROPERTY, null)); + } + + protected boolean propertyChanged(String propertyName, Object oldValue, Object newValue) { + if (propertyName.equals(PREFERRED_ROOT_NAMESPACE_CATEGORY_PROPERTY_PATHNAME)) { + String path = (String) newValue; + if (!updatePreferredRootNamespaceCategory(path)) { + return false; + } + } + super.propertyChanged(propertyName, oldValue, newValue); + return true; + } + + private boolean updatePreferredRootNamespaceCategory(String path) { + if (path != null) { + path = path.trim(); + } + preferredRootNamespaceCategory = null; + if (!StringUtils.isBlank(path)) { + try { + preferredRootNamespaceCategory = new CategoryPath(path); + } + catch (Exception e) { + // ignore invalid path + Msg.error(this, "Ignoring invalid preferred root namespace category path: " + path); + return false; + } + } + return true; } void setProgramUserData(ProgramUserDataDB programUserData) { @@ -556,7 +599,17 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM public void setCompiler(String compiler) { Options pl = getOptions(PROGRAM_INFO); pl.setString(COMPILER, compiler); - changed = true; + } + + @Override + public CategoryPath getPreferredRootNamespaceCategoryPath() { + return preferredRootNamespaceCategory; + } + + @Override + public void setPreferredRootNamespaceCategoryPath(String categoryPath) { + Options pl = getOptions(PROGRAM_INFO); + pl.setString(PREFERRED_ROOT_NAMESPACE_CATEGORY_PROPERTY, categoryPath); } @Override @@ -571,7 +624,6 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM public void setExecutablePath(String path) { Options pl = getOptions(PROGRAM_INFO); pl.setString(EXECUTABLE_PATH, path); - changed = true; } @Override @@ -591,7 +643,6 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM public void setExecutableFormat(String format) { Options pl = getOptions(PROGRAM_INFO); pl.setString(EXECUTABLE_FORMAT, format); - changed = true; } @Override @@ -611,7 +662,6 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM public void setExecutableMD5(String md5) { Options pl = getOptions(PROGRAM_INFO); pl.setString(EXECUTABLE_MD5, md5); - changed = true; } @Override @@ -631,7 +681,6 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM public void setExecutableSHA256(String sha256) { Options pl = getOptions(PROGRAM_INFO); pl.setString(EXECUTABLE_SHA256, sha256); - changed = true; } @Override @@ -1738,6 +1787,7 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM for (int i = 0; i < NUM_MANAGERS; i++) { managers[i].invalidateCache(all); } + restoreProgramInformationOptions(); installExtensions(); // Reload any extensions } catch (IOException e) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeUtilities.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeUtilities.java index 166d62a211..fe4167514a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeUtilities.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeUtilities.java @@ -19,11 +19,11 @@ import java.util.*; import java.util.regex.Pattern; import ghidra.app.util.NamespaceUtils; +import ghidra.app.util.SymbolPathParser; import ghidra.docking.settings.Settings; -import ghidra.program.model.address.GlobalNamespace; import ghidra.program.model.data.*; import ghidra.program.model.data.Enum; -import ghidra.program.model.listing.Library; +import ghidra.program.model.listing.*; import ghidra.program.model.symbol.Namespace; import ghidra.util.UniversalID; import ghidra.util.exception.AssertException; @@ -354,11 +354,37 @@ public class DataTypeUtilities { : new CategoryPath(baseCategory, categoryPathParts); } + /** + * Find the structure data type which corresponds to the specified class namespace + * within the specified data type manager. + * The structure must utilize a namespace-based category path, however, + * the match criteria can be fuzzy and relies primarily on the full class namespace. + * A properly named class structure must reside within a category whose trailing + * path either matches the class namespace or the class-parent's namespace. + * Preference is given to it residing within the class-parent's namespace. + * @param dataTypeManager data type manager which should be searched. + * @param classNamespace class namespace + * @return existing structure which resides within matching category. + */ + public static Structure findExistingClassStruct(DataTypeManager dataTypeManager, GhidraClass classNamespace) { + + Structure dt = findPreferredDataType(dataTypeManager, classNamespace, + classNamespace.getName(), Structure.class, true); + if (dt != null) { + return dt; + } + + final String[] namespacePaths = getRelativeCategoryPaths(classNamespace); + + return findDataType(dataTypeManager, classNamespace.getName(), Structure.class, + categoryPath -> getCategoryMatchType(categoryPath, namespacePaths, true)); + } + /** * Attempt to find the data type whose dtName and specified namespace match a stored data type - * within the specified dataTypeManager. The best match will be returned. The namespace will be - * used in checking data type parent categories, however if no type corresponds to the namespace - * another type whose name matches may be returned. + * within the specified dataTypeManager. The first match which satisfies the category path + * requirement will be returned. If a non-root namespace is specified the datatype's trailing + * category path must match the specified namespace path. * * @param dataTypeManager data type manager * @param namespace namespace associated with dtName (null indicates no namespace constraint) @@ -366,18 +392,26 @@ public class DataTypeUtilities { * @param classConstraint optional data type interface constraint (e.g., Structure), or null * @return best matching data type */ - public static DataType findDataType(DataTypeManager dataTypeManager, Namespace namespace, - String dtName, Class classConstraint) { + public static T findDataType(DataTypeManager dataTypeManager, + Namespace namespace, String dtName, Class classConstraint) { + + T dt = + findPreferredDataType(dataTypeManager, namespace, dtName, classConstraint, false); + if (dt != null) { + return dt; + } + + final String[] namespacePaths = getRelativeCategoryPaths(namespace); + return findDataType(dataTypeManager, dtName, classConstraint, - categoryPath -> hasPreferredNamespaceCategory(categoryPath, namespace)); + categoryPath -> getCategoryMatchType(categoryPath, namespacePaths, false)); } /** * Attempt to find the data type whose dtNameWithNamespace match a stored data type within the - * specified dataTypeManager. The best match will be returned. The namespace will be used in - * checking data type parent categories, however if no type corresponds to the namespace another - * type whose name matches may be returned. NOTE: name parsing assumes :: delimiter and can be - * thrown off if name include template information which could contain namespaces. + * specified dataTypeManager. The namespace will be used in checking data type parent categories. + * NOTE: name parsing assumes :: namespace delimiter which can be thrown off if name includes + * template information which could contain namespaces (see {@link SymbolPathParser#parse(String)}). * * @param dataTypeManager data type manager * @param dtNameWithNamespace name of data type qualified with namespace (e.g., @@ -385,14 +419,34 @@ public class DataTypeUtilities { * @param classConstraint optional data type interface constraint (e.g., Structure), or null * @return best matching data type */ - public static DataType findNamespaceQualifiedDataType(DataTypeManager dataTypeManager, - String dtNameWithNamespace, Class classConstraint) { + public static T findNamespaceQualifiedDataType( + DataTypeManager dataTypeManager, + String dtNameWithNamespace, Class classConstraint) { - String[] splitName = dtNameWithNamespace.split(Namespace.DELIMITER); - String dtName = splitName[splitName.length - 1]; + List pathList = SymbolPathParser.parse(dtNameWithNamespace); + int nameIndex = pathList.size() - 1; + String dtName = pathList.get(nameIndex); + + CategoryPath rootPath = getPreferredRootNamespaceCategoryPath(dataTypeManager); + if (rootPath != null) { + List namespacePath = pathList.subList(0, nameIndex); + T dt = getAssignableDataType(dataTypeManager, rootPath, namespacePath, dtName, + classConstraint); + if (dt != null) { + return dt; + } + } + + // generate namespace path with / instead of :: separators + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < nameIndex; i++) { + buf.append(CategoryPath.DELIMITER_STRING); + buf.append(pathList.get(i)); + } + final String namespacePath = buf.toString(); // root path will have empty string return findDataType(dataTypeManager, dtName, classConstraint, - dataType -> hasPreferredNamespaceCategory(dataType, splitName)); + categoryPath -> getCategoryMatchType(categoryPath, namespacePath)); } /** @@ -410,40 +464,92 @@ public class DataTypeUtilities { return cPrimitiveNameMap.get(dataTypeName); } - private static boolean hasPreferredNamespaceCategory(DataType dataType, - String[] splitDataTypeName) { - // last element of split array is data type name and is ignored here - if (splitDataTypeName.length == 1) { - return true; + private static final int NAMESPACE_PATH_INDEX = 0; + private static final int PARENT_NAMESPACE_PATH_INDEX = 1; + + /** + * Get relative/partial category paths which corresponds to a specified namespace. + * Any {@link Library} namespace will be ignored and treated like the global namespace + * when generating a related category path. An empty string will be returned for the + * global namespace. + * @param namespace data type namespace + * @return partial two-element array with category path for namespace [NAMESPACE_PATH_INDEX] + * and parent-namespace [PARENT_NAMESPACE_PATH_INDEX]. + * A null is returned if namespace is null or the root/global namespace. + */ + private static String[] getRelativeCategoryPaths(Namespace namespace) { + if (namespace == null || namespace.isGlobal() || namespace.isLibrary()) { + return null; } - CategoryPath categoryPath = dataType.getCategoryPath(); - int index = splitDataTypeName.length - 2; - while (index >= 0) { - if (categoryPath.equals(CategoryPath.ROOT) || - !categoryPath.getName().equals(splitDataTypeName[index])) { - return false; - } - categoryPath = categoryPath.getParent(); - --index; + String[] paths = new String[2]; + StringBuilder buf = new StringBuilder(); + for (String n : namespace.getParentNamespace().getPathList(true)) { + buf.append(CategoryPath.DELIMITER_STRING); + buf.append(n); } - return true; + paths[PARENT_NAMESPACE_PATH_INDEX] = buf.toString(); + buf.append(CategoryPath.DELIMITER_STRING); + buf.append(namespace.getName()); + paths[NAMESPACE_PATH_INDEX] = buf.toString(); + return paths; } - private static boolean hasPreferredNamespaceCategory(DataType dataType, Namespace namespace) { - if (namespace == null) { - return true; + private enum CategoryMatchType { + NONE, SECONDARY, PREFERRED; + } + + /** + * Namespace category matcher. Only those datatypes contained within a catgeory + * whose trailing category path matches the specified namespacePath will be considered + * a possible match. If the namespacePath is empty array all category paths will + * be considered a match with preference given to the root category. + * @param categoryPath datatype category path + * @param namespacePath namespace path + * @return {@link CategoryMatchType#PREFERRED} if namespace match found, {@link CategoryMatchType#SECONDARY} + * if no namespace constraint specified else {@link CategoryMatchType#NONE} if namespace constraint not + * satisfied. + */ + private static CategoryMatchType getCategoryMatchType(CategoryPath categoryPath, + String namespacePath) { + if (namespacePath.length() == 0) { + // root or unspecified namespace - prefer root category + return categoryPath.isRoot() ? CategoryMatchType.PREFERRED : CategoryMatchType.SECONDARY; } - CategoryPath categoryPath = dataType.getCategoryPath(); - Namespace ns = namespace; - while (!(ns instanceof GlobalNamespace) && !(ns instanceof Library)) { - if (categoryPath.equals(CategoryPath.ROOT) || - !categoryPath.getName().equals(ns.getName())) { - return false; - } - categoryPath = categoryPath.getParent(); - ns = ns.getParentNamespace(); + String path = categoryPath.getPath(); + return path.endsWith(namespacePath) ? CategoryMatchType.PREFERRED : CategoryMatchType.NONE; + } + + /** + * Namespace category matcher. + * @param categoryPath datatype category path + * @param namespacePaths namespace paths constraint or null for no namespace. This value should + * be obtained from the {@link #getRelativeCategoryPaths(Namespace)} method. + * @param parentNamespacePreferred if true matching on parent namespace is + * enabled and preferred over match on actual namespace. This is used for + * class structure searching. + * @return {@link CategoryMatchType#PREFERRED} is returned if parentNamespacePreferred is true + * and category path matches on parent-namespace or parentNamespacePreferred is false + * and category path matches on namespace. {@link CategoryMatchType#SECONDARY} is returned + * if parentNamespacePreferred is true and category path matches on namespace. Otherwise + * {@link CategoryMatchType#NONE} is returned. + */ + private static CategoryMatchType getCategoryMatchType(CategoryPath categoryPath, + String[] namespacePaths, boolean parentNamespacePreferred) { + if (namespacePaths == null) { + // root or unspecified namespace - prefer root category + return categoryPath.isRoot() ? CategoryMatchType.PREFERRED : CategoryMatchType.SECONDARY; } - return true; + + String path = categoryPath.getPath(); + if (parentNamespacePreferred && + path.endsWith(namespacePaths[PARENT_NAMESPACE_PATH_INDEX])) { + return CategoryMatchType.PREFERRED; + } + if (path.endsWith(namespacePaths[NAMESPACE_PATH_INDEX])) { + return parentNamespacePreferred ? CategoryMatchType.SECONDARY + : CategoryMatchType.PREFERRED; + } + return CategoryMatchType.NONE; } /** @@ -451,38 +557,170 @@ public class DataTypeUtilities { * preferred namespace. */ private static interface NamespaceMatcher { - boolean isNamespaceCategoryMatch(DataType dataType); + /** + * Score category path match. + * @param path category path + * @return path match type + */ + CategoryMatchType getMatchType(CategoryPath path); } - private static DataType findDataType(DataTypeManager dataTypeManager, String dtName, - Class classConstraint, NamespaceMatcher preferredCategoryMatcher) { + private static CategoryPath getPreferredRootNamespaceCategoryPath( + DataTypeManager dataTypeManager) { + if (!(dataTypeManager instanceof ProgramBasedDataTypeManager)) { + return null; + } + ProgramBasedDataTypeManager pdtm = (ProgramBasedDataTypeManager) dataTypeManager; + Program p = pdtm.getProgram(); + return p.getPreferredRootNamespaceCategoryPath(); + } + + /** + * Get the specified datatype by full path and return only if its type corresponds to class + * constraint if specified. + * @param A standard interface which extends {@link DataType} (e.g., {@link Structure}). + * @param dataTypeManager datatype manager to query + * @param rootPath root category path + * @param namespacePath an optional namespace path to be checked under rootPath. + * If null or empty the rootPath will be checked for dtName. + * @param dtName datatype name + * @param classConstraint datatype class constraint (optional, may be null) + * @return datatype which corresponds to specified path or null if not found + */ + private static T getAssignableDataType(DataTypeManager dataTypeManager, + CategoryPath rootPath, List namespacePath, String dtName, + Class classConstraint) { + + Category category = dataTypeManager.getCategory(rootPath); + if (category == null) { + return null; + } + + if (namespacePath == null || namespacePath.isEmpty()) { + return getAssignableDataType(category, dtName, classConstraint); + } + + CategoryPath categoryPath = new CategoryPath(rootPath, namespacePath); + category = dataTypeManager.getCategory(categoryPath); + if (category == null) { + return null; + } + return getAssignableDataType(category, dtName, classConstraint); + } + + /** + * Get the specified datatype by name and category and return only if its type + * corresponds to an class constraint if specified. + * @param A standard interface which extends {@link DataType} (e.g., {@link Structure}). + * @param category datatype category to query + * @param dtName datatype name + * @param classConstraint datatype class constraint (optional, may be null) + * @return datatype which corresponds to specified path or null if not found + */ + @SuppressWarnings("unchecked") + private static T getAssignableDataType(Category category, String dtName, + Class classConstraint) { + DataType dt = category.getDataType(dtName); + if (dt != null && + (classConstraint == null || classConstraint.isAssignableFrom(dt.getClass()))) { + return (T) dt; + } + return null; + } + + /** + * Perform a preferred category namespace qualified datatype search using + * category path supplied by {@link Program#getPreferredRootNamespaceCategoryPath()}. + * Any {@link Library} namespace will be ignored and treated like the global namespace + * when generating a related category path. This method only applies to + * {@link ProgramBasedDataTypeManager} and will always return null for other + * datatype managers. + * @param dataTypeManager datatype manager + * @param namespace namespace constraint or null for no namespace. + * @param dtName datatype name + * @param classConstraint type of datatype by its interface class (e.g., {@link Structure}). + * @param parentNamespacePreferred if true matching on parent namespace is + * enabled and preferred over match on actual namespace. This is relavent for + * class structure searching. + * @return preferred datatype match if found + */ + private static T findPreferredDataType(DataTypeManager dataTypeManager, + Namespace namespace, String dtName, Class classConstraint, + boolean parentNamespacePreferred) { + CategoryPath rootPath = getPreferredRootNamespaceCategoryPath(dataTypeManager); + if (rootPath == null) { + return null; + } + + if (namespace == null || namespace.isGlobal() || namespace.isLibrary()) { + return getAssignableDataType(dataTypeManager, rootPath, null, dtName, classConstraint); + } + + if (parentNamespacePreferred) { + T dt = getAssignableDataType(dataTypeManager, rootPath, + namespace.getParentNamespace().getPathList(true), dtName, classConstraint); + if (dt != null) { + return dt; + } + } + + return getAssignableDataType(dataTypeManager, rootPath, namespace.getPathList(true), dtName, + classConstraint); + } + + /** + * Compare datatype category path lengths for sorting shortest path first. + * Tie-breaker based on path name sort. + * Rationale is to provide some deterministic datatype selection behavior and + * to allow duplicates within a hierarchical orgainzation to prefer the short + * path to reduce bad namespace matches. + */ + private static final Comparator DATATYPE_CATEGORY_PATH_LENGTH_COMPARATOR = + (DataType dt1, DataType dt2) -> { + String catPath1 = dt1.getCategoryPath().getPath(); + String catPath2 = dt2.getCategoryPath().getPath(); + int cmp = catPath1.length() - catPath2.length(); + if (cmp == 0) { + cmp = catPath1.compareTo(catPath2); + } + return cmp; + }; + + /** + * Perform a namespace qualified datatype search. + * @param dataTypeManager datatype manager + * @param dtName datatype name + * @param classConstraint type of datatype by its interface class (e.g., {@link Structure}). + * @param categoryMatcher responsible for evaluating the category path + * for a possible match with a namespace constraint. + * @return The first {@link CategoryMatchType#PREFERRED} match will be + * returned if found. If none are {@link CategoryMatchType#PREFERRED}, the first + * {@link CategoryMatchType#SECONDARY} match will be returned. Otherwise null is returned. + */ + @SuppressWarnings("unchecked") + private static T findDataType(DataTypeManager dataTypeManager, + String dtName, Class classConstraint, NamespaceMatcher categoryMatcher) { + ArrayList list = new ArrayList<>(); dataTypeManager.findDataTypes(dtName, list); + Collections.sort(list, DATATYPE_CATEGORY_PATH_LENGTH_COMPARATOR); if (!list.isEmpty()) { - //use the datatype that exists in the root category, - //otherwise just pick the first one... - DataType anyDt = null; - DataType preferredDataType = null; + T secondaryMatch = null; for (DataType existingDT : list) { if (classConstraint != null && !classConstraint.isAssignableFrom(existingDT.getClass())) { continue; } - if (preferredCategoryMatcher == null) { - if (existingDT.getCategoryPath().equals(CategoryPath.ROOT)) { - return existingDT; - } + CategoryMatchType matchType = + categoryMatcher.getMatchType(existingDT.getCategoryPath()); + if (matchType == CategoryMatchType.PREFERRED) { + return (T) existingDT; // preferred match } - if (preferredCategoryMatcher.isNamespaceCategoryMatch(existingDT)) { - preferredDataType = existingDT; + else if (secondaryMatch == null && matchType == CategoryMatchType.SECONDARY) { + secondaryMatch = (T) existingDT; } - // If all else fails return any matching name for backward compatibility - anyDt = existingDT; } - if (preferredDataType != null) { - return preferredDataType; - } - return anyDt; + return secondaryMatch; } return null; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/Program.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/Program.java index ad2ae1ca5b..948d01fcbd 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/Program.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/Program.java @@ -19,6 +19,7 @@ import java.util.Date; import ghidra.framework.store.LockException; import ghidra.program.database.IntRangeMap; +import ghidra.program.database.data.DataTypeUtilities; import ghidra.program.database.map.AddressMap; import ghidra.program.model.address.*; import ghidra.program.model.data.*; @@ -60,6 +61,9 @@ public interface Program extends DataTypeManagerDomainObject { public static final String DATE_CREATED = "Date Created"; /** Name of ghidra version property */ public static final String CREATED_WITH_GHIDRA_VERSION = "Created With Ghidra Version"; + /** Name of ghidra preferred root namespace category property */ + public static final String PREFERRED_ROOT_NAMESPACE_CATEGORY_PROPERTY = + "Preferred Root Namespace Category"; /** Creation date to ask for analysis */ public static final String ANALYSIS_START_DATE = "2007-Jan-01"; /** Format string of analysis date */ @@ -160,6 +164,37 @@ public interface Program extends DataTypeManagerDomainObject { */ public void setCompiler(String compiler); + /** + * Gets the preferred root data type category path which corresponds + * to the global namespace of a namespace-based storage area. Preference + * will be given to this category when searching for data types + * within a specific namespace. + * + * This setting corresponds to the Program Information option + * "Preferred Root Namespace Category. See {@link DataTypeUtilities} + * and its various find methods for its usage details. + * + * @return data type category path for root namespace or null if not set or is invalid. + */ + public CategoryPath getPreferredRootNamespaceCategoryPath(); + + /** + * Sets the preferred data type category path which corresponds + * to the root of a namespace hierarchy storage area. Preference + * will be given to this category when searching for data types + * within a specific namespace. + * + * This setting corresponds to the Program Information option + * "Preferred Root Namespace Category. See {@link DataTypeUtilities} + * and its various find methods for its usage details. + * + * @param categoryPath data type category path for root namespace or null + * to clear option. The specified path must be absolute and start with "/" + * and must not end with one (e.g., /ClassDataTypes). An invalid + * path setting will be ignored. + */ + public void setPreferredRootNamespaceCategoryPath(String categoryPath); + /** * Gets the path to the program's executable file. * For example, C:\Temp\test.exe. diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/VariableUtilities.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/VariableUtilities.java index d96cf06d44..781317ce39 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/VariableUtilities.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/VariableUtilities.java @@ -43,7 +43,7 @@ public class VariableUtilities { /** * Get a precedence value for the specified variable. * This value can be used to assist with LocalVariable.compareTo(Variable var) - * @param var + * @param var function variable * @return numeric precedence */ public static int getPrecedence(Variable var) { @@ -76,8 +76,8 @@ public class VariableUtilities { * Compare storage varnodes for two lists of variables. No check is done to ensure that * storage is considered good/valid (i.e., BAD_STORAGE, UNASSIGNED_STORAGE and VOID_STORAGE * all have an empty varnode list and would be considered a match) - * @param vars - * @param otherVars + * @param vars function variables + * @param otherVars other function variables * @return true if the exact sequence of variable storage varnodes matches across two lists of variables. */ public static boolean storageMatches(List vars, List otherVars) { @@ -96,8 +96,8 @@ public class VariableUtilities { * Compare storage varnodes for two lists of variables. No check is done to ensure that * storage is considered good/valid (i.e., BAD_STORAGE, UNASSIGNED_STORAGE and VOID_STORAGE * all have an empty varnode list and would be considered a match) - * @param vars - * @param otherVars + * @param vars function variables + * @param otherVars other function variables * @return true if the exact sequence of variable storage varnodes matches across two lists of variables. */ public static boolean storageMatches(List vars, Variable... otherVars) { @@ -116,8 +116,8 @@ public class VariableUtilities { /** * Compare two variables without using the instance specific compareTo method. - * @param v1 - * @param v2 + * @param v1 a function variable + * @param v2 another function variable * @return a negative value if v1 < v2, 0 if equal, and * positive if v1 > v2 */ @@ -162,8 +162,8 @@ public class VariableUtilities { /** * Determine the appropriate data type for an automatic parameter - * @param function - * @param returnDataType + * @param function function whose auto param datatype is to be determined + * @param returnDataType function's return datatype * @param storage variable storage for an auto-parameter (isAutoStorage should be true) * @return auto-parameter data type */ @@ -174,7 +174,7 @@ public class VariableUtilities { if (autoParameterType == AutoParameterType.THIS) { DataType classStruct = findOrCreateClassStruct(function); if (classStruct == null) { - classStruct = DataType.VOID; + classStruct = VoidDataType.dataType; } return getPointer(function.getProgram(), classStruct, storage.size()); } @@ -197,7 +197,7 @@ public class VariableUtilities { * @param storage variable storage whose size must match the specified data type size * @param dataType a datatype checked using {@link #checkDataType(DataType, boolean, int, Program)} * @param allowSizeMismatch if true size mismatch will be ignore - * @throws InvalidInputException + * @throws InvalidInputException if specified storage is not suitable for datatype */ public static void checkStorage(VariableStorage storage, DataType dataType, boolean allowSizeMismatch) throws InvalidInputException { @@ -212,7 +212,7 @@ public class VariableUtilities { * @param dataType a datatype checked using {@link #checkDataType(DataType, boolean, int, Program)} * @param allowSizeMismatch if true size mismatch will be ignore * @return original storage or resized storage with the correct size. - * @throws InvalidInputException + * @throws InvalidInputException if specified storage is not suitable for datatype */ public static VariableStorage checkStorage(Function function, VariableStorage storage, DataType dataType, boolean allowSizeMismatch) throws InvalidInputException { @@ -363,10 +363,10 @@ public class VariableUtilities { * Perform resize variable storage to desired newSize. This method has limited ability to grow * storage if current storage does not have a stack component or if other space constraints * are exceeded. - * @param curStorage - * @param dataType + * @param curStorage current variable storage + * @param dataType variable datatype * @param alignStack if false no attempt is made to align stack usage for big-endian - * @param function + * @param function function which corresponds to resized variable storage * @return resize storage * @throws InvalidInputException if unable to resize storage to specified size. */ @@ -572,10 +572,10 @@ public class VariableUtilities { /** * Check for variable storage conflict and optionally remove conflicting variables. - * @param function + * @param function function which corresponds to specified variable * @param var existing function variable or null for new variable * @param newStorage new/updated variable storage - * @param deleteConflictingVariables + * @param deleteConflictingVariables if true function's conflicting variables may be deleted * @throws VariableSizeException if deleteConflictingVariables is false and another variable conflicts */ public static void checkVariableConflict(Function function, Variable var, @@ -616,9 +616,9 @@ public class VariableUtilities { /** * Check for variable storage conflict and optionally remove conflicting variables. * @param existingVariables variables to check (may contain null entries) - * @param var - * @param newStorage - * @throws VariableSizeException + * @param var function variable + * @param conflictHandler variable conflict handler + * @param newStorage variable storage * @throws VariableSizeException if another variable conflicts */ public static void checkVariableConflict(List existingVariables, @@ -685,7 +685,7 @@ public class VariableUtilities { /** * Determine the minimum stack offset for parameters - * @param function + * @param function function whose stack use is to be examined * @return stack parameter offset or null if it could not be determined */ public static Integer getBaseStackParamOffset(Function function) { @@ -712,7 +712,8 @@ public class VariableUtilities { /** * Generate a suitable 'this' parameter for the specified function - * @param function + * @param function function for which a this parameter is to be generated + * @param convention function calling convention * @return this parameter or null of calling convention is not a 'thiscall' * or some other error prevents it * @deprecated should rely on auto-param instead - try not to use this method which may be eliminated @@ -769,10 +770,18 @@ public class VariableUtilities { /** * Find the structure data type which corresponds to the specified class namespace * within the specified data type manager. + * * The preferred structure will utilize a namespace-based category path, however, * the match criteria can be fuzzy and relies primarily on the class name. - * While a new empty structure may be returned, it will not be added to the program's data type - * manager. + * A properly named class structure must reside within a category whose trailing + * path either matches the class namespace or the class-parent's namespace. + * Preference is given to it residing within the class-parent's namespace. + * + * If a match is not found an empty placeholder structure will be instantiated + * and returned. A newly instantiated structure will not be added to the data type manager + * and may refer to a non-existing category path which corresponds to the class-parent's + * namespace. + * * @param classNamespace class namespace * @param dataTypeManager data type manager which should be searched and whose * data organization should be used. @@ -789,9 +798,19 @@ public class VariableUtilities { /** * Find the structure data type which corresponds to the specified function's class namespace - * within the function's program. One will be instantiated if not found. + * within the function's program. + * * The preferred structure will utilize a namespace-based category path, however, * the match criteria can be fuzzy and relies primarily on the class name. + * A properly named class structure must reside within a category whose trailing + * path either matches the class namespace or the class-parent's namespace. + * Preference is given to it residing within the class-parent's namespace. + * + * If a match is not found an empty placeholder structure will be instantiated + * and returned. A newly instantiated structure will not be added to the data type manager + * and may refer to a non-existing category path which corresponds to the class-parent's + * namespace. + * * @param function function's whose class namespace is the basis for the structure * @return new or existing structure whose name matches the function's class namespace or * null if function not contained within a class namespace. @@ -807,9 +826,14 @@ public class VariableUtilities { /** * Find the structure data type which corresponds to the specified class namespace - * within the specified data type manager. . + * within the specified data type manager. + * * The preferred structure will utilize a namespace-based category path, however, * the match criteria can be fuzzy and relies primarily on the class name. + * A properly named class structure must reside within a category whose trailing + * path either matches the class namespace or the class-parent's namespace. + * Preference is given to it residing within the class-parent's namespace. + * * @param classNamespace class namespace * @param dataTypeManager data type manager which should be searched. * @return existing structure whose name matches the specified class namespace @@ -817,15 +841,19 @@ public class VariableUtilities { */ public static Structure findExistingClassStruct(GhidraClass classNamespace, DataTypeManager dataTypeManager) { - return (Structure) DataTypeUtilities.findDataType(dataTypeManager, - classNamespace.getParentNamespace(), classNamespace.getName(), Structure.class); + return DataTypeUtilities.findExistingClassStruct(dataTypeManager, classNamespace); } /** * Find the structure data type which corresponds to the specified function's class namespace * within the function's program. + * * The preferred structure will utilize a namespace-based category path, however, * the match criteria can be fuzzy and relies primarily on the class name. + * A properly named class structure must reside within a category whose trailing + * path either matches the class namespace or the class-parent's namespace. + * Preference is given to it residing within the class-parent's namespace. + * * @param func the function. * @return existing structure whose name matches the specified function's class namespace * or null if not found. diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/Namespace.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/Namespace.java index 10369b3560..678480c8b6 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/Namespace.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/Namespace.java @@ -15,6 +15,8 @@ */ package ghidra.program.model.symbol; +import java.util.*; + import ghidra.program.model.address.AddressSetView; import ghidra.program.model.listing.CircularDependencyException; import ghidra.util.exception.DuplicateNameException; @@ -24,6 +26,7 @@ import ghidra.util.exception.InvalidInputException; * The Namespace interface */ public interface Namespace { + static final long GLOBAL_NAMESPACE_ID = 0; /** * The delimiter that is used to separate namespace nodes in a namespace @@ -63,6 +66,24 @@ public interface Namespace { */ public String getName(boolean includeNamespacePath); + /** + * Get the namespace path as a list of namespace names. + * @param omitLibrary if true Library name (if applicable) will be + * omitted from returned list and treated same as global namespace. + * @return namespace path list or empty list for global namespace + */ + public default List getPathList(boolean omitLibrary) { + if (isGlobal()) { + return Collections.emptyList(); + } + ArrayDeque list = new ArrayDeque<>(); + for (Namespace n = this; !n.isGlobal() && !(omitLibrary && n.isLibrary()); n = + n.getParentNamespace()) { + list.addFirst(n.getName()); + } + return List.copyOf(list); + } + /** * Return the namespace id * @return the namespace id @@ -96,11 +117,20 @@ public interface Namespace { throws DuplicateNameException, InvalidInputException, CircularDependencyException; /** - * Return true if this is the global namespace; - * @return true if this is the global namespace; + * Return true if this is the global namespace + * @return true if this is the global namespace */ public default boolean isGlobal() { return getID() == GLOBAL_NAMESPACE_ID; } + /** + * Return true if this is a library + * @return true if this is a library + */ + public default boolean isLibrary() { + Symbol s = getSymbol(); + return s != null && s.getSymbolType() == SymbolType.LIBRARY; + } + } diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/ProgramTestDouble.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/ProgramTestDouble.java index 79fb37a1ef..692fada05a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/ProgramTestDouble.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/ProgramTestDouble.java @@ -26,6 +26,7 @@ import ghidra.program.database.IntRangeMap; import ghidra.program.database.data.ProgramDataTypeManager; import ghidra.program.database.map.AddressMap; import ghidra.program.model.address.*; +import ghidra.program.model.data.CategoryPath; import ghidra.program.model.lang.*; import ghidra.program.model.listing.*; import ghidra.program.model.mem.Memory; @@ -376,6 +377,16 @@ public class ProgramTestDouble implements Program { throw new UnsupportedOperationException(); } + @Override + public CategoryPath getPreferredRootNamespaceCategoryPath() { + return null; + } + + @Override + public void setPreferredRootNamespaceCategoryPath(String categoryPath) { + throw new UnsupportedOperationException(); + } + @Override public String getExecutablePath() { throw new UnsupportedOperationException();