mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
GP-1765 - Fixed sometimes incorrect Find Dialog result highlighting
This commit is contained in:
parent
026fad27ab
commit
68b7f88063
7 changed files with 534 additions and 188 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,7 +240,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
|
||||||
syntax_color[ClangToken.GLOBAL_COLOR] = options.getGlobalColor();
|
syntax_color[ClangToken.GLOBAL_COLOR] = options.getGlobalColor();
|
||||||
syntax_color[ClangToken.DEFAULT_COLOR] = options.getDefaultColor();
|
syntax_color[ClangToken.DEFAULT_COLOR] = options.getDefaultColor();
|
||||||
|
|
||||||
// setting the metrics here will indirectly trigger the new font to be used deeper in
|
// setting the metrics here will indirectly trigger the new font to be used deeper in
|
||||||
// the bowels of the FieldPanel (you can get the font from the metrics)
|
// the bowels of the FieldPanel (you can get the font from the metrics)
|
||||||
Font font = options.getDefaultFont();
|
Font font = options.getDefaultFont();
|
||||||
metrics = Toolkit.getDefaultToolkit().getFontMetrics(font);
|
metrics = Toolkit.getDefaultToolkit().getFontMetrics(font);
|
||||||
|
@ -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);
|
||||||
|
@ -369,7 +369,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
// Search Related Methods
|
// Search Related Methods
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
private SearchLocation findNextTokenGoingForward(
|
private SearchLocation findNextTokenGoingForward(
|
||||||
java.util.function.Function<String, SearchMatch> matcher, String searchString,
|
java.util.function.Function<String, SearchMatch> matcher, String searchString,
|
||||||
|
@ -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,18 +553,28 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
// End Search Related Methods
|
// End Search Related Methods
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
ClangToken getTokenForLocation(FieldLocation fieldLocation) {
|
ClangToken getTokenForLocation(FieldLocation fieldLocation) {
|
||||||
int row = fieldLocation.getIndex().intValue();
|
int row = fieldLocation.getIndex().intValue();
|
||||||
|
@ -573,7 +597,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
|
||||||
}
|
}
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
// Inner Classes
|
// Inner Classes
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
private static class LineNumberFieldElement extends ClangFieldElement {
|
private static class LineNumberFieldElement extends ClangFieldElement {
|
||||||
private static final Color FOREGROUND_COLOR = new Color(125, 125, 125);
|
private static final Color FOREGROUND_COLOR = new Color(125, 125, 125);
|
||||||
|
|
|
@ -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
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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()) {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue