GP-4930 Improved GoTo dialog to support full and partial namespace paths with wildcard support

This commit is contained in:
ghidragon 2024-10-01 11:52:32 -04:00
parent 7148590e5c
commit 0e0b0255f2
17 changed files with 2167 additions and 1176 deletions

View File

@ -4,9 +4,9 @@
* 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.
@ -16,39 +16,19 @@
package ghidra.app.plugin.core.gotoquery;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ghidra.app.services.QueryData;
import ghidra.app.util.query.ProgramLocationPreviewTableModel;
import ghidra.framework.model.DomainObjectException;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramLocation;
import ghidra.util.UserSearchUtils;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.ClosedException;
import ghidra.util.task.TaskMonitor;
public class GoToQueryResultsTableModel extends ProgramLocationPreviewTableModel {
private QueryData queryData;
private int maxSearchHits;
private List<ProgramLocation> locations;
private SymbolTable symbolTable;
public GoToQueryResultsTableModel(Program prog, QueryData queryData,
ServiceProvider serviceProvider, int maxSearchHits, TaskMonitor monitor) {
super("Goto", serviceProvider, prog, monitor);
this.symbolTable = prog.getSymbolTable();
this.queryData = queryData;
this.maxSearchHits = maxSearchHits;
}
public GoToQueryResultsTableModel(Program prog, ServiceProvider serviceProvider,
List<ProgramLocation> locations, TaskMonitor monitor) {
super("Goto", serviceProvider, prog, monitor);
@ -66,102 +46,8 @@ public class GoToQueryResultsTableModel extends ProgramLocationPreviewTableModel
if (locations != null) {
accumulator.addAll(locations);
locations = null;
return;
}
try {
doLoadMaybeWithExceptions(accumulator, monitor);
}
catch (DomainObjectException doe) {
// Super Special Code:
// There comes a time when this table is asked to load, but the program from whence
// the load comes is no longer open. Normal table models we would dispose, but this
// one is special in that nobody that has a handle to it will get notification of
// the program being closed. So, we must anticipate the problem and deal with it
// ourselves.
Throwable cause = doe.getCause();
if (!(cause instanceof ClosedException)) {
throw doe;
}
cancelAllUpdates();
}
}
private void doLoadMaybeWithExceptions(Accumulator<ProgramLocation> accumulator,
TaskMonitor monitor) throws CancelledException {
searchDefinedSymbols(accumulator, monitor);
searchDynamicSymbols(accumulator, monitor);
}
private void searchDynamicSymbols(Accumulator<ProgramLocation> accumulator, TaskMonitor monitor)
throws CancelledException {
if (!queryData.isIncludeDynamicLables()) {
return;
}
String queryString = queryData.getQueryString();
if (!queryData.isWildCard()) {
// if no wild cards, just parse off the address from the string and go there.
parseDynamic(accumulator, queryString);
return;
}
boolean caseSensitive = queryData.isCaseSensitive();
Pattern pattern = UserSearchUtils.createSearchPattern(queryString, caseSensitive);
ReferenceManager refMgr = getProgram().getReferenceManager();
AddressSet addressSet = getProgram().getAddressFactory().getAddressSet();
AddressIterator addrIt = refMgr.getReferenceDestinationIterator(addressSet, true);
while (addrIt.hasNext() && accumulator.size() < maxSearchHits) {
monitor.checkCancelled();
Address addr = addrIt.next();
Symbol s = symbolTable.getPrimarySymbol(addr);
if (!s.isDynamic()) {
continue;
}
Matcher matcher = pattern.matcher(s.getName());
if (matcher.matches()) {
ProgramLocation programLocation = s.getProgramLocation();
if (programLocation != null) {
accumulator.add(programLocation);
}
}
}
}
private void parseDynamic(Accumulator<ProgramLocation> accumulator, String queryString) {
Address address =
SymbolUtilities.parseDynamicName(getProgram().getAddressFactory(), queryString);
if (address == null) {
return;
}
Symbol s = symbolTable.getPrimarySymbol(address);
if (s == null) {
return;
}
if (s.getName().equalsIgnoreCase(queryString)) {
accumulator.add(s.getProgramLocation());
}
}
private boolean searchDefinedSymbols(Accumulator<ProgramLocation> accumulator,
TaskMonitor monitor) throws CancelledException {
SymbolIterator it =
symbolTable.getSymbolIterator(queryData.getQueryString(), queryData.isCaseSensitive());
while (it.hasNext() && accumulator.size() < maxSearchHits) {
monitor.checkCancelled();
Symbol s = it.next();
ProgramLocation programLocation = s.getProgramLocation();
if (programLocation != null) {
accumulator.add(programLocation);
}
}
return false;
}
}

View File

@ -4,9 +4,9 @@
* 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.
@ -20,12 +20,12 @@ public class QueryData {
/**
* Wildcard char for any string.
*/
private String ANY_STRING_WILDCARD = "*";
private static String ANY_STRING_WILDCARD = "*";
/**
* Wildcard char for a single char.
*/
private String ANY_CHAR_WILDCARD = "?";
private static String ANY_CHAR_WILDCARD = "?";
private final String queryString;
private final boolean caseSensitive;
@ -55,6 +55,10 @@ public class QueryData {
}
public boolean isWildCard() {
return queryString.contains(ANY_STRING_WILDCARD) || queryString.contains(ANY_CHAR_WILDCARD);
return hasWildCards(queryString);
}
public static boolean hasWildCards(String query) {
return query.contains(ANY_STRING_WILDCARD) || query.contains(ANY_CHAR_WILDCARD);
}
}

View File

@ -15,18 +15,12 @@
*/
package ghidra.app.util.navigation;
import java.awt.Component;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.SwingUtilities;
import docking.widgets.table.threaded.ThreadedTableModelListener;
import ghidra.GhidraOptions;
import ghidra.app.nav.Navigatable;
import ghidra.app.nav.NavigationUtils;
import ghidra.app.plugin.core.gotoquery.GoToHelper;
import ghidra.app.plugin.core.gotoquery.GoToQueryResultsTableModel;
import ghidra.app.plugin.core.navigation.NavigationOptions;
import ghidra.app.plugin.core.table.TableComponentProvider;
@ -39,14 +33,12 @@ import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.*;
import ghidra.program.util.AddressEvaluator;
import ghidra.program.util.ProgramLocation;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.table.AddressArrayTableModel;
import ghidra.util.table.GhidraProgramTableModel;
import ghidra.util.task.TaskLauncher;
import ghidra.util.task.TaskMonitor;
public class GoToQuery {
@ -60,68 +52,44 @@ public class GoToQuery {
private QueryData queryData;
private Address fromAddress;
private GhidraProgramTableModel<?> model;
private GoToServiceListener listener;
private TaskMonitor monitor;
protected ProgramGroup programs;
private GoToService goToService;
private GoToQueryThreadedTableModelListener tableModelListener;
private final int maxHits;
private final Plugin plugin;
private final Navigatable navigatable;
private NavigationOptions navigationOptions;
private PluginTool tool;
public GoToQuery(Navigatable navigatable, Plugin plugin, GoToService goToService,
QueryData queryData, Address fromAddr, GoToServiceListener listener,
NavigationOptions navigationOptions, TaskMonitor monitor) {
QueryData queryData, Address fromAddr, NavigationOptions navigationOptions,
TaskMonitor monitor) {
this.navigatable = navigatable;
this.queryData = queryData;
this.plugin = plugin;
this.goToService = goToService;
this.navigationOptions = navigationOptions;
this.tool = plugin.getTool();
Options opt = plugin.getTool().getOptions(SearchConstants.SEARCH_OPTION_NAME);
Options options = plugin.getTool().getOptions(SearchConstants.SEARCH_OPTION_NAME);
this.maxHits =
opt.getInt(GhidraOptions.OPTION_SEARCH_LIMIT, SearchConstants.DEFAULT_SEARCH_LIMIT);
options.getInt(GhidraOptions.OPTION_SEARCH_LIMIT, SearchConstants.DEFAULT_SEARCH_LIMIT);
this.fromAddress = fromAddr;
this.monitor = monitor;
if (listener != null) {
this.listener = listener;
}
else {
this.listener = new DummyGoToServiceListener();
}
programs = getAllPrograms();
tableModelListener = new GoToQueryThreadedTableModelListener();
}
private ProgramGroup getAllPrograms() {
ProgramManager progService = plugin.getTool().getService(ProgramManager.class);
return new ProgramGroup(progService.getAllOpenPrograms(), navigatable.getProgram());
}
public boolean processQuery() {
if (processAddressExpression()) {
return true;
}
if (processWildCard()) {
return true;
}
// Queries can be of several different types. Handle all the non-symbol types first since
// they are faster to try, as they don't require searching through all the program's
// symbols.
if (processFileOffset()) {
return true;
}
if (processSymbolInParsedScope()) {
return true;
}
if (processSymbolInPrograms(getSearchPrograms())) {
if (processAddressExpression()) {
return true;
}
@ -129,242 +97,9 @@ public class GoToQuery {
return true;
}
if (processDynamicOrCaseInsensitive()) {
return true;
}
notifyListener(false);
return false;
}
private boolean checkForOverride() {
GoToOverrideService override = goToService.getOverrideService();
if (override == null) {
return false;
}
ProgramLocation pLoc = override.goTo(queryData.getQueryString());
if (pLoc != null) {
goToService.goTo(navigatable, pLoc, pLoc.getProgram());
notifyListener(true);
return true;
}
return false;
}
private boolean processAddress() {
if (checkForOverride()) {
return true;
}
String queryString = queryData.getQueryString();
for (Program program : getSearchPrograms()) {
Address[] addresses = program.parseAddress(queryString, queryData.isCaseSensitive());
Address[] validAddresses = validateAddresses(program, addresses);
if (validAddresses.length > 0) {
goToAddresses(program, validAddresses);
return true;
}
}
// check once more if the current location has an address for the address string. This
// will catch the case where the current location is in FILE space.
Program currentProgram = navigatable.getProgram();
Address fileAddress = getFileAddress(currentProgram, queryString);
if (fileAddress != null) {
goToAddresses(currentProgram, new Address[] { fileAddress });
return true;
}
return false;
}
private Address getFileAddress(Program program, String queryString) {
if (fromAddress == null) {
return null;
}
try {
Address address = fromAddress.getAddressSpace().getAddress(queryString);
if (address != null && program.getMemory().contains(address)) {
return address;
}
}
catch (AddressFormatException e) {
// ignore and return null
}
return null;
}
private void goToAddresses(Program program, Address[] validAddresses) {
if (validAddresses.length == 1) {
goTo(program, validAddresses[0], fromAddress);
notifyListener(true);
return;
}
Swing.runIfSwingOrRunLater(() -> {
model = new AddressArrayTableModel("Goto: ", plugin.getTool(), program, validAddresses,
monitor);
model.addInitialLoadListener(tableModelListener);
});
}
private void goToProgramLocations(Program program, List<ProgramLocation> locations) {
if (locations.size() == 1) {
goTo(program, locations.get(0));
notifyListener(true);
return;
}
Swing.runIfSwingOrRunLater(() -> {
model = new GoToQueryResultsTableModel(program, plugin.getTool(), locations, monitor);
model.addInitialLoadListener(tableModelListener);
});
}
private boolean processDynamicOrCaseInsensitive() {
if (!queryData.isIncludeDynamicLables() && queryData.isCaseSensitive()) {
return false;
}
Swing.runIfSwingOrRunLater(() -> {
model = new GoToQueryResultsTableModel(navigatable.getProgram(), queryData,
plugin.getTool(), maxHits, monitor);
model.addInitialLoadListener(tableModelListener);
});
return true;
}
private boolean processSymbolInPrograms(Iterable<Program> searchPrograms) {
for (Program program : searchPrograms) {
List<ProgramLocation> locations = getValidSymbolLocationsForProgram(program);
if (!locations.isEmpty()) {
goToProgramLocations(program, locations);
return true;
}
}
return false;
}
private List<ProgramLocation> getValidSymbolLocationsForProgram(Program program) {
List<ProgramLocation> locations = new ArrayList<>();
SymbolTable symTable = program.getSymbolTable();
SymbolIterator it = symTable.getSymbols(queryData.getQueryString());
while (it.hasNext() && locations.size() < maxHits) {
Symbol symbol = it.next();
ProgramLocation location = getProgramLocationForSymbol(symbol, program);
if (location != null) {
locations.add(location);
}
else {
locations.addAll(getExtenalLinkageLocations(symbol));
}
}
return locations;
}
private Collection<ProgramLocation> getExtenalLinkageLocations(Symbol symbol) {
Collection<ProgramLocation> locations = new ArrayList<>();
Program program = symbol.getProgram();
Address[] externalLinkageAddresses =
NavigationUtils.getExternalLinkageAddresses(program, symbol.getAddress());
for (Address address : externalLinkageAddresses) {
ProgramLocation location = GoToHelper.getProgramLocationForAddress(address, program);
if (location != null) {
locations.add(location);
}
}
return locations;
}
private ProgramLocation getProgramLocationForSymbol(Symbol symbol, Program program) {
Address symbolAddress = symbol.getAddress();
if (symbolAddress.isExternalAddress()) {
return null;
}
if ((symbolAddress.isMemoryAddress() && !program.getMemory().contains(symbolAddress))) {
return null;
}
return symbol.getProgramLocation();
}
private boolean processSymbolInParsedScope() {
String queryInput = queryData.getQueryString();
int colonPos = queryInput.lastIndexOf("::");
if (colonPos < 0) {
return false;
}
String scopeName = queryInput.substring(0, colonPos);
String symbolName = queryInput.substring(colonPos + 2);
if (goToSymbolInScope(scopeName, symbolName)) {
notifyListener(true);
return true;
}
return false;
}
private Iterable<Program> getSearchPrograms() {
return navigationOptions.isGoToRestrictedToCurrentProgram()
? Collections.singleton(navigatable.getProgram())
: programs;
}
private boolean processAddressExpression() {
String queryInput = queryData.getQueryString();
if (!isAddressExpression(queryInput)) {
return false;
}
boolean relative = queryInput.matches("^\\s*[+-].*");
Address baseAddr = relative ? fromAddress : null;
for (Program program : getSearchPrograms()) {
Address evalAddr = AddressEvaluator.evaluate(program, baseAddr, queryInput);
if (evalAddr != null) {
boolean success = goTo(program, new ProgramLocation(program, evalAddr));
notifyListener(success);
return true;
}
}
return false;
}
private QueryData cleanupQuery(Program program, QueryData qData) {
String input = qData.getQueryString();
int colonPosition = input.indexOf("::");
if (colonPosition >= 0) {
String preColonString = input.substring(0, colonPosition);
if (isAddressSpaceName(program, preColonString) ||
isBlockName(program, preColonString)) {
// strip off block name or the address space name part
input = input.substring(colonPosition + 2); // 2 for both ':' chars
qData =
new QueryData(input, qData.isCaseSensitive(), qData.isIncludeDynamicLables());
}
}
return qData;
}
private boolean processWildCard() {
if (!queryData.isWildCard()) {
return false;
}
Swing.runIfSwingOrRunLater(() -> {
Program program = navigatable.getProgram();
model = new GoToQueryResultsTableModel(program, cleanupQuery(program, queryData),
plugin.getTool(), maxHits, monitor);
model.addInitialLoadListener(tableModelListener);
});
return true;
// none of the specialized query handlers matched, so try to process the query
// as a symbol (label, function name, variable name, etc.)
return processSymbols();
}
private boolean processFileOffset() {
@ -373,7 +108,6 @@ public class GoToQuery {
if (matcher.matches()) {
try {
long offset = Long.decode(matcher.group(1));
// NOTE: Addresses are parsed via AbstractAddressSpace.parseString(String addr)
Program currentProgram = navigatable.getProgram();
Memory mem = currentProgram.getMemory();
List<Address> addresses = mem.locateAddressesForFileOffset(offset);
@ -389,24 +123,138 @@ public class GoToQuery {
return false;
}
private boolean isAddressExpression(String input) {
return (input.indexOf('+') >= 0 || input.indexOf('-') >= 0 || input.indexOf('*') > 0);
}
private boolean processAddressExpression() {
String queryInput = queryData.getQueryString();
if (!isAddressExpression(queryInput)) {
return false;
}
private boolean isAddressSpaceName(Program program, String input) {
return program.getAddressFactory().getAddressSpace(input) != null;
}
private boolean isBlockName(Program program, String input) {
MemoryBlock[] blocks = program.getMemory().getBlocks();
for (MemoryBlock element : blocks) {
if (element.getName().equals(input)) {
return true;
// checking for leading "+" or "-", ignoring spaces.
boolean relative = queryInput.matches("^\\s*[+-].*");
Address baseAddr = relative ? fromAddress : null;
for (Program program : getSearchPrograms()) {
Address evalAddr = AddressEvaluator.evaluate(program, baseAddr, queryInput);
if (evalAddr != null) {
return goTo(program, new ProgramLocation(program, evalAddr));
}
}
return false;
}
private boolean processAddress() {
String queryString = queryData.getQueryString();
for (Program program : getSearchPrograms()) {
Address[] addresses = program.parseAddress(queryString, queryData.isCaseSensitive());
Address[] validAddresses = validateAddresses(program, addresses);
if (validAddresses.length > 0) {
return goToAddresses(program, validAddresses);
}
}
// check once more if the current location has an address for the address string. This
// will catch the case where the current location is in FILE space.
Program currentProgram = navigatable.getProgram();
Address fileAddress = getFileAddress(currentProgram, queryString);
if (fileAddress != null) {
return goToAddresses(currentProgram, new Address[] { fileAddress });
}
return false;
}
private boolean processSymbols() {
GoToSymbolSearchTask task =
new GoToSymbolSearchTask(queryData, getSearchPrograms(), maxHits);
TaskLauncher.launch(task);
List<ProgramLocation> locations = task.getResults();
if (locations.isEmpty()) {
return false;
}
Program program = locations.get(0).getProgram();
return goToProgramLocations(program, locations);
}
private List<ProgramLocation> toProgramLocations(Address[] addresses, Program program) {
return Arrays.stream(addresses).map(a -> new ProgramLocation(program, a)).toList();
}
private Address getFileAddress(Program program, String addressString) {
if (fromAddress == null) {
return null;
}
try {
Address address = fromAddress.getAddressSpace().getAddress(addressString);
if (address != null && program.getMemory().contains(address)) {
return address;
}
}
catch (AddressFormatException e) {
// ignore and return null
}
return null;
}
private boolean goToAddresses(Program program, Address[] validAddresses) {
List<ProgramLocation> locations = toProgramLocations(validAddresses, program);
return goToProgramLocations(program, locations);
}
private boolean goToProgramLocations(Program program, List<ProgramLocation> locations) {
if (locations.size() == 1) {
return goTo(program, locations.get(0));
}
Swing.runIfSwingOrRunLater(() -> showResultsInTable(locations));
return true;
}
private void showResultsInTable(List<ProgramLocation> locations) {
Program program = locations.get(0).getProgram();
if (locations.size() > maxHits) {
showMaxSearchWarning(locations.size());
}
showModelInTable(new GoToQueryResultsTableModel(program, tool, locations, monitor));
}
private void showModelInTable(GhidraProgramTableModel<?> model) {
TableService service = tool.getService(TableService.class);
TableComponentProvider<?> provider = service.showTable(
"Goto " + queryData.getQueryString(), "Goto", model, "Go To", navigatable);
provider.requestFocus();
}
/**
* Returns the programs to search. If searching more than the current program, make sure
* the current program is first in the list.
* @return the list of program to search with the current program first
*/
private List<Program> getSearchPrograms() {
Program currentProgram = navigatable.getProgram();
List<Program> searchPrograms = new ArrayList<>();
searchPrograms.add(currentProgram);
if (!navigationOptions.isGoToRestrictedToCurrentProgram()) {
ProgramManager programManager = plugin.getTool().getService(ProgramManager.class);
Program[] allOpenPrograms = programManager.getAllOpenPrograms();
for (Program program : allOpenPrograms) {
if (program != currentProgram) {
searchPrograms.add(program);
}
}
}
return searchPrograms;
}
private boolean isAddressExpression(String input) {
return (input.indexOf('+') >= 0 || input.indexOf('-') >= 0 || input.indexOf('*') > 0);
}
private Address[] validateAddresses(Program program, Address[] addrs) {
Memory memory = program.getMemory();
ArrayList<Address> list = new ArrayList<>();
@ -447,96 +295,6 @@ public class GoToQuery {
return currentSpace.equals(address.getAddressSpace());
}
private boolean goToSymbolInScope(String scopeName, String symbolStr) {
for (Program program : getSearchPrograms()) {
SymbolTable symTable = program.getSymbolTable();
Namespace scope = getScope(program, program.getGlobalNamespace(), scopeName);
if (scope != null) {
List<Symbol> symbols = symTable.getSymbols(symbolStr, scope);
if (!symbols.isEmpty()) {
return gotoLabels(program, symbols);
}
}
//else see if scopeName is really memoryBlock name.
return goToSymbolInMemoryBlock(scopeName, symbolStr, program);
}
return false;
}
private boolean gotoLabels(Program program, List<Symbol> symbols) {
if (symbols.size() == 1) {
return gotoLabel(program, symbols.get(0));
}
List<ProgramLocation> programLocations = new ArrayList<>();
for (Symbol symbol : symbols) {
ProgramLocation programLocation = symbol.getProgramLocation();
if (programLocation != null) {
programLocations.add(symbol.getProgramLocation());
}
}
goToProgramLocations(program, programLocations);
return true;
}
private boolean goToSymbolInMemoryBlock(String scopeName, String symbolStr, Program program) {
List<Symbol> globalSymbols =
program.getSymbolTable().getLabelOrFunctionSymbols(symbolStr, null);
if (globalSymbols.isEmpty()) {
return false;
}
List<Symbol> matchingSymbols = new ArrayList<>();
for (Symbol symbol : globalSymbols) {
Address address = symbol.getAddress();
MemoryBlock block = program.getMemory().getBlock(address);
if (block != null && block.getName().equals(scopeName)) {
matchingSymbols.add(symbol);
}
}
if (matchingSymbols.isEmpty()) {
return false;
}
return gotoLabels(program, matchingSymbols);
}
private Namespace getScope(Program program, Namespace parent, String scopeName) {
int colonIndex = scopeName.lastIndexOf("::");
if (colonIndex >= 0) {
String parentScopeName = scopeName.substring(0, colonIndex);
scopeName = scopeName.substring(colonIndex + 2);
parent = getScope(program, parent, parentScopeName);
if (parent == null) {
return null;
}
}
SymbolTable symTable = program.getSymbolTable();
Namespace namespace = symTable.getNamespace(scopeName, parent);
return namespace;
}
private boolean gotoLabel(Program program, Symbol symbol) {
if (symbol == null) {
return false;
}
ProgramLocation loc = symbol.getProgramLocation();
if (loc == null) {
return false;
}
if (goToService.goTo(navigatable, loc, program)) {
return true;
}
return false;
}
private boolean goTo(Program program, ProgramLocation loc) {
if (loc == null) {
return false;
@ -548,116 +306,10 @@ public class GoToQuery {
return goToService.goTo(navigatable, loc, program);
}
private boolean goTo(Program program, Address gotoAddress, Address refAddress) {
if (program == null) {
program = navigatable.getProgram();
}
if (program.getMemory().contains(gotoAddress)) {
goToService.goTo(navigatable, program, gotoAddress, refAddress);
return true;
}
return false;
}
private void notifyListener(boolean hasData) {
listener.gotoCompleted(queryData.getQueryString(), hasData);
}
//==================================================================================================
// Inner Classes
//==================================================================================================
/**
* A class to maintain our collection of open programs and to provide an <code>Iterator</code>
* when we need to process the collection. The {@link #iterator()} method has a side-effect
* of putting the current program at the front of the <code>Iterator</code> so that the current
* program is always searched first when processing the collection of programs.
*/
protected class ProgramGroup implements Iterable<Program> {
private List<Program> programList;
public ProgramGroup(Program[] programs, Program navigatableProgram) {
programList = new ArrayList<>(Arrays.asList(programs));
if (!programList.contains(navigatableProgram)) {
programList.add(navigatableProgram);
}
}
@Override
public Iterator<Program> iterator() {
List<Program> newList = new ArrayList<>(programList);
Program currentProgram = navigatable.getProgram();
int index = newList.indexOf(currentProgram);
Collections.swap(newList, 0, index);
return newList.iterator();
}
}
private class GoToQueryThreadedTableModelListener implements ThreadedTableModelListener {
@Override
public void loadPending() {
// don't care
}
@Override
public void loadingStarted() {
// don't care
}
@Override
public void loadingFinished(boolean wasCancelled) {
int rowCount = model.getRowCount();
boolean hasData = rowCount > 0;
if (!hasData) {
notifyListener(false);
return;
}
if (rowCount == 1) {
goTo(null, model.getProgramLocation(0, 0));
notifyListener(true);
return;
}
PluginTool tool = plugin.getTool();
if (tool == null) {
return; // this can happen if a search is taking place when the tool is closed
}
TableService service = tool.getService(TableService.class);
TableComponentProvider<?> provider = service.showTable(
"Goto " + queryData.getQueryString(), "Goto", model, "Go To", navigatable);
if (model.getRowCount() >= maxHits) {
showMaxSearchWarning(provider.getComponent(), model.getRowCount());
}
notifyListener(true);
}
private void showMaxSearchWarning(final Component parent, final int matchCount) {
// to parent the following dialog properly, we must make sure the above query results
// component has been shown (it gets shown in an invoke later during a docking windows update)
SwingUtilities.invokeLater(() -> Msg.showWarn(getClass(), parent,
"Search Limit Exceeded!",
"Stopped search after finding " + matchCount + " matches.\n" +
"The search limit can be changed at Edit->Tool Options, under Search."));
}
}
private class DummyGoToServiceListener implements GoToServiceListener {
@Override
public void gotoCompleted(String queryString, boolean foundResults) {
// stubbed
}
@Override
public void gotoFailed(Exception exc) {
// stubbed
}
private void showMaxSearchWarning(int matchCount) {
Msg.showWarn(getClass(), null,
"Search Limit Exceeded!",
"Stopped search after finding " + matchCount + " matches.\n" +
"The search limit can be changed at Edit->Tool Options, under Search.");
}
}

View File

@ -4,9 +4,9 @@
* 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.
@ -139,10 +139,14 @@ public class GoToServiceImpl implements GoToService {
navigatable = defaultNavigatable;
}
GoToQuery query = new GoToQuery(navigatable, plugin, this, queryData, fromAddr, listener,
GoToQuery query = new GoToQuery(navigatable, plugin, this, queryData, fromAddr,
helper.getOptions(), monitor);
return query.processQuery();
boolean result = query.processQuery();
if (listener != null) {
listener.gotoCompleted(queryData.getQueryString(), result);
}
return result;
}
@Override

View File

@ -0,0 +1,55 @@
/* ###
* 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.app.util.navigation;
import java.util.List;
import ghidra.app.services.QueryData;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
/**
* Task for searching for symbols. All the logic for the search is done by the
* {@link SymbolSearcher}.
*/
public class GoToSymbolSearchTask extends Task {
private QueryData queryData;
private List<Program> searchPrograms;
private List<ProgramLocation> results;
private int limit;
public GoToSymbolSearchTask(QueryData queryData, List<Program> searchPrograms, int limit) {
super("Searching Symbols...");
this.queryData = queryData;
this.searchPrograms = searchPrograms;
this.limit = limit;
}
@Override
public void run(TaskMonitor monitor) throws CancelledException {
SymbolSearcher searcher = new SymbolSearcher(queryData, limit, monitor);
results = searcher.findMatchingSymbolLocations(searchPrograms);
}
public List<ProgramLocation> getResults() {
return results;
}
}

View File

@ -0,0 +1,258 @@
/* ###
* 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.app.util.navigation;
import static ghidra.util.UserSearchUtils.*;
import java.util.Arrays;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import ghidra.app.services.QueryData;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.Symbol;
/**
* Class for matching symbol names with or without namespace paths and wildcards.
*/
public class SymbolMatcher {
private String symbolName;
private Pattern pattern;
private boolean caseSensitive;
private boolean isRelativePath;
private boolean isPossibleMemoryBlockPattern;
public SymbolMatcher(String queryString, boolean caseSensitive) {
// assume users entering spaces is a mistake, so just remove them.
queryString = queryString.replaceAll("\\s", "");
this.caseSensitive = caseSensitive;
this.isRelativePath = !queryString.startsWith(Namespace.DELIMITER);
this.symbolName = getSymbolName(queryString);
this.pattern = createPattern(queryString);
this.isPossibleMemoryBlockPattern = checkIfPossibleMemoryBlockPattern(queryString);
}
private boolean checkIfPossibleMemoryBlockPattern(String queryString) {
// A legacy feature is the ability to also be able to find a label in a particular memory
// block using the same syntax as a symbol in a namespace. So something like
// "block::bob" would find the symbol "bob" (regardless of its namespace) if it were
// in a memory block named "block". (Also, this only worked if there wasn't also a
// symbol in a namespace named "block"). Now that wildcards are supported in the namespace
// specifications, this feature becomes even more confusing. To avoid this, the legacy
// memory block feature will not support wildcards and probably should be removed
// at some point.
int lastIndexOf = queryString.lastIndexOf(Namespace.DELIMITER);
// if no delimiter exists or it starts with a delimiter, then it can't match a memory block
if (lastIndexOf < 1) {
return false;
}
String qualifierPart = queryString.substring(0, lastIndexOf);
// if the qualifier is a multi part path, then it can't match a memory block
if (qualifierPart.indexOf(Namespace.DELIMITER) >= 0) {
return false;
}
// we don't support wildcard when matching against memory block names
return !qualifierPart.contains("*") && !qualifierPart.contains("?");
}
private String getSymbolName(String queryString) {
int index = queryString.lastIndexOf(Namespace.DELIMITER);
if (index < 0) {
return queryString;
}
return queryString.substring(index + Namespace.DELIMITER.length());
}
private Pattern createPattern(String userInput) {
// We only support globbing characters in the query, any other regex characters need
// to be escaped before we feed it to Java's regex Pattern class. But we need to do it
// before we begin our substitutions as we will be adding some of those character into
// the query string and we don't want those to be escaped.
String s = escapeNonGlobbingRegexCharacters(userInput);
s = replaceNamespaceDelimiters(s);
s = removeExcessStars(s);
s = convertNameGlobingToRegEx(s);
s = convertPathGlobingToRegEx(s);
s = convertRelativePathToRegEx(s);
return Pattern.compile(s, createRegexOptions());
}
private String removeExcessStars(String s) {
// There is never a reason to have 3 or more stars in the query. To avoid errors
// creating a regex pattern, replace runs of 3 or more starts with two stars.
// Later, the method that handles path globbing (**) chars, will either convert
// ** to a path matching expression, or if not valid in its location, to a
// single * regex pattern.
int start = s.indexOf("***");
while (start >= 0) {
int end = findFirstNonStar(s, start);
s = s.substring(0, start + 2) + s.substring(end);
start = s.indexOf("***");
}
return s;
}
private int findFirstNonStar(String query, int index) {
while (index < query.length() && query.charAt(index) == '*') {
index++;
}
return index;
}
private String replaceNamespaceDelimiters(String s) {
// To make regex processing easier, replace any namespace delimiter ("::") with
// a single character delimiter. We chose the space character because spaces can't
// exist in namespace names or symbol names.
// also we remove any starting delimiters
if (!isRelativePath) {
s = s.substring(Namespace.DELIMITER.length());
}
return s.replaceAll(Namespace.DELIMITER, " ");
}
private String convertPathGlobingToRegEx(String s) {
// Path globbing uses "**" to match any number of namespace elements in the symbol path
// Valid examples of path globbing are "a::**::b", "**::a", or "a::**".
//
// In order to handle the case where it matches zero path elements ("a::**::b" should
// match "a::b"), we need to remove either the starting delimiter or the ending delimiter.
// Also note that we are doing this replacement after all "::" have been replaced by spaces.
// First replace " ** " with a regex pattern that matches either: a space followed by one
// or more characters followed by another space; or a single space. The second case
// handles when the ** matches zero elements such as "a::**::b" matches "a::b".
s = s.replaceAll(" \\*\\* ", "( .* | )");
// If the string starts with "** ", replace it with the regex pattern that matches either:
// anything followed by a space; or nothing at all.
s = s.replaceAll("^\\*\\* ", "(.* |)");
// If the string ends with " **", replace it with the regex pattern that matches a space
// followed by anything.
s = s.replaceAll(" \\*\\*$", " .*");
// Finally, any other "**", not handled is considered a mistake and is treated as if a
// single start was entered, which is mapped to the regex expression any number of
// non-space characters.
s = s.replaceAll("\\*\\*", "[^ ]*");
return s;
}
private String convertNameGlobingToRegEx(String s) {
// Name globing here refers to using the "*" or "?" globbing characters. However,
// we only want them to apply to a single namespace or symbol name element. In other words
// we can't use the reg-ex ".*" because it would match across delimiters which we don't
// want. The alternative is to use the "match everything but" construct where we use
// "[^ ] which means match anything but spaces which is the delimiter we are using.
//
// There is a wrinkle for this substitution. We are replacing only single "*" characters,
// but we need to avoid doubles (**) as those will be handled later by the path globbing.
// To do this we used look ahead and look behind regex to only match single stars and not
// double stars.
// replace single "*" with the regex pattern that matches everything but spaces
s = s.replaceAll("(?<!\\*)\\*(?!\\*)", "[^ ]*");
// replace "?" with regex pattern that matches a single character
s = s.replaceAll("\\?", ".");
return s;
}
private String convertRelativePathToRegEx(String s) {
// If the query is relative, add a "match anything" regex pattern to the front of the query
// so that it will match any number of parent namespaces containing the specified
// symbol/namespace path.
if (!s.isBlank() && isRelativePath) {
s = ".*" + s;
}
return s;
}
public String getSymbolName() {
return symbolName;
}
private int createRegexOptions() {
if (!caseSensitive) {
return Pattern.CASE_INSENSITIVE;
}
return 0;
}
/**
* Returns true if the symbol name part of the query string has no wildcards and is
* case sensitive.
* @return true if the query has no wildcards and is case sensitive.
*/
public boolean hasFullySpecifiedName() {
return !QueryData.hasWildCards(symbolName) && caseSensitive;
}
/**
* Returns true if there are wildcards in the symbol name.
* @return true if there are wildcards in the symbol name
*/
public boolean hasWildCardsInSymbolName() {
return QueryData.hasWildCards(symbolName);
}
/**
* Returns true if the given symbol matches the query specification for this matcher.
* @param symbol the symbol to test
* @return true if the given symbol matches the query specification for this matcher
*/
public boolean matches(Symbol symbol) {
String path = createSymbolPathWithSpaces(symbol);
if (pattern.matcher(path).matches()) {
return true;
}
// legacy feature where the query may have specified a memory block name instead of a
// namespace path.
return checkMemoryBlockName(symbol);
}
private String createSymbolPathWithSpaces(Symbol symbol) {
String[] path = symbol.getPath();
return Arrays.stream(path).collect(Collectors.joining(" "));
}
private boolean checkMemoryBlockName(Symbol symbol) {
if (!isPossibleMemoryBlockPattern) {
return false;
}
Program program = symbol.getProgram();
MemoryBlock block = program.getMemory().getBlock(symbol.getAddress());
if (block != null) {
String blockNamePath = block.getName() + " " + symbol.getName();
return pattern.matcher(blockNamePath).matches();
}
return false;
}
}

View File

@ -0,0 +1,227 @@
/* ###
* 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.app.util.navigation;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.nav.NavigationUtils;
import ghidra.app.plugin.core.gotoquery.GoToHelper;
import ghidra.app.services.QueryData;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramLocation;
import ghidra.util.task.TaskMonitor;
/**
* Class for searching for symbols that match a given query string.
* <P>
* The query string may include full or partial (absolute or relative) namespace path information.
* The standard namespace delimiter ("::") is used to separate the query into it separate pieces,
* with each piece used to either match a namespace or a symbol name, with the symbol
* name piece always being the last piece (or the only piece).
* <P>
* Both the namespace pieces and the symbol name piece may contain wildcards ("*" or "?") and those
* wildcards only apply to a single element. For example, if a symbol's full path was "a::b::c::d"
* and the query was "a::*::d", it would not match as the "*" can only match one element.
* <P>
* By default all queries are considered relative. In other words, the first namespace element
* does not need to be at the root global level. For example, in the "a::b::c::d" example, the "d"
* symbol could be found by "d", "c::d", "b::c::d". To avoid this behavior, the query may begin
* with a "::" delimiter which means the path is absolute and the first element must be at the
* root level. So, in the previous example, "::a::b::c::d" would match but, "::c::d" would not.
* <P>
* There are also two parameters in the QueryData object that affect how the search algorithm is
* conducted. One is "Case Sensitive" and the other is "Include Dynamic Labels". If the search
* is case insensitive or there are wild cards in the symbol name, the only option is to do a full
* search of all defined symbols, looking for matches. If that is not the case, the search can
* do a direct look up of matching symbols using the program database's symbol index.
* <P>
* If the "Include Dynamic Labels" options is on, then a brute force of the defined references is
* also performed, looking at all addresses that a reference points to, getting the dynamic
* (not stored) symbol at that address and checking if it matches.
* <P>
* One last behavior to note is that the search takes a list of programs to search. However, it
* only returns results from the FIRST program to have any results. If the need to search all
* programs completely is ever needed, a second "find" method could easily be added.
*/
public class SymbolSearcher {
private SymbolMatcher symbolMatcher;
private QueryData queryData;
private int limit;
private TaskMonitor monitor;
public SymbolSearcher(QueryData data, int limit, TaskMonitor monitor) {
this.queryData = data;
this.limit = limit;
this.monitor = monitor;
this.symbolMatcher =
new SymbolMatcher(queryData.getQueryString(), queryData.isCaseSensitive());
}
public List<ProgramLocation> findMatchingSymbolLocations(List<Program> searchPrograms) {
List<ProgramLocation> locations = new ArrayList<>();
for (Program program : searchPrograms) {
if (monitor.isCancelled()) {
break;
}
if (findMatchingSymbolLocations(program, locations)) {
return locations;
}
}
return locations;
}
private boolean findMatchingSymbolLocations(Program program, List<ProgramLocation> locations) {
if (!findSymbolsByDirectLookup(program, locations)) {
findSymbolsByBruteForce(program, locations);
}
return !locations.isEmpty();
}
private boolean findSymbolsByDirectLookup(Program program, List<ProgramLocation> locations) {
// can only do direct lookup of symbol name if it has no wildcards and is case sensitive
if (!symbolMatcher.hasFullySpecifiedName()) {
return false;
}
String symbolName = symbolMatcher.getSymbolName();
return scanSymbols(program, program.getSymbolTable().getSymbols(symbolName), locations);
}
private void findSymbolsByBruteForce(Program program, List<ProgramLocation> locations) {
// only need to do this if the name is fuzzy; otherwise a direct lookup already happened
if (!symbolMatcher.hasFullySpecifiedName()) {
searchDefinedSymbols(program, locations);
}
// if dynamic symbols are on, we also need to search through references, looking for default
// symbol names (LAB*, FUN*, etc.)
if (queryData.isIncludeDynamicLables()) {
searchDynamicSymbolsByReference(program, locations);
}
}
private void searchDynamicSymbolsByReference(Program program, List<ProgramLocation> locations) {
if (!symbolMatcher.hasWildCardsInSymbolName()) {
// if no wild cards, just parse off the address from the string and go there.
parseDynamic(program, locations);
return;
}
SymbolTable symbolTable = program.getSymbolTable();
ReferenceManager refMgr = program.getReferenceManager();
AddressSet addressSet = program.getAddressFactory().getAddressSet();
AddressIterator addrIt = refMgr.getReferenceDestinationIterator(addressSet, true);
while (addrIt.hasNext() && locations.size() < limit) {
if (monitor.isCancelled()) {
return;
}
Address addr = addrIt.next();
Symbol s = symbolTable.getPrimarySymbol(addr);
if (s.isDynamic()) {
addSymbolIfMatches(s, locations);
}
}
}
private boolean addSymbolIfMatches(Symbol s, List<ProgramLocation> locations) {
if (symbolMatcher.matches(s)) {
ProgramLocation programLocation = getProgramLocationForSymbol(s);
if (programLocation != null) {
locations.add(programLocation);
return true;
}
return addExternalLinkageLocations(s, locations);
}
return false;
}
private boolean addExternalLinkageLocations(Symbol symbol, List<ProgramLocation> locations) {
boolean addedLocations = false;
Program program = symbol.getProgram();
Address[] externalLinkageAddresses =
NavigationUtils.getExternalLinkageAddresses(program, symbol.getAddress());
for (Address address : externalLinkageAddresses) {
ProgramLocation location = GoToHelper.getProgramLocationForAddress(address, program);
if (location != null) {
addedLocations = true;
locations.add(location);
}
}
return addedLocations;
}
private void parseDynamic(Program program, List<ProgramLocation> locations) {
AddressFactory addressFactory = program.getAddressFactory();
String symbolName = symbolMatcher.getSymbolName();
Address address = SymbolUtilities.parseDynamicName(addressFactory, symbolName);
if (address == null) {
return;
}
Symbol s = program.getSymbolTable().getPrimarySymbol(address);
addSymbolIfMatches(s, locations);
}
private void searchDefinedSymbols(Program program, List<ProgramLocation> locations) {
String symbolName = symbolMatcher.getSymbolName();
SymbolTable symbolTable = program.getSymbolTable();
SymbolIterator it = symbolTable.getSymbolIterator(symbolName, queryData.isCaseSensitive());
scanSymbols(program, it, locations);
}
private boolean scanSymbols(Program program, SymbolIterator it,
List<ProgramLocation> locations) {
boolean addedSymbols = false;
while (it.hasNext() && locations.size() < limit) {
if (monitor.isCancelled()) {
break;
}
Symbol symbol = it.next();
addedSymbols |= addSymbolIfMatches(symbol, locations);
}
return addedSymbols;
}
private ProgramLocation getProgramLocationForSymbol(Symbol symbol) {
Address symbolAddress = symbol.getAddress();
if (symbolAddress.isExternalAddress()) {
return null;
}
Memory memory = symbol.getProgram().getMemory();
if ((symbolAddress.isMemoryAddress() && !memory.contains(symbolAddress))) {
return null;
}
return symbol.getProgramLocation();
}
}

View File

@ -4,9 +4,9 @@
* 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.
@ -17,9 +17,9 @@ package ghidra.app.plugin.core.navigation;
import static org.junit.Assert.*;
import java.util.*;
import java.util.List;
import java.util.Set;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import org.junit.*;
@ -38,20 +38,17 @@ import ghidra.app.cmd.label.CreateNamespacesCmd;
import ghidra.app.cmd.refs.AddMemRefCmd;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.gotoquery.GoToQueryResultsTableModel;
import ghidra.app.plugin.core.progmgr.MultiTabPlugin;
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
import ghidra.app.plugin.core.table.TableComponentProvider;
import ghidra.app.plugin.core.table.TableServicePlugin;
import ghidra.app.services.ProgramManager;
import ghidra.app.services.QueryData;
import ghidra.app.util.MemoryBlockUtils;
import ghidra.app.util.SearchConstants;
import ghidra.app.util.bin.ByteArrayProvider;
import ghidra.app.util.navigation.GoToAddressLabelDialog;
import ghidra.framework.options.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.ServiceProviderStub;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB;
import ghidra.program.database.mem.FileBytes;
@ -65,10 +62,8 @@ import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.VariableNameFieldLocation;
import ghidra.test.*;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.table.GhidraProgramTableModel;
import ghidra.util.table.field.LabelTableColumn;
import ghidra.util.task.TaskMonitor;
import util.CollectionUtils;
@ -81,7 +76,6 @@ public class GoToAddressLabelPluginTest extends AbstractGhidraHeadedIntegrationT
private GoToAddressLabelDialog dialog;
private CodeBrowserPlugin cbPlugin;
private CodeViewerProvider provider;
private JButton okButton;
@Before
public void setUp() throws Exception {
@ -97,7 +91,6 @@ public class GoToAddressLabelPluginTest extends AbstractGhidraHeadedIntegrationT
provider = cbPlugin.getProvider();
showTool(tool);
dialog = plugin.getDialog();
okButton = (JButton) TestUtils.getInstanceField("okButton", dialog);
setCaseSensitive(true);
}
@ -628,14 +621,9 @@ public class GoToAddressLabelPluginTest extends AbstractGhidraHeadedIntegrationT
tx(program, () -> {
symbol.setName("COmlg32.dll_PageSetupDlgW", SourceType.USER_DEFINED);
});
JCheckBox cb = findComponent(dialog, JCheckBox.class);
runSwing(() -> {
cb.setSelected(false);
dialog.setText("COm*");
dialog.okCallback();
});
setCaseSensitive(false);
setText("COm*");
performOkCallback();
GhidraProgramTableModel<?> model = waitForModel();
assertEquals(3, model.getRowCount());
@ -809,16 +797,13 @@ public class GoToAddressLabelPluginTest extends AbstractGhidraHeadedIntegrationT
//
loadProgram("x86");
setText("*");
setCaseSensitive(true);
performOkCallback();
boolean caseSensitive = true;
List<String> list = search("*", caseSensitive);
assertTrue("A wildcard search did not find all symbols - found: " + list, list.size() > 20);
list = search("dat*", caseSensitive);
assertEquals(0, list.size());
list = search("DAT*", caseSensitive);
assertItemsStartWtih(list, "DAT");
GhidraProgramTableModel<?> model = waitForModel();
List<?> list = model.getModelData();
assertTrue("A wildcard search did not find all symbols, found " + list, list.size() > 20);
}
@Test
@ -862,50 +847,6 @@ public class GoToAddressLabelPluginTest extends AbstractGhidraHeadedIntegrationT
});
}
private void assertItemsStartWtih(List<String> list, String prefix) {
for (String s : list) {
assertTrue(String.format("List item '%s' does not start with prefix '%s'", s, prefix),
s.startsWith(prefix));
}
}
private List<String> search(String text, boolean caseSensitive) {
QueryData queryData = new QueryData(text, caseSensitive, true);
GoToQueryResultsTableModel model = runSwing(() -> {
int maxHits = 100;
return new GoToQueryResultsTableModel(program, queryData, new ServiceProviderStub(),
maxHits, TaskMonitor.DUMMY);
});
waitForTableModel(model);
int columnIndex = model.getColumnIndex(LabelTableColumn.class);
List<String> results = new ArrayList<>();
List<ProgramLocation> data = model.getModelData();
if (data.isEmpty()) {
// debug
Msg.debug(this, "No search results found *or* failure to wait for threaded table " +
"model - " + "trying again");
printOpenWindows();
waitForTableModel(model);
data = model.getModelData();
Msg.debug(this, "\twaited again--still empty? - size: " + data.size());
}
for (ProgramLocation loc : data) {
String label = (String) model.getColumnValueForRow(loc, columnIndex);
results.add(label);
}
return results;
}
private void loadProgram(String programName) throws Exception {
program = doLoadProgram(programName);
@ -1048,21 +989,11 @@ public class GoToAddressLabelPluginTest extends AbstractGhidraHeadedIntegrationT
}
private GhidraProgramTableModel<?> waitForModel() throws Exception {
int i = 0;
while (i++ < 50) {
TableComponentProvider<?>[] providers = getProviders();
if (providers.length > 0) {
GThreadedTablePanel<?> panel = (GThreadedTablePanel<?>) TestUtils
.getInstanceField("threadedPanel", providers[0]);
GTable table = panel.getTable();
while (panel.isBusy()) {
Thread.sleep(50);
}
return (GhidraProgramTableModel<?>) table.getModel();
}
Thread.sleep(50);
}
throw new Exception("Unable to get threaded table model");
TableComponentProvider<?> tableProvider =
waitForComponentProvider(TableComponentProvider.class);
GhidraProgramTableModel<?> model = tableProvider.getModel();
waitForTableModel(model);
return model;
}
private TableComponentProvider<?>[] getProviders() {
@ -1102,14 +1033,6 @@ public class GoToAddressLabelPluginTest extends AbstractGhidraHeadedIntegrationT
private void performOkCallback() throws Exception {
runSwing(() -> dialog.okCallback());
waitForSwing();
waitForOKCallback();
waitForSwing();
}
private void waitForOKCallback() {
waitForCondition(() -> runSwing(() -> okButton.isEnabled()));
}
private void assumeCurrentAddressSpace(boolean b) {

View File

@ -0,0 +1,239 @@
/* ###
* 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.app.plugin.core.navigation;
import static org.junit.Assert.*;
import javax.swing.JCheckBox;
import org.junit.*;
import generic.test.TestUtils;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.progmgr.MultiTabPlugin;
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
import ghidra.app.plugin.core.table.TableComponentProvider;
import ghidra.app.services.ProgramManager;
import ghidra.app.util.navigation.GoToAddressLabelDialog;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.listing.Program;
import ghidra.test.*;
import ghidra.util.Swing;
import ghidra.util.table.GhidraProgramTableModel;
public class GoToAddressLabelPluginWithNamespaceTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env;
private PluginTool tool;
private AddressFactory addrFactory;
private Program program;
private GoToAddressLabelPlugin plugin;
private GoToAddressLabelDialog dialog;
private CodeBrowserPlugin cbPlugin;
private CodeViewerProvider provider;
private GhidraProgramTableModel<?> model;
@Before
public void setUp() throws Exception {
env = new TestEnv();
tool = env.getTool();
tool.addPlugin(GoToAddressLabelPlugin.class.getName());
tool.addPlugin(ProgramManagerPlugin.class.getName());
tool.addPlugin(CodeBrowserPlugin.class.getName());
tool.addPlugin(MultiTabPlugin.class.getName());
plugin = env.getPlugin(GoToAddressLabelPlugin.class);
cbPlugin = env.getPlugin(CodeBrowserPlugin.class);
provider = cbPlugin.getProvider();
showTool(tool);
buildProgram();
final ProgramManager pm = tool.getService(ProgramManager.class);
runSwing(() -> pm.openProgram(program.getDomainFile()));
dialog = plugin.getDialog();
setCaseSensitive(true);
showDialog();
}
@After
public void tearDown() {
closeAllWindows();
env.dispose();
}
@Test
public void testExactSearch() throws Exception {
setText("billy");
performOkCallback();
assertEquals(addr("00001000"), cbPlugin.getCurrentAddress());
model = waitForModel();
assertEquals(4, model.getRowCount());
assertRow(0, "00001000", "billy");
assertRow(1, "00001001", "billy");
assertRow(2, "00001002", "billy");
assertRow(3, "00001003", "billy");
}
@Test
public void testWildInSymbolName() throws Exception {
setText("*ll*");
performOkCallback();
model = waitForModel();
assertEquals(8, model.getRowCount());
assertRow(0, "00001000", "billy");
assertRow(1, "00001001", "billy");
assertRow(2, "00001002", "billy");
assertRow(3, "00001003", "billy");
assertRow(4, "00001004", "sally");
assertRow(5, "00001005", "sally");
assertRow(6, "00001006", "sally");
assertRow(7, "00001007", "sally");
}
@Test
public void testWildWithSpecifiedNamespace() throws Exception {
setText("parent::*");
performOkCallback();
assertEquals(addr("00001000"), cbPlugin.getCurrentAddress());
model = waitForModel();
assertEquals(6, model.getRowCount());
assertRow(0, "00001001", "billy");
assertRow(1, "00001002", "billy");
assertRow(2, "00001003", "billy");
assertRow(3, "00001005", "sally");
assertRow(4, "00001006", "sally");
assertRow(5, "00001007", "sally");
}
@Test
public void testWildSymbolNameForSpecificParent() throws Exception {
setText("parent2::*");
performOkCallback();
model = waitForModel();
assertEquals(3, model.getRowCount());
assertRow(0, "00001009", "bob");
assertRow(1, "0000100a", "bob");
assertRow(2, "0000100b", "bob");
}
@Test
public void testWildSymbolNameAndWildParentInSpecifiedUpperNamesapce() throws Exception {
setText("middle::*::*");
performOkCallback();
model = waitForModel();
assertEquals(6, model.getRowCount());
assertRow(0, "00001002", "billy");
assertRow(1, "00001003", "billy");
assertRow(2, "00001006", "sally");
assertRow(3, "00001007", "sally");
assertRow(4, "0000100a", "bob");
assertRow(5, "0000100b", "bob");
}
@Test
public void testAbsoluteNamespacePathWithWilds() throws Exception {
setText("::middle::*::*");
performOkCallback();
model = waitForModel();
assertEquals(3, model.getRowCount());
assertRow(0, "00001002", "billy");
assertRow(1, "00001006", "sally");
assertRow(2, "0000100a", "bob");
}
@Test
public void testNoMatches() throws Exception {
setText("xyz");
performOkCallback();
waitForTasks();
assertTrue(dialog.isVisible());
}
//==================================================================================================
// Private Methods
//==================================================================================================
private void assertRow(int row, String address, String name) {
assertEquals(address, model.getValueAt(row, 0).toString());
assertEquals(name, model.getValueAt(row, 1));
}
private void buildProgram() throws Exception {
ToyProgramBuilder builder = new ToyProgramBuilder("Test", false);
builder.createMemory("Block1", "0x1000", 1000);
builder.createLabel("0x1000", "billy");
builder.createLabel("0x1001", "billy", "parent");
builder.createLabel("0x1002", "billy", "middle::parent");
builder.createLabel("0x1003", "billy", "top::middle::parent");
builder.createLabel("0x1004", "sally");
builder.createLabel("0x1005", "sally", "parent");
builder.createLabel("0x1006", "sally", "middle::parent");
builder.createLabel("0x1007", "sally", "top::middle::parent");
builder.createLabel("0x1008", "bob");
builder.createLabel("0x1009", "bob", "parent2");
builder.createLabel("0x100a", "bob", "middle::parent2");
builder.createLabel("0x100b", "bob", "top::middle::parent2");
program = builder.getProgram();
addrFactory = program.getAddressFactory();
}
private GhidraProgramTableModel<?> waitForModel() throws Exception {
TableComponentProvider<?> tableProvider =
waitForComponentProvider(TableComponentProvider.class);
GhidraProgramTableModel<?> tableModel = tableProvider.getModel();
waitForTableModel(tableModel);
return tableModel;
}
private void setCaseSensitive(final boolean selected) {
final JCheckBox checkBox =
(JCheckBox) TestUtils.getInstanceField("caseSensitiveBox", dialog);
runSwing(() -> checkBox.setSelected(selected));
}
private Address addr(String address) {
return addrFactory.getAddress(address);
}
private void showDialog() {
Swing.runLater(() -> dialog.show(provider, cbPlugin.getCurrentAddress(), tool));
waitForSwing();
}
private void setText(final String text) throws Exception {
runSwing(() -> dialog.setText(text));
}
private void performOkCallback() throws Exception {
runSwing(() -> dialog.okCallback());
}
}

View File

@ -4,9 +4,9 @@
* 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.
@ -251,6 +251,6 @@ public class OrganizationNodeTest extends AbstractDockingTest {
}
private GTreeNode node(String name) {
return new CodeSymbolNode(null, new StubSymbol(name, null));
return new CodeSymbolNode(null, new StubSymbol(name));
}
}

View File

@ -0,0 +1,482 @@
/* ###
* 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.app.util.navigation;
import static org.junit.Assert.*;
import org.junit.Test;
import ghidra.program.model.StubProgram;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.*;
import ghidra.program.model.symbol.*;
public class SymbolMatcherTest {
private SymbolMatcher matcher;
@Test
public void testNoNamespaceQueryCaseSensitive() {
matcher = new SymbolMatcher("bob", true);
assertMatches(matcher, "bob");
assertMatches(matcher, "a::bob");
assertMatches(matcher, "a::b::bob");
assertMatches(matcher, "a::b::c::bob");
assertNotMatches(matcher, "Bob");
assertNotMatches(matcher, "a::Bob");
assertNotMatches(matcher, "a::b::Bob");
assertNotMatches(matcher, "a::b::bob:joe");
}
@Test
public void testNoNamespaceQueryCaseInsenstive() {
matcher = new SymbolMatcher("bob", false);
assertMatches(matcher, "bob");
assertMatches(matcher, "a::bob");
assertMatches(matcher, "a::b::bob");
assertMatches(matcher, "a::b::c::bob");
assertMatches(matcher, "Bob");
assertMatches(matcher, "a::Bob");
assertMatches(matcher, "a::b::Bob");
assertMatches(matcher, "a::b::c::Bob");
}
@Test
public void testNoNamespaceQueryWildCardsCaseSensitive() {
matcher = new SymbolMatcher("bo*", true);
assertMatches(matcher, "bob");
assertMatches(matcher, "a::bob");
assertMatches(matcher, "a::b::bob");
assertMatches(matcher, "a::b::c::bob");
assertNotMatches(matcher, "Bob");
assertNotMatches(matcher, "a::Bob");
assertNotMatches(matcher, "a::b::Bob");
assertNotMatches(matcher, "a::b::c::Bob");
}
@Test
public void testNoNamespaceQueryWildCardsCaseInsensitive() {
matcher = new SymbolMatcher("bo*", false);
assertMatches(matcher, "bob");
assertMatches(matcher, "a::bob");
assertMatches(matcher, "a::b::bob");
assertMatches(matcher, "a::b::c::bob");
assertMatches(matcher, "Bob");
assertMatches(matcher, "a::Bob");
assertMatches(matcher, "a::b::Bob");
assertMatches(matcher, "a::b::c::Bob");
}
@Test
public void testNoNamespaceQuerySingleCharWildCardsCaseSensitive() {
matcher = new SymbolMatcher("b?b", true);
assertMatches(matcher, "bob");
assertMatches(matcher, "a::bob");
assertMatches(matcher, "a::b::bob");
assertMatches(matcher, "a::b::c::bob");
assertNotMatches(matcher, "Bob");
assertNotMatches(matcher, "a::Bob");
assertNotMatches(matcher, "a::b::Bob");
assertNotMatches(matcher, "a::b::c::Bob");
}
@Test
public void testNoNamespaceQuerySingleCharWildCardsCaseInsensitive() {
matcher = new SymbolMatcher("b?b", false);
assertMatches(matcher, "bob");
assertMatches(matcher, "a::bob");
assertMatches(matcher, "a::b::bob");
assertMatches(matcher, "a::b::c::bob");
assertMatches(matcher, "Bob");
assertMatches(matcher, "a::Bob");
assertMatches(matcher, "a::b::Bob");
assertMatches(matcher, "a::b::c::Bob");
}
@Test
public void testWithNamespace() {
matcher = new SymbolMatcher("apple::bob", false);
assertMatches(matcher, "apple::bob");
assertMatches(matcher, "x::apple::bob");
assertMatches(matcher, "x::y::apple::bob");
assertNotMatches(matcher, "bob");
assertNotMatches(matcher, "Bob");
assertNotMatches(matcher, "dog::Bob");
assertNotMatches(matcher, "apple::x::Bob");
}
@Test
public void testWithNamespaceTwoLevels() {
matcher = new SymbolMatcher("apple::car::bob", false);
assertMatches(matcher, "apple::car::bob");
assertMatches(matcher, "x::apple::car::bob");
assertMatches(matcher, "x::y::apple::car::bob");
assertNotMatches(matcher, "bob");
assertNotMatches(matcher, "apple::bob");
assertNotMatches(matcher, "apple::x::bob");
}
@Test
public void testWithFullWildNamespace() {
matcher = new SymbolMatcher("*::bob", false);
assertMatches(matcher, "apple::bob");
assertMatches(matcher, "dog::bob");
assertMatches(matcher, "x::y::bob");
assertNotMatches(matcher, "bob");
assertNotMatches(matcher, "joe");
assertNotMatches(matcher, "bo");
assertNotMatches(matcher, "bobby");
assertNotMatches(matcher, "x::boby");
}
@Test
public void testWithPartialWildNamespace() {
matcher = new SymbolMatcher("*a*::bob", false);
assertMatches(matcher, "apple::bob");
assertMatches(matcher, "banana::bob");
assertMatches(matcher, "x::car::bob");
assertNotMatches(matcher, "bob");
assertNotMatches(matcher, "apple::bo");
assertNotMatches(matcher, "apple::x::bob");
}
@Test
public void testWithWildNamespaceAbsolutePath() {
matcher = new SymbolMatcher("::*::bob", false);
assertMatches(matcher, "apple::bob");
assertMatches(matcher, "x::bob");
assertNotMatches(matcher, "bob");
assertNotMatches(matcher, "x::apple::bo");
assertNotMatches(matcher, "apple::x::bob");
}
@Test
public void testEmptyPath() {
matcher = new SymbolMatcher("", false);
assertNotMatches(matcher, "bob");
assertNotMatches(matcher, "a:b");
}
@Test
public void testPathGlobbing() {
matcher = new SymbolMatcher("Apple::**::dog", false);
assertMatches(matcher, "Apple::dog");
assertMatches(matcher, "Apple::x::dog");
assertMatches(matcher, "Apple::x::y::dog");
assertMatches(matcher, "Apple::x::Apple::dog");
assertMatches(matcher, "a::b::Apple::x::y::dog");
assertNotMatches(matcher, "dog");
assertNotMatches(matcher, "x::dog");
assertNotMatches(matcher, "Apple::x::doggy");
assertNotMatches(matcher, "Applebob::x::dog");
}
@Test
public void testMulitiplePathGlobbing() {
matcher = new SymbolMatcher("Apple::**::cat::**::dog", false);
assertMatches(matcher, "Apple::cat::dog");
assertMatches(matcher, "Apple::x::cat::dog");
assertMatches(matcher, "Apple::x::cat::y::dog");
assertMatches(matcher, "Apple::cat::x::Apple::dog");
assertMatches(matcher, "Apple::x::Apple::cat::dog");
assertMatches(matcher, "a::b::Apple::x::cat::dog");
assertNotMatches(matcher, "dog");
assertNotMatches(matcher, "Apple::dog");
assertNotMatches(matcher, "cat::dog");
}
@Test
public void testPathGlobbingAtEnd() {
matcher = new SymbolMatcher("Apple::**", false);
assertMatches(matcher, "Apple::dog");
assertMatches(matcher, "Apple::cat::dog");
assertMatches(matcher, "Apple::x::cat::dog");
assertMatches(matcher, "Apple::x::cat::y::dog");
assertMatches(matcher, "Apple::cat::x::Apple::dog");
assertMatches(matcher, "Apple::x::Apple::cat::dog");
assertMatches(matcher, "a::b::Apple::x::cat::dog");
assertNotMatches(matcher, "dog");
assertNotMatches(matcher, "Apple");
}
@Test
public void testPathGlobbingAtStart() {
// note that this really should be no different than the query "dog", but it should still
// work if you put the "**::" at the beginning
matcher = new SymbolMatcher("**::dog", false);
assertMatches(matcher, "dog");
assertMatches(matcher, "Apple::dog");
assertMatches(matcher, "Apple::cat::dog");
assertMatches(matcher, "Apple::x::cat::dog");
assertMatches(matcher, "Apple::x::cat::y::dog");
assertMatches(matcher, "Apple::cat::x::Apple::dog");
assertMatches(matcher, "Apple::x::Apple::cat::dog");
assertMatches(matcher, "a::b::Apple::x::cat::dog");
assertNotMatches(matcher, "Apple");
}
@Test
public void testBadDoubleStartActsLikeSingleStar() {
// Should behave as if the query was "Apple*::dog because we only support "**" when
// it is completely surrounded by delimiters. In this case the ** is treated as if
// the user made a mistake and meant to input just a single *.
matcher = new SymbolMatcher("Apple**::dog", false);
assertMatches(matcher, "Apple::x::Apple::dog");
assertMatches(matcher, "Apple::dog");
assertMatches(matcher, "Applebob::dog");
assertNotMatches(matcher, "Apple::x::dog");
assertNotMatches(matcher, "Apple::x::y::dog");
assertNotMatches(matcher, "a::b::Apple::x::y::dog");
assertNotMatches(matcher, "dog");
assertNotMatches(matcher, "x::dog");
assertNotMatches(matcher, "Apple::x::doggy");
}
@Test
public void testPathGlobbingWithNameGlobbing() {
matcher = new SymbolMatcher("Ap*le::**::do*", false);
assertMatches(matcher, "Apple::dog");
assertMatches(matcher, "Apple::x::dog");
assertMatches(matcher, "Apple::x::y::dog");
assertMatches(matcher, "Apple::x::Apple::dog");
assertMatches(matcher, "a::b::Apple::x::y::dog");
assertMatches(matcher, "Apple::x::doggy");
assertNotMatches(matcher, "dog");
assertNotMatches(matcher, "x::dog");
}
@Test
public void testNameGlobingAfterDotInName() {
// We don't support the regex ".*" directly from user input. If the user
// enters "*.*::bob", the "." should only match the literal '.' character.
matcher = new SymbolMatcher("*.*::bob", false);
assertMatches(matcher, "a.a::bob");
assertNotMatches(matcher, "a.a::c::bob");
}
@Test
public void testNameGlobingExcessStars() {
// 3 stars is assumed to be a mistake and will be treated as though it were a single *
matcher = new SymbolMatcher("a***b::bob", false);
assertMatches(matcher, "axxxb::bob");
assertNotMatches(matcher, "a::b::bob");
// In this context, where the extended *s are enclosed in delimiters, we assume the user
// meant **
matcher = new SymbolMatcher("a::***::bob", false);
assertMatches(matcher, "a::b::bob");
assertNotMatches(matcher, "axxxb::bob");
matcher = new SymbolMatcher("a****b::bob", false);
assertMatches(matcher, "axxxb::bob");
assertNotMatches(matcher, "a::b::bob");
matcher = new SymbolMatcher("a::****::bob", false);
assertMatches(matcher, "a::b::bob");
assertNotMatches(matcher, "axxxb::bob");
matcher = new SymbolMatcher("bob*****", false);
assertMatches(matcher, "bobby");
}
@Test
public void testBlockNameMatches() {
// all of our symbols are stubbed to be in the ".text" block
matcher = new SymbolMatcher(".text::bob", false);
// since ".text is the block name for all of our symbols in this test, any
// "bob" symbol, regardless of its namespace should match the query ".text::bob"
assertMatches(matcher, "bob");
assertMatches(matcher, "aaa::bob");
assertMatches(matcher, "x::y::z::bob");
}
@Test
public void testBlockNameMatchesDontSupportWildsInBlockName() {
// All of our symbols are stubbed to be in the ".text" block. Legacy code allowed
// users to search for symbols in memory blocks int the form <block name>::<symbol name>.
// We still support that, but didn't add wildcard support as that might be even more
// confusing that it already is.
matcher = new SymbolMatcher(".t*xt::bob", false);
assertNotMatches(matcher, "bob");
assertNotMatches(matcher, "aaa::bob");
assertNotMatches(matcher, "x::y::z::bob");
matcher = new SymbolMatcher(".t?xt::bob", false);
assertNotMatches(matcher, "bob");
assertNotMatches(matcher, "aaa::bob");
assertNotMatches(matcher, "x::y::z::bob");
}
@Test
public void testBlockNameMatchesSupportWildsInSymbolName() {
// all of our symbols are stubbed to be in the ".text" block
matcher = new SymbolMatcher(".text::bob*", false);
assertMatches(matcher, "bob");
assertMatches(matcher, "bobx");
assertMatches(matcher, "bobyy");
assertMatches(matcher, "aaa::bobz");
assertMatches(matcher, "x::y::z::bobby");
}
@Test
public void testGetSymbolName() {
matcher = new SymbolMatcher("a::b::c", false);
assertEquals("c", matcher.getSymbolName());
}
@Test
public void testHasFullySpecifiedName() {
matcher = new SymbolMatcher("a::b::c", false);
assertFalse(matcher.hasFullySpecifiedName());
matcher = new SymbolMatcher("a::b::c", true);
assertTrue(matcher.hasFullySpecifiedName());
matcher = new SymbolMatcher("a::b::c*", true);
assertFalse(matcher.hasFullySpecifiedName());
matcher = new SymbolMatcher("a::b*::c", true);
assertTrue(matcher.hasFullySpecifiedName());
}
@Test
public void testHasWildcardsInSymbolName() {
// This is testing the SymbolMatcher.hasWidCardsInSymbolName() method, which is used
// to optimize symbol searching when symbol names don't have wildcard characters.
matcher = new SymbolMatcher("a::b::c", false);
assertFalse(matcher.hasWildCardsInSymbolName());
matcher = new SymbolMatcher("a::b::c", true);
assertFalse(matcher.hasWildCardsInSymbolName());
matcher = new SymbolMatcher("a::b::c*", true);
assertTrue(matcher.hasWildCardsInSymbolName());
matcher = new SymbolMatcher("a::b*::c", true);
assertFalse(matcher.hasWildCardsInSymbolName());
}
private Symbol symbol(String path) {
String[] split = path.split(Namespace.DELIMITER);
String name = split[split.length - 1];
Namespace namespace = getNamespace(split);
return new TestSymbol(name, namespace);
}
private Namespace getNamespace(String[] split) {
Namespace namespace = null;
for (int i = 0; i < split.length - 1; i++) {
namespace = new StubNamespace(split[i], namespace);
}
return namespace;
}
private void assertMatches(SymbolMatcher symbolMatcher, String path) {
Symbol s = symbol(path);
assertTrue(symbolMatcher.matches(s));
}
private void assertNotMatches(SymbolMatcher symbolMatcher, String path) {
Symbol s = symbol(path);
assertFalse(symbolMatcher.matches(s));
}
private class TestMemoryBlock extends MemoryBlockStub {
@Override
public String getName() {
return ".text";
}
}
private class TestMemory extends StubMemory {
@Override
public MemoryBlock getBlock(Address addr) {
return new TestMemoryBlock();
}
}
private class TestProgram extends StubProgram {
@Override
public Memory getMemory() {
return new TestMemory();
}
}
private class TestSymbol extends StubSymbol {
public TestSymbol(String name, Namespace parent) {
super(name, parent);
}
@Override
public Program getProgram() {
return new TestProgram();
}
}
}

View File

@ -4,9 +4,9 @@
* 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.
@ -238,9 +238,13 @@ public abstract class SymbolDB extends DatabaseObject implements Symbol {
}
}
private void fillListWithNamespacePath(Namespace namespace, ArrayList<String> list) {
private void fillListWithNamespacePath(Namespace namespace, List<String> list) {
if (namespace == null || namespace.getID() == Namespace.GLOBAL_NAMESPACE_ID) {
// we don't include the global namespace name in the path
return;
}
Namespace parentNamespace = namespace.getParentNamespace();
if (parentNamespace != null && parentNamespace.getID() != Namespace.GLOBAL_NAMESPACE_ID) {
if (parentNamespace != null) {
fillListWithNamespacePath(parentNamespace, list);
}
list.add(namespace.getName());

View File

@ -4,9 +4,9 @@
* 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.
@ -41,7 +41,7 @@ public interface Symbol {
/**
* Gets the full path name for this symbol as an ordered array of strings ending
* with the symbol name. The global symbol will return an empty array.
* with the symbol name.
* @return the array indicating the full path name for this symbol.
*/
public String[] getPath();

View File

@ -0,0 +1,77 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.program.model.symbol;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.CircularDependencyException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
public class StubNamespace implements Namespace {
private Namespace parent;
private String name;
public StubNamespace(String name, Namespace parent) {
this.name = name;
this.parent = parent;
}
@Override
public Symbol getSymbol() {
return new StubSymbol(name);
}
@Override
public boolean isExternal() {
return false;
}
@Override
public String getName() {
return name;
}
@Override
public String getName(boolean includeNamespacePath) {
if (!includeNamespacePath || parent == null) {
return name;
}
return parent.getName(true) + Namespace.DELIMITER + name;
}
@Override
public long getID() {
return 1;
}
@Override
public Namespace getParentNamespace() {
return parent;
}
@Override
public AddressSetView getBody() {
return null;
}
@Override
public void setParentNamespace(Namespace parentNamespace)
throws DuplicateNameException, InvalidInputException, CircularDependencyException {
// ignore
}
}

View File

@ -4,9 +4,9 @@
* 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.
@ -15,6 +15,9 @@
*/
package ghidra.program.model.symbol;
import java.util.ArrayList;
import java.util.List;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.CircularDependencyException;
import ghidra.program.model.listing.Program;
@ -30,6 +33,11 @@ public class StubSymbol implements Symbol {
private long id;
private String name;
private Address address;
private Namespace parent;
public StubSymbol(String name) {
this.name = name;
}
public StubSymbol(String name, Address address) {
this.name = name;
@ -37,6 +45,12 @@ public class StubSymbol implements Symbol {
id = nextId++;
}
public StubSymbol(String name, Namespace parent) {
this.name = name;
this.parent = parent;
id = nextId++;
}
@Override
public Address getAddress() {
return address;
@ -49,7 +63,12 @@ public class StubSymbol implements Symbol {
@Override
public String[] getPath() {
return new String[] { name };
ArrayList<String> list = new ArrayList<>();
fillListWithNamespacePath(getParentNamespace(), list);
list.add(getName());
String[] path = list.toArray(new String[list.size()]);
return path;
}
@Override
@ -64,7 +83,7 @@ public class StubSymbol implements Symbol {
@Override
public Namespace getParentNamespace() {
return null;
return parent;
}
@Override
@ -225,4 +244,15 @@ public class StubSymbol implements Symbol {
return id == other.id;
}
private void fillListWithNamespacePath(Namespace namespace, List<String> list) {
if (namespace == null || namespace.getID() == Namespace.GLOBAL_NAMESPACE_ID) {
// we don't include the global namespace name in the path
return;
}
Namespace parentNamespace = namespace.getParentNamespace();
if (parentNamespace != null) {
fillListWithNamespacePath(parentNamespace, list);
}
list.add(namespace.getName());
}
}

View File

@ -309,7 +309,7 @@ public class UserSearchUtils {
String escaped = escapeEscapeCharacters(input);
if (allowGlobbing) {
escaped = escapeSomeRegexCharacters(escaped, GLOB_CHARACTERS);
escaped = escapeNonGlobbingRegexCharacters(input);
escaped = convertGlobbingCharactersToRegex(escaped);
}
else {
@ -376,6 +376,15 @@ public class UserSearchUtils {
return Pattern.quote(input);
}
/**
* Escapes all special regex characters except globbing chars (*?)
* @param input the string to sanitize
* @return a new string with all non-globing regex characters escaped.
*/
public static String escapeNonGlobbingRegexCharacters(String input) {
return escapeSomeRegexCharacters(input, GLOB_CHARACTERS);
}
/**
* Escapes all regex characters with the '\' character, except for those in the given exclusion
* array.
@ -384,7 +393,7 @@ public class UserSearchUtils {
* @param doNotEscape an array of characters that should not be escaped
* @return A new regex string with special characters escaped.
*/
// note: 'package' for testing
// package for testing
static String escapeSomeRegexCharacters(String input, char[] doNotEscape) {
StringBuilder buffy = new StringBuilder();
for (int i = 0; i < input.length(); i++) {