diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadedIntegrationTest.java b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadedIntegrationTest.java index 4547e080a0..1c0a54fbc4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadedIntegrationTest.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadedIntegrationTest.java @@ -36,7 +36,6 @@ import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.util.PluginException; import ghidra.program.model.listing.Program; import ghidra.util.TaskUtilities; -import ghidra.util.exception.AssertException; import junit.framework.AssertionFailedError; import utility.application.ApplicationLayout; @@ -44,21 +43,14 @@ public abstract class AbstractGhidraHeadedIntegrationTest extends AbstractGhidraHeadlessIntegrationTest { public AbstractGhidraHeadedIntegrationTest() { - super(); - // Ensure that all headed tests use swing popups when displaying errors. Setting this // to false would force errors to only be written to the console. setErrorGUIEnabled(true); } @Override - protected ApplicationLayout createApplicationLayout() { - try { - return new GhidraTestApplicationLayout(new File(getTestDirectoryPath())); - } - catch (IOException e) { - throw new AssertException(e); - } + protected ApplicationLayout createApplicationLayout() throws IOException { + return new GhidraTestApplicationLayout(new File(getTestDirectoryPath())); } @Override @@ -70,7 +62,7 @@ public abstract class AbstractGhidraHeadedIntegrationTest /** * Flushes the given program's events before waiting for the swing update manager - * + * * @param program The program whose events will be flushed; may be null */ public static void waitForProgram(Program program) { @@ -84,11 +76,11 @@ public abstract class AbstractGhidraHeadedIntegrationTest /** * Adds the given plugin to the tool and then returns the instance of the plugin that was * added - * + * * @param tool the tool * @param c the class of the plugin to add * @return the newly added plugin - * @throws PluginException if the plugin could not be constructed, or there was problem + * @throws PluginException if the plugin could not be constructed, or there was problem * executing its init() method, or if a plugin of this class already exists in the tool */ public static T addPlugin(PluginTool tool, Class c) @@ -122,9 +114,9 @@ public abstract class AbstractGhidraHeadedIntegrationTest } /** - * Shows the given DialogComponentProvider using the given tool's - * {@link PluginTool#showDialog(DialogComponentProvider)} method. - * + * Shows the given DialogComponentProvider using the given tool's + * {@link PluginTool#showDialog(DialogComponentProvider)} method. + * * @param tool The tool used to show the given provider. * @param provider The DialogComponentProvider to show. * @return The provider once it has been shown, or null if the provider is not shown within @@ -143,7 +135,7 @@ public abstract class AbstractGhidraHeadedIntegrationTest /** * Waits for the tool to finish executing commands and tasks - * + * * @param tool the tool * @throws AssertionFailedError if the tool does not finish work within a reasonable limit */ @@ -171,8 +163,8 @@ public abstract class AbstractGhidraHeadedIntegrationTest /** * Save the given tool to the project tool chest. If the tool already exists, then it will - * be overwritten with the given tool. - * + * be overwritten with the given tool. + * * @param project The project which with the tool is associated. * @param tool The tool to be saved * @return the new tool @@ -194,9 +186,9 @@ public abstract class AbstractGhidraHeadedIntegrationTest } /** - * Triggers a browser click at the current cursor location. Thus, this method should be + * Triggers a browser click at the current cursor location. Thus, this method should be * called only after the browser location is set the the desired field. - * + * * @param codeBrowser the CodeBrowserPlugin * @param clickCount the click count */ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java index 059ca01833..beaa54342a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java @@ -46,7 +46,6 @@ import ghidra.program.model.symbol.Symbol; import ghidra.program.util.*; import ghidra.util.Msg; import ghidra.util.classfinder.ClassSearcher; -import ghidra.util.exception.AssertException; import ghidra.util.exception.RollbackException; import junit.framework.AssertionFailedError; import utility.application.ApplicationLayout; @@ -75,13 +74,8 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock } @Override - protected ApplicationLayout createApplicationLayout() { - try { - return new GhidraTestApplicationLayout(new File(getTestDirectoryPath())); - } - catch (IOException e) { - throw new AssertException(e); - } + protected ApplicationLayout createApplicationLayout() throws IOException { + return new GhidraTestApplicationLayout(new File(getTestDirectoryPath())); } @Override diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServerApplicationLayout.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServerApplicationLayout.java index 13beaf102b..8f0f1a3ebd 100644 --- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServerApplicationLayout.java +++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServerApplicationLayout.java @@ -17,7 +17,6 @@ package ghidra.server.remote; import java.io.FileNotFoundException; import java.io.IOException; -import java.util.ArrayList; import java.util.Collections; import ghidra.framework.ApplicationProperties; @@ -27,14 +26,14 @@ import utility.application.ApplicationUtilities; import utility.module.ModuleUtilities; /** - * The Ghidra server application layout defines the customizable elements of the Ghidra + * The Ghidra server application layout defines the customizable elements of the Ghidra * server application's directory structure. */ public class GhidraServerApplicationLayout extends ApplicationLayout { /** * Constructs a new Ghidra server application layout object. - * + * * @throws FileNotFoundException if there was a problem getting a user directory. * @throws IOException if there was a problem getting the application properties. */ @@ -61,7 +60,7 @@ public class GhidraServerApplicationLayout extends ApplicationLayout { // Modules - required to find module data files modules = ModuleUtilities.findModules(applicationRootDirs, - ModuleUtilities.findModuleRootDirectories(applicationRootDirs, new ArrayList<>())); + ModuleUtilities.findModuleRootDirectories(applicationRootDirs)); } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/framework/DockingApplicationLayout.java b/Ghidra/Framework/Docking/src/main/java/docking/framework/DockingApplicationLayout.java index 21758d2c8c..d169142e32 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/framework/DockingApplicationLayout.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/framework/DockingApplicationLayout.java @@ -16,7 +16,8 @@ package docking.framework; import java.io.FileNotFoundException; -import java.util.*; +import java.util.Collection; +import java.util.Objects; import generic.jar.ResourceFile; import ghidra.framework.ApplicationProperties; @@ -24,10 +25,11 @@ import ghidra.util.SystemUtilities; import util.CollectionUtils; import utility.application.ApplicationLayout; import utility.application.ApplicationUtilities; +import utility.module.ClasspathFilter; import utility.module.ModuleUtilities; /** - * The docking application layout defines the customizable elements of a docking application's + * The docking application layout defines the customizable elements of a docking application's * directory structure. */ public class DockingApplicationLayout extends ApplicationLayout { @@ -36,7 +38,7 @@ public class DockingApplicationLayout extends ApplicationLayout { /** * Constructs a new docking application layout object with the given name and version. - * + * * @param name The name of the application. * @param version The version of the application. * @throws FileNotFoundException if there was a problem getting a user directory. @@ -48,7 +50,7 @@ public class DockingApplicationLayout extends ApplicationLayout { /** * Constructs a new docking application layout object with the given set of application * properties. The default Ghidra application root directory(s) will be used. - * + * * @param applicationProperties The properties object that will be read system properties. * @throws FileNotFoundException if there was a problem getting a user directory. */ @@ -60,9 +62,9 @@ public class DockingApplicationLayout extends ApplicationLayout { /** * Constructs a new docking application layout object with the given set of application * properties. - * + * * @param applicationRootDirs list of application root directories which should be - * used to idenitfy modules and resources. The first entry will be treated as the + * used to identify modules and resources. The first entry will be treated as the * installation root. * @param applicationProperties The properties object that will be read system properties. * @throws FileNotFoundException if there was a problem getting a user directory. @@ -81,8 +83,14 @@ public class DockingApplicationLayout extends ApplicationLayout { // Modules if (SystemUtilities.isInDevelopmentMode()) { + + // In development mode we rely on the IDE's classpath to determine which modules to + // include, as opposed to scanning the filesystem. This prevents unrelated modules + // from being used. + modules = ModuleUtilities.findModules(applicationRootDirs, - ModuleUtilities.findModuleRootDirectories(applicationRootDirs, new ArrayList<>())); + ModuleUtilities.findModuleRootDirectories(applicationRootDirs), + new ClasspathFilter()); } else { modules = ModuleUtilities.findModules(applicationRootDirs, applicationRootDirs); @@ -95,10 +103,10 @@ public class DockingApplicationLayout extends ApplicationLayout { } /** - * Get the default list of Application directories. In repo-based + * Get the default list of Application directories. In repo-based * development mode this includes the root Ghidra directory within each repo. - * When not in development mode, the requirement is that the current working - * directory correspond to the installation root. The first entry will be + * When not in development mode, the requirement is that the current working + * directory correspond to the installation root. The first entry will be * the primary root in both cases. * @return root directories */ diff --git a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java index 8a07ce8d7f..16de685cae 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java @@ -76,8 +76,6 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { new TestFailingErrorDisplayWrapper(); public AbstractDockingTest() { - super(); - installNonNativeSystemClipboard(); } @@ -110,13 +108,8 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { } @Override - protected ApplicationLayout createApplicationLayout() { - try { - return new GhidraTestApplicationLayout(new File(getTestDirectoryPath())); - } - catch (IOException e) { - throw new AssertException(e); - } + protected ApplicationLayout createApplicationLayout() throws IOException { + return new GhidraTestApplicationLayout(new File(getTestDirectoryPath())); } @Override @@ -445,7 +438,7 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { /** * A convenience method to close all of the windows and frames that the current Java * windowing environment knows about - * + * * @deprecated instead call the new {@link #closeAllWindows()} */ @Deprecated @@ -727,11 +720,7 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { } T t = windowManager.getComponentProvider(clazz); - if (t != null) { - return t; - } - - return null; + return t; } /** @@ -1167,10 +1156,10 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { * If you do not know the owner name, then use * the call {@link #getActionsByName(Tool, String)} instead (this will not include * reserved system actions). - * + * *

Note: more specific test case subclasses provide other methods for finding actions * when you have an owner name (which is usually the plugin name). - * + * * @param tool the tool containing all system actions * @param owner the owner of the action * @param name the name to match @@ -1193,7 +1182,7 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { /** * Returns the action by the given name that belongs to the given provider - * + * * @param provider the provider * @param actionName the action name * @return the action @@ -1485,7 +1474,7 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { /** * Simulates a user initiated keystroke using the keybinding of the given action - * + * * @param destination the component for the action being executed * @param action The action to simulate pressing */ @@ -1717,7 +1706,7 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { * Fires a {@link KeyListener#keyPressed(KeyEvent)}, * {@link KeyListener#keyTyped(KeyEvent)} * and {@link KeyListener#keyReleased(KeyEvent)} for the given key stroke - * + * * @param c the destination component * @param ks the key stroke */ @@ -2141,8 +2130,7 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { return runSwing(() -> action.isEnabledForContext(new ActionContext())); } - public static boolean isEnabled(DockingActionIf action, - ActionContextProvider contextProvider) { + public static boolean isEnabled(DockingActionIf action, ActionContextProvider contextProvider) { return runSwing(() -> action.isEnabledForContext(contextProvider.getActionContext(null))); } diff --git a/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java b/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java index 1e4939ece0..a630722b7d 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java @@ -136,7 +136,14 @@ public abstract class AbstractGenericTest extends AbstractGTest { initializeSystemProperties(); - ApplicationLayout layout = test.createApplicationLayout(); + ApplicationLayout layout; + try { + layout = test.createApplicationLayout(); + } + catch (Exception e) { + throw new AssertException(e); + } + initializeLayout(layout); ApplicationConfiguration configuration = test.createApplicationConfiguration(); if (initialized) { @@ -157,7 +164,7 @@ public abstract class AbstractGenericTest extends AbstractGTest { Application.initializeApplication(layout, configuration); } catch (Exception e) { - throw e; + throw new AssertException(e); } } @@ -208,13 +215,8 @@ public abstract class AbstractGenericTest extends AbstractGTest { initialize(this); } - protected ApplicationLayout createApplicationLayout() { - try { - return new GhidraTestApplicationLayout(new File(getTestDirectoryPath())); - } - catch (IOException e) { - throw new AssertException(e); - } + protected ApplicationLayout createApplicationLayout() throws IOException { + return new GhidraTestApplicationLayout(new File(getTestDirectoryPath())); } protected ApplicationConfiguration createApplicationConfiguration() { diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraApplicationLayout.java b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraApplicationLayout.java index 31423b8833..ad3fd284d4 100644 --- a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraApplicationLayout.java +++ b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraApplicationLayout.java @@ -35,7 +35,7 @@ public class GhidraApplicationLayout extends ApplicationLayout { /** * Constructs a new Ghidra application layout object. - * + * * @throws FileNotFoundException if there was a problem getting a user * directory. * @throws IOException if there was a problem getting the application @@ -52,7 +52,6 @@ public class GhidraApplicationLayout extends ApplicationLayout { // Application installation directory applicationInstallationDir = findGhidraApplicationInstallationDir(); - // User directories userTempDir = ApplicationUtilities.getDefaultUserTempDir(getApplicationProperties()); userCacheDir = ApplicationUtilities.getDefaultUserCacheDir(getApplicationProperties()); @@ -76,7 +75,7 @@ public class GhidraApplicationLayout extends ApplicationLayout { *

* This is used when something external to Ghidra needs Ghidra's layout * (like the Eclipse GhidraDevPlugin). - * + * * @param applicationInstallationDir The application installation directory. * @throws FileNotFoundException if there was a problem getting a user * directory. @@ -101,21 +100,21 @@ public class GhidraApplicationLayout extends ApplicationLayout { userCacheDir = ApplicationUtilities.getDefaultUserCacheDir(getApplicationProperties()); userSettingsDir = ApplicationUtilities.getDefaultUserSettingsDir(getApplicationProperties(), getApplicationInstallationDir()); - + // Extensions extensionInstallationDirs = findExtensionInstallationDirectories(); extensionArchiveDir = findExtensionArchiveDirectory(); // Patch directory patchDir = findPatchDirectory(); - + // Modules modules = findGhidraModules(); } /** * Finds the application root directories for this application layout. - * + * * @return A collection of the application root directories for this layout. */ protected Collection findGhidraApplicationRootDirs() { @@ -125,7 +124,7 @@ public class GhidraApplicationLayout extends ApplicationLayout { /** * Finds the application installation directory for this Ghidra application * layout. - * + * * @return The application installation directory for this Ghidra * application layout. Could be null if there is no application * installation directory. @@ -144,7 +143,7 @@ public class GhidraApplicationLayout extends ApplicationLayout { /** * Finds the modules for this Ghidra application layout. - * + * * @return The modules for this Ghidra application layout. * @throws IOException if there was a problem finding the modules on disk. */ @@ -161,7 +160,7 @@ public class GhidraApplicationLayout extends ApplicationLayout { if (extensionModuleDirs != null) { for (File extensionModuleDir : extensionModuleDirs) { - // Skip extensions that live in an application root directory...we've already + // Skip extensions that live in an application root directory...we've already // found those. if (applicationRootDirs.stream() .anyMatch(dir -> FileUtilities.isPathContainedWithin(dir.getFile(false), @@ -183,7 +182,7 @@ public class GhidraApplicationLayout extends ApplicationLayout { // These might exist if Ghidra was launched from an Eclipse project that resides // external to the Ghidra installation. for (String entry : System.getProperty("java.class.path", "").split(File.pathSeparator)) { - final ResourceFile classpathEntry = new ResourceFile(entry); + ResourceFile classpathEntry = new ResourceFile(entry); // We only care about directories (skip jars) if (!classpathEntry.isDirectory()) { @@ -193,8 +192,8 @@ public class GhidraApplicationLayout extends ApplicationLayout { // Skip classpath entries that live in an application root directory...we've already // found those. if (applicationRootDirs.stream() - .anyMatch(dir -> FileUtilities.isPathContainedWithin( - dir.getFile(false), classpathEntry.getFile(false)))) { + .anyMatch(dir -> FileUtilities.isPathContainedWithin(dir.getFile(false), + classpathEntry.getFile(false)))) { continue; } @@ -234,7 +233,7 @@ public class GhidraApplicationLayout extends ApplicationLayout { *

- * + * * @return the archive folder, or null if can't be determined */ protected ResourceFile findExtensionArchiveDirectory() { @@ -250,23 +249,23 @@ public class GhidraApplicationLayout extends ApplicationLayout { } /** - * Returns a prioritized list of directories where Ghidra extensions are installed. These + * Returns a prioritized list of directories where Ghidra extensions are installed. These * should be at the following locations:
* - * + * * @return the install folder, or null if can't be determined */ protected List findExtensionInstallationDirectories() { List dirs = new ArrayList<>(); - + // Would like to find a better way to do this, but for the moment this seems the - // only solution. We want to get the 'Extensions' directory in ghidra, but there's - // no way to retrieve that directory directly. We can only get the full set of + // only solution. We want to get the 'Extensions' directory in ghidra, but there's + // no way to retrieve that directory directly. We can only get the full set of // application root dirs and search for it, hoping we don't encounter one with the // name 'Extensions' in one of the other root dirs. if (SystemUtilities.isInDevelopmentMode()) { diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraTestApplicationLayout.java b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraTestApplicationLayout.java index 68109c994b..a17cdfc89b 100644 --- a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraTestApplicationLayout.java +++ b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraTestApplicationLayout.java @@ -16,13 +16,17 @@ package ghidra; import java.io.*; -import java.util.Collections; -import java.util.List; +import java.nio.file.Path; +import java.util.*; +import java.util.function.Predicate; import generic.jar.ResourceFile; +import ghidra.framework.GModule; +import utility.module.ClasspathFilter; +import utility.module.ModuleUtilities; /** - * The Ghidra test application layout defines the customizable elements of the Ghidra + * The Ghidra test application layout defines the customizable elements of the Ghidra * application's directory structure when running a test. *

* This layout exists because tests often need to provide their own user settings @@ -35,14 +39,13 @@ public class GhidraTestApplicationLayout extends GhidraApplicationLayout { * directory. *

* This layout is useful when running Ghidra tests. - * + * * @param userSettingsDir The custom user settings directory to use. * @throws FileNotFoundException if there was a problem getting a user directory. * @throws IOException if there was a problem getting the application properties. */ public GhidraTestApplicationLayout(File userSettingsDir) throws FileNotFoundException, IOException { - super(); this.userSettingsDir = userSettingsDir; } @@ -63,4 +66,51 @@ public class GhidraTestApplicationLayout extends GhidraApplicationLayout { File dir = new File(getUserTempDir(), "patch"); return new ResourceFile(dir); } + + @Override + protected Map findGhidraModules() throws IOException { + + // + // 1) Enforces module dependencies by classpath to better control the test environment. + // 2) Add any dependent modules into the tests that are not already on the classpath. For + // example, this class adds all processor modules, as we do not use classpath + // dependencies for processor modules usage. + // + Set modulePatterns = getDependentModulePatterns(); + Predicate additionalPaths = path -> { + String pathString = path.toString(); + return modulePatterns.stream().anyMatch(pattern -> pathString.contains(pattern)); + }; + + Collection roots = + ModuleUtilities.findModuleRootDirectories(applicationRootDirs); + return ModuleUtilities.findModules(applicationRootDirs, roots, + new ClasspathFilter(additionalPaths)); + } + + /** + * Returns patterns that will be used to check against each discovered module. Matching module + * paths will be included as modules to be used during testing. By default, only modules that + * match the classpath entries are included. If your tests needs modules not referenced by the + * classpath, then you can override this method and add any module patterns needed. + * + *

The pattern is any desired text that will be matched against. If you wish to use path + * separators, be sure to do so in a platform-dependent manner. + * + * @return the patterns + */ + protected Set getDependentModulePatterns() { + //@formatter:off + char slash = File.separatorChar; + return new HashSet<>(Set.of( + slash + "Processors" + slash, + "TestResources", + + // This could easily be in a subclass, included by only those tests that need this + // entry. At the time of writing, there are 8 tests that need this module. For now, + // adding the entry here seems like the easiest thing to do. + "DemanglerGnu" + )); + //@formatter:on + } } diff --git a/Ghidra/Framework/Utility/src/main/java/utility/application/ApplicationLayout.java b/Ghidra/Framework/Utility/src/main/java/utility/application/ApplicationLayout.java index 50e58a44d5..75cbb2414c 100644 --- a/Ghidra/Framework/Utility/src/main/java/utility/application/ApplicationLayout.java +++ b/Ghidra/Framework/Utility/src/main/java/utility/application/ApplicationLayout.java @@ -25,11 +25,11 @@ import ghidra.framework.GModule; import utilities.util.FileUtilities; /** - * The Application Layout base class defines the customizable elements of the application's + * The Application Layout base class defines the customizable elements of the application's * directory structure. Create a subclass to define a custom layout. *

- * If a layout changes in a significant way, the - * {@link ApplicationProperties#APPLICATION_LAYOUT_VERSION_PROPERTY} should be incremented so + * If a layout changes in a significant way, the + * {@link ApplicationProperties#APPLICATION_LAYOUT_VERSION_PROPERTY} should be incremented so * external things like Eclipse GhidraDev know to look in different places for things. */ public abstract class ApplicationLayout { @@ -47,7 +47,7 @@ public abstract class ApplicationLayout { /** * Gets the application properties from the application layout - * + * * @return The application properties. Should never be null. */ public final ApplicationProperties getApplicationProperties() { @@ -56,7 +56,7 @@ public abstract class ApplicationLayout { /** * Gets the application root directories from the application layout. - * + * * @return A collection of application root directories (or null if not set). */ public final Collection getApplicationRootDirs() { @@ -65,7 +65,7 @@ public abstract class ApplicationLayout { /** * Gets the application installation directory from the application layout. - * + * * @return The application installation directory (or null if not set). */ public final ResourceFile getApplicationInstallationDir() { @@ -74,7 +74,7 @@ public abstract class ApplicationLayout { /** * Gets the application's modules from the application layout. - * + * * @return The application's modules as a map (mapping module name to module for convenience). */ public final Map getModules() { @@ -83,7 +83,7 @@ public abstract class ApplicationLayout { /** * Gets the user temp directory from the application layout. - * + * * @return The user temp directory (or null if not set). */ public final File getUserTempDir() { @@ -92,7 +92,7 @@ public abstract class ApplicationLayout { /** * Gets the user cache directory from the application layout. - * + * * @return The user cache directory (or null if not set). */ public final File getUserCacheDir() { @@ -101,7 +101,7 @@ public abstract class ApplicationLayout { /** * Gets the user settings directory from the application layout. - * + * * @return The user settings directory (or null if not set). */ public final File getUserSettingsDir() { @@ -110,10 +110,10 @@ public abstract class ApplicationLayout { /** * Returns the directory where archived application Extensions are stored. - * - * @return the application Extensions archive directory. Could be null if the + * + * @return the application Extensions archive directory. Could be null if the * {@link ApplicationLayout} does not support application Extensions. - * + * */ public final ResourceFile getExtensionArchiveDir() { return extensionArchiveDir; @@ -121,7 +121,7 @@ public abstract class ApplicationLayout { /** * Returns an {@link List ordered list} of the application Extensions installation directories. - * + * * @return an {@link List ordered list} of the application Extensions installation directories. * Could be empty if the {@link ApplicationLayout} does not support application Extensions. */ @@ -140,7 +140,7 @@ public abstract class ApplicationLayout { /** * Creates the application's user directories (or ensures they already exist). - * + * * @throws IOException if there was a problem creating the application's user directories. */ public final void createUserDirs() throws IOException { @@ -168,13 +168,14 @@ public abstract class ApplicationLayout { } /** - * Checks whether or not the application is using a "single jar" layout. Custom application - * layouts that extend this class can override this method once they determine they are in + * Checks whether or not the application is using a "single jar" layout. Custom application + * layouts that extend this class can override this method once they determine they are in * single jar mode. - * + * * @return true if the application is using a "single jar" layout; otherwise, false. */ public boolean inSingleJarMode() { return false; } + } diff --git a/Ghidra/Framework/Utility/src/main/java/utility/module/ClasspathFilter.java b/Ghidra/Framework/Utility/src/main/java/utility/module/ClasspathFilter.java new file mode 100644 index 0000000000..e07665125d --- /dev/null +++ b/Ghidra/Framework/Utility/src/main/java/utility/module/ClasspathFilter.java @@ -0,0 +1,73 @@ +/* ### + * 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 utility.module; + +import java.io.File; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Predicate; + +import generic.jar.ResourceFile; +import ghidra.framework.GModule; + +/** + * A predicate used to filter modules using the classpath. Only modules included in the classpath + * will pass this filter. Any modules not on the classpath may be included by calling + * {@link #ClasspathFilter(Predicate)} with a predicate that allows other module paths. + */ +public class ClasspathFilter implements Predicate { + + private Predicate additionalPaths = p -> false; + private Set cpModulePaths = new HashSet<>(); + + /** + * Default constructor to allow only modules on the classpath. + */ + public ClasspathFilter() { + String cp = System.getProperty("java.class.path"); + String[] cpPathStrings = cp.split(File.pathSeparator); + for (String cpPathString : cpPathStrings) { + Path modulePath = ModuleUtilities.getModule(cpPathString); + if (modulePath != null) { + Path normalized = modulePath.normalize().toAbsolutePath(); + cpModulePaths.add(normalized); + } + } + } + + /** + * Constructor that allows any module to be included whose path passed the given predicate. If + * the predicate returns false, then a given module will only be included if it is in the + * classpath. + * + * @param additionalPaths a predicate that allows additional module paths (they do not need to + * be on the system classpath) + */ + public ClasspathFilter(Predicate additionalPaths) { + this(); + this.additionalPaths = additionalPaths; + } + + @Override + public boolean test(GModule m) { + ResourceFile file = m.getModuleRoot(); + Path path = Path.of(file.getAbsolutePath()); + Path normalized = path.normalize().toAbsolutePath(); + return additionalPaths.test(normalized) || cpModulePaths.contains(normalized); + } + +} diff --git a/Ghidra/Framework/Utility/src/main/java/utility/module/ModuleUtilities.java b/Ghidra/Framework/Utility/src/main/java/utility/module/ModuleUtilities.java index e340102298..72ae0a48ae 100644 --- a/Ghidra/Framework/Utility/src/main/java/utility/module/ModuleUtilities.java +++ b/Ghidra/Framework/Utility/src/main/java/utility/module/ModuleUtilities.java @@ -19,6 +19,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.*; import java.util.*; +import java.util.function.Predicate; import generic.jar.ResourceFile; import ghidra.framework.GModule; @@ -39,14 +40,14 @@ public class ModuleUtilities { public static final String MODULE_LIST = "MODULE_LIST"; /** - * How many directories deep to look for module directories, starting from an application root + * How many directories deep to look for module directories, starting from an application root * directory. For example, 3 would pick up modules as deep as: root/category/category/module */ private static final int MAX_MODULE_DEPTH = 3; /** * Checks if the given directory is a module. - * + * * @param dir the directory to check. * @return true if the given directory is a module */ @@ -56,7 +57,7 @@ public class ModuleUtilities { /** * Returns true if the given path is a module root directory. - * + * * @param path the path to check * @return true if the given path is a module root directory. */ @@ -68,14 +69,14 @@ public class ModuleUtilities { /** * Searches the given root directory for module root directories. Adds any discovered module * root directories to the given collection. - * + * * @param rootDir The directory to start looking for module root directories in. * @param moduleRootDirs A collection to add discovered module root directories to. * @return The given collection with any discovered modules added. */ public static Collection findModuleRootDirectories(ResourceFile rootDir, Collection moduleRootDirs) { - // look for any external GPL modules + // look for any external GPL modules findModuleRootDirectoriesHelper(new ResourceFile(rootDir, "../GPL"), moduleRootDirs, MAX_MODULE_DEPTH); return findModuleRootDirectoriesHelper(rootDir, moduleRootDirs, MAX_MODULE_DEPTH); @@ -100,10 +101,25 @@ public class ModuleUtilities { return moduleRootDirs; } + /** + * Searches the given root directories for module root directories. Adds any discovered module + * root directories to the returned collection. + * + *

Note: if you need to control the type of collection used to store the module roots, then + * call {@link #findModuleRootDirectories(Collection, Collection)}. + * + * @param rootDirs The directories to look for module root directories in. + * @return a new collection with any discovered modules added. + */ + public static Collection findModuleRootDirectories( + Collection rootDirs) { + return findModuleRootDirectories(rootDirs, new ArrayList<>()); + } + /** * Searches the given root directories for module root directories. Adds any discovered module * root directories to the given collection. - * + * * @param rootDirs The directories to look for module root directories in. * @param moduleRootDirs A collection to add discovered module root directories to. * @return The given collection with any discovered modules added. @@ -120,7 +136,7 @@ public class ModuleUtilities { * Searches the given jar root directory for module root directories. Uses a "module list" * file to locate the module root directories. Adds any discovered module root directories * to the given collection. - * + * * @param rootDir The jar directory to start looking for module root directories in. * @param moduleRootDirs A collection to add discovered module root directories to. * @return The given collection with any discovered modules added. @@ -137,7 +153,7 @@ public class ModuleUtilities { /** * Searches for modules in a given collection of module root directories. - * + * * @param appRootDirs The collection of application root directories associated with the the given * list of module root directories. * @param moduleRootDirs A collection of module root directories to search for modules in. @@ -146,10 +162,31 @@ public class ModuleUtilities { public static Map findModules(Collection appRootDirs, Collection moduleRootDirs) { + Predicate allModules = module -> true; + return findModules(appRootDirs, moduleRootDirs, allModules); + } + + /** + * Searches for modules in a given collection of module root directories. + * + * @param appRootDirs The collection of application root directories associated with the the given + * list of module root directories. + * @param moduleRootDirs A collection of module root directories to search for modules in. + * @param moduleFilter a predicate used to filter modules; a given module will not be included + * when the predicate returns false. + * @return The discovered modules as a map (mapping module name to module for convenience). + */ + public static Map findModules(Collection appRootDirs, + Collection moduleRootDirs, Predicate moduleFilter) { + Map map = new TreeMap<>(); for (ResourceFile moduleRoot : moduleRootDirs) { GModule gModule = new GModule(appRootDirs, moduleRoot); + if (!moduleFilter.test(gModule)) { + continue; + } + if (map.put(moduleRoot.getName(), gModule) != null) { StringBuilder collided = new StringBuilder(); for (ResourceFile collideRoot : moduleRootDirs) { @@ -167,7 +204,7 @@ public class ModuleUtilities { /** * Gets the "lib" directories from the given modules. - * + * * @param modules The modules to get the lib directories of. * @return A collection of lib directories from the given modules. */ @@ -189,7 +226,7 @@ public class ModuleUtilities { /** * Gets the directory locations of the .class files and resources from the given modules. - * + * * @param modules The modules to get the compiled .class and resources directories of. * @return A collection of directories containing classes and resources from the given modules. */ @@ -197,8 +234,8 @@ public class ModuleUtilities { String[] binaryPathTokens = BINARY_PATH.split(":"); List binDirectories = new ArrayList<>(); for (GModule module : modules.values()) { - Arrays.stream(binaryPathTokens).forEach( - token -> module.collectExistingModuleDirs(binDirectories, token)); + Arrays.stream(binaryPathTokens) + .forEach(token -> module.collectExistingModuleDirs(binDirectories, token)); } return binDirectories; } @@ -215,10 +252,10 @@ public class ModuleUtilities { *
*
and false for these paths: *
- *
+ *
* /some/random/path
* /some/dir/features/ - * + * * @param pathName the path name to check * @return true if the given path is parented by a module root directory. * @see #isModuleDirectory(Path) @@ -228,7 +265,7 @@ public class ModuleUtilities { } /** - * Returns the path of the module containing the given path string, if it is parented by a + * Returns the path of the module containing the given path string, if it is parented by a * module root directory. *

* For example, given a module path of /some/dir/features/cool_module/, then this @@ -240,10 +277,10 @@ public class ModuleUtilities { *
*
and null for these paths: *
- *
+ *
* /some/random/path
* /some/dir/features/ - * + * * @param pathName the path name to check * @return the module root directory; null if the path is not in a module * @see #isModuleDirectory(Path) @@ -271,18 +308,18 @@ public class ModuleUtilities { /** * Returns a file that is the root folder of the repository containing the given file. 'Root' - * here means a folder that contains a repository folder. As an example, given a repo + * here means a folder that contains a repository folder. As an example, given a repo * structure of: - * + * *

/userdir/repoRoot/repoDir/.git
- * + * *

then this method, given will produce the following results (input -> output):
- * + * *

/userdir/repoRoot/repoDir/.git -> /userdir/repoRoot *
/userdir/repoRoot/repoDir -> /userdir/repoRoot *
/userdir/repoRoot -> /userdir/repoRoot - * - * + * + * * @param f the child file of the desired repo * @return a file that is the root folder of the repository containing the given file; null * if the given file is not under a repo directory or itself a repo root @@ -311,16 +348,16 @@ public class ModuleUtilities { } /** - * Returns a file that is the repository folder containing the given file. As an example, + * Returns a file that is the repository folder containing the given file. As an example, * given a repo structure of: - * + * *

/userdir/repoRoot/repoDir/.git
- * + * *

then this method, given will produce the following results (input -> output):
- * + * *

/userdir/repoRoot/repoDir/.git -> /userdir/repoRoot/repoDir *
/userdir/repoRoot/repoDir -> /userdir/repoRoot/repoDir - * + * * @param f the child file of the desired repo * @return a file that is the repo folder of the repository containing the given file; null * if the given file is not under a repo directory @@ -339,7 +376,7 @@ public class ModuleUtilities { /** * Checks to see if the given {@link GModule module} is external to the Ghidra installation * directory - * + * * @param module the module to check * @param layout Ghidra's layout * @return true if the given {@link GModule module} is external to the Ghidra installation diff --git a/Ghidra/Test/IntegrationTest/build.gradle b/Ghidra/Test/IntegrationTest/build.gradle index b2ba16df34..49b52cce81 100644 --- a/Ghidra/Test/IntegrationTest/build.gradle +++ b/Ghidra/Test/IntegrationTest/build.gradle @@ -60,6 +60,7 @@ dependencies { testImplementation project(path: ':Base', configuration: 'integrationTestArtifacts') testImplementation project(path: ':FunctionGraph', configuration: 'testArtifacts') testImplementation project(path: ':PDB', configuration: 'testArtifacts') + testImplementation project(path: ':GnuDemangler', configuration: 'testArtifacts') } // For Java 9, we must explicitly export references to the internal classes we are using. diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcodeCPort/slgh_compile/WithBlockTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcodeCPort/slgh_compile/WithBlockTest.java deleted file mode 100644 index d6e5a09be9..0000000000 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcodeCPort/slgh_compile/WithBlockTest.java +++ /dev/null @@ -1,177 +0,0 @@ -/* ### - * 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.pcodeCPort.slgh_compile; - -import static org.junit.Assert.assertEquals; - -import java.util.concurrent.atomic.AtomicReference; - -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; -import org.junit.Before; -import org.junit.Test; - -import ghidra.app.plugin.languages.sleigh.ConstructorEntryVisitor; -import ghidra.app.plugin.languages.sleigh.SleighLanguages; -import ghidra.app.plugin.processors.sleigh.*; -import ghidra.app.plugin.processors.sleigh.pattern.DisjointPattern; -import ghidra.app.plugin.processors.sleigh.symbol.SubtableSymbol; -import ghidra.program.model.lang.LanguageID; -import ghidra.test.AbstractGhidraHeadlessIntegrationTest; - -public class WithBlockTest extends AbstractGhidraHeadlessIntegrationTest { - - protected static boolean setupDone; - protected static LanguageID langID; - protected static SleighLanguageProvider provider; - protected static SleighLanguage lang; - - @Before - public void setUp() throws Exception { - if (!setupDone) { - langID = new LanguageID("TestWith:BE:32:default"); - provider = new SleighLanguageProvider(); - lang = (SleighLanguage) provider.getLanguage(langID); - setupDone = true; - } - } - - protected String opnd(int n) { - return "\n" + (char) ('A' + n); - } - - protected Pair findConstructor(String table, String firstPrint) { - AtomicReference fpat = new AtomicReference<>(null); - AtomicReference fcon = new AtomicReference<>(null); - SleighLanguages.traverseConstructors(lang, new ConstructorEntryVisitor() { - @Override - public int visit(SubtableSymbol subtable, DisjointPattern pattern, Constructor cons) { - if (table.equals(subtable.getName()) && - firstPrint.equals(cons.getPrintPieces().get(0))) { - if (null != fpat.get()) { - throw new AssertionError("Multiple constructors found. " + - "Write the test slaspec such that no two constructors in the same " + - "table share the same first printpiece."); - } - fpat.set(pattern); - fcon.set(cons); - } - return CONTINUE; - } - }); - if (null == fpat.get()) { - throw new AssertionError( - "No such constructor found: " + table + ":" + firstPrint + "..."); - } - return new ImmutablePair<>(fpat.get(), fcon.get()); - } - - @Test - public void testNOP1_NoWith() { - Pair NOP1 = findConstructor("instruction", "NOP1"); - assertEquals("ins:SS:XF:XX:XX:XX", NOP1.getLeft().toString()); - assertEquals(0, NOP1.getRight().getContextChanges().size()); - } - - @Test - public void testRel8addr_WithTabRel8_WithChgPhase3() { - Pair Rel8addr = findConstructor("Rel8", opnd(1)); - assertEquals("always", Rel8addr.getLeft().toString()); - assertEquals(1, Rel8addr.getRight().getContextChanges().size()); - assertEquals("ctx&F0:00:00:00 := 0x3( << 28)", - Rel8addr.getRight().getContextChanges().get(0).toString()); - } - - @Test - public void testOP1r0_WithTabOp1() { - Pair OP1r0 = findConstructor("OP1", "r0"); - assertEquals("cmb:(ctx:1X:XX:XX:XX,ins:X0:XX:XX:XX)", OP1r0.getLeft().toString()); - assertEquals(1, OP1r0.getRight().getContextChanges().size()); - assertEquals("ctx&F0:00:00:00 := 0x2( << 28)", - OP1r0.getRight().getContextChanges().get(0).toString()); - } - - @Test - public void testOP1r1_WithPatPhase1() { - Pair OP1r1 = findConstructor("OP1", "r1"); - assertEquals("cmb:(ctx:1X:XX:XX:XX,ins:X1:XX:XX:XX)", OP1r1.getLeft().toString()); - assertEquals(1, OP1r1.getRight().getContextChanges().size()); - assertEquals("ctx&F0:00:00:00 := 0x2( << 28)", - OP1r1.getRight().getContextChanges().get(0).toString()); - } - - @Test - public void testOP1r2_WithChgPhase2() { - Pair OP1r2 = findConstructor("OP1", "r2"); - assertEquals("ins:X2:XX:XX:XX", OP1r2.getLeft().toString()); - assertEquals(2, OP1r2.getRight().getContextChanges().size()); - assertEquals("ctx&F0:00:00:00 := 0x2( << 28)", - OP1r2.getRight().getContextChanges().get(0).toString()); - assertEquals("ctx&F0:00:00:00 := 0x1( << 28)", - OP1r2.getRight().getContextChanges().get(1).toString()); - } - - @Test - public void testDSTr0_WithPatPhase1_WithTabDST() { - Pair DSTr0 = findConstructor("DST", "r0"); - assertEquals("cmb:(ctx:1X:XX:XX:XX,ins:SS:0X:XX:XX:XX)", DSTr0.getLeft().toString()); - assertEquals(0, DSTr0.getRight().getContextChanges().size()); - } - - @Test - public void testOP1r3_WithPatPhase1_WithTabOP1() { - Pair OP1r3 = findConstructor("OP1", "r3"); - assertEquals("cmb:(ctx:1X:XX:XX:XX,ins:X3:XX:XX:XX)", OP1r3.getLeft().toString()); - assertEquals(0, OP1r3.getRight().getContextChanges().size()); - } - - @Test - public void testOP2r1_WithPatPhase1_WithTabOP1() { - Pair OP2r1 = findConstructor("OP2", "r1"); - assertEquals("cmb:(ctx:1X:XX:XX:XX,ins:1X:XX:XX:XX)", OP2r1.getLeft().toString()); - assertEquals(0, OP2r1.getRight().getContextChanges().size()); - } - - @Test - public void testOP2r0_WithPatPhase1_WithTabOP2() { - Pair OP2r0 = findConstructor("OP2", "r0"); - assertEquals("cmb:(ctx:1X:XX:XX:XX,ins:0X:XX:XX:XX)", OP2r0.getLeft().toString()); - assertEquals(0, OP2r0.getRight().getContextChanges().size()); - } - - @Test - public void testNOP2_NoWith() { - Pair NOP2 = findConstructor("instruction", "NOP2"); - assertEquals("ins:SS:XE:XX:XX:XX", NOP2.getLeft().toString()); - assertEquals(0, NOP2.getRight().getContextChanges().size()); - } - - @Test - public void testADD_NoWith() { - Pair ADD = findConstructor("instruction", "ADD"); - assertEquals("cmb:(ctx:1X:XX:XX:XX,ins:SS:X0:XX:XX:XX)", ADD.getLeft().toString()); - assertEquals(0, ADD.getRight().getContextChanges().size()); - } - - @Test - public void testLD_NoWith() { - Pair LD = findConstructor("instruction", "LD"); - assertEquals("cmb:(ctx:1X:XX:XX:XX,ins:SS:X8:XX:XX:XX)", LD.getLeft().toString()); - assertEquals(0, LD.getRight().getContextChanges().size()); - } - - // TODO: Explicit override of subtable in with -}