Merge remote-tracking branch 'origin/GT-3035-dragonmacher-restore-integration-tests'

This commit is contained in:
dragonmacher
2019-08-15 18:35:23 -04:00
99 changed files with 18957 additions and 44 deletions
@@ -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;
@@ -593,7 +593,7 @@ public class GhidraScriptUtil {
* @param name the name of the script
* @return the name as a '.java' file path (with '/'s and not '.'s)
*/
private static String fixupName(String name) {
static String fixupName(String name) {
if (name.endsWith(".java")) {
name = name.substring(0, name.length() - 5);
}
@@ -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 {
* <P>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 extends DialogComponentProvider> T waitForDialogComponent(Class<T> 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,11 @@ public class TestEnv {
deleteSavedFrontEndTool();
String projectDirectoryName = AbstractGTest.getTestDirectoryPath();
return GhidraProject.createProject(projectDirectoryName, projectName, true);
GhidraProject gp = GhidraProject.createProject(projectDirectoryName, projectName, true);
installDefaultTool(gp);
return gp;
}
private static void deleteOldTestTools() {
@@ -399,6 +401,19 @@ public class TestEnv {
}
}
private static void installDefaultTool(GhidraProject gp) {
//
// 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));
}
private void initializeSimpleTool() {
if (tool != null) {
@@ -466,6 +481,7 @@ public class TestEnv {
/**
* This method differs from {@link #launchDefaultTool()} in that this method does not set the
* <tt>tool</tt> variable in of this <tt>TestEnv</tt> instance.
* @return the tool
*/
public PluginTool createDefaultTool() {
PluginTool newTool = launchDefaultToolByName(AbstractGenericTest.DEFAULT_TEST_TOOL_NAME);
@@ -497,15 +513,14 @@ public class TestEnv {
return tool;
}
protected PluginTool launchDefaultToolByName(final String toolName) {
AtomicReference<PluginTool> 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 +530,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 +577,7 @@ public class TestEnv {
/**
* Returns GhidraProject associated with this environment
* @return the project
*/
public GhidraProject getGhidraProject() {
return gp;
@@ -559,6 +587,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 +608,6 @@ public class TestEnv {
return gp.getProjectManager();
}
/**
* Returns Project associated with this environment
*/
public Project getProject() {
return gp.getProject();
}
@@ -646,6 +672,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 +702,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 +766,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 +854,8 @@ public class TestEnv {
* Launches a tool of the given name using the given domain file.
* <p>
* 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) {
@@ -1216,5 +1245,7 @@ public class TestEnv {
DefaultProjectManager pm = gp.getProjectManager();
pm.addDefaultTools(tc);
installDefaultTool(gp);
}
}
@@ -0,0 +1,61 @@
/* ###
* 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.script;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import generic.test.AbstractGenericTest;
public class GhidraScriptUtilTest extends AbstractGenericTest {
@Test
public void fixupName_WithExtension() {
String input = "Bob.java";
assertEquals(GhidraScriptUtil.fixupName(input), "Bob.java");
}
@Test
public void fixupName_WithoutExtension() {
String input = "Bob";
assertEquals(GhidraScriptUtil.fixupName(input), "Bob.java");
}
@Test
public void fixupName_WithPackageDots() {
String input = "a.b.c.Bob";
assertEquals(GhidraScriptUtil.fixupName(input), "a/b/c/Bob.java");
}
@Test
public void fixupName_WithPackageSlashes() {
String input = "a/b/c/Bob";
assertEquals(GhidraScriptUtil.fixupName(input), "a/b/c/Bob.java");
}
@Test
public void fixupName_InnerClass() {
String input = "Bob$InnerClass";
assertEquals(GhidraScriptUtil.fixupName(input), "Bob.java");
}
@Test
public void fixupName_InnerClass_WithPackageDots() {
String input = "a.b.c.Bob$InnerClass";
assertEquals(GhidraScriptUtil.fixupName(input), "a/b/c/Bob.java");
}
}
@@ -0,0 +1,74 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util;
import static org.junit.Assert.assertEquals;
import org.junit.*;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.listing.Program;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
public class PseudoDisassemblerTest extends AbstractGhidraHeadlessIntegrationTest {
private ProgramBuilder programBuilder;// Instructions are 2-byte aligned
private Program program;
private PseudoDisassembler disassembler;
private int txId;
@Before
public void setUp() throws Exception {
programBuilder = new ProgramBuilder("Test", ProgramBuilder._ARM);
program = programBuilder.getProgram();
txId = program.startTransaction("Add Memory");// leave open until tearDown
programBuilder.createMemory(".text", "0", 64).setExecute(true);// initialized
programBuilder.createUninitializedMemory(".unint", "0x40", 64).setExecute(true);// uninitialized
programBuilder.createUninitializedMemory(".dat", "0x80", 64);// no-execute
programBuilder.createMemory(".text2", "0x3e0", 0x800).setExecute(true);// initialized
disassembler = new PseudoDisassembler(program);
}
@After
public void tearDown() throws Exception {
if (program != null) {
program.endTransaction(txId, true);
}
if (programBuilder != null) {
programBuilder.dispose();
}
}
@Test
public void testToStringArmSeparator() throws Exception {
programBuilder.setBytes("0", "08 f8 00 00 40 00");// strb.w r0,[r8,r0,0x0]
programBuilder.setRegisterValue("TMode", "0", "1", 1);
PseudoInstruction instr =
disassembler.disassemble(program.getAddressFactory().getAddress("0"));
String str = instr.toString();
assertEquals("strb.w r0,[r8,r0,lsl #0x0]", str);// wan't to make sure all markup is printed
programBuilder.setBytes("0", "00 f0 20 03");// nopeq
programBuilder.setRegisterValue("TMode", "0", "1", 0);
instr = disassembler.disassemble(program.getAddressFactory().getAddress("0"));
str = instr.toString();
assertEquals("nopeq", str);
}
}
@@ -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;
@@ -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;
@@ -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;
@@ -0,0 +1,122 @@
/* ###
* 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.imagepanel;
import static org.junit.Assert.assertTrue;
import java.awt.Image;
import javax.swing.Icon;
import javax.swing.JFrame;
import org.junit.*;
import docking.test.AbstractDockingTest;
import resources.ResourceManager;
import resources.icons.EmptyIcon;
public class ImagePanelTest extends AbstractDockingTest {
private JFrame frame;
private ImagePanel imagePanel;
@Before
public void setUp() throws Exception {
Icon emptyIcon = new EmptyIcon(32, 32);
Image emptyImage = ResourceManager.getImageIcon(emptyIcon).getImage();
imagePanel = new ImagePanel(emptyImage);
frame = new JFrame("ImagePanel Test");
frame.getContentPane().add(imagePanel);
frame.setSize(400, 400);
frame.setVisible(true);
}
@After
public void tearDown() throws Exception {
frame.dispose();
}
private void reset() {
imagePanel.setZoomFactor(1.0f);
assertTrue("Unable to reset zoom factor",
Float.compare(imagePanel.getZoomFactor(), 1.0f) == 0);
}
@Test
public void testZoom_Neutral() {
reset();
imagePanel.setZoomFactor(1.0f);
assertTrue("Zoom factor not set to 1.0x",
Float.compare(imagePanel.getZoomFactor(), 1.0f) == 0);
}
@Test
public void testZoom_10Point0f() {
reset();
imagePanel.setZoomFactor(10.0f);
assertTrue("Zoom factor not set to 10.0x",
Float.compare(imagePanel.getZoomFactor(), 10.0f) == 0);
}
@Test
public void testZoom_0Point05() {
reset();
imagePanel.setZoomFactor(0.05f);
assertTrue("Zoom factor not set to 0.05x",
Float.compare(imagePanel.getZoomFactor(), 0.05f) == 0);
}
@Test
public void testZoom_20Point0() {
reset();
imagePanel.setZoomFactor(20.0f);
assertTrue("Zoom factor not set to 20.0x; should be 10.0x",
Float.compare(imagePanel.getZoomFactor(), 10.0f) == 0);
}
@Test
public void testZoom_0Point001() {
reset();
imagePanel.setZoomFactor(0.001f);
assertTrue("Zoom factor not set to 0.001x; should be 0.05x",
Float.compare(imagePanel.getZoomFactor(), 0.05f) == 0);
}
@Test
public void testZoom_3Point75() {
reset();
imagePanel.setZoomFactor(3.75f);
assertTrue("Zoom factor not set to 3.75x; should be 4.0x",
Float.compare(imagePanel.getZoomFactor(), 4.0f) == 0);
}
}
@@ -0,0 +1,290 @@
/* ###
* 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.textfield;
import static org.junit.Assert.*;
import java.math.BigInteger;
import java.util.concurrent.atomic.AtomicIntegerArray;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.junit.*;
import docking.test.AbstractDockingTest;
public class IntegerTextFieldTest extends AbstractDockingTest {
private JFrame frame;
private IntegerTextField field;
private JTextField textField;
@Before
public void setUp() throws Exception {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
field = new IntegerTextField(10);
field.setShowNumberMode(true);
textField = (JTextField) field.getComponent();
frame = new JFrame("Test");
frame.getContentPane().add(field.getComponent());
frame.pack();
frame.setVisible(true);
}
@After
public void tearDown() throws Exception {
frame.setVisible(false);
}
@Test
public void testDefaultState() {
assertNull(field.getValue());// no value
assertEquals(0, field.getIntValue());// the "int value" return for null is 0
assertEquals(0, field.getLongValue());
assertTrue(!field.isHexMode());
assertNull(field.getMaxValue());
}
@Test
public void testTypeValidDecimalNumber() {
triggerText(textField, "123");
assertEquals(123, field.getIntValue());
}
@Test
public void testTypeValidHexNumber() {
triggerText(textField, "0x2abcdef");
assertEquals(0x2abcdef, field.getIntValue());
}
@Test
public void testInvalidCharsIgnored() {
triggerText(textField, "123ghijklmnopqrstuvwxyz4");
assertEquals(1234, field.getIntValue());
}
@Test
public void testHexCharsIgnoredInDecimalMode() {
assertTrue(!field.isHexMode());
triggerText(textField, "123ghijklmnopqrstuvwxyz4");
assertEquals(1234, field.getIntValue());
}
@Test
public void testXchangesHexMode() {
assertTrue(!field.isHexMode());
triggerText(textField, "0");
assertTrue(!field.isHexMode());
triggerText(textField, "x");
assertTrue(field.isHexMode());
triggerBackspaceKey(textField);
assertTrue(!field.isHexMode());
}
@Test
public void testHexModeWithoutPrefix() {
triggerText(textField, "abc");// not allowed when using hex prefix, so expect empty
assertEquals(null, field.getValue());
field.setAllowsHexPrefix(false);
field.setHexMode();
triggerText(textField, "abc");
assertEquals(0xabc, field.getIntValue());
}
@Test
public void testNegative() {
triggerText(textField, "-123");
assertEquals(-123, field.getIntValue());
}
@Test
public void testNegativeHex() {
triggerText(textField, "-0xa");
assertEquals(-10, field.getIntValue());
}
@Test
public void testNegativeNotAllowed() {
field.setAllowNegativeValues(false);
triggerText(textField, "-123");
assertEquals(123, field.getIntValue());
}
@Test
public void testSetNegativeWithCurrentNegativeValue() {
field.setValue(-123);
field.setAllowNegativeValues(false);
assertEquals(null, field.getValue());
}
@Test
public void testMax() {
field.setMaxValue(BigInteger.valueOf(13l));
triggerText(textField, "12");
assertEquals(12, field.getIntValue());
field.setValue(null);
triggerText(textField, "13");
assertEquals(13, field.getIntValue());
field.setValue(null);
triggerText(textField, "14");// four should be ignored
assertEquals(1, field.getIntValue());
}
@Test
public void testSetMaxToValueSmallerThanCurrent() {
field.setValue(500);
field.setMaxValue(BigInteger.valueOf(400));
assertEquals(400, field.getIntValue());
}
@Test
public void testMaxInHex() {
field.setMaxValue(BigInteger.valueOf(0xd));
triggerText(textField, "0xc");
assertEquals(12, field.getIntValue());
field.setValue(null);
triggerText(textField, "0xd");
assertEquals(13, field.getIntValue());
field.setValue(null);
triggerText(textField, "0xe");// e should be ignored
assertEquals(0, field.getIntValue());
}
@Test
public void testSwitchingHexMode() {
field.setValue(255);
assertEquals("255", field.getText());
field.setHexMode();
assertEquals("0xff", field.getText());
field.setDecimalMode();
assertEquals("255", field.getText());
}
@Test
public void testChangeListenerAfterValidInput() {
TestChangeListener listener = new TestChangeListener();
field.addChangeListener(listener);
triggerText(textField, "123");
assertEquals(3, listener.count);
assertEquals(1, listener.values.get(0));
assertEquals(12, listener.values.get(1));
assertEquals(123, listener.values.get(2));
triggerBackspaceKey(textField);
assertEquals(12, listener.values.get(3));
}
@Test
public void testChangeListenerAfterSwitchingModes() {
triggerText(textField, "123");
TestChangeListener listener = new TestChangeListener();
field.addChangeListener(listener);
setHexMode();
assertEquals(2, listener.count);
assertEquals(123, listener.values.get(1));
}
@Test
public void testNegativeHexFromValue() {
field.setValue(-255);
setHexMode();
assertEquals("-0xff", field.getText());
}
@Test
public void testNullValue() {
field.setValue(12);
assertEquals("12", field.getText());
field.setValue(null);
assertEquals("", field.getText());
assertEquals(0, field.getIntValue());
assertEquals(0l, field.getLongValue());
assertEquals(null, field.getValue());
}
@Test
public void testHexValueInDontRequireHexPrefixMode() {
field.setAllowsHexPrefix(false);
field.setHexMode();
field.setValue(255);
assertEquals("ff", field.getText());
}
@Test
public void testSetNotAllowNegativeModeWhileCurrentValueIsNegative() {
field.setValue(-10);
field.setAllowNegativeValues(false);
assertEquals("", field.getText());
assertEquals(0, field.getIntValue());
}
@Test
public void testSetLongValue() {
field.setValue(100L);
assertEquals(100L, field.getLongValue());
assertEquals(100, field.getIntValue());
}
@Test
public void testSettingNegativeNumberWhenNegativesArentAllowed() {
field.setValue(10);
field.setAllowNegativeValues(false);
field.setValue(-10);
assertEquals("", field.getText());
}
@Test
public void testUseHexPrefixUpdatesTextField() {
field.setAllowsHexPrefix(false);
field.setHexMode();
field.setValue(255);
assertEquals("ff", field.getText());
field.setAllowsHexPrefix(true);
assertEquals("0xff", field.getText());
}
private void setHexMode() {
runSwing(() -> field.setHexMode());
waitForSwing();
}
class TestChangeListener implements ChangeListener {
volatile int count;
private AtomicIntegerArray values = new AtomicIntegerArray(10);
@Override
public void stateChanged(ChangeEvent e) {
values.set(count++, field.getIntValue());
}
}
}
@@ -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<GTreeNode>());
}
@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<GTreeNode> 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<GTreeNode> 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<GTreeNode> 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;
}
}
}
@@ -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<GTreeNode> allChildren = rootNode.getAllChildren();
typeFilterText("Many B1");
clearFilterText();
List<GTreeNode> 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<GTreeNode>());
}
@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<GTreeNode> 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<GTreeNode> 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;
}
}
}
@@ -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<GTreeNode>());
}
@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<GTreeNode> 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<GTreeNode> 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<GTreeNode> 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;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,67 @@
/* ###
* 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.util.task;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import docking.test.AbstractDockingTest;
import generic.test.AbstractGenericTest;
public class TaskMonitorSplitterTest extends AbstractDockingTest {
TaskMonitor baseMonitor;
public TaskMonitorSplitterTest() {
super();
baseMonitor = new TaskMonitorComponent();
}
@Test
public void testBasicUse() {
TaskMonitor[] monitors = TaskMonitorSplitter.splitTaskMonitor(baseMonitor, 4);
monitors[0].initialize(100);
monitors[0].setProgress(1);
assertEquals(1, monitors[0].getProgress());
assertEquals(TaskMonitorSplitter.MONITOR_SIZE / 400, baseMonitor.getProgress());
monitors[0].incrementProgress(1);
assertEquals(2 * TaskMonitorSplitter.MONITOR_SIZE / 400, baseMonitor.getProgress());
monitors[0].setProgress(10);
assertEquals(10 * TaskMonitorSplitter.MONITOR_SIZE / 400, baseMonitor.getProgress());
}
@Test
public void testMaxSettings() {
TaskMonitor[] monitors = TaskMonitorSplitter.splitTaskMonitor(baseMonitor, 4);
monitors[0].initialize(100);
monitors[0].setProgress(50);
assertEquals(50 * TaskMonitorSplitter.MONITOR_SIZE / 400, baseMonitor.getProgress());
monitors[0].setMaximum(25);
assertEquals(25, monitors[0].getMaximum());
assertEquals(TaskMonitorSplitter.MONITOR_SIZE / 4, baseMonitor.getProgress());
monitors[0].setMaximum(100);
assertEquals(25 * TaskMonitorSplitter.MONITOR_SIZE / 400, baseMonitor.getProgress());
}
}
@@ -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;
@@ -15,13 +15,16 @@
*/
package utilities.util;
import static generic.test.AbstractGenericTest.assertListEqualsArrayOrdered;
import static generic.test.AbstractGTest.assertListEqualsArrayOrdered;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.*;
import java.io.File;
import java.io.IOException;
import java.io.*;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.Test;
@@ -159,4 +162,62 @@ public class FileUtilitiesTest {
assertFalse(FileUtilities.isPathContainedWithin(new File("/a/b"), new File("/a/b/../c")));
assertFalse(FileUtilities.isPathContainedWithin(new File("/a/b"), new File("/a/bc")));
}
@Test
public void copyFile_ResourceFile_To_ResourceFile() throws Exception {
File from = File.createTempFile("from.file", ".txt");
FileUtilities.writeLinesToFile(from, Arrays.asList("From file contents"));
from.deleteOnExit();
File to = File.createTempFile("to.file", ".txt");
to.deleteOnExit();
FileUtilities.writeLinesToFile(to, Arrays.asList("To file contents"));
FileUtilities.copyFile(new ResourceFile(from), new ResourceFile(to), null);
String text = FileUtils.readFileToString(to, Charset.defaultCharset());
assertThat(text, equalTo("From file contents\n"));
}
@Test(expected = IOException.class)
public void copyFile_ExceptionFromInputStream() throws Exception {
ResourceFile from = new ResourceFile(new File("/fake.from.file")) {
@Override
public InputStream getInputStream() throws IOException {
throw new IOException("Test Exception");
}
};
File to = File.createTempFile("to.file", ".txt");
to.deleteOnExit();
// should fail
FileUtilities.copyFile(from, new ResourceFile(to), null);
}
@Test(expected = IOException.class)
public void copyFile_ExceptionFromOutputStream() throws Exception {
File from = File.createTempFile("from.file", ".txt");
from.deleteOnExit();
ResourceFile to = new ResourceFile(new File("/to.from.file")) {
@Override
public OutputStream getOutputStream() throws FileNotFoundException {
throw new FileNotFoundException("Test Exception");
}
};
// should fail
FileUtilities.copyFile(new ResourceFile(from), to, null);
}
public void copyFile_WithMonitor() {
// too slow due to the nature of how the progress is reported in chunks--we would
// have to generate too much data, which would take seconds to test that progress
// is correctly reported
}
}
@@ -0,0 +1,196 @@
/* ###
* 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 help;
import static org.junit.Assert.assertNotNull;
import java.io.IOException;
import java.nio.file.*;
import org.junit.After;
import org.junit.Before;
import generic.test.AbstractGenericTest;
import utilities.util.FileUtilities;
public abstract class AbstractHelpTest extends AbstractGenericTest {
protected static final String HELP_FILENAME_PREFIX = "Fake";
protected static final String HELP_FILENAME = HELP_FILENAME_PREFIX + ".html";
private Path testTempDir;
public AbstractHelpTest() {
super();
}
@Before
public void setUp() throws Exception {
testTempDir = Files.createTempDirectory(testName.getMethodName());
}
@After
public void tearDown() throws Exception {
FileUtilities.deleteDir(testTempDir.toFile());
}
protected Path createHelpBuildOutputDir() throws IOException {
Path out = testTempDir.resolve("build/help/main/help");
Files.createDirectories(out);
return out;
}
protected Path createFakeHelpTopic(Path helpDir) throws IOException {
return createFakeHelpTopic("FakeTopic", helpDir);
}
protected Path createFakeHelpTopic(String topicName, Path helpDir) throws IOException {
Path topicsDir = helpDir.resolve("topics");
Path fakeTopicDir = topicsDir.resolve(topicName);
Files.createDirectories(fakeTopicDir);
return fakeTopicDir;
}
protected Path createTempHelpDir() throws IOException {
Path helpDir = testTempDir.resolve("help");
Files.createDirectory(helpDir);
return helpDir;
}
protected void addRequiredHelpDirStructure(Path helpDir) throws IOException {
// HelpFile wants to read one of these, so put one there
createEmpty_TOC_Source_File(helpDir);
createSharedDir(helpDir);
}
protected Path createSharedDir(Path helpDir) throws IOException {
Path sharedDir = helpDir.resolve("shared");
Files.createDirectory(sharedDir);
Path css = sharedDir.resolve("Frontpage.css");
Files.createFile(css);
Path png = sharedDir.resolve("test.png");
Files.createFile(png);
return sharedDir;
}
protected Path createEmpty_TOC_Source_File(Path dir) throws IOException {
Path fullTOCPath = dir.resolve("TOC_Source.xml");
Path file = Files.createFile(fullTOCPath);
//@formatter:off
String TOCXML = "<?xml version='1.0' encoding='ISO-8859-1' ?>\n" +
"<!-- Auto-generated on Fri Apr 03 09:37:08 EDT 2015 -->\n\n" +
"<tocroot>\n" +
"</tocroot>\n";
//@formatter:on
Files.write(file, TOCXML.getBytes(), StandardOpenOption.CREATE);
return file;
}
protected Path createHelpContent(Path topic, String anchor) throws IOException {
Path htmlPath = topic.resolve(HELP_FILENAME);
Path file = Files.createFile(htmlPath);
if (anchor == null) {
anchor = "Default_Anchor";
}
//@formatter:off
String HTML =
"<HTML>\n" +
"<HEAD>\n" +
"<TITLE>Configure Tool</TITLE>\n" +
"<LINK rel=\"stylesheet\" type=\"text/css\" href=\"../../shared/Frontpage.css\">\n" +
"</HEAD>\n" +
"<BODY>\n" +
" <H1><A name=\""+anchor+"\"></A>Configure Tool</H1>\n" +
" Some text with reference to shared image <IMG src=\"../../shared/test.png\">\n" +
" \n" +
"</BODY>\n" +
"</HTML>\n";
//@formatter:on
Files.write(file, HTML.getBytes(), StandardOpenOption.CREATE);
return file;
}
protected Path createHelpContent_WithReferenceHREF(Path topic, String HREF) throws IOException {
Path htmlPath = topic.resolve(HELP_FILENAME);
Path file = Files.createFile(htmlPath);
assertNotNull("Must specify the A tag HREF attribute", HREF);
//@formatter:off
String HTML =
"<HTML>\n" +
"<HEAD>\n" +
"<TITLE>Configure Tool</TITLE>\n" +
"<LINK rel=\"stylesheet\" type=\"text/css\" href=\"../../shared/Frontpage.css\">\n" +
"</HEAD>\n" +
"<BODY>\n" +
" <H1><A name=\"Fake_Anchor\"></A>Configure Tool</H1>\n" +
" And this is a link <A HREF=\""+HREF+"\">Click Me</A>" +
" \n" +
"</BODY>\n" +
"</HTML>\n";
//@formatter:on
Files.write(file, HTML.getBytes(), StandardOpenOption.CREATE);
return file;
}
protected Path createHelpContent_WithReferenceIMG_SRC(Path topic, String SRC)
throws IOException {
Path htmlPath = topic.resolve(HELP_FILENAME);
Path file = Files.createFile(htmlPath);
assertNotNull("Must specify the A tag SRC attribute", SRC);
//@formatter:off
String HTML =
"<HTML>\n" +
"<HEAD>\n" +
"<TITLE>Configure Tool</TITLE>\n" +
"<LINK rel=\"stylesheet\" type=\"text/css\" href=\"../../shared/Frontpage.css\">\n" +
"</HEAD>\n" +
"<BODY>\n" +
" <H1><A name=\"Fake_Anchor\"></A>Configure Tool</H1>\n" +
" Some text with reference to shared image <IMG src=\""+SRC+"\">\n" +
" \n" +
"</BODY>\n" +
"</HTML>\n";
//@formatter:on
Files.write(file, HTML.getBytes(), StandardOpenOption.CREATE);
return file;
}
protected void copy(Path from, Path to) throws Exception {
FileUtilities.copyDir(from.toFile(), to.toFile(), null);
}
}
@@ -0,0 +1,99 @@
/* ###
* 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 help;
import static org.junit.Assert.*;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.Test;
public class HelpBuildUtilsTest extends AbstractHelpTest {
private static final String HELP_TOPIC_PATH = "/some/fake/path/to/help/topics";
private static final String TOPIC_AND_FILENAME = "FooTopic/FooFile.html";
private static final String HTML_FILE_PATH = HELP_TOPIC_PATH + '/' + TOPIC_AND_FILENAME;
public HelpBuildUtilsTest() {
super();
}
@Test
public void testGetRelativeHelpPath() {
String relativeString = "help/topics/FooTopic/FooFile.html";
Path path = Paths.get("/some/fake/path/to/" + relativeString);
Path relative = HelpBuildUtils.relativizeWithHelpTopics(path);
assertEquals(relativeString, relative.toString());
}
@Test
public void testGetRelativeHelpPath_NoHelpTopicInPath() {
String invalidRelativeString = "help/topicz/" + TOPIC_AND_FILENAME;
Path path = Paths.get("/some/fake/path/to/" + invalidRelativeString);
Path relative = HelpBuildUtils.relativizeWithHelpTopics(path);
assertNull(relative);
}
@Test
public void testLocateReference_Local_HelpSystemSyntax() throws URISyntaxException {
Path sourceFile = Paths.get(HTML_FILE_PATH);
String reference = "help/topics/shared/foo.png";
Path resolved = HelpBuildUtils.locateReference(sourceFile, reference);
assertEquals("Help System syntax was not preserved", Paths.get(reference), resolved);
}
@Test
public void testLocateReference_Local_RelativeSyntax() throws URISyntaxException {
Path sourceFile = Paths.get(HTML_FILE_PATH);
String reference = "../shared/foo.png";// go up one to the help dir
Path resolved = HelpBuildUtils.locateReference(sourceFile, reference);
assertEquals("Relative syntax did not locate file",
Paths.get(HELP_TOPIC_PATH + "/shared/foo.png"), resolved);
}
@Test
public void testLocateReference_Remote() throws URISyntaxException {
Path sourceFile = Paths.get(HTML_FILE_PATH);
String reference = "http://some.fake.server/foo.png";
Path resolved = HelpBuildUtils.locateReference(sourceFile, reference);
assertNull(resolved);
boolean isRemote = HelpBuildUtils.isRemote(reference);
assertTrue(isRemote);
}
@Test
public void testLocateReferences_Icons() throws URISyntaxException {
Path sourceFile = Paths.get(HTML_FILE_PATH);
String reference = "Icons.REFRESH_ICON"; // see Icons class
ImageLocation location = HelpBuildUtils.locateImageReference(sourceFile, reference);
Path resolved = location.getResolvedPath();
String name = resolved.getFileName().toString();
assertEquals("Help System syntax was not preserved", "reload3.png", name);
assertTrue(location.isRuntime());
assertFalse(location.isRemote());
}
@Test
public void testLocateReferences_Icons_BadName() throws URISyntaxException {
Path sourceFile = Paths.get(HTML_FILE_PATH);
String reference = "Icons.REFRESH_ICON_BAD"; // non-existent
ImageLocation location = HelpBuildUtils.locateImageReference(sourceFile, reference);
Path resolved = location.getResolvedPath();
assertNull(resolved);
}
}
@@ -0,0 +1,418 @@
/* ###
* 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 help;
import static org.junit.Assert.assertEquals;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.help.HelpSet;
import org.junit.Test;
import help.validator.LinkDatabase;
import help.validator.location.*;
import help.validator.model.*;
public class OverlayHelpTreeTest {
@Test
public void testSourceTOCFileThatDependsUponPreBuiltHelp() {
//
// We want to make sure the overlay tree will properly resolve help TOC items being
// built from TOC_Source.xml files when that file uses <TOCREF> items that are defined
// in a help <TOCITEM> that lives inside of a pre-built jar file.
//
/*
Example makeup we will create:
PreBuild_TOC.xml
<tocitem id="root" target="fake">
<tocitem id="child_1" target="fake" />
</tocitem>
TOC_Source.xml
<tocref id="root">
<tocref="child_1">
<tocdef id="child_2" target="fake" />
</tocref>
</tocref>
*/
TOCItemExternal root = externalItem("root");
TOCItemExternal child_1 = externalItem(root, "child_1");
Path tocSourceFile = Paths.get("/fake/path_2/TOC_Source.xml");
String root_ID = root.getIDAttribute();
TOCItemReference root_ref = referenceItem(root_ID, tocSourceFile);
String child_1_ID = child_1.getIDAttribute();
TOCItemReference child_1_ref = referenceItem(root_ref, child_1_ID, tocSourceFile);
TOCItemDefinition child_2 = definitionItem(child_1_ref, "child_2", tocSourceFile);
TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub();
tocProvider.addExternal(root);
tocProvider.addExternal(child_1);
tocProvider.addDefinition(child_2);
TOCSpyWriter spy = printOverlayTree(tocProvider, tocSourceFile);
assertNodeCount(spy, 3);
assertOrder(spy, 1, root);
assertOrder(spy, 2, child_1);
assertOrder(spy, 3, child_2);
}
@Test
public void testSourceTOCFileThatDependsUponPreBuiltHelp_MultiplePreBuiltInputs() {
//
// We want to make sure the overlay tree will properly resolve help TOC items being
// built from TOC_Source.xml files when that file uses <TOCREF> items that are defined
// in a help <TOCITEM> that lives inside of multiple pre-built jar files.
//
/*
Example makeup we will create:
PreBuild_TOC.xml
<tocitem id="root" target="fake">
<tocitem id="child_1" target="fake">
<tocitem="prebuilt_a_child" target="fake" />
</tocitem>
</tocitem>
Another PreBuild_TOC.xml
<tocitem id="root" target="fake">
<tocitem id="child_1" target="fake">
<tocitem="prebuilt_b_child" target="fake" />
</tocitem>
</tocitem>
TOC_Source.xml
<tocref id="root">
<tocref="child_1">
<tocdef id="child_2" target="fake" />
</tocref>
</tocref>
*/
TOCItemExternal root_a = externalItem("root");
TOCItemExternal child_1_a = externalItem(root_a, "child_1");
TOCItemExternal prebuilt_a_child = externalItem(child_1_a, "prebuilt_a_child");
// note: same ID values, since they represent the same nodes, but from different TOC files
TOCItemExternal root_b = externalItemAlt(null, "root");
TOCItemExternal child_1_b = externalItemAlt(root_b, "child_1");
TOCItemExternal prebuilt_b_child = externalItemAlt(child_1_b, "prebuilt_b_child");
Path tocSourceFile = Paths.get("/fake/path_2/TOC_Source.xml");
String root_ID = root_a.getIDAttribute();
TOCItemReference root_ref = referenceItem(root_ID, tocSourceFile);
String child_1_ID = child_1_a.getIDAttribute();
TOCItemReference child_1_ref = referenceItem(root_ref, child_1_ID, tocSourceFile);
TOCItemDefinition child_2 = definitionItem(child_1_ref, "child_2", tocSourceFile);
TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub();
tocProvider.addExternal(root_a);
tocProvider.addExternal(root_b);
tocProvider.addExternal(child_1_a);
tocProvider.addExternal(child_1_b);
tocProvider.addExternal(prebuilt_a_child);
tocProvider.addExternal(prebuilt_b_child);
tocProvider.addDefinition(child_2);
TOCSpyWriter spy = printOverlayTree(tocProvider, tocSourceFile);
assertNodeCount(spy, 3);
assertOrder(spy, 1, root_a);// could also be root_b, same ID
assertOrder(spy, 2, child_1_a);// could also be child_1_b, same ID
assertOrder(spy, 3, child_2);
// note: prebuilt_a_child and prebuilt_b_child don't get output, since they do not have
// the same TOC file ID as the help file being processed (in other words, they don't
// live in the TOC_Source.xml being processes, so they are not part of the output).
}
@Test
public void testSourceTOCFileThatDependsAnotherTOCSourceFile() {
/*
The first source file defines attributes that the second file references.
Example makeup we will create:
TOC_Source.xml
<tocdef id="root" target="fake">
<tocdef id="child_1" target="fake" />
</tocdef>
Another TOC_Source.xml
<tocref id="root">
<tocref="child_1">
<tocdef id="child_2" target="fake" />
</tocref>
</tocref>
*/
Path toc_1 = Paths.get("/fake/path_1/TOC_Source.xml");
TOCItemDefinition root = definitionItem("root", toc_1);
TOCItemDefinition child_1 = definitionItem(root, "child_1", toc_1);
Path toc_2 = Paths.get("/fake/path_2/TOC_Source.xml");
String root_ID = root.getIDAttribute();
String child_1_ID = child_1.getIDAttribute();
TOCItemReference root_ref = referenceItem(root_ID, toc_2);
TOCItemReference child_1_ref = referenceItem(root_ref, child_1_ID, toc_2);
TOCItemDefinition child_2 = definitionItem(child_1_ref, "child_2", toc_2);
TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub();
tocProvider.addDefinition(root);
tocProvider.addDefinition(child_1);
tocProvider.addDefinition(child_2);// in the second TOC file
TOCSpyWriter spy = printOverlayTree(tocProvider, toc_2);
assertNodeCount(spy, 3);
assertOrder(spy, 1, root);
assertOrder(spy, 2, child_1);
assertOrder(spy, 3, child_2);
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private TOCSpyWriter printOverlayTree(TOCItemProviderTestStub tocItemProvider, Path tocFile) {
//
// Create a test version of the LinkDatabase for the overlay tree, with test versions of
// it's required TOC input file and HelpModuleLocation
//
GhidraTOCFileDummy toc = new GhidraTOCFileDummy(tocFile);
OverlayHelpModuleLocationTestStub location = new OverlayHelpModuleLocationTestStub(toc);
LinkDatabaseTestStub db = new LinkDatabaseTestStub(location);
// This is the class we are testing!!
OverlayHelpTree overlayHelpTree = new OverlayHelpTree(tocItemProvider, db);
TOCSpyWriter spy = new TOCSpyWriter();
String TOCID = tocFile.toUri().toString();
overlayHelpTree.printTreeForID(spy, TOCID);
System.out.println(spy.toString());
return spy;
}
private TOCItemDefinition definitionItem(String ID, Path tocSourceFile) {
return definitionItem(null, ID, tocSourceFile);
}
private TOCItemDefinition definitionItem(TOCItem parent, String ID, Path tocSourceFile) {
String target = "fake";
String sort = "";
int line = 1;
return new TOCItemDefinition(parent, tocSourceFile, ID, ID, target, sort, line);
}
private TOCItemReference referenceItem(String referenceID, Path tocSourceFile) {
return referenceItem(null, referenceID, tocSourceFile);
}
private TOCItemReference referenceItem(TOCItem parent, String referenceID, Path tocSourceFile) {
return new TOCItemReference(parent, tocSourceFile, referenceID, 1);
}
private TOCItemExternal externalItem(String ID) {
return externalItem(null, ID);
}
private TOCItemExternal externalItem(TOCItem parent, String ID) {
Path tocFile = Paths.get("/fake/path_1/PreBuild_TOC.xml");
String target = "fake";
String sort = "";
int line = 1;
return new TOCItemExternal(parent, tocFile, ID, ID, target, sort, line);
}
private TOCItemExternal externalItemAlt(TOCItem parent, String ID) {
Path tocFile = Paths.get("/fake/path_1/PreBuild_TOC.xml");
String target = "fake";
String sort = "";
int line = 1;
return new TOCItemExternal(parent, tocFile, ID, ID, target, sort, line);
}
private void assertOrder(TOCSpyWriter spy, int ordinal, TOCItem item) {
String ID = spy.getItem(ordinal - 1 /* make an index */);
assertEquals("Did not find TOC item at expected index: " + ordinal, item.getIDAttribute(),
ID);
}
private void assertNodeCount(TOCSpyWriter spy, int count) {
assertEquals("Did not get exactly one node per TOC item input", count, spy.getItemCount());
}
private class TOCSpyWriter extends PrintWriter {
private StringWriter stringWriter;
private List<String> tocItems = new ArrayList<>();
public TOCSpyWriter() {
super(new StringWriter(), true);
stringWriter = ((StringWriter) out);
}
String getItem(int position) {
return tocItems.get(position);
}
int getItemCount() {
return tocItems.size();
}
@Override
public void println(String s) {
super.println(s);
s = s.trim();
if (!s.startsWith("<tocitem")) {
return;
}
storeDisplayAttribute(s);
}
private void storeDisplayAttribute(String s) {
// create a pattern to pull out the display string
Pattern p = Pattern.compile(".*display=\"(.*)\" toc_id.*");
Matcher matcher = p.matcher(s.trim());
if (!matcher.matches()) {
return;// not a TOC item
}
String value = matcher.group(1);
tocItems.add(value);
}
@Override
public String toString() {
return stringWriter.getBuffer().toString();
}
}
private class TOCItemProviderTestStub implements TOCItemProvider {
Map<String, TOCItemExternal> externals = new HashMap<>();
Map<String, TOCItemDefinition> definitions = new HashMap<>();
void addExternal(TOCItemExternal item) {
String displayText = item.getIDAttribute();
externals.put(displayText, item);
}
void addDefinition(TOCItemDefinition item) {
String ID = item.getIDAttribute();
definitions.put(ID, item);
}
@Override
public Map<String, TOCItemExternal> getTOCItemExternalsByDisplayMapping() {
return externals;
}
@Override
public Map<String, TOCItemDefinition> getTOCItemDefinitionsByIDMapping() {
return definitions;
}
}
private class LinkDatabaseTestStub extends LinkDatabase {
public LinkDatabaseTestStub(HelpModuleLocation loc) {
super(HelpModuleCollection.fromHelpLocations(Collections.singleton(loc)));
}
@Override
public String getIDForLink(String target) {
return "test_ID_" + target;
}
}
private class OverlayHelpModuleLocationTestStub extends HelpModuleLocationTestDouble {
OverlayHelpModuleLocationTestStub(GhidraTOCFileDummy toc) {
super(Paths.get("/fake/help"));
this.sourceTOCFile = toc;
}
@Override
protected void loadHelpTopics() {
// no! ...don't really go to the filesystem
}
@Override
public GhidraTOCFile loadSourceTOCFile() {
return null;// we set this in the constructor
}
@Override
public HelpSet loadHelpSet() {
return null;
}
@Override
public boolean isHelpInputSource() {
return true;
}
}
private class GhidraTOCFileDummy extends GhidraTOCFileTestDouble {
public GhidraTOCFileDummy(Path path) {
super(path);
}
}
}
@@ -0,0 +1,28 @@
/* ###
* 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 help.validator.location;
import java.nio.file.Path;
import help.validator.location.HelpModuleLocation;
public abstract class HelpModuleLocationTestDouble extends HelpModuleLocation {
// this class exists to open up the package-level constructor
public HelpModuleLocationTestDouble(Path source) {
super(source);
}
}
@@ -0,0 +1,70 @@
/* ###
* 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 help.validator.model;
import static org.junit.Assert.assertEquals;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.Assert;
import org.junit.Test;
import generic.test.AbstractGenericTest;
import ghidra.util.exception.AssertException;
public class AnchorDefinitionTest extends AbstractGenericTest {
public AnchorDefinitionTest() {
super();
}
@Test
public void testFileWithoutAnchor() {
// this should generate and ID that is the filename only
Path fullPath = Paths.get("/fake/full/path/help/topics/TopicName/HelpFilename.html"); // dir case
AnchorDefinition def = new AnchorDefinition(fullPath, null, 1);
assertEquals("TopicName_HelpFilename", def.getId());
}
@Test
public void testFileInHelpTopicDir() {
Path fullPath = Paths.get("/fake/full/path/help/topics/TopicName/HelpFilename.html"); // dir case
AnchorDefinition def = new AnchorDefinition(fullPath, "anchor_1", 1);
assertEquals("TopicName_anchor_1", def.getId());
}
@Test
public void testFileInHelpTopicJar() {
Path fullPath = Paths.get("/help/topics/TopicName/HelpFilename.html"); // jar case
AnchorDefinition def = new AnchorDefinition(fullPath, "anchor_1", 1);
assertEquals("TopicName_anchor_1", def.getId());
}
@Test
public void testFileInHelpDir_NotUnderHelpTopic() {
Path fullPath = Paths.get("/fake/full/path/help/HelpFilename.html"); // dir case
try {
new AnchorDefinition(fullPath, "anchor_1", 1);
Assert.fail("Did not fail with file not living under a help topic directory");
}
catch (AssertException e) {
// good
}
}
}
@@ -0,0 +1,28 @@
/* ###
* 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.
* 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 help.validator.model;
import java.nio.file.Path;
public class GhidraTOCFileTestDouble extends GhidraTOCFile {
// this class exists to open up constructor access
public GhidraTOCFileTestDouble(Path sourceFile) {
super(sourceFile);
}
}
@@ -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 help.validator.model;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.nio.file.*;
import java.util.Collection;
import org.junit.Test;
import help.AbstractHelpTest;
import help.validator.*;
import help.validator.location.DirectoryHelpModuleLocation;
public class HelpFileTest extends AbstractHelpTest {
@Test
public void testGoodHTML() throws IOException {
Path helpDir = createTempHelpDir();
addRequiredHelpDirStructure(helpDir);
Path topic = createFakeHelpTopic(helpDir);
DirectoryHelpModuleLocation helpLocation =
new DirectoryHelpModuleLocation(helpDir.toFile());
Path html = createGoodHTMLFile(topic);
new HelpFile(helpLocation, html);
// if we get here, then no exceptions happened
}
@Test
public void testBadHTML_InvalidStyleSheet() throws Exception {
Path helpDir = createTempHelpDir();
addRequiredHelpDirStructure(helpDir);
Path topic = createFakeHelpTopic(helpDir);
DirectoryHelpModuleLocation helpLocation =
new DirectoryHelpModuleLocation(helpDir.toFile());
Path html = createBadHTMLFile_InvalidStyleSheet(topic);
try {
new HelpFile(helpLocation, html);
fail("Parsing did not fail for invalid stylesheet");
}
catch (Exception e) {
// good
}
}
@Test
public void testBadHTML_InvalidAnchorRef_BadURI() throws Exception {
Path helpDir = createTempHelpDir();
addRequiredHelpDirStructure(helpDir);
Path topic = createFakeHelpTopic(helpDir);
DirectoryHelpModuleLocation helpLocation =
new DirectoryHelpModuleLocation(helpDir.toFile());
Path html = createBadHTMLFile_InvalidAnchor_BadURI(topic);
try {
new HelpFile(helpLocation, html);
fail("Parsing did not fail for invalid stylesheet");
}
catch (Exception e) {
// good
}
}
@Test
public void testBadHTML_InvalidAnchorRef_WrongAttribtues() throws Exception {
// no 'name' or 'href' attribute
Path helpDir = createTempHelpDir();
addRequiredHelpDirStructure(helpDir);
Path topic = createFakeHelpTopic(helpDir);
DirectoryHelpModuleLocation helpLocation =
new DirectoryHelpModuleLocation(helpDir.toFile());
Path html = createBadHTMLFile_InvalidAnchor_WrongAttributes(topic);
try {
new HelpFile(helpLocation, html);
fail("Parsing did not fail for invalid stylesheet");
}
catch (Exception e) {
// good
}
}
@Test
public void testBadHTML_InvalidIMG_WrongAttribtues() throws Exception {
// no 'src'
Path helpDir = createTempHelpDir();
addRequiredHelpDirStructure(helpDir);
Path topic = createFakeHelpTopic(helpDir);
DirectoryHelpModuleLocation helpLocation =
new DirectoryHelpModuleLocation(helpDir.toFile());
Path html = createBadHTMLFile_InvalidIMG_WrongAttributes(topic);
try {
new HelpFile(helpLocation, html);
fail("Parsing did not fail for invalid stylesheet");
}
catch (Exception e) {
// good
}
}
@Test
public void testCommentGetsIgnored() throws Exception {
Path helpDir = createTempHelpDir();
addRequiredHelpDirStructure(helpDir);
Path topic = createFakeHelpTopic(helpDir);
DirectoryHelpModuleLocation helpLocation =
new DirectoryHelpModuleLocation(helpDir.toFile());
Path html = createGoodHTMLFile_InvalidAnchor_CommentedOut_MultiLineComment(topic);
HelpFile helpFile = new HelpFile(helpLocation, html);
Collection<HREF> hrefs = helpFile.getAllHREFs();
assertTrue(hrefs.isEmpty());
}
// @Test
// for debugging a real help file
public void test() throws Exception {
Path path = Paths.get("<home dir>/<git>/ghidra/Ghidra/Features/" +
"Base/src/main/help/help/topics/Annotations/Annotations.html");
Path helpDir = createTempHelpDir();
addRequiredHelpDirStructure(helpDir);
DirectoryHelpModuleLocation helpLocation =
new DirectoryHelpModuleLocation(helpDir.toFile());
AnchorManager anchorManager = new AnchorManager();
ReferenceTagProcessor tagProcessor = new ReferenceTagProcessor(helpLocation, anchorManager);
HTMLFileParser.scanHtmlFile(path, tagProcessor);
}
//==================================================================================================
// Private Methods
//==================================================================================================
/** Has valid links */
private Path createGoodHTMLFile(Path topic) throws IOException {
String anchor = "ManagePluginsDialog";
return createHelpContent(topic, anchor);
}
private Path createBadHTMLFile_InvalidAnchor_WrongAttributes(Path topic) throws IOException {
Path htmlPath = topic.resolve("FakeHTML_WrongAttributes.html");
Path file = Files.createFile(htmlPath);
String badAttr = "bob=1";
//@formatter:off
String HTML =
"<HTML>\n" +
"<HEAD>\n" +
"<TITLE>Configure Tool</TITLE>\n" +
"<LINK rel=\"stylesheet\" type=\"text/css\" href=\"../../shared/Frontpage.css\">\n" +
"</HEAD>\n" +
"<BODY>\n" +
"<H1><A name=\"ManagePluginsDialog\"></A>Configure Tool</H1>\n" +
"Some text with reference to shared image <a "+badAttr+">Click me</a>\n" +
"\n" +
"</BODY>\n" +
"</HTML>\n";
//@formatter:on
Files.write(file, HTML.getBytes(), StandardOpenOption.CREATE);
return file;
}
private Path createBadHTMLFile_InvalidIMG_WrongAttributes(Path topic) throws IOException {
Path htmlPath = topic.resolve("FakeHTML_WrongAttributes.html");
Path file = Files.createFile(htmlPath);
String badAttr = "bob=1";
//@formatter:off
String HTML =
"<HTML>\n" +
"<HEAD>\n" +
"<TITLE>Configure Tool</TITLE>\n" +
"<LINK rel=\"stylesheet\" type=\"text/css\" href=\"../../shared/Frontpage.css\">\n" +
"</HEAD>\n" +
"<BODY>\n" +
"<H1><A name=\"ManagePluginsDialog\"></A>Configure Tool</H1>\n" +
"Some text with reference to shared image <IMG "+badAttr+"s>\n" +
"\n" +
"</BODY>\n" +
"</HTML>\n";
//@formatter:on
Files.write(file, HTML.getBytes(), StandardOpenOption.CREATE);
return file;
}
private Path createBadHTMLFile_InvalidAnchor_BadURI(Path topic) throws IOException {
Path htmlPath = topic.resolve("FakeHTML_BadURI.html");
Path file = Files.createFile(htmlPath);
String badURI = ":baduri"; // no scheme name on this URI
//@formatter:off
String HTML =
"<HTML>\n" +
"<HEAD>\n" +
"<TITLE>Configure Tool</TITLE>\n" +
"<LINK rel=\"stylesheet\" type=\"text/css\" href=\"../../shared/Frontpage.css\">\n" +
"</HEAD>\n" +
"<BODY>\n" +
"<H1><A name=\"ManagePluginsDialog\"></A>Configure Tool</H1>\n" +
"Some text with reference to shared image <a href=\""+badURI+"\">Click me</a>\n" +
"\n" +
"</BODY>\n" +
"</HTML>\n";
//@formatter:on
Files.write(file, HTML.getBytes(), StandardOpenOption.CREATE);
return file;
}
private Path createBadHTMLFile_InvalidStyleSheet(Path topic) throws IOException {
Path htmlPath = topic.resolve("FakeHTML_InvalidStyleSheet.html");
Path file = Files.createFile(htmlPath);
String badName = "bad_name";
//@formatter:off
String HTML =
"<HTML>\n" +
"<HEAD>\n" +
"<TITLE>Configure Tool</TITLE>\n" +
"<LINK rel=\"stylesheet\" type=\"text/css\" href=\"../../shared/"+badName+".css\">\n" +
"</HEAD>\n" +
"<BODY>\n" +
"<H1><A name=\"ManagePluginsDialog\"></A>Configure Tool</H1>\n" +
"Some text with reference to shared image <IMG src=\"../../shared/test.png\">\n" +
"\n" +
"</BODY>\n" +
"</HTML>\n";
//@formatter:on
Files.write(file, HTML.getBytes(), StandardOpenOption.CREATE);
return file;
}
private Path createGoodHTMLFile_InvalidAnchor_CommentedOut_MultiLineComment(Path topic)
throws IOException {
Path htmlPath = topic.resolve("HTMLWithComment.html");
Path file = Files.createFile(htmlPath);
String badURI = ":baduri"; // no scheme name on this URI
//@formatter:off
String HTML =
"<HTML>\n" +
"<HEAD>\n" +
"<TITLE>Configure Tool</TITLE>\n" +
"<LINK rel=\"stylesheet\" type=\"text/css\" href=\"../../shared/Frontpage.css\">\n" +
"</HEAD>\n" +
"<BODY>\n" +
"<H1><A name=\"ManagePluginsDialog\"></A>Configure Tool</H1>\n" +
" <!--" +
" Some text with reference to shared image <a href=\""+badURI+"\">Click me</a>\n" +
" -->" +
"\n" +
"</BODY>\n" +
"</HTML>\n";
//@formatter:on
Files.write(file, HTML.getBytes(), StandardOpenOption.CREATE);
return file;
}
}
@@ -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;
@@ -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;
@@ -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;
/**
*
@@ -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.
@@ -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;
@@ -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;
/**
@@ -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;
@@ -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<ToolIconURL> {
private Border emptyBorder = BorderFactory.createEmptyBorder(5, 5, 5, 5);
@@ -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;
@@ -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
@@ -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 {
@@ -1,2 +1,3 @@
##VERSION: 2.0
build.gradle||GHIDRA||||END|
src/test.slow/java/ghidra/pcodeCPort/slgh_compile/pcode1.xml||GHIDRA||||END|
@@ -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());
}
}
@@ -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);
}
}
}
@@ -0,0 +1,172 @@
/* ###
* 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.script;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import org.junit.*;
import org.junit.experimental.categories.Category;
import generic.jar.ResourceFile;
import generic.test.category.NightlyCategory;
import ghidra.app.services.ProgramManager;
import ghidra.framework.Application;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.RefType;
import ghidra.program.model.symbol.Reference;
import ghidra.test.*;
@Category(NightlyCategory.class)
public class WindowsResourceReferenceScriptTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env;
private File script;
@Before
public void setUp() throws Exception {
env = new TestEnv();
ResourceFile resourceFile =
Application.getModuleFile("Decompiler", "ghidra_scripts/WindowsResourceReference.java");
script = resourceFile.getFile(true);
}
private void openProgram(Program program) {
ProgramManager pm = env.getTool().getService(ProgramManager.class);
pm.openProgram(program.getDomainFile());
}
private void closeProgram() {
ProgramManager pm = env.getTool().getService(ProgramManager.class);
pm.closeProgram();
waitForPostedSwingRunnables();
}
@After
public void tearDown() throws Exception {
env.dispose();
}
/*
* Checks against known result set of addresses for use in regression testing.
* Checks each address location of created reference and checks
* that the correct format of address has been created to the resource
*/
@Test
public void testWinmineNormalCases() throws Exception {
Reference[] refs; //Array of mnemonic references
RefType type; //Type of reference
Instruction inst;
Boolean isAddr;
Program program = env.getProgram("Winmine__XP.exe.gzf");
openProgram(program);
ScriptTaskListener scriptId = env.runScript(script);
waitForScriptCompletion(scriptId, 65000);
program.flushEvents();
waitForPostedSwingRunnables();
Listing listing = program.getListing();
//Fill array with addresses of reference results
Address[] winmineTestAddrs = propagateWinMineTestAddrs(program);
for (Address winmineTestAddr : winmineTestAddrs) {
inst = listing.getInstructionAt(winmineTestAddr);
refs = inst.getMnemonicReferences();
//Check a reference exists on the Mnemonic
assertNotNull(refs);
type = refs[0].getReferenceType();
isAddr = refs[0].getToAddress().isMemoryAddress();
//Check the reference is a real memory address
assertTrue(isAddr);
//Check the reference type created is of type DATA
assertTrue(type.equals(RefType.DATA));
}
closeProgram();
}
@Test
public void testMIPNormalCases() throws Exception {
Reference[] refs; //Array of mnemonic references
RefType type; //Type of reference
Boolean isAddr;
Instruction inst;
Program program = env.getProgram("mip.exe.gzf");
openProgram(program);
ScriptTaskListener scriptID = env.runScript(script);
waitForScriptCompletion(scriptID, 60000);
program.flushEvents();
waitForPostedSwingRunnables();
Listing listing = program.getListing();
Address[] mipTestAddrs = propagateMIPTestAddrs(program);
for (Address mipTestAddr : mipTestAddrs) {
inst = listing.getInstructionAt(mipTestAddr);
refs = inst.getMnemonicReferences();
//Check a reference exists on the mnemonic
assertNotNull(refs);
type = refs[0].getReferenceType();
isAddr = refs[0].getToAddress().isMemoryAddress();
//check the reference is a real memory address
assertTrue(isAddr);
//check the reference type created is of type DATA
assertTrue(type.equals(RefType.DATA));
}
closeProgram();
}
private Address addr(long offset, Program program) {
return program.getAddressFactory().getDefaultAddressSpace().getAddress(offset);
}
/*
* Creates and returns the known result set to check against for use
* in regression testing
*/
protected Address[] propagateWinMineTestAddrs(Program pgm) {
Address[] winmineTestAddrs = { addr(0x01001b99, pgm), addr(0x01001bc2, pgm),
addr(0x01001b5e, pgm), addr(0x010022c2, pgm), addr(0x01002243, pgm),
addr(0x01003d52, pgm), addr(0x010022ac, pgm), addr(0x01002334, pgm),
addr(0x01001f3b, pgm), addr(0x0100398f, pgm), addr(0x01003ade, pgm),
addr(0x01003aec, pgm), addr(0x01003ad0, pgm), addr(0x010039c5, pgm),
addr(0x01003d45, pgm), addr(0x0100385b, pgm), addr(0x01003d36, pgm),
addr(0x01003920, pgm), addr(0x0100390e, pgm) };
return winmineTestAddrs;
}
protected Address[] propagateMIPTestAddrs(Program pgm) {
Address[] mipTestAddrs =
{ addr(0x1400172c7L, pgm), addr(0x14005282dL, pgm), addr(0x14005276cL, pgm),
addr(0x1400523baL, pgm), addr(0x14004ca38L, pgm), addr(0x14003d855L, pgm),
addr(0x14001a964L, pgm), addr(0x14001846fL, pgm), addr(0x140025c87L, pgm) };
return mipTestAddrs;
}
}
@@ -0,0 +1,195 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.bin.format.dwarf4.next;
import static org.junit.Assert.assertNotNull;
import java.io.IOException;
import org.jdom.JDOMException;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import generic.jar.ResourceFile;
import generic.test.category.NightlyCategory;
import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import ghidra.util.xml.XmlUtilities;
import utilities.util.FileResolutionResult;
import utilities.util.FileUtilities;
@Category(NightlyCategory.class)
public class DWARFRegisterMappingsTest extends AbstractGhidraHeadlessIntegrationTest {
private Language x86Lang;
@Before
public void setup() throws Exception {
x86Lang = DefaultLanguageService.getLanguageService().getLanguage(
new LanguageID("x86:LE:32:default"));
}
/**
* Test reading the DWARF register mappings for every language that has a DWARF register
* mapping file specified in its LDEF file.
* @throws IOException
*/
@Test
public void testReadMappings() throws IOException {
for (LanguageDescription langDesc : DefaultLanguageService.getLanguageService().getLanguageDescriptions(
false)) {
if (!DWARFRegisterMappingsManager.hasDWARFRegisterMapping(langDesc)) {
continue;
}
Language lang =
DefaultLanguageService.getLanguageService().getLanguage(langDesc.getLanguageID());
ResourceFile mappingFile =
DWARFRegisterMappingsManager.getDWARFRegisterMappingFileFor(lang);
FileResolutionResult dwarfFileFRR;
if ((dwarfFileFRR = FileUtilities.existsAndIsCaseDependent(mappingFile)) != null &&
!dwarfFileFRR.isOk()) {
throw new IOException(
"DWARF register mapping filename case problem: " + dwarfFileFRR.getMessage());
}
DWARFRegisterMappings drm = DWARFRegisterMappingsManager.readMappingForLang(lang);
assertNotNull("DWARF mapping read failed for " + langDesc.getLanguageID(), drm);
}
}
@Test
public void testOkMappings() throws JDOMException, IOException {
DWARFRegisterMappingsManager.readMappingFrom(
XmlUtilities.fromString(
"<dwarf><register_mappings><register_mapping dwarf=\"0\" ghidra=\"EAX\" stackpointer=\"true\"/></register_mappings><call_frame_cfa value=\"4\"/></dwarf>"),
x86Lang);
}
@Test(expected = IOException.class)
public void testMissingStackPointerMappings() throws JDOMException, IOException {
DWARFRegisterMappingsManager.readMappingFrom(
XmlUtilities.fromString(
"<dwarf><register_mappings><register_mapping dwarf=\"0\" ghidra=\"EAX\"/></register_mappings><call_frame_cfa value=\"4\"/></dwarf>"),
x86Lang);
}
@Test(expected = IOException.class)
public void testBadStackPointerAttrMappings() throws JDOMException, IOException {
DWARFRegisterMappingsManager.readMappingFrom(
XmlUtilities.fromString(
"<dwarf><register_mappings><register_mapping dwarf=\"0\" ghidra=\"EAX\" stackpointer=\"NOT_A_BOOLEAN_STRING\"/></register_mappings><call_frame_cfa value=\"4\"/></dwarf>"),
x86Lang);
}
@Test(expected = IOException.class)
public void testMissingMappingsElemMappings() throws JDOMException, IOException {
DWARFRegisterMappingsManager.readMappingFrom(XmlUtilities.fromString("<dwarf></dwarf>"),
x86Lang);
}
@Test(expected = IOException.class)
public void testBadDuplicateMapping() throws JDOMException, IOException {
DWARFRegisterMappingsManager.readMappingFrom(
XmlUtilities.fromString(
"<dwarf><register_mappings><register_mapping dwarf=\"0\" ghidra=\"EAX\" stackpointer=\"true\"/><register_mapping dwarf=\"0\" ghidra=\"EBX\"/></register_mappings></dwarf>"),
x86Lang);
}
@Test
public void testGoodDuplicateMapping() throws JDOMException, IOException {
DWARFRegisterMappingsManager.readMappingFrom(
XmlUtilities.fromString(
"<dwarf><register_mappings><register_mapping dwarf=\"0\" ghidra=\"EAX\" stackpointer=\"true\"/><register_mapping dwarf=\"1\" ghidra=\"EAX\"/></register_mappings></dwarf>"),
x86Lang);
}
@Test(expected = IOException.class)
public void testBadDWARFRegVal() throws JDOMException, IOException {
DWARFRegisterMappingsManager.readMappingFrom(
XmlUtilities.fromString(
"<dwarf><register_mappings><register_mapping missing_dwarf_attrib=\"0\" ghidra=\"EAX\" stackpointer=\"true\"/></register_mappings></dwarf>"),
x86Lang);
}
@Test(expected = IOException.class)
public void testBadDWARFRegVal2() throws JDOMException, IOException {
DWARFRegisterMappingsManager.readMappingFrom(
XmlUtilities.fromString(
"<dwarf><register_mappings><register_mapping dwarf=\"not_a_number\" ghidra=\"EAX\" stackpointer=\"true\"/></register_mappings></dwarf>"),
x86Lang);
}
@Test(expected = IOException.class)
public void testMissingGhidraReg() throws JDOMException, IOException {
DWARFRegisterMappingsManager.readMappingFrom(
XmlUtilities.fromString(
"<dwarf><register_mappings><register_mapping dwarf=\"0\" missing_ghidra_attr=\"EAX\" stackpointer=\"true\"/></register_mappings></dwarf>"),
x86Lang);
}
@Test(expected = IOException.class)
public void testBadGhidraReg() throws JDOMException, IOException {
DWARFRegisterMappingsManager.readMappingFrom(
XmlUtilities.fromString(
"<dwarf><register_mappings><register_mapping dwarf=\"0\" ghidra=\"BLAH\" stackpointer=\"true\"/></register_mappings></dwarf>"),
x86Lang);
}
@Test
public void testAutoInc() throws JDOMException, IOException {
DWARFRegisterMappingsManager.readMappingFrom(
XmlUtilities.fromString(
"<dwarf><register_mappings><register_mapping dwarf=\"0\" ghidra=\"EAX\" stackpointer=\"true\"/><register_mapping dwarf=\"1\" ghidra=\"ST0\" auto_count=\"8\"/></register_mappings></dwarf>"),
x86Lang);
}
@Test(expected = IOException.class)
public void testTooLargeAutoIncCount() throws JDOMException, IOException {
DWARFRegisterMappingsManager.readMappingFrom(
XmlUtilities.fromString(
"<dwarf><register_mappings><register_mapping dwarf=\"0\" ghidra=\"EAX\" stackpointer=\"true\"/><register_mapping dwarf=\"1\" ghidra=\"ST0\" auto_count=\"16\"/></register_mappings></dwarf>"),
x86Lang);
}
@Test(expected = IOException.class)
public void testBadFormatAutoIncCount() throws JDOMException, IOException {
DWARFRegisterMappingsManager.readMappingFrom(
XmlUtilities.fromString(
"<dwarf><register_mappings><register_mapping dwarf=\"0\" ghidra=\"EAX\" stackpointer=\"true\"/><register_mapping dwarf=\"1\" ghidra=\"ST0\" auto_count=\"foo\"/></register_mappings></dwarf>"),
x86Lang);
}
@Test(expected = IOException.class)
public void testBadCFAValue() throws JDOMException, IOException {
DWARFRegisterMappingsManager.readMappingFrom(
XmlUtilities.fromString(
"<dwarf><register_mappings><register_mapping dwarf=\"0\" ghidra=\"EAX\" stackpointer=\"true\"/></register_mappings><call_frame_cfa value=\"foo\"/></dwarf>"),
x86Lang);
}
@Test(expected = IOException.class)
public void testNegCFAValue() throws JDOMException, IOException {
DWARFRegisterMappingsManager.readMappingFrom(
XmlUtilities.fromString(
"<dwarf><register_mappings><register_mapping dwarf=\"0\" ghidra=\"EAX\" stackpointer=\"true\"/></register_mappings><call_frame_cfa value=\"-1\"/></dwarf>"),
x86Lang);
}
}
@@ -0,0 +1,248 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.demangler;
import static org.junit.Assert.*;
import org.junit.*;
import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Listing;
import ghidra.program.model.symbol.*;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import ghidra.test.ToyProgramBuilder;
import ghidra.util.task.TaskMonitor;
public class DemangledAddressTableTest extends AbstractGhidraHeadlessIntegrationTest {
private ProgramDB program;
private int txID;
@Before
public void setUp() throws Exception {
ToyProgramBuilder builder = new ToyProgramBuilder("test", true);
builder.createMemory(".text", "0x0100", 0x100);
// Extent of Address Table determined by one of the following in order of precedence:
// 1. Size of undefined array at start of table
// 2. Next label beyond start which resides within current block
// 3. End of block containing table
// Address Pointers will only be created if extent of table contains only
// Undefined types or pointers
//@formatter:off
builder.setBytes("0x0110", new byte[] {
0, 0, 1, 0x40,
0, 0, 1, 0x41,
0, 0, 1, 0x42,
0, 0, 1, 0x43,
0, 0, 1, 0x44,
0, 0, 1, 0x45,
0, 0, 1, 0x46,
0, 0, 0, 0, // should be skipped
0, 0, 1, 0x48,
0, 0, 2, 0x49, // should stop table (no memory)
0, 0, 1, 0x4a,
0, 0, 1, 0x4b
});
//@formatter:on
program = builder.getProgram();
txID = program.startTransaction("Test");
}
@After
public void tearDown() throws Exception {
program.endTransaction(txID, false);
}
/**
* Test that the DemangledAddressTable will properly create a sequence of data
* pointers. This test deals with the simple case where no existing data
* is present. End of block considered end of address table.
*/
@Test
public void testApply_NoNextSymbol_NoData() throws Exception {
// this is: vtable for UnqiueSpace
String mangled = "_ZTV11UniqueSpace";
Address addr = addr("0x0110");
SymbolTable symbolTable = program.getSymbolTable();
symbolTable.createLabel(addr, mangled, SourceType.IMPORTED);
DemangledObject demangled = DemanglerUtil.demangle(mangled);
assertTrue(demangled instanceof DemangledAddressTable);
assertTrue(demangled.applyTo(program, addr, new DemanglerOptions(), TaskMonitor.DUMMY));
// expected: UniqueSpace::vtable
Symbol[] symbols = symbolTable.getSymbols(addr);
assertEquals(2, symbols.length);
assertEquals("vtable", symbols[0].getName());
assertEquals(mangled, symbols[1].getName());
Namespace ns = symbols[0].getParentNamespace();
assertEquals("UniqueSpace", ns.getName(false));
assertPointersAt(0, 0, "0x110", "0x114", "0x118", "0x11c", "0x120", "0x124", "0x128",
/* skip 0 */ "0x130");
}
/**
* Test that the DemangledAddressTable will not create a sequence of data
* pointers due to a data collision. This test deals with the case where primitive types have been
* previously created. End of block considered end of address table.
*/
@Test
public void testApply_NoNextSymbol_DataCollision() throws Exception {
// this is: vtable for UnqiueSpace
String mangled = "_ZTV11UniqueSpace";
Address addr = addr("0x0110");
SymbolTable symbolTable = program.getSymbolTable();
symbolTable.createLabel(addr, mangled, SourceType.IMPORTED);
Listing listing = program.getListing();
listing.createData(addr("0x114"), Undefined4DataType.dataType);
listing.createData(addr("0x118"), Undefined4DataType.dataType);
listing.createData(addr("0x120"), DWordDataType.dataType);
DemangledObject demangled = DemanglerUtil.demangle(mangled);
assertTrue(demangled instanceof DemangledAddressTable);
assertTrue(demangled.applyTo(program, addr, new DemanglerOptions(), TaskMonitor.DUMMY));
// expected: UniqueSpace::vtable
Symbol[] symbols = symbolTable.getSymbols(addr);
assertEquals(2, symbols.length);
assertEquals("vtable", symbols[0].getName());
assertEquals(mangled, symbols[1].getName());
Namespace ns = symbols[0].getParentNamespace();
assertEquals("UniqueSpace", ns.getName(false));
// should fail to create pointers since table region (to end of block) contains
// data other than pointer or undefined
assertPointersAt(3, 0);
}
/**
* Test that the DemangledAddressTable will properly create a sequence of data
* pointers. This test deals with the case where primitive types have been
* previously created. Next label considered end of address table.
*/
@Test
public void testApply_WithNextSymbol_UndefinedData() throws Exception {
// this is: vtable for UnqiueSpace
String mangled = "_ZTV11UniqueSpace";
Address addr = addr("0x0110");
SymbolTable symbolTable = program.getSymbolTable();
symbolTable.createLabel(addr, mangled, SourceType.IMPORTED);
Listing listing = program.getListing();
listing.createData(addr("0x114"), Undefined4DataType.dataType);
listing.createData(addr("0x118"), Undefined4DataType.dataType);
listing.createData(addr("0x120"), DWordDataType.dataType);
symbolTable.createLabel(addr("0x120"), "NextLabel", SourceType.IMPORTED);
DemangledObject demangled = DemanglerUtil.demangle(mangled);
assertTrue(demangled instanceof DemangledAddressTable);
assertTrue(demangled.applyTo(program, addr, new DemanglerOptions(), TaskMonitor.DUMMY));
// expected: UniqueSpace::vtable
Symbol[] symbols = symbolTable.getSymbols(addr);
assertEquals(2, symbols.length);
assertEquals("vtable", symbols[0].getName());
assertEquals(mangled, symbols[1].getName());
Namespace ns = symbols[0].getParentNamespace();
assertEquals("UniqueSpace", ns.getName(false));
assertPointersAt(1, 0, "0x110", "0x114", "0x118", "0x11c");
}
/**
* Test that the DemangledAddressTable will properly create a sequence of data
* pointers. This test deals with the case where primitive types have been
* previously created where the first is an undefined array which dictates the
* extent of the address table. Next label beyond end of address table.
*/
@Test
public void testApply_WithUndefinedArray() throws Exception {
// this is: vtable for UnqiueSpace
String mangled = "_ZTV11UniqueSpace";
Address addr = addr("0x0110");
SymbolTable symbolTable = program.getSymbolTable();
symbolTable.createLabel(addr, mangled, SourceType.IMPORTED);
Listing listing = program.getListing();
listing.createData(addr("0x110"), Undefined.getUndefinedDataType(12));
listing.createData(addr("0x11c"), Undefined4DataType.dataType);
listing.createData(addr("0x120"), DWordDataType.dataType);
symbolTable.createLabel(addr("0x120"), "NextLabel", SourceType.IMPORTED);
DemangledObject demangled = DemanglerUtil.demangle(mangled);
assertTrue(demangled instanceof DemangledAddressTable);
assertTrue(demangled.applyTo(program, addr, new DemanglerOptions(), TaskMonitor.DUMMY));
// expected: UniqueSpace::vtable
Symbol[] symbols = symbolTable.getSymbols(addr);
assertEquals(2, symbols.length);
assertEquals("vtable", symbols[0].getName());
assertEquals(mangled, symbols[1].getName());
Namespace ns = symbols[0].getParentNamespace();
assertEquals("UniqueSpace", ns.getName(false));
assertPointersAt(2, 0, "0x110", "0x114", "0x118");
}
private void assertPointersAt(int totalNonPointerData, int totalInstructions, String... addrs) {
Listing listing = program.getListing();
int index = 0;
for (Data d : listing.getDefinedData(true)) {
if (d.isPointer()) {
assertTrue("too many pointers found, expected only " + addrs.length,
index < addrs.length);
assertEquals("unexpected pointer at " + d.getAddress(), addr(addrs[index++]),
d.getAddress());
}
}
assertEquals("insufficient pointers created", addrs.length, index);
assertEquals("missing expected non-pointer data", totalNonPointerData,
listing.getNumDefinedData() - index);
assertEquals("missing expected instructions", totalInstructions,
listing.getNumInstructions());
}
private Address addr(String addr) {
return program.getAddressFactory().getAddress(addr);
}
}
@@ -0,0 +1,356 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.demangler;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.junit.*;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.data.VoidDataType;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import ghidra.test.ToyProgramBuilder;
import ghidra.util.task.TaskMonitor;
public class DemangledFunctionTest extends AbstractGhidraHeadlessIntegrationTest {
private ProgramBuilder programBuilder;
private ProgramDB program;
private int txID;
@Before
public void setUp() throws Exception {
programBuilder = new ToyProgramBuilder("test", true);
programBuilder.createMemory(".text", "0x0100", 0x100);
program = programBuilder.getProgram();
txID = program.startTransaction("Test");
}
@After
public void tearDown() throws Exception {
program.endTransaction(txID, false);
}
/**
* Test that the DemangledFunction will properly create a cascade of namespaces for
* functions that live inside of a class that lives inside of a namespace.
* This test applies a demangled name where the mangled name does NOT exist.
*/
@Test
public void testApply_FunctionInClassInNamespace() throws Exception {
// this is: public long __thiscall ATL::CRegKey::Close(void)
String mangled = "?CloseM@CRegKeyM@ATL@@QAEJXZ";
DemangledObject demangled = DemanglerUtil.demangle(mangled);
assertTrue(demangled instanceof DemangledFunction);
Address addr = addr("0x0101");
assertTrue(demangled.applyTo(program, addr, new DemanglerOptions(), TaskMonitor.DUMMY));
assertFunction("CloseM", addr);
SymbolTable symbolTable = program.getSymbolTable();
// expected: ATL::CRegKey::Close
Symbol[] symbols = symbolTable.getSymbols(addr);
assertEquals(1, symbols.length);
assertEquals("CloseM", symbols[0].getName());
Namespace ns = symbols[0].getParentNamespace();
assertEquals("CRegKeyM", ns.getName(false));
ns = ns.getParentNamespace();
assertEquals("ATL", ns.getName(false));
}
/**
* Test that the DemangledFunction will properly update a thunk function
* with its namespace, and ripple through to the underlying default thunked
* function. The thunk 'this' parameter should utilize the Class
* within which the thunk resides.
*/
@Test
public void testApplyToThunk_FunctionInClassInNamespace() throws Exception {
// this is: public long __thiscall ATL::CRegKey::Close(void)
String mangled = "?Close@CRegKey@ATL@@QAEJXZ";
DemangledObject demangled = DemanglerUtil.demangle(mangled);
assertTrue(demangled instanceof DemangledFunction);
FunctionManager functionMgr = program.getFunctionManager();
Function f1 = functionMgr.createFunction(null, addr("0x0110"),
new AddressSet(addr("0x0110")), SourceType.DEFAULT);
Function f2 = functionMgr.createFunction(mangled, addr("0x0100"),
new AddressSet(addr("0x0100")), SourceType.IMPORTED);
f2.setThunkedFunction(f1);
Address addr = addr("0x0100");
demangled.applyTo(program, addr, new DemanglerOptions(), TaskMonitor.DUMMY);
assertFunction("Close", addr);
assertNoBookmarkAt(addr);
SymbolTable symbolTable = program.getSymbolTable();
// expected: ATL::CRegKey::Close
Symbol[] symbols = symbolTable.getSymbols(addr);
assertEquals(2, symbols.length); // both mangled and demangled
assertTrue(symbols[0].isPrimary());
assertEquals("Close", symbols[0].getName());
Namespace ns = symbols[0].getParentNamespace();
assertEquals("CRegKey", ns.getName(false));
ns = ns.getParentNamespace();
assertEquals("ATL", ns.getName(false));
Parameter[] parameters = f2.getParameters();
assertEquals("Missing parameter", 1, parameters.length);
assertTrue(parameters[0].isAutoParameter());
assertEquals("The 'this' param has incorrect type", "CRegKey *",
parameters[0].getDataType().getDisplayName());
}
/**
* Test that the DemangledFunction will properly create a cascade of namespaces for
* functions that live inside of a class that lives inside of a namespace.
* This test applies a demangled name where the mangled name exists.
*/
@Test
public void testApply_FunctionInClassInNamespace2() throws Exception {
// this is: public long __thiscall ATL::CRegKey::Close(void)
String mangled = "?Close@CRegKey@ATL@@QAEJXZ";
Address addr = addr("0x0101");
SymbolTable symbolTable = program.getSymbolTable();
symbolTable.createLabel(addr, mangled, SourceType.IMPORTED);
DemangledObject demangled = DemanglerUtil.demangle(mangled);
assertTrue(demangled instanceof DemangledFunction);
assertTrue(demangled.applyTo(program, addr, new DemanglerOptions(), TaskMonitor.DUMMY));
assertFunction("Close", addr);
assertNoBookmarkAt(addr);
// expected: ATL::CRegKey::Close
Symbol[] symbols = symbolTable.getSymbols(addr);
assertEquals(2, symbols.length);
assertEquals("Close", symbols[0].getName());
assertEquals(mangled, symbols[1].getName());
Namespace ns = symbols[0].getParentNamespace();
assertEquals("CRegKey", ns.getName(false));
ns = ns.getParentNamespace();
assertEquals("ATL", ns.getName(false));
}
/**
* Test that the DemangledFunction will properly create a cascade of namespaces for
* functions that live inside of a class that lives inside of a namespace.
* This test applies a demangled name where the mangled name exists with address suffix.
*/
@Test
public void testApply_FunctionInClassInNamespace3() throws Exception {
// this is: public long __thiscall ATL::CRegKey::Close(void)
String mangled = "?Close@CRegKey@ATL@@QAEJXZ";
Address addr = addr("0x0101");
SymbolTable symbolTable = program.getSymbolTable();
String mangledWithAddr = SymbolUtilities.getAddressAppendedName(mangled, addr);
symbolTable.createLabel(addr, mangledWithAddr, SourceType.IMPORTED);
DemangledObject demangled = DemanglerUtil.demangle(mangled);
assertTrue(demangled instanceof DemangledFunction);
assertTrue(demangled.applyTo(program, addr, new DemanglerOptions(), TaskMonitor.DUMMY));
assertFunction("Close", addr);
assertNoBookmarkAt(addr);
// expected: ATL::CRegKey::Close
Symbol[] symbols = symbolTable.getSymbols(addr);
assertEquals(2, symbols.length);
assertEquals("Close", symbols[0].getName());
assertEquals(mangledWithAddr, symbols[1].getName());
Namespace ns = symbols[0].getParentNamespace();
assertEquals("CRegKey", ns.getName(false));
ns = ns.getParentNamespace();
assertEquals("ATL", ns.getName(false));
}
/**
* Test that the DemangledFunction will properly create a cascade of namespaces for
* functions that live inside of a class that lives inside of a namespace.
* This test applies a demangled name where both the mangled name exists and
* the simple demangled name exists in the global space.
*/
@Test
public void testApply_FunctionInClassInNamespace4() throws Exception {
// this is: public long __thiscall ATL::CRegKey::Close(void)
String mangled = "?Close@CRegKey@ATL@@QAEJXZ";
Address addr = addr("0x0101");
SymbolTable symbolTable = program.getSymbolTable();
symbolTable.createLabel(addr, "Close", SourceType.IMPORTED);
symbolTable.createLabel(addr, mangled, SourceType.IMPORTED);
DemangledObject demangled = DemanglerUtil.demangle(mangled);
assertTrue(demangled instanceof DemangledFunction);
assertTrue(demangled.applyTo(program, addr, new DemanglerOptions(), TaskMonitor.DUMMY));
assertFunction("Close", addr);
assertNoBookmarkAt(addr);
// expected: ATL::CRegKey::Close
Symbol[] symbols = symbolTable.getSymbols(addr);
assertEquals(2, symbols.length);
assertEquals("Close", symbols[0].getName());
assertEquals(mangled, symbols[1].getName());
Namespace ns = symbols[0].getParentNamespace();
assertEquals("CRegKey", ns.getName(false));
ns = ns.getParentNamespace();
assertEquals("ATL", ns.getName(false));
}
/**
* Test that the DemangledFunction will properly create a cascade of namespaces for
* functions that live inside of a class that lives inside of a namespace.
* This test applies a demangled name where the mangled name exists on an external
* location symbol.
*/
@Test
public void testApply_ExtrnalFunctionInClassInNamespace() throws Exception {
// this is: public long __thiscall ATL::CRegKey::Close(void)
String mangled = "?Close@CRegKey@ATL@@QAEJXZ";
ExternalManager externalManager = program.getExternalManager();
Library lib = externalManager.addExternalLibraryName("MY.DLL", SourceType.IMPORTED);
ExternalLocation extLoc =
externalManager.addExtLocation(lib, mangled, null, SourceType.IMPORTED);
Address addr = extLoc.getExternalSpaceAddress();
DemangledObject demangled = DemanglerUtil.demangle(mangled);
assertTrue(demangled instanceof DemangledFunction);
assertTrue(demangled.applyTo(program, addr, new DemanglerOptions(), TaskMonitor.DUMMY));
assertFunction("Close", addr);
assertNoBookmarkAt(addr);
SymbolTable symbolTable = program.getSymbolTable();
// expected: ATL::CRegKey::Close
Symbol[] symbols = symbolTable.getSymbols(addr);
assertEquals(1, symbols.length);
assertEquals("Close", symbols[0].getName());
Namespace ns = symbols[0].getParentNamespace();
assertEquals("CRegKey", ns.getName(false));
ns = ns.getParentNamespace();
assertEquals("ATL", ns.getName(false));
extLoc = externalManager.getExternalLocation(symbols[0]);
assertNotNull(extLoc);
assertEquals("Close", extLoc.getLabel());
assertEquals(mangled, extLoc.getOriginalImportedName());
}
@Test
public void testFunctionVariable() throws Exception {
//
// This makes sure that a variable inside of a function namespace prevents a class
// namespace object from being created when a function does not exist. Instead it should
// create a simple namespace.
//
String mangled = "_ZZ18__gthread_active_pvE20__gthread_active_ptr";
String functionName = "__gthread_active_p";
programBuilder.createEmptyFunction(functionName, "0x0101", 10, new VoidDataType());
programBuilder.createLabel("0x0103", mangled);
DemangledObject demangled = DemanglerUtil.demangle(program, mangled);
assertNotNull(demangled);
assertTrue(demangled instanceof DemangledVariable);
assertEquals("__gthread_active_p()::__gthread_active_ptr", demangled.getSignature(false));
Address addr = addr("0x0103");
demangled.applyTo(program, addr, new DemanglerOptions(), TaskMonitor.DUMMY);
assertSimpleNamespaceExists("__gthread_active_p()");
assertNoBookmarkAt(addr);
SymbolTable symbolTable = program.getSymbolTable();
Symbol[] symbols = symbolTable.getSymbols(addr);
assertEquals(2, symbols.length);
assertEquals("_ZZ18__gthread_active_pvE20__gthread_active_ptr", symbols[1].getName());
assertEquals("__gthread_active_ptr", symbols[0].getName());
Namespace ns = symbols[0].getParentNamespace();
assertEquals("__gthread_active_p()", ns.getName(false));
}
private void assertNoBookmarkAt(Address addr) {
BookmarkManager bm = program.getBookmarkManager();
Bookmark[] bookmarks = bm.getBookmarks(addr);
if (bookmarks.length > 0) {
fail("Expected no bookmark; found " + Arrays.toString(bookmarks));
}
}
private void assertSimpleNamespaceExists(String name) {
SymbolTable symbolTable = program.getSymbolTable();
Namespace ns = symbolTable.getNamespace(name, program.getGlobalNamespace());
assertNotNull(ns);
assertEquals(SymbolType.NAMESPACE, ns.getSymbol().getSymbolType());
}
private void assertFunction(String name, Address addr) {
FunctionManager fm = program.getFunctionManager();
Function function = fm.getFunctionAt(addr);
assertNotNull("Expected function to get created at " + addr, function);
assertEquals(name, function.getName());
}
private Address addr(String addr) {
return program.getAddressFactory().getAddress(addr);
}
}
@@ -0,0 +1,81 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.demangler.gnu;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import ghidra.app.cmd.label.DemanglerCmd;
import ghidra.app.util.demangler.*;
import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.Address;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import ghidra.test.ToyProgramBuilder;
public class GnuDemanglerIntegrationTest extends AbstractGhidraHeadlessIntegrationTest {
private ProgramDB program;
@Before
public void setUp() throws Exception {
ToyProgramBuilder builder = new ToyProgramBuilder("test", true);
builder.createMemory(".text", "0x01001000", 0x100);
program = builder.getProgram();
}
@Test
public void testExceptionOnFailToDemangle() throws Exception {
String mangled = "?InvokeHelper@CWnd@@QAAXJGGPAXPBEZZ_00912eaf";
DemanglerCmd cmd = new DemanglerCmd(addr("01001000"), mangled);
// this used to trigger an exception
cmd.applyTo(program);
}
@Test
public void testDemangler_Format_EDG_DemangleOnlyKnownPatterns_False_DollarInNamespace()
throws DemangledException {
String mangled = "MyFunction__11MyNamespacePQ215$ParamNamespace9paramName";
GnuDemangler demangler = new GnuDemangler();
demangler.canDemangle(program);// this perform initialization
DemangledObject result = demangler.demangle(mangled, false);
assertNotNull(result);
assertEquals("undefined MyNamespace::MyFunction($ParamNamespace::paramName *)",
result.getSignature(false));
DemanglerOptions options = new DemanglerOptions();
options.setDemangleOnlyKnownPatterns(false);
DemanglerCmd cmd = new DemanglerCmd(addr("01001000"), mangled, options);
// this used to trigger an exception
boolean success = applyCmd(program, cmd);
assertTrue("Demangler command failed: " + cmd.getStatusMsg(), success);
assertNotNull(cmd.getDemangledObject());
}
private Address addr(String address) {
return program.getAddressFactory().getAddress(address);
}
}
@@ -0,0 +1,94 @@
/* ###
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.headless;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Locale;
import sun.java2d.HeadlessGraphicsEnvironment;
public class MyHeadlessGraphicsEnvironment extends GraphicsEnvironment {
static volatile boolean swingErrorRegistered = false;
private static String preferredGraphicsEnv;
private GraphicsEnvironment localEnv;
static void setup() {
//System.setProperty("java.awt.headless", "true");
preferredGraphicsEnv = System.getProperty("java.awt.graphicsenv");
System.setProperty("java.awt.graphicsenv", MyHeadlessGraphicsEnvironment.class.getName());
}
public MyHeadlessGraphicsEnvironment() {
swingErrorRegistered = true;
try {
throw new Exception("Swing invocation detected for Headless Mode");
}
catch (Exception e) {
e.printStackTrace();
}
getRealGraphicsEnvironemnt();
}
@Override
public Graphics2D createGraphics(BufferedImage img) {
return null;
}
@Override
public Font[] getAllFonts() {
return localEnv.getAllFonts();
}
@Override
public String[] getAvailableFontFamilyNames() {
return localEnv.getAvailableFontFamilyNames();
}
@Override
public String[] getAvailableFontFamilyNames(Locale l) {
return localEnv.getAvailableFontFamilyNames(l);
}
@Override
public GraphicsDevice getDefaultScreenDevice() throws HeadlessException {
return localEnv.getDefaultScreenDevice();
}
@Override
public GraphicsDevice[] getScreenDevices() throws HeadlessException {
return localEnv.getScreenDevices();
}
private void getRealGraphicsEnvironemnt() {
try {
localEnv = (GraphicsEnvironment) Class.forName(preferredGraphicsEnv).newInstance();
if (isHeadless()) {
localEnv = new HeadlessGraphicsEnvironment(localEnv);
}
} catch (ClassNotFoundException e) {
throw new Error("Could not find class: " + preferredGraphicsEnv);
} catch (InstantiationException e) {
throw new Error("Could not instantiate Graphics Environment: " + preferredGraphicsEnv);
} catch (IllegalAccessException e) {
throw new Error("Could not access Graphics Environment: " + preferredGraphicsEnv);
}
}
}
@@ -0,0 +1,195 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.headless;
import java.awt.*;
import java.awt.Dialog.ModalExclusionType;
import java.awt.Dialog.ModalityType;
import java.awt.datatransfer.Clipboard;
import java.awt.font.TextAttribute;
import java.awt.im.InputMethodHighlight;
import java.awt.image.*;
import java.net.URL;
import java.util.Map;
import java.util.Properties;
import sun.awt.HeadlessToolkit;
public class MyHeadlessToolkit extends Toolkit {
static volatile boolean swingErrorRegistered = false;
private static String preferredToolkit;
private Toolkit localToolKit;
static void setup() {
//System.setProperty("java.awt.headless", "true");
preferredToolkit = System.getProperty("awt.toolkit", "sun.awt.X11.XToolkit");
System.setProperty("awt.toolkit", MyHeadlessToolkit.class.getName());
}
public MyHeadlessToolkit() {
swingErrorRegistered = true;
try {
throw new Exception("Swing invocation detected for Headless Mode");
}
catch (Exception e) {
e.printStackTrace();
}
getRealToolkit();
}
@Override
public void beep() {
localToolKit.beep();
}
@Override
public int checkImage(Image image, int width, int height,
ImageObserver observer) {
return localToolKit.checkImage(image, width, height, observer);
}
@Override
public Image createImage(String filename) {
return localToolKit.createImage(filename);
}
@Override
public Image createImage(URL url) {
return localToolKit.createImage(url);
}
@Override
public Image createImage(ImageProducer producer) {
return localToolKit.createImage(producer);
}
@Override
public Image createImage(byte[] imagedata, int imageoffset, int imagelength) {
return localToolKit.createImage(imagedata, imageoffset, imagelength);
}
@Override
public ColorModel getColorModel() throws HeadlessException {
return localToolKit.getColorModel();
}
@Override
public String[] getFontList() {
return localToolKit.getFontList();
}
@Override
public FontMetrics getFontMetrics(Font font) {
return localToolKit.getFontMetrics(font);
}
@Override
public Image getImage(String filename) {
return localToolKit.getImage(filename);
}
@Override
public Image getImage(URL url) {
return localToolKit.getImage(url);
}
@Override
public PrintJob getPrintJob(Frame frame, String jobtitle, Properties props) {
return localToolKit.getPrintJob(frame, jobtitle, props);
}
@Override
public int getScreenResolution() throws HeadlessException {
return localToolKit.getScreenResolution();
}
@Override
public Dimension getScreenSize() throws HeadlessException {
return localToolKit.getScreenSize();
}
@Override
public Clipboard getSystemClipboard() throws HeadlessException {
return localToolKit.getSystemClipboard();
}
@Override
protected EventQueue getSystemEventQueueImpl() {
return localToolKit.getSystemEventQueue();
}
@Override
public boolean isModalExclusionTypeSupported(
ModalExclusionType modalExclusionType) {
return localToolKit.isModalExclusionTypeSupported(modalExclusionType);
}
@Override
public boolean isModalityTypeSupported(ModalityType modalityType) {
return localToolKit.isModalityTypeSupported(modalityType);
}
@Override
public Map<TextAttribute, ?> mapInputMethodHighlight(
InputMethodHighlight highlight) throws HeadlessException {
return localToolKit.mapInputMethodHighlight(highlight);
}
@Override
public boolean prepareImage(Image image, int width, int height,
ImageObserver observer) {
return localToolKit.prepareImage(image, width, height, observer);
}
@Override
public void sync() {
localToolKit.sync();
}
private void getRealToolkit() {
try {
// We disable the JIT during toolkit initialization. This
// tends to touch lots of classes that aren't needed again
// later and therefore JITing is counter-productiive.
java.lang.Compiler.disable();
Class<?> cls = null;
try {
try {
cls = Class.forName(preferredToolkit);
} catch (ClassNotFoundException ee) {
throw new AWTError("Toolkit not found: " + preferredToolkit);
}
if (cls != null) {
localToolKit = (Toolkit)cls.newInstance();
if (GraphicsEnvironment.isHeadless()) {
localToolKit = new HeadlessToolkit(localToolKit);
}
}
} catch (InstantiationException e) {
throw new AWTError("Could not instantiate Toolkit: " + preferredToolkit);
} catch (IllegalAccessException e) {
throw new AWTError("Could not access Toolkit: " + preferredToolkit);
}
} finally {
// Make sure to always re-enable the JIT.
java.lang.Compiler.enable();
}
}
}
@@ -0,0 +1,261 @@
/* ###
* 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.framework.analysis;
import static org.junit.Assert.assertNotNull;
import java.io.*;
import java.util.ArrayList;
import org.junit.*;
import generic.jar.ResourceFile;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.services.*;
import ghidra.framework.task.GScheduledTask;
import ghidra.framework.task.GTaskListener;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB;
import ghidra.program.model.symbol.Symbol;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import mockit.*;
public class AnalysisManagerTest extends AbstractGhidraHeadlessIntegrationTest {
@Mocked
GTaskListener listener;
private ProgramBuilder programBuilder;
private ProgramDB program;
private AnalysisManager analysisManager;
private ArrayList<Analyzer> analyzers;
class TaskTypeDelegate implements Delegate<GScheduledTask> {
private String name;
TaskTypeDelegate(String name) {
this.name = name;
}
void validate(GScheduledTask scheduledTask) {
Assert.assertEquals(name, scheduledTask.getTask().getClass().getSimpleName());
}
}
class TaskNameDelegate implements Delegate<GScheduledTask> {
private String name;
TaskNameDelegate(String name) {
this.name = name;
}
void validate(GScheduledTask scheduledTask) {
Assert.assertEquals(name, scheduledTask.getTask().getName());
}
}
@Before
public void setUp() throws Exception {
programBuilder = new ProgramBuilder();
programBuilder.createMemory("AAA", "0x100", 0x1000);
program = programBuilder.getProgram();
analyzers = new ArrayList<>();
}
@Test
public void testTwoAnalyzersWithOnePhases() {
analyzers.add(new Analyzer1());
analyzers.add(new Analyzer2());
AnalysisRecipe recipe = new AnalysisRecipe("Test Recipe", analyzers, program);
analysisManager = new AnalysisManager(program, recipe);
analysisManager.addTaskListener(listener);
analysisManager.runAnalysis(null);
analysisManager.waitForAnalysis(1000);
new VerificationsInOrder() {
{
listener.initialize();
listener.taskStarted(with(new TaskTypeDelegate("StartPhaseTask")));
listener.taskCompleted(with(new TaskTypeDelegate("StartPhaseTask")), null);
listener.taskStarted(with(new TaskTypeDelegate("KickStartAnalyzersTask")));
listener.taskCompleted(with(new TaskTypeDelegate("KickStartAnalyzersTask")), null);
listener.taskStarted(with(new TaskNameDelegate("Analyzer1")));
listener.taskCompleted(with(new TaskNameDelegate("Analyzer1")), null);
listener.taskStarted(with(new TaskNameDelegate("Analyzer2")));
listener.taskCompleted(with(new TaskNameDelegate("Analyzer2")), null);
}
};
}
@Test
public void testTwoAnalyzersWithTwoPhases() {
analyzers.add(new Analyzer1());
analyzers.add(new Analyzer2());
AnalysisRecipe recipe = new AnalysisRecipe("Test Recipe", analyzers, program);
AnalysisPhase firstPhase = recipe.createPhase();
recipe.setAnalyzerStartPhase(analyzers.get(0), firstPhase);
analysisManager = new AnalysisManager(program, recipe);
analysisManager.addTaskListener(listener);
analysisManager.runAnalysis(null);
analysisManager.waitForAnalysis(1000);
new VerificationsInOrder() {
{
listener.initialize();
listener.taskStarted(with(new TaskTypeDelegate("StartPhaseTask")));
listener.taskCompleted(with(new TaskTypeDelegate("StartPhaseTask")), null);
listener.taskStarted(with(new TaskTypeDelegate("KickStartAnalyzersTask")));
listener.taskCompleted(with(new TaskTypeDelegate("KickStartAnalyzersTask")), null);
listener.taskStarted(with(new TaskNameDelegate("Analyzer1")));
listener.taskCompleted(with(new TaskNameDelegate("Analyzer1")), null);
listener.taskStarted(with(new TaskTypeDelegate("StartPhaseTask")));
listener.taskCompleted(with(new TaskTypeDelegate("StartPhaseTask")), null);
listener.taskStarted(with(new TaskNameDelegate("Analyzer2")));
listener.taskCompleted(with(new TaskNameDelegate("Analyzer2")), null);
}
};
}
@Test
public void testTwoAnalyzersWithTwoPhasesAnalyzerInSecondPhaseOff() {
analyzers.add(new Analyzer1());
analyzers.add(new Analyzer2());
AnalysisRecipe recipe = new AnalysisRecipe("Test Recipe", analyzers, program);
AnalysisPhase firstPhase = recipe.createPhase();
recipe.setAnalyzerStartPhase(analyzers.get(0), firstPhase);
recipe.setAnalyzerEnablement(analyzers.get(1), false);
analysisManager = new AnalysisManager(program, recipe);
analysisManager.addTaskListener(listener);
analysisManager.runAnalysis(null);
analysisManager.waitForAnalysis(1000);
new VerificationsInOrder() {
{
listener.initialize();
listener.taskStarted(with(new TaskTypeDelegate("StartPhaseTask")));
listener.taskCompleted(with(new TaskTypeDelegate("StartPhaseTask")), null);
listener.taskStarted(with(new TaskTypeDelegate("KickStartAnalyzersTask")));
listener.taskCompleted(with(new TaskTypeDelegate("KickStartAnalyzersTask")), null);
listener.taskStarted(with(new TaskNameDelegate("Analyzer1")));
listener.taskCompleted(with(new TaskNameDelegate("Analyzer1")), null);
listener.taskStarted(with(new TaskTypeDelegate("StartPhaseTask")));
listener.taskCompleted(with(new TaskTypeDelegate("StartPhaseTask")), null);
listener.taskStarted((GScheduledTask) any);
times = 0; // make sure no more taskStarted calls are mode
}
};
}
@Test
public void testSciptAnalyzer() throws Exception {
final ResourceFile scriptFile = createScriptFile();
analyzers.add(new Analyzer1());
GhidraScriptAnalyzerAdapter analyzer =
new GhidraScriptAnalyzerAdapter(scriptFile, AnalyzerType.BYTE_ANALYZER, 10000);
analyzers.add(analyzer);
AnalysisRecipe recipe = new AnalysisRecipe("Test Recipe", analyzers, program);
analysisManager = new AnalysisManager(program, recipe);
analysisManager.addTaskListener(listener);
analysisManager.runAnalysis(null);
analysisManager.waitForAnalysis(10000);
// verify that the script ran
Symbol symbol = getUniqueSymbol(program, "TEST_SYMBOL");
assertNotNull(symbol);
new VerificationsInOrder() {
{
listener.initialize();
listener.taskStarted(with(new TaskTypeDelegate("StartPhaseTask")));
listener.taskCompleted(with(new TaskTypeDelegate("StartPhaseTask")), null);
listener.taskStarted(with(new TaskTypeDelegate("KickStartAnalyzersTask")));
listener.taskCompleted(with(new TaskTypeDelegate("KickStartAnalyzersTask")), null);
listener.taskStarted(with(new TaskNameDelegate("Analyzer1")));
listener.taskCompleted(with(new TaskNameDelegate("Analyzer1")), null);
listener.taskStarted(with(new TaskNameDelegate("Script: " + scriptFile.getName())));
listener.taskCompleted(
with(new TaskNameDelegate("Script: " + scriptFile.getName())), null);
listener.taskStarted((GScheduledTask) any);
times = 0; // make sure no more taskStarted calls are mode
}
};
}
private ResourceFile createScriptFile() throws Exception {
ResourceFile newScriptFile = createTempScriptFile("TestAnalyzerScript");
String filename = newScriptFile.getName();
String className = filename.replaceAll("\\.java", "");
//@formatter:off
String newScript =
"import ghidra.app.script.GhidraScript;\n\n"+
"import ghidra.program.model.address.Address;\n"+
"import ghidra.program.model.symbol.SourceType;\n"+
"public class "+className+" extends GhidraScript {\n\n"+
" @Override\n"+
" protected void run() throws Exception {\n"+
" Address minAddress = currentProgram.getMinAddress();\n"+
" currentProgram.getSymbolTable().createLabel(minAddress, \"TEST_SYMBOL\",\n"+
" SourceType.USER_DEFINED);\n"+
" }\n\n"+
"}\n";
//@formatter:on
writeStringToFile(newScriptFile, newScript);
return newScriptFile;
}
private void writeStringToFile(ResourceFile file, String string) throws IOException {
BufferedWriter writer = new BufferedWriter(new FileWriter(file.getFile(false)));
writer.write(string);
writer.close();
}
private ResourceFile createTempScriptFile(String name) throws IOException {
File userScriptsDir = new File(GhidraScriptUtil.USER_SCRIPTS_DIR);
if (name.length() > 50) {
// too long and the script manager complains
name = name.substring(name.length() - 50);
}
File tempFile = File.createTempFile(name, ".java", userScriptsDir);
tempFile.deleteOnExit();
return new ResourceFile(tempFile);
}
public static class Analyzer1 extends AnalyzerTestStub {
Analyzer1() {
super("Analyzer1", AnalyzerType.BYTE_ANALYZER, true, new AnalysisPriority("1", 1));
}
}
public static class Analyzer2 extends AnalyzerTestStub {
Analyzer2() {
super("Analyzer2", AnalyzerType.BYTE_ANALYZER, true, new AnalysisPriority("2", 2));
}
}
}

Some files were not shown because too many files have changed in this diff Show More