From 9235902820ffb118d2b7fa96e9453446897f6e01 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Wed, 28 Sep 2022 11:02:43 -0400 Subject: [PATCH] GP-2618: Re-work script error handling --- .../gui/objects/DebuggerObjectsProvider.java | 3 +- .../script/GhidraScriptComponentProvider.java | 14 ++- .../app/script/GhidraScriptLoadException.java | 58 +++++++++++++ .../app/script/GhidraScriptProvider.java | 49 ++++++++--- .../ghidra/app/script/JavaScriptProvider.java | 86 +++++++++++++------ .../src/main/java/ghidra/test/TestEnv.java | 5 +- .../ghidra/python/PythonScriptProvider.java | 28 ++++-- 7 files changed, 181 insertions(+), 62 deletions(-) create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptLoadException.java diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java index 41319d864f..459bc57e96 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java @@ -20,6 +20,7 @@ import java.awt.Color; import java.awt.event.MouseEvent; import java.io.PrintWriter; import java.lang.invoke.MethodHandles; +import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; @@ -1592,7 +1593,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter try { script = provider.getScriptInstance(sourceFile, writer); } - catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { + catch (GhidraScriptLoadException e) { Msg.error(this, e.getMessage()); return; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java index 73ce3145f3..ebc7b8fb07 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java @@ -19,6 +19,7 @@ import java.awt.BorderLayout; import java.awt.Rectangle; import java.awt.event.*; import java.io.*; +import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; @@ -671,14 +672,9 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { try { return provider.getScriptInstance(scriptFile, console.getStdErr()); } - catch (IllegalAccessException e) { - console.addErrorMessage("", "Unable to access script: " + scriptName); - } - catch (InstantiationException e) { - console.addErrorMessage("", "Unable to instantiate script: " + scriptName); - } - catch (ClassNotFoundException e) { - console.addErrorMessage("", "Unable to locate script class: " + scriptName); + catch (GhidraScriptLoadException e) { + console.addErrorMessage("", "Unable to load script: " + scriptName); + console.addErrorMessage("", " detail: " + e.getMessage()); } // show the error icon @@ -819,7 +815,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { /* Unusual Algorithm - + The tree nodes represent categories, but do not contain nodes for individual scripts. We wish to remove any of the tree nodes that no longer represent script categories. (This can happen when a script is deleted or its category is changed.) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptLoadException.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptLoadException.java new file mode 100644 index 0000000000..34d85448cf --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptLoadException.java @@ -0,0 +1,58 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.script; + +import ghidra.util.exception.UsrException; + +/** + * An exception for when a script provider cannot create a script instance + */ +public class GhidraScriptLoadException extends UsrException { + /** + * Construct an exception with a custom message and cause + * + *

+ * Note that the error message displayed to the user does not automatically include details from + * the cause. The client must provide details from the cause in the message as needed. + * + * @param message the error message including details and possible remedies + * @param cause the exception causing this one + */ + public GhidraScriptLoadException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Construct an exception with a message + * + * @param message the error message including details and possible remedies + */ + public GhidraScriptLoadException(String message) { + super(message); + } + + /** + * Construct an exception with a cause + * + *

+ * This will copy the cause's message into this exception's message. + * + * @param cause the exception causing this one + */ + public GhidraScriptLoadException(Throwable cause) { + super(cause.getMessage(), cause); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java index 8791fbf2bb..a68f214945 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java @@ -23,9 +23,11 @@ import generic.jar.ResourceFile; import ghidra.util.classfinder.ExtensionPoint; /** - * NOTE: ALL GhidraScriptProvider CLASSES MUST END IN "ScriptProvider". If not, - * the ClassSearcher will not find them. - * + * A provider that can compile, interpret, load, etc., Ghidra Scripts from a given language. + * + *

+ * NOTE: ALL GhidraScriptProvider CLASSES MUST END IN "ScriptProvider". If not, the + * ClassSearcher will not find them. */ public abstract class GhidraScriptProvider implements ExtensionPoint, Comparable { @@ -56,6 +58,7 @@ public abstract class GhidraScriptProvider /** * Deletes the script file and unloads the script from the script manager. + * * @param scriptSource the script source file * @return true if the script was completely deleted and cleaned up */ @@ -65,31 +68,36 @@ public abstract class GhidraScriptProvider /** * Returns a description for this type of script. + * * @return a description for this type of script */ public abstract String getDescription(); /** * Returns the file extension for this type of script. + * + *

* For example, ".java" or ".py". + * * @return the file extension for this type of script */ public abstract String getExtension(); /** * Returns a GhidraScript instance for the specified source file. + * * @param sourceFile the source file - * @param writer the print writer to write warning/error messages + * @param writer the print writer to write warning/error messages. If the error prevents + * success, throw an exception instead. The caller will print the error. * @return a GhidraScript instance for the specified source file - * @throws ClassNotFoundException if the script class cannot be found - * @throws InstantiationException if the construction of the script fails for some reason - * @throws IllegalAccessException if the class constructor is not accessible + * @throws GhidraScriptLoadException when the script instance cannot be created */ public abstract GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer) - throws ClassNotFoundException, InstantiationException, IllegalAccessException; + throws GhidraScriptLoadException; /** * Creates a new script using the specified file. + * * @param newScript the new script file * @param category the script category * @throws IOException if an error occurs writing the file @@ -99,7 +107,10 @@ public abstract class GhidraScriptProvider /** * Returns a Pattern that matches block comment openings. + * + *

* If block comments are not supported by this provider, then this returns null. + * * @return the Pattern for block comment openings, null if block comments are not supported */ public Pattern getBlockCommentStart() { @@ -108,7 +119,10 @@ public abstract class GhidraScriptProvider /** * Returns a Pattern that matches block comment closings. + * + *

* If block comments are not supported by this provider, then this returns null. + * * @return the Pattern for block comment closings, null if block comments are not supported */ public Pattern getBlockCommentEnd() { @@ -117,14 +131,20 @@ public abstract class GhidraScriptProvider /** * Returns the comment character. + * + *

* For example, "//" or "#". + * * @return the comment character */ public abstract String getCommentCharacter(); /** - * Writes the script header. + * Writes the script header. + * + *

* Include a place holder for each meta-data item. + * * @param writer the print writer * @param category the default category */ @@ -150,6 +170,7 @@ public abstract class GhidraScriptProvider /** * Writes the script body template. + * * @param writer the print writer */ protected void writeBody(PrintWriter writer) { @@ -159,8 +180,9 @@ public abstract class GhidraScriptProvider /** * Fixup a script name for searching in script directories. * - *

This method is part of a poorly specified behavior that is due for future amendment, - * see {@link GhidraScriptUtil#fixupName(String)}. + *

+ * This method is part of a poorly specified behavior that is due for future amendment, see + * {@link GhidraScriptUtil#fixupName(String)}. * * @param scriptName the name of the script, must end with this provider's extension * @return a (relative) file path to the corresponding script @@ -172,6 +194,7 @@ public abstract class GhidraScriptProvider /** * Return the start of certification header line if this file type is subject to certification. + * * @return start of certification header or null if not supported */ protected String getCertifyHeaderStart() { @@ -179,8 +202,9 @@ public abstract class GhidraScriptProvider } /** - * Return the prefix for each certification header body line if this file is subject to + * Return the prefix for each certification header body line if this file is subject to * certification. + * * @return certification header body prefix or null if not supported */ protected String getCertificationBodyPrefix() { @@ -189,6 +213,7 @@ public abstract class GhidraScriptProvider /** * Return the end of certification header line if this file type is subject to certification. + * * @return end of certification header or null if not supported */ protected String getCertifyHeaderEnd() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java index a958cd6c8b..a8a9c5bc83 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java @@ -16,6 +16,7 @@ package ghidra.app.script; import java.io.*; +import java.lang.reflect.InvocationTargetException; import java.util.Collections; import java.util.regex.Pattern; @@ -26,6 +27,9 @@ import ghidra.app.plugin.core.osgi.*; import ghidra.util.Msg; import ghidra.util.task.TaskMonitor; +/** + * The provider for Ghidra Scripts written in Java + */ public class JavaScriptProvider extends GhidraScriptProvider { private static final Pattern BLOCK_COMMENT_START = Pattern.compile("/\\*"); private static final Pattern BLOCK_COMMENT_END = Pattern.compile("\\*/"); @@ -33,14 +37,16 @@ public class JavaScriptProvider extends GhidraScriptProvider { private final BundleHost bundleHost; /** - * Create a new {@link JavaScriptProvider} associated with the current bundle host used by scripting. + * Create a new {@link JavaScriptProvider} associated with the current bundle host used by + * scripting. */ public JavaScriptProvider() { bundleHost = GhidraScriptUtil.getBundleHost(); } /** - * Get the {@link GhidraSourceBundle} containing the given source file, assuming it already exists. + * Get the {@link GhidraSourceBundle} containing the given source file, assuming it already + * exists. * * @param sourceFile the source file * @return the bundle @@ -80,35 +86,53 @@ public class JavaScriptProvider extends GhidraScriptProvider { @Override public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer) - throws ClassNotFoundException, InstantiationException, IllegalAccessException { + throws GhidraScriptLoadException { try { Class clazz = loadClass(sourceFile, writer); - Object object; - object = clazz.getDeclaredConstructor().newInstance(); - if (object instanceof GhidraScript) { - GhidraScript script = (GhidraScript) object; + if (GhidraScript.class.isAssignableFrom(clazz)) { + GhidraScript script = (GhidraScript) clazz.getDeclaredConstructor().newInstance(); script.setSourceFile(sourceFile); return script; } - String message = "Not a valid Ghidra script: " + sourceFile.getName(); - writer.println(message); - Msg.error(this, message); - return null; // class is not GhidraScript - + throw new GhidraScriptLoadException( + "Ghidra scripts in Java must extend " + GhidraScript.class.getName() + ". " + + sourceFile.getName() + " does not."); } - catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { - throw e; + catch (ClassNotFoundException e) { + throw new GhidraScriptLoadException("The class could not be found. " + + "It must be the public class of the .java file: " + e.getMessage(), e); + } + catch (NoClassDefFoundError e) { + throw new GhidraScriptLoadException("The class could not be found or loaded, " + + "perhaps due to a previous initialization error: " + e.getMessage(), e); + } + catch (ExceptionInInitializerError e) { + throw new GhidraScriptLoadException( + "Error during class initialization: " + e.getException(), e.getException()); + } + catch (InvocationTargetException e) { + throw new GhidraScriptLoadException( + "Error during class construction: " + e.getTargetException(), + e.getTargetException()); + } + catch (NoSuchMethodException e) { + throw new GhidraScriptLoadException( + "The default constructor does not exist: " + e.getMessage(), e); + } + catch (IllegalAccessException e) { + throw new GhidraScriptLoadException( + "The class or its default constructor is not accessible: " + e.getMessage(), e); } catch (Exception e) { - throw new ClassNotFoundException("", e); + throw new GhidraScriptLoadException("Unexpected error: " + e); } } /** - * Activate and build the {@link GhidraSourceBundle} containing {@code sourceFile} - * then load the script's class from its class loader. + * Activate and build the {@link GhidraSourceBundle} containing {@code sourceFile} then load the + * script's class from its class loader. * * @param sourceFile the source file * @param writer the target for build messages @@ -164,9 +188,10 @@ public class JavaScriptProvider extends GhidraScriptProvider { } /** - * Returns a Pattern that matches block comment openings. + * {@inheritDoc} + * + *

* For Java this is "/*". - * @return the Pattern for Java block comment openings */ @Override public Pattern getBlockCommentStart() { @@ -174,9 +199,10 @@ public class JavaScriptProvider extends GhidraScriptProvider { } /** - * Returns a Pattern that matches block comment closings. + * {@inheritDoc} + * + *

* In Java this is an asterisk followed by a forward slash. - * @return the Pattern for Java block comment closings */ @Override public Pattern getBlockCommentEnd() { @@ -204,14 +230,19 @@ public class JavaScriptProvider extends GhidraScriptProvider { } /** + * {@inheritDoc} + * + *

+ * Fix script name for search in script directories, such as Java package parts in the name and + * inner class names. * - * Fix script name for search in script directories, such as Java package parts in the name and inner class names. + *

+ * This method can handle names with '$' (inner classes) and names with '.' characters for + * package separators * - *

This method can handle names with '$' (inner classes) and names with '.' - * characters for package separators - * - *

It is part of a poorly specified behavior that is due for future amendment, - * see {@link GhidraScriptUtil#fixupName(String)}. + *

+ * It is part of a poorly specified behavior that is due for future amendment, see + * {@link GhidraScriptUtil#fixupName(String)}. * * @param scriptName the name of the script * @return the name as a '.java' file path (with '/'s and not '.'s) @@ -227,5 +258,4 @@ public class JavaScriptProvider extends GhidraScriptProvider { } return path + ".java"; } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java b/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java index 5e0d2b2982..869b939bd0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java @@ -35,8 +35,7 @@ import ghidra.app.events.OpenProgramPluginEvent; import ghidra.app.plugin.core.analysis.AutoAnalysisManager; import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin; import ghidra.app.plugin.core.script.GhidraScriptMgrPlugin; -import ghidra.app.script.GhidraScript; -import ghidra.app.script.JavaScriptProvider; +import ghidra.app.script.*; import ghidra.app.services.ProgramManager; import ghidra.base.project.GhidraProject; import ghidra.framework.Application; @@ -564,7 +563,7 @@ public class TestEnv { try { script = scriptProvider.getScriptInstance(resourceFile, writer); } - catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { + catch (GhidraScriptLoadException e) { Msg.error(TestEnv.class, "Problem creating script", e); } diff --git a/Ghidra/Features/Python/src/main/java/ghidra/python/PythonScriptProvider.java b/Ghidra/Features/Python/src/main/java/ghidra/python/PythonScriptProvider.java index 090e63b8f3..a6545d56f4 100644 --- a/Ghidra/Features/Python/src/main/java/ghidra/python/PythonScriptProvider.java +++ b/Ghidra/Features/Python/src/main/java/ghidra/python/PythonScriptProvider.java @@ -19,8 +19,7 @@ import java.io.*; import java.util.regex.Pattern; import generic.jar.ResourceFile; -import ghidra.app.script.GhidraScript; -import ghidra.app.script.GhidraScriptProvider; +import ghidra.app.script.*; public class PythonScriptProvider extends GhidraScriptProvider { @@ -37,8 +36,11 @@ public class PythonScriptProvider extends GhidraScriptProvider { } /** - * Returns a Pattern that matches block comment openings. + * {@inheritDoc} + * + *

* In Python this is a triple single quote sequence, "'''". + * * @return the Pattern for Python block comment openings */ @Override @@ -47,8 +49,11 @@ public class PythonScriptProvider extends GhidraScriptProvider { } /** - * Returns a Pattern that matches block comment closings. + * {@inheritDoc} + * + *

* In Python this is a triple single quote sequence, "'''". + * * @return the Pattern for Python block comment openings */ @Override @@ -88,11 +93,16 @@ public class PythonScriptProvider extends GhidraScriptProvider { @Override public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer) - throws ClassNotFoundException, InstantiationException, IllegalAccessException { + throws GhidraScriptLoadException { - Class clazz = Class.forName(PythonScript.class.getName()); - GhidraScript script = (GhidraScript) clazz.newInstance(); - script.setSourceFile(sourceFile); - return script; + try { + Class clazz = Class.forName(PythonScript.class.getName()); + GhidraScript script = (GhidraScript) clazz.getConstructor().newInstance(); + script.setSourceFile(sourceFile); + return script; + } + catch (Exception e) { + throw new GhidraScriptLoadException(e); + } } }