Merge remote-tracking branch 'origin/GP-2618_Dan_scriptErrorMessages--SQUASHED'

This commit is contained in:
Ryan Kurtz
2022-09-29 01:01:09 -04:00
7 changed files with 181 additions and 62 deletions
@@ -20,6 +20,7 @@ import java.awt.Color;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationTargetException;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -1592,7 +1593,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
try { try {
script = provider.getScriptInstance(sourceFile, writer); script = provider.getScriptInstance(sourceFile, writer);
} }
catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { catch (GhidraScriptLoadException e) {
Msg.error(this, e.getMessage()); Msg.error(this, e.getMessage());
return; return;
} }
@@ -19,6 +19,7 @@ import java.awt.BorderLayout;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.event.*; import java.awt.event.*;
import java.io.*; import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate; import java.util.function.Predicate;
@@ -671,14 +672,9 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
try { try {
return provider.getScriptInstance(scriptFile, console.getStdErr()); return provider.getScriptInstance(scriptFile, console.getStdErr());
} }
catch (IllegalAccessException e) { catch (GhidraScriptLoadException e) {
console.addErrorMessage("", "Unable to access script: " + scriptName); console.addErrorMessage("", "Unable to load script: " + scriptName);
} console.addErrorMessage("", " detail: " + e.getMessage());
catch (InstantiationException e) {
console.addErrorMessage("", "Unable to instantiate script: " + scriptName);
}
catch (ClassNotFoundException e) {
console.addErrorMessage("", "Unable to locate script class: " + scriptName);
} }
// show the error icon // show the error icon
@@ -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
*
* <p>
* 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
*
* <p>
* 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);
}
}
@@ -23,9 +23,11 @@ import generic.jar.ResourceFile;
import ghidra.util.classfinder.ExtensionPoint; import ghidra.util.classfinder.ExtensionPoint;
/** /**
* NOTE: ALL GhidraScriptProvider CLASSES MUST END IN "ScriptProvider". If not, * A provider that can compile, interpret, load, etc., Ghidra Scripts from a given language.
* the ClassSearcher will not find them.
* *
* <p>
* <b>NOTE:</b> ALL GhidraScriptProvider CLASSES MUST END IN "ScriptProvider". If not, the
* ClassSearcher will not find them.
*/ */
public abstract class GhidraScriptProvider public abstract class GhidraScriptProvider
implements ExtensionPoint, Comparable<GhidraScriptProvider> { implements ExtensionPoint, Comparable<GhidraScriptProvider> {
@@ -56,6 +58,7 @@ public abstract class GhidraScriptProvider
/** /**
* Deletes the script file and unloads the script from the script manager. * Deletes the script file and unloads the script from the script manager.
*
* @param scriptSource the script source file * @param scriptSource the script source file
* @return true if the script was completely deleted and cleaned up * @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. * Returns a description for this type of script.
*
* @return a description for this type of script * @return a description for this type of script
*/ */
public abstract String getDescription(); public abstract String getDescription();
/** /**
* Returns the file extension for this type of script. * Returns the file extension for this type of script.
*
* <p>
* For example, ".java" or ".py". * For example, ".java" or ".py".
*
* @return the file extension for this type of script * @return the file extension for this type of script
*/ */
public abstract String getExtension(); public abstract String getExtension();
/** /**
* Returns a GhidraScript instance for the specified source file. * Returns a GhidraScript instance for the specified source file.
*
* @param sourceFile the 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 * @return a GhidraScript instance for the specified source file
* @throws ClassNotFoundException if the script class cannot be found * @throws GhidraScriptLoadException when the script instance cannot be created
* @throws InstantiationException if the construction of the script fails for some reason
* @throws IllegalAccessException if the class constructor is not accessible
*/ */
public abstract GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer) public abstract GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer)
throws ClassNotFoundException, InstantiationException, IllegalAccessException; throws GhidraScriptLoadException;
/** /**
* Creates a new script using the specified file. * Creates a new script using the specified file.
*
* @param newScript the new script file * @param newScript the new script file
* @param category the script category * @param category the script category
* @throws IOException if an error occurs writing the file * @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. * Returns a Pattern that matches block comment openings.
*
* <p>
* If block comments are not supported by this provider, then this returns null. * 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 * @return the Pattern for block comment openings, null if block comments are not supported
*/ */
public Pattern getBlockCommentStart() { public Pattern getBlockCommentStart() {
@@ -108,7 +119,10 @@ public abstract class GhidraScriptProvider
/** /**
* Returns a Pattern that matches block comment closings. * Returns a Pattern that matches block comment closings.
*
* <p>
* If block comments are not supported by this provider, then this returns null. * 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 * @return the Pattern for block comment closings, null if block comments are not supported
*/ */
public Pattern getBlockCommentEnd() { public Pattern getBlockCommentEnd() {
@@ -117,14 +131,20 @@ public abstract class GhidraScriptProvider
/** /**
* Returns the comment character. * Returns the comment character.
*
* <p>
* For example, "//" or "#". * For example, "//" or "#".
*
* @return the comment character * @return the comment character
*/ */
public abstract String getCommentCharacter(); public abstract String getCommentCharacter();
/** /**
* Writes the script header. * Writes the script header.
*
* <p>
* Include a place holder for each meta-data item. * Include a place holder for each meta-data item.
*
* @param writer the print writer * @param writer the print writer
* @param category the default category * @param category the default category
*/ */
@@ -150,6 +170,7 @@ public abstract class GhidraScriptProvider
/** /**
* Writes the script body template. * Writes the script body template.
*
* @param writer the print writer * @param writer the print writer
*/ */
protected void writeBody(PrintWriter writer) { protected void writeBody(PrintWriter writer) {
@@ -159,8 +180,9 @@ public abstract class GhidraScriptProvider
/** /**
* Fixup a script name for searching in script directories. * Fixup a script name for searching in script directories.
* *
* <p>This method is part of a poorly specified behavior that is due for future amendment, * <p>
* 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 * @param scriptName the name of the script, must end with this provider's extension
* @return a (relative) file path to the corresponding script * @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 the start of certification header line if this file type is subject to certification.
*
* @return start of certification header or null if not supported * @return start of certification header or null if not supported
*/ */
protected String getCertifyHeaderStart() { protected String getCertifyHeaderStart() {
@@ -181,6 +204,7 @@ 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. * certification.
*
* @return certification header body prefix or null if not supported * @return certification header body prefix or null if not supported
*/ */
protected String getCertificationBodyPrefix() { 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 the end of certification header line if this file type is subject to certification.
*
* @return end of certification header or null if not supported * @return end of certification header or null if not supported
*/ */
protected String getCertifyHeaderEnd() { protected String getCertifyHeaderEnd() {
@@ -16,6 +16,7 @@
package ghidra.app.script; package ghidra.app.script;
import java.io.*; import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections; import java.util.Collections;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -26,6 +27,9 @@ import ghidra.app.plugin.core.osgi.*;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
/**
* The provider for Ghidra Scripts written in Java
*/
public class JavaScriptProvider extends GhidraScriptProvider { public class JavaScriptProvider extends GhidraScriptProvider {
private static final Pattern BLOCK_COMMENT_START = Pattern.compile("/\\*"); private static final Pattern BLOCK_COMMENT_START = Pattern.compile("/\\*");
private static final Pattern BLOCK_COMMENT_END = Pattern.compile("\\*/"); private static final Pattern BLOCK_COMMENT_END = Pattern.compile("\\*/");
@@ -33,14 +37,16 @@ public class JavaScriptProvider extends GhidraScriptProvider {
private final BundleHost bundleHost; 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() { public JavaScriptProvider() {
bundleHost = GhidraScriptUtil.getBundleHost(); 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 * @param sourceFile the source file
* @return the bundle * @return the bundle
@@ -80,35 +86,53 @@ public class JavaScriptProvider extends GhidraScriptProvider {
@Override @Override
public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer) public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer)
throws ClassNotFoundException, InstantiationException, IllegalAccessException { throws GhidraScriptLoadException {
try { try {
Class<?> clazz = loadClass(sourceFile, writer); Class<?> clazz = loadClass(sourceFile, writer);
Object object;
object = clazz.getDeclaredConstructor().newInstance();
if (object instanceof GhidraScript) { if (GhidraScript.class.isAssignableFrom(clazz)) {
GhidraScript script = (GhidraScript) object; GhidraScript script = (GhidraScript) clazz.getDeclaredConstructor().newInstance();
script.setSourceFile(sourceFile); script.setSourceFile(sourceFile);
return script; return script;
} }
String message = "Not a valid Ghidra script: " + sourceFile.getName(); throw new GhidraScriptLoadException(
writer.println(message); "Ghidra scripts in Java must extend " + GhidraScript.class.getName() + ". " +
Msg.error(this, message); sourceFile.getName() + " does not.");
return null; // class is not GhidraScript
} }
catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { catch (ClassNotFoundException e) {
throw 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) { catch (Exception e) {
throw new ClassNotFoundException("", e); throw new GhidraScriptLoadException("Unexpected error: " + e);
} }
} }
/** /**
* Activate and build the {@link GhidraSourceBundle} containing {@code sourceFile} * Activate and build the {@link GhidraSourceBundle} containing {@code sourceFile} then load the
* then load the script's class from its class loader. * script's class from its class loader.
* *
* @param sourceFile the source file * @param sourceFile the source file
* @param writer the target for build messages * @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}
*
* <p>
* For Java this is "/*". * For Java this is "/*".
* @return the Pattern for Java block comment openings
*/ */
@Override @Override
public Pattern getBlockCommentStart() { public Pattern getBlockCommentStart() {
@@ -174,9 +199,10 @@ public class JavaScriptProvider extends GhidraScriptProvider {
} }
/** /**
* Returns a Pattern that matches block comment closings. * {@inheritDoc}
*
* <p>
* In Java this is an asterisk followed by a forward slash. * In Java this is an asterisk followed by a forward slash.
* @return the Pattern for Java block comment closings
*/ */
@Override @Override
public Pattern getBlockCommentEnd() { 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. * <p>
* Fix script name for search in script directories, such as Java package parts in the name and
* inner class names.
* *
* <p>This method can handle names with '$' (inner classes) and names with '.' * <p>
* characters for package separators * This method can handle names with '$' (inner classes) and names with '.' characters for
* package separators
* *
* <p>It is part of a poorly specified behavior that is due for future amendment, * <p>
* 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 * @param scriptName the name of the script
* @return the name as a '.java' file path (with '/'s and not '.'s) * @return the name as a '.java' file path (with '/'s and not '.'s)
@@ -227,5 +258,4 @@ public class JavaScriptProvider extends GhidraScriptProvider {
} }
return path + ".java"; return path + ".java";
} }
} }
@@ -35,8 +35,7 @@ import ghidra.app.events.OpenProgramPluginEvent;
import ghidra.app.plugin.core.analysis.AutoAnalysisManager; import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin; import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
import ghidra.app.plugin.core.script.GhidraScriptMgrPlugin; import ghidra.app.plugin.core.script.GhidraScriptMgrPlugin;
import ghidra.app.script.GhidraScript; import ghidra.app.script.*;
import ghidra.app.script.JavaScriptProvider;
import ghidra.app.services.ProgramManager; import ghidra.app.services.ProgramManager;
import ghidra.base.project.GhidraProject; import ghidra.base.project.GhidraProject;
import ghidra.framework.Application; import ghidra.framework.Application;
@@ -564,7 +563,7 @@ public class TestEnv {
try { try {
script = scriptProvider.getScriptInstance(resourceFile, writer); script = scriptProvider.getScriptInstance(resourceFile, writer);
} }
catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { catch (GhidraScriptLoadException e) {
Msg.error(TestEnv.class, "Problem creating script", e); Msg.error(TestEnv.class, "Problem creating script", e);
} }
@@ -19,8 +19,7 @@ import java.io.*;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.script.GhidraScript; import ghidra.app.script.*;
import ghidra.app.script.GhidraScriptProvider;
public class PythonScriptProvider extends GhidraScriptProvider { public class PythonScriptProvider extends GhidraScriptProvider {
@@ -37,8 +36,11 @@ public class PythonScriptProvider extends GhidraScriptProvider {
} }
/** /**
* Returns a Pattern that matches block comment openings. * {@inheritDoc}
*
* <p>
* In Python this is a triple single quote sequence, "'''". * In Python this is a triple single quote sequence, "'''".
*
* @return the Pattern for Python block comment openings * @return the Pattern for Python block comment openings
*/ */
@Override @Override
@@ -47,8 +49,11 @@ public class PythonScriptProvider extends GhidraScriptProvider {
} }
/** /**
* Returns a Pattern that matches block comment closings. * {@inheritDoc}
*
* <p>
* In Python this is a triple single quote sequence, "'''". * In Python this is a triple single quote sequence, "'''".
*
* @return the Pattern for Python block comment openings * @return the Pattern for Python block comment openings
*/ */
@Override @Override
@@ -88,11 +93,16 @@ public class PythonScriptProvider extends GhidraScriptProvider {
@Override @Override
public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer) public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer)
throws ClassNotFoundException, InstantiationException, IllegalAccessException { throws GhidraScriptLoadException {
Class<?> clazz = Class.forName(PythonScript.class.getName()); try {
GhidraScript script = (GhidraScript) clazz.newInstance(); Class<?> clazz = Class.forName(PythonScript.class.getName());
script.setSourceFile(sourceFile); GhidraScript script = (GhidraScript) clazz.getConstructor().newInstance();
return script; script.setSourceFile(sourceFile);
return script;
}
catch (Exception e) {
throw new GhidraScriptLoadException(e);
}
} }
} }