GT-3222 - Fixed bugs in Data Type navigation that caused failure after

opening and archive for edit
This commit is contained in:
dragonmacher 2019-10-11 16:28:52 -04:00
parent 591ed4de31
commit 0b532431cd
9 changed files with 172 additions and 176 deletions

View file

@ -205,9 +205,9 @@ public class DataTypeManagerPlugin extends ProgramPlugin
}
/**
* Add project pathname to recently opened list.
* @param projectName
* @param pathname
* Add project archive name to recently opened list
* @param projectName the project name
* @param pathname the pathname
*/
public void addRecentlyOpenedProjectArchive(String projectName, String pathname) {
String projectPathname = DataTypeManagerHandler.getProjectPathname(projectName, pathname);
@ -239,8 +239,8 @@ public class DataTypeManagerPlugin extends ProgramPlugin
/**
* Get a project archive file by project name and pathname
* @param projectName
* @param pathname
* @param projectName the project name
* @param pathname the project pathname
* @return project archive domain file or null if it does not exist
* or can not be found (e.g., projectName is not the active project)
*/
@ -272,10 +272,6 @@ public class DataTypeManagerPlugin extends ProgramPlugin
dataTypeManagerHandler.dispose();
}
/**
* Tells the Plugin to read its data-independant (preferences)
* properties from the input stream.
*/
@Override
public void readConfigState(SaveState saveState) {
dataTypeManagerHandler.restore(saveState);
@ -311,10 +307,6 @@ public class DataTypeManagerPlugin extends ProgramPlugin
dataTypePropertyManager.programClosed(program);
}
/**
* Program was opened.
* @param program
*/
@Override
protected void programActivated(Program program) {
program.addListener(this);
@ -574,7 +566,6 @@ public class DataTypeManagerPlugin extends ProgramPlugin
openDialog.addOkActionListener(listener);
}
tool.showDialog(openDialog);
// updateActions();
}
@Override

View file

@ -83,7 +83,8 @@ public class DataTypesProvider extends ComponentProviderAdapter {
private HelpLocation helpLocation;
private DataTypeManagerPlugin plugin;
private HistoryList<DataType> navigationHistory = new HistoryList<>(15, dt -> {
private HistoryList<DataTypeIdUrl> navigationHistory = new HistoryList<>(15, url -> {
DataType dt = url.getDataType(plugin);
setDataTypeSelected(dt);
});
private MultiActionDockingAction nextAction;
@ -465,35 +466,12 @@ public class DataTypesProvider extends ComponentProviderAdapter {
try {
url = new DataTypeIdUrl(href);
}
catch (NumberFormatException e) {
Msg.debug(this, "Could not parse Data Type ID URL '" + href + "'");
catch (IllegalArgumentException e) {
Msg.debug(this, "Could not parse Data Type ID URL '" + href + "'", e);
return null;
}
DataTypeManager manager = getManager(url);
if (manager == null) {
// this shouldn't be possible, unless the url is old and the manager has been closed
Msg.debug(this, "Could not find data type for " + event.getDescription());
return null;
}
DataType dt = manager.findDataTypeForID(url.getDataTypeId());
return dt;
}
private DataTypeManager getManager(DataTypeIdUrl url) {
UniversalID id = url.getDataTypeManagerId();
return getManager(id);
}
private DataTypeManager getManager(UniversalID id) {
DataTypeManager[] mgs = plugin.getDataTypeManagers();
for (DataTypeManager dtm : mgs) {
if (dtm.getUniversalID().equals(id)) {
return dtm;
}
}
return null;
return url.getDataType(plugin);
}
private void updatePreviewPane() {
@ -550,6 +528,7 @@ public class DataTypesProvider extends ComponentProviderAdapter {
void dispose() {
previewUpdateManager.dispose();
archiveGTree.dispose();
navigationHistory.clear();
}
@Override
@ -861,6 +840,10 @@ public class DataTypesProvider extends ComponentProviderAdapter {
return conflictMode.getHandler();
}
DataTypeManagerPlugin getPlugin() {
return plugin;
}
private DataType getDataTypeFrom(TreePath path) {
if (path == null) {
return null;
@ -876,7 +859,7 @@ public class DataTypesProvider extends ComponentProviderAdapter {
return dt;
}
HistoryList<DataType> getNavigationHistory() {
HistoryList<DataTypeIdUrl> getNavigationHistory() {
return navigationHistory;
}
@ -898,7 +881,11 @@ public class DataTypesProvider extends ComponentProviderAdapter {
return; // Ignore events from the GTree's housekeeping
}
navigationHistory.add(dt);
if (dt == null) {
return;
}
navigationHistory.add(new DataTypeIdUrl(dt));
contextChanged();
}
}

View file

@ -23,6 +23,7 @@ import javax.swing.Icon;
import docking.ActionContext;
import docking.action.*;
import docking.menu.MultiActionDockingAction;
import ghidra.app.util.datatype.DataTypeIdUrl;
import ghidra.base.actions.HorizontalRuleAction;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.DataTypeManager;
@ -38,7 +39,7 @@ class NextPreviousDataTypeAction extends MultiActionDockingAction {
private boolean isNext;
private String owner;
private DataTypesProvider provider;
private HistoryList<DataType> history;
private HistoryList<DataTypeIdUrl> history;
public NextPreviousDataTypeAction(DataTypesProvider provider, String owner, boolean isNext) {
super(isNext ? "Next Data Type in History" : "Previous Data Type in History", owner);
@ -92,19 +93,19 @@ class NextPreviousDataTypeAction extends MultiActionDockingAction {
DataTypeManager lastDtm = null;
List<DockingActionIf> results = new ArrayList<>();
List<DataType> types =
List<DataTypeIdUrl> types =
isNext ? history.getNextHistoryItems() : history.getPreviousHistoryItems();
for (DataType dt : types) {
for (DataTypeIdUrl url : types) {
DataType dt = url.getDataType(provider.getPlugin());
DataTypeManager dtm = dt.getDataTypeManager();
if (dtm != lastDtm && !results.isEmpty()) {
// add a separator to show the user they are navigating across managers
results.add(createHorizontalRule(lastDtm, dtm));
}
results.add(new NavigationAction(dt));
results.add(new NavigationAction(dt.getDisplayName()));
lastDtm = dtm;
}
@ -122,10 +123,10 @@ class NextPreviousDataTypeAction extends MultiActionDockingAction {
private class NavigationAction extends DockingAction {
private NavigationAction(DataType dt) {
private NavigationAction(String dtDisplayName) {
super("DataTypeNavigationAction_" + ++navigationActionIdCount, owner);
setMenuBarData(new MenuData(new String[] { dt.getDisplayName() }));
setMenuBarData(new MenuData(new String[] { dtDisplayName }));
setEnabled(true);
setHelpLocation(new HelpLocation("DataTypeManagerPlugin", "Navigation_Actions"));
}

View file

@ -223,7 +223,7 @@ public class ArchiveNode extends CategoryNode {
return null;
}
CategoryNode node = findCategoryNode(parentCategory);
CategoryNode node = findCategoryNode(parentCategory, loadChildren);
if (node == null) {
return null;
}

View file

@ -15,40 +15,52 @@
*/
package ghidra.app.util.datatype;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.DataTypeManager;
import ghidra.util.Msg;
import ghidra.app.services.DataTypeManagerService;
import ghidra.program.model.data.*;
import ghidra.util.UniversalID;
/**
* A class to produce and parse URLs of the form:
* <pre>
* datatype:/12345678/12345678
* datatype:/12345678?uid=12345678&name=Bob
* </pre>
* where the first number is the ID of the {@link DataTypeManager} and the second number is
* the {@link DataType} ID.
*/
public class DataTypeIdUrl {
// see javadoc for format
private static String PROTOCOL = "datatype";
private static Pattern URL_PATTERN = Pattern.compile(PROTOCOL + ":/(\\d+)/(\\d+)");
private static Pattern URL_PATTERN =
Pattern.compile(PROTOCOL + ":/(\\d+)\\?uid=(\\d*)&name=(\\w+)");
private UniversalID dataTypeManagerId;
private UniversalID dataTypeId;
private String dataTypeName;
/**
* Constructs a url from the given data type
* @param dt the data type; cannot be null
*/
public DataTypeIdUrl(DataType dt) {
DataTypeManager dtm = dt.getDataTypeManager();
if (dtm == null) {
Msg.debug(this, "");
}
dataTypeManagerId = dtm.getUniversalID();
dataTypeManagerId = Objects.requireNonNull(dtm.getUniversalID());
dataTypeId = dt.getUniversalID();
dataTypeName = Objects.requireNonNull(dt.getName());
}
public DataTypeIdUrl(String url) {
/**
* Constructs a url from the given url string
*
* @param url the url
* @throws IllegalArgumentException if the url does not match the expected {@link #URL_PATTERN}
* or if there is an issue parsing the id within the given url
*/
public DataTypeIdUrl(String url) throws IllegalArgumentException {
Matcher matcher = URL_PATTERN.matcher(url);
if (!matcher.matches()) {
@ -57,9 +69,22 @@ public class DataTypeIdUrl {
String dtmId = matcher.group(1);
String dtId = matcher.group(2);
try {
dataTypeManagerId = new UniversalID(Long.parseLong(dtmId));
}
catch (NumberFormatException e) {
throw new IllegalArgumentException("Exception parsing Data Type Manager ID: ", e);
}
try {
dataTypeId = new UniversalID(Long.parseLong(dtId));
}
catch (NumberFormatException e) {
throw new IllegalArgumentException("Exception parsing Data Type ID: ", e);
}
dataTypeName = matcher.group(3);
}
public UniversalID getDataTypeManagerId() {
return dataTypeManagerId;
@ -69,8 +94,86 @@ public class DataTypeIdUrl {
return dataTypeId;
}
/**
* Uses the given service and its {@link DataTypeManager}s to find the data type
* represented by this url
*
* @param service the service
* @return the data type; null if there was an error restoring the type, such as if the
* parent {@link DataTypeManager} has been closed
*/
public DataType getDataType(DataTypeManagerService service) {
DataTypeManager manager = findManager(service);
if (manager == null) {
return null;
}
if (dataTypeId == null) {
// The ID will be null for built-in types. In that case, the name will not be
// null. Further, built-in types live at the root, so we can just ask for the
// type by name.
return manager.getDataType(new DataTypePath(CategoryPath.ROOT, dataTypeName));
}
DataType dt = manager.findDataTypeForID(dataTypeId);
return dt;
}
private DataTypeManager findManager(DataTypeManagerService service) {
return getManagerById(service);
}
private DataTypeManager getManagerById(DataTypeManagerService service) {
DataTypeManager[] mgs = service.getDataTypeManagers();
for (DataTypeManager dtm : mgs) {
if (dtm.getUniversalID().equals(dataTypeManagerId)) {
return dtm;
}
}
return null;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((dataTypeId == null) ? 0 : dataTypeId.hashCode());
result = prime * result + ((dataTypeManagerId == null) ? 0 : dataTypeManagerId.hashCode());
result = prime * result + ((dataTypeName == null) ? 0 : dataTypeName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
DataTypeIdUrl other = (DataTypeIdUrl) obj;
if (!Objects.equals(dataTypeId, other.dataTypeId)) {
return false;
}
if (!Objects.equals(dataTypeManagerId, other.dataTypeManagerId)) {
return false;
}
if (!Objects.equals(dataTypeName, other.dataTypeName)) {
return false;
}
return true;
}
@Override
public String toString() {
return PROTOCOL + ":/" + dataTypeManagerId.toString() + '/' + dataTypeId.toString();
return PROTOCOL + ":/" + dataTypeManagerId.toString() + "?uid=" +
Objects.toString(dataTypeId, "") + "&name=" + dataTypeName;
}
}

View file

@ -15,7 +15,7 @@
*/
package ghidra.app.plugin.core.datamgr;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.awt.Container;
@ -24,7 +24,8 @@ import java.awt.event.KeyEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.*;
import java.util.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@ -39,10 +40,6 @@ import docking.action.DockingActionIf;
import docking.action.ToggleDockingActionIf;
import docking.actions.KeyBindingUtils;
import docking.widgets.OptionDialog;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.dialogs.InputWithChoicesDialog;
import docking.widgets.fieldpanel.support.Highlight;
import docking.widgets.table.threaded.ThreadedTableModel;
import docking.widgets.tree.GTreeNode;
import ghidra.app.context.ProgramActionContext;
import ghidra.app.plugin.core.datamgr.actions.CreateTypeDefDialog;
@ -50,23 +47,15 @@ import ghidra.app.plugin.core.datamgr.archive.Archive;
import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler;
import ghidra.app.plugin.core.datamgr.tree.*;
import ghidra.app.plugin.core.function.EditFunctionSignatureDialog;
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferencesPlugin;
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferencesProvider;
import ghidra.app.plugin.core.programtree.ProgramTreePlugin;
import ghidra.app.services.CodeViewerService;
import ghidra.app.services.ProgramManager;
import ghidra.app.util.HighlightProvider;
import ghidra.app.util.datatype.DataTypeSelectionEditor;
import ghidra.app.util.viewer.field.*;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB;
import ghidra.program.database.data.ProgramDataTypeManager;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.*;
import ghidra.test.*;
import ghidra.util.Msg;
import ghidra.util.classfinder.ClassFilter;
@ -856,85 +845,6 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe
return builtinNode;
}
private void findReferencesToField(String choice) {
DockingActionIf searchAction = getAction(plugin, "Find Uses of Field");
assertTrue(searchAction.isEnabledForContext(treeContext));
DataTypeTestUtils.performAction(searchAction, tree, false);
InputWithChoicesDialog d = waitForDialogComponent(InputWithChoicesDialog.class);
@SuppressWarnings("unchecked")
GhidraComboBox<String> combo = (GhidraComboBox<String>) getInstanceField("combo", d);
setComboBoxSelection(combo, choice);
pressButtonByText(d, "OK");
waitForSearchResults();
}
@SuppressWarnings("unchecked")
private LocationReferencesProvider getLocationReferencesProvider() {
LocationReferencesPlugin locationRefsPlugin =
getPlugin(tool, LocationReferencesPlugin.class);
List<LocationReferencesProvider> providerList =
(List<LocationReferencesProvider>) getInstanceField("providerList", locationRefsPlugin);
if (providerList.size() == 0) {
return null;
}
return providerList.get(0);
}
private ThreadedTableModel<?, ?> getTableModel() {
waitForCondition(() -> getLocationReferencesProvider() != null);
LocationReferencesProvider refsProvider = getLocationReferencesProvider();
Object referencesPanel = getInstanceField("referencesPanel", refsProvider);
return (ThreadedTableModel<?, ?>) getInstanceField("tableModel", referencesPanel);
}
private void waitForSearchResults() {
ThreadedTableModel<?, ?> model = getTableModel();
waitForTableModel(model);
}
private HighlightProvider getHighlightProvider() {
CodeViewerService service = tool.getService(CodeViewerService.class);
FormatManager fm = (FormatManager) getInstanceField("formatMgr", service);
return (HighlightProvider) getInstanceField("highlightProvider", fm);
}
private void assertOperandHighlight(String rep, Address addr) {
assertHighlight(OperandFieldFactory.class, rep, addr);
}
private void assertFieldNameHighlight(String rep, Address addr) {
assertHighlight(FieldNameFieldFactory.class, rep, addr);
}
private void assertHighlight(Class<? extends FieldFactory> clazz, String rep, Address addr) {
Listing listing = program.getListing();
CodeUnit cu = listing.getCodeUnitContaining(addr);
if (cu instanceof Data) {
Data data = (Data) cu;
Address minAddress = data.getMinAddress();
long offset = addr.subtract(minAddress);
if (offset != 0) {
Data subData = data.getComponentAt((int) offset);
cu = subData;
}
}
HighlightProvider highlighter = getHighlightProvider();
Highlight[] highlights = highlighter.getHighlights(rep, cu, clazz, -1);
assertNotNull(highlights);
assertTrue(highlights.length != 0);
}
private Address addr(long offset) {
AddressFactory addrMap = program.getAddressFactory();
AddressSpace space = addrMap.getDefaultAddressSpace();
return space.getAddress(offset);
}
private void assertSingleFilterMatch(String[] path) {
GTreeNode rootNode = tree.getRootNode();

View file

@ -31,6 +31,7 @@ import ghidra.app.plugin.core.datamgr.tree.DataTypeArchiveGTree;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.task.TaskMonitor;
import utilities.util.FileUtilities;
@ -146,20 +147,18 @@ public class DataTypeTestUtils {
static void closeArchive(final ArchiveNode archiveNode, final boolean deleteFile)
throws Exception {
final Exception[] container = new Exception[1];
SwingUtilities.invokeAndWait(() -> {
Exception exception = Swing.runNow(() -> {
try {
doCloseArchive(archiveNode, deleteFile);
return null;
}
catch (Exception e) {
container[0] = e;
return e;
}
});
if (container[0] != null) {
throw new RuntimeException("Exception closing archive on Swing thread!: ",
container[0]);
if (exception != null) {
throw new RuntimeException("Exception closing archive on Swing thread!: ", exception);
}
}

View file

@ -83,6 +83,10 @@ public class HistoryList<T> {
* allow duplicates, but to also move the position of an item if it is re-added to the
* list.
*
* <p>For correct behavior when not allowing duplicates, ensure you have defined an
* <code>equals</code> method to work as you expect. If two different items are considered
* equal, then this class will only remove the duplicate if the equals method returns true.
*
* <p>The default is false
*
* @param allowDuplicates true to allow duplicates
@ -162,7 +166,6 @@ public class HistoryList<T> {
* <p>No action is taken if the current pointer is already at the beginning of the list.
*/
public void goBack() {
if (historyIndex == 0) {
return;
}
@ -217,7 +220,7 @@ public class HistoryList<T> {
/**
* Get all items in the history that come after the current history item. They are
* returned in navigation order, as traversed if {@link #goForward() is called.
* returned in navigation order, as traversed if {@link #goForward()} is called.
*
* @return the items
*/

View file

@ -31,8 +31,8 @@ import ghidra.util.task.TaskMonitorAdapter;
* DataTypeManager for a file. Can import categories from a file, or export
* categories to a packed database.
*/
public class FileDataTypeManager extends StandAloneDataTypeManager implements
FileArchiveBasedDataTypeManager {
public class FileDataTypeManager extends StandAloneDataTypeManager
implements FileArchiveBasedDataTypeManager {
public final static String EXTENSION = "gdt"; // Ghidra Data Types
/**
@ -108,15 +108,14 @@ public class FileDataTypeManager extends StandAloneDataTypeManager implements
* @param outputFilename filename for output
* @param databaseId new databaseId
*/
public void saveAs(File saveFile, UniversalID newUniversalId) throws DuplicateFileException,
IOException {
public void saveAs(File saveFile, UniversalID newUniversalId)
throws DuplicateFileException, IOException {
ResourceFile resourceSaveFile = new ResourceFile(saveFile);
// TODO: this should really be a package method and not public!
validateFilename(resourceSaveFile);
try {
universalID = newUniversalId;
packedDB =
((PackedDBHandle) dbHandle).saveAs("DTArchive", saveFile.getParentFile(),
packedDB = ((PackedDBHandle) dbHandle).saveAs("DTArchive", saveFile.getParentFile(),
saveFile.getName(), newUniversalId.getValue(), TaskMonitorAdapter.DUMMY_MONITOR);
file = resourceSaveFile;
updateRootCategoryName(resourceSaveFile, getRootCategory());
@ -134,8 +133,7 @@ public class FileDataTypeManager extends StandAloneDataTypeManager implements
ResourceFile resourceSaveFile = new ResourceFile(saveFile);
validateFilename(resourceSaveFile);
try {
packedDB =
((PackedDBHandle) dbHandle).saveAs("DTArchive", saveFile.getParentFile(),
packedDB = ((PackedDBHandle) dbHandle).saveAs("DTArchive", saveFile.getParentFile(),
saveFile.getName(), TaskMonitorAdapter.DUMMY_MONITOR);
file = resourceSaveFile;
updateRootCategoryName(resourceSaveFile, getRootCategory());
@ -268,4 +266,8 @@ public class FileDataTypeManager extends StandAloneDataTypeManager implements
return ArchiveType.FILE;
}
@Override
public String toString() {
return getClass().getSimpleName() + " - " + getName();
}
}