mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-26 17:27:10 +08:00
Merge remote-tracking branch 'origin/GP-4930_ghidragon_improving_goto'
This commit is contained in:
File diff suppressed because it is too large
Load Diff
+4
-118
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
+55
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+18
-95
@@ -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) {
|
||||
|
||||
+239
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
+3
-3
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-4
@@ -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());
|
||||
|
||||
+3
-3
@@ -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();
|
||||
|
||||
+77
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
+34
-4
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
Reference in New Issue
Block a user