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
commit f1177763aa
7 changed files with 181 additions and 62 deletions

View file

@ -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;
}

View file

@ -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.)

View file

@ -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);
}
}

View file

@ -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.
*
* <p>
* <b>NOTE:</b> ALL GhidraScriptProvider CLASSES MUST END IN "ScriptProvider". If not, the
* ClassSearcher will not find them.
*/
public abstract class 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.
*
* @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.
*
* <p>
* 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.
*
* <p>
* 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.
*
* <p>
* 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.
*
* <p>
* For example, "//" or "#".
*
* @return the comment character
*/
public abstract String getCommentCharacter();
/**
* Writes the script header.
* Writes the script header.
*
* <p>
* 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.
*
* <p>This method is part of a poorly specified behavior that is due for future amendment,
* see {@link GhidraScriptUtil#fixupName(String)}.
* <p>
* 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() {

View file

@ -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}
*
* <p>
* 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}
*
* <p>
* 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}
*
* <p>
* 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.
* <p>
* This method can handle names with '$' (inner classes) and names with '.' characters for
* package separators
*
* <p>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,
* see {@link GhidraScriptUtil#fixupName(String)}.
* <p>
* 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";
}
}

View file

@ -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);
}

View file

@ -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}
*
* <p>
* 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}
*
* <p>
* 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);
}
}
}