GP-1994 refined datatype search and add ability to specify a program's

preferred root-namespace category node
This commit is contained in:
ghidra1 2022-05-06 15:11:39 -04:00
parent 4b600847eb
commit d7fc209657
12 changed files with 734 additions and 179 deletions

View file

@ -46,7 +46,8 @@ import ghidra.program.util.ChangeManager;
import ghidra.program.util.ProgramChangeRecord; import ghidra.program.util.ProgramChangeRecord;
import ghidra.trace.database.DBTrace; import ghidra.trace.database.DBTrace;
import ghidra.trace.database.listing.*; 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.database.symbol.DBTraceFunctionSymbolView;
import ghidra.trace.model.Trace.*; import ghidra.trace.model.Trace.*;
import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.TraceAddressSnapRange;
@ -1071,6 +1072,18 @@ public class DBTraceProgramView implements TraceProgramView {
throw new UnsupportedOperationException(); 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 @Override
public String getExecutablePath() { public String getExecutablePath() {
return trace.getExecutablePath(); return trace.getExecutablePath();

View file

@ -26,6 +26,7 @@ import ghidra.framework.store.LockException;
import ghidra.program.database.IntRangeMap; import ghidra.program.database.IntRangeMap;
import ghidra.program.database.map.AddressMap; import ghidra.program.database.map.AddressMap;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.model.pcode.Varnode; import ghidra.program.model.pcode.Varnode;
@ -141,6 +142,16 @@ public class DBTraceProgramViewRegisters implements TraceProgramView {
view.setCompiler(compiler); view.setCompiler(compiler);
} }
@Override
public CategoryPath getPreferredRootNamespaceCategoryPath() {
return view.getPreferredRootNamespaceCategoryPath();
}
@Override
public void setPreferredRootNamespaceCategoryPath(String categoryPath) {
view.setPreferredRootNamespaceCategoryPath(categoryPath);
}
@Override @Override
public String getExecutablePath() { public String getExecutablePath() {
return view.getExecutablePath(); return view.getExecutablePath();

View file

@ -342,6 +342,9 @@ public class DemangledDataType extends DemangledType {
static DataType findDataType(DataTypeManager dataTypeManager, Demangled namespace, static DataType findDataType(DataTypeManager dataTypeManager, Demangled namespace,
String dtName) { String dtName) {
// TODO: add support for use of Program.getPreferredRootNamespaceCategoryPath when
// searching for datatypes
List<DataType> list = new ArrayList<>(); List<DataType> list = new ArrayList<>();
dataTypeManager.findDataTypes(dtName, list); dataTypeManager.findDataTypes(dtName, list);
if (list.isEmpty()) { if (list.isEmpty()) {

View file

@ -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");
}
}

View file

@ -60,8 +60,12 @@ public abstract class DomainObjectAdapter implements DomainObject {
private ArrayList<Object> consumers; private ArrayList<Object> consumers;
protected Map<String, String> metadata = new LinkedHashMap<String, String>(); protected Map<String, String> metadata = new LinkedHashMap<String, String>();
// A flag indicating whether the domain object has changed. Any methods of this domain object // FIXME: (see GP-2003) "changed" flag is improperly manipulated by various methods.
// which cause its state to change must set this flag to true // 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; protected boolean changed = false;
// a flag indicating that this object is temporary // a flag indicating that this object is temporary

View file

@ -214,9 +214,6 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
return changeSet; return changeSet;
} }
/**
* @see db.util.ErrorHandler#dbError(java.io.IOException)
*/
@Override @Override
public void dbError(IOException e) { public void dbError(IOException e) {
fatalErrorOccurred = true; fatalErrorOccurred = true;
@ -238,9 +235,6 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
return names; return names;
} }
/**
* @see ghidra.framework.model.DomainObject#getOptions(java.lang.String)
*/
@Override @Override
public Options getOptions(String propertyListName) { public Options getOptions(String propertyListName) {
return new SubOptions(options, propertyListName, propertyListName + Options.DELIMITER); return new SubOptions(options, propertyListName, propertyListName + Options.DELIMITER);
@ -259,25 +253,16 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
options.performAlterations(propertyAlterations); options.performAlterations(propertyAlterations);
} }
/**
* @see ghidra.framework.model.DomainObject#canLock()
*/
@Override @Override
public boolean canLock() { public boolean canLock() {
return transactionMgr.getCurrentTransaction() == null && !closed; return transactionMgr.getCurrentTransaction() == null && !closed;
} }
/**
* @see ghidra.framework.model.DomainObject#isLocked()
*/
@Override @Override
public boolean isLocked() { public boolean isLocked() {
return transactionMgr.isLocked(); return transactionMgr.isLocked();
} }
/**
* @see ghidra.framework.model.DomainObject#lock(String)
*/
@Override @Override
public boolean lock(String reason) { public boolean lock(String reason) {
return transactionMgr.lock(reason); return transactionMgr.lock(reason);
@ -308,17 +293,11 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
return transactionMgr.lockForSnapshot(this, hasProgress, title); return transactionMgr.lockForSnapshot(this, hasProgress, title);
} }
/**
* @see ghidra.framework.model.DomainObject#forceLock(boolean, String)
*/
@Override @Override
public void forceLock(boolean rollback, String reason) { public void forceLock(boolean rollback, String reason) {
transactionMgr.forceLock(rollback, reason); transactionMgr.forceLock(rollback, reason);
} }
/**
* @see ghidra.framework.model.DomainObject#unlock()
*/
@Override @Override
public void unlock() { public void unlock() {
transactionMgr.unlock(); transactionMgr.unlock();
@ -336,9 +315,6 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
return startTransaction(description, null); return startTransaction(description, null);
} }
/**
* @see ghidra.framework.model.UndoableDomainObject#startTransaction(java.lang.String)
*/
@Override @Override
public int startTransaction(String description, AbortedTransactionListener listener) { public int startTransaction(String description, AbortedTransactionListener listener) {
int id = -1; int id = -1;
@ -359,9 +335,6 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
return id; return id;
} }
/**
* @see ghidra.framework.model.UndoableDomainObject#endTransaction(int, boolean)
*/
@Override @Override
public void endTransaction(int transactionID, boolean commit) { public void endTransaction(int transactionID, boolean commit) {
transactionMgr.endTransaction(this, transactionID, commit, true); transactionMgr.endTransaction(this, transactionID, commit, true);
@ -395,65 +368,41 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
return transactionMgr.getUndoStackDepth(); return transactionMgr.getUndoStackDepth();
} }
/**
* @see ghidra.framework.model.Undoable#canRedo()
*/
@Override @Override
public boolean canRedo() { public boolean canRedo() {
return transactionMgr.canRedo(); return transactionMgr.canRedo();
} }
/**
* @see ghidra.framework.model.Undoable#canUndo()
*/
@Override @Override
public boolean canUndo() { public boolean canUndo() {
return transactionMgr.canUndo(); return transactionMgr.canUndo();
} }
/**
* @see ghidra.framework.model.Undoable#getRedoName()
*/
@Override @Override
public String getRedoName() { public String getRedoName() {
return transactionMgr.getRedoName(); return transactionMgr.getRedoName();
} }
/**
* @see ghidra.framework.model.Undoable#getUndoName()
*/
@Override @Override
public String getUndoName() { public String getUndoName() {
return transactionMgr.getUndoName(); return transactionMgr.getUndoName();
} }
/**
* @see ghidra.framework.model.UndoableDomainObject#getCurrentTransaction()
*/
@Override @Override
public Transaction getCurrentTransaction() { public Transaction getCurrentTransaction() {
return transactionMgr.getCurrentTransaction(); return transactionMgr.getCurrentTransaction();
} }
/**
* @see ghidra.framework.model.Undoable#redo()
*/
@Override @Override
public void redo() throws IOException { public void redo() throws IOException {
transactionMgr.redo(); transactionMgr.redo();
} }
/**
* @see ghidra.framework.model.Undoable#undo()
*/
@Override @Override
public void undo() throws IOException { public void undo() throws IOException {
transactionMgr.undo(); transactionMgr.undo();
} }
/**
* @see ghidra.framework.model.DomainObject#isChanged()
*/
@Override @Override
public boolean isChanged() { public boolean isChanged() {
if (dbh == null) { if (dbh == null) {
@ -484,9 +433,6 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
return true; return true;
} }
/**
* @see ghidra.framework.model.Undoable#clearUndo()
*/
@Override @Override
public void clearUndo() { public void clearUndo() {
clearUndo(true); clearUndo(true);
@ -500,9 +446,6 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
options.clearCache(); options.clearCache();
} }
/**
* @see ghidra.framework.model.DomainObject#canSave()
*/
@Override @Override
public synchronized boolean canSave() { public synchronized boolean canSave() {
DomainFile df = getDomainFile(); DomainFile df = getDomainFile();
@ -512,9 +455,6 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
return dbh.canUpdate(); return dbh.canUpdate();
} }
/**
* @see ghidra.framework.model.DomainObject#save(java.lang.String, ghidra.util.task.TaskMonitor)
*/
@Override @Override
public void save(String comment, TaskMonitor monitor) throws IOException, CancelledException { public void save(String comment, TaskMonitor monitor) throws IOException, CancelledException {
if (!canSave()) { 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 @Override
public void saveToPackedFile(File outputFile, TaskMonitor monitor) public void saveToPackedFile(File outputFile, TaskMonitor monitor)
throws IOException, CancelledException { throws IOException, CancelledException {
@ -620,17 +557,11 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
} }
} }
/**
* @see ghidra.framework.model.DomainObject#isClosed()
*/
@Override @Override
public boolean isClosed() { public boolean isClosed() {
return closed; return closed;
} }
/**
* @see ghidra.framework.model.UndoableDomainObject#hasTerminatedTransaction()
*/
@Override @Override
public boolean hasTerminatedTransaction() { public boolean hasTerminatedTransaction() {
return transactionMgr.hasTerminatedTransaction(); return transactionMgr.hasTerminatedTransaction();

View file

@ -19,6 +19,8 @@ import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.*; import java.util.*;
import org.apache.commons.lang3.StringUtils;
import db.DBConstants; import db.DBConstants;
import db.DBHandle; import db.DBHandle;
import ghidra.app.plugin.processors.sleigh.SleighLanguage; 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.symbol.*;
import ghidra.program.database.util.AddressSetPropertyMapDB; import ghidra.program.database.util.AddressSetPropertyMapDB;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.mem.MemoryBlock;
@ -208,6 +211,11 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
private Map<String, AddressSetPropertyMapDB> addrSetPropertyMap = new HashMap<>(); private Map<String, AddressSetPropertyMapDB> addrSetPropertyMap = new HashMap<>();
private Map<String, IntRangeMapDB> intRangePropertyMap = new HashMap<>(); private Map<String, IntRangeMapDB> 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 * Constructs a new ProgramDB
* @param name the name of the program * @param name the name of the program
@ -247,7 +255,7 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
listing = new ListingDB(); listing = new ListingDB();
changeSet = new ProgramDBChangeSet(addrMap, NUM_UNDOS); changeSet = new ProgramDBChangeSet(addrMap, NUM_UNDOS);
initManagers(CREATE, TaskMonitor.DUMMY); initManagers(CREATE, TaskMonitor.DUMMY);
propertiesCreate(); createProgramInformationOptions();
programUserData = new ProgramUserDataDB(this); programUserData = new ProgramUserDataDB(this);
endTransaction(id, true); endTransaction(id, true);
clearUndo(false); clearUndo(false);
@ -369,7 +377,8 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
changed = true; changed = true;
} }
propertiesRestore(); registerProgramInformationOptions();
restoreProgramInformationOptions();
recordChanges = true; recordChanges = true;
endTransaction(id, true); endTransaction(id, true);
clearUndo(false); clearUndo(false);
@ -433,25 +442,59 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
recordChanges = true; recordChanges = true;
} }
private void propertiesRestore() { private void registerProgramInformationOptions() {
Options pl = getOptions(PROGRAM_INFO); Options pl = getOptions(PROGRAM_INFO);
boolean origChangeState = changed;
pl.registerOption(EXECUTABLE_PATH, UNKNOWN, null, "Original import path of program image"); pl.registerOption(EXECUTABLE_PATH, UNKNOWN, null, "Original import path of program image");
pl.registerOption(EXECUTABLE_FORMAT, UNKNOWN, null, "Original program image format"); pl.registerOption(EXECUTABLE_FORMAT, UNKNOWN, null, "Original program image format");
pl.registerOption(CREATED_WITH_GHIDRA_VERSION, "3.0 or earlier", null, pl.registerOption(CREATED_WITH_GHIDRA_VERSION, "3.0 or earlier", null,
"Version of Ghidra used to create this program."); "Version of Ghidra used to create this program.");
pl.registerOption(DATE_CREATED, JANUARY_1_1970, null, "Date this program was created"); 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); Options pl = getOptions(PROGRAM_INFO);
boolean origChangeState = changed;
pl.setString(EXECUTABLE_PATH, UNKNOWN); pl.setString(EXECUTABLE_PATH, UNKNOWN);
pl.setString(EXECUTABLE_FORMAT, UNKNOWN); pl.setString(EXECUTABLE_FORMAT, UNKNOWN);
pl.setString(CREATED_WITH_GHIDRA_VERSION, Application.getApplicationVersion()); pl.setString(CREATED_WITH_GHIDRA_VERSION, Application.getApplicationVersion());
pl.setDate(DATE_CREATED, new Date()); 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) { void setProgramUserData(ProgramUserDataDB programUserData) {
@ -556,7 +599,17 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
public void setCompiler(String compiler) { public void setCompiler(String compiler) {
Options pl = getOptions(PROGRAM_INFO); Options pl = getOptions(PROGRAM_INFO);
pl.setString(COMPILER, compiler); 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 @Override
@ -571,7 +624,6 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
public void setExecutablePath(String path) { public void setExecutablePath(String path) {
Options pl = getOptions(PROGRAM_INFO); Options pl = getOptions(PROGRAM_INFO);
pl.setString(EXECUTABLE_PATH, path); pl.setString(EXECUTABLE_PATH, path);
changed = true;
} }
@Override @Override
@ -591,7 +643,6 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
public void setExecutableFormat(String format) { public void setExecutableFormat(String format) {
Options pl = getOptions(PROGRAM_INFO); Options pl = getOptions(PROGRAM_INFO);
pl.setString(EXECUTABLE_FORMAT, format); pl.setString(EXECUTABLE_FORMAT, format);
changed = true;
} }
@Override @Override
@ -611,7 +662,6 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
public void setExecutableMD5(String md5) { public void setExecutableMD5(String md5) {
Options pl = getOptions(PROGRAM_INFO); Options pl = getOptions(PROGRAM_INFO);
pl.setString(EXECUTABLE_MD5, md5); pl.setString(EXECUTABLE_MD5, md5);
changed = true;
} }
@Override @Override
@ -631,7 +681,6 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
public void setExecutableSHA256(String sha256) { public void setExecutableSHA256(String sha256) {
Options pl = getOptions(PROGRAM_INFO); Options pl = getOptions(PROGRAM_INFO);
pl.setString(EXECUTABLE_SHA256, sha256); pl.setString(EXECUTABLE_SHA256, sha256);
changed = true;
} }
@Override @Override
@ -1738,6 +1787,7 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
for (int i = 0; i < NUM_MANAGERS; i++) { for (int i = 0; i < NUM_MANAGERS; i++) {
managers[i].invalidateCache(all); managers[i].invalidateCache(all);
} }
restoreProgramInformationOptions();
installExtensions(); // Reload any extensions installExtensions(); // Reload any extensions
} }
catch (IOException e) { catch (IOException e) {

View file

@ -19,11 +19,11 @@ import java.util.*;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import ghidra.app.util.NamespaceUtils; import ghidra.app.util.NamespaceUtils;
import ghidra.app.util.SymbolPathParser;
import ghidra.docking.settings.Settings; import ghidra.docking.settings.Settings;
import ghidra.program.model.address.GlobalNamespace;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.program.model.data.Enum; import ghidra.program.model.data.Enum;
import ghidra.program.model.listing.Library; import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Namespace; import ghidra.program.model.symbol.Namespace;
import ghidra.util.UniversalID; import ghidra.util.UniversalID;
import ghidra.util.exception.AssertException; import ghidra.util.exception.AssertException;
@ -354,11 +354,37 @@ public class DataTypeUtilities {
: new CategoryPath(baseCategory, categoryPathParts); : 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 * 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 * within the specified dataTypeManager. The first match which satisfies the category path
* used in checking data type parent categories, however if no type corresponds to the namespace * requirement will be returned. If a non-root namespace is specified the datatype's trailing
* another type whose name matches may be returned. * category path must match the specified namespace path.
* *
* @param dataTypeManager data type manager * @param dataTypeManager data type manager
* @param namespace namespace associated with dtName (null indicates no namespace constraint) * @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 * @param classConstraint optional data type interface constraint (e.g., Structure), or null
* @return best matching data type * @return best matching data type
*/ */
public static DataType findDataType(DataTypeManager dataTypeManager, Namespace namespace, public static <T extends DataType> T findDataType(DataTypeManager dataTypeManager,
String dtName, Class<? extends DataType> classConstraint) { Namespace namespace, String dtName, Class<T> classConstraint) {
T dt =
findPreferredDataType(dataTypeManager, namespace, dtName, classConstraint, false);
if (dt != null) {
return dt;
}
final String[] namespacePaths = getRelativeCategoryPaths(namespace);
return findDataType(dataTypeManager, dtName, classConstraint, 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 * 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 * specified dataTypeManager. The namespace will be used in checking data type parent categories.
* checking data type parent categories, however if no type corresponds to the namespace another * NOTE: name parsing assumes :: namespace delimiter which can be thrown off if name includes
* type whose name matches may be returned. NOTE: name parsing assumes :: delimiter and can be * template information which could contain namespaces (see {@link SymbolPathParser#parse(String)}).
* thrown off if name include template information which could contain namespaces.
* *
* @param dataTypeManager data type manager * @param dataTypeManager data type manager
* @param dtNameWithNamespace name of data type qualified with namespace (e.g., * @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 * @param classConstraint optional data type interface constraint (e.g., Structure), or null
* @return best matching data type * @return best matching data type
*/ */
public static DataType findNamespaceQualifiedDataType(DataTypeManager dataTypeManager, public static <T extends DataType> T findNamespaceQualifiedDataType(
String dtNameWithNamespace, Class<? extends DataType> classConstraint) { DataTypeManager dataTypeManager,
String dtNameWithNamespace, Class<T> classConstraint) {
String[] splitName = dtNameWithNamespace.split(Namespace.DELIMITER); List<String> pathList = SymbolPathParser.parse(dtNameWithNamespace);
String dtName = splitName[splitName.length - 1]; int nameIndex = pathList.size() - 1;
String dtName = pathList.get(nameIndex);
CategoryPath rootPath = getPreferredRootNamespaceCategoryPath(dataTypeManager);
if (rootPath != null) {
List<String> 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, return findDataType(dataTypeManager, dtName, classConstraint,
dataType -> hasPreferredNamespaceCategory(dataType, splitName)); categoryPath -> getCategoryMatchType(categoryPath, namespacePath));
} }
/** /**
@ -410,40 +464,92 @@ public class DataTypeUtilities {
return cPrimitiveNameMap.get(dataTypeName); return cPrimitiveNameMap.get(dataTypeName);
} }
private static boolean hasPreferredNamespaceCategory(DataType dataType, private static final int NAMESPACE_PATH_INDEX = 0;
String[] splitDataTypeName) { private static final int PARENT_NAMESPACE_PATH_INDEX = 1;
// last element of split array is data type name and is ignored here
if (splitDataTypeName.length == 1) { /**
return true; * 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(); String[] paths = new String[2];
int index = splitDataTypeName.length - 2; StringBuilder buf = new StringBuilder();
while (index >= 0) { for (String n : namespace.getParentNamespace().getPathList(true)) {
if (categoryPath.equals(CategoryPath.ROOT) || buf.append(CategoryPath.DELIMITER_STRING);
!categoryPath.getName().equals(splitDataTypeName[index])) { buf.append(n);
return false;
} }
categoryPath = categoryPath.getParent(); paths[PARENT_NAMESPACE_PATH_INDEX] = buf.toString();
--index; buf.append(CategoryPath.DELIMITER_STRING);
} buf.append(namespace.getName());
return true; paths[NAMESPACE_PATH_INDEX] = buf.toString();
return paths;
} }
private static boolean hasPreferredNamespaceCategory(DataType dataType, Namespace namespace) { private enum CategoryMatchType {
if (namespace == null) { NONE, SECONDARY, PREFERRED;
return true;
} }
CategoryPath categoryPath = dataType.getCategoryPath();
Namespace ns = namespace; /**
while (!(ns instanceof GlobalNamespace) && !(ns instanceof Library)) { * Namespace category matcher. Only those datatypes contained within a catgeory
if (categoryPath.equals(CategoryPath.ROOT) || * whose trailing category path matches the specified namespacePath will be considered
!categoryPath.getName().equals(ns.getName())) { * a possible match. If the namespacePath is empty array all category paths will
return false; * 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.getParent(); String path = categoryPath.getPath();
ns = ns.getParentNamespace(); return path.endsWith(namespacePath) ? CategoryMatchType.PREFERRED : CategoryMatchType.NONE;
} }
return true;
/**
* 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;
}
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. * preferred namespace.
*/ */
private static interface NamespaceMatcher { 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, private static CategoryPath getPreferredRootNamespaceCategoryPath(
Class<? extends DataType> classConstraint, NamespaceMatcher preferredCategoryMatcher) { 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 <T> 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 extends DataType> T getAssignableDataType(DataTypeManager dataTypeManager,
CategoryPath rootPath, List<String> namespacePath, String dtName,
Class<? extends DataType> 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 <T> 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 extends DataType> T getAssignableDataType(Category category, String dtName,
Class<? extends DataType> 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 extends DataType> T findPreferredDataType(DataTypeManager dataTypeManager,
Namespace namespace, String dtName, Class<T> 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> 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 extends DataType> T findDataType(DataTypeManager dataTypeManager,
String dtName, Class<T> classConstraint, NamespaceMatcher categoryMatcher) {
ArrayList<DataType> list = new ArrayList<>(); ArrayList<DataType> list = new ArrayList<>();
dataTypeManager.findDataTypes(dtName, list); dataTypeManager.findDataTypes(dtName, list);
Collections.sort(list, DATATYPE_CATEGORY_PATH_LENGTH_COMPARATOR);
if (!list.isEmpty()) { if (!list.isEmpty()) {
//use the datatype that exists in the root category, T secondaryMatch = null;
//otherwise just pick the first one...
DataType anyDt = null;
DataType preferredDataType = null;
for (DataType existingDT : list) { for (DataType existingDT : list) {
if (classConstraint != null && if (classConstraint != null &&
!classConstraint.isAssignableFrom(existingDT.getClass())) { !classConstraint.isAssignableFrom(existingDT.getClass())) {
continue; continue;
} }
if (preferredCategoryMatcher == null) { CategoryMatchType matchType =
if (existingDT.getCategoryPath().equals(CategoryPath.ROOT)) { categoryMatcher.getMatchType(existingDT.getCategoryPath());
return existingDT; if (matchType == CategoryMatchType.PREFERRED) {
return (T) existingDT; // preferred match
}
else if (secondaryMatch == null && matchType == CategoryMatchType.SECONDARY) {
secondaryMatch = (T) existingDT;
} }
} }
if (preferredCategoryMatcher.isNamespaceCategoryMatch(existingDT)) { return secondaryMatch;
preferredDataType = existingDT;
}
// If all else fails return any matching name for backward compatibility
anyDt = existingDT;
}
if (preferredDataType != null) {
return preferredDataType;
}
return anyDt;
} }
return null; return null;
} }

View file

@ -19,6 +19,7 @@ import java.util.Date;
import ghidra.framework.store.LockException; import ghidra.framework.store.LockException;
import ghidra.program.database.IntRangeMap; import ghidra.program.database.IntRangeMap;
import ghidra.program.database.data.DataTypeUtilities;
import ghidra.program.database.map.AddressMap; import ghidra.program.database.map.AddressMap;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
@ -60,6 +61,9 @@ public interface Program extends DataTypeManagerDomainObject {
public static final String DATE_CREATED = "Date Created"; public static final String DATE_CREATED = "Date Created";
/** Name of ghidra version property */ /** Name of ghidra version property */
public static final String CREATED_WITH_GHIDRA_VERSION = "Created With Ghidra Version"; 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 */ /** Creation date to ask for analysis */
public static final String ANALYSIS_START_DATE = "2007-Jan-01"; public static final String ANALYSIS_START_DATE = "2007-Jan-01";
/** Format string of analysis date */ /** Format string of analysis date */
@ -160,6 +164,37 @@ public interface Program extends DataTypeManagerDomainObject {
*/ */
public void setCompiler(String compiler); 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
* <i>"Preferred Root Namespace Category</i>. 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
* <i>"Preferred Root Namespace Category</i>. 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., <i>/ClassDataTypes</i>). An invalid
* path setting will be ignored.
*/
public void setPreferredRootNamespaceCategoryPath(String categoryPath);
/** /**
* Gets the path to the program's executable file. * Gets the path to the program's executable file.
* For example, <code>C:\Temp\test.exe</code>. * For example, <code>C:\Temp\test.exe</code>.

View file

@ -43,7 +43,7 @@ public class VariableUtilities {
/** /**
* Get a precedence value for the specified variable. * Get a precedence value for the specified variable.
* This value can be used to assist with LocalVariable.compareTo(Variable var) * This value can be used to assist with LocalVariable.compareTo(Variable var)
* @param var * @param var function variable
* @return numeric precedence * @return numeric precedence
*/ */
public static int getPrecedence(Variable var) { 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 * 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 * 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) * all have an empty varnode list and would be considered a match)
* @param vars * @param vars function variables
* @param otherVars * @param otherVars other function variables
* @return true if the exact sequence of variable storage varnodes matches across two lists of variables. * @return true if the exact sequence of variable storage varnodes matches across two lists of variables.
*/ */
public static boolean storageMatches(List<Variable> vars, List<Variable> otherVars) { public static boolean storageMatches(List<Variable> vars, List<Variable> otherVars) {
@ -96,8 +96,8 @@ public class VariableUtilities {
* Compare storage varnodes for two lists of variables. No check is done to ensure that * 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 * 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) * all have an empty varnode list and would be considered a match)
* @param vars * @param vars function variables
* @param otherVars * @param otherVars other function variables
* @return true if the exact sequence of variable storage varnodes matches across two lists of variables. * @return true if the exact sequence of variable storage varnodes matches across two lists of variables.
*/ */
public static boolean storageMatches(List<? extends Variable> vars, Variable... otherVars) { public static boolean storageMatches(List<? extends Variable> vars, Variable... otherVars) {
@ -116,8 +116,8 @@ public class VariableUtilities {
/** /**
* Compare two variables without using the instance specific compareTo method. * Compare two variables without using the instance specific compareTo method.
* @param v1 * @param v1 a function variable
* @param v2 * @param v2 another function variable
* @return a negative value if v1 &lt; v2, 0 if equal, and * @return a negative value if v1 &lt; v2, 0 if equal, and
* positive if v1 &gt; v2 * positive if v1 &gt; v2
*/ */
@ -162,8 +162,8 @@ public class VariableUtilities {
/** /**
* Determine the appropriate data type for an automatic parameter * Determine the appropriate data type for an automatic parameter
* @param function * @param function function whose auto param datatype is to be determined
* @param returnDataType * @param returnDataType function's return datatype
* @param storage variable storage for an auto-parameter (isAutoStorage should be true) * @param storage variable storage for an auto-parameter (isAutoStorage should be true)
* @return auto-parameter data type * @return auto-parameter data type
*/ */
@ -174,7 +174,7 @@ public class VariableUtilities {
if (autoParameterType == AutoParameterType.THIS) { if (autoParameterType == AutoParameterType.THIS) {
DataType classStruct = findOrCreateClassStruct(function); DataType classStruct = findOrCreateClassStruct(function);
if (classStruct == null) { if (classStruct == null) {
classStruct = DataType.VOID; classStruct = VoidDataType.dataType;
} }
return getPointer(function.getProgram(), classStruct, storage.size()); 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 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 dataType a datatype checked using {@link #checkDataType(DataType, boolean, int, Program)}
* @param allowSizeMismatch if true size mismatch will be ignore * @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, public static void checkStorage(VariableStorage storage, DataType dataType,
boolean allowSizeMismatch) throws InvalidInputException { boolean allowSizeMismatch) throws InvalidInputException {
@ -212,7 +212,7 @@ public class VariableUtilities {
* @param dataType a datatype checked using {@link #checkDataType(DataType, boolean, int, Program)} * @param dataType a datatype checked using {@link #checkDataType(DataType, boolean, int, Program)}
* @param allowSizeMismatch if true size mismatch will be ignore * @param allowSizeMismatch if true size mismatch will be ignore
* @return original storage or resized storage with the correct size. * @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, public static VariableStorage checkStorage(Function function, VariableStorage storage,
DataType dataType, boolean allowSizeMismatch) throws InvalidInputException { 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 * 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 * storage if current storage does not have a stack component or if other space constraints
* are exceeded. * are exceeded.
* @param curStorage * @param curStorage current variable storage
* @param dataType * @param dataType variable datatype
* @param alignStack if false no attempt is made to align stack usage for big-endian * @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 * @return resize storage
* @throws InvalidInputException if unable to resize storage to specified size. * @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. * 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 var existing function variable or null for new variable
* @param newStorage new/updated variable storage * @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 * @throws VariableSizeException if deleteConflictingVariables is false and another variable conflicts
*/ */
public static void checkVariableConflict(Function function, Variable var, 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. * Check for variable storage conflict and optionally remove conflicting variables.
* @param existingVariables variables to check (may contain null entries) * @param existingVariables variables to check (may contain null entries)
* @param var * @param var function variable
* @param newStorage * @param conflictHandler variable conflict handler
* @throws VariableSizeException * @param newStorage variable storage
* @throws VariableSizeException if another variable conflicts * @throws VariableSizeException if another variable conflicts
*/ */
public static void checkVariableConflict(List<? extends Variable> existingVariables, public static void checkVariableConflict(List<? extends Variable> existingVariables,
@ -685,7 +685,7 @@ public class VariableUtilities {
/** /**
* Determine the minimum stack offset for parameters * 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 * @return stack parameter offset or null if it could not be determined
*/ */
public static Integer getBaseStackParamOffset(Function function) { public static Integer getBaseStackParamOffset(Function function) {
@ -712,7 +712,8 @@ public class VariableUtilities {
/** /**
* Generate a suitable 'this' parameter for the specified function * Generate a suitable 'this' parameter for the specified function
* @param function * @param function function for which a <code>this</code> parameter is to be generated
* @param convention function calling convention
* @return this parameter or null of calling convention is not a 'thiscall' * @return this parameter or null of calling convention is not a 'thiscall'
* or some other error prevents it * or some other error prevents it
* @deprecated should rely on auto-param instead - try not to use this method which may be eliminated * @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 * 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 preferred structure will utilize a namespace-based category path, however,
* the match criteria can be fuzzy and relies primarily on the class name. * 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 * A properly named class structure must reside within a category whose trailing
* manager. * 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 classNamespace class namespace
* @param dataTypeManager data type manager which should be searched and whose * @param dataTypeManager data type manager which should be searched and whose
* data organization should be used. * 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 * 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 preferred structure will utilize a namespace-based category path, however,
* the match criteria can be fuzzy and relies primarily on the class name. * 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 * @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 * @return new or existing structure whose name matches the function's class namespace or
* null if function not contained within a class namespace. * 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 * 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 preferred structure will utilize a namespace-based category path, however,
* the match criteria can be fuzzy and relies primarily on the class name. * 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 classNamespace class namespace
* @param dataTypeManager data type manager which should be searched. * @param dataTypeManager data type manager which should be searched.
* @return existing structure whose name matches the specified class namespace * @return existing structure whose name matches the specified class namespace
@ -817,15 +841,19 @@ public class VariableUtilities {
*/ */
public static Structure findExistingClassStruct(GhidraClass classNamespace, public static Structure findExistingClassStruct(GhidraClass classNamespace,
DataTypeManager dataTypeManager) { DataTypeManager dataTypeManager) {
return (Structure) DataTypeUtilities.findDataType(dataTypeManager, return DataTypeUtilities.findExistingClassStruct(dataTypeManager, classNamespace);
classNamespace.getParentNamespace(), classNamespace.getName(), Structure.class);
} }
/** /**
* Find the structure data type which corresponds to the specified function's class namespace * Find the structure data type which corresponds to the specified function's class namespace
* within the function's program. * within the function's program.
*
* The preferred structure will utilize a namespace-based category path, however, * The preferred structure will utilize a namespace-based category path, however,
* the match criteria can be fuzzy and relies primarily on the class name. * 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. * @param func the function.
* @return existing structure whose name matches the specified function's class namespace * @return existing structure whose name matches the specified function's class namespace
* or null if not found. * or null if not found.

View file

@ -15,6 +15,8 @@
*/ */
package ghidra.program.model.symbol; package ghidra.program.model.symbol;
import java.util.*;
import ghidra.program.model.address.AddressSetView; import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.CircularDependencyException; import ghidra.program.model.listing.CircularDependencyException;
import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.DuplicateNameException;
@ -24,6 +26,7 @@ import ghidra.util.exception.InvalidInputException;
* The Namespace interface * The Namespace interface
*/ */
public interface Namespace { public interface Namespace {
static final long GLOBAL_NAMESPACE_ID = 0; static final long GLOBAL_NAMESPACE_ID = 0;
/** /**
* The delimiter that is used to separate namespace nodes in a namespace * The delimiter that is used to separate namespace nodes in a namespace
@ -63,6 +66,24 @@ public interface Namespace {
*/ */
public String getName(boolean includeNamespacePath); 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<String> getPathList(boolean omitLibrary) {
if (isGlobal()) {
return Collections.emptyList();
}
ArrayDeque<String> 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
* @return the namespace id * @return the namespace id
@ -96,11 +117,20 @@ public interface Namespace {
throws DuplicateNameException, InvalidInputException, CircularDependencyException; 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() { public default boolean isGlobal() {
return getID() == GLOBAL_NAMESPACE_ID; 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;
}
} }

View file

@ -26,6 +26,7 @@ import ghidra.program.database.IntRangeMap;
import ghidra.program.database.data.ProgramDataTypeManager; import ghidra.program.database.data.ProgramDataTypeManager;
import ghidra.program.database.map.AddressMap; import ghidra.program.database.map.AddressMap;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.Memory;
@ -376,6 +377,16 @@ public class ProgramTestDouble implements Program {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public CategoryPath getPreferredRootNamespaceCategoryPath() {
return null;
}
@Override
public void setPreferredRootNamespaceCategoryPath(String categoryPath) {
throw new UnsupportedOperationException();
}
@Override @Override
public String getExecutablePath() { public String getExecutablePath() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();