diff --git a/Ghidra/Configurations/Public_Release/src/global/docs/ChangeHistory.md b/Ghidra/Configurations/Public_Release/src/global/docs/ChangeHistory.md index 0b0e8f53c2..7caba5dd3d 100644 --- a/Ghidra/Configurations/Public_Release/src/global/docs/ChangeHistory.md +++ b/Ghidra/Configurations/Public_Release/src/global/docs/ChangeHistory.md @@ -10,6 +10,7 @@ * _Data Types_. Fixed the Data Type Preview to show the typedef data type before the typedef name. (GP-6858) * _Debugger:Agents_. Patched a possible out-of-memory vulnerability in TraceRmi. (GP-6718) * _Debugger:Listing_. Added __Disassemble as MIPS__:_:16e__ for the dynamic listing. (GP-6655) +* _Framework_. Changed potentially unsafe calls to `Class.forName` to perform additional checks prior to instantiation. (GP-6717) * _Multi-User_. Increased the default value for serialization filter `maxarray` limit from 32,000 to 200,000. This was done to avoid related class serialization errors when communicating with the Ghidra Server for large repository database files. See `Ghidra/Framework/FileSystem/data/serialFilterREADME.md` - it may be necessary to specify an increased `maxarray` value if related serialization errors occur. (GP-6842) * _Multi-User_. Corrected memory leak issue related to Ghidra Server, affecting both client and server. Buffer compression logic failed to properly release system resources. (GP-6862) * _Multi-User:Merge_. Corrected bug in Property List Merge which could cause an exception. (GP-6854) @@ -19,6 +20,10 @@ * _Processors_. Corrected issue with RISC-V attempting to write to constants. (GP-6849, Issue #9198) * _PyGhidra_. Handled a possible `NotADirectoryError` in `pyghidra_launcher.py`. (GP-6825) +### Notable API Changes +* _BSim_. (GP-6866) The unused methods `FunctionDatabase.getConfigurationTemplates` and `FunctionDatabase.isConfigTemplate` have been removed. +* _Framework_. (GP-6717) Added `ClassSearcher.forNameSafe` method. + # Ghidra 12.1 Change History (May 2026) ### New Features diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/AbstractDebuggerParameterDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/AbstractDebuggerParameterDialog.java index 6aec34def2..3cf3d7da32 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/AbstractDebuggerParameterDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/AbstractDebuggerParameterDialog.java @@ -52,7 +52,6 @@ import ghidra.util.layout.PairLayout; public abstract class AbstractDebuggerParameterDialog

extends DialogComponentProvider implements PropertyChangeListener { - static final String KEY_MEMORIZED_ARGUMENTS = "memorizedArguments"; public static class BigIntEditor extends PropertyEditorSupport { String asText = ""; @@ -435,21 +434,7 @@ public abstract class AbstractDebuggerParameterDialog

extends DialogComponent } } - protected record NameTypePair(String name, Class type) { - public static NameTypePair fromString(String name) throws ClassNotFoundException { - String[] parts = name.split(",", 2); - if (parts.length != 2) { - // This appears to be a bad assumption - empty fields results in solitary labels - return new NameTypePair(parts[0], String.class); - //throw new IllegalArgumentException("Could not parse name,type"); - } - return new NameTypePair(parts[0], Class.forName(parts[1])); - } - - public final String encodeString() { - return name + "," + type.getName(); - } - } + protected record NameTypePair(String name, Class type) {} private final BidiMap paramEditors = new DualLinkedHashBidiMap<>(); @@ -722,46 +707,6 @@ public abstract class AbstractDebuggerParameterDialog

extends DialogComponent new ValStr<>(editor.getValue(), editor.getAsText())); } - public void writeConfigState(SaveState saveState) { - SaveState subState = new SaveState(); - for (Map.Entry> ent : memorized.entrySet()) { - NameTypePair ntp = ent.getKey(); - P param = parameters.get(ntp.name()); - if (param == null) { - continue; - } - parameterSaveValue(param, subState, ntp.encodeString(), ent.getValue()); - } - saveState.putSaveState(KEY_MEMORIZED_ARGUMENTS, subState); - } - - public void readConfigState(SaveState saveState) { - /** - * TODO: This method is defunct. It is only used by the DebuggerObjectsProvider, which is - * now deprecated, but I suspect other providers intend to use this in the same way. If - * those providers don't manually load/compute initial and default values at the time of - * prompting, then this will need to be fixed. The decode of the values will need to be - * delayed until (and repeated every time) parameters are populated. - */ - SaveState subState = saveState.getSaveState(KEY_MEMORIZED_ARGUMENTS); - if (subState == null) { - return; - } - for (String name : subState.getNames()) { - try { - NameTypePair ntp = NameTypePair.fromString(name); - P param = parameters.get(ntp.name()); - if (param == null) { - continue; - } - memorized.put(ntp, parameterLoadValue(param, subState, ntp.encodeString())); - } - catch (Exception e) { - Msg.error(this, "Error restoring memorized parameter " + name, e); - } - } - } - public void setDescription(String htmlDescription) { if (htmlDescription == null) { descriptionLabel.setBorder(BorderFactory.createEmptyBorder()); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/InvocationDialogHelper.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/InvocationDialogHelper.java index 82b8c66bf7..dcb6d58906 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/InvocationDialogHelper.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/InvocationDialogHelper.java @@ -27,12 +27,11 @@ import docking.test.AbstractDockingTest; import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils; import ghidra.async.SwingExecutorService; import ghidra.debug.api.ValStr; -import ghidra.framework.options.SaveState; public class InvocationDialogHelper> { - public static > InvocationDialogHelper waitFor( - Class cls) { + public static > InvocationDialogHelper + waitFor(Class cls) { D dialog = AbstractDockingTest.waitForDialogComponent(cls); return new InvocationDialogHelper<>(dialog); } @@ -86,16 +85,4 @@ public class InvocationDialogHelper dialog.invoke(null)); } - - public SaveState saveState() { - SaveState parent = new SaveState(); - runSwing(() -> dialog.writeConfigState(parent)); - return parent.getSaveState(AbstractDebuggerParameterDialog.KEY_MEMORIZED_ARGUMENTS); - } - - public void loadState(SaveState state) { - SaveState parent = new SaveState(); - parent.putSaveState(AbstractDebuggerParameterDialog.KEY_MEMORIZED_ARGUMENTS, state); - runSwing(() -> dialog.readConfigState(parent)); - } } diff --git a/Ghidra/Extensions/Jython/src/main/java/ghidra/jython/JythonScriptProvider.java b/Ghidra/Extensions/Jython/src/main/java/ghidra/jython/JythonScriptProvider.java index 71b408e7e5..7495d7fcdc 100644 --- a/Ghidra/Extensions/Jython/src/main/java/ghidra/jython/JythonScriptProvider.java +++ b/Ghidra/Extensions/Jython/src/main/java/ghidra/jython/JythonScriptProvider.java @@ -40,8 +40,7 @@ public class JythonScriptProvider extends AbstractPythonScriptProvider { throws GhidraScriptLoadException { try { - Class clazz = Class.forName(JythonScript.class.getName()); - GhidraScript script = (GhidraScript) clazz.getConstructor().newInstance(); + GhidraScript script = new JythonScript(); script.setSourceFile(sourceFile); return script; } diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/FunctionDatabase.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/FunctionDatabase.java index 3509b0109b..c1bb78cc6c 100755 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/FunctionDatabase.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/FunctionDatabase.java @@ -15,13 +15,9 @@ */ package ghidra.features.bsim.query; -import java.io.*; -import java.util.*; +import java.io.FileNotFoundException; +import java.io.IOException; -import javax.xml.parsers.*; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; import org.xml.sax.SAXException; import generic.jar.ResourceFile; @@ -33,7 +29,6 @@ import ghidra.features.bsim.query.facade.SFOverviewInfo; import ghidra.features.bsim.query.facade.SFQueryInfo; import ghidra.features.bsim.query.protocol.*; import ghidra.framework.Application; -import ghidra.util.Msg; public interface FunctionDatabase extends AutoCloseable { @@ -333,72 +328,6 @@ public interface FunctionDatabase extends AutoCloseable { return new WeightedLSHCosineVectorFactory(); } - /** - * Returns a list of all configuration template files. - * - * @return list of template files - */ - public static List getConfigurationTemplates() { - List templateFiles = new ArrayList<>(); - - ResourceFile moduleDataSubDirectory; - try { - moduleDataSubDirectory = Application.getModuleDataSubDirectory(""); - File templateDir = new File(moduleDataSubDirectory.getAbsolutePath()); - if (!templateDir.exists()) { - return Collections.emptyList(); - } - - FilenameFilter nameFilter = (dir, name) -> { - if (!name.endsWith(".xml")) { - return false; - } - - return true; - }; - - File[] files = templateDir.listFiles(nameFilter); - if (files != null) { - for (File file : files) { - if (isConfigTemplate(file)) { - templateFiles.add(file); - } - } - } - } - catch (IOException e) { - Msg.error(null, "Error retrieving configuration templates", e); - } - - return templateFiles; - } - - /** - * Determines if a given xml file is a config template. This is done by opening the file - * and checking for the presence of a {@code } root tag. - * - * @param file the file to inspect - * @return true if the file is config template - */ - static boolean isConfigTemplate(File file) { - - try { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - DocumentBuilder builder = factory.newDocumentBuilder(); - Document doc = builder.parse(file); - - Element rootElem = doc.getDocumentElement(); - if (rootElem.getTagName().equals("dbconfig")) { - return true; - } - } - catch (ParserConfigurationException | SAXException | IOException e) { - Msg.error(null, "Error inspecting xml file", e); - } - - return false; - } - /** * Get the maximum number of functions to be queried per staged query when searching * for similar functions. diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/description/DescriptionManager.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/description/DescriptionManager.java index c6ad517abd..fb19d51428 100755 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/description/DescriptionManager.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/description/DescriptionManager.java @@ -4,9 +4,9 @@ * 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. @@ -225,9 +225,6 @@ public class DescriptionManager { */ public ExecutableRecord newExecutableRecord(String md5, String enm, String cnm, String arc, Date dt, String repo, String path, RowKey id) throws LSHException { - if (md5.length() != 32) { - throw new LSHException("MD5 field must be exactly 32 hex characters"); - } ExecutableRecord newexe = new ExecutableRecord(md5, enm, cnm, arc, dt, id, repo, path); if (!exerec.add(newexe)) { ExecutableRecord oldexe = exerec.floor(newexe); diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/description/ExecutableRecord.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/description/ExecutableRecord.java index 5a920bd31e..61f41af730 100755 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/description/ExecutableRecord.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/description/ExecutableRecord.java @@ -19,6 +19,8 @@ import java.io.IOException; import java.io.Writer; import java.net.URL; import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import generic.hash.SimpleCRC32; import ghidra.features.bsim.query.LSHException; @@ -53,6 +55,9 @@ public class ExecutableRecord implements Comparable { public static final int METADATA_PATH = 32; public static final int METADATA_LIBR = 64; + private static final String md5Regex = "[a-fA-F0-9]{32}"; + private static final Pattern md5Matcher = Pattern.compile(md5Regex); + private final String md5sum; // The MD5 hash of the executable private final String executableName; // The name of the executable private final String architecture; // The architecture on which the executable runs @@ -77,6 +82,13 @@ public class ExecutableRecord implements Comparable { public List catinsert; // Non-null, if there are only insertions } + private static void checkValidMD5(String md5) { + Matcher matcher = md5Matcher.matcher(md5); + if (!matcher.matches()) { + throw new IllegalArgumentException("Invalid MD5 hash string: " + md5); + } + } + /** * Convert a 32-bit integer to hexadecimal ascii representation * @param val is the integer to encode @@ -125,8 +137,10 @@ public class ExecutableRecord implements Comparable { /** * Constructor for searching within a DescriptionManager * @param md5 is hash of executable being searched for + * @throws IllegalArgumentException if {@code md5} is not a valid md5 hash string */ protected ExecutableRecord(String md5) { + checkValidMD5(md5); md5sum = md5; executableName = ""; architecture = ""; @@ -151,9 +165,11 @@ public class ExecutableRecord implements Comparable { * @param id is the row id of the record * @param repo is the repository containing the executable (may be null) * @param path is the path to the executable (may be null) + * @throws IllegalArgumentException if {@code md5} is not a valid MD5 hash string */ public ExecutableRecord(String md5, String execName, String compilerName, String architecture, Date date, RowKey id, String repo, String path) { + checkValidMD5(md5); this.md5sum = md5; this.executableName = execName; this.architecture = architecture; @@ -177,9 +193,11 @@ public class ExecutableRecord implements Comparable { * @param id is the row id of the record * @param repo is the repository containing the executable (may be null) * @param pth is the path to the executable (may be null) + * @throws IllegalArgumentException if {@code md5} is not a valid MD5 hash string. */ public ExecutableRecord(String md5, String enm, String cnm, String arc, Date dt, List uc, RowKey id, String repo, String pth) { + checkValidMD5(md5); md5sum = md5; executableName = enm; architecture = arc; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/nav/LocationMemento.java b/Ghidra/Features/Base/src/main/java/ghidra/app/nav/LocationMemento.java index 4097d73e43..5bb322e1bf 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/nav/LocationMemento.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/nav/LocationMemento.java @@ -24,6 +24,7 @@ import ghidra.program.util.AddressFieldLocation; import ghidra.program.util.ProgramLocation; import ghidra.util.Msg; import ghidra.util.SystemUtilities; +import ghidra.util.classfinder.ClassSearcher; public class LocationMemento { private static final String PROGRAM_PATH = "PROGRAM_PATH_"; @@ -147,34 +148,25 @@ public class LocationMemento { return null; } - ClassLoader loader = LocationMemento.class.getClassLoader(); try { - Class clazz = Class.forName(className, false, loader); - if (!LocationMemento.class.isAssignableFrom(clazz)) { - Msg.error(LocationMemento.class, "Class is not a LocationMemento: " + clazz); - return null; - } - - Class mementoClass = clazz.asSubclass(LocationMemento.class); + Class mementoClass = ClassSearcher.forNameSafe(className, + LocationMemento.class, LocationMemento.class.getClassLoader()); Constructor constructor = mementoClass.getConstructor(SaveState.class, Program[].class); return constructor.newInstance(saveState, programs); } catch (ClassNotFoundException e) { - // class must have been deleted or renamed + // this can happen for locations created by plugins that are no longer installed + Msg.info(LocationMemento.class, + "Unable to identify saved location, class not found: " + className); } - catch (InstantiationException e) { - Msg.showError(ProgramLocation.class, null, "Programming Error", "Class " + className + - " must have public constructor!", e); - } - catch (IllegalAccessException e) { - Msg.showError(ProgramLocation.class, null, "Programming Error", "Class " + className + + catch (InstantiationException | IllegalAccessException e) { + Msg.showError(LocationMemento.class, null, "Programming Error", "Class " + className + " must have public constructor!", e); } catch (NoSuchMethodException e) { - Msg.showError(ProgramLocation.class, null, "Programming Error", "Class " + className + + Msg.showError(LocationMemento.class, null, "Programming Error", "Class " + className + " must have a public constructor that takes a SaveState " + "and a Program[]!", e); - e.printStackTrace(); } catch (InvocationTargetException e) { Throwable cause = e.getCause(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java index 53b2db3974..a516aee929 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java @@ -54,6 +54,7 @@ import ghidra.program.model.listing.Program; import ghidra.program.util.GhidraProgramUtilities; import ghidra.program.util.ProgramLocation; import ghidra.util.*; +import ghidra.util.classfinder.ClassSearcher; import ghidra.util.exception.*; import ghidra.util.task.TaskMonitor; import utilities.util.FileUtilities; @@ -774,21 +775,21 @@ public class HeadlessAnalyzer { classLoaderForDotClassScripts = URLClassLoader.newInstance(urls.toArray(new URL[0])); - Class c = Class.forName(className, true, classLoaderForDotClassScripts); + ClassSearcher.forNameSafe(className, GhidraScript.class, + classLoaderForDotClassScripts); - if (GhidraScript.class.isAssignableFrom(c)) { - // No issues, but return null, which signifies we don't actually have a - // ResourceFile to associate with the script name - return null; - } - - Msg.error(this, - "REPORT SCRIPT ERROR: java class '" + className + "' is not a GhidraScript"); + // No issues, but return null, which signifies we don't actually have a + // ResourceFile to associate with the script name + return null; } catch (ClassNotFoundException e) { Msg.error(this, "REPORT SCRIPT ERROR: java class not found for '" + className + "'"); } + catch (ClassCastException e) { + Msg.error(this, + "REPORT SCRIPT ERROR: java class '" + className + "' is not a GhidraScript"); + } throw new IllegalArgumentException("Invalid script: " + scriptName); } @@ -901,13 +902,14 @@ public class HeadlessAnalyzer { } String className = scriptName.substring(0, scriptName.length() - 6); - Class c = Class.forName(className, true, classLoaderForDotClassScripts); + Class c = ClassSearcher.forNameSafe(className, + GhidraScript.class, classLoaderForDotClassScripts); // Get parent folder to pass to GhidraScript File parentFile = new File(c.getResource(c.getSimpleName() + ".class").toURI()) .getParentFile(); - currScript = (GhidraScript) c.getConstructor().newInstance(); + currScript = c.getConstructor().newInstance(); currScript.setScriptArgs(scriptArgs); if (options.propertiesFilePaths.size() > 0) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonViewState.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonViewState.java index 9c64c5b3f6..09486c2b83 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonViewState.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonViewState.java @@ -20,6 +20,7 @@ import java.util.Map.Entry; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.PluginTool; +import ghidra.util.classfinder.ClassSearcher; /** * A state object to save settings each type of comparison view known by the system. This class @@ -73,9 +74,8 @@ public class CodeComparisonViewState { String[] names = classStates.getNames(); for (String className : names) { try { - @SuppressWarnings("unchecked") - Class clazz = - (Class) Class.forName(className); + Class clazz = ClassSearcher.forNameSafe(className, + CodeComparisonView.class, getClass().getClassLoader()); SaveState classState = classStates.getSaveState(className); states.put(clazz, classState); } diff --git a/Ghidra/Features/Base/src/main/java/help/screenshot/AbstractScreenShotGenerator.java b/Ghidra/Features/Base/src/main/java/help/screenshot/AbstractScreenShotGenerator.java index 9bb943ba02..8d5690a49b 100644 --- a/Ghidra/Features/Base/src/main/java/help/screenshot/AbstractScreenShotGenerator.java +++ b/Ghidra/Features/Base/src/main/java/help/screenshot/AbstractScreenShotGenerator.java @@ -15,7 +15,8 @@ */ package help.screenshot; -import static org.junit.Assert.*; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import java.awt.*; import java.awt.geom.GeneralPath; @@ -80,6 +81,7 @@ import ghidra.program.util.ProgramSelection; import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.TestEnv; import ghidra.util.ColorUtils; +import ghidra.util.classfinder.ClassSearcher; import ghidra.util.exception.AssertException; import ghidra.util.exception.UsrException; import ghidra.util.task.TaskMonitor; @@ -1355,10 +1357,9 @@ public abstract class AbstractScreenShotGenerator extends AbstractGhidraHeadedIn public Plugin loadPlugin(String className) { try { - Class clazz = Class.forName(className); - @SuppressWarnings("unchecked") - Class pluginClazz = (Class) clazz; - return loadPlugin(pluginClazz); + Class clazz = + ClassSearcher.forNameSafe(className, Plugin.class, getClass().getClassLoader()); + return loadPlugin(clazz); } catch (ClassCastException e) { e.printStackTrace(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/ManagePlugins2Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/ManagePlugins2Test.java index bb7cba6f52..ab2ad9cd29 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/ManagePlugins2Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/ManagePlugins2Test.java @@ -4,9 +4,9 @@ * 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. @@ -15,8 +15,8 @@ */ package ghidra.framework.plugintool.dialog; -import static org.hamcrest.collection.IsEmptyCollection.*; -import static org.hamcrest.core.IsNot.*; +import static org.hamcrest.collection.IsEmptyCollection.empty; +import static org.hamcrest.core.IsNot.not; import static org.junit.Assert.*; import java.util.*; @@ -31,6 +31,7 @@ import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.*; import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.TestEnv; +import ghidra.util.classfinder.ClassSearcher; import resources.Icons; /** @@ -372,9 +373,9 @@ public class ManagePlugins2Test extends AbstractGhidraHeadedIntegrationTest { private void loadPlugin(String name) { try { - Class clazz = Class.forName(name); - Plugin plugin = - (Plugin) clazz.getDeclaredConstructor(PluginTool.class).newInstance(tool); + Class clazz = + ClassSearcher.forNameSafe(name, Plugin.class, getClass().getClassLoader()); + Plugin plugin = clazz.getDeclaredConstructor(PluginTool.class).newInstance(tool); loadedPlugins.add(plugin); } catch (Exception e) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/test/TestKeyEventDispatcher.java b/Ghidra/Framework/Docking/src/main/java/docking/test/TestKeyEventDispatcher.java index 4c3d4996a1..baeb0fbdf4 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/test/TestKeyEventDispatcher.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/test/TestKeyEventDispatcher.java @@ -22,7 +22,6 @@ import javax.swing.SwingUtilities; import docking.FocusOwnerProvider; import generic.test.TestUtils; -import ghidra.util.Msg; /** * A class that helps to delegate key events to the system override key event dispatcher. This @@ -64,30 +63,22 @@ public class TestKeyEventDispatcher { } private static KeyEventDispatcher getOverriddenKeyEventDispatcher() { - // Note: our custom key event dispatcher has package access, so we cannot refer to // it directly - try { - Class clazz = Class.forName("docking.KeyBindingOverrideKeyEventDispatcher"); - Object customDispatcher = TestUtils.getInstanceField("instance", clazz); - if (customDispatcher == null) { - return null; // not installed - } - - // - // Dependency Inject our own focus provider so that we can force the event - // dispatcher to deliver events to our component - // - TestUtils.invokeInstanceMethod("setFocusOwnerProvider", customDispatcher, - FocusOwnerProvider.class, focusProvider); - - return (KeyEventDispatcher) customDispatcher; - } - catch (ClassNotFoundException e) { - Msg.error(TestKeyEventDispatcher.class, "Unable to find the system KeyEventDispatcher", - e); - return null; + Class clazz = docking.KeyBindingOverrideKeyEventDispatcher.class; + Object customDispatcher = TestUtils.getInstanceField("instance", clazz); + if (customDispatcher == null) { + return null; // not installed } + + // + // Dependency Inject our own focus provider so that we can force the event + // dispatcher to deliver events to our component + // + TestUtils.invokeInstanceMethod("setFocusOwnerProvider", customDispatcher, + FocusOwnerProvider.class, focusProvider); + + return (KeyEventDispatcher) customDispatcher; } private static class TestFocusOwnerProvider implements FocusOwnerProvider { diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java index efcb48dd9d..5e750d609d 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java @@ -34,6 +34,7 @@ import ghidra.program.model.lang.*; import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; import ghidra.util.Msg; +import ghidra.util.classfinder.ClassSearcher; /** * A p-code thread which incorporates per-architecture state modifiers @@ -232,16 +233,11 @@ public class ModifiedPcodeThread extends DefaultPcodeThread { return null; } try { - Class c = Class.forName(classname); - if (!EmulateInstructionStateModifier.class.isAssignableFrom(c)) { - Msg.error(this, - "Language " + language.getLanguageID() + " does not specify a valid " + - GhidraLanguagePropertyKeys.EMULATE_INSTRUCTION_STATE_MODIFIER_CLASS); - throw new RuntimeException(classname + " does not implement interface " + - EmulateInstructionStateModifier.class.getName()); - } - Constructor constructor = c.getConstructor(Emulate.class); - return (EmulateInstructionStateModifier) constructor.newInstance(emulate); + Class c = ClassSearcher.forNameSafe( + classname, EmulateInstructionStateModifier.class, getClass().getClassLoader()); + Constructor constructor = + c.getConstructor(Emulate.class); + return constructor.newInstance(emulate); } catch (Exception e) { Msg.error(this, "Language " + language.getLanguageID() + " does not specify a valid " + diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emulate/Emulate.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emulate/Emulate.java index a8c28bd66e..c0ef5fca99 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emulate/Emulate.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emulate/Emulate.java @@ -31,6 +31,7 @@ import ghidra.program.model.listing.Instruction; import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; import ghidra.util.Msg; +import ghidra.util.classfinder.ClassSearcher; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -113,18 +114,10 @@ public class Emulate { return; } try { - Class c = Class.forName(classname); - if (!EmulateInstructionStateModifier.class.isAssignableFrom(c)) { - Msg.error(this, - "Language " + language.getLanguageID() + " does not specify a valid " + - GhidraLanguagePropertyKeys.EMULATE_INSTRUCTION_STATE_MODIFIER_CLASS); - throw new RuntimeException(classname + " does not implement interface " + - EmulateInstructionStateModifier.class.getName()); - } - Class instructionStateModifierClass = - (Class) c; + Class c = ClassSearcher.forNameSafe( + classname, EmulateInstructionStateModifier.class, getClass().getClassLoader()); Constructor constructor = - instructionStateModifierClass.getConstructor(Emulate.class); + c.getConstructor(Emulate.class); instructionStateModifier = constructor.newInstance(this); } catch (Exception e) { diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/Application.java b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/Application.java index 4c753f4dc5..ad170f33b1 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/Application.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/Application.java @@ -252,7 +252,7 @@ public class Application { private ResourceFile getModuleForClass(String className) { try { - Class callersClass = Class.forName(className); + Class callersClass = Class.forName(className, false, getClass().getClassLoader()); return getModuleForClass(callersClass); } catch (ClassNotFoundException e) { diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/GProperties.java b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/GProperties.java index 8c8debe80a..529f64baab 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/GProperties.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/GProperties.java @@ -33,6 +33,7 @@ import com.google.gson.*; import ghidra.util.Msg; import ghidra.util.NumericUtilities; +import ghidra.util.classfinder.ClassSearcher; import ghidra.util.exception.AssertException; import ghidra.util.xml.XmlUtilities; import utilities.util.FileUtilities; @@ -433,17 +434,16 @@ public class GProperties { } Enum getEnumValue(String enumClassName, String value) { - - ClassLoader loader = getClass().getClassLoader(); try { - Class clazz = Class.forName(enumClassName, false, loader); - if (!Enum.class.isAssignableFrom(clazz)) { - Msg.error(this, "Class is not an Enum: " + clazz); - return null; - } + // It would be better to restrict this to some interface we define. Otherwise, we ought + // to examine the static initializers of every enum in our classpath. Luckily, the + // constructor invocations are all defined by the enum values, and so cannot be randomly + // invoked. Famous last words: I doubt there's anything of concern in any enum's static + // initializer.... + Class enumClass = + ClassSearcher.forNameSafe(enumClassName, Enum.class, getClass().getClassLoader()); - // Note: calling valueOf() will trigger class initialization - Method m = clazz.getMethod("valueOf", new Class[] { String.class }); + Method m = enumClass.getMethod("valueOf", new Class[] { String.class }); if (m != null) { return (Enum) m.invoke(null, new Object[] { value }); } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/TestSuiteUtilities.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/TestSuiteUtilities.java index 99bc0bce20..4e9af75d1c 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/TestSuiteUtilities.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/TestSuiteUtilities.java @@ -4,9 +4,9 @@ * 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. @@ -21,7 +21,6 @@ import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; -import ghidra.util.exception.AssertException; import junit.framework.TestSuite; import utilities.util.FileUtilities; @@ -34,11 +33,7 @@ public class TestSuiteUtilities { private static Class TEST_CASE_CLASS = createTestClass(); private static Class createTestClass() { - try { - return Class.forName("junit.framework.TestCase"); - } catch (ClassNotFoundException e) { - throw new AssertException(); - } + return junit.framework.TestCase.class; } /** diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassSearcher.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassSearcher.java index 23d25f7554..9cf7bb677c 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassSearcher.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassSearcher.java @@ -274,6 +274,42 @@ public class ClassSearcher { } + /** + * Get the named class in a safe(r) manner. + *

+ * This addresses the concern that many tools and database entries can refer to custom types, + * e.g., options and properties, that may permit an attacker to construct instances of arbitrary + * class, those constructors perhaps implementing gadgets that could be used in an exploit + * chain. + *

+ * The pattern to "mitigate" this is to ensure the found class implements a given interface or + * extends from a given class, before invoking a constructor. Ideally, this check can + * be applied before class initialization. While much narrower, static initializers may + * also provide gadgets. This method implements the pattern which avoids class initialization + * until the type has been checked. + *

+ * WARNING: Do not pass {@link Object}, a standard JDK interface, or any interface from a + * dependency like apache-commons. The purpose of the super type is to narrow the set of + * acceptable classes to those we control or that a user has intentionally installed/loaded as a + * Ghidra extension. Thus, the static initializers and constructors of all subclasses should be + * reviewed carefully. + * + * @param the super type + * @param name the class binary name, as in {@link Class#forName(String, boolean, ClassLoader)}. + * @param sup the super type + * @param loader the class loader, or {@code null} to use the bootstrap loader. + * @return the found class as a subclass of the given type + * @throws ClassNotFoundException if the class cannot be found + * @throws ClassCastException if the found class is not a sub type of the given type + */ + public static Class forNameSafe(String name, Class sup, ClassLoader loader) + throws ClassNotFoundException { + Class cls = Class.forName(name, false, loader); + Class sub = cls.asSubclass(sup); + Class.forName(name, true, loader); // Initialize it this time + return sub; + } + /** * Add a change listener that will be notified when the classpath * is searched for new classes. @@ -631,11 +667,11 @@ public class ClassSearcher { /** * If the given class name matches the known extension name patterns, then this method will try - * to load that class using the provided path. Extensions may be loaded using their own - * class loader, depending on the system property + * to load that class using the provided path. Extensions may be loaded using their own + * class loader, depending on the system property * {@link GhidraClassLoader#ENABLE_RESTRICTED_EXTENSIONS_PROPERTY}. *

- * Examples: + * Examples: *

 	 * /foo/bar/baz/file.jar fully.qualified.ClassName
 	 * /foo/bar/baz/bin fully.qualified.ClassName
diff --git a/Ghidra/Framework/Generic/src/test/java/ghidra/util/classfinder/ClassSearcherTest.java b/Ghidra/Framework/Generic/src/test/java/ghidra/util/classfinder/ClassSearcherTest.java
new file mode 100644
index 0000000000..e3187136e1
--- /dev/null
+++ b/Ghidra/Framework/Generic/src/test/java/ghidra/util/classfinder/ClassSearcherTest.java
@@ -0,0 +1,80 @@
+/* ###
+ * 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.classfinder;
+
+import static org.junit.Assert.*;
+
+import java.lang.reflect.Constructor;
+
+import org.junit.Test;
+
+public class ClassSearcherTest {
+	public enum Canary {
+		ALIVE, DEAD;
+	}
+
+	public static Canary canary;
+
+	public interface TestSuper {
+	}
+
+	public interface WrongSuper {
+	}
+
+	public static class ClassWithStaticInitializerSideEffects implements TestSuper {
+		static {
+			canary = Canary.DEAD;
+		}
+	}
+
+	/**
+	 * Test that our safe(r) version of {@link Class#forName(String)} does not invoke any static
+	 * initializer.
+	 * 
+	 * @throws Exception because
+	 */
+	@Test
+	public void testForNameSafeNoClinit() throws Exception {
+		canary = Canary.ALIVE;
+
+		Class found = ClassSearcher.forNameSafe(
+			"ghidra.util.classfinder.ClassSearcherTest$ClassWithStaticInitializerSideEffects",
+			TestSuper.class, getClass().getClassLoader());
+		assertNotNull(found);
+		assertEquals(Canary.ALIVE, canary);
+		Constructor constructor = found.getConstructor();
+		assertEquals(Canary.ALIVE, canary);
+		TestSuper instance = constructor.newInstance();
+		assertNotNull(instance);
+		assertEquals(Canary.DEAD, canary);
+	}
+
+	@Test
+	public void testForNameSafeNoClinitErr() throws Exception {
+		canary = Canary.ALIVE;
+
+		try {
+			ClassSearcher.forNameSafe(
+				"ghidra.util.classfinder.ClassSearcherTest$ClassWithStaticInitializerSideEffects",
+				WrongSuper.class, getClass().getClassLoader());
+			fail("Should not have permitted the class");
+		}
+		catch (ClassCastException e) {
+			// pass
+		}
+		assertEquals(Canary.ALIVE, canary);
+	}
+}
diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/ThemePreferences.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/ThemePreferences.java
index 75edc6f2ec..35bb3c6a8c 100644
--- a/Ghidra/Framework/Gui/src/main/java/generic/theme/ThemePreferences.java
+++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/ThemePreferences.java
@@ -20,6 +20,7 @@ import java.io.IOException;
 
 import ghidra.framework.preferences.Preferences;
 import ghidra.util.Msg;
+import ghidra.util.classfinder.ClassSearcher;
 
 /**
  * Reads and writes current theme info to preferences
@@ -47,15 +48,9 @@ public class ThemePreferences {
 		else if (themeId.startsWith(DiscoverableGTheme.CLASS_PREFIX)) {
 			String className = themeId.substring(DiscoverableGTheme.CLASS_PREFIX.length());
 			try {
-				ClassLoader loader = getClass().getClassLoader();
-				Class clazz = Class.forName(className, false, loader);
-				if (!GTheme.class.isAssignableFrom(clazz)) {
-					Msg.showError(GTheme.class, null, "Can't Load Previous Theme",
-						"Theme class name does not point to a GTheme instance: " + className);
-					return ThemeManager.getDefaultTheme();
-				}
-
-				return (GTheme) clazz.getDeclaredConstructor().newInstance();
+				Class clazz =
+					ClassSearcher.forNameSafe(className, GTheme.class, getClass().getClassLoader());
+				return clazz.getDeclaredConstructor().newInstance();
 			}
 			catch (Exception e) {
 				Msg.showError(GTheme.class, null, "Can't Load Previous Theme",
diff --git a/Ghidra/Framework/Gui/src/main/java/ghidra/framework/options/FileOptions.java b/Ghidra/Framework/Gui/src/main/java/ghidra/framework/options/FileOptions.java
index bf681a3589..829115f528 100644
--- a/Ghidra/Framework/Gui/src/main/java/ghidra/framework/options/FileOptions.java
+++ b/Ghidra/Framework/Gui/src/main/java/ghidra/framework/options/FileOptions.java
@@ -4,9 +4,9 @@
  * 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.
@@ -25,6 +25,7 @@ import org.apache.commons.io.FilenameUtils;
 
 import ghidra.util.HelpLocation;
 import ghidra.util.Msg;
+import ghidra.util.classfinder.ClassSearcher;
 
 public class FileOptions extends AbstractOptions {
 
@@ -53,8 +54,9 @@ public class FileOptions extends AbstractOptions {
 	public CustomOption readCustomOption(GProperties properties) {
 		String customOptionClassName = properties.getString("CUSTOM_OPTION_CLASS", null);
 		try {
-			Class c = Class.forName(customOptionClassName);
-			CustomOption customOption = (CustomOption) c.getDeclaredConstructor().newInstance();
+			Class c = ClassSearcher.forNameSafe(customOptionClassName,
+				CustomOption.class, getClass().getClassLoader());
+			CustomOption customOption = c.getDeclaredConstructor().newInstance();
 			customOption.readState(properties);
 			return customOption;
 		}
diff --git a/Ghidra/Framework/Gui/src/main/java/ghidra/framework/options/OptionType.java b/Ghidra/Framework/Gui/src/main/java/ghidra/framework/options/OptionType.java
index e358029ee4..d9a2d7f16a 100644
--- a/Ghidra/Framework/Gui/src/main/java/ghidra/framework/options/OptionType.java
+++ b/Ghidra/Framework/Gui/src/main/java/ghidra/framework/options/OptionType.java
@@ -27,6 +27,7 @@ import org.jdom2.input.SAXBuilder;
 import org.jdom2.output.XMLOutputter;
 
 import ghidra.util.Msg;
+import ghidra.util.classfinder.ClassSearcher;
 import ghidra.util.xml.GenericXMLOutputter;
 import ghidra.util.xml.XmlUtilities;
 
@@ -231,8 +232,9 @@ public enum OptionType {
 			String customOptionClassName =
 				saveState.getString(CustomOption.CUSTOM_OPTION_CLASS_NAME_KEY, null);
 			try {
-				Class c = Class.forName(customOptionClassName);
-				CustomOption option = (CustomOption) c.getConstructor().newInstance();
+				Class c = ClassSearcher.forNameSafe(customOptionClassName,
+					CustomOption.class, OptionType.class.getClassLoader());
+				CustomOption option = c.getConstructor().newInstance();
 				option.readState(saveState);
 				return option;
 			}
diff --git a/Ghidra/Framework/Gui/src/main/java/ghidra/framework/options/ToolOptions.java b/Ghidra/Framework/Gui/src/main/java/ghidra/framework/options/ToolOptions.java
index c08fb8c46f..4f8582e952 100644
--- a/Ghidra/Framework/Gui/src/main/java/ghidra/framework/options/ToolOptions.java
+++ b/Ghidra/Framework/Gui/src/main/java/ghidra/framework/options/ToolOptions.java
@@ -30,6 +30,7 @@ import org.jdom2.Element;
 
 import ghidra.util.*;
 import ghidra.util.bean.opteditor.OptionsVetoException;
+import ghidra.util.classfinder.ClassSearcher;
 import ghidra.util.exception.AssertException;
 
 /**
@@ -151,9 +152,11 @@ public class ToolOptions extends AbstractOptions {
 				continue; // shouldn't happen
 			}
 
-			Class c = Class.forName(element.getAttributeValue(CLASS_ATTRIBUTE));
-			Constructor constructor = c.getDeclaredConstructor();
-			WrappedOption wo = (WrappedOption) constructor.newInstance();
+			Class c =
+				ClassSearcher.forNameSafe(element.getAttributeValue(CLASS_ATTRIBUTE),
+					WrappedOption.class, getClass().getClassLoader());
+			Constructor constructor = c.getDeclaredConstructor();
+			WrappedOption wo = constructor.newInstance();
 			wo.readState(new SaveState(element));
 			if (wo instanceof WrappedCustomOption wrappedCustom && !wrappedCustom.isValid()) {
 				continue;
diff --git a/Ghidra/Framework/Gui/src/main/java/ghidra/framework/options/WrappedCustomOption.java b/Ghidra/Framework/Gui/src/main/java/ghidra/framework/options/WrappedCustomOption.java
index f6f155ca12..3f35cafe5c 100644
--- a/Ghidra/Framework/Gui/src/main/java/ghidra/framework/options/WrappedCustomOption.java
+++ b/Ghidra/Framework/Gui/src/main/java/ghidra/framework/options/WrappedCustomOption.java
@@ -16,6 +16,7 @@
 package ghidra.framework.options;
 
 import ghidra.util.Msg;
+import ghidra.util.classfinder.ClassSearcher;
 
 public class WrappedCustomOption implements WrappedOption {
 
@@ -33,28 +34,21 @@ public class WrappedCustomOption implements WrappedOption {
 
 	@Override
 	public void readState(SaveState saveState) {
-		String className = saveState.getString("CUSTOM OPTION CLASS", null);
+		String customOptionClassName = saveState.getString("CUSTOM OPTION CLASS", null);
 		valid = false;
 		try {
-
-			ClassLoader loader = getClass().getClassLoader();
-			Class clazz = Class.forName(className, false, loader);
-			if (!CustomOption.class.isAssignableFrom(clazz)) {
-				Msg.error(this, "Can't create custom option instance; not a CustomOpiton: " +
-					className);
-				return;
-			}
-
-			value = (CustomOption) clazz.getConstructor().newInstance();
+			Class c = ClassSearcher.forNameSafe(customOptionClassName,
+				CustomOption.class, getClass().getClassLoader());
+			value = c.getConstructor().newInstance();
 			value.readState(saveState);
 			valid = true;
 		}
 		catch (ClassNotFoundException e) {
 			Msg.info(this,
-				"Custom option class '%s' does not exist".formatted(className));
+				"Custom option class '%s' does not exist".formatted(customOptionClassName));
 		}
 		catch (Exception e) {
-			Msg.error(this, "Can't create custom option instance for: " + className, e);
+			Msg.error(this, "Can't create custom option instance for: " + customOptionClassName, e);
 		}
 	}
 
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/ToolUtils.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/ToolUtils.java
index cad16dcc4b..568bad7d20 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/ToolUtils.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/ToolUtils.java
@@ -25,10 +25,13 @@ import org.jdom2.*;
 import org.jdom2.input.SAXBuilder;
 import org.jdom2.output.XMLOutputter;
 
+import com.sun.source.util.Plugin;
+
 import ghidra.framework.model.ProjectManager;
 import ghidra.framework.model.ToolTemplate;
 import ghidra.framework.project.tool.GhidraToolTemplate;
 import ghidra.util.*;
+import ghidra.util.classfinder.ClassSearcher;
 import ghidra.util.exception.AssertException;
 import ghidra.util.xml.GenericXMLOutputter;
 import ghidra.util.xml.XmlUtilities;
@@ -175,7 +178,7 @@ public class ToolUtils {
 			// check to see if we can still find the plugin class (it may have
 			// been removed)
 			try {
-				Class.forName(value);
+				ClassSearcher.forNameSafe(value, Plugin.class, ToolUtils.class.getClassLoader());
 			}
 			catch (Throwable t) {
 				// oh well, leave it out
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/PluginUtils.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/PluginUtils.java
index 92a690fa93..dd8ce1aa99 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/PluginUtils.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/PluginUtils.java
@@ -85,11 +85,8 @@ public class PluginUtils {
 				}
 			}
 
-			Class tmpClass = Class.forName(pluginClassName);
-			if (!Plugin.class.isAssignableFrom(tmpClass)) {
-				throw new PluginException(
-					"Class " + pluginClassName + " is not derived from Plugin");
-			}
+			Class tmpClass = ClassSearcher.forNameSafe(pluginClassName,
+				Plugin.class, PluginUtils.class.getClassLoader());
 			return tmpClass.asSubclass(Plugin.class);
 		}
 		catch (ClassNotFoundException e) {
@@ -134,8 +131,8 @@ public class PluginUtils {
 		}
 		if (defaultProviderClassName != null) {
 			try {
-				Class tmpClass = Class.forName(defaultProviderClassName);
-				return tmpClass.asSubclass(Plugin.class);
+				return ClassSearcher.forNameSafe(defaultProviderClassName, Plugin.class,
+					PluginUtils.class.getClassLoader());
 			}
 			catch (ClassCastException cce) {
 				Msg.error(PluginUtils.class,
@@ -145,7 +142,6 @@ public class PluginUtils {
 			catch (ClassNotFoundException e) {
 				throw new AssertException(
 					"default provider class for " + serviceClass.getName() + " not found!");
-
 			}
 		}
 		return null;
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraToolTemplate.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraToolTemplate.java
index b995f92be7..70769da083 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraToolTemplate.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraToolTemplate.java
@@ -22,11 +22,11 @@ import javax.swing.ImageIcon;
 import org.jdom2.Element;
 
 import docking.util.image.ToolIconURL;
-import ghidra.framework.model.Project;
-import ghidra.framework.model.ToolTemplate;
+import ghidra.framework.model.*;
 import ghidra.framework.plugintool.PluginTool;
 import ghidra.util.Msg;
 import ghidra.util.NumericUtilities;
+import ghidra.util.classfinder.ClassSearcher;
 
 /**
  * Implementation for a tool template that has the class names of the
@@ -135,8 +135,8 @@ public class GhidraToolTemplate implements ToolTemplate {
 			Element elem = (Element) list.get(i);
 			String className = elem.getAttribute(CLASS_NAME_XML_NAME).getValue();
 			try {
-				// no need to perform static initialization; clients only check isAssignableFrom()
-				dtList.add(Class.forName(className, false, loader));
+				dtList.add(ClassSearcher
+						.forNameSafe(className, DomainObject.class, loader));
 			}
 			catch (ClassNotFoundException e) {
 				Msg.warn(this, "Tool supported content class not found: " + className);
@@ -182,8 +182,7 @@ public class GhidraToolTemplate implements ToolTemplate {
 			iconElem.setText(NumericUtilities.convertBytesToString(iconURL.getIconBytes()));
 		}
 		root.addContent(iconElem);
-
-		root.addContent((toolElement.clone()));
+		root.addContent(toolElement.clone());
 
 		return root;
 	}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguage.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguage.java
index a696a6394a..3576bad85d 100644
--- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguage.java
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguage.java
@@ -45,6 +45,7 @@ import ghidra.program.model.pcode.*;
 import ghidra.program.model.util.ProcessorSymbolType;
 import ghidra.sleigh.grammar.SourceFileIndexer;
 import ghidra.util.*;
+import ghidra.util.classfinder.ClassSearcher;
 import ghidra.util.exception.InvalidInputException;
 import ghidra.util.exception.TimeoutException;
 import ghidra.util.task.PreserveStateWrappingTaskMonitor;
@@ -1492,18 +1493,10 @@ public class SleighLanguage implements Language {
 			return;
 		}
 		try {
-			Class helperClass = Class.forName(className);
-			if (!ParallelInstructionLanguageHelper.class.isAssignableFrom(helperClass)) {
-				Msg.error(this,
-					"Invalid Class specified for " +
-						GhidraLanguagePropertyKeys.PARALLEL_INSTRUCTION_HELPER_CLASS + " (" +
-						helperClass.getName() + "): " + description.getSpecFile());
-			}
-			else {
-				parallelHelper =
-					(ParallelInstructionLanguageHelper) helperClass.getDeclaredConstructor()
-							.newInstance();
-			}
+			Class helperClass =
+				ClassSearcher.forNameSafe(className, ParallelInstructionLanguageHelper.class,
+					getClass().getClassLoader());
+			parallelHelper = helperClass.getDeclaredConstructor().newInstance();
 		}
 		catch (Exception e) {
 			throw new SleighException("Failed to instantiate " +
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeManagerDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeManagerDB.java
index ccbacf5583..371f9c7acd 100644
--- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeManagerDB.java
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeManagerDB.java
@@ -51,6 +51,7 @@ import ghidra.program.model.lang.*;
 import ghidra.program.model.listing.Function;
 import ghidra.util.*;
 import ghidra.util.Lock.Closeable;
+import ghidra.util.classfinder.ClassSearcher;
 import ghidra.util.classfinder.ClassTranslator;
 import ghidra.util.exception.*;
 import ghidra.util.task.TaskMonitor;
@@ -2491,7 +2492,8 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
 
 	/**
 	 * Queue a datatype to deleted in response to another datatype being deleted.
-	 * @param id datatype ID to be removed
+	 * @param dt datatype to be deleted
+	 * @param id datatype ID to be deleted
 	 */
 	protected void addDataTypeToDelete(DataType dt, long id) {
 		if (dt instanceof DataTypeDB dbDt) {
@@ -2807,9 +2809,10 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
 			String classPath = record.getString(BuiltinDBAdapter.BUILT_IN_CLASSNAME_COL);
 			String name = record.getString(BuiltinDBAdapter.BUILT_IN_NAME_COL);
 			try {
-				Class clazz;
+				Class clazz;
 				try {
-					clazz = Class.forName(classPath);
+					clazz = ClassSearcher.forNameSafe(classPath, BuiltInDataType.class,
+						getClass().getClassLoader());
 				}
 				catch (ClassNotFoundException | NoClassDefFoundError e) {
 					// Check the classNameMap.
@@ -2818,15 +2821,15 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
 						throw e;
 					}
 					try {
-						clazz = Class.forName(newClassPath);
+						clazz = ClassSearcher.forNameSafe(newClassPath, BuiltInDataType.class,
+							getClass().getClassLoader());
 					}
 					catch (ClassNotFoundException e1) {
 						throw e1;
 					}
 				}
 
-				BuiltInDataType bdt =
-					(BuiltInDataType) clazz.getDeclaredConstructor().newInstance();
+				BuiltInDataType bdt = clazz.getDeclaredConstructor().newInstance();
 				bdt.setName(name);
 				bdt.setCategoryPath(catPath);
 
@@ -4541,7 +4544,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
 	 * Check for cached equivalence of a type contained within this datatype manager
 	 * against another datatype. Every call to this method when {@code null} is
 	 * returned must be following by an invocation of
-	 * {@link #putCachedEquivalence(DataTypeDB, DataType, boolean)} once an
+	 * {@link #putCachedEquivalence(DataTypeDB, DataType, Boolean)} once an
 	 * equivalence determination has been made. The number of outstanding calls to
 	 * this method will be tracked. When the outstanding call count returns to zero
 	 * the cache will be cleared. 
@@ -4554,7 +4557,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager { * @return true, false or {@code null} if unknown. A {@code null} value mandates * that the caller make a determination and put the result into the * cache when known (see - * {@link #putCachedEquivalence(DataTypeDB, DataType, boolean)}. + * {@link #putCachedEquivalence(DataTypeDB, DataType, Boolean)}. */ Boolean getCachedEquivalence(DataTypeDB dataTypeDB, DataType dataType) { EquivalenceCache cache = equivalenceCache.get(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/properties/ObjectPropertyMapDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/properties/ObjectPropertyMapDB.java index 782a6185a9..f14fb987e4 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/properties/ObjectPropertyMapDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/properties/ObjectPropertyMapDB.java @@ -28,6 +28,7 @@ import ghidra.program.util.ChangeManager; import ghidra.util.Lock.Closeable; import ghidra.util.Msg; import ghidra.util.Saveable; +import ghidra.util.classfinder.ClassSearcher; import ghidra.util.classfinder.ClassTranslator; import ghidra.util.exception.*; import ghidra.util.task.TaskMonitor; @@ -133,9 +134,10 @@ public class ObjectPropertyMapDB extends PropertyMapDB */ @SuppressWarnings("unchecked") public static Class getSaveableClassForName(String classPath) { - Class c = null; + Class c = null; try { - c = Class.forName(classPath); + c = ClassSearcher.forNameSafe(classPath, Saveable.class, + ObjectPropertyMapDB.class.getClassLoader()); } catch (ClassNotFoundException e) { // Check the classNameMap. @@ -143,10 +145,10 @@ public class ObjectPropertyMapDB extends PropertyMapDB if (newClassPath != null) { classPath = newClassPath; try { - c = Class.forName(newClassPath); + c = ClassSearcher.forNameSafe(newClassPath, Saveable.class, + ObjectPropertyMapDB.class.getClassLoader()); } catch (ClassNotFoundException e1) { - // Since we can't get the class, at least handle it generically. } } @@ -159,7 +161,7 @@ public class ObjectPropertyMapDB extends PropertyMapDB "Object property class does not implement Saveable interface: " + classPath); } // If unable to get valid Saveable class use generic implementation - return (c != null) ? (Class) c : GenericSaveable.class; + return (c != null) ? c : GenericSaveable.class; } /** diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/Disassembler.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/Disassembler.java index 876bf6978f..479cc8150a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/Disassembler.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/Disassembler.java @@ -37,6 +37,7 @@ import ghidra.program.util.AbstractProgramContext; import ghidra.program.util.ProgramContextImpl; import ghidra.util.Msg; import ghidra.util.SystemUtilities; +import ghidra.util.classfinder.ClassSearcher; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -1864,29 +1865,28 @@ public class Disassembler implements DisassemblerConflictHandler { } - @SuppressWarnings("unchecked") private static Class getLanguageSpecificDisassembler(Language parser) { String className = parser.getProperty(GhidraLanguagePropertyKeys.CUSTOM_DISASSEMBLER_CLASS); if (className == null) { return null; } try { - Class disassemblerClass = Class.forName(className); - if (!Disassembler.class.isAssignableFrom(disassemblerClass)) { - Msg.error(Disassembler.class, - "Invalid Class specified for " + - GhidraLanguagePropertyKeys.CUSTOM_DISASSEMBLER_CLASS + " (" + - disassemblerClass.getName() + "): " + - parser.getLanguageDescription().getLanguageID()); - return null; - } - return (Class) disassemblerClass; + Class disassemblerClass = ClassSearcher.forNameSafe(className, + Disassembler.class, Disassembler.class.getClassLoader()); + return disassemblerClass; } catch (ClassNotFoundException e) { throw new RuntimeException("Invalid Class specified for " + GhidraLanguagePropertyKeys.CUSTOM_DISASSEMBLER_CLASS + " (" + className + "): " + parser.getLanguageDescription().getLanguageID(), e); } + catch (ClassCastException e) { + Msg.error(Disassembler.class, + "Invalid Class specified for " + + GhidraLanguagePropertyKeys.CUSTOM_DISASSEMBLER_CLASS + " (" + className + + "): " + + parser.getLanguageDescription().getLanguageID()); + return null; + } } - } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/BasicCompilerSpec.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/BasicCompilerSpec.java index 9b36c235c5..ab14bef53a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/BasicCompilerSpec.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/BasicCompilerSpec.java @@ -38,6 +38,7 @@ import ghidra.program.model.listing.*; import ghidra.program.model.pcode.*; import ghidra.util.Msg; import ghidra.util.SystemUtilities; +import ghidra.util.classfinder.ClassSearcher; import ghidra.util.exception.DuplicateNameException; import ghidra.util.xml.SpecXmlUtils; import ghidra.xml.*; @@ -249,7 +250,6 @@ public class BasicCompilerSpec implements CompilerSpec { restoreXml(parser); } - @SuppressWarnings("unchecked") private void buildInjectLibrary() { String classname = language.getProperty(GhidraLanguagePropertyKeys.PCODE_INJECT_LIBRARY_CLASS); @@ -258,18 +258,10 @@ public class BasicCompilerSpec implements CompilerSpec { } else { try { - Class c = Class.forName(classname); - if (!PcodeInjectLibrary.class.isAssignableFrom(c)) { - Msg.error(this, - "Language " + language.getLanguageID() + " does not specify a valid " + - GhidraLanguagePropertyKeys.PCODE_INJECT_LIBRARY_CLASS); - throw new RuntimeException(classname + " does not implement interface " + - PcodeInjectLibrary.class.getName()); - } - Class injectLibraryClass = - (Class) c; + Class c = ClassSearcher.forNameSafe(classname, + PcodeInjectLibrary.class, getClass().getClassLoader()); Constructor constructor = - injectLibraryClass.getConstructor(SleighLanguage.class); + c.getConstructor(SleighLanguage.class); pcodeInject = constructor.newInstance(language); } catch (Exception e) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ProgramLocation.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ProgramLocation.java index 5d22182486..dc42ebde3b 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ProgramLocation.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ProgramLocation.java @@ -22,6 +22,7 @@ import ghidra.framework.options.SaveState; import ghidra.program.model.address.Address; import ghidra.program.model.listing.*; import ghidra.util.Msg; +import ghidra.util.classfinder.ClassSearcher; /** * ProgramLocation provides information about a location in a program in the most @@ -278,10 +279,10 @@ public class ProgramLocation implements Cloneable, Comparable { return null; } - ClassLoader loader = ProgramLocation.class.getClassLoader(); try { + Class locationClass = ClassSearcher.forNameSafe(className, + ProgramLocation.class, ProgramLocation.class.getClassLoader()); - Class locationClass = Class.forName(className, false, loader); if (locationClass.isInterface()) { // This check is needed due to a refactoring that has changed a class into an // interface. The class name may have been saved into the tool. Upon restoring we @@ -290,13 +291,7 @@ public class ProgramLocation implements Cloneable, Comparable { return null; } - if (!ProgramLocation.class.isAssignableFrom(locationClass)) { - Msg.error(ProgramLocation.class, - "Class is not a ProgramLocation: " + locationClass); - return null; - } - - ProgramLocation loc = (ProgramLocation) locationClass.getConstructor().newInstance(); + ProgramLocation loc = locationClass.getConstructor().newInstance(); loc.restoreState(program, saveState); if (loc.getAddress() != null) { return loc; @@ -305,6 +300,8 @@ public class ProgramLocation implements Cloneable, Comparable { } catch (ClassNotFoundException e) { // this can happen for locations created by plugins that are no longer installed + Msg.info(ProgramLocation.class, + "Unable to restore program location, class not found: " + className); } catch (InstantiationException | IllegalAccessException | NoSuchMethodException e) { Msg.showError(ProgramLocation.class, null, "Programming Error", diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/SimpleLanguageTranslator.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/SimpleLanguageTranslator.java index 3eb1f52598..9f38bc8a6a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/SimpleLanguageTranslator.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/SimpleLanguageTranslator.java @@ -33,6 +33,7 @@ import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.*; import ghidra.program.model.listing.IncompatibleLanguageException; import ghidra.program.model.listing.Program; +import ghidra.util.classfinder.ClassSearcher; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; import ghidra.util.xml.XmlUtilities; @@ -386,18 +387,18 @@ class SimpleLanguageTranslator extends LanguageTranslatorAdapter { } } - @SuppressWarnings("unchecked") private static Class parsePostUpgradeHandlerEntry( Element element) throws SAXException { - String className = element.getAttributeValue("class"); if (className == null) { throw new SAXException(element.getName() + " must specify 'class' attribute"); } try { - Class clazz = Class.forName(className); + Class clazz = + ClassSearcher.forNameSafe(className, LanguagePostUpgradeInstructionHandler.class, + SimpleLanguageTranslator.class.getClassLoader()); getPostUpgradeInstructionHandler((Program) null, clazz); // test construction - return (Class) clazz; + return clazz; } catch (Exception e) { if (e instanceof SAXException) { diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLaunchDialogTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLaunchDialogTest.java index f44bbb2c4e..2cf7449bc9 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLaunchDialogTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLaunchDialogTest.java @@ -24,7 +24,8 @@ import java.nio.file.Paths; import java.util.Map; import java.util.concurrent.CompletableFuture; -import org.junit.*; +import org.junit.Before; +import org.junit.Test; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest; import ghidra.app.plugin.core.debug.gui.InvocationDialogHelper; @@ -32,7 +33,6 @@ import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser import ghidra.async.SwingExecutorService; import ghidra.debug.api.ValStr; import ghidra.debug.api.tracermi.LaunchParameter; -import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.AutoConfigState.PathIsDir; import ghidra.framework.plugintool.AutoConfigState.PathIsFile; @@ -146,37 +146,6 @@ public class TraceRmiLaunchDialogTest extends AbstractGhidraHeadedDebuggerTest { result.h.invoke(); } - @Test - public void testIntSaveHexValue() throws Throwable { - PromptResult result = prompt(PARAM_INT); - result.h.setArgAsString(PARAM_INT, "0x11"); - result.h.invoke(); - - SaveState state = result.h.saveState(); - assertEquals("0x11", state.getString("some_int,java.math.BigInteger", null)); - } - - @Test - @Ignore - public void testIntLoadHexValue() throws Throwable { - /** - * TODO: This is a bit out of order. However, the dialog cannot load/decode from the state - * until it has the parameters. Worse, to check that user input was valid, the dialog - * verifies that the value it gets back matches the text in the box, because if it doesn't, - * then the editor must have failed to parse/decode the value. Currently, loading the state - * while the dialog box has already populated its values, does not modify the contents of - * any editor, so the text will not match, causing this test to fail. - */ - PromptResult result = prompt(PARAM_INT); - SaveState state = new SaveState(); - state.putString("some_int,java.math.BigInteger", "0x11"); - result.h.loadState(state); - result.h.invoke(); - - Map> args = waitOn(result.args); - assertEquals(Map.of("some_int", intVal(17, "0x11")), args); - } - @Test public void testBoolDefaultValue() throws Throwable { PromptResult result = prompt(PARAM_BOOL); diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/util/headless/MyHeadlessGraphicsEnvironment.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/util/headless/MyHeadlessGraphicsEnvironment.java index 6424cf94ab..60770e9392 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/util/headless/MyHeadlessGraphicsEnvironment.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/util/headless/MyHeadlessGraphicsEnvironment.java @@ -1,13 +1,12 @@ /* ### * 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. @@ -18,8 +17,10 @@ package ghidra.app.util.headless; import java.awt.*; import java.awt.image.BufferedImage; +import java.lang.reflect.InvocationTargetException; import java.util.Locale; +import ghidra.util.classfinder.ClassSearcher; import sun.java2d.HeadlessGraphicsEnvironment; public class MyHeadlessGraphicsEnvironment extends GraphicsEnvironment { @@ -78,17 +79,23 @@ public class MyHeadlessGraphicsEnvironment extends GraphicsEnvironment { private void getRealGraphicsEnvironemnt() { try { - localEnv = (GraphicsEnvironment) Class.forName(preferredGraphicsEnv).newInstance(); + localEnv = ClassSearcher + .forNameSafe(preferredGraphicsEnv, GraphicsEnvironment.class, + getClass().getClassLoader()) + .getConstructor() + .newInstance(); if (isHeadless()) { localEnv = new HeadlessGraphicsEnvironment(localEnv); } - } catch (ClassNotFoundException e) { + } + catch (ClassNotFoundException e) { throw new Error("Could not find class: " + preferredGraphicsEnv); - } catch (InstantiationException e) { + } + catch (InstantiationException | NoSuchMethodException | InvocationTargetException e) { throw new Error("Could not instantiate Graphics Environment: " + preferredGraphicsEnv); - } catch (IllegalAccessException e) { + } + catch (IllegalAccessException e) { throw new Error("Could not access Graphics Environment: " + preferredGraphicsEnv); } } - } diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/util/headless/MyHeadlessToolkit.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/util/headless/MyHeadlessToolkit.java index 98d081f7e9..00eb0295f6 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/util/headless/MyHeadlessToolkit.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/util/headless/MyHeadlessToolkit.java @@ -4,9 +4,9 @@ * 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. @@ -22,10 +22,12 @@ import java.awt.datatransfer.Clipboard; import java.awt.font.TextAttribute; import java.awt.im.InputMethodHighlight; import java.awt.image.*; +import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.util.Map; import java.util.Properties; +import ghidra.util.classfinder.ClassSearcher; import sun.awt.HeadlessToolkit; public class MyHeadlessToolkit extends Toolkit { @@ -163,22 +165,23 @@ public class MyHeadlessToolkit extends Toolkit { private void getRealToolkit() { - Class cls = null; + Class cls = null; try { try { - cls = Class.forName(preferredToolkit); + cls = ClassSearcher.forNameSafe(preferredToolkit, Toolkit.class, + getClass().getClassLoader()); } catch (ClassNotFoundException ee) { throw new AWTError("Toolkit not found: " + preferredToolkit); } if (cls != null) { - localToolKit = (Toolkit) cls.newInstance(); + localToolKit = cls.getConstructor().newInstance(); if (GraphicsEnvironment.isHeadless()) { localToolKit = new HeadlessToolkit(localToolKit); } } } - catch (InstantiationException e) { + catch (InstantiationException | NoSuchMethodException | InvocationTargetException e) { throw new AWTError("Could not instantiate Toolkit: " + preferredToolkit); } catch (IllegalAccessException e) {