diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/MergeManagerProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/MergeManagerProvider.java index c5ab8722f4..d323b5a605 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/MergeManagerProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/MergeManagerProvider.java @@ -23,6 +23,7 @@ import javax.swing.*; import docking.ActionContext; import docking.WindowPosition; import docking.options.editor.ButtonPanelFactory; +import docking.util.image.ToolIconURL; import docking.widgets.OptionDialog; import docking.widgets.label.*; import ghidra.app.context.ListingActionContext; @@ -32,7 +33,6 @@ import ghidra.app.util.HelpTopics; import ghidra.app.util.viewer.format.FieldHeaderComp; import ghidra.app.util.viewer.format.FieldHeaderLocation; import ghidra.framework.plugintool.ComponentProviderAdapter; -import ghidra.framework.project.tool.ToolIconURL; import ghidra.program.util.ProgramLocation; import ghidra.util.HelpLocation; import ghidra.util.layout.VerticalLayout; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java b/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java index 5b3050488e..a78010c223 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java @@ -22,8 +22,6 @@ import java.util.*; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; -import javax.swing.JFrame; - import org.jdom.Element; import docking.ComponentProvider; @@ -332,6 +330,7 @@ public class TestEnv { *

This method is considered sub-standard and users should prefer instead * {@link #launchDefaultTool()} or {@link #launchDefaultTool(Program)}. * + * @param p the program * @return the newly shown tool */ public PluginTool showTool(Program p) { @@ -357,7 +356,7 @@ public class TestEnv { /** * Waits for the first window of the given class. This method is the same as - * {@link #waitForDialogComponent(Window, Class, int)} with the exception that the parent + * {@link #waitForDialogComponent(Class, int)} with the exception that the parent * window is assumed to be this instance's tool frame. * * @param ghidraClass The class of the dialog the user desires @@ -369,8 +368,7 @@ public class TestEnv { @Deprecated public T waitForDialogComponent(Class ghidraClass, int maxTimeMS) { - JFrame frame = lazyTool().getToolFrame(); - return AbstractDockingTest.waitForDialogComponent(frame, ghidraClass, maxTimeMS); + return AbstractDockingTest.waitForDialogComponent(ghidraClass); } private static GhidraProject createGhidraTestProject(String projectName) throws IOException { @@ -381,7 +379,20 @@ public class TestEnv { deleteSavedFrontEndTool(); String projectDirectoryName = AbstractGTest.getTestDirectoryPath(); - return GhidraProject.createProject(projectDirectoryName, projectName, true); + GhidraProject gp = GhidraProject.createProject(projectDirectoryName, projectName, true); + + // + // Unusual Code Alert: The default tool is not always found in the testing environment, + // depending upon where the test lives. This code maps the test tool to that tool name + // so that tests will have the default tool as needed. + // + Project project = gp.getProject(); + ToolChest toolChest = project.getLocalToolChest(); + ToolTemplate template = getToolTemplate(AbstractGenericTest.DEFAULT_TEST_TOOL_NAME); + template.setName(AbstractGenericTest.DEFAULT_TOOL_NAME); + AbstractGenericTest.runSwing(() -> toolChest.replaceToolTemplate(template)); + + return gp; } private static void deleteOldTestTools() { @@ -466,6 +477,7 @@ public class TestEnv { /** * This method differs from {@link #launchDefaultTool()} in that this method does not set the * tool variable in of this TestEnv instance. + * @return the tool */ public PluginTool createDefaultTool() { PluginTool newTool = launchDefaultToolByName(AbstractGenericTest.DEFAULT_TEST_TOOL_NAME); @@ -497,15 +509,14 @@ public class TestEnv { return tool; } - protected PluginTool launchDefaultToolByName(final String toolName) { - AtomicReference ref = new AtomicReference<>(); - AbstractGenericTest.runSwing(() -> { + protected PluginTool launchDefaultToolByName(String toolName) { - ToolTemplate toolTemplate = - ToolUtils.readToolTemplate("defaultTools/" + toolName + ".tool"); + return AbstractGenericTest.runSwing(() -> { + + ToolTemplate toolTemplate = getToolTemplate(toolName); if (toolTemplate == null) { Msg.debug(this, "Unable to find tool: " + toolName); - return; + return null; } boolean wasErrorGUIEnabled = AbstractDockingTest.isUseErrorGUI(); @@ -515,11 +526,23 @@ public class TestEnv { Project project = frontEndToolInstance.getProject(); ToolManager toolManager = project.getToolManager(); Workspace workspace = toolManager.getActiveWorkspace(); - ref.set((PluginTool) workspace.runTool(toolTemplate)); AbstractDockingTest.setErrorGUIEnabled(wasErrorGUIEnabled); + return (PluginTool) workspace.runTool(toolTemplate); + }); + } + + private static ToolTemplate getToolTemplate(String toolName) { + + return AbstractGenericTest.runSwing(() -> { + ToolTemplate toolTemplate = + ToolUtils.readToolTemplate("defaultTools/" + toolName + ".tool"); + if (toolTemplate == null) { + Msg.debug(TestEnv.class, "Unable to find tool: " + toolName); + return null; + } + return toolTemplate; }); - return ref.get(); } public ScriptTaskListener runScript(File script) throws PluginException { @@ -550,6 +573,7 @@ public class TestEnv { /** * Returns GhidraProject associated with this environment + * @return the project */ public GhidraProject getGhidraProject() { return gp; @@ -559,6 +583,7 @@ public class TestEnv { * A convenience method to close and then reopen the default project created by this TestEnv * instance. This will not delete the project between opening and closing and will restore * the project to its previous state. + * @throws IOException if any exception occurs while saving and reopening */ public void closeAndReopenProject() throws IOException { gp.setDeleteOnClose(false); @@ -579,9 +604,6 @@ public class TestEnv { return gp.getProjectManager(); } - /** - * Returns Project associated with this environment - */ public Project getProject() { return gp.getProject(); } @@ -646,6 +668,8 @@ public class TestEnv { * the only reason to use this method vice openProgram(). * * @param programName the name of the program zip file without the ".gzf" extension. + * @return the restored domain file + * @throws FileNotFoundException if the program file cannot be found */ public DomainFile restoreProgram(String programName) throws FileNotFoundException { DomainFile df = programManager.addProgramToProject(getProject(), programName); @@ -674,12 +698,12 @@ public class TestEnv { * @param relativePathName This should be a pathname relative to the "test_resources/testdata" * director or relative to the "typeinfo" directory. The name should * include the ".gdt" suffix. - * @param domainFolder the folder in the test project where the archive should be created. - * @param monitor monitor for canceling this restore. + * @param domainFolder the folder in the test project where the archive should be created * @return the domain file that was created in the project + * @throws Exception if an exception occurs */ public DomainFile restoreDataTypeArchive(String relativePathName, DomainFolder domainFolder) - throws InvalidNameException, IOException, VersionException { + throws Exception { File gdtFile; try { @@ -738,10 +762,10 @@ public class TestEnv { * @param program program object * @param replace if true any existing cached database with the same name will be replaced * @param monitor task monitor - * @throws DuplicateNameException if already cached + * @throws Exception if already cached */ public void saveToCache(String progName, ProgramDB program, boolean replace, - TaskMonitor monitor) throws IOException, DuplicateNameException, CancelledException { + TaskMonitor monitor) throws Exception { programManager.saveToCache(progName, program, replace, monitor); } @@ -826,7 +850,8 @@ public class TestEnv { * Launches a tool of the given name using the given domain file. *

* Note: the tool returned will have auto save disabled by default. - * + * + * @param toolName the tool's name * @return the tool that is launched */ public PluginTool launchTool(String toolName) { diff --git a/Ghidra/Features/Base/src/test.slow/java/docking/widgets/tree/GTreeFilterTest.java b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/tree/GTreeFilterTest.java new file mode 100644 index 0000000000..e9adecb531 --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/tree/GTreeFilterTest.java @@ -0,0 +1,604 @@ +/* ### + * 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 docking.widgets.tree; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; + +import javax.swing.Icon; +import javax.swing.JComponent; + +import org.junit.*; + +import docking.*; +import docking.test.AbstractDockingTest; +import docking.widgets.filter.*; +import ghidra.test.DummyTool; +import ghidra.util.StringUtilities; + +public class GTreeFilterTest extends AbstractDockingTest { + + private GTree gTree; + private FilterTextField filterField; + + private GTreeRootNode root; + + private DockingWindowManager winMgr; + + @Before + public void setUp() throws Exception { + root = new TestRootNode(); + gTree = new GTree(root); + + filterField = (FilterTextField) gTree.getFilterField(); + + winMgr = new DockingWindowManager(new DummyTool(), null); + winMgr.addComponent(new TestTreeComponentProvider()); + winMgr.setVisible(true); + + waitForTree(); + } + + @After + public void tearDown() throws Exception { + winMgr.dispose(); + } + + @Test + public void testContains() { + setFilterOptions(TextFilterStrategy.CONTAINS, false); + // no filter text - make sure all 5 nodes are there + assertEquals(5, root.getChildCount()); + + setFilterText("ABC"); + assertEquals("Expected 4 of nodes to be in filtered tree!", 4, root.getChildCount()); + + checkContainsNode("ABC"); + checkContainsNode("XABC"); + checkContainsNode("ABCX"); + checkContainsNode("XABCX"); + + setFilterText("MMM"); + assertEquals("Expected 4 of nodes to be in filtered tree!", 0, root.getChildCount()); + + setFilterText(""); + assertEquals("Expected all 5 nodes to be back", 5, root.getChildCount()); + } + + @Test + public void testMultiWordContains() { + setFilterOptions(TextFilterStrategy.CONTAINS, false); + // no filter text - make sure all 5 nodes are there + assertEquals(5, root.getChildCount()); + + setFilterOptions(TextFilterStrategy.CONTAINS, false, true, ' ', + MultitermEvaluationMode.AND); + + setFilterText("CX AB"); + assertEquals(2, root.getChildCount()); + + setFilterOptions(TextFilterStrategy.CONTAINS, false, true, ' ', MultitermEvaluationMode.OR); + + setFilterText("CX AB"); + assertEquals(4, root.getChildCount()); + + checkContainsNode("ABCX"); + checkContainsNode("XABCX"); + + setFilterText(""); + assertEquals("Expected all 5 nodes to be back", 5, root.getChildCount()); + } + + @Test + public void testMultiWordContainsDelimiters() { + + setFilterOptions(TextFilterStrategy.CONTAINS, false); + // no filter text - make sure all 5 nodes are there + assertEquals(5, root.getChildCount()); + + for (char delim : FilterOptions.VALID_MULTITERM_DELIMITERS.toCharArray()) { + setFilterOptions(TextFilterStrategy.CONTAINS, false, true, delim, + MultitermEvaluationMode.AND); + + setFilterText("CX" + delim + "AB"); + assertEquals(2, root.getChildCount()); + + setFilterOptions(TextFilterStrategy.CONTAINS, false, true, delim, + MultitermEvaluationMode.OR); + + setFilterText("CX" + delim + "AB"); + assertEquals(4, root.getChildCount()); + + checkContainsNode("ABCX"); + checkContainsNode("XABCX"); + + setFilterText(""); + assertEquals("Expected all 5 nodes to be back", 5, root.getChildCount()); + } + + } + + @Test + public void testMultiWordContainsDelimitersWithLeadingSpaces() { + + setFilterOptions(TextFilterStrategy.CONTAINS, false); + // no filter text - make sure all 5 nodes are there + assertEquals(5, root.getChildCount()); + + String delimPad = StringUtilities.pad("", ' ', 1); + + for (char delim : FilterOptions.VALID_MULTITERM_DELIMITERS.toCharArray()) { + setFilterOptions(TextFilterStrategy.CONTAINS, false, true, delim, + MultitermEvaluationMode.AND); + + String delimStr = delimPad + delim; + + setFilterText("CX" + delimStr + "AB"); + assertEquals(2, root.getChildCount()); + + setFilterOptions(TextFilterStrategy.CONTAINS, false, true, delim, + MultitermEvaluationMode.OR); + + setFilterText("CX" + delimStr + "AB"); + assertEquals(4, root.getChildCount()); + + checkContainsNode("ABCX"); + checkContainsNode("XABCX"); + + setFilterText(""); + assertEquals("Expected all 5 nodes to be back", 5, root.getChildCount()); + + } + } + + @Test + public void testMultiWordContainsDelimitersWithTrailingSpaces() { + + setFilterOptions(TextFilterStrategy.CONTAINS, false); + // no filter text - make sure all 5 nodes are there + assertEquals(5, root.getChildCount()); + + String delimPad = StringUtilities.pad("", ' ', 1); + + for (char delim : FilterOptions.VALID_MULTITERM_DELIMITERS.toCharArray()) { + setFilterOptions(TextFilterStrategy.CONTAINS, false, true, delim, + MultitermEvaluationMode.AND); + + String delimStr = delim + delimPad; + + setFilterText("CX" + delimStr + "AB"); + assertEquals(2, root.getChildCount()); + + setFilterOptions(TextFilterStrategy.CONTAINS, false, true, delim, + MultitermEvaluationMode.OR); + + setFilterText("CX" + delimStr + "AB"); + assertEquals(4, root.getChildCount()); + + checkContainsNode("ABCX"); + checkContainsNode("XABCX"); + + setFilterText(""); + assertEquals("Expected all 5 nodes to be back", 5, root.getChildCount()); + + } + } + + @Test + public void testMultiWordContainsDelimitersWithBoundingSpaces() { + + setFilterOptions(TextFilterStrategy.CONTAINS, false); + // no filter text - make sure all 5 nodes are there + assertEquals(5, root.getChildCount()); + + String delimPad = StringUtilities.pad("", ' ', 1); + + for (char delim : FilterOptions.VALID_MULTITERM_DELIMITERS.toCharArray()) { + setFilterOptions(TextFilterStrategy.CONTAINS, false, true, delim, + MultitermEvaluationMode.AND); + + String delimStr = delimPad + delim + delimPad; + + setFilterText("CX" + delimStr + "AB"); + assertEquals(2, root.getChildCount()); + + setFilterOptions(TextFilterStrategy.CONTAINS, false, true, delim, + MultitermEvaluationMode.OR); + + setFilterText("CX" + delimStr + "AB"); + assertEquals(4, root.getChildCount()); + + checkContainsNode("ABCX"); + checkContainsNode("XABCX"); + + setFilterText(""); + assertEquals("Expected all 5 nodes to be back", 5, root.getChildCount()); + + } + } + + @Test + public void testInvertedContains() { + setFilterOptions(TextFilterStrategy.CONTAINS, true); + + assertEquals(5, root.getChildCount()); + + setFilterText("ABC"); + assertEquals(1, root.getChildCount()); + + checkDoesNotContainsNode("ABC"); + checkDoesNotContainsNode("XABC"); + checkDoesNotContainsNode("ABCX"); + checkDoesNotContainsNode("XABCX"); + + setFilterText("MMM"); + assertEquals(5, root.getChildCount()); + + setFilterText(""); + assertEquals("Expected all 5 nodes to be back", 5, root.getChildCount()); + } + + @Test + public void testInvertedMultiWordContains() { + setFilterOptions(TextFilterStrategy.CONTAINS, true, true, ' ', MultitermEvaluationMode.AND); + // no filter text - make sure all 5 nodes are there + assertEquals(5, root.getChildCount()); + + setFilterText("CX AB"); + + checkDoesNotContainsNode("ABCX"); + checkDoesNotContainsNode("XABCX"); + assertEquals(3, root.getChildCount()); + + setFilterOptions(TextFilterStrategy.CONTAINS, true, true, ' ', MultitermEvaluationMode.OR); + setFilterText(""); + // no filter text - make sure all 5 nodes are there + assertEquals(5, root.getChildCount()); + + setFilterText("CX AB"); + + checkDoesNotContainsNode("ABCX"); + checkDoesNotContainsNode("XABCX"); + assertEquals(1, root.getChildCount()); + + setFilterText(""); + assertEquals("Expected all 5 nodes to be back", 5, root.getChildCount()); + } + + @Test + public void testStartsWith() { + setFilterOptions(TextFilterStrategy.STARTS_WITH, false); + // no filter text - make sure all 5 nodes are there + assertEquals(5, root.getChildCount()); + + setFilterText("ABC"); + checkContainsNode("ABC"); + checkContainsNode("ABCX"); + assertEquals(2, root.getChildCount()); + + setFilterText("MMM"); + assertEquals(0, root.getChildCount()); + + setFilterText(""); + assertEquals("Expected all 5 nodes to be back", 5, root.getChildCount()); + } + + @Test + public void testInvertedStartsWith() { + setFilterOptions(TextFilterStrategy.STARTS_WITH, true); + // no filter text - make sure all 5 nodes are there + assertEquals(5, root.getChildCount()); + + setFilterText("ABC"); + checkDoesNotContainsNode("ABC"); + checkDoesNotContainsNode("ABCX"); + assertEquals(3, root.getChildCount()); + + setFilterText("MMM"); + assertEquals(5, root.getChildCount()); + + setFilterText(""); + assertEquals("Expected all 5 nodes to be back", 5, root.getChildCount()); + } + + @Test + public void testExactMatch() { + setFilterOptions(TextFilterStrategy.MATCHES_EXACTLY, false); + // no filter text - make sure all 5 nodes are there + assertEquals(5, root.getChildCount()); + + setFilterText("ABC"); + checkContainsNode("ABC"); + assertEquals(1, root.getChildCount()); + + setFilterText("MMM"); + assertEquals(0, root.getChildCount()); + + setFilterText(""); + assertEquals("Expected all 5 nodes to be back", 5, root.getChildCount()); + } + + @Test + public void testInvertedExactMatch() { + setFilterOptions(TextFilterStrategy.MATCHES_EXACTLY, true); + // no filter text - make sure all 5 nodes are there + assertEquals(5, root.getChildCount()); + + setFilterText("ABC"); + checkDoesNotContainsNode("ABC"); + assertEquals(4, root.getChildCount()); + + setFilterText("MMM"); + assertEquals(5, root.getChildCount()); + + setFilterText(""); + assertEquals("Expected all 5 nodes to be back", 5, root.getChildCount()); + } + + @Test + public void testRegExMatch() { + setFilterOptions(TextFilterStrategy.REGULAR_EXPRESSION, false); + // no filter text - make sure all 5 nodes are there + assertEquals(5, root.getChildCount()); + + setFilterText("^ABC$"); + checkContainsNode("ABC"); + assertEquals("Expected 1 node match exacly match ABC!", 1, root.getChildCount()); + + setFilterText("ABC"); + checkContainsNode("ABC"); + checkContainsNode("XABC"); + checkContainsNode("ABCX"); + checkContainsNode("XABCX"); + assertEquals("Expected 4 of nodes that contain the text ABC!", 4, root.getChildCount()); + + setFilterText("XA.{0,2}X"); + checkContainsNode("XABCX"); + assertEquals(1, root.getChildCount()); + + setFilterText("X{0,1}A.{0,2}X"); + checkContainsNode("XABCX"); + checkContainsNode("ABCX"); + assertEquals(2, root.getChildCount()); + + setFilterText(""); + assertEquals("Expected all 5 nodes to be back", 5, root.getChildCount()); + } + + @Test + public void testInvertedRegExMatch() { + setFilterOptions(TextFilterStrategy.REGULAR_EXPRESSION, true); + // no filter text - make sure all 5 nodes are there + assertEquals(5, root.getChildCount()); + + setFilterText("^ABC$"); + checkDoesNotContainsNode("ABC"); + assertEquals(4, root.getChildCount()); + + setFilterText("ABC"); + checkDoesNotContainsNode("ABC"); + checkDoesNotContainsNode("XABC"); + checkDoesNotContainsNode("ABCX"); + checkDoesNotContainsNode("XABCX"); + assertEquals(1, root.getChildCount()); + + setFilterText("XA.{0,2}X"); + checkDoesNotContainsNode("XABCX"); + assertEquals(4, root.getChildCount()); + + setFilterText("X{0,1}A.{0,2}X"); + checkDoesNotContainsNode("XABCX"); + checkDoesNotContainsNode("ABCX"); + assertEquals(3, root.getChildCount()); + + setFilterText(""); + assertEquals("Expected all 5 nodes to be back", 5, root.getChildCount()); + } + + @Test + public void testSwitchFilterTypes() { + setFilterOptions(TextFilterStrategy.STARTS_WITH, false); + setFilterText("ABC"); + checkContainsNode("ABC"); + checkContainsNode("ABCX"); + assertEquals(2, root.getChildCount()); + + setFilterOptions(TextFilterStrategy.MATCHES_EXACTLY, false); + checkContainsNode("ABC"); + assertEquals(1, root.getChildCount()); + + setFilterOptions(TextFilterStrategy.CONTAINS, false); + assertEquals("Expected 4 of nodes to be in filtered tree!", 4, root.getChildCount()); + checkContainsNode("ABC"); + checkContainsNode("XABC"); + checkContainsNode("ABCX"); + checkContainsNode("XABCX"); + + } + + @Test + public void testSavingSelectedFilterType() { + setFilterOptions(TextFilterStrategy.MATCHES_EXACTLY, false); + setFilterText("ABC"); + checkContainsNode("ABC"); + assertEquals(1, root.getChildCount()); + + Object originalValue = getInstanceField("uniquePreferenceKey", gTree); + setInstanceField("preferenceKey", gTree.getFilterProvider(), "XYZ"); + setFilterOptions(TextFilterStrategy.STARTS_WITH, false); + checkContainsNode("ABC"); + checkContainsNode("ABCX"); + assertEquals(2, root.getChildCount()); + + setInstanceField("preferenceKey", gTree.getFilterProvider(), originalValue); + setInstanceField("optionsSet", gTree.getFilterProvider(), false); + restorePreferences(); + checkContainsNode("ABC"); + assertEquals(1, root.getChildCount()); + + } + + private void restorePreferences() { + runSwing(() -> { + GTreeFilterProvider filterProvider = gTree.getFilterProvider(); + String key = (String) getInstanceField("uniquePreferenceKey", gTree); + Class[] classes = new Class[] { DockingWindowManager.class, String.class }; + Object[] objs = new Object[] { winMgr, key }; + invokeInstanceMethod("loadFilterPreference", filterProvider, classes, objs); + }); + waitForTree(); + } + + private void checkContainsNode(String string) { + List children = root.getChildren(); + for (GTreeNode gTreeNode : children) { + if (gTreeNode.getName().equals(string)) { + return; + } + } + Assert.fail("Expected node " + string + " to be included in filter, but was not found!"); + } + + private void checkDoesNotContainsNode(String string) { + List children = root.getChildren(); + for (GTreeNode gTreeNode : children) { + if (gTreeNode.getName().equals(string)) { + Assert.fail("Expected node " + string + + " to be NOT be included in filter, but was not found!"); + } + } + } + + private void setFilterText(final String text) { + runSwing(() -> { + filterField.setText(text); + }); + waitForTree(); + } + + private void setFilterOptions(final TextFilterStrategy filterStrategy, final boolean inverted) { + + runSwing(() -> { + FilterOptions filterOptions = new FilterOptions(filterStrategy, false, false, inverted); + ((DefaultGTreeFilterProvider) gTree.getFilterProvider()).setFilterOptions( + filterOptions); + }); + waitForTree(); + + } + + private void setFilterOptions(TextFilterStrategy filterStrategy, boolean inverted, + boolean multiTerm, char splitCharacter, MultitermEvaluationMode evalMode) { + runSwing(() -> { + FilterOptions filterOptions = new FilterOptions(filterStrategy, false, false, inverted, + multiTerm, splitCharacter, evalMode); + ((DefaultGTreeFilterProvider) gTree.getFilterProvider()).setFilterOptions( + filterOptions); + }); + waitForTree(); + } + + private void waitForTree() { + waitForTree(gTree); + } + + private class TestRootNode extends AbstractGTreeRootNode { + + TestRootNode() { + List children = new ArrayList<>(); + children.add(new LeafNode("XYZ")); + children.add(new LeafNode("ABC")); + children.add(new LeafNode("ABCX")); + children.add(new LeafNode("XABC")); + children.add(new LeafNode("XABCX")); + setChildren(children); + } + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return "Root"; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return false; + } + } + + /** + * A basic leaf node + */ + private class LeafNode extends AbstractGTreeNode { + + private final String name; + + LeafNode(String name) { + this.name = name; + } + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return true; + } + } + + class TestTreeComponentProvider extends ComponentProvider { + + public TestTreeComponentProvider() { + super(null, "Test", "Test"); + setDefaultWindowPosition(WindowPosition.STACK); + setTabText("Test"); + } + + @Override + public JComponent getComponent() { + return gTree; + } + + @Override + public String getTitle() { + return "Test Tree"; + } + } + +} diff --git a/Ghidra/Features/ByteViewer/src/test.slow/java/ghidra/framework/plugintool/dialog/SaveToolConfigDialogTest.java b/Ghidra/Features/ByteViewer/src/test.slow/java/ghidra/framework/plugintool/dialog/SaveToolConfigDialogTest.java index 495130750e..55a7615d0b 100644 --- a/Ghidra/Features/ByteViewer/src/test.slow/java/ghidra/framework/plugintool/dialog/SaveToolConfigDialogTest.java +++ b/Ghidra/Features/ByteViewer/src/test.slow/java/ghidra/framework/plugintool/dialog/SaveToolConfigDialogTest.java @@ -28,6 +28,7 @@ import org.junit.*; import docking.action.DockingActionIf; import docking.tool.ToolConstants; +import docking.util.image.ToolIconURL; import docking.widgets.OptionDialog; import docking.widgets.filechooser.GhidraFileChooser; import generic.test.AbstractGTest; @@ -46,7 +47,6 @@ import ghidra.framework.model.ToolTemplate; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.util.PluginException; import ghidra.framework.preferences.Preferences; -import ghidra.framework.project.tool.ToolIconURL; import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.TestEnv; import ghidra.util.exception.AssertException; diff --git a/Ghidra/Features/ProgramDiff/src/test.slow/java/ghidra/app/plugin/core/diff/DiffSaveSettingsTest.java b/Ghidra/Features/ProgramDiff/src/test.slow/java/ghidra/app/plugin/core/diff/DiffSaveSettingsTest.java index feb26c6366..869f07cf44 100644 --- a/Ghidra/Features/ProgramDiff/src/test.slow/java/ghidra/app/plugin/core/diff/DiffSaveSettingsTest.java +++ b/Ghidra/Features/ProgramDiff/src/test.slow/java/ghidra/app/plugin/core/diff/DiffSaveSettingsTest.java @@ -23,12 +23,12 @@ import org.junit.*; import docking.action.DockingActionIf; import docking.action.ToggleDockingAction; +import docking.util.image.ToolIconURL; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin; import ghidra.framework.main.FrontEndPlugin; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.project.tool.GhidraTool; -import ghidra.framework.project.tool.ToolIconURL; import ghidra.program.database.ProgramDB; import ghidra.test.ClassicSampleX86ProgramBuilder; import ghidra.test.TestEnv; diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/ToolIconURL.java b/Ghidra/Framework/Docking/src/main/java/docking/util/image/ToolIconURL.java similarity index 99% rename from Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/ToolIconURL.java rename to Ghidra/Framework/Docking/src/main/java/docking/util/image/ToolIconURL.java index 15a06f786c..81cb1739ee 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/ToolIconURL.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/util/image/ToolIconURL.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.framework.project.tool; +package docking.util.image; import generic.Images; diff --git a/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/tree/GTreeSlowLoadingNode1Test.java b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/tree/GTreeSlowLoadingNode1Test.java new file mode 100644 index 0000000000..b2f6d17b45 --- /dev/null +++ b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/tree/GTreeSlowLoadingNode1Test.java @@ -0,0 +1,309 @@ +/* ### + * 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 docking.widgets.tree; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.List; + +import javax.swing.*; + +import org.junit.*; + +import docking.test.AbstractDockingTest; +import docking.widgets.OptionDialog; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +public class GTreeSlowLoadingNode1Test extends AbstractDockingTest { + + private static final int MAX_DEPTH = 4; + private static final int MIN_CHILD_COUNT = 3; + private static final int MAX_CHILD_COUNT = 40; + + private volatile boolean pauseChildLoading = false; + + private JFrame frame; + private GTree gTree; + + @Before + public void setUp() throws Exception { + + gTree = new GTree(new EmptyRootNode()); + + frame = new JFrame("GTree Test"); + frame.getContentPane().add(gTree); + frame.setSize(400, 400); + frame.setVisible(true); + + waitForTree(); + } + + @After + public void tearDown() throws Exception { + gTree.dispose(); + frame.dispose(); + } + + @Test + public void testBasicLoading() { + gTree.setRootNode(new TestRootNode(100)); + + waitForTree(); + + // make sure we have some children + GTreeRootNode rootNode = gTree.getRootNode(); + GTreeNode nonLeaf1 = rootNode.getChild(0); + assertNotNull(nonLeaf1); + GTreeNode leaf1 = rootNode.getChild(1); + assertNotNull(leaf1); + GTreeNode nonLeaf2 = rootNode.getChild(2); + assertNotNull(nonLeaf2); + + int childCount = nonLeaf1.getChildCount(); + assertTrue("Did not find children for: " + nonLeaf1, childCount > 1); + assertEquals("An expected leaf node has some children", 0, leaf1.getChildCount()); + childCount = nonLeaf2.getChildCount(); + assertTrue("Did not find children for: " + nonLeaf2, childCount > 1); + } + + @Test + public void testSlowNodeShowsProgressBar() { + gTree.setRootNode(new TestRootNode(5000)); + + waitForTree(); + + GTreeRootNode rootNode = gTree.getRootNode(); + GTreeNode nonLeaf1 = rootNode.getChild(0); + assertNotNull(nonLeaf1); + + gTree.expandPath(nonLeaf1); + + assertProgressPanel(true); + + assertTrue(nonLeaf1.isInProgress()); + + // Press the cancel button on the progress monitor + pressProgressPanelCancelButton(); + + waitForTree(); + + // Verify no progress component + assertProgressPanel(false); + } + +//================================================================================================== +// Private Methods +//================================================================================================== + + private void waitForTree() { + waitForTree(gTree); + } + + private void assertProgressPanel(boolean isShowing) { + JComponent panel = (JComponent) getInstanceField("progressPanel", gTree); + if (!isShowing) { + assertNull("Panel is showing when it should not be", panel); + return; + } + + if (panel == null || !panel.isShowing()) { + int maxWaits = 50;// wait a couple seconds, as the progress bar may be delayed + int tryCount = 0; + while (tryCount < maxWaits) { + panel = (JComponent) getInstanceField("progressPanel", gTree); + if (panel != null && panel.isShowing()) { + return;// finally showing! + } + tryCount++; + try { + Thread.sleep(50); + } + catch (Exception e) { + // who cares? + } + } + } + + Assert.fail("Progress panel is not showing as expected"); + } + + private void pressProgressPanelCancelButton() { + Object taskMonitorComponent = getInstanceField("monitor", gTree); + final JButton cancelButton = + (JButton) getInstanceField("cancelButton", taskMonitorComponent); + runSwing(() -> cancelButton.doClick(), false); + + OptionDialog confirDialog = waitForDialogComponent(frame, OptionDialog.class, 2000); + final JButton confirmCancelButton = findButtonByText(confirDialog, "Yes"); + runSwing(() -> confirmCancelButton.doClick()); + } + +//================================================================================================== +// Inner Classes +//================================================================================================== + + private class EmptyRootNode extends AbstractGTreeRootNode { + + EmptyRootNode() { + setChildren(new ArrayList()); + } + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return "Empty Test GTree Root Node"; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return true; + } + } + + private class TestRootNode extends AbstractGTreeRootNode { + + TestRootNode(int loadDelayMillis) { + List children = new ArrayList<>(); + children.add(new TestSlowLoadingNode(loadDelayMillis, 1)); + children.add(new TestLeafNode()); + children.add(new TestSlowLoadingNode(loadDelayMillis, 1)); + setChildren(children); + } + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return "Test GTree Root Node"; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return false; + } + } + + private class TestSlowLoadingNode extends GTreeSlowLoadingNode { + + private final long loadDelayMillis; + private final int depth; + + TestSlowLoadingNode(long loadDelayMillis, int depth) { + this.loadDelayMillis = loadDelayMillis; + this.depth = depth; + } + + @Override + public List generateChildren(TaskMonitor monitor) throws CancelledException { + + if (depth > MAX_DEPTH) { + return new ArrayList<>(); + } + + if (monitor.isCancelled()) { + return new ArrayList<>(); + } + + sleep(loadDelayMillis); + + while (pauseChildLoading) { + sleep(100); + } + + int childCount = getRandomInt(MIN_CHILD_COUNT, MAX_CHILD_COUNT); + List children = new ArrayList<>(); + for (int i = 0; i < childCount; i++) { + if (monitor.isCancelled()) { + return new ArrayList<>(); + } + int value = getRandomInt(0, 1); + if (value == 0) { + children.add(new TestSlowLoadingNode(loadDelayMillis, depth + 1)); + } + else { + children.add(new TestLeafNode()); + } + } + return children; + } + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return getClass().getSimpleName(); + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return false; + } + + } + + private class TestLeafNode extends AbstractGTreeNode { + + private String name = getClass().getSimpleName() + getRandomString(); + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return true; + } + + } +} diff --git a/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/tree/GTreeSlowLoadingNode2Test.java b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/tree/GTreeSlowLoadingNode2Test.java new file mode 100644 index 0000000000..a716d59c07 --- /dev/null +++ b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/tree/GTreeSlowLoadingNode2Test.java @@ -0,0 +1,255 @@ +/* ### + * 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 docking.widgets.tree; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; + +import javax.swing.*; + +import org.junit.*; + +import docking.test.AbstractDockingTest; +import docking.widgets.filter.FilterTextField; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +public class GTreeSlowLoadingNode2Test extends AbstractDockingTest { + + private static final int MAX_DEPTH = 4; + private static final int MIN_CHILD_COUNT = 3; + private static final int MAX_CHILD_COUNT = 3; + + private volatile boolean pauseChildLoading = false; + + private JFrame frame; + private GTree gTree; + private FilterTextField filterField; + + @Before + public void setUp() throws Exception { + + gTree = new GTree(new EmptyRootNode()); + filterField = (FilterTextField) gTree.getFilterField(); + + frame = new JFrame("GTree Test"); + frame.getContentPane().add(gTree); + frame.setSize(400, 400); + frame.setVisible(true); + + waitForTree(); + + } + + @After + public void tearDown() throws Exception { + gTree.dispose(); + frame.dispose(); + } + + @Test + public void testBasicLoading() { + gTree.setRootNode(new TestRootNode(0)); + waitForTree(); + // make sure we have some children + GTreeRootNode rootNode = gTree.getRootNode(); + List allChildren = rootNode.getAllChildren(); + typeFilterText("Many B1"); + clearFilterText(); + List allChildren2 = rootNode.getAllChildren(); + assertEquals("Children were reloaded instead of being reused", allChildren, allChildren2); + } + +//================================================================================================== +// Private Methods +//================================================================================================== + + private void typeFilterText(String text) { + JTextField textField = (JTextField) getInstanceField("textField", filterField); + triggerText(textField, text); + waitForTree(); + } + + private void setFilterText(final String text) { + runSwing(() -> filterField.setText(text)); + waitForTree(); + } + + private void clearFilterText() { + setFilterText(""); + } + + private void waitForTree() { + waitForTree(gTree); + } + +//================================================================================================== +// Inner Classes +//================================================================================================== + + private class EmptyRootNode extends AbstractGTreeRootNode { + + EmptyRootNode() { + setChildren(new ArrayList()); + } + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return "Empty Test GTree Root Node"; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return true; + } + } + + private class TestRootNode extends TestSlowLoadingNode implements GTreeRootNode { + private GTree tree; + + TestRootNode(int loadDelayMillis) { + super(loadDelayMillis, 3); + } + + @Override + public void setGTree(GTree tree) { + this.tree = tree; + } + + @Override + public GTree getGTree() { + return tree; + } + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return "Test GTree Root Node"; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return false; + } + } + + private class TestSlowLoadingNode extends GTreeSlowLoadingNode { + + private final long loadDelayMillis; + private final int depth; + + TestSlowLoadingNode(long loadDelayMillis, int depth) { + this.loadDelayMillis = loadDelayMillis; + this.depth = depth; + } + + @Override + public List generateChildren(TaskMonitor monitor) throws CancelledException { + + if (depth > MAX_DEPTH) { + return new ArrayList<>(); + } + + if (monitor.isCancelled()) { + return new ArrayList<>(); + } + + sleep(loadDelayMillis); + + while (pauseChildLoading) { + sleep(100); + } + + int childCount = getRandomInt(MIN_CHILD_COUNT, MAX_CHILD_COUNT); + List children = new ArrayList<>(); + for (int i = 0; i < childCount; i++) { + if (monitor.isCancelled()) { + return new ArrayList<>(); + } + children.add(new TestSlowLoadingNode(0, depth + 1)); + } + return children; + } + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return getClass().getSimpleName(); + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return false; + } + + } + + private class TestLeafNode extends AbstractGTreeNode { + + private String name = getClass().getSimpleName() + getRandomString(); + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return true; + } + + } +} diff --git a/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/tree/GTreeSlowLoadingNodeTest.java b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/tree/GTreeSlowLoadingNodeTest.java new file mode 100644 index 0000000000..23f1ee6c1c --- /dev/null +++ b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/tree/GTreeSlowLoadingNodeTest.java @@ -0,0 +1,309 @@ +/* ### + * 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 docking.widgets.tree; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.List; + +import javax.swing.*; + +import org.junit.*; + +import docking.test.AbstractDockingTest; +import docking.widgets.OptionDialog; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +public class GTreeSlowLoadingNodeTest extends AbstractDockingTest { + + private static final int MAX_DEPTH = 4; + private static final int MIN_CHILD_COUNT = 3; + private static final int MAX_CHILD_COUNT = 40; + + private volatile boolean pauseChildLoading = false; + + private JFrame frame; + private GTree gTree; + + @Before + public void setUp() throws Exception { + + gTree = new GTree(new EmptyRootNode()); + + frame = new JFrame("GTree Test"); + frame.getContentPane().add(gTree); + frame.setSize(400, 400); + frame.setVisible(true); + + waitForTree(); + } + + @After + public void tearDown() throws Exception { + gTree.dispose(); + frame.dispose(); + } + + @Test + public void testBasicLoading() { + gTree.setRootNode(new TestRootNode(100)); + + waitForTree(); + + // make sure we have some children + GTreeRootNode rootNode = gTree.getRootNode(); + GTreeNode nonLeaf1 = rootNode.getChild(0); + assertNotNull(nonLeaf1); + GTreeNode leaf1 = rootNode.getChild(1); + assertNotNull(leaf1); + GTreeNode nonLeaf2 = rootNode.getChild(2); + assertNotNull(nonLeaf2); + + int childCount = nonLeaf1.getChildCount(); + assertTrue("Did not find children for: " + nonLeaf1, childCount > 1); + assertEquals("An expected leaf node has some children", 0, leaf1.getChildCount()); + childCount = nonLeaf2.getChildCount(); + assertTrue("Did not find children for: " + nonLeaf2, childCount > 1); + } + + @Test + public void testSlowNodeShowsProgressBar() { + gTree.setRootNode(new TestRootNode(5000)); + + waitForTree(); + + GTreeRootNode rootNode = gTree.getRootNode(); + GTreeNode nonLeaf1 = rootNode.getChild(0); + assertNotNull(nonLeaf1); + + gTree.expandPath(nonLeaf1); + + assertProgressPanel(true); + + assertTrue(nonLeaf1.isInProgress()); + + // Press the cancel button on the progress monitor + pressProgressPanelCancelButton(); + + waitForTree(); + + // Verify no progress component + assertProgressPanel(false); + } + +//================================================================================================== +// Private Methods +//================================================================================================== + + private void waitForTree() { + waitForTree(gTree); + } + + private void assertProgressPanel(boolean isShowing) { + JComponent panel = (JComponent) getInstanceField("progressPanel", gTree); + if (!isShowing) { + assertNull("Panel is showing when it should not be", panel); + return; + } + + if (panel == null || !panel.isShowing()) { + int maxWaits = 50;// wait a couple seconds, as the progress bar may be delayed + int tryCount = 0; + while (tryCount < maxWaits) { + panel = (JComponent) getInstanceField("progressPanel", gTree); + if (panel != null && panel.isShowing()) { + return;// finally showing! + } + tryCount++; + try { + Thread.sleep(50); + } + catch (Exception e) { + // who cares? + } + } + } + + Assert.fail("Progress panel is not showing as expected"); + } + + private void pressProgressPanelCancelButton() { + Object taskMonitorComponent = getInstanceField("monitor", gTree); + final JButton cancelButton = + (JButton) getInstanceField("cancelButton", taskMonitorComponent); + runSwing(() -> cancelButton.doClick(), false); + + OptionDialog confirDialog = waitForDialogComponent(OptionDialog.class); + final JButton confirmCancelButton = findButtonByText(confirDialog, "Yes"); + runSwing(() -> confirmCancelButton.doClick()); + } + +//================================================================================================== +// Inner Classes +//================================================================================================== + + private class EmptyRootNode extends AbstractGTreeRootNode { + + EmptyRootNode() { + setChildren(new ArrayList()); + } + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return "Empty Test GTree Root Node"; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return true; + } + } + + private class TestRootNode extends AbstractGTreeRootNode { + + TestRootNode(int loadDelayMillis) { + List children = new ArrayList<>(); + children.add(new TestSlowLoadingNode(loadDelayMillis, 1)); + children.add(new TestLeafNode()); + children.add(new TestSlowLoadingNode(loadDelayMillis, 1)); + setChildren(children); + } + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return "Test GTree Root Node"; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return false; + } + } + + private class TestSlowLoadingNode extends GTreeSlowLoadingNode { + + private final long loadDelayMillis; + private final int depth; + + TestSlowLoadingNode(long loadDelayMillis, int depth) { + this.loadDelayMillis = loadDelayMillis; + this.depth = depth; + } + + @Override + public List generateChildren(TaskMonitor monitor) throws CancelledException { + + if (depth > MAX_DEPTH) { + return new ArrayList<>(); + } + + if (monitor.isCancelled()) { + return new ArrayList<>(); + } + + sleep(loadDelayMillis); + + while (pauseChildLoading) { + sleep(100); + } + + int childCount = getRandomInt(MIN_CHILD_COUNT, MAX_CHILD_COUNT); + List children = new ArrayList<>(); + for (int i = 0; i < childCount; i++) { + if (monitor.isCancelled()) { + return new ArrayList<>(); + } + int value = getRandomInt(0, 1); + if (value == 0) { + children.add(new TestSlowLoadingNode(loadDelayMillis, depth + 1)); + } + else { + children.add(new TestLeafNode()); + } + } + return children; + } + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return getClass().getSimpleName(); + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return false; + } + + } + + private class TestLeafNode extends AbstractGTreeNode { + + private String name = getClass().getSimpleName() + getRandomString(); + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return true; + } + + } +} diff --git a/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/tree/GTreeTest.java b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/tree/GTreeTest.java new file mode 100644 index 0000000000..895effedce --- /dev/null +++ b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/tree/GTreeTest.java @@ -0,0 +1,1273 @@ +/* ### + * 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 docking.widgets.tree; + +import static org.junit.Assert.*; + +import java.awt.Component; +import java.awt.Rectangle; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.*; +import javax.swing.tree.TreePath; + +import org.junit.*; + +import docking.test.AbstractDockingTest; +import docking.widgets.OptionDialog; +import docking.widgets.filter.FilterOptions; +import docking.widgets.filter.TextFilterStrategy; +import docking.widgets.tree.support.GTreeFilter; + +public class GTreeTest extends AbstractDockingTest { + + private JFrame frame; + private GTree gTree; + private int nodeIdCounter = 1; + + /** + * Test variable to easily control the filtering of test filters. + */ + private volatile boolean filterEnabled = true; + + @Before + public void setUp() throws Exception { + gTree = new GTree(new TestRootNode()); + + frame = new JFrame("GTree Test"); + frame.getContentPane().add(gTree); + frame.setSize(400, 400); + frame.setVisible(true); + + waitForTree(); + } + + @After + public void tearDown() throws Exception { + gTree.dispose(); + frame.dispose(); + } + + @Test + public void testFilterFromKeyStroke() { + GTreeNode node = findNodeInTree("Leaf Child - Many B1"); + assertNotNull("Did not find existing child node in non filtered tree", node); + + GTreeNode node2 = findNodeInTree("Leaf Child - Single B1"); + assertNotNull("Did not find existing child node in non filtered tree", node2); + + typeFilterText("Many B1"); + node = findNodeInTree("Leaf Child - Many B1"); + assertNotNull("Did not find existing child node in filtered tree", node); + + node2 = findNodeInTree("Leaf Child - Single B1"); + assertNull("Found a node in the tree that should have been filtered out", node2); + + clearFilterText(); + + findNodeInTree("Leaf Child - Many B1"); + assertNotNull("Did not find existing child node in non filtered tree", node); + + node2 = findNodeInTree("Leaf Child - Single B1"); + assertNotNull("Did not find existing child node in non filtered tree", node2); + + typeFilterText("Zoolander"); + node = findNodeInTree("Leaf Child - Many B1"); + assertNull("Found a node in the tree that should have been filtered out", node); + } + + @Test + public void testFilterFromSetFilterText() { + GTreeNode node = findNodeInTree("Leaf Child - Many B1"); + assertNotNull("Did not find existing child node in non filtered tree", node); + + GTreeNode node2 = findNodeInTree("Leaf Child - Single B1"); + assertNotNull("Did not find existing child node in non filtered tree", node2); + + setFilterText("Many B1"); + node = findNodeInTree("Leaf Child - Many B1"); + assertNotNull("Did not find existing child node in filtered tree", node); + + node2 = findNodeInTree("Leaf Child - Single B1"); + assertNull("Found a node in the tree that should have been filtered out", node2); + + clearFilterText(); + + findNodeInTree("Leaf Child - Many B1"); + assertNotNull("Did not find existing child node in non filtered tree", node); + + node2 = findNodeInTree("Leaf Child - Single B1"); + assertNotNull("Did not find existing child node in non filtered tree", node2); + + setFilterText("Zoolander"); + node = findNodeInTree("Leaf Child - Many B1"); + assertNull("Found a node in the tree that should have been filtered out", node); + } + + @Test + public void testRefilter() { + final GTreeFilterProvider provider = new DefaultGTreeFilterProvider(gTree) { + @Override + public GTreeFilter getFilter() { + if (filterEnabled) { + return super.getFilter(); + } + return new DisabledGTreeFilter(); + } + }; + // Set the filter provider + runSwing(() -> gTree.setFilterProvider(provider)); + + setFilterOptions(TextFilterStrategy.CONTAINS, false); + // + // Some basic filter tests + // + GTreeNode node = findNodeInTree("Leaf Child - Many B1"); + assertNotNull("Did not find existing child node in non filtered tree", node); + + GTreeNode node2 = findNodeInTree("Leaf Child - Single B1"); + assertNotNull("Did not find existing child node in non filtered tree", node2); + + setFilterText("Many B1"); + node = findNodeInTree("Leaf Child - Many B1"); + assertNotNull("Did not find existing child node in filtered tree", node); + + node2 = findNodeInTree("Leaf Child - Single B1"); + assertNull("Found a node in the tree that should have been filtered out", node2); + + clearFilterText(); + + findNodeInTree("Leaf Child - Many B1"); + assertNotNull("Did not find existing child node in non filtered tree", node); + + setFilterText("Zoolander"); + node = findNodeInTree("Leaf Child - Many B1"); + assertNull("Found a node in the tree that should have been filtered out", node); + + // + // Now try filtering and re-filtering + // + setFilterText("Many B1"); + node = findNodeInTree("Leaf Child - Many B1"); + assertNotNull("Did not find existing child node in filtered tree", node); + + node2 = findNodeInTree("Leaf Child - Single B1"); + assertNull("Found a node in the tree that should have been filtered out", node2); + + gTree.refilter(); + waitForTree(); + node = findNodeInTree("Leaf Child - Many B1"); + assertNotNull("Did not find existing child node in filtered tree", node); + + node2 = findNodeInTree("Leaf Child - Single B1"); + assertNull("Found a node in the tree that should have been filtered out", node2); + + filterEnabled = false; + gTree.refilter(); + waitForTree(); + node = findNodeInTree("Leaf Child - Many B1"); + assertNull("Found node when filter should not have matched after a refilter", node); + + node2 = findNodeInTree("Leaf Child - Single B1"); + assertNull("Found a node in the tree that should have been filtered out", node2); + } + + @Test + public void testChangeFilterSettingsWithFilterTextInPlace() { + + GTreeNode node = findNodeInTree("Leaf Child - Many B1"); + assertNotNull("Did not find existing child node in non filtered tree", node); + + GTreeNode node2 = findNodeInTree("Leaf Child - Single B1"); + assertNotNull("Did not find existing child node in non filtered tree", node2); + + typeFilterText("Many B1"); + node = findNodeInTree("Leaf Child - Many B1"); + assertNotNull("Did not find existing child node in filtered tree", node); + + setFilterOptions(TextFilterStrategy.CONTAINS, false); + + node = findNodeInTree("Leaf Child - Many B1"); + assertNotNull("Did not find existing child node in filtered tree", node); + + node2 = findNodeInTree("Leaf Child - Single B1"); + assertNull("Found a node in the tree that should have been filtered out", node2); + } + + @Test + public void testSelectionReturnsAfterFiltering() { + // + // Test that a selection made before a filter is still there after a filter + // + GTreeNode node = findNodeInTree("Leaf Child - Many B1"); + assertNotNull("Did not find existing child node in non filtered tree", node); + + gTree.setSelectedNode(node); + waitForTree(); + + TreePath selectionPath = gTree.getSelectionPath(); + assertNotNull("Tree did not select node", selectionPath); + assertEquals(node, selectionPath.getLastPathComponent()); + + setFilterText("No match text"); + node = findNodeInTree("Leaf Child - Many B1"); + assertNull("Found node that should have not passed filter", node); + + selectionPath = gTree.getSelectionPath(); + assertNull("Selection remained in tree after filter", selectionPath); + + clearFilterText(); + + selectionPath = gTree.getSelectionPath(); + assertNotNull("Tree did not select node", selectionPath); + node = findNodeInTree("Leaf Child - Many B1"); + assertEquals(node, selectionPath.getLastPathComponent()); + } + + @Test + public void testSelectionDuringFilterReturnsAfterFilter() { + // + // Test that a selection made during a filter returns after the filter is removed. + // + setFilterText("Many B1"); + GTreeNode node = findNodeInTree("Leaf Child - Many B1"); + assertNotNull("Did not find existing child node in filtered tree", node); + + gTree.setSelectedNode(node); + waitForTree(); + TreePath selectionPath = gTree.getSelectionPath(); + assertNotNull("Tree did not select node", selectionPath); + assertEquals(node, selectionPath.getLastPathComponent()); + + clearFilterText(); + + selectionPath = gTree.getSelectionPath(); + assertNotNull("Tree did not select node", selectionPath); + assertEquals(node, selectionPath.getLastPathComponent()); + + // + // Same test, but with a selection that existed before the filter's selection change + // + gTree.clearSelectionPaths(); + waitForTree(); + + gTree.setSelectedNode(node); + waitForTree(); + + selectionPath = gTree.getSelectionPath(); + assertNotNull("Tree did not select node", selectionPath); + assertEquals(node, selectionPath.getLastPathComponent()); + + setFilterText("ChildrenNodeA");// filter to show the node below + + GTreeNode anotherNode = + findNodeInTree(NonLeafWithOneLevelOfChildrenNodeA.class.getSimpleName()); + assertNotNull("Could not find expected node", anotherNode); + + gTree.setSelectedNode(anotherNode); + waitForTree(); + + selectionPath = gTree.getSelectionPath(); + assertNotNull("Tree did not select node", selectionPath); + assertEquals(anotherNode, selectionPath.getLastPathComponent()); + + clearFilterText(); + + selectionPath = gTree.getSelectionPath(); + assertNotNull("Tree did not select node", selectionPath); + assertEquals(anotherNode, selectionPath.getLastPathComponent()); + } + + @Test + public void testProgrammaticSelectAndFilter() { + // + // Open a node and select it. Make sure it is again selected if it passes the filter. + // + GTreeNode node = findNodeInTree("Leaf Child - Many B1"); + assertNotNull("Did not find existing child node in non filtered tree", node); + + gTree.setSelectedNode(node); + waitForTree(); + + TreePath selectionPath = gTree.getSelectionPath(); + assertNotNull("Tree did not select node", selectionPath); + assertEquals(node, selectionPath.getLastPathComponent()); + + setFilterText("Many B1"); + node = findNodeInTree("Leaf Child - Many B1"); + assertNotNull("Did not find existing child node in filtered tree", node); + + selectionPath = gTree.getSelectionPath(); + assertNotNull("Node matching filter should still be selected after filter", selectionPath); + assertEquals(node, selectionPath.getLastPathComponent()); + + clearFilterText(); + + selectionPath = gTree.getSelectionPath(); + assertNotNull("Tree did not select node", selectionPath); + node = findNodeInTree("Leaf Child - Many B1"); + assertEquals(node, selectionPath.getLastPathComponent()); + } + + @Test + public void testSetSelectedPaths() { + TreePath selectionPath = gTree.getSelectionPath(); + assertNull("Unexpectedly have a selected path by default", selectionPath); + + GTreeNode node = findNodeInTree("Leaf Child - Many B1"); + assertNotNull("Did not find existing child node in non filtered tree", node); + + gTree.setSelectedNode(node); + waitForTree(); + + selectionPath = gTree.getSelectionPath(); + assertNotNull("Tree did not select node", selectionPath); + assertEquals(node, selectionPath.getLastPathComponent()); + + gTree.clearSelectionPaths(); + waitForTree(); + + selectionPath = gTree.getSelectionPath(); + assertNull("Selection paths not cleared", selectionPath); + + // + // now try calling select node while another node is already selected + // + GTreeNode anotherNode = + findNodeInTree(NonLeafWithOneLevelOfChildrenNodeA.class.getSimpleName()); + assertNotNull("Could not find expected node", anotherNode); + + gTree.setSelectedNode(node); + waitForTree(); + + selectionPath = gTree.getSelectionPath(); + assertNotNull("Tree did not select node", selectionPath); + assertEquals(node, selectionPath.getLastPathComponent()); + + gTree.setSelectedNode(anotherNode); + waitForTree(); + + selectionPath = gTree.getSelectionPath(); + assertNotNull("Tree did not select node", selectionPath); + assertEquals(anotherNode, selectionPath.getLastPathComponent()); + + // + // try multiple paths + // + List pathsToSelect = new ArrayList<>(); + pathsToSelect.add(node.getTreePath()); + pathsToSelect.add(anotherNode.getTreePath()); + + gTree.setSelectionPaths(pathsToSelect); + waitForTree(); + + TreePath[] paths = gTree.getSelectionPaths(); + assertEquals("Found non-matching selected path count afterr calling setSelectionPaths", + pathsToSelect.size(), paths.length); + for (int i = 0; i < paths.length; i++) { + assertEquals("Unexpected path selection", pathsToSelect.get(i), paths[i]); + } + } + + @Test + public void testExpandPath() { + List expandedPaths = gTree.getExpandedPaths(gTree.getRootNode()); + assertEquals("Unexpectedly have an expanded tree by default", 1, expandedPaths.size()); + assertEquals(gTree.getRootNode(), expandedPaths.get(0).getLastPathComponent()); + + GTreeNode node = findNodeInTree("Leaf Child - Many B1"); + assertNotNull("Did not find existing child node in non filtered tree", node); + + gTree.expandPath(node); + waitForTree(); + + expandedPaths = gTree.getExpandedPaths(gTree.getRootNode()); + assertTrue("Did not get expanded paths after calling expand paths", + expandedPaths.size() > 0); + assertExpaned(expandedPaths, node.getParent()); + } + + @Test + public void testPreventRootCollapseExpandsRoot() { + gTree.collapseAll(gTree.getRootNode()); + waitForTree(); + + gTree.setRootNodeAllowedToCollapse(false); + waitForTree(); + + List expanded = gTree.getExpandedPaths(gTree.getRootNode()); + + assertEquals("Should have only one expanded path", 1, expanded.size()); + TreePath path0 = expanded.get(0); + + assertEquals(path0.getLastPathComponent(), gTree.getRootNode()); + assertEquals("Expanded path does not end at tree root", path0.getLastPathComponent(), + gTree.getRootNode()); + } + + @Test + public void testPreventRootCollapseAttemptsRootCollapse() { + gTree.setRootNodeAllowedToCollapse(false); + + gTree.expandAll(); + waitForTree(); + + gTree.collapseAll(gTree.getRootNode()); + waitForTree(); + + List expanded = gTree.getExpandedPaths(gTree.getRootNode()); + + assertEquals("Should have only one expanded path", 1, expanded.size()); + TreePath path0 = expanded.get(0); + + assertEquals("Expanded path does not end at tree root", path0.getLastPathComponent(), + gTree.getRootNode()); + } + + @Test + public void testCancelFilter() { + // + // Setup a long running filter and make sure that the progress bar is show. Then, cancel + // that filter and make sure that the filter is cleared and the progress bar is hidden. + // + + final ReallySlowGTreeFilter filter = new ReallySlowGTreeFilter(); + final GTreeFilterProvider provider = new DefaultGTreeFilterProvider(gTree) { + @Override + public GTreeFilter getFilter() { + GTreeFilter superFilter = super.getFilter(); + if (superFilter == null) { + return null; + } + return filter; + } + }; + provider.setFilterText("Hey"); + // Set the filter (Note: don't use setTextFilterFactory(), as it waits for the tree, + // which is not what this test wants to do) + runSwing(() -> gTree.setFilterProvider(provider)); + + // Wait for the tree to be 'busy' + waitForTreeToStartWork(); + + // Verify the progress monitor is showing + assertProgressPanel(true); + + // Press the cancel button on the progress monitor + pressProgressPanelCancelButton(); + + waitForTree(); + + // Verify no filter + String filterText = gTree.getFilterText(); + assertEquals("", filterText); + + // Verify no progress component + assertProgressPanel(false); + } + + @Test + public void testGetAndRestoreTreeState() { + // + // Test that we can setup the tree, record its state, change the tree and then restore + // the saved state + // + TreePath selectionPath = gTree.getSelectionPath(); + assertNull("Unexpectedly have a selected path by default", selectionPath); + + GTreeNode originalNode = findNodeInTree("Leaf Child - Many B1"); + assertNotNull("Did not find existing child node in non filtered tree", originalNode); + + gTree.setSelectedNode(originalNode); + waitForTree(); + + selectionPath = gTree.getSelectionPath(); + assertNotNull("Tree did not select node", selectionPath); + assertEquals(originalNode, selectionPath.getLastPathComponent()); + + GTreeState treeState = gTree.getTreeState(); + + gTree.clearSelectionPaths(); + waitForTree(); + + selectionPath = gTree.getSelectionPath(); + assertNull("Selection paths not cleared", selectionPath); + + GTreeNode anotherNode = + findNodeInTree(NonLeafWithOneLevelOfChildrenNodeA.class.getSimpleName()); + assertNotNull("Could not find expected node", anotherNode); + + gTree.setSelectedNode(anotherNode); + waitForTree(); + + selectionPath = gTree.getSelectionPath(); + assertNotNull("Tree did not select node", selectionPath); + assertEquals(anotherNode, selectionPath.getLastPathComponent()); + + gTree.restoreTreeState(treeState); + waitForTree(); + + selectionPath = gTree.getSelectionPath(); + assertNotNull("Tree did not select node", selectionPath); + assertEquals(originalNode, selectionPath.getLastPathComponent()); + } + + @Test + public void testFilterPathsRestoredWithFurtherFiltering_NoSelection() throws Exception { + + installLargeTreeModel(); + + // this filter allows all children to stay in the view + setFilterText("Leaf"); + + TreePath firstVisiblePath = scrollTo(50); + + setFilterText("Leaf Child"); + + TreePath newFirstVisiblePath = getLastVisiblePath(); + assertCloseEnough(firstVisiblePath, newFirstVisiblePath); + } + + @Test + public void testFilterPathsRestoredWithFurtherFiltering_HaveSelection() throws Exception { + + // + // Test to verify that the selection restoration takes precedence over the 'visible + // view' restoration. + // + + installLargeTreeModel(); + + // this filter allows all children to stay in the view + setFilterText("Leaf"); + + selectRow(5); + + TreePath expected = getLastVisiblePath(); + + scrollTo(50); + + setFilterText("Leaf Child"); + + TreePath newFirstVisiblePath = getLastVisiblePath(); + assertCloseEnough(expected, newFirstVisiblePath); + } + + @Test + public void testFilterPathsRestored_WhenTooManyItemsExpanded_NoSelection() { + + // + // Tests the algorithm for throwing away expanded paths when there are too many to + // restore. + // + + installLargeTreeModel_WithManyExpandablePaths(); + + // generate more than 'max' expanded states to force the algorithm to toss some states + expandAll(); + + scrollTo(50); + + TreePath expected = getLastVisiblePath(); + + // keep all items in filter; move the view to the top + setFilterText("a"); + + // verify that the view contains the expanded path + TreePath newFirstVisiblePath = getLastVisiblePath(); + assertCloseEnough(expected, newFirstVisiblePath); + } + + @Test + public void testFilterPathsRestored_WhenTooManyItemsExpanded_HaveSelection() { + + // + // Tests the algorithm for throwing away expanded paths when there are too many to + // restore. + // + + installLargeTreeModel_WithManyExpandablePaths(); + + // generate more than 'max' expanded and selected states to force the algorithm + // to toss some states + expandAll(); + int topRow = 50; + + scrollTo(topRow); + selectRow(topRow); + + // keep all items in filter; move the view to the top + setFilterText("a"); + + // verify that the view contains the selected path + TreePath selectedPath = getSelectedPath(); + assertCloseEnough(rowToPath(topRow), selectedPath); + } + + @Test + public void testFilterPathsRestored_WhenTooManyItemsExpanded_HaveTooManyItemsSelected() { + + // + // Tests the algorithm for throwing away expanded paths when there are too many to + // restore. + // + + installLargeTreeModel_WithManyExpandablePaths(); + + // generate more than 'max' expanded and selected states to force the algorithm + // to toss some states + expandAll(); + int topRow = 50; + + scrollTo(topRow); + selectContiguousRows(0, topRow + 5); + + // keep all items in filter; move the view to the top + setFilterText("a"); + + // verify that the view contains the selected path + TreePath selectedPath = getSelectedPath(); + assertCloseEnough(rowToPath(topRow), selectedPath); + } + +//================================================================================================== +// Private methods +//================================================================================================== + + private void installLargeTreeModel() { + runSwing(() -> frame.getContentPane().remove(gTree)); + gTree = new TestGTree(new ManyLeafChildrenRootNode()); + runSwing(() -> frame.getContentPane().add(gTree)); + + frame.validate(); + } + + private void installLargeTreeModel_WithManyExpandablePaths() { + runSwing(() -> frame.getContentPane().remove(gTree)); + gTree = new TestGTree(new ManyNonLeafChildrenRootNode()); + runSwing(() -> frame.getContentPane().add(gTree)); + + frame.validate(); + } + + private void expandAll() { + gTree.expandAll(); + waitForTree(); + } + + private void selectRow(int row) { + TreePath path = rowToPath(row); + gTree.setSelectedNodeByPathName(path); + } + + private void selectContiguousRows(int from, int to) { + + // hack: process the list backwards so that the 'to' row is what ends up in the view--the + // tree will scroll to the first selected element + List paths = new ArrayList<>(); + for (int row = to; row >= from; row--) { + paths.add(rowToPath(row)); + } + + gTree.setSelectionPaths(paths); + waitForTree(); + } + + private TreePath rowToPath(int row) { + TreePath path = gTree.getPathForRow(row); + return path; + } + + private void assertExpaned(List expandedPaths, GTreeNode expected) { + + for (TreePath path : expandedPaths) { + GTreeNode node = (GTreeNode) path.getLastPathComponent(); + if (expected.equals(node)) { + return; + } + } + + fail("Node not expaded: " + expected + "; expanded paths: " + expandedPaths); + } + + /** Verifies the actual is within one of the expected */ + private void assertCloseEnough(TreePath expected, TreePath actual) { + + if (actual == null) { + fail("No actual TreePath found"); + } + + if (expected.equals(actual)) { + return; // good + } + + // Unusual Code: we add some 'fudge' to the viewable tree rows (1 in the top and + // bottom direction). So, the actual path visible may be off by that + // much. + int fudge = 2; // 1 in both directions + int expectedRow = gTree.getRowForPath(expected); + if (rowNumberIsWithin(expected, actual, fudge)) { + return; // good enough + } + + int actualRow = gTree.getRowForPath(actual); + fail("Tree paths do not match - expected row " + expectedRow + "; actual row " + actualRow); + } + + private boolean rowNumberIsWithin(TreePath expected, TreePath actual, int fudge) { + + //int expectedRow = gTree.getRowForPath(expected); + int actualRow = gTree.getRowForPath(actual); + + // first, go up + for (int offset = 0; offset > fudge; offset--) { + int previousRow = actualRow - offset; + TreePath previousPath = gTree.getPathForRow(previousRow); + if (actual.equals(previousPath)) { + return true; // good enough + } + } + + // next, go down + for (int offset = 0; offset < fudge; offset++) { + int previousRow = actualRow + offset; + TreePath nextPath = gTree.getPathForRow(previousRow); + if (actual.equals(nextPath)) { + return true; // good enough + } + } + + return false; + } + + private TreePath scrollTo(int row) { + runSwing(() -> { + TreePath path = gTree.getPathForRow(row); + gTree.scrollPathToVisible(path); + }); + + TreePath path = getLastVisiblePath(); + return path; + } + + private TreePath getLastVisiblePath() { + Rectangle r = gTree.getViewRect(); + + JTree jTree = gTree.getJTree(); + int start = jTree.getClosestRowForLocation(r.x, r.y + r.height); + TreePath path = gTree.getPathForRow(start); + return path; + } + + @Override + protected void testFailed(Throwable e) { + StringBuilder buffy = new StringBuilder(); + printTree(gTree.getRootNode(), 0, buffy); + System.err.println("GTree state: "); + System.err.println(buffy.toString()); + } + + private void printTree(GTreeNode node, int indentLevel, StringBuilder buffy) { + buffy.append('\n'); + for (int i = 0; i < indentLevel; i++) { + buffy.append('\t'); + } + buffy.append("node: " + node.getName()); + int nextIndentLevel = indentLevel + 1; + List children = node.getChildren(); + for (GTreeNode child : children) { + printTree(child, nextIndentLevel, buffy); + } + } + + private void assertProgressPanel(boolean isShowing) { + JComponent panel = (JComponent) getInstanceField("progressPanel", gTree); + if (!isShowing) { + assertNull("Panel is showing when it should not be", panel); + return; + } + + if (panel == null || !panel.isShowing()) { + int maxWaits = 50;// wait a couple seconds, as the progress bar may be delayed + int tryCount = 0; + while (tryCount < maxWaits) { + panel = (JComponent) getInstanceField("progressPanel", gTree); + if (panel != null && panel.isShowing()) { + return;// finally showing! + } + tryCount++; + try { + Thread.sleep(50); + } + catch (Exception e) { + // who cares? + } + } + } + + Assert.fail("Progress panel is not showing as expected"); + } + + private void pressProgressPanelCancelButton() { + Object taskMonitorComponent = getInstanceField("monitor", gTree); + final JButton cancelButton = + (JButton) getInstanceField("cancelButton", taskMonitorComponent); + runSwing(() -> cancelButton.doClick(), false); + + OptionDialog confirDialog = waitForDialogComponent(OptionDialog.class); + JButton confirmCancelButton = findButtonByText(confirDialog, "Yes"); + runSwing(() -> confirmCancelButton.doClick()); + } + + private void waitForTree() { + waitForTree(gTree); + } + + private void waitForTreeToStartWork() { + + waitForCondition(() -> gTree.isBusy(), + "Tree did not start filtering task or finished too soon"); + } + + private void typeFilterText(String text) { + Component filterField = gTree.getFilterField(); + JTextField textField = (JTextField) getInstanceField("textField", filterField); + triggerText(textField, text); + waitForTree(); + } + + private void setFilterText(final String text) { + runSwing(() -> gTree.setFilterText(text)); + waitForTree(); + } + + private void clearFilterText() { + setFilterText(""); + } + + private TreePath getSelectedPath() { + waitForTree(); + return gTree.getSelectionPath(); + } + + private GTreeNode findNodeInTree(String name) { + GTreeRootNode rootNode = gTree.getRootNode(); + return findNodeInTree(rootNode, name); + } + + private GTreeNode findNodeInTree(GTreeNode node, String name) { + if (node.getName().equals(name)) { + return node; + } + + List children = node.getChildren(); + for (GTreeNode child : children) { + if (child.getName().startsWith(name)) { + return child; + } + + GTreeNode grandChild = findNodeInTree(child, name); + if (grandChild != null) { + return grandChild; + } + } + + return null; + } + + private void setFilterOptions(final TextFilterStrategy filterStrategy, final boolean inverted) { + runSwing(() -> { + FilterOptions filterOptions = new FilterOptions(filterStrategy, false, false, inverted); + ((DefaultGTreeFilterProvider) gTree.getFilterProvider()).setFilterOptions( + filterOptions); + }); + waitForTree(); + + } + +//================================================================================================== +// Inner Classes +//================================================================================================== + + private class TestGTree extends GTree { + + public TestGTree(GTreeRootNode root) { + super(root); + } + + @Override + public GTreeState getTreeState() { + return new GTreeState(this) { + @Override + int getMaxItemCount() { + return 5; // smaller number for testing + } + }; + } + + @Override + public GTreeState getTreeState(GTreeNode node) { + return new GTreeState(this, node) { + @Override + int getMaxItemCount() { + return 5; // smaller number for testing + } + }; + } + } + + private class TestRootNode extends AbstractGTreeRootNode { + + TestRootNode() { + List children = new ArrayList<>(); + children.add(new NonLeafWithOneLevelOfChildrenNodeA()); + children.add(new LeafNode("Leaf Child - Root1")); + children.add(new NonLeafWithManyLevelOfChildrenNodeA()); + setChildren(children); + } + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return "Test GTree Root Node"; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return false; + } + } + + private class ManyLeafChildrenRootNode extends AbstractGTreeRootNode { + + ManyLeafChildrenRootNode() { + List children = new ArrayList<>(); + children.add(new NonLeafWithMoreChildrenThanFitInTheView(true)); + setChildren(children); + } + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return "Test GTree Root Node"; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return false; + } + } + + private class ManyNonLeafChildrenRootNode extends AbstractGTreeRootNode { + + ManyNonLeafChildrenRootNode() { + List children = new ArrayList<>(); + children.add(new NonLeafWithMoreChildrenThanFitInTheView(false)); + setChildren(children); + } + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return "Test GTree Root Node"; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return false; + } + } + + /** + * A basic node with some children. + */ + private class NonLeafWithOneLevelOfChildrenNodeA extends AbstractGTreeNode { + + private String name = Integer.toString(++nodeIdCounter); + + NonLeafWithOneLevelOfChildrenNodeA() { + List children = new ArrayList<>(); + setChildren(children); + + } + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return getClass().getSimpleName() + " (" + name + ")"; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return false; + } + } + + /** + * A basic node with some children. + */ + private class NonLeafWithOneLevelOfChildrenNodeB extends AbstractGTreeNode { + private String name = Integer.toString(++nodeIdCounter); + + NonLeafWithOneLevelOfChildrenNodeB() { + this(1); + } + + NonLeafWithOneLevelOfChildrenNodeB(int n) { + List children = new ArrayList<>(); + name = Integer.toString(n); + children.add(new LeafNode("Leaf Child - Single B" + n)); + setChildren(children); + } + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return getClass().getSimpleName() + " (" + name + ")"; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return false; + } + } + + /** + * A basic leaf node + */ + private class LeafNode extends AbstractGTreeNode { + + private final String name; + + LeafNode(String name) { + this.name = name; + } + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return true; + } + } + + /** + * A node with children that may have children + */ + private class NonLeafWithManyLevelOfChildrenNodeA extends AbstractGTreeNode { + + private String name = Integer.toString(++nodeIdCounter); + + NonLeafWithManyLevelOfChildrenNodeA() { + List children = new ArrayList<>(); + children.add(new NonLeafWithManyLevelOfChildrenNodeB()); + setChildren(children); + } + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return getClass().getSimpleName() + " (" + name + ")"; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return false; + } + } + + /** + * A node with children that may have children + */ + private class NonLeafWithManyLevelOfChildrenNodeB extends AbstractGTreeNode { + + private String name = Integer.toString(++nodeIdCounter); + + NonLeafWithManyLevelOfChildrenNodeB() { + List children = new ArrayList<>(); + children.add(new NonLeafWithOneLevelOfChildrenNodeB()); + children.add(new LeafNode("Leaf Child - Many B1")); + setChildren(children); + } + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return getClass().getSimpleName() + " (" + name + ")"; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return false; + } + } + + /** + * A basic node with some children. + */ + private class NonLeafWithMoreChildrenThanFitInTheView extends AbstractGTreeNode { + + private String name = Integer.toString(++nodeIdCounter); + + NonLeafWithMoreChildrenThanFitInTheView(boolean childrenAreLeaves) { + List children = new ArrayList<>(); + + for (int i = 0; i < 100; i++) { + + if (childrenAreLeaves) { + children.add(new LeafNode("Leaf Child - " + i)); + } + else { + children.add(new NonLeafWithOneLevelOfChildrenNodeB(i)); + } + + } + + setChildren(children); + } + + @Override + public Icon getIcon(boolean expanded) { + return null; + } + + @Override + public String getName() { + return getClass().getSimpleName() + "(" + name + ")"; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return false; + } + } + + private class DisabledGTreeFilter implements GTreeFilter { + + @Override + public boolean acceptsNode(GTreeNode node) { + return false; + } + + @Override + public boolean showFilterMatches() { + return false; + } + + } + + private class ReallySlowGTreeFilter implements GTreeFilter { + + @Override + public boolean acceptsNode(GTreeNode node) { + if (!SwingUtilities.isEventDispatchThread()) { + // this filter is called by the worker thread AND the swing thread for rendering, + // make sure not to wait in the swing thread + sleep(2000); + } + return false; + } + + @Override + public boolean showFilterMatches() { + return false; + } + + } + +} diff --git a/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java b/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java index b209b92556..e525a19686 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java @@ -62,6 +62,7 @@ public abstract class AbstractGenericTest extends AbstractGTest { private static File debugDirectory; public static final String TESTDATA_DIRECTORY_NAME = "testdata"; + public static final String DEFAULT_TOOL_NAME = "CodeBrowser"; public static final String DEFAULT_TEST_TOOL_NAME = "TestCodeBrowser"; private static boolean initialized = false; diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndTool.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndTool.java index 7c67117b8b..f507be5de8 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndTool.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndTool.java @@ -40,6 +40,7 @@ import docking.help.Help; import docking.help.HelpService; import docking.tool.ToolConstants; import docking.util.AnimationUtils; +import docking.util.image.ToolIconURL; import docking.widgets.OptionDialog; import generic.jar.ResourceFile; import generic.util.WindowUtilities; diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ToolButton.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ToolButton.java index 7c561932aa..5fbae30216 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ToolButton.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ToolButton.java @@ -32,11 +32,11 @@ import docking.dnd.*; import docking.help.Help; import docking.help.HelpService; import docking.tool.ToolConstants; +import docking.util.image.ToolIconURL; import docking.widgets.EmptyBorderButton; import ghidra.framework.main.datatree.*; import ghidra.framework.model.*; import ghidra.framework.plugintool.PluginTool; -import ghidra.framework.project.tool.ToolIconURL; import ghidra.util.*; import ghidra.util.bean.GGlassPane; import ghidra.util.exception.AssertException; diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/Tool.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/Tool.java index 823ab8141d..864e2ebd6d 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/Tool.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/Tool.java @@ -21,8 +21,8 @@ import java.beans.PropertyVetoException; import org.jdom.Element; import docking.DockingTool; +import docking.util.image.ToolIconURL; import ghidra.framework.plugintool.PluginEvent; -import ghidra.framework.project.tool.ToolIconURL; /** * diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/ToolTemplate.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/ToolTemplate.java index 815151c575..b211aec18c 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/ToolTemplate.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/ToolTemplate.java @@ -19,7 +19,7 @@ import javax.swing.ImageIcon; import org.jdom.Element; -import ghidra.framework.project.tool.ToolIconURL; +import docking.util.image.ToolIconURL; /** * Configuration of a tool that knows how to create tools. diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java index 0abe43fc2f..91d89fdaa9 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java @@ -42,6 +42,7 @@ import docking.help.Help; import docking.help.HelpService; import docking.tool.ToolConstants; import docking.tool.util.DockingToolConstants; +import docking.util.image.ToolIconURL; import docking.widgets.OptionDialog; import ghidra.framework.OperatingSystem; import ghidra.framework.Platform; @@ -56,7 +57,6 @@ import ghidra.framework.plugintool.dialog.ManagePluginsDialog; import ghidra.framework.plugintool.mgr.*; import ghidra.framework.plugintool.util.*; import ghidra.framework.project.ProjectDataService; -import ghidra.framework.project.tool.ToolIconURL; import ghidra.util.*; import ghidra.util.task.Task; import ghidra.util.task.TaskLauncher; diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/IconMap.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/IconMap.java index cc7c8bd5ea..20497c60b6 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/IconMap.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/IconMap.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +15,9 @@ */ package ghidra.framework.plugintool.dialog; -import ghidra.framework.project.tool.ToolIconURL; - import java.util.*; +import docking.util.image.ToolIconURL; import resources.ResourceManager; /** diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/SaveToolConfigDialog.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/SaveToolConfigDialog.java index 0320960714..d7d0a626ec 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/SaveToolConfigDialog.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/SaveToolConfigDialog.java @@ -27,6 +27,7 @@ import javax.swing.event.*; import docking.DialogComponentProvider; import docking.options.editor.ButtonPanelFactory; +import docking.util.image.ToolIconURL; import docking.widgets.OptionDialog; import docking.widgets.filechooser.GhidraFileChooser; import docking.widgets.label.GLabel; @@ -34,7 +35,6 @@ import ghidra.framework.model.*; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.preferences.Preferences; import ghidra.framework.project.tool.GhidraToolTemplate; -import ghidra.framework.project.tool.ToolIconURL; import ghidra.util.HelpLocation; import ghidra.util.NamingUtilities; import ghidra.util.filechooser.ExtensionFileFilter; diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/ToolIconUrlRenderer.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/ToolIconUrlRenderer.java index 036a349485..b18ccdfd25 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/ToolIconUrlRenderer.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/ToolIconUrlRenderer.java @@ -22,8 +22,8 @@ import javax.swing.BorderFactory; import javax.swing.JList; import javax.swing.border.Border; +import docking.util.image.ToolIconURL; import docking.widgets.list.GListCellRenderer; -import ghidra.framework.project.tool.ToolIconURL; class ToolIconUrlRenderer extends GListCellRenderer { private Border emptyBorder = BorderFactory.createEmptyBorder(5, 5, 5, 5); diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraToolTemplate.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraToolTemplate.java index 47e80e0834..49beb9b2b1 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraToolTemplate.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraToolTemplate.java @@ -21,6 +21,7 @@ import javax.swing.ImageIcon; import org.jdom.Element; +import docking.util.image.ToolIconURL; import ghidra.framework.model.*; import ghidra.util.Msg; import ghidra.util.NumericUtilities; diff --git a/Ghidra/Features/Base/src/test/java/ghidra/test/DummyTool.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/test/DummyTool.java similarity index 98% rename from Ghidra/Features/Base/src/test/java/ghidra/test/DummyTool.java rename to Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/test/DummyTool.java index fb1076ca11..32c00a05ca 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/test/DummyTool.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/test/DummyTool.java @@ -30,10 +30,10 @@ import docking.*; import docking.action.DockingActionIf; import docking.actions.DockingToolActions; import docking.actions.PopupActionProvider; +import docking.util.image.ToolIconURL; import ghidra.framework.model.*; import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.PluginEvent; -import ghidra.framework.project.tool.ToolIconURL; import ghidra.program.model.listing.Program; public class DummyTool implements Tool { @@ -361,8 +361,7 @@ public class DummyTool implements Tool { @Override public void setMenuGroup(String[] menuPath, String group, String menuSubGroup) { - // TODO Auto-generated method stub - + //do nothing } @Override diff --git a/Ghidra/Features/Base/src/test/java/ghidra/test/DummyToolActions.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/test/DummyToolActions.java similarity index 100% rename from Ghidra/Features/Base/src/test/java/ghidra/test/DummyToolActions.java rename to Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/test/DummyToolActions.java diff --git a/Ghidra/Features/Base/src/test/java/ghidra/test/DummyToolTemplate.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/test/DummyToolTemplate.java similarity index 96% rename from Ghidra/Features/Base/src/test/java/ghidra/test/DummyToolTemplate.java rename to Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/test/DummyToolTemplate.java index bf25e9daf7..75612afaea 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/test/DummyToolTemplate.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/test/DummyToolTemplate.java @@ -19,8 +19,8 @@ import javax.swing.ImageIcon; import org.jdom.Element; +import docking.util.image.ToolIconURL; import ghidra.framework.model.*; -import ghidra.framework.project.tool.ToolIconURL; import ghidra.program.model.listing.Program; public class DummyToolTemplate implements ToolTemplate { diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/cmd/function/CreateFunctionThunkTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/cmd/function/CreateFunctionThunkTest.java new file mode 100644 index 0000000000..17f1774aff --- /dev/null +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/cmd/function/CreateFunctionThunkTest.java @@ -0,0 +1,131 @@ +/* ### + * 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.cmd.function; + +import static org.junit.Assert.*; + +import org.junit.*; + +import ghidra.app.plugin.core.analysis.AnalysisBackgroundCommand; +import ghidra.app.plugin.core.analysis.AutoAnalysisManager; +import ghidra.framework.cmd.Command; +import ghidra.framework.options.Options; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.database.ProgramBuilder; +import ghidra.program.model.data.DataType; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.test.AbstractGhidraHeadedIntegrationTest; +import ghidra.test.TestEnv; + +public class CreateFunctionThunkTest extends AbstractGhidraHeadedIntegrationTest { + + private TestEnv env; + private PluginTool tool; + + private Program program; + + private ProgramBuilder builder; + + @Before + public void setUp() throws Exception { + env = new TestEnv(); + tool = env.getTool(); + } + + @After + public void tearDown() { + if (program != null) { + env.release(program); + } + program = null; + env.dispose(); + } + + private void analyze() { + // turn off some analyzers + setAnalysisOptions("Stack"); + setAnalysisOptions("Embedded Media"); + setAnalysisOptions("DWARF"); + setAnalysisOptions("Create Address Tables"); + setAnalysisOptions("MIPS Constant Reference Analyzer"); + + AutoAnalysisManager analysisMgr = AutoAnalysisManager.getAnalysisManager(program); + analysisMgr.reAnalyzeAll(null); + + Command cmd = new AnalysisBackgroundCommand(analysisMgr, false); + tool.execute(cmd, program); + waitForBusyTool(tool); + } + + protected void setAnalysisOptions(String optionName) { + int txId = program.startTransaction("Analyze"); + Options analysisOptions = program.getOptions(Program.ANALYSIS_PROPERTIES); + analysisOptions.setBoolean(optionName, false); + program.endTransaction(txId, true); + } + + @Test + public void testDelaySlotThunk() throws Exception { + + builder = new ProgramBuilder("thunk", ProgramBuilder._MIPS); + + builder.setBytes("0x1000", "08 22 96 44 24 04 00 02 08 11 96 44 00 00 00 00"); + builder.disassemble("0x1000", 27, false); + builder.disassemble("0x1008", 27, false); + builder.createFunction("0x1000"); + builder.createFunction("0x1008"); + + builder.analyze(); + + program = builder.getProgram(); + + Function noThunk = program.getFunctionManager().getFunctionAt(builder.addr(0x1000)); + assertEquals(false, noThunk.isThunk()); + + Function isThunk = program.getFunctionManager().getFunctionAt(builder.addr(0x1008)); + assertEquals(true, isThunk.isThunk()); + } + + /** + * This tests the forcing of a function to be a thunk with CreateThunkFunctionCmd + * Tests that the Function start analyzer will create a thunk given the thunk tag on a matching function + * That the MIPS BE language has a thunking pattern. + * That the MIPS 64/32 hybrid with sign extension of registers still gets found as a thunk. + * That the thunking function can be found with out the constant reference analyzer + * + */ + @Test + public void testDelayMips6432SlotThunk() throws Exception { + + builder = new ProgramBuilder("thunk", ProgramBuilder._MIPS_6432); + + builder.setBytes("0x466050", "3c 0f 00 47 8d f9 72 24 03 20 00 08 25 f8 72 24"); + builder.setBytes("0x477224", "00 47 99 c0"); + builder.createEmptyFunction("chdir", "0x4799c0", 1, DataType.VOID); + builder.disassemble("0x466050", 27, true); + + builder.createFunction("0x466050"); + + program = builder.getProgram(); + + analyze(); + + Function isThunk = program.getFunctionManager().getFunctionAt(builder.addr(0x466050)); + assertEquals(true, isThunk.isThunk()); + assertEquals("chdir", isThunk.getName()); + } +} diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java new file mode 100644 index 0000000000..7fb6ca334f --- /dev/null +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java @@ -0,0 +1,1736 @@ +/* ### + * 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.clipboard; + +import static org.junit.Assert.*; + +import java.awt.*; +import java.awt.datatransfer.*; +import java.awt.event.FocusListener; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.util.*; +import java.util.List; + +import javax.swing.*; + +import org.junit.*; + +import docking.*; +import docking.action.*; +import docking.dnd.GClipboard; +import docking.widgets.OptionDialog; +import docking.widgets.fieldpanel.FieldPanel; +import docking.widgets.fieldpanel.support.FieldSelection; +import generic.test.TestUtils; +import ghidra.app.cmd.label.RenameLabelCmd; +import ghidra.app.events.ProgramSelectionPluginEvent; +import ghidra.app.plugin.core.byteviewer.*; +import ghidra.app.plugin.core.clear.ClearCmd; +import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; +import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; +import ghidra.app.plugin.core.decompile.*; +import ghidra.app.plugin.core.format.ByteBlockSelection; +import ghidra.app.plugin.core.format.DataFormatModel; +import ghidra.app.plugin.core.functiongraph.FGClipboardProvider; +import ghidra.app.services.ClipboardContentProviderService; +import ghidra.app.util.ByteCopier; +import ghidra.app.util.viewer.field.LabelFieldFactory; +import ghidra.framework.plugintool.Plugin; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.database.ProgramBuilder; +import ghidra.program.model.address.*; +import ghidra.program.model.data.DataType; +import ghidra.program.model.listing.*; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.mem.MemoryAccessException; +import ghidra.program.model.symbol.*; +import ghidra.program.util.*; +import ghidra.test.*; +import ghidra.util.Msg; + +/** + * + * Note: This test is sensitive to focus. So, don't click any windows while this test + * is running. + * + */ + +public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest { + + private static final String COPY_ACTION_NAME = "Copy"; + private static final String PASTE_ACTION_NAME = "Paste"; + private static final String COPY_SPECIAL_ACTION_NAME = "Copy Special"; + private static final Transferable DUMMY_TRANSFERABLE = new DummyTransferable(); + + private PluginTool tool; + private TestEnv env; + private Program program; + private Map> actionMap; + private ByteViewerClipboardProvider byteViewerClipboardProvider; + private DecompilerClipboardProvider decompileClipboardProvider; + private CodeBrowserClipboardProvider codeBrowserClipboardProvider; + private CodeBrowserPlugin codeBrowserPlugin; + private ByteViewerPlugin byteViewerPlugin; + private DecompilePlugin decompilePlugin; + private CodeViewerProvider codeViewerProvider; + private ClipboardPlugin clipboardPlugin; + private ComponentProvider decompileProvider; + private ComponentProviderWrapper codeViewerWrapper; + private ComponentProviderWrapper byteViewerWrapper; + private ComponentProviderWrapper decompilerWrapper; + + @Before + public void setUp() throws Exception { + + String name = super.testName.getMethodName(); + if (name.endsWith("_Notepad")) { + program = createNotepadProgram(); + } + else { + program = createDefaultProgram(); + } + + env = new TestEnv(); + setErrorGUIEnabled(false); + tool = env.launchDefaultTool(program); + + waitForBusyTool(tool); + waitForSwing(); + + // get actions for each service provider + clipboardPlugin = getPlugin(tool, ClipboardPlugin.class); + actionMap = getActionMap(); + + // Showing decompiler establishes clipboard provider for it + initializeComponentProviders(); + + initializeClipboardProviders(); + + clearClipboardContents(); + } + + @After + public void tearDown() throws Exception { + env.dispose(); + } + + private Program createDefaultProgram() throws Exception { + ProgramBuilder builder = new ProgramBuilder("default", ProgramBuilder._TOY, this); + + builder.createMemory("test", "0x01001050", 20000); + + builder.setBytes("0x01001050", + "0e 5e f4 77 33 58 f4 77 91 45 f4 77 88 7c f4 77 8d 70 f5 77 05 62 f4 77 f0 a3 " + + "f4 77 09 56 f4 77 10 17 f4 77 f7 29 f6 77 02 59 f4 77"); + + builder.createMemoryReference("0x01002cc0", "0x01002cf0", RefType.DATA, + SourceType.USER_DEFINED); + builder.createMemoryReference("0x01002d04", "0x01002d0f", RefType.DATA, + SourceType.USER_DEFINED); + + DataType dt = DataType.DEFAULT; + Parameter p = new ParameterImpl(null, dt, builder.getProgram()); + builder.createEmptyFunction("ghidra", "0x01002cf5", 1, dt, p); + builder.createEmptyFunction("sscanf", "0x0100415a", 1, dt, p); + + builder.createComment("0x0100415a", + "\n;|||||||||||||||||||| FUNCTION ||||||||||||||||||||||||||||||||||||||||||||||||||\n ", + CodeUnit.PLATE_COMMENT); + + builder.setBytes("0x0100418c", "ff 15 08 10 00 01"); + builder.disassemble("0x0100418c", 6); + + return builder.getProgram(); + } + + private Program createNotepadProgram() throws Exception { + ClassicSampleX86ProgramBuilder builder = + new ClassicSampleX86ProgramBuilder("notepad", false, this); + + // need a default label at 01002cf0, so make up a reference + builder.createMemoryReference("01002ce5", "01002cf0", RefType.FALL_THROUGH, + SourceType.ANALYSIS); + + return builder.getProgram(); + } + + @SuppressWarnings("unchecked") + private Map> getActionMap() { + return (Map>) getInstanceField( + "serviceActionMap", clipboardPlugin); + } + + private void initializeClipboardProviders() { + Map> serviceMap = getActionMap(); + Set services = serviceMap.keySet(); + for (Object object : services) { + if (object instanceof ByteViewerClipboardProvider) { + byteViewerClipboardProvider = (ByteViewerClipboardProvider) object; + } + else if (object instanceof CodeBrowserClipboardProvider && + !(object instanceof FGClipboardProvider)) { + codeBrowserClipboardProvider = (CodeBrowserClipboardProvider) object; + } + else if (object instanceof DecompilerClipboardProvider) { + decompileClipboardProvider = (DecompilerClipboardProvider) object; + } + } + } + + private void initializeComponentProviders() { + codeBrowserPlugin = getPlugin(tool, CodeBrowserPlugin.class); + codeViewerProvider = + (CodeViewerProvider) invokeInstanceMethod("getProvider", codeBrowserPlugin); + codeViewerWrapper = new CodeViewerWrapper(codeViewerProvider); + tool.showComponentProvider(codeViewerProvider, true); + + decompilePlugin = getPlugin(tool, DecompilePlugin.class); + ComponentProvider decompiler = tool.getComponentProvider("Decompiler"); + tool.showComponentProvider(decompiler, true); + + waitForSwing(); + byteViewerPlugin = getPlugin(tool, ByteViewerPlugin.class); + ComponentProvider provider = + (ComponentProvider) getInstanceField("connectedProvider", byteViewerPlugin); + byteViewerWrapper = new ByteViewerWrapper(provider); + tool.showComponentProvider(provider, true); + setByteViewerEditable(false); + + decompileProvider = waitForComponentProvider(DecompilerProvider.class); + decompilerWrapper = new DecompilerWrapper(decompileProvider); + + waitForSwing(); + } + + private DockingAction getAction(ClipboardContentProviderService service, String actionName) { + List list = actionMap.get(service); + for (DockingAction pluginAction : list) { + if (pluginAction.getName().equals(actionName)) { + return pluginAction; + } + } + return null; + } + + private Address addr(String address) { + AddressFactory addressFactory = program.getAddressFactory(); + return addressFactory.getAddress(address); + } + + private void renameLabel(ProgramLocation location, String oldName, String newName) { + RenameLabelCmd cmd = + new RenameLabelCmd(location.getAddress(), oldName, newName, SourceType.USER_DEFINED); + assertTrue(applyCmd(program, cmd)); + SymbolTable symbolTable = program.getSymbolTable(); + Symbol currentSymbol = symbolTable.getPrimarySymbol(location.getAddress()); + assertEquals(newName, currentSymbol.getName()); + } + + @Test + public void testCodeBrowser_CopyFromLabel_PasteToLabel() throws Exception { + // + // Test that we can copy (simulating the keyboard action) from one label and then + // paste onto another label. This is useful for copying across programs, as doing so + // in the same program will fail due to a name collision. So, we have to perform the + // following steps to test the code: + // 1) Name a label to a custom name + // 2) Put the cursor on that label and execute the copy command (to get the name on the clipboard) + // 3) Perform an undo to remove the custom name we created + // 4) Put the cursor on a different label and execute the paste command + // + + String oldLabelName = "DAT_01002cf0"; + LabelFieldLocation location = + new LabelFieldLocation(program, addr("01002cf0"), oldLabelName, null, 0); + codeBrowserPlugin.goTo(location); + + // 1) + String newLabelName = "BigBuddyBob"; + renameLabel(location, oldLabelName, newLabelName); + + ProgramLocation currentLocation = codeBrowserPlugin.getCurrentLocation(); + assertTrue(currentLocation instanceof LabelFieldLocation); + location = (LabelFieldLocation) currentLocation; + assertTrue(newLabelName.equals(location.getName())); + + assertTrue(codeBrowserClipboardProvider.canCopy()); + + // 2) + DockingAction copyAction = getAction(codeBrowserClipboardProvider, COPY_ACTION_NAME); + ActionContext context = codeViewerProvider.getActionContext(null); + performAction(copyAction, context, true); + + // 3) + undo(program); + + // 4) + LabelFieldLocation newLabelLocation = + new LabelFieldLocation(program, addr("01002d0f"), "DAT_01002d0f", null, 0); + codeBrowserPlugin.goTo(newLabelLocation); + + DockingAction pasteAction = getAction(codeBrowserClipboardProvider, PASTE_ACTION_NAME); + context = codeViewerProvider.getActionContext(null); + performAction(pasteAction, context, true); + waitForBusyTool(tool); + + currentLocation = codeBrowserPlugin.getCurrentLocation(); + assertTrue(currentLocation instanceof LabelFieldLocation); + location = (LabelFieldLocation) currentLocation; + Msg.debug(this, "checking name!: " + location); + assertEquals(newLabelName, location.getName()); + } + + @Test + public void testCodeBrowser_CopyFromLabel_PasteToFunction() throws Exception { + // + // Test that we can copy (simulating the keyboard action) from one label and then + // paste onto a function. This is useful for copying across programs, as doing so + // in the same program will fail due to a name collision. So, we have to perform the + // following steps to test the code: + // 1) Name a label to a custom name + // 2) Put the cursor on that label and execute the copy command (to get the name on the clipboard) + // 3) Perform an undo to remove the custom name we created + // 4) Put the cursor on a different label and execute the paste command + // + + String oldLabelName = "DAT_01002cf0"; + LabelFieldLocation location = + new LabelFieldLocation(program, addr("01002cf0"), oldLabelName, null, 0); + codeBrowserPlugin.goTo(location); + + // 1) + String newLabelName = "BigBuddyBob"; + renameLabel(location, oldLabelName, newLabelName); + + ProgramLocation currentLocation = codeBrowserPlugin.getCurrentLocation(); + assertTrue(currentLocation instanceof LabelFieldLocation); + location = (LabelFieldLocation) currentLocation; + assertTrue(newLabelName.equals(location.getName())); + + assertTrue(codeBrowserClipboardProvider.canCopy()); + + // 2) + DockingAction copyAction = getAction(codeBrowserClipboardProvider, COPY_ACTION_NAME); + ActionContext context = codeViewerProvider.getActionContext(null); + performAction(copyAction, context, true); + + // 3) + undo(program); + + // 4) + FunctionNameFieldLocation functionLocation = + new FunctionNameFieldLocation(program, addr("01002cf5"), "ghidra"); + codeBrowserPlugin.goTo(functionLocation); + + DockingAction pasteAction = getAction(codeBrowserClipboardProvider, PASTE_ACTION_NAME); + context = codeViewerProvider.getActionContext(null); + performAction(pasteAction, context, true); + waitForBusyTool(tool); + + currentLocation = codeBrowserPlugin.getCurrentLocation(); + assertTrue(currentLocation instanceof FunctionNameFieldLocation); + functionLocation = (FunctionNameFieldLocation) currentLocation; + assertEquals(newLabelName, functionLocation.getFunctionName()); + } + + @Test + public void testCodeBrowser_CopyFromLabel_PasteToComment() throws Exception { + // + // Test that we can copy (simulating the keyboard action) from a label and then + // paste onto a comment. + // + + String oldLabelName = "DAT_01002cf0"; + LabelFieldLocation location = + new LabelFieldLocation(program, addr("01002cf0"), oldLabelName, null, 0); + codeBrowserPlugin.goTo(location); + + // 1) + String newLabelName = "BigBuddyBob"; + renameLabel(location, oldLabelName, newLabelName); + + ProgramLocation currentLocation = codeBrowserPlugin.getCurrentLocation(); + assertTrue(currentLocation instanceof LabelFieldLocation); + location = (LabelFieldLocation) currentLocation; + assertTrue(newLabelName.equals(location.getName())); + + assertTrue(codeBrowserClipboardProvider.canCopy()); + + // 2) + DockingAction copyAction = getAction(codeBrowserClipboardProvider, COPY_ACTION_NAME); + ActionContext context = codeViewerProvider.getActionContext(null); + performAction(copyAction, context, true); + + // 3) + PlateFieldLocation plateFieldLocation = + new PlateFieldLocation(program, addr("0100415a"), null, 1, 10, new String[] { "" }, 0); + + codeBrowserPlugin.goTo(plateFieldLocation); + + DockingAction pasteAction = getAction(codeBrowserClipboardProvider, PASTE_ACTION_NAME); + context = codeViewerProvider.getActionContext(null); + performAction(pasteAction, context, true); + waitForBusyTool(tool); + + currentLocation = codeBrowserPlugin.getCurrentLocation(); + assertTrue(currentLocation instanceof PlateFieldLocation); + plateFieldLocation = (PlateFieldLocation) currentLocation; + String[] comments = plateFieldLocation.getComment(); + assertEquals(1, comments.length); + assertEquals(newLabelName, comments[0]); + } + + @Test + public void testCodeBrowser_CopyFromLabel_PasteToVariable_Notepad() throws Exception { + // + // Test that we can copy (simulating the keyboard action) from one label and then + // paste onto a variable. This is useful for copying across programs, as doing so + // in the same program will fail due to a name collision. + // + // So, we have to perform the following steps to test the code: + // 1) Name a label to a custom name + // 2) Put the cursor on that label and execute the copy command (to get the name on the clipboard) + // 3) Perform an undo to remove the custom name we created + // 4) Put the cursor on a different label and execute the paste command + // + + String oldLabelName = "LAB_01002cf0"; + LabelFieldLocation location = + new LabelFieldLocation(program, addr("01002cf0"), oldLabelName, null, 0); + codeBrowserPlugin.goTo(location); + + // 1) + String newLabelName = "BigBuddyBob"; + renameLabel(location, oldLabelName, newLabelName); + + ProgramLocation currentLocation = codeBrowserPlugin.getCurrentLocation(); + assertTrue(currentLocation instanceof LabelFieldLocation); + location = (LabelFieldLocation) currentLocation; + assertTrue(newLabelName.equals(location.getName())); + + assertTrue(codeBrowserClipboardProvider.canCopy()); + + // 2) + DockingAction copyAction = getAction(codeBrowserClipboardProvider, COPY_ACTION_NAME); + ActionContext context = codeViewerProvider.getActionContext(null); + performAction(copyAction, context, true); + + // 3) + undo(program); + + // 4) + String operandPrefix = "dword ptr [EBP + "; + String operandReferenceName = "destStr]"; + OperandFieldLocation variableOperandReferenceLocation = new OperandFieldLocation(program, + addr("0100416c"), null, addr("0x8"), operandPrefix + operandReferenceName, 1, 9); + codeBrowserPlugin.goTo(variableOperandReferenceLocation); + + DockingAction pasteAction = getAction(codeBrowserClipboardProvider, PASTE_ACTION_NAME); + context = codeViewerProvider.getActionContext(null); + performAction(pasteAction, context, true); + + currentLocation = codeBrowserPlugin.getCurrentLocation(); + assertTrue(currentLocation instanceof OperandFieldLocation); + variableOperandReferenceLocation = (OperandFieldLocation) currentLocation; + assertEquals(operandPrefix + newLabelName + "]", + variableOperandReferenceLocation.getOperandRepresentation()); + } + + @Test + public void testCodeBrowser_CopyFromOperandReference_PasteToLabel_Notepad() throws Exception { + // + // Test that we can copy (simulating the keyboard action) from an operand reference + // label and then paste onto another label. This is useful for copying across + // programs, as doing so in the same program will fail due to a name + // collision. + + // So, we have to perform the following steps to test the code: + // 1) Put the cursor on an operand and execute the copy command (to get the name on the clipboard) + // 2) Name that operand's label to a custom name + // 3) Put the cursor on a different label and execute the paste command + // + + // 1) + String operandText = "dword ptr [->ADVAPI32.dll::RegQueryValueExW]"; + OperandFieldLocation operandFieldLocation = new OperandFieldLocation(program, + addr("0100418c"), null, addr("01001008"), operandText, 15, 0); + codeBrowserPlugin.goTo(operandFieldLocation); + + ProgramLocation currentLocation = codeBrowserPlugin.getCurrentLocation(); + assertTrue(currentLocation instanceof OperandFieldLocation); + operandFieldLocation = (OperandFieldLocation) currentLocation; + assertEquals(operandText, operandFieldLocation.getOperandRepresentation()); + + assertTrue(codeBrowserClipboardProvider.canCopy()); + + DockingAction copyAction = getAction(codeBrowserClipboardProvider, COPY_ACTION_NAME); + ActionContext context = codeViewerProvider.getActionContext(null); + performAction(copyAction, context, true); + + // 2) + String oldLabelName = "ADVAPI32.dll_RegQueryValueExW"; + LabelFieldLocation renameLabelLocation = + new LabelFieldLocation(program, addr("01001008"), oldLabelName, null, 0); + codeBrowserPlugin.goTo(renameLabelLocation); + + String newLabelName = "BigBuddyBob"; + renameLabel(renameLabelLocation, oldLabelName, newLabelName); + + // 3) + String oldPasteName = "LAB_01002cf0"; + LabelFieldLocation pasteLabelLocation = + new LabelFieldLocation(program, addr("01002cf0"), oldPasteName, null, 0); + assertTrue( + codeBrowserPlugin.goToField(addr("01002cf0"), LabelFieldFactory.FIELD_NAME, 0, 0)); + + DockingAction pasteAction = getAction(codeBrowserClipboardProvider, PASTE_ACTION_NAME); + context = codeViewerProvider.getActionContext(null); + performAction(pasteAction, context, true); + waitForBusyTool(tool); + + currentLocation = codeBrowserPlugin.getCurrentLocation(); + assertTrue(currentLocation instanceof LabelFieldLocation); + pasteLabelLocation = (LabelFieldLocation) currentLocation; + assertEquals(oldLabelName, pasteLabelLocation.getName()); + } + + @Test + public void testCodeBrowser_CopyFromFunction_PasteToLabel() throws Exception { + // + // Test that we can copy (simulating the keyboard action) from one function label and then + // paste onto another label. This is useful for copying across programs, as doing so + // in the same program will fail due to a name collision. So, we have to perform the + // following steps to test the code: + // 1) Name a label to a custom name + // 2) Put the cursor on that label and execute the copy command (to get the name on the clipboard) + // 3) Perform an undo to remove the custom name we created + // 4) Put the cursor on a different label and execute the paste command + // + + String functionName = "sscanf"; + FunctionNameFieldLocation location = + new FunctionNameFieldLocation(program, addr("0100415a"), functionName); + codeBrowserPlugin.goTo(location); + + // 1) + String newLabelName = "BigBuddyBob"; + renameLabel(location, functionName, newLabelName); + + ProgramLocation currentLocation = codeBrowserPlugin.getCurrentLocation(); + assertTrue(currentLocation instanceof FunctionNameFieldLocation); + location = (FunctionNameFieldLocation) currentLocation; + assertTrue(newLabelName.equals(location.getFunctionName())); + + assertTrue(codeBrowserClipboardProvider.canCopy()); + + // 2) + DockingAction copyAction = getAction(codeBrowserClipboardProvider, COPY_ACTION_NAME); + ActionContext context = codeViewerProvider.getActionContext(null); + performAction(copyAction, context, true); + assertEquals(newLabelName, getClipboardContents()); + + // 3) + undo(program); + + // 4) + LabelFieldLocation newLabelLocation = + new LabelFieldLocation(program, addr("01002d0f"), "DAT_01002d0f", null, 0); + codeBrowserPlugin.goTo(newLabelLocation); + + DockingAction pasteAction = getAction(codeBrowserClipboardProvider, PASTE_ACTION_NAME); + context = codeViewerProvider.getActionContext(null); + performAction(pasteAction, context, true); + waitForBusyTool(tool); + + currentLocation = codeBrowserPlugin.getCurrentLocation(); + assertTrue(currentLocation instanceof LabelFieldLocation); + LabelFieldLocation labelLocation = (LabelFieldLocation) currentLocation; + assertEquals(newLabelName, labelLocation.getName()); + } + + @Test + public void testCodeBrowser_CopyFromFunction_PasteToComment() throws Exception { + // + // Test that we can copy (simulating the keyboard action) from a function and then + // paste onto a comment. + // + + // 1) + String functionName = "sscanf"; + FunctionNameFieldLocation location = + new FunctionNameFieldLocation(program, addr("0100415a"), functionName); + codeBrowserPlugin.goTo(location); + + ProgramLocation currentLocation = codeBrowserPlugin.getCurrentLocation(); + assertTrue(currentLocation instanceof FunctionNameFieldLocation); + location = (FunctionNameFieldLocation) currentLocation; + assertTrue(functionName.equals(location.getFunctionName())); + + assertTrue(codeBrowserClipboardProvider.canCopy()); + + // 2) + DockingAction copyAction = getAction(codeBrowserClipboardProvider, COPY_ACTION_NAME); + ActionContext context = codeViewerProvider.getActionContext(null); + performAction(copyAction, context, true); + + // 3) + PlateFieldLocation plateFieldLocation = + new PlateFieldLocation(program, addr("0100415a"), null, 1, 10, new String[] { "" }, 0); + + codeBrowserPlugin.goTo(plateFieldLocation); + + DockingAction pasteAction = getAction(codeBrowserClipboardProvider, PASTE_ACTION_NAME); + context = codeViewerProvider.getActionContext(null); + performAction(pasteAction, context, true); + + currentLocation = codeBrowserPlugin.getCurrentLocation(); + assertTrue(currentLocation instanceof PlateFieldLocation); + plateFieldLocation = (PlateFieldLocation) currentLocation; + String[] comments = plateFieldLocation.getComment(); + assertEquals(1, comments.length); + assertEquals(functionName, comments[0]); + } + + @Test + public void testCodeBrowser_CopyFromComment_PasteToComment() throws Exception { + // + // Test that we can copy (simulating the keyboard action) from one comment and then + // paste onto another comment. + // + + PlateFieldLocation plateFieldLocation = + new PlateFieldLocation(program, addr("0100415a"), null, 1, 10, new String[] { "" }, 0); + + codeBrowserPlugin.goTo(plateFieldLocation); + + ProgramLocation currentLocation = codeBrowserPlugin.getCurrentLocation(); + assertTrue(currentLocation instanceof PlateFieldLocation); + plateFieldLocation = (PlateFieldLocation) currentLocation; + String commentText = plateFieldLocation.getComment()[1]; + assertEquals( + ";|||||||||||||||||||| FUNCTION ||||||||||||||||||||||||||||||||||||||||||||||||||", + commentText); + + assertTrue(codeBrowserClipboardProvider.canCopy()); + + // 2) + DockingAction copyAction = getAction(codeBrowserClipboardProvider, COPY_ACTION_NAME); + ActionContext context = codeViewerProvider.getActionContext(null); + performAction(copyAction, context, true); + + // 3) + // 0100415a PreCommentFieldLocation + CommentFieldLocation commentFieldLocation = new CommentFieldLocation(program, + addr("0100415a"), null, new String[] { "" }, CodeUnit.PRE_COMMENT, 5, 10); + + codeBrowserPlugin.goTo(plateFieldLocation); + + DockingAction pasteAction = getAction(codeBrowserClipboardProvider, PASTE_ACTION_NAME); + context = codeViewerProvider.getActionContext(null); + performAction(pasteAction, context, true); + waitForBusyTool(tool); + + currentLocation = codeBrowserPlugin.getCurrentLocation(); + assertTrue(currentLocation instanceof CommentFieldLocation); + commentFieldLocation = (CommentFieldLocation) currentLocation; + String[] comments = commentFieldLocation.getComment(); + assertEquals(2, comments.length); + assertEquals(commentText, comments[0]); + } + + @Test + public void testCopyActionEnablement() { + + // no copy on by default + DockingAction byteViewerCopyAction = + getAction(byteViewerClipboardProvider, COPY_ACTION_NAME); + DockingAction codeBrowserCopyAction = + getAction(codeBrowserClipboardProvider, COPY_ACTION_NAME); + DockingAction decompileCopyAction = getAction(decompileClipboardProvider, COPY_ACTION_NAME); + + waitForSwing(); + + assertTrue(!byteViewerCopyAction.isEnabled()); + assertTrue(!codeBrowserCopyAction.isEnabled()); + + // this action is enabled on any text + // assertTrue(!decompileCopyAction.isEnabled()); + + // give the providers focus and check their actions state + assertTrue(!byteViewerCopyAction.isEnabled()); + + // the code browser is special--make sure that it not only has focus, but that the location + // of the cursor is not one of the 'special' copy locations + codeBrowserPlugin.goTo(new MnemonicFieldLocation(program, addr("1001050"))); + assertTrue(codeBrowserCopyAction.isEnabled()); + + // For each provider: + // check copy on selection state + makeSelection(byteViewerWrapper); + + // check the state + assertTrue(byteViewerCopyAction.isEnabled()); + + // clear the selection + byteViewerWrapper.clearSelection(); + + // check copy on selection state + makeSelection(codeViewerWrapper); + + // check the state + boolean enabled = codeBrowserCopyAction.isEnabled(); + assertTrue(enabled); + + // clear the selection + codeViewerWrapper.clearSelection(); + + // check copy on selection state + makeSelection(decompilerWrapper); + + // check the state + assertTrue(decompileCopyAction.isEnabled()); + } + + @Test + public void testPasteActionEnablement() { + + DockingAction byteViewerCopyAction = + getAction(byteViewerClipboardProvider, COPY_ACTION_NAME); + DockingAction codeBrowserCopyAction = + getAction(codeBrowserClipboardProvider, COPY_ACTION_NAME); + DockingAction decompileCopyAction = getAction(decompileClipboardProvider, COPY_ACTION_NAME); + + // no paste on by default + DockingAction byteViewerPasteAction = + getAction(byteViewerClipboardProvider, PASTE_ACTION_NAME); + DockingAction codeBrowserPasteAction = + getAction(codeBrowserClipboardProvider, PASTE_ACTION_NAME); + + assertTrue(!byteViewerPasteAction.isEnabled()); + assertTrue(!codeBrowserPasteAction.isEnabled()); + + // For each provider make sure no paste on a selection without a copy: + // check copy on selection state + makeSelection(byteViewerWrapper); + + // check the state + assertTrue(!byteViewerPasteAction.isEnabled()); + + // clear the selection + byteViewerWrapper.clearSelection(); + + // check copy on selection state + codeViewerWrapper.clearSelection(); + + // check the state + assertTrue(!codeBrowserPasteAction.isEnabled()); + + // clear the selection + codeViewerWrapper.clearSelection(); + + // check copy on selection state + makeSelection(decompilerWrapper); + + // clear the selection + decompilerWrapper.clearSelection(); + + // For each provider make sure paste on a selection after a copy: + // check paste on selection state + makeSelection(byteViewerWrapper); + copy(byteViewerWrapper, byteViewerCopyAction); + + // check the state + assertTrue(!byteViewerPasteAction.isEnabled()); + setByteViewerEditable(true); + assertTrue(byteViewerPasteAction.isEnabled()); + + // clear the selection and clipboard + byteViewerWrapper.clearSelection(); + clearClipboardContents(); + + makeSelection(codeViewerWrapper); + + copy(codeViewerWrapper, codeBrowserCopyAction); + +// TODO: if we ever find a way to prevent the paste action from being enabled without +// actually doing the paste, then the following line of code can be put back in the test +// check the state - can't paste a generic copy! +//assertTrue( !codeBrowserPasteAction.isEnabled() ); + + // now perform a 'copy special' and choose a type that the paste will accept + DockingAction codeBrowserCopySpecialAction = + getAction(codeBrowserClipboardProvider, COPY_SPECIAL_ACTION_NAME); + assertTrue(codeBrowserCopySpecialAction.isEnabled()); + copySpecial(codeViewerWrapper, codeBrowserCopySpecialAction); + + assertTrue(codeBrowserPasteAction.isEnabled()); + + // clear the selection and clipboard + codeViewerWrapper.clearSelection(); + clearClipboardContents(); + + // check copy on selection state + makeSelection(decompilerWrapper); + copy(decompilerWrapper, decompileCopyAction); + + // clear the selection and clipboard + decompilerWrapper.clearSelection(); + clearClipboardContents(); + } + + @Test + public void testCopyPasteAcrossServiceProviders() { + DockingAction byteViewerCopyAction = + getAction(byteViewerClipboardProvider, COPY_ACTION_NAME); + DockingAction codeBrowserCopyAction = + getAction(codeBrowserClipboardProvider, COPY_ACTION_NAME); + DockingAction decompileCopyAction = getAction(decompileClipboardProvider, COPY_ACTION_NAME); + + DockingAction byteViewerPasteAction = + getAction(byteViewerClipboardProvider, PASTE_ACTION_NAME); + DockingAction codeBrowserPasteAction = + getAction(codeBrowserClipboardProvider, PASTE_ACTION_NAME); + + // byte viewer to code browser and decompiler + makeSelection(byteViewerWrapper); + copy(byteViewerWrapper, byteViewerCopyAction); + + assertTrue(!byteViewerPasteAction.isEnabled()); + setByteViewerEditable(true); + assertTrue(byteViewerPasteAction.isEnabled()); + assertTrue(codeBrowserPasteAction.isEnabled()); + + codeViewerWrapper.clearSelection(); + clearClipboardContents(); + + // sanity check + assertTrue(!byteViewerPasteAction.isEnabled()); + assertTrue(!codeBrowserPasteAction.isEnabled()); + + // code browser to byte viewer and decompiler + makeSelection(codeViewerWrapper); + copy(codeViewerWrapper, codeBrowserCopyAction); + +// TODO: if we ever find a way to prevent the paste action from being enabled without +// actually doing the paste, then the following code can be put back in the test +//assertTrue( !byteViewerPasteAction.isEnabled() ); +//assertTrue( !codeBrowserPasteAction.isEnabled() ); + + // now perform a 'copy special' and choose a type that the paste will accept + DockingAction codeBrowserCopySpecialAction = + getAction(codeBrowserClipboardProvider, COPY_SPECIAL_ACTION_NAME); + copySpecial(codeViewerWrapper, codeBrowserCopySpecialAction); + + assertTrue(byteViewerPasteAction.isEnabled()); + assertTrue(codeBrowserPasteAction.isEnabled()); + + // change the edit state of the byte viewer and make sure we can paste + setByteViewerEditable(true); + + assertTrue(byteViewerPasteAction.isEnabled()); + + // clear the selection and clipboard + codeViewerWrapper.clearSelection(); + clearClipboardContents(); + + // sanity check + assertTrue(!byteViewerPasteAction.isEnabled()); + assertTrue(!codeBrowserPasteAction.isEnabled()); + + // copy from decompiler can not paste into the other two + makeSelection(decompilerWrapper); + copy(decompilerWrapper, decompileCopyAction); + +// TODO: if we ever find a way to prevent the paste action from being enabled without +// actually doing the paste, then the following code can be put back in the test +//assertTrue( !byteViewerPasteAction.isEnabled() ); +//assertTrue( !codeBrowserPasteAction.isEnabled() ); + } + + @Test + public void testCopyPasteAcrossServiceProviders_CodeBrowser_To_ByteViewer() throws Exception { + DockingAction codeBrowserCopyAction = + getAction(codeBrowserClipboardProvider, COPY_ACTION_NAME); + final DockingAction byteViewerPasteAction = + getAction(byteViewerClipboardProvider, PASTE_ACTION_NAME); + + // code browser to byte viewer and decompiler + makeSelection(codeViewerWrapper); + copy(codeViewerWrapper, codeBrowserCopyAction); + + // now perform a 'copy special' and choose a type that the paste will accept + DockingAction codeBrowserCopySpecialAction = + getAction(codeBrowserClipboardProvider, COPY_SPECIAL_ACTION_NAME); + copySpecial(codeViewerWrapper, codeBrowserCopySpecialAction); + + String clipboardContents = getClipboardContents(); + + // move the cursor down to paste our bytes; clear the code we need to be able to paste + ProgramSelection currentSelection = codeBrowserPlugin.getCurrentSelection(); + Address pasteAddress = currentSelection.getMaxAddress(); + long pasteLength = currentSelection.getNumAddresses(); + clearSelectedBytes(pasteAddress, pasteLength); + + codeBrowserPlugin.goTo(new ProgramLocation(program, pasteAddress)); + + // change the edit state of the byte viewer and make sure we can paste + setByteViewerEditable(true); + + assertTrue(byteViewerPasteAction.isEnabled()); + + runSwing(() -> byteViewerPasteAction.actionPerformed(getActionContext(byteViewerWrapper)), + false); + + OptionDialog d = waitForDialogComponent(OptionDialog.class); + pressButton(d.getDefaultButton()); + + waitForBusyTool(tool); + + assertByteViewerBytes(clipboardContents, pasteAddress); + } + + @Test + public void testCopyPasteAcrossServiceProviders_CodeBrowser_To_ByteViewer_BytesWithNoSpaces() + throws Exception { + DockingAction codeBrowserCopyAction = + getAction(codeBrowserClipboardProvider, COPY_ACTION_NAME); + final DockingAction byteViewerPasteAction = + getAction(byteViewerClipboardProvider, PASTE_ACTION_NAME); + + // code browser to byte viewer and decompiler + makeSelection(codeViewerWrapper); + copy(codeViewerWrapper, codeBrowserCopyAction); + + // now perform a 'copy special' and choose a type that the paste will accept + DockingAction codeBrowserCopySpecialAction = + getAction(codeBrowserClipboardProvider, COPY_SPECIAL_ACTION_NAME); + copySpecial_ByteStringNoSpaces(codeViewerWrapper, codeBrowserCopySpecialAction); + + String clipboardContents = getClipboardContents(); + + // move the cursor down to paste our bytes; clear the code we need to be able to paste + ProgramSelection currentSelection = codeBrowserPlugin.getCurrentSelection(); + Address pasteAddress = currentSelection.getMaxAddress(); + long pasteLength = currentSelection.getNumAddresses(); + clearSelectedBytes(pasteAddress, pasteLength); + + codeBrowserPlugin.goTo(new ProgramLocation(program, pasteAddress)); + + // change the edit state of the byte viewer and make sure we can paste + setByteViewerEditable(true); + + assertTrue(byteViewerPasteAction.isEnabled()); + + runSwing(() -> byteViewerPasteAction.actionPerformed(getActionContext(byteViewerWrapper)), + false); + + OptionDialog d = waitForDialogComponent(OptionDialog.class); + pressButton(d.getDefaultButton()); + + waitForBusyTool(tool); + + assertByteViewerBytes(clipboardContents, pasteAddress); + } + + @Test + public void testCopyTextSelection_ByteViewer_SingleLayout() throws Exception { + + ByteViewerOptionsDialog dialog = launchByteViewerOptions(); + setByteViewerViewSelected(dialog, "Ascii", true); + setByteViewerViewSelected(dialog, "Octal", true); + pressButtonByText(dialog.getComponent(), "OK"); + + ByteViewerPlugin plugin = env.getPlugin(ByteViewerPlugin.class); + ProgramByteViewerComponentProvider provider = plugin.getProvider(); + ByteViewerPanel panel = + (ByteViewerPanel) invokeInstanceMethod("getByteViewerPanel", provider); + + Window window = windowForComponent(panel); + assertNotNull(window); + Dimension size = window.getSize(); + window.setSize(1000, size.height);// resize so that we can click the various views + + // + // Test copying from the Hex view + // + ByteViewerComponent bc = findByteViewerComponent(panel, "Hex"); + assertTrue(bc.isVisible()); + + Rectangle bounds = bc.getBounds(); + clickMouse(bc, 1, bounds.x + 20, bounds.y + 20, 1, 0); + + ProgramSelection selection = + new ProgramSelection(program.getAddressFactory(), addr("1001050"), addr("1001052")); + tool.firePluginEvent(new ProgramSelectionPluginEvent("Test", selection, program)); + + waitForBusyTool(tool); + + final DockingAction byteViewerCopyAction = + getAction(byteViewerClipboardProvider, COPY_ACTION_NAME); + runSwing(() -> byteViewerCopyAction.actionPerformed(getActionContext(byteViewerWrapper))); + + Clipboard systemClipboard = GClipboard.getSystemClipboard(); + Transferable contents = systemClipboard.getContents(systemClipboard); + assertNotNull(contents); + assertTrue("Contents not copied into system clipboard", (contents != DUMMY_TRANSFERABLE)); + Object data = contents.getTransferData(DataFlavor.stringFlavor); + assertEquals("0e 5e f4", data); + + // + // Test copying from the Hex view + // + bc = findByteViewerComponent(panel, "Ascii"); + assertTrue(bc.isVisible()); + + bounds = bc.getBounds(); + clickMouse(bc, 1, bounds.x + 20, bounds.y + 20, 1, 0); + + selection = + new ProgramSelection(program.getAddressFactory(), addr("1001050"), addr("1001052")); + tool.firePluginEvent(new ProgramSelectionPluginEvent("Test", selection, program)); + + waitForBusyTool(tool); + + runSwing(() -> byteViewerCopyAction.actionPerformed(getActionContext(byteViewerWrapper))); + + systemClipboard = GClipboard.getSystemClipboard(); + contents = systemClipboard.getContents(systemClipboard); + assertNotNull(contents); + assertTrue("Contents not copied into system clipboard", (contents != DUMMY_TRANSFERABLE)); + data = contents.getTransferData(DataFlavor.stringFlavor); + assertEquals(". ^ .", data); + + // + // Test copying from the Octal view + // + bc = findByteViewerComponent(panel, "Octal"); + assertTrue(bc.isVisible()); + + bounds = bc.getBounds(); + clickMouse(bc, 1, bounds.x + 20, bounds.y + 20, 1, 0); + + selection = + new ProgramSelection(program.getAddressFactory(), addr("1001050"), addr("1001052")); + tool.firePluginEvent(new ProgramSelectionPluginEvent("Test", selection, program)); + + waitForBusyTool(tool); + + runSwing(() -> byteViewerCopyAction.actionPerformed(getActionContext(byteViewerWrapper))); + + systemClipboard = GClipboard.getSystemClipboard(); + contents = systemClipboard.getContents(systemClipboard); + assertNotNull(contents); + assertTrue("Contents not copied into system clipboard", (contents != DUMMY_TRANSFERABLE)); + data = contents.getTransferData(DataFlavor.stringFlavor); + assertEquals("016 136 364", data); + } + + @Test + public void testCopyTextSelection_ByteViewer_MultipleLayout() throws Exception { + ByteViewerOptionsDialog dialog = launchByteViewerOptions(); + setByteViewerViewSelected(dialog, "Ascii", true); + setByteViewerViewSelected(dialog, "Octal", true); + pressButtonByText(dialog.getComponent(), "OK"); + + ByteViewerPlugin plugin = env.getPlugin(ByteViewerPlugin.class); + ProgramByteViewerComponentProvider provider = plugin.getProvider(); + ByteViewerPanel panel = + (ByteViewerPanel) invokeInstanceMethod("getByteViewerPanel", provider); + + Window window = windowForComponent(panel); + assertNotNull(window); + Dimension size = window.getSize(); + window.setSize(1000, size.height);// resize so that we can click the various views + + // + // Test copying from the Hex view + // + ByteViewerComponent bc = findByteViewerComponent(panel, "Hex"); + assertTrue(bc.isVisible()); + + Rectangle bounds = bc.getBounds(); + clickMouse(bc, 1, bounds.x + 20, bounds.y + 20, 1, 0); + + ProgramSelection selection = + new ProgramSelection(program.getAddressFactory(), addr("1001051"), addr("1001070")); + tool.firePluginEvent(new ProgramSelectionPluginEvent("Test", selection, program)); + + waitForBusyTool(tool); + + final DockingAction byteViewerCopyAction = + getAction(byteViewerClipboardProvider, COPY_ACTION_NAME); + runSwing(() -> byteViewerCopyAction.actionPerformed(getActionContext(byteViewerWrapper))); + + Clipboard systemClipboard = GClipboard.getSystemClipboard(); + Transferable contents = systemClipboard.getContents(systemClipboard); + assertNotNull(contents); + assertTrue("Contents not copied into system clipboard", (contents != DUMMY_TRANSFERABLE)); + Object data = contents.getTransferData(DataFlavor.stringFlavor); + assertEquals("5e f4 77 33 58 f4 77 91 45 f4 77 88 7c f4 77 8d 70 f5 77 05 " + + "62 f4 77 f0 a3 f4 77 09 56 f4 77 10", data); + + // + // Test copying from the Hex view + // + bc = findByteViewerComponent(panel, "Ascii"); + assertTrue(bc.isVisible()); + + bounds = bc.getBounds(); + clickMouse(bc, 1, bounds.x + 20, bounds.y + 20, 1, 0); + + selection = + new ProgramSelection(program.getAddressFactory(), addr("1001051"), addr("1001070")); + tool.firePluginEvent(new ProgramSelectionPluginEvent("Test", selection, program)); + + waitForBusyTool(tool); + + runSwing(() -> byteViewerCopyAction.actionPerformed(getActionContext(byteViewerWrapper))); + + systemClipboard = GClipboard.getSystemClipboard(); + contents = systemClipboard.getContents(systemClipboard); + assertNotNull(contents); + assertTrue("Contents not copied into system clipboard", (contents != DUMMY_TRANSFERABLE)); + data = contents.getTransferData(DataFlavor.stringFlavor); + assertEquals("^ . w 3 X . w . E . w . | . w . p . w . b . w . . . w . V . w .", data); + + // + // Test copying from the Octal view + // + bc = findByteViewerComponent(panel, "Octal"); + assertTrue(bc.isVisible()); + + bounds = bc.getBounds(); + clickMouse(bc, 1, bounds.x + 20, bounds.y + 20, 1, 0); + + selection = + new ProgramSelection(program.getAddressFactory(), addr("1001051"), addr("1001070")); + tool.firePluginEvent(new ProgramSelectionPluginEvent("Test", selection, program)); + + waitForBusyTool(tool); + + runSwing(() -> byteViewerCopyAction.actionPerformed(getActionContext(byteViewerWrapper))); + + systemClipboard = GClipboard.getSystemClipboard(); + contents = systemClipboard.getContents(systemClipboard); + assertNotNull(contents); + assertTrue("Contents not copied into system clipboard", (contents != DUMMY_TRANSFERABLE)); + data = contents.getTransferData(DataFlavor.stringFlavor); + assertEquals("136 364 167 063 130 364 167 221 105 364 167 210 174 364 167 215 160 " + + "365 167 005 142 364 167 360 243 364 167 011 126 364 167 020", data); + } + + @Test + public void testCodeBrowserPasteAsciiBytes() throws Exception { + // + // Test that we can paste characters from clipboard that contain some other special characters + // that are not normal ascii text and only the normal ascii text will get pasted. + // So, we have to perform the following steps to test the code: + // 1) Put ill formed characters into clipboard + // 2) Put the cursor on the code unit for pasting. + // 3) Paste the bytes + // 4) Verify the bytes don't contain the non-ascii text. + // + + final char[] droppedChars = + new char[] { (char) 0x30, (char) 0x31, (char) 0xa0, (char) 0xc2, (char) 0x01, + (char) 0x63, (char) 0x30, (char) 0xa0, (char) 0x35, (char) 0x65, (char) 0x20 }; +// final byte[] pastedBytes = +// new byte[] { (byte) 0x30, (byte) 0x31, (byte) 0x63, (byte) 0x30, (byte) 0x35, +// (byte) 0x65 }; + final byte[] resultBytes = new byte[] { (byte) 0x01, (byte) 0xc0, (byte) 0x5e }; + + Clipboard systemClipboard = GClipboard.getSystemClipboard(); + Transferable transferable = new Transferable() { + + @Override + public boolean isDataFlavorSupported(DataFlavor flavor) { + if (flavor.equals(DataFlavor.stringFlavor)) { + return true; + } + return false; + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[] { DataFlavor.stringFlavor }; + } + + @Override + public Object getTransferData(DataFlavor flavor) + throws UnsupportedFlavorException, IOException { + if (flavor.equals(DataFlavor.stringFlavor)) { + return new String(droppedChars); + } + return null; + } + }; + // 1) Put ill formed characters into clipboard + systemClipboard.setContents(transferable, (clipboard, contents) -> { + // dummy listener so that we can be properly garbage collected + }); + Transferable contents = systemClipboard.getContents(systemClipboard); + assertNotNull(contents); + assertTrue("Contents not copied into system clipboard", (contents != DUMMY_TRANSFERABLE)); + Object data = contents.getTransferData(DataFlavor.stringFlavor); + assertEquals(new String(droppedChars), data); + + AddressFieldLocation location = new AddressFieldLocation(program, addr("01002cf0")); + // 2) Put the cursor on the code unit for pasting. + codeBrowserPlugin.goTo(location); + + ActionContext context = codeViewerProvider.getActionContext(null); + + DockingAction pasteAction = getAction(codeBrowserClipboardProvider, PASTE_ACTION_NAME); + context = codeViewerProvider.getActionContext(null); + // 3) Paste the bytes + performAction(pasteAction, context, false); + OptionDialog d = waitForDialogComponent(OptionDialog.class); + pressButton(d.getDefaultButton()); + + waitForBusyTool(tool); + + ProgramLocation currentLocation = codeBrowserPlugin.getCurrentLocation(); + assertTrue(currentLocation instanceof AddressFieldLocation); + AddressFieldLocation addressLocation = (AddressFieldLocation) currentLocation; + assertEquals("01002cf0", addressLocation.getAddressRepresentation()); + Address address = addressLocation.getAddress(); + Memory memory = program.getMemory(); + byte[] memoryBytes = new byte[resultBytes.length]; + // 4) Verify the bytes don't contain the non-ascii text. + memory.getBytes(address, memoryBytes, 0, resultBytes.length); + assertTrue("The expected bytes were not pasted in the CodeBrowser.", + Arrays.equals(resultBytes, memoryBytes)); + } + + @Test + public void testByteViewerPasteAsciiBytes() throws Exception { + final DockingAction byteViewerPasteAction = + getAction(byteViewerClipboardProvider, PASTE_ACTION_NAME); + // + // Test that we can paste characters from clipboard that contain some other special characters + // that are not normal ascii text and only the normal ascii text will get pasted. + // So, we have to perform the following steps to test the code: + // 1) Put ill formed characters into clipboard + // 2) Put the cursor on the byte viewer address for pasting. + // 3) Paste the bytes + // 4) Verify the bytes don't contain the non-ascii text. + // + + final char[] droppedChars = + new char[] { (char) 0x30, (char) 0x31, (char) 0xa0, (char) 0xc2, (char) 0x01, + (char) 0x63, (char) 0x30, (char) 0xa0, (char) 0x35, (char) 0x65, (char) 0x20 }; +// final byte[] pastedBytes = +// new byte[] { (byte) 0x30, (byte) 0x31, (byte) 0x63, (byte) 0x30, (byte) 0x35, +// (byte) 0x65 }; + final byte[] resultBytes = new byte[] { (byte) 0x01, (byte) 0xc0, (byte) 0x5e }; + + Clipboard systemClipboard = GClipboard.getSystemClipboard(); + Transferable transferable = new Transferable() { + + @Override + public boolean isDataFlavorSupported(DataFlavor flavor) { + if (flavor.equals(DataFlavor.stringFlavor)) { + return true; + } + return false; + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[] { DataFlavor.stringFlavor }; + } + + @Override + public Object getTransferData(DataFlavor flavor) + throws UnsupportedFlavorException, IOException { + if (flavor.equals(DataFlavor.stringFlavor)) { + return new String(droppedChars); + } + return null; + } + }; + // 1) Put ill formed characters into clipboard + systemClipboard.setContents(transferable, (clipboard, contents) -> { + // dummy listener so that we can be properly garbage collected + }); + Transferable contents = systemClipboard.getContents(systemClipboard); + assertNotNull(contents); + assertTrue("Contents not copied into system clipboard", (contents != DUMMY_TRANSFERABLE)); + Object data = contents.getTransferData(DataFlavor.stringFlavor); + assertEquals(new String(droppedChars), data); + + Address address = program.getAddressFactory().getAddress("01002cf0"); + AddressFieldLocation location = new AddressFieldLocation(program, address); + // 2) Put the cursor on the byte viewer address for pasting. + codeBrowserPlugin.goTo(location); + + // change the edit state of the byte viewer and make sure we can paste + setByteViewerEditable(true); + + assertTrue(byteViewerPasteAction.isEnabled()); + + // 3) Paste the bytes + runSwing(() -> byteViewerPasteAction.actionPerformed(getActionContext(byteViewerWrapper)), + false); + + OptionDialog d = waitForDialogComponent(OptionDialog.class); + pressButton(d.getDefaultButton()); + + waitForSwing(); + + Memory memory = program.getMemory(); + byte[] memoryBytes = new byte[resultBytes.length]; + // 4) Verify the bytes don't contain the non-ascii text. + memory.getBytes(address, memoryBytes, 0, resultBytes.length); + assertTrue("The expected bytes were not pasted in the ByteViewer.", + Arrays.equals(resultBytes, memoryBytes)); + } + +//================================================================================================== +// Helper Methods +//================================================================================================== + + private ByteViewerOptionsDialog launchByteViewerOptions() { + Plugin plugin = env.getPlugin(ByteViewerPlugin.class); + final DockingActionIf action = getAction(plugin, "Byte Viewer Options"); + assertTrue(action.isEnabled()); + + SwingUtilities.invokeLater(() -> action.actionPerformed(new ActionContext())); + waitForSwing(); + ByteViewerOptionsDialog d = waitForDialogComponent(ByteViewerOptionsDialog.class); + return d; + } + + private void setByteViewerViewSelected(ByteViewerOptionsDialog dialog, String viewName, + boolean selected) { + Map checkboxMap = (Map) getInstanceField("checkboxMap", dialog); + JCheckBox checkbox = (JCheckBox) checkboxMap.get(viewName); + checkbox.setSelected(selected); + } + + private ByteViewerComponent findByteViewerComponent(Container container, String name) { + Component[] c = container.getComponents(); + for (Component element : c) { + if (element instanceof ByteViewerComponent) { + DataFormatModel model = + (DataFormatModel) invokeInstanceMethod("getDataModel", element); + if (model.getName().equals(name)) { + return (ByteViewerComponent) element; + } + } + else if (element instanceof Container) { + ByteViewerComponent bvc = findByteViewerComponent((Container) element, name); + if (bvc != null) { + return bvc; + } + } + } + return null; + } + + private void setByteViewerEditable(boolean editable) { + ComponentProvider provider = + (ComponentProvider) getInstanceField("connectedProvider", byteViewerPlugin); + + ToggleDockingActionIf action = + (ToggleDockingActionIf) getInstanceField("editModeAction", provider); + + if (editable == action.isSelected()) { + return; + } + + performAction(action, true); + } + + private void makeSelection(ComponentProviderWrapper wrapper) { + ComponentProvider provider = wrapper.getComponentProvider(); + + wrapper.clearSelection(); + + Component component = provider.getComponent(); + + Point point = wrapper.getStartMouseDragLocation(); + int startX = point.x; + int startY = point.y; + + Point endPoint = wrapper.getEndMouseDragLocation(); + int endX = endPoint.x; + int endY = endPoint.y; + + final Component deepestComponent = + SwingUtilities.getDeepestComponentAt(component, startX, startY); + Msg.debug(this, "Preparing to make a selection on Java component \"" + deepestComponent + + "\" for test " + wrapper.getComponentProvider().getName()); +// Container parent = deepestComponent.getParent(); +// while ( parent != null ) { +// Err.debug( this, "\twith parent: " + parent ); +// parent = parent.getParent(); +// } + dragMouse(deepestComponent, MouseEvent.BUTTON1, startX, startY, endX, endY, 0); + Msg.debug(this, "\tafter make selection"); + + wrapper.verifySelection(); + } + + private void clearClipboardContents() { + Clipboard systemClipboard = GClipboard.getSystemClipboard(); + systemClipboard.setContents(DUMMY_TRANSFERABLE, null); + waitForSwing(); + + // something useful or snarky, it's up to me. + // make sure that the state is initialized properly, because the + // testing environment might have cruft in it (note, normal environment + // has cruft too) and this updates the stuff in the stuff + TestUtils.invokeInstanceMethod("updateCopyState", clipboardPlugin); + TestUtils.invokeInstanceMethod("updatePasteState", clipboardPlugin); + } + + private String getClipboardContents() throws Exception { + Clipboard systemClipboard = GClipboard.getSystemClipboard(); + Transferable transferable = systemClipboard.getContents(this); + return (String) transferable.getTransferData(DataFlavor.stringFlavor); + } + + private void copy(ComponentProviderWrapper wrapper, final DockingAction copyAction) { + wrapper.verifySelection(); + + runSwing(() -> { + copyAction.actionPerformed(getActionContext(wrapper)); + }); + + Clipboard systemClipboard = GClipboard.getSystemClipboard(); + Transferable contents = systemClipboard.getContents(systemClipboard); + assertNotNull(contents); + assertTrue("Contents not copied into system clipboard", (contents != DUMMY_TRANSFERABLE)); + } + + private ActionContext getActionContext(ComponentProviderWrapper wrapper) { + return runSwing(() -> { + ComponentProvider provider = wrapper.getComponentProvider(); + ActionContext context = provider.getActionContext(null); + return context; + }); + } + + private void copySpecial(ComponentProviderWrapper wrapper, final DockingAction copyAction) { + wrapper.verifySelection(); + executeOnSwingWithoutBlocking(() -> copyAction.actionPerformed(getActionContext(wrapper))); + + // get the dialog and make a selection + JDialog dialog = waitForJDialog("Copy Special"); + assertNotNull(dialog); + DockingDialog dockingDialog = (DockingDialog) dialog; + final DialogComponentProvider component = + (DialogComponentProvider) getInstanceField("component", dockingDialog); + Object listPanel = getInstanceField("listPanel", component); + final JList list = (JList) getInstanceField("list", listPanel); + + runSwing(() -> { + list.setSelectedValue(ByteCopier.BYTE_STRING_TYPE, true); + JButton okButton = (JButton) getInstanceField("okButton", component); + okButton.doClick(); + }); + + try { + waitForTasks(); + } + catch (Exception e) { + Assert.fail(); + } + } + + private void copySpecial_ByteStringNoSpaces(ComponentProviderWrapper wrapper, + final DockingAction copyAction) { + wrapper.verifySelection(); + executeOnSwingWithoutBlocking(() -> copyAction.actionPerformed(getActionContext(wrapper))); + + // get the dialog and make a selection + JDialog dialog = waitForJDialog("Copy Special"); + assertNotNull(dialog); + DockingDialog dockingDialog = (DockingDialog) dialog; + final DialogComponentProvider component = + (DialogComponentProvider) getInstanceField("component", dockingDialog); + Object listPanel = getInstanceField("listPanel", component); + final JList list = (JList) getInstanceField("list", listPanel); + + runSwing(() -> { + list.setSelectedValue(ByteCopier.BYTE_STRING_NO_SPACE_TYPE, true); + JButton okButton = (JButton) getInstanceField("okButton", component); + okButton.doClick(); + }); + + try { + waitForTasks(); + } + catch (Exception e) { + Assert.fail("Unexpected exception waiting for tasks"); + } + } + + private void assertByteViewerBytes(String clipboardContents, Address address) + throws MemoryAccessException, AddressOutOfBoundsException { + Memory memory = program.getMemory(); + + //b090db777880db774893db77 + // expecting a bytes string of the format: b0 90 db 77 78 80 db 77 48 93 db 77 + String[] bytes = null; + if (clipboardContents.contains(" ")) { + bytes = clipboardContents.split("\\s"); + } + else { + bytes = new String[clipboardContents.length() >> 1]; + for (int i = 0; i < bytes.length; i++) { + int stringOffset = i * 2; + bytes[i] = clipboardContents.substring(stringOffset, stringOffset + 2); + } + } + for (int i = 0; i < bytes.length; i++) { + byte bite = memory.getByte(address.add(i)); + if (!bytes[i].equals(Integer.toHexString(bite & 0xFF))) { + Msg.debug(this, "not equal at index: " + i); + } + assertEquals("Bytes not pasted as expected", bytes[i], + Integer.toHexString(bite & 0xFF)); + } + } + + private void clearSelectedBytes(Address address, long length) { + AddressSet addressSet = new AddressSet(address); + for (int i = 1; i < length; i++) { + addressSet.add(address.add(i)); + } + + int startTransaction = program.startTransaction("Test - Clear Bytes"); + try { + ClearCmd clearCmd = new ClearCmd(addressSet); + clearCmd.applyTo(program); + } + finally { + program.endTransaction(startTransaction, true); + } + } + + /* + * We remove the FieldPanel focus listeners for these tests, as when they lose focus, + * the selection mechanism does not work as expected. Focus changes can happen + * indeterminately during parallel batch testing. + */ + private void removeFieldPanelFocusListeners(Container c) { + if (c instanceof FieldPanel) { + removeFocusListeners((JComponent) c); + } + + Component[] children = c.getComponents(); + for (Component child : children) { + if (!(child instanceof Container)) { + continue; + } + removeFieldPanelFocusListeners((Container) child); + } + } + + private void removeFocusListeners(JComponent c) { + FocusListener[] listeners = c.getFocusListeners(); + for (FocusListener l : listeners) { + c.removeFocusListener(l); + } + } + +//================================================================================================== +// Inner Classes +//================================================================================================== + + interface ComponentProviderWrapper { + public Point getStartMouseDragLocation(); + + public ActionContext getContext(); + + public Point getEndMouseDragLocation(); + + public void clearSelection(); + + public void verifySelection(); + + public ComponentProvider getComponentProvider(); + } + + class ByteViewerWrapper implements ComponentProviderWrapper { + private final ByteViewerComponentProvider provider; + + public ByteViewerWrapper(ComponentProvider provider) { + this.provider = (ByteViewerComponentProvider) provider; + + removeFieldPanelFocusListeners(provider.getComponent()); + } + + @Override + public void verifySelection() { + Object byteViewerPanel = getInstanceField("panel", provider); + ByteBlockSelection byteBlockSelection = + (ByteBlockSelection) invokeInstanceMethod("getViewerSelection", byteViewerPanel); + assertTrue("No selection in the byte viewer.", + byteBlockSelection.getNumberOfRanges() > 0); + } + + @Override + public Point getStartMouseDragLocation() { + return new Point(100, 30); + } + + @Override + public Point getEndMouseDragLocation() { + return new Point(300, 30); + } + + @Override + public ComponentProvider getComponentProvider() { + return provider; + } + + @Override + public ActionContext getContext() { + return provider.getActionContext(null); + } + + @Override + public void clearSelection() { + Object byteViewerPanel = getInstanceField("panel", provider); + runSwing(() -> { + invokeInstanceMethod("setViewerSelection", byteViewerPanel, + new Class[] { ByteBlockSelection.class }, + new Object[] { new ByteBlockSelection() }); + }); + + } + } + + class CodeViewerWrapper implements ComponentProviderWrapper { + + private final CodeViewerProvider provider; + + public CodeViewerWrapper(CodeViewerProvider provider) { + this.provider = provider; + + removeFieldPanelFocusListeners(provider.getComponent()); + } + + @Override + public void verifySelection() { + ProgramSelection selection = provider.getSelection(); + if (!selection.isEmpty()) { + return; + } + + CodeBrowserClipboardProvider clipboardProvider = + (CodeBrowserClipboardProvider) getInstanceField("codeViewerClipboardProvider", + provider); + + assertTrue("No selection in the code browser.", + clipboardProvider.getStringContent() != null); + } + + @Override + public Point getStartMouseDragLocation() { + return new Point(100, 110); + } + + @Override + public Point getEndMouseDragLocation() { + return new Point(300, 210); + } + + @Override + public ComponentProvider getComponentProvider() { + return provider; + } + + @Override + public ActionContext getContext() { + return provider.getActionContext(null); + } + + @Override + public void clearSelection() { + runSwing(() -> provider.programSelectionChanged(new ProgramSelection())); + } + } + + class DecompilerWrapper implements ComponentProviderWrapper { + + private final DecompilerProvider provider; + + public DecompilerWrapper(ComponentProvider provider) { + this.provider = (DecompilerProvider) provider; + + removeFieldPanelFocusListeners(provider.getComponent()); + } + + @Override + public void verifySelection() { + Object controller = getInstanceField("controller", provider); + Object decompilerPanel = getInstanceField("decompilerPanel", controller); + FieldPanel fieldPanel = (FieldPanel) getInstanceField("fieldPanel", decompilerPanel); + FieldSelection selection = fieldPanel.getSelection(); + assertTrue("No selection in the decompile provider.", !selection.isEmpty()); + } + + @Override + public Point getStartMouseDragLocation() { + return new Point(10, 5); + } + + @Override + public Point getEndMouseDragLocation() { + return new Point(210, 5); + } + + @Override + public ComponentProvider getComponentProvider() { + return provider; + } + + @Override + public ActionContext getContext() { + return provider.getActionContext(null); + } + + @Override + public void clearSelection() { + runSwing(() -> provider.setSelection(new ProgramSelection())); + } + } + + static class DummyTransferable implements Transferable { + + @Override + public Object getTransferData(DataFlavor flavor) + throws UnsupportedFlavorException, IOException { + return null; + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[0]; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor flavor) { + return true; + } + + } +} diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/processors/SetLanguageTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/processors/SetLanguageTest.java new file mode 100644 index 0000000000..f3bafcc6e9 --- /dev/null +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/processors/SetLanguageTest.java @@ -0,0 +1,307 @@ +/* ### + * 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.processors; + +import static org.junit.Assert.*; + +import java.math.BigInteger; + +import org.junit.*; + +import docking.ActionContext; +import docking.action.DockingActionIf; +import docking.widgets.MultiLineLabel; +import docking.widgets.OptionDialog; +import docking.widgets.tree.GTree; +import docking.widgets.tree.GTreeNode; +import ghidra.framework.main.FrontEndTool; +import ghidra.framework.main.datatree.DomainFileNode; +import ghidra.framework.model.DomainFile; +import ghidra.framework.model.DomainFolder; +import ghidra.plugin.importer.NewLanguagePanel; +import ghidra.program.database.ProgramBuilder; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.RefType; +import ghidra.program.model.symbol.SourceType; +import ghidra.test.AbstractGhidraHeadedIntegrationTest; +import ghidra.test.TestEnv; +import ghidra.util.task.TaskMonitor; + +public class SetLanguageTest extends AbstractGhidraHeadedIntegrationTest { + + private TestEnv env; + private FrontEndTool frontEndTool; + + private DockingActionIf setLanguageAction; + private GTreeNode notepadNode; + private DomainFile notepadFile; + private GTreeNode xyzFolderNode; + + private AddressFactory addrFactory; + + @Before + public void setUp() throws Exception { + env = new TestEnv(); + + setErrorGUIEnabled(true); + frontEndTool = env.getFrontEndTool(); + env.showFrontEndTool(); + + setLanguageAction = getAction(frontEndTool, "LanguageProviderPlugin", "Set Language"); + + // NOTE: Only test translation from a supported language to another supported language + +// TODO: Change test data to a supported case (e.g., MIPS-32 to MIPS-64) + + DomainFolder rootFolder = env.getProject().getProjectData().getRootFolder(); + + ProgramBuilder builder = new ProgramBuilder("notepad", "x86:LE:32:default"); + Program p = builder.getProgram(); + + assertEquals(new LanguageID("x86:LE:32:default"), p.getLanguageID()); + rootFolder.createFile("notepad", p, TaskMonitor.DUMMY); + env.release(p); + builder.dispose(); + + rootFolder.createFolder("XYZ"); + GTree tree = findComponent(frontEndTool.getToolFrame(), GTree.class); + waitForTree(tree); + + GTreeNode rootNode = tree.getRootNode(); + xyzFolderNode = rootNode.getChild(0); + notepadNode = rootNode.getChild(1); + notepadFile = ((DomainFileNode) notepadNode).getDomainFile(); + + waitForSwing(); + } + + @After + public void tearDown() throws Exception { + env.dispose(); + } + + @Test + public void testActionEnablement() throws Exception { + assertTrue(setLanguageAction.isEnabled()); + assertTrue(!setLanguageAction.isEnabledForContext(createContext(xyzFolderNode))); + assertTrue(setLanguageAction.isEnabledForContext(createContext(notepadNode))); + } + + private Address addr(String address) { + return addrFactory.getAddress(address); + } + + private void startSetLanguage(final LanguageID languageID, final CompilerSpecID compilerSpecID, + boolean isFailureCase) throws Exception { + if (languageID == null) { + throw new RuntimeException("languageID == null not allowed"); + } + if (compilerSpecID == null) { + throw new RuntimeException("compilerSpecID == null not allowed"); + } + + // this triggers a modal dialog + runSwing(() -> { + ActionContext context = createContext(notepadNode); + assertTrue(setLanguageAction.isEnabledForContext(context)); + setLanguageAction.actionPerformed(context); + }, false); + + OptionDialog confirmDlg = waitForDialogComponent(OptionDialog.class); + assertNotNull(confirmDlg); + MultiLineLabel msgLabel = findComponent(confirmDlg, MultiLineLabel.class); + assertNotNull(msgLabel); + assertTrue(msgLabel.getLabel().indexOf("Setting the language can not be undone") >= 0); + assertTrue(msgLabel.getLabel().indexOf("make a copy") > 0); + + pressButtonByText(confirmDlg, "Ok"); + + final SetLanguageDialog dlg = waitForDialogComponent(SetLanguageDialog.class); + assertNotNull(dlg); + final NewLanguagePanel languagePanel = + (NewLanguagePanel) getInstanceField("selectLangPanel", dlg); + assertNotNull(languagePanel); + + waitForSwing(); + + runSwing(() -> { + NewLanguagePanel selectLangPanel = + (NewLanguagePanel) getInstanceField("selectLangPanel", dlg); + selectLangPanel.setSelectedLcsPair( + new LanguageCompilerSpecPair(languageID, compilerSpecID)); + }, true); + + waitForSwing(); + + pressButtonByText(dlg, "OK"); + + if (!isFailureCase) { + confirmDlg = waitForDialogComponent(OptionDialog.class); + assertNotNull(confirmDlg); + msgLabel = findComponent(confirmDlg, MultiLineLabel.class); + assertNotNull(msgLabel); + assertTrue(msgLabel.getLabel().indexOf("Would you like to Save") >= 0); + + pressButtonByText(confirmDlg, "Save"); + } + } + + @Test + public void testReplaceLanguage() throws Exception { + + startSetLanguage(new LanguageID("x86:LE:32:System Management Mode"), + new CompilerSpecID("default"), false); + + waitForTasks(); + + Program p = (Program) notepadFile.getDomainObject(this, false, false, TaskMonitor.DUMMY); + assertNotNull(p); + try { + assertEquals(new LanguageID("x86:LE:32:System Management Mode"), + p.getLanguage().getLanguageID()); + + // TODO: Other checks needed ?? + + } + finally { + p.release(this); + } + } + + @Test + public void testReplaceLanguageFailure() throws Exception { + + startSetLanguage(new LanguageID("8051:BE:16:default"), new CompilerSpecID("default"), true); + + final OptionDialog errDlg = waitForDialogComponent(OptionDialog.class); + assertNotNull(errDlg); + MultiLineLabel msgLabel = findComponent(errDlg, MultiLineLabel.class); + assertNotNull(msgLabel); + assertTrue(msgLabel.getLabel().indexOf("Language translation not supported") >= 0); + + pressButtonByText(errDlg, "OK"); + closeAllWindows(); + } + + @Test + public void testReplaceLanguage2() throws Exception { + + Program p = (Program) notepadFile.getDomainObject(this, false, false, TaskMonitor.DUMMY); + try { + int txId = p.startTransaction("set Language"); + addrFactory = p.getAddressFactory(); + ProgramContext pc = p.getProgramContext(); + Register ax = pc.getRegister("ax"); + Register ebp = pc.getRegister("ebp"); + Register ebx = pc.getRegister("ebx"); + pc.setValue(ax, addr("0x1001000"), addr("0x1001000"), BigInteger.valueOf(0x1234)); + pc.setValue(ebp, addr("0x1001000"), addr("0x1001000"), BigInteger.valueOf(0x12345678)); + pc.setValue(ebx, addr("0x1001000"), addr("0x1001000"), BigInteger.valueOf(0x12345678)); + assertEquals(0x1234, pc.getValue(ax, addr("0x1001000"), false).longValue()); + assertEquals(0x12345678, pc.getValue(ebp, addr("0x1001000"), false).longValue()); + assertEquals(0x12345678, pc.getValue(ebx, addr("0x1001000"), false).longValue()); + p.endTransaction(txId, true); + + p.save(null, TaskMonitor.DUMMY); + } + finally { + p.release(this); + } + + startSetLanguage(new LanguageID("x86:LE:32:default"), new CompilerSpecID("gcc"), false); + waitForTasks(); + + p = (Program) notepadFile.getDomainObject(this, true, false, TaskMonitor.DUMMY); + try { + addrFactory = p.getAddressFactory(); + ProgramContext pc = p.getProgramContext(); + Register ax = pc.getRegister("ax"); + Register ebp = pc.getRegister("ebp"); + Register ebx = pc.getRegister("ebx"); + assertEquals(0x1234, pc.getValue(ax, addr("0x1001000"), false).longValue()); + assertEquals(0x12345678, pc.getValue(ebp, addr("0x1001000"), false).longValue()); + assertEquals(0x12345678, pc.getValue(ebx, addr("0x1001000"), false).longValue()); + } + finally { + p.release(this); + } + } + + @Test + public void testReplaceLanguage3() throws Exception { + + Program p = (Program) notepadFile.getDomainObject(this, false, false, TaskMonitor.DUMMY); + addrFactory = p.getAddressFactory(); + ProgramContext pc = p.getProgramContext(); + Register eax = pc.getRegister("eax"); + Register esi = pc.getRegister("esi"); + Register edi = pc.getRegister("edi"); + try { + int txId = p.startTransaction("set Language"); + + Function f = p.getListing().createFunction("BOB", addr("0x10041a8"), + new AddressSet(addr("0x10041a8"), addr("0x10041c0")), SourceType.USER_DEFINED); + f.setCustomVariableStorage(true); + ParameterImpl param = new ParameterImpl("PARAM_ONE", null, eax, p); + f.addParameter(param, SourceType.USER_DEFINED); + LocalVariableImpl local1 = new LocalVariableImpl("LOCAL_ONE", 0, null, esi, p); + LocalVariableImpl local2 = new LocalVariableImpl("LOCAL_TWO", 0, null, edi, p); + + f.addLocalVariable(local1, SourceType.USER_DEFINED); + f.addLocalVariable(local2, SourceType.USER_DEFINED); + + p.getReferenceManager().addRegisterReference(addr("0x10041b2"), 0, esi, RefType.DATA, + SourceType.USER_DEFINED); + p.getReferenceManager().addRegisterReference(addr("0x10041b3"), 0, edi, RefType.DATA, + SourceType.USER_DEFINED); + + p.endTransaction(txId, true); + + p.save(null, TaskMonitor.DUMMY); + } + finally { + p.release(this); + } + + startSetLanguage(new LanguageID("x86:LE:32:default"), new CompilerSpecID("gcc"), false); + waitForTasks(); + + p = (Program) notepadFile.getDomainObject(this, true, false, TaskMonitor.DUMMY); + try { + addrFactory = p.getAddressFactory(); + + Function fun = p.getListing().getFunctionAt(addr("0x10041a8")); + Parameter[] params = fun.getParameters(); + assertEquals(1, params.length); + assertEquals("PARAM_ONE", params[0].getName()); + assertTrue(params[0].isRegisterVariable()); + assertEquals(eax, params[0].getRegister()); + + Variable[] locals = fun.getLocalVariables(); + assertEquals(2, locals.length); + assertEquals("LOCAL_ONE", locals[0].getName()); + assertEquals("LOCAL_TWO", locals[1].getName()); + assertTrue(params[0].isRegisterVariable()); + assertEquals(esi, locals[0].getRegister()); + assertEquals(edi, locals[1].getRegister()); + } + finally { + p.release(this); + } + } +}