GP-1765 - Fixed sometimes incorrect Find Dialog result highlighting

This commit is contained in:
dragonmacher 2022-03-03 18:42:23 -05:00
parent 026fad27ab
commit 68b7f88063
7 changed files with 534 additions and 188 deletions

View file

@ -20,7 +20,6 @@ import static org.junit.Assert.*;
import java.awt.Component;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.*;
@ -55,12 +54,10 @@ import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramSelection;
import ghidra.test.*;
import ghidra.util.task.TaskMonitorAdapter;
import ghidra.util.task.TaskMonitor;
public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
private static final int DIALOG_WAIT_TIME = 3000;
private TestEnv env;
private PluginTool tool;
private AddressFactory addrFactory;
@ -104,13 +101,13 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
ActionContext actionContext = cb.getProvider().getActionContext(null);
assertNull(actionContext);
actionContext = new ActionContext();
assertTrue(!createFunction.isEnabledForContext(actionContext));
assertTrue(!createThunk.isEnabledForContext(actionContext));
assertTrue(!editThunk.isEnabledForContext(actionContext));
assertTrue(!revertThunk.isEnabledForContext(actionContext));
assertTrue(!deleteFunction.isEnabledForContext(actionContext));
assertTrue(!editComment.isEnabledForContext(actionContext));
assertTrue(!deleteComment.isEnabledForContext(actionContext));
assertFalse(createFunction.isEnabledForContext(actionContext));
assertFalse(createThunk.isEnabledForContext(actionContext));
assertFalse(editThunk.isEnabledForContext(actionContext));
assertFalse(revertThunk.isEnabledForContext(actionContext));
assertFalse(deleteFunction.isEnabledForContext(actionContext));
assertFalse(editComment.isEnabledForContext(actionContext));
assertFalse(deleteComment.isEnabledForContext(actionContext));
env.showTool();
loadProgram("notepad");
@ -119,21 +116,21 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
actionContext = cb.getProvider().getActionContext(null);
assertTrue(createFunction.isEnabledForContext(actionContext));
assertTrue(createThunk.isEnabledForContext(actionContext));
assertTrue(!editThunk.isEnabledForContext(actionContext));
assertTrue(!revertThunk.isEnabledForContext(actionContext));
assertTrue(!deleteFunction.isEnabledForContext(actionContext));
assertTrue(!editComment.isEnabledForContext(actionContext));
assertTrue(!deleteComment.isEnabledForContext(actionContext));
assertFalse(editThunk.isEnabledForContext(actionContext));
assertFalse(revertThunk.isEnabledForContext(actionContext));
assertFalse(deleteFunction.isEnabledForContext(actionContext));
assertFalse(editComment.isEnabledForContext(actionContext));
assertFalse(deleteComment.isEnabledForContext(actionContext));
assertTrue(cb.goToField(addr("0x1001000"), "Address", 0, 0));
actionContext = cb.getProvider().getActionContext(null);
assertTrue(!createFunction.isEnabledForContext(actionContext));
assertTrue(!createThunk.isEnabledForContext(actionContext));
assertTrue(!editThunk.isEnabledForContext(actionContext));
assertTrue(!revertThunk.isEnabledForContext(actionContext));
assertTrue(!deleteFunction.isEnabledForContext(actionContext));
assertTrue(!editComment.isEnabledForContext(actionContext));
assertTrue(!deleteComment.isEnabledForContext(actionContext));
assertFalse(createFunction.isEnabledForContext(actionContext));
assertFalse(createThunk.isEnabledForContext(actionContext));
assertFalse(editThunk.isEnabledForContext(actionContext));
assertFalse(revertThunk.isEnabledForContext(actionContext));
assertFalse(deleteFunction.isEnabledForContext(actionContext));
assertFalse(editComment.isEnabledForContext(actionContext));
assertFalse(deleteComment.isEnabledForContext(actionContext));
assertTrue(cb.goToField(addr("0x1006420"), "Address", 0, 0));
actionContext = cb.getProvider().getActionContext(null);
@ -146,46 +143,46 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
assertTrue(cb.goToField(addr("0x1006420"), "Function Signature", 0, 0));
actionContext = cb.getProvider().getActionContext(null);
assertTrue(!createFunction.isEnabledForContext(actionContext));
assertTrue(!createThunk.isEnabledForContext(actionContext));
assertFalse(createFunction.isEnabledForContext(actionContext));
assertFalse(createThunk.isEnabledForContext(actionContext));
assertTrue(editThunk.isEnabledForContext(actionContext));
assertTrue(!revertThunk.isEnabledForContext(actionContext));
assertFalse(revertThunk.isEnabledForContext(actionContext));
assertTrue(deleteFunction.isEnabledForContext(actionContext));
assertTrue(!editComment.isEnabledForContext(actionContext));
assertTrue(!deleteComment.isEnabledForContext(actionContext));
assertFalse(editComment.isEnabledForContext(actionContext));
assertFalse(deleteComment.isEnabledForContext(actionContext));
assertTrue(cb.goToField(addr("0x1006420"), "Variable Name", 1, 0, 0));
actionContext = cb.getProvider().getActionContext(null);
assertTrue(!createFunction.isEnabledForContext(actionContext));
assertTrue(!createThunk.isEnabledForContext(actionContext));
assertFalse(createFunction.isEnabledForContext(actionContext));
assertFalse(createThunk.isEnabledForContext(actionContext));
assertTrue(editThunk.isEnabledForContext(actionContext));
assertTrue(!revertThunk.isEnabledForContext(actionContext));
assertTrue(!deleteFunction.isEnabledForContext(actionContext));
assertFalse(revertThunk.isEnabledForContext(actionContext));
assertFalse(deleteFunction.isEnabledForContext(actionContext));
assertTrue(editComment.isEnabledForContext(actionContext));
assertTrue(!deleteComment.isEnabledForContext(actionContext));
assertFalse(deleteComment.isEnabledForContext(actionContext));
createThunk(addr("0x10030d2"), "comdlg32.dll::CommDlgExtendedError", true);
assertTrue(cb.goToField(addr("0x10030d2"), "Function Signature", 0, 0));
actionContext = cb.getProvider().getActionContext(null);
assertTrue(!createFunction.isEnabledForContext(actionContext));
assertTrue(!createThunk.isEnabledForContext(actionContext));
assertFalse(createFunction.isEnabledForContext(actionContext));
assertFalse(createThunk.isEnabledForContext(actionContext));
assertTrue(editThunk.isEnabledForContext(actionContext));
assertTrue(revertThunk.isEnabledForContext(actionContext));
assertTrue(deleteFunction.isEnabledForContext(actionContext));
assertTrue(!editComment.isEnabledForContext(actionContext));
assertTrue(!deleteComment.isEnabledForContext(actionContext));
assertFalse(editComment.isEnabledForContext(actionContext));
assertFalse(deleteComment.isEnabledForContext(actionContext));
closeProgram();
actionContext = cb.getProvider().getActionContext(null);
assertNull(actionContext);
actionContext = new ActionContext();
assertTrue(!createFunction.isEnabledForContext(actionContext));
assertTrue(!deleteFunction.isEnabledForContext(actionContext));
assertTrue(!editThunk.isEnabledForContext(actionContext));
assertTrue(!revertThunk.isEnabledForContext(actionContext));
assertTrue(!editComment.isEnabledForContext(actionContext));
assertTrue(!deleteComment.isEnabledForContext(actionContext));
assertFalse(createFunction.isEnabledForContext(actionContext));
assertFalse(deleteFunction.isEnabledForContext(actionContext));
assertFalse(editThunk.isEnabledForContext(actionContext));
assertFalse(revertThunk.isEnabledForContext(actionContext));
assertFalse(editComment.isEnabledForContext(actionContext));
assertFalse(deleteComment.isEnabledForContext(actionContext));
}
@ -323,7 +320,7 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
performAction(editThunk, cb.getProvider(), false);
ThunkReferenceAddressDialog thunkDlg =
waitForDialogComponent(null, ThunkReferenceAddressDialog.class, 100);
waitForDialogComponent(ThunkReferenceAddressDialog.class);
assertNotNull(thunkDlg);
JTextField thunkedEntryField = findComponent(thunkDlg, JTextField.class);
assertEquals("comdlg32.dll::CommDlgExtendedError", thunkedEntryField.getText());
@ -374,13 +371,7 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
assertTrue(func.isThunk());
assertEquals("CommDlgExtendedError", func.getName());
int txId = program.startTransaction("Set Name");
try {
func.setName("foo", SourceType.USER_DEFINED);
}
finally {
program.endTransaction(txId, true);
}
tx(program, () -> func.setName("foo", SourceType.USER_DEFINED));
performAction(revertThunk, cb.getProvider(), false);
waitForBusyTool();
@ -406,7 +397,7 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
Function func = program.getListing().getFunctionAt(addr("0x1006420"));
assertNotNull(func);
assertTrue(!func.isThunk());
assertFalse(func.isThunk());
assertEquals("entry", func.getName());
assertTrue(func.getLocalVariables().length != 0);
@ -417,7 +408,7 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
waitForSwing();
ThunkReferenceAddressDialog thunkDlg =
waitForDialogComponent(null, ThunkReferenceAddressDialog.class, 100);
waitForDialogComponent(ThunkReferenceAddressDialog.class);
assertNotNull(thunkDlg);
JTextField thunkedEntryField = findComponent(thunkDlg, JTextField.class);
assertEquals("", thunkedEntryField.getText());
@ -433,7 +424,7 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
undo(program);// undo changed function
assertTrue(!func.isThunk());
assertFalse(func.isThunk());
assertEquals("entry", func.getName());
assertTrue(func.getLocalVariables().length != 0);
@ -530,11 +521,10 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
loadProgram("notepad");
createFunctionAtEntry();
assertTrue(cb.goToField(addr("0x1006420"), "Variable Name", 1, 0, 0));
assertTrue(!deleteComment.isEnabledForContext(cb.getProvider().getActionContext(null)));
assertFalse(deleteComment.isEnabledForContext(cb.getProvider().getActionContext(null)));
performAction(editComment, cb.getProvider(), false);
waitForBusyTool();
VariableCommentDialog vcd = waitForDialogComponent(tool.getToolFrame(),
VariableCommentDialog.class, DIALOG_WAIT_TIME);
VariableCommentDialog vcd = waitForDialogComponent(VariableCommentDialog.class);
assertNotNull(vcd);
JTextArea textArea = findComponent(vcd, JTextArea.class);
triggerText(textArea, "My New Comment");
@ -544,8 +534,7 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
assertTrue(deleteComment.isEnabledForContext(cb.getProvider().getActionContext(null)));
performAction(editComment, cb.getProvider(), false);
vcd = waitForDialogComponent(tool.getToolFrame(), VariableCommentDialog.class,
DIALOG_WAIT_TIME);
vcd = waitForDialogComponent(VariableCommentDialog.class);
textArea = findComponent(vcd, JTextArea.class);
triggerText(textArea, "more stuff");
pressButtonByText(vcd.getComponent(), "OK");
@ -894,17 +883,13 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
// put a byte at local_8
Function function = program.getListing().getFunctionAt(a);
Variable[] vars = function.getLocalVariables(VariableFilter.STACK_VARIABLE_FILTER);
int transactionID = program.startTransaction("test");
try {
DataType byteDT = program.getDataTypeManager().addDataType(new ByteDataType(),
tx(program, () -> {
DataType byteDT = program.getDataTypeManager()
.addDataType(new ByteDataType(),
DataTypeConflictHandler.DEFAULT_HANDLER);
vars[1].setDataType(byteDT, SourceType.ANALYSIS);
}
finally {
program.endTransaction(transactionID, true);
}
program.flushEvents();
waitForSwing();
});
cb.updateNow();
assertTrue(cb.goToField(a, "Variable Type", 5, 0, 0));
@ -912,18 +897,14 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
performAction(createArray, cb.getProvider(), false);
waitForSwing();
final NumberInputDialog d =
env.waitForDialogComponent(NumberInputDialog.class, DIALOG_WAIT_TIME);
NumberInputDialog d = waitForDialogComponent(NumberInputDialog.class);
assertNotNull(d);
vars = function.getLocalVariables(VariableFilter.STACK_VARIABLE_FILTER);
assertEquals(1, d.getMin());
assertEquals(Integer.MAX_VALUE, d.getMax());
final AtomicInteger result = new AtomicInteger(0);
runSwing(() -> result.set(d.getValue()));
assertEquals(12, result.get());
int result = runSwing(() -> d.getValue());
assertEquals(12, result);
runSwing(() -> d.setInput(4));
@ -1032,7 +1013,7 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
performAction(rename, cb.getProvider(), false);
waitForBusyTool();
AddEditDialog dialog = env.waitForDialogComponent(AddEditDialog.class, DIALOG_WAIT_TIME);
AddEditDialog dialog = waitForDialogComponent(AddEditDialog.class);
assertNotNull(dialog);
GhidraComboBox<?> combo = findComponent(dialog, GhidraComboBox.class);
@ -1058,14 +1039,14 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
performAction(rename, cb.getProvider(), false);
dialog = env.waitForDialogComponent(AddEditDialog.class, DIALOG_WAIT_TIME);
dialog = waitForDialogComponent(AddEditDialog.class);
assertNotNull(dialog);
JComboBox<?> nameBox = (JComboBox<?>) getInstanceField("labelNameChoices", dialog);
final JTextField editorField = (JTextField) nameBox.getEditor().getEditorComponent();
JTextField editorField = (JTextField) nameBox.getEditor().getEditorComponent();
assertNotNull(editorField);
SwingUtilities.invokeAndWait(() -> editorField.setText("fred"));
//typeText("fred");
runSwing(() -> editorField.setText("fred"));
pressButtonByText(dialog, "OK");
assertEquals("fred", cb.getCurrentFieldText());
@ -1084,17 +1065,14 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
assertEquals("dword ptr [EBP + fred],0x0", cb.getCurrentFieldText());
performAction(renameFunctionVar, cb.getProvider(), false);
dialog = env.waitForDialogComponent(AddEditDialog.class, DIALOG_WAIT_TIME);
dialog = waitForDialogComponent(AddEditDialog.class);
assertNotNull(dialog);
nameBox = (JComboBox<?>) getInstanceField("labelNameChoices", dialog);
final JTextField editorField2 = (JTextField) nameBox.getEditor().getEditorComponent();
JTextField editorField2 = (JTextField) nameBox.getEditor().getEditorComponent();
assertNotNull(editorField);
SwingUtilities.invokeAndWait(() -> editorField2.setText("bob"));
runSwing(() -> editorField2.setText("bob"));
//
// typeText("bob");
// waitForSwing();
pressButtonByText(dialog, "OK");
waitForBusyTool();
assertEquals("dword ptr [EBP + bob],0x0", cb.getCurrentFieldText());
@ -1137,7 +1115,7 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
undo(program);
cb.updateNow();
assertTrue(!cb.getCurrentFieldText().equals("undefined"));
assertFalse(cb.getCurrentFieldText().equals("undefined"));
redo(program);
cb.updateNow();
assertEquals("undefined", cb.getCurrentFieldText());
@ -1235,7 +1213,7 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
performAction(rename, cb.getProvider(), false);
waitForBusyTool();
AddEditDialog dialog = env.waitForDialogComponent(AddEditDialog.class, DIALOG_WAIT_TIME);
AddEditDialog dialog = waitForDialogComponent(AddEditDialog.class);
assertNotNull(dialog);
GhidraComboBox<?> combo = findComponent(dialog, GhidraComboBox.class);
@ -1246,7 +1224,6 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
waitForBusyTool();
assertEquals("hello", cb.getCurrentFieldText());
assertEquals("hello", function.getName());
}
@ -1262,18 +1239,18 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
Function entry = getFunction("entry");
assertNotNull(entry);
Set<Function> called = entry.getCalledFunctions(TaskMonitorAdapter.DUMMY_MONITOR);
Set<Function> called = entry.getCalledFunctions(TaskMonitor.DUMMY);
assertEquals(4, called.size());
Set<Function> calling = entry.getCallingFunctions(TaskMonitorAdapter.DUMMY_MONITOR);
Set<Function> calling = entry.getCallingFunctions(TaskMonitor.DUMMY);
assertEquals(0, calling.size());// nobody calls entry
for (Function f : called) {
Set<Function> calling_f = f.getCallingFunctions(TaskMonitorAdapter.DUMMY_MONITOR);
Set<Function> calling_f = f.getCallingFunctions(TaskMonitor.DUMMY);
assertTrue(calling_f.contains(entry));
}
}
/**
/*
* Tests that setting a function register param to have a data type larger than
* its storage allows will produce an error message in the status box, and not simply fail
* silently.
@ -1292,8 +1269,7 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
assertTrue(cb.goToField(addr("0x1006420"), "Variable Type", 0, 0));
performAction(chooseDataType, cb.getProvider(), false);
DataTypeSelectionDialog dialog =
waitForDialogComponent(null, DataTypeSelectionDialog.class, DIALOG_WAIT_TIME);
DataTypeSelectionDialog dialog = waitForDialogComponent(DataTypeSelectionDialog.class);
setEditorText(dialog, "int[0x8888888]");
@ -1403,13 +1379,12 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
throws Exception {
deleteExistingFunction(thunkEntry);
assertTrue(cb.goToField(thunkEntry, "Address", 0, 0));
performAction(createThunk, cb.getProvider(), false);
ThunkReferenceAddressDialog thunkDialog =
waitForDialogComponent(null, ThunkReferenceAddressDialog.class, 100);
waitForDialogComponent(ThunkReferenceAddressDialog.class);
assertNotNull(thunkDialog);
JTextField thunkedEntryField = findComponent(thunkDialog, JTextField.class);
assertEquals(expectedDefault ? refFunc : "", thunkedEntryField.getText());
@ -1486,12 +1461,6 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
}
performAction(createFunction, cb.getProvider(), false);
// FunctionNameDialog d = (FunctionNameDialog)waitForDialogComponent(tool.getToolFrame(),
// FunctionNameDialog.class, 2000);
//assertNotNull(d);
//pressButtonByText(d, "OK");
waitForBusyTool();
// cheat setting custom storage since we are not testing the edit function dialog here
@ -1510,13 +1479,7 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
}
private void setCustomParameterStorage(Function function, boolean enabled) {
int txId = program.startTransaction("Set Custom Storage");
try {
function.setCustomVariableStorage(enabled);
}
finally {
program.endTransaction(txId, true);
}
tx(program, () -> function.setCustomVariableStorage(enabled));
}
private void waitForBusyTool() {
@ -1569,20 +1532,13 @@ public class Function1Test extends AbstractGhidraHeadedIntegrationTest {
private void loadProgram(String programName) throws Exception {
if ("notepad".equals(programName)) {
ClassicSampleX86ProgramBuilder builder = new ClassicSampleX86ProgramBuilder();
program = builder.getProgram();
ProgramManager pm = tool.getService(ProgramManager.class);
pm.openProgram(program.getDomainFile());
builder.dispose();
waitForSwing();
addrFactory = program.getAddressFactory();
}
else {
Assert.fail("don't have program: " + programName);
}
}
private Function getFunction(String name) {
List<Function> functions = program.getListing().getGlobalFunctions(name);

View file

@ -122,27 +122,27 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
@Override
public void modelSizeChanged(IndexMapper mapper) {
for (int i = 0; i < listeners.size(); ++i) {
listeners.get(i).modelSizeChanged(mapper);
for (LayoutModelListener listener : listeners) {
listener.modelSizeChanged(mapper);
}
}
public void modelChanged() {
for (int i = 0; i < listeners.size(); ++i) {
listeners.get(i).modelSizeChanged(IndexMapper.IDENTITY_MAPPER);
for (LayoutModelListener listener : listeners) {
listener.modelSizeChanged(IndexMapper.IDENTITY_MAPPER);
}
}
@Override
public void dataChanged(BigInteger start, BigInteger end) {
for (int i = 0; i < listeners.size(); ++i) {
listeners.get(i).dataChanged(start, end);
for (LayoutModelListener listener : listeners) {
listener.dataChanged(start, end);
}
}
public void layoutChanged() {
for (int i = 0; i < listeners.size(); ++i) {
listeners.get(i).dataChanged(BigInteger.ZERO, numIndexes);
for (LayoutModelListener listener : listeners) {
listener.dataChanged(BigInteger.ZERO, numIndexes);
}
}
@ -336,11 +336,11 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
for (String element : errlines_init) {
splitToMaxWidthLines(errlines, element);
}
for (int i = 0; i < errlines.size(); ++i) {
for (String errline : errlines) {
ClangTokenGroup line = new ClangTokenGroup(docroot);
ClangBreak lineBreak = new ClangBreak(line, 1);
ClangSyntaxToken message =
new ClangSyntaxToken(line, errlines.get(i), ClangXML.COMMENT_COLOR);
new ClangSyntaxToken(line, errline, ClangXML.COMMENT_COLOR);
line.AddTokenGroup(lineBreak);
line.AddTokenGroup(message);
docroot.AddTokenGroup(line);
@ -378,14 +378,23 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
int row = currentLocation.getIndex().intValue();
for (int i = row; i < fieldList.length; i++) {
ClangTextField field = (ClangTextField) fieldList[i];
String textLine =
String partialLine =
getTextLineFromOffset((i == row) ? currentLocation : null, field, true);
SearchMatch match = matcher.apply(textLine);
if (match != SearchMatch.NO_MATCH) {
if (i == row) {
SearchMatch match = matcher.apply(partialLine);
if (match == SearchMatch.NO_MATCH) {
continue;
}
if (i == row) { // cursor is on this line
//
// The match start for all lines without the cursor will be relative to the start
// of the line, which is 0. However, when searching on the row with the cursor,
// the match start is relative to the cursor position. Update the start to
// compensate for the difference between the start of the line and the cursor.
//
String fullLine = field.getText();
match.start += fullLine.length() - textLine.length();
int cursorOffset = fullLine.length() - partialLine.length();
match.start += cursorOffset;
match.end += cursorOffset;
}
FieldNumberColumnPair pair = getFieldIndexFromOffset(match.start, field);
FieldLocation fieldLocation =
@ -394,7 +403,6 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
return new FieldBasedSearchLocation(fieldLocation, match.start, match.end - 1,
searchString, true);
}
}
return null;
}
@ -442,7 +450,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
if (matcher.find()) {
int start = matcher.start();
int end = matcher.end();
return new SearchMatch(start, end);
return new SearchMatch(start, end, textLine);
}
return SearchMatch.NO_MATCH;
@ -469,7 +477,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
end = matcher.end();
}
return new SearchMatch(start, end);
return new SearchMatch(start, end, textLine);
};
return findNextTokenGoingBackward(reverse, searchString, currentLocation);
@ -486,7 +494,8 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
if (index == -1) {
return SearchMatch.NO_MATCH;
}
return new SearchMatch(index, index + searchString.length());
return new SearchMatch(index, index + searchString.length(), textLine);
};
return findNextTokenGoingForward(function, searchString, currentLocation);
@ -498,7 +507,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
if (index == -1) {
return SearchMatch.NO_MATCH;
}
return new SearchMatch(index, index + searchString.length());
return new SearchMatch(index, index + searchString.length(), textLine);
};
return findNextTokenGoingBackward(function, searchString, currentLocation);
@ -506,6 +515,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
private String getTextLineFromOffset(FieldLocation location, ClangTextField textField,
boolean forwardSearch) {
if (location == null) { // the cursor location is not on this line; use all of the text
return textField.getText();
}
@ -518,12 +528,16 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
if (forwardSearch) {
// Protects against the location column being out of range (this can
// happen if we're searching forward and the cursor is past the last token).
if (location.getCol() + 1 >= partialText.length()) {
int nextCol = location.getCol();
// protects against the location column being out of range (this can happen if we're
// searching forward and the cursor is past the last token)
if (nextCol >= partialText.length()) {
return "";
}
return partialText.substring(location.getCol() + 1);
// skip a character to start the next search; this prevents matching the previous match
return partialText.substring(nextCol);
}
// backwards search
@ -539,13 +553,23 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
}
private static class SearchMatch {
private static SearchMatch NO_MATCH = new SearchMatch(-1, -1);
private static SearchMatch NO_MATCH = new SearchMatch(-1, -1, null);
private int start;
private int end;
private String textLine;
SearchMatch(int start, int end) {
SearchMatch(int start, int end, String textLine) {
this.start = start;
this.end = end;
this.textLine = textLine;
}
@Override
public String toString() {
if (this == NO_MATCH) {
return "NO MATCH";
}
return "[start=" + start + ",end=" + end + "]: " + textLine;
}
}
//==================================================================================================

View file

@ -838,6 +838,10 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
repaint();
}
public FieldBasedSearchLocation getSearchResults() {
return (FieldBasedSearchLocation) currentSearchLocation;
}
//==================================================================================================
// End Search Methods
//==================================================================================================

View file

@ -18,6 +18,7 @@ package ghidra.app.plugin.core.decompile.actions;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.List;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
@ -58,6 +59,31 @@ public class FindAction extends AbstractDecompilerAction {
return findDialog;
}
@Override
protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) {
return true;
}
@Override
protected void decompilerActionPerformed(DecompilerActionContext context) {
DecompilerPanel decompilerPanel = context.getDecompilerPanel();
FindDialog dialog = getFindDialog(decompilerPanel);
String text = decompilerPanel.getSelectedText();
if (text == null) {
text = decompilerPanel.getHighlightedText();
// note: if we decide to grab the text under the cursor, then use
// text = decompilerPanel.getTextUnderCursor();
}
if (!StringUtils.isBlank(text)) {
dialog.setSearchText(text);
}
// show over the root frame, so the user can still see the Decompiler window
context.getTool().showDialog(dialog);
}
private static class DecompilerSearcher implements FindDialogSearcher {
private DecompilerPanel decompilerPanel;
@ -112,35 +138,60 @@ public class FindAction extends AbstractDecompilerAction {
public SearchLocation search(String text, CursorPosition position, boolean searchForward,
boolean useRegex) {
DecompilerCursorPosition decompilerCursorPosition = (DecompilerCursorPosition) position;
FieldLocation fieldLocation = decompilerCursorPosition.getFieldLocation();
return useRegex ? decompilerPanel.searchTextRegex(text, fieldLocation, searchForward)
: decompilerPanel.searchText(text, fieldLocation, searchForward);
FieldLocation startLocation =
getNextSearchStartLocation(decompilerCursorPosition, searchForward);
return useRegex ? decompilerPanel.searchTextRegex(text, startLocation, searchForward)
: decompilerPanel.searchText(text, startLocation, searchForward);
}
private FieldLocation getNextSearchStartLocation(
DecompilerCursorPosition decompilerCursorPosition, boolean searchForward) {
FieldLocation startLocation = decompilerCursorPosition.getFieldLocation();
FieldBasedSearchLocation currentSearchLocation = decompilerPanel.getSearchResults();
if (currentSearchLocation == null) {
return startLocation; // nothing to do; no prior search hit
}
@Override
protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) {
return true;
//
// Special Case Handling: Start the search at the cursor location by default.
// However, if the cursor location is at the beginning of previous search hit, then
// move the cursor forward by one character to ensure the previous search hit is not
// found.
//
// Note: for a forward or backward search the cursor is placed at the beginning of the
// match.
//
if (Objects.equals(startLocation, currentSearchLocation.getFieldLocation())) {
if (searchForward) {
// Given:
// -search text: 'fox'
// -search domain: 'What the |fox say'
// -a previous search hit just before 'fox'
//
// Move the cursor just past the 'f' so the next forward search will not
// find the current 'fox' hit. Thus the new search domain for this line
// will be: "ox say"
//
startLocation.col += 1;
}
else {
// Given:
// -search text: 'fox'
// -search domain: 'What the |fox say'
// -a previous search hit just before 'fox'
//
// Move the cursor just past the 'o' so the next backward search will not
// find the current 'fox' hit. Thus the new search domain for this line
// will be: "What the fo"
//
int length = currentSearchLocation.getMatchLength();
startLocation.col += length - 1;
}
}
@Override
protected void decompilerActionPerformed(DecompilerActionContext context) {
DecompilerPanel decompilerPanel = context.getDecompilerPanel();
FindDialog dialog = getFindDialog(decompilerPanel);
String text = decompilerPanel.getSelectedText();
if (text == null) {
text = decompilerPanel.getHighlightedText();
// note: if we decide to grab the text under the cursor, then use
// text = decompilerPanel.getTextUnderCursor();
return startLocation;
}
if (!StringUtils.isBlank(text)) {
dialog.setSearchText(text);
}
// show over the root frame, so the user can still see the Decompiler window
context.getTool().showDialog(dialog);
}
}

View file

@ -0,0 +1,303 @@
/* ###
* 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.decompile;
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Test;
import docking.action.DockingActionIf;
import docking.widgets.FindDialog;
import docking.widgets.dialogs.InputDialog;
import docking.widgets.fieldpanel.support.FieldLocation;
import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.plugin.core.decompile.actions.FieldBasedSearchLocation;
import ghidra.program.model.listing.Program;
import ghidra.test.ClassicSampleX86ProgramBuilder;
public class DecompilerFindDialogTest extends AbstractDecompilerTest {
private FindDialog findDialog;
@Override
@After
public void tearDown() throws Exception {
close(findDialog);
super.tearDown();
}
@Override
protected Program getProgram() throws Exception {
return buildProgram();
}
private Program buildProgram() throws Exception {
ClassicSampleX86ProgramBuilder builder =
new ClassicSampleX86ProgramBuilder("notepad", false, this);
return builder.getProgram();
}
@Test
public void testFind() {
/*
bool FUN_01002239(int param_1)
{
undefined4 uVar1;
int iVar2;
undefined4 *puVar3;
bool bVar4;
undefined *puVar5;
undefined2 local_210;
undefined4 local_20e [129];
int local_8;
local_210 = 0;
puVar3 = local_20e;
...
...
...
*/
decompile("1002239");
String text = "puVar";
showFind(text);
next();
int length = text.length();
int line = 9;
int column = 12;
assertSearchHit(line, column, length);
line = 11;
column = 11;
next();
assertSearchHit(line, column, length);
line = 17;
column = 0;
next();
assertSearchHit(line, column, length);
line = 11;
column = 11;
previous();
assertSearchHit(line, column, length);
line = 9;
column = 12;
previous();
assertSearchHit(line, column, length);
}
@Test
public void testSearchWithinField() {
//
// Test that we find multiple search hits within a field in both directions
//
/*
bool FUN_01002239(int param_1)
{
undefined4 uVar1;
int iVar2;
undefined4 *pu1111Var;
bool bVar4;
undefined *puVar5;
undefined2 local_210;
undefined4 local_20e [129];
int local_8;
local_210 = 0;
puVar3 = local_20e;
...
...
...
*/
decompile("1002239");
int line = 9;
int column = 12;
setDecompilerLocation(line, column);
rename("pu1111Var");
// reset
setDecompilerLocation(1, 0);
String text = "11";
showFind(text);
next();
int length = 2;
line = 9;
column = 14;
assertSearchHit(line, column, length);
column++; // same variable; one char over
next();
assertSearchHit(line, column, length);
column++; // same variable; one char over
next();
assertSearchHit(line, column, length);
column--; // same variable; one char over
previous();
assertSearchHit(line, column, length);
column--; // same variable; one char over
previous();
assertSearchHit(line, column, length);
}
@Test
public void testMultipleSearchHitsOnTheSameLine() {
//
// Test that we find multiple search hits within a single line
//
/*
bool FUN_01002239(int param_1)
{
undefined4 uVar1;
int iVar2;
undefined4 *puVar3;
bool bVar4;
undefined *puVar5;
undefined2 local_210;
undefined4 local_20e [129];
int local_8;
local_210 = 0;
puVar3 = local_20e;
local_210 = 0;
puVar3 = local_20e;
for (iVar2 = 0x81; iVar2 != 0; iVar2 = iVar2 + -1) {
*puVar3 = 0;
puVar3 = puVar3 + 1;
}
...
...
...
*/
decompile("1002239");
// skip past some search hits for test brevity
setDecompilerLocation(18, 0);
String text = "puVar3";
showFind(text);
next();
int length = text.length();
int line = 19;
int column = 1;
assertSearchHit(line, column, length); // *|puVar3 = 0;
line = 20;
column = 0;
next();
assertSearchHit(line, column, length); // |puVar3 = puVar3 + 1;
line = 20;
column = 9;
next();
assertSearchHit(line, column, length); // puVar3 = |puVar3 + 1;
line = 20;
column = 0;
previous();
assertSearchHit(line, column, length);
line = 19;
column = 1;
previous();
assertSearchHit(line, column, length);
}
//==================================================================================================
// Private Methods
//==================================================================================================
private void next() {
runSwing(() -> findDialog.next());
}
private void previous() {
runSwing(() -> findDialog.previous());
}
private void assertSearchHit(int line, int column, int length) {
waitForSwing();
assertCurrentLocation(line, column);
DecompilerPanel panel = getDecompilerPanel();
FieldBasedSearchLocation searchResults = panel.getSearchResults();
FieldLocation searchCursorLocation = searchResults.getFieldLocation();
int searchLineNumber = searchCursorLocation.getIndex().intValue() + 1;
assertEquals("Search result is on the wrong line", line, searchLineNumber);
int searchStartColumn = searchResults.getStartIndexInclusive();
assertEquals("Search result does not start on the correct character", column,
searchStartColumn);
int searchEndColumn = searchResults.getEndIndexInclusive();
assertEquals("Search result does not end on the correct character", column + length - 1,
searchEndColumn);
}
private void assertCurrentLocation(int line, int col) {
DecompilerPanel panel = provider.getDecompilerPanel();
FieldLocation actual = panel.getCursorPosition();
FieldLocation expected = loc(line, col);
assertEquals("Decompiler cursor is not at the expected location", expected, actual);
}
private void showFind(String text) {
DockingActionIf findAction = getAction(decompiler, "Find");
performAction(findAction, provider, true);
findDialog = waitForDialogComponent(FindDialog.class);
runSwing(() -> findDialog.setSearchText(text));
}
private void rename(String newName) {
DockingActionIf action = getAction(decompiler, "Rename Variable");
performAction(action, provider.getActionContext(null), false);
InputDialog dialog = waitForDialogComponent(InputDialog.class);
runSwing(() -> dialog.setValue(newName));
pressButtonByText(dialog, "OK");
waitForDecompiler();
}
}

View file

@ -136,6 +136,14 @@ public class FindDialog extends DialogComponentProvider {
textField.setText("");
}
public void next() {
doSearch(true);
}
public void previous() {
doSearch(false);
}
private void doSearch(boolean forward) {
if (!nextButton.isEnabled()) {

View file

@ -21,7 +21,7 @@ import java.util.List;
import org.junit.Test;
import generic.test.AbstractGenericTest;
import ghidra.util.Swing;
public class FindDialogTest {
@ -32,8 +32,8 @@ public class FindDialogTest {
findDialog.setHistory(List.of("search1"));
String searchText = "search"; // a prefix of an existing history entry
findDialog.setSearchText(searchText);
assertEquals(searchText, AbstractGenericTest.runSwing(() -> findDialog.getSearchText()));
Swing.runNow(() -> findDialog.setSearchText(searchText));
assertEquals(searchText, Swing.runNow(() -> findDialog.getSearchText()));
}
private class DummySearcher implements FindDialogSearcher {