mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-31 17:07:41 +08:00
Merge remote-tracking branch 'origin/GT-3035-dragonmacher-restore-integration-tests'
This commit is contained in:
@@ -23,6 +23,7 @@ import javax.swing.*;
|
|||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.WindowPosition;
|
import docking.WindowPosition;
|
||||||
import docking.options.editor.ButtonPanelFactory;
|
import docking.options.editor.ButtonPanelFactory;
|
||||||
|
import docking.util.image.ToolIconURL;
|
||||||
import docking.widgets.OptionDialog;
|
import docking.widgets.OptionDialog;
|
||||||
import docking.widgets.label.*;
|
import docking.widgets.label.*;
|
||||||
import ghidra.app.context.ListingActionContext;
|
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.FieldHeaderComp;
|
||||||
import ghidra.app.util.viewer.format.FieldHeaderLocation;
|
import ghidra.app.util.viewer.format.FieldHeaderLocation;
|
||||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||||
import ghidra.framework.project.tool.ToolIconURL;
|
|
||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
import ghidra.util.layout.VerticalLayout;
|
import ghidra.util.layout.VerticalLayout;
|
||||||
|
|||||||
@@ -593,7 +593,7 @@ public class GhidraScriptUtil {
|
|||||||
* @param name the name of the script
|
* @param name the name of the script
|
||||||
* @return the name as a '.java' file path (with '/'s and not '.'s)
|
* @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")) {
|
if (name.endsWith(".java")) {
|
||||||
name = name.substring(0, name.length() - 5);
|
name = name.substring(0, name.length() - 5);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ import java.util.*;
|
|||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.swing.JFrame;
|
|
||||||
|
|
||||||
import org.jdom.Element;
|
import org.jdom.Element;
|
||||||
|
|
||||||
import docking.ComponentProvider;
|
import docking.ComponentProvider;
|
||||||
@@ -332,6 +330,7 @@ public class TestEnv {
|
|||||||
* <P>This method is considered sub-standard and users should prefer instead
|
* <P>This method is considered sub-standard and users should prefer instead
|
||||||
* {@link #launchDefaultTool()} or {@link #launchDefaultTool(Program)}.
|
* {@link #launchDefaultTool()} or {@link #launchDefaultTool(Program)}.
|
||||||
*
|
*
|
||||||
|
* @param p the program
|
||||||
* @return the newly shown tool
|
* @return the newly shown tool
|
||||||
*/
|
*/
|
||||||
public PluginTool showTool(Program p) {
|
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
|
* 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.
|
* window is assumed to be this instance's tool frame.
|
||||||
*
|
*
|
||||||
* @param ghidraClass The class of the dialog the user desires
|
* @param ghidraClass The class of the dialog the user desires
|
||||||
@@ -369,8 +368,7 @@ public class TestEnv {
|
|||||||
@Deprecated
|
@Deprecated
|
||||||
public <T extends DialogComponentProvider> T waitForDialogComponent(Class<T> ghidraClass,
|
public <T extends DialogComponentProvider> T waitForDialogComponent(Class<T> ghidraClass,
|
||||||
int maxTimeMS) {
|
int maxTimeMS) {
|
||||||
JFrame frame = lazyTool().getToolFrame();
|
return AbstractDockingTest.waitForDialogComponent(ghidraClass);
|
||||||
return AbstractDockingTest.waitForDialogComponent(frame, ghidraClass, maxTimeMS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GhidraProject createGhidraTestProject(String projectName) throws IOException {
|
private static GhidraProject createGhidraTestProject(String projectName) throws IOException {
|
||||||
@@ -381,7 +379,11 @@ public class TestEnv {
|
|||||||
deleteSavedFrontEndTool();
|
deleteSavedFrontEndTool();
|
||||||
|
|
||||||
String projectDirectoryName = AbstractGTest.getTestDirectoryPath();
|
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() {
|
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() {
|
private void initializeSimpleTool() {
|
||||||
|
|
||||||
if (tool != null) {
|
if (tool != null) {
|
||||||
@@ -466,6 +481,7 @@ public class TestEnv {
|
|||||||
/**
|
/**
|
||||||
* This method differs from {@link #launchDefaultTool()} in that this method does not set the
|
* 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.
|
* <tt>tool</tt> variable in of this <tt>TestEnv</tt> instance.
|
||||||
|
* @return the tool
|
||||||
*/
|
*/
|
||||||
public PluginTool createDefaultTool() {
|
public PluginTool createDefaultTool() {
|
||||||
PluginTool newTool = launchDefaultToolByName(AbstractGenericTest.DEFAULT_TEST_TOOL_NAME);
|
PluginTool newTool = launchDefaultToolByName(AbstractGenericTest.DEFAULT_TEST_TOOL_NAME);
|
||||||
@@ -497,15 +513,14 @@ public class TestEnv {
|
|||||||
return tool;
|
return tool;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PluginTool launchDefaultToolByName(final String toolName) {
|
protected PluginTool launchDefaultToolByName(String toolName) {
|
||||||
AtomicReference<PluginTool> ref = new AtomicReference<>();
|
|
||||||
AbstractGenericTest.runSwing(() -> {
|
|
||||||
|
|
||||||
ToolTemplate toolTemplate =
|
return AbstractGenericTest.runSwing(() -> {
|
||||||
ToolUtils.readToolTemplate("defaultTools/" + toolName + ".tool");
|
|
||||||
|
ToolTemplate toolTemplate = getToolTemplate(toolName);
|
||||||
if (toolTemplate == null) {
|
if (toolTemplate == null) {
|
||||||
Msg.debug(this, "Unable to find tool: " + toolName);
|
Msg.debug(this, "Unable to find tool: " + toolName);
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean wasErrorGUIEnabled = AbstractDockingTest.isUseErrorGUI();
|
boolean wasErrorGUIEnabled = AbstractDockingTest.isUseErrorGUI();
|
||||||
@@ -515,11 +530,23 @@ public class TestEnv {
|
|||||||
Project project = frontEndToolInstance.getProject();
|
Project project = frontEndToolInstance.getProject();
|
||||||
ToolManager toolManager = project.getToolManager();
|
ToolManager toolManager = project.getToolManager();
|
||||||
Workspace workspace = toolManager.getActiveWorkspace();
|
Workspace workspace = toolManager.getActiveWorkspace();
|
||||||
ref.set((PluginTool) workspace.runTool(toolTemplate));
|
|
||||||
|
|
||||||
AbstractDockingTest.setErrorGUIEnabled(wasErrorGUIEnabled);
|
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 {
|
public ScriptTaskListener runScript(File script) throws PluginException {
|
||||||
@@ -550,6 +577,7 @@ public class TestEnv {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns GhidraProject associated with this environment
|
* Returns GhidraProject associated with this environment
|
||||||
|
* @return the project
|
||||||
*/
|
*/
|
||||||
public GhidraProject getGhidraProject() {
|
public GhidraProject getGhidraProject() {
|
||||||
return gp;
|
return gp;
|
||||||
@@ -559,6 +587,7 @@ public class TestEnv {
|
|||||||
* A convenience method to close and then reopen the default project created by this 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
|
* instance. This will not delete the project between opening and closing and will restore
|
||||||
* the project to its previous state.
|
* the project to its previous state.
|
||||||
|
* @throws IOException if any exception occurs while saving and reopening
|
||||||
*/
|
*/
|
||||||
public void closeAndReopenProject() throws IOException {
|
public void closeAndReopenProject() throws IOException {
|
||||||
gp.setDeleteOnClose(false);
|
gp.setDeleteOnClose(false);
|
||||||
@@ -579,9 +608,6 @@ public class TestEnv {
|
|||||||
return gp.getProjectManager();
|
return gp.getProjectManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns Project associated with this environment
|
|
||||||
*/
|
|
||||||
public Project getProject() {
|
public Project getProject() {
|
||||||
return gp.getProject();
|
return gp.getProject();
|
||||||
}
|
}
|
||||||
@@ -646,6 +672,8 @@ public class TestEnv {
|
|||||||
* the only reason to use this method vice openProgram().
|
* the only reason to use this method vice openProgram().
|
||||||
*
|
*
|
||||||
* @param programName the name of the program zip file without the ".gzf" extension.
|
* @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 {
|
public DomainFile restoreProgram(String programName) throws FileNotFoundException {
|
||||||
DomainFile df = programManager.addProgramToProject(getProject(), programName);
|
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"
|
* @param relativePathName This should be a pathname relative to the "test_resources/testdata"
|
||||||
* director or relative to the "typeinfo" directory. The name should
|
* director or relative to the "typeinfo" directory. The name should
|
||||||
* include the ".gdt" suffix.
|
* include the ".gdt" suffix.
|
||||||
* @param domainFolder the folder in the test project where the archive should be created.
|
* @param domainFolder the folder in the test project where the archive should be created
|
||||||
* @param monitor monitor for canceling this restore.
|
|
||||||
* @return the domain file that was created in the project
|
* @return the domain file that was created in the project
|
||||||
|
* @throws Exception if an exception occurs
|
||||||
*/
|
*/
|
||||||
public DomainFile restoreDataTypeArchive(String relativePathName, DomainFolder domainFolder)
|
public DomainFile restoreDataTypeArchive(String relativePathName, DomainFolder domainFolder)
|
||||||
throws InvalidNameException, IOException, VersionException {
|
throws Exception {
|
||||||
|
|
||||||
File gdtFile;
|
File gdtFile;
|
||||||
try {
|
try {
|
||||||
@@ -738,10 +766,10 @@ public class TestEnv {
|
|||||||
* @param program program object
|
* @param program program object
|
||||||
* @param replace if true any existing cached database with the same name will be replaced
|
* @param replace if true any existing cached database with the same name will be replaced
|
||||||
* @param monitor task monitor
|
* @param monitor task monitor
|
||||||
* @throws DuplicateNameException if already cached
|
* @throws Exception if already cached
|
||||||
*/
|
*/
|
||||||
public void saveToCache(String progName, ProgramDB program, boolean replace,
|
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);
|
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.
|
* Launches a tool of the given name using the given domain file.
|
||||||
* <p>
|
* <p>
|
||||||
* Note: the tool returned will have auto save disabled by default.
|
* Note: the tool returned will have auto save disabled by default.
|
||||||
*
|
*
|
||||||
|
* @param toolName the tool's name
|
||||||
* @return the tool that is launched
|
* @return the tool that is launched
|
||||||
*/
|
*/
|
||||||
public PluginTool launchTool(String toolName) {
|
public PluginTool launchTool(String toolName) {
|
||||||
@@ -1216,5 +1245,7 @@ public class TestEnv {
|
|||||||
|
|
||||||
DefaultProjectManager pm = gp.getProjectManager();
|
DefaultProjectManager pm = gp.getProjectManager();
|
||||||
pm.addDefaultTools(tc);
|
pm.addDefaultTools(tc);
|
||||||
|
|
||||||
|
installDefaultTool(gp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -28,6 +28,7 @@ import org.junit.*;
|
|||||||
|
|
||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
import docking.tool.ToolConstants;
|
import docking.tool.ToolConstants;
|
||||||
|
import docking.util.image.ToolIconURL;
|
||||||
import docking.widgets.OptionDialog;
|
import docking.widgets.OptionDialog;
|
||||||
import docking.widgets.filechooser.GhidraFileChooser;
|
import docking.widgets.filechooser.GhidraFileChooser;
|
||||||
import generic.test.AbstractGTest;
|
import generic.test.AbstractGTest;
|
||||||
@@ -46,7 +47,6 @@ import ghidra.framework.model.ToolTemplate;
|
|||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.plugintool.util.PluginException;
|
import ghidra.framework.plugintool.util.PluginException;
|
||||||
import ghidra.framework.preferences.Preferences;
|
import ghidra.framework.preferences.Preferences;
|
||||||
import ghidra.framework.project.tool.ToolIconURL;
|
|
||||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||||
import ghidra.test.TestEnv;
|
import ghidra.test.TestEnv;
|
||||||
import ghidra.util.exception.AssertException;
|
import ghidra.util.exception.AssertException;
|
||||||
|
|||||||
+1
-1
@@ -23,12 +23,12 @@ import org.junit.*;
|
|||||||
|
|
||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
import docking.action.ToggleDockingAction;
|
import docking.action.ToggleDockingAction;
|
||||||
|
import docking.util.image.ToolIconURL;
|
||||||
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||||
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
|
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
|
||||||
import ghidra.framework.main.FrontEndPlugin;
|
import ghidra.framework.main.FrontEndPlugin;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.project.tool.GhidraTool;
|
import ghidra.framework.project.tool.GhidraTool;
|
||||||
import ghidra.framework.project.tool.ToolIconURL;
|
|
||||||
import ghidra.program.database.ProgramDB;
|
import ghidra.program.database.ProgramDB;
|
||||||
import ghidra.test.ClassicSampleX86ProgramBuilder;
|
import ghidra.test.ClassicSampleX86ProgramBuilder;
|
||||||
import ghidra.test.TestEnv;
|
import ghidra.test.TestEnv;
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.framework.project.tool;
|
package docking.util.image;
|
||||||
|
|
||||||
import generic.Images;
|
import generic.Images;
|
||||||
|
|
||||||
+122
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+519
File diff suppressed because it is too large
Load Diff
+290
@@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+309
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+255
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+309
@@ -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;
|
private static File debugDirectory;
|
||||||
|
|
||||||
public static final String TESTDATA_DIRECTORY_NAME = "testdata";
|
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";
|
public static final String DEFAULT_TEST_TOOL_NAME = "TestCodeBrowser";
|
||||||
|
|
||||||
private static boolean initialized = false;
|
private static boolean initialized = false;
|
||||||
|
|||||||
@@ -15,13 +15,16 @@
|
|||||||
*/
|
*/
|
||||||
package utilities.util;
|
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 static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.*;
|
||||||
import java.io.IOException;
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.junit.Test;
|
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/b/../c")));
|
||||||
assertFalse(FileUtilities.isPathContainedWithin(new File("/a/b"), new File("/a/bc")));
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
@@ -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.help.HelpService;
|
||||||
import docking.tool.ToolConstants;
|
import docking.tool.ToolConstants;
|
||||||
import docking.util.AnimationUtils;
|
import docking.util.AnimationUtils;
|
||||||
|
import docking.util.image.ToolIconURL;
|
||||||
import docking.widgets.OptionDialog;
|
import docking.widgets.OptionDialog;
|
||||||
import generic.jar.ResourceFile;
|
import generic.jar.ResourceFile;
|
||||||
import generic.util.WindowUtilities;
|
import generic.util.WindowUtilities;
|
||||||
|
|||||||
@@ -32,11 +32,11 @@ import docking.dnd.*;
|
|||||||
import docking.help.Help;
|
import docking.help.Help;
|
||||||
import docking.help.HelpService;
|
import docking.help.HelpService;
|
||||||
import docking.tool.ToolConstants;
|
import docking.tool.ToolConstants;
|
||||||
|
import docking.util.image.ToolIconURL;
|
||||||
import docking.widgets.EmptyBorderButton;
|
import docking.widgets.EmptyBorderButton;
|
||||||
import ghidra.framework.main.datatree.*;
|
import ghidra.framework.main.datatree.*;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.project.tool.ToolIconURL;
|
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
import ghidra.util.bean.GGlassPane;
|
import ghidra.util.bean.GGlassPane;
|
||||||
import ghidra.util.exception.AssertException;
|
import ghidra.util.exception.AssertException;
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import java.beans.PropertyVetoException;
|
|||||||
import org.jdom.Element;
|
import org.jdom.Element;
|
||||||
|
|
||||||
import docking.DockingTool;
|
import docking.DockingTool;
|
||||||
|
import docking.util.image.ToolIconURL;
|
||||||
import ghidra.framework.plugintool.PluginEvent;
|
import ghidra.framework.plugintool.PluginEvent;
|
||||||
import ghidra.framework.project.tool.ToolIconURL;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import javax.swing.ImageIcon;
|
|||||||
|
|
||||||
import org.jdom.Element;
|
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.
|
* Configuration of a tool that knows how to create tools.
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import docking.help.Help;
|
|||||||
import docking.help.HelpService;
|
import docking.help.HelpService;
|
||||||
import docking.tool.ToolConstants;
|
import docking.tool.ToolConstants;
|
||||||
import docking.tool.util.DockingToolConstants;
|
import docking.tool.util.DockingToolConstants;
|
||||||
|
import docking.util.image.ToolIconURL;
|
||||||
import docking.widgets.OptionDialog;
|
import docking.widgets.OptionDialog;
|
||||||
import ghidra.framework.OperatingSystem;
|
import ghidra.framework.OperatingSystem;
|
||||||
import ghidra.framework.Platform;
|
import ghidra.framework.Platform;
|
||||||
@@ -56,7 +57,6 @@ import ghidra.framework.plugintool.dialog.ManagePluginsDialog;
|
|||||||
import ghidra.framework.plugintool.mgr.*;
|
import ghidra.framework.plugintool.mgr.*;
|
||||||
import ghidra.framework.plugintool.util.*;
|
import ghidra.framework.plugintool.util.*;
|
||||||
import ghidra.framework.project.ProjectDataService;
|
import ghidra.framework.project.ProjectDataService;
|
||||||
import ghidra.framework.project.tool.ToolIconURL;
|
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
import ghidra.util.task.Task;
|
import ghidra.util.task.Task;
|
||||||
import ghidra.util.task.TaskLauncher;
|
import ghidra.util.task.TaskLauncher;
|
||||||
|
|||||||
+1
-3
@@ -1,6 +1,5 @@
|
|||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -16,10 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.framework.plugintool.dialog;
|
package ghidra.framework.plugintool.dialog;
|
||||||
|
|
||||||
import ghidra.framework.project.tool.ToolIconURL;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import docking.util.image.ToolIconURL;
|
||||||
import resources.ResourceManager;
|
import resources.ResourceManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
-1
@@ -27,6 +27,7 @@ import javax.swing.event.*;
|
|||||||
|
|
||||||
import docking.DialogComponentProvider;
|
import docking.DialogComponentProvider;
|
||||||
import docking.options.editor.ButtonPanelFactory;
|
import docking.options.editor.ButtonPanelFactory;
|
||||||
|
import docking.util.image.ToolIconURL;
|
||||||
import docking.widgets.OptionDialog;
|
import docking.widgets.OptionDialog;
|
||||||
import docking.widgets.filechooser.GhidraFileChooser;
|
import docking.widgets.filechooser.GhidraFileChooser;
|
||||||
import docking.widgets.label.GLabel;
|
import docking.widgets.label.GLabel;
|
||||||
@@ -34,7 +35,6 @@ import ghidra.framework.model.*;
|
|||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.preferences.Preferences;
|
import ghidra.framework.preferences.Preferences;
|
||||||
import ghidra.framework.project.tool.GhidraToolTemplate;
|
import ghidra.framework.project.tool.GhidraToolTemplate;
|
||||||
import ghidra.framework.project.tool.ToolIconURL;
|
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
import ghidra.util.NamingUtilities;
|
import ghidra.util.NamingUtilities;
|
||||||
import ghidra.util.filechooser.ExtensionFileFilter;
|
import ghidra.util.filechooser.ExtensionFileFilter;
|
||||||
|
|||||||
+1
-1
@@ -22,8 +22,8 @@ import javax.swing.BorderFactory;
|
|||||||
import javax.swing.JList;
|
import javax.swing.JList;
|
||||||
import javax.swing.border.Border;
|
import javax.swing.border.Border;
|
||||||
|
|
||||||
|
import docking.util.image.ToolIconURL;
|
||||||
import docking.widgets.list.GListCellRenderer;
|
import docking.widgets.list.GListCellRenderer;
|
||||||
import ghidra.framework.project.tool.ToolIconURL;
|
|
||||||
|
|
||||||
class ToolIconUrlRenderer extends GListCellRenderer<ToolIconURL> {
|
class ToolIconUrlRenderer extends GListCellRenderer<ToolIconURL> {
|
||||||
private Border emptyBorder = BorderFactory.createEmptyBorder(5, 5, 5, 5);
|
private Border emptyBorder = BorderFactory.createEmptyBorder(5, 5, 5, 5);
|
||||||
|
|||||||
+1
@@ -21,6 +21,7 @@ import javax.swing.ImageIcon;
|
|||||||
|
|
||||||
import org.jdom.Element;
|
import org.jdom.Element;
|
||||||
|
|
||||||
|
import docking.util.image.ToolIconURL;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.NumericUtilities;
|
import ghidra.util.NumericUtilities;
|
||||||
|
|||||||
+2
-3
@@ -30,10 +30,10 @@ import docking.*;
|
|||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
import docking.actions.DockingToolActions;
|
import docking.actions.DockingToolActions;
|
||||||
import docking.actions.PopupActionProvider;
|
import docking.actions.PopupActionProvider;
|
||||||
|
import docking.util.image.ToolIconURL;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.options.ToolOptions;
|
import ghidra.framework.options.ToolOptions;
|
||||||
import ghidra.framework.plugintool.PluginEvent;
|
import ghidra.framework.plugintool.PluginEvent;
|
||||||
import ghidra.framework.project.tool.ToolIconURL;
|
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
|
|
||||||
public class DummyTool implements Tool {
|
public class DummyTool implements Tool {
|
||||||
@@ -361,8 +361,7 @@ public class DummyTool implements Tool {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMenuGroup(String[] menuPath, String group, String menuSubGroup) {
|
public void setMenuGroup(String[] menuPath, String group, String menuSubGroup) {
|
||||||
// TODO Auto-generated method stub
|
//do nothing
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
+1
-1
@@ -19,8 +19,8 @@ import javax.swing.ImageIcon;
|
|||||||
|
|
||||||
import org.jdom.Element;
|
import org.jdom.Element;
|
||||||
|
|
||||||
|
import docking.util.image.ToolIconURL;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.project.tool.ToolIconURL;
|
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
|
|
||||||
public class DummyToolTemplate implements ToolTemplate {
|
public class DummyToolTemplate implements ToolTemplate {
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
##VERSION: 2.0
|
##VERSION: 2.0
|
||||||
build.gradle||GHIDRA||||END|
|
build.gradle||GHIDRA||||END|
|
||||||
|
src/test.slow/java/ghidra/pcodeCPort/slgh_compile/pcode1.xml||GHIDRA||||END|
|
||||||
|
|||||||
+131
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
+1736
File diff suppressed because it is too large
Load Diff
+307
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+172
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+195
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+248
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+356
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+81
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+94
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+195
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+261
@@ -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
Reference in New Issue
Block a user