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

View file

@ -122,27 +122,27 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
@Override @Override
public void modelSizeChanged(IndexMapper mapper) { public void modelSizeChanged(IndexMapper mapper) {
for (int i = 0; i < listeners.size(); ++i) { for (LayoutModelListener listener : listeners) {
listeners.get(i).modelSizeChanged(mapper); listener.modelSizeChanged(mapper);
} }
} }
public void modelChanged() { public void modelChanged() {
for (int i = 0; i < listeners.size(); ++i) { for (LayoutModelListener listener : listeners) {
listeners.get(i).modelSizeChanged(IndexMapper.IDENTITY_MAPPER); listener.modelSizeChanged(IndexMapper.IDENTITY_MAPPER);
} }
} }
@Override @Override
public void dataChanged(BigInteger start, BigInteger end) { public void dataChanged(BigInteger start, BigInteger end) {
for (int i = 0; i < listeners.size(); ++i) { for (LayoutModelListener listener : listeners) {
listeners.get(i).dataChanged(start, end); listener.dataChanged(start, end);
} }
} }
public void layoutChanged() { public void layoutChanged() {
for (int i = 0; i < listeners.size(); ++i) { for (LayoutModelListener listener : listeners) {
listeners.get(i).dataChanged(BigInteger.ZERO, numIndexes); listener.dataChanged(BigInteger.ZERO, numIndexes);
} }
} }
@ -336,11 +336,11 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
for (String element : errlines_init) { for (String element : errlines_init) {
splitToMaxWidthLines(errlines, element); splitToMaxWidthLines(errlines, element);
} }
for (int i = 0; i < errlines.size(); ++i) { for (String errline : errlines) {
ClangTokenGroup line = new ClangTokenGroup(docroot); ClangTokenGroup line = new ClangTokenGroup(docroot);
ClangBreak lineBreak = new ClangBreak(line, 1); ClangBreak lineBreak = new ClangBreak(line, 1);
ClangSyntaxToken message = ClangSyntaxToken message =
new ClangSyntaxToken(line, errlines.get(i), ClangXML.COMMENT_COLOR); new ClangSyntaxToken(line, errline, ClangXML.COMMENT_COLOR);
line.AddTokenGroup(lineBreak); line.AddTokenGroup(lineBreak);
line.AddTokenGroup(message); line.AddTokenGroup(message);
docroot.AddTokenGroup(line); docroot.AddTokenGroup(line);
@ -378,22 +378,30 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
int row = currentLocation.getIndex().intValue(); int row = currentLocation.getIndex().intValue();
for (int i = row; i < fieldList.length; i++) { for (int i = row; i < fieldList.length; i++) {
ClangTextField field = (ClangTextField) fieldList[i]; ClangTextField field = (ClangTextField) fieldList[i];
String textLine = String partialLine =
getTextLineFromOffset((i == row) ? currentLocation : null, field, true); getTextLineFromOffset((i == row) ? currentLocation : null, field, true);
SearchMatch match = matcher.apply(partialLine);
SearchMatch match = matcher.apply(textLine); if (match == SearchMatch.NO_MATCH) {
if (match != SearchMatch.NO_MATCH) { continue;
if (i == row) {
String fullLine = field.getText();
match.start += fullLine.length() - textLine.length();
}
FieldNumberColumnPair pair = getFieldIndexFromOffset(match.start, field);
FieldLocation fieldLocation =
new FieldLocation(i, pair.getFieldNumber(), 0, pair.getColumn());
return new FieldBasedSearchLocation(fieldLocation, match.start, match.end - 1,
searchString, true);
} }
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();
int cursorOffset = fullLine.length() - partialLine.length();
match.start += cursorOffset;
match.end += cursorOffset;
}
FieldNumberColumnPair pair = getFieldIndexFromOffset(match.start, field);
FieldLocation fieldLocation =
new FieldLocation(i, pair.getFieldNumber(), 0, pair.getColumn());
return new FieldBasedSearchLocation(fieldLocation, match.start, match.end - 1,
searchString, true);
} }
return null; return null;
} }
@ -442,7 +450,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
if (matcher.find()) { if (matcher.find()) {
int start = matcher.start(); int start = matcher.start();
int end = matcher.end(); int end = matcher.end();
return new SearchMatch(start, end); return new SearchMatch(start, end, textLine);
} }
return SearchMatch.NO_MATCH; return SearchMatch.NO_MATCH;
@ -469,7 +477,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
end = matcher.end(); end = matcher.end();
} }
return new SearchMatch(start, end); return new SearchMatch(start, end, textLine);
}; };
return findNextTokenGoingBackward(reverse, searchString, currentLocation); return findNextTokenGoingBackward(reverse, searchString, currentLocation);
@ -486,7 +494,8 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
if (index == -1) { if (index == -1) {
return SearchMatch.NO_MATCH; return SearchMatch.NO_MATCH;
} }
return new SearchMatch(index, index + searchString.length());
return new SearchMatch(index, index + searchString.length(), textLine);
}; };
return findNextTokenGoingForward(function, searchString, currentLocation); return findNextTokenGoingForward(function, searchString, currentLocation);
@ -498,7 +507,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
if (index == -1) { if (index == -1) {
return SearchMatch.NO_MATCH; return SearchMatch.NO_MATCH;
} }
return new SearchMatch(index, index + searchString.length()); return new SearchMatch(index, index + searchString.length(), textLine);
}; };
return findNextTokenGoingBackward(function, searchString, currentLocation); return findNextTokenGoingBackward(function, searchString, currentLocation);
@ -506,6 +515,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
private String getTextLineFromOffset(FieldLocation location, ClangTextField textField, private String getTextLineFromOffset(FieldLocation location, ClangTextField textField,
boolean forwardSearch) { boolean forwardSearch) {
if (location == null) { // the cursor location is not on this line; use all of the text if (location == null) { // the cursor location is not on this line; use all of the text
return textField.getText(); return textField.getText();
} }
@ -518,12 +528,16 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
if (forwardSearch) { if (forwardSearch) {
// Protects against the location column being out of range (this can int nextCol = location.getCol();
// happen if we're searching forward and the cursor is past the last token).
if (location.getCol() + 1 >= partialText.length()) { // 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 "";
} }
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 // backwards search
@ -539,13 +553,23 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
} }
private static class SearchMatch { 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 start;
private int end; private int end;
private String textLine;
SearchMatch(int start, int end) { SearchMatch(int start, int end, String textLine) {
this.start = start; this.start = start;
this.end = end; 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(); repaint();
} }
public FieldBasedSearchLocation getSearchResults() {
return (FieldBasedSearchLocation) currentSearchLocation;
}
//================================================================================================== //==================================================================================================
// End Search Methods // 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.InputEvent;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.util.List; import java.util.List;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -58,6 +59,31 @@ public class FindAction extends AbstractDecompilerAction {
return findDialog; 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 static class DecompilerSearcher implements FindDialogSearcher {
private DecompilerPanel decompilerPanel; private DecompilerPanel decompilerPanel;
@ -112,35 +138,60 @@ public class FindAction extends AbstractDecompilerAction {
public SearchLocation search(String text, CursorPosition position, boolean searchForward, public SearchLocation search(String text, CursorPosition position, boolean searchForward,
boolean useRegex) { boolean useRegex) {
DecompilerCursorPosition decompilerCursorPosition = (DecompilerCursorPosition) position; DecompilerCursorPosition decompilerCursorPosition = (DecompilerCursorPosition) position;
FieldLocation fieldLocation = decompilerCursorPosition.getFieldLocation(); FieldLocation startLocation =
return useRegex ? decompilerPanel.searchTextRegex(text, fieldLocation, searchForward) getNextSearchStartLocation(decompilerCursorPosition, searchForward);
: decompilerPanel.searchText(text, fieldLocation, searchForward); return useRegex ? decompilerPanel.searchTextRegex(text, startLocation, searchForward)
: decompilerPanel.searchText(text, startLocation, searchForward);
} }
} private FieldLocation getNextSearchStartLocation(
DecompilerCursorPosition decompilerCursorPosition, boolean searchForward) {
@Override FieldLocation startLocation = decompilerCursorPosition.getFieldLocation();
protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) { FieldBasedSearchLocation currentSearchLocation = decompilerPanel.getSearchResults();
return true; if (currentSearchLocation == null) {
} return startLocation; // nothing to do; no prior search hit
}
@Override //
protected void decompilerActionPerformed(DecompilerActionContext context) { // Special Case Handling: Start the search at the cursor location by default.
DecompilerPanel decompilerPanel = context.getDecompilerPanel(); // However, if the cursor location is at the beginning of previous search hit, then
FindDialog dialog = getFindDialog(decompilerPanel); // move the cursor forward by one character to ensure the previous search hit is not
String text = decompilerPanel.getSelectedText(); // found.
if (text == null) { //
text = decompilerPanel.getHighlightedText(); // Note: for a forward or backward search the cursor is placed at the beginning of the
// match.
//
if (Objects.equals(startLocation, currentSearchLocation.getFieldLocation())) {
// note: if we decide to grab the text under the cursor, then use if (searchForward) {
// text = decompilerPanel.getTextUnderCursor(); // 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;
}
}
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(""); textField.setText("");
} }
public void next() {
doSearch(true);
}
public void previous() {
doSearch(false);
}
private void doSearch(boolean forward) { private void doSearch(boolean forward) {
if (!nextButton.isEnabled()) { if (!nextButton.isEnabled()) {

View file

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