mirror of
https://github.com/LegacyModdingMC/UniMix.git
synced 2026-11-13 00:47:52 +00:00
Add error handler support
This commit is contained in:
1 parent
88fafc87e5
commit
dffb1065d3
8 files changed
+340
-28
No files matched your search
@@ -28,6 +28,7 @@ import java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -335,6 +336,11 @@ public class MixinEnvironment {
|
||||
*/
|
||||
private final boolean[] options;
|
||||
|
||||
/**
|
||||
* Error handlers for environment
|
||||
*/
|
||||
private final Set<String> errorHandlers = new LinkedHashSet<String>();
|
||||
|
||||
/**
|
||||
* Detected side
|
||||
*/
|
||||
@@ -352,7 +358,7 @@ public class MixinEnvironment {
|
||||
* Class name transformer (if present)
|
||||
*/
|
||||
private IClassNameTransformer nameTransformer;
|
||||
|
||||
|
||||
private MixinEnvironment(Phase phase) {
|
||||
this.phase = phase;
|
||||
this.configsKey = MixinEnvironment.CONFIGS_KEY + "." + this.phase.name.toLowerCase();
|
||||
@@ -421,6 +427,24 @@ public class MixinEnvironment {
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new error handler class to this environment
|
||||
*
|
||||
* @param handlerName Handler class to add
|
||||
* @return fluent interface
|
||||
*/
|
||||
public MixinEnvironment registerErrorHandlerClass(String handlerName) {
|
||||
this.errorHandlers.add(handlerName);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered error handlers for this environment
|
||||
*/
|
||||
public Set<String> getErrorHandlerClasses() {
|
||||
return Collections.<String>unmodifiableSet(this.errorHandlers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the active mixin transformer instance (if any)
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered.org <http://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package org.spongepowered.asm.mixin.extensibility;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface IMixinConfig {
|
||||
|
||||
/**
|
||||
* Get the name of the file from which this configuration object was
|
||||
* initialised
|
||||
*/
|
||||
public abstract String getName();
|
||||
|
||||
/**
|
||||
* Get the package containing all mixin classes
|
||||
*/
|
||||
public abstract String getMixinPackage();
|
||||
|
||||
/**
|
||||
* Get the priority
|
||||
*/
|
||||
public abstract int getPriority();
|
||||
|
||||
/**
|
||||
* Get the companion plugin, if available
|
||||
*/
|
||||
public abstract IMixinConfigPlugin getPlugin();
|
||||
|
||||
/**
|
||||
* True if this mixin is <em>required</em> (failure to apply a defined mixin
|
||||
* is an <em>error</em> condition).
|
||||
*/
|
||||
public abstract boolean isRequired();
|
||||
|
||||
/**
|
||||
* Get targets for this configuration
|
||||
*/
|
||||
public abstract Set<String> getTargets();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered.org <http://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package org.spongepowered.asm.mixin.extensibility;
|
||||
|
||||
import org.apache.logging.log4j.Level;
|
||||
|
||||
|
||||
/**
|
||||
* Interface for objects which want to perform custom behaviour when fatal mixin
|
||||
* errors occur. For example displaying a user-friendly error message
|
||||
*/
|
||||
public interface IMixinErrorHandler {
|
||||
|
||||
/**
|
||||
* Action to take when handling an error. By default, if a config is marked
|
||||
* as "required" then the default action will be {@link #ERROR}, and will be
|
||||
* {@link #WARN} otherwise.
|
||||
*/
|
||||
public static enum ErrorAction {
|
||||
|
||||
/**
|
||||
* Take no action, this should be treated as a non-critical error and
|
||||
* processing should continue
|
||||
*/
|
||||
NONE(Level.INFO),
|
||||
|
||||
/**
|
||||
* Generate a warning but continue processing
|
||||
*/
|
||||
WARN(Level.WARN),
|
||||
|
||||
/**
|
||||
* Throw a {@link org.spongepowered.asm.mixin.MixinApplyError} to halt
|
||||
* further processing if possible
|
||||
*/
|
||||
ERROR(Level.FATAL);
|
||||
|
||||
public final Level logLevel;
|
||||
|
||||
private ErrorAction(Level logLevel) {
|
||||
this.logLevel = logLevel;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an error occurs applying a mixin. This allows
|
||||
* the plugin to display more user-friendly error messages if required.
|
||||
*
|
||||
* <p>By default, when a critical error occurs the mixin processor will
|
||||
* raise a warning if the config is not marked as "required" and will throw
|
||||
* an {@link Error} if it is. This behaviour can be altered by returning
|
||||
* different values from this method.</p>
|
||||
*
|
||||
* <p>The original throwable which was caught is passed in via the <code>
|
||||
* th</code> parameter and the default action is passed in to the <code>
|
||||
* action</code> parameter. A plugin can choose to output a friendly message
|
||||
* but leave the original behaviour intact (by returning <code>null</code>
|
||||
* or returning <code>action</code> directly. Alternatively it may throw a
|
||||
* different exception or error, or can reduce the severity of the error by
|
||||
* returning a different {@link ErrorAction}.</p>
|
||||
*
|
||||
* @param targetClassName Class being transformed when the error occurred
|
||||
* @param th Throwable which was caught
|
||||
* @param mixin Mixin which was being applied at the time of the error
|
||||
* @param action Default action
|
||||
* @return null to perform the default action (or return action) or new
|
||||
* action to take
|
||||
*/
|
||||
public abstract ErrorAction onError(String targetClassName, Throwable th, IMixinInfo mixin, ErrorAction action);
|
||||
|
||||
}
|
||||
@@ -27,9 +27,17 @@ package org.spongepowered.asm.mixin.extensibility;
|
||||
import java.util.List;
|
||||
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.spongepowered.asm.mixin.MixinEnvironment.Phase;
|
||||
|
||||
public interface IMixinInfo {
|
||||
|
||||
/**
|
||||
* Get the config to which this mixin belongs
|
||||
*
|
||||
* @return the mixin config
|
||||
*/
|
||||
public IMixinConfig getConfig();
|
||||
|
||||
/**
|
||||
* Get the simple name of the mixin
|
||||
*
|
||||
@@ -42,7 +50,7 @@ public interface IMixinInfo {
|
||||
*
|
||||
* @return mixin class name
|
||||
*/
|
||||
public abstract String getClassName();
|
||||
public abstract String getClassName();
|
||||
|
||||
/**
|
||||
* Get the ref (internal name) of the mixin class
|
||||
@@ -87,4 +95,11 @@ public interface IMixinInfo {
|
||||
* @return the priority
|
||||
*/
|
||||
public abstract int getPriority();
|
||||
|
||||
/**
|
||||
* Get the mixin phase
|
||||
*
|
||||
* @return the phase
|
||||
*/
|
||||
public abstract Phase getPhase();
|
||||
}
|
||||
@@ -43,6 +43,7 @@ import org.objectweb.asm.tree.ClassNode;
|
||||
import org.spongepowered.asm.launch.MixinInitialisationError;
|
||||
import org.spongepowered.asm.mixin.MixinEnvironment;
|
||||
import org.spongepowered.asm.mixin.MixinEnvironment.Option;
|
||||
import org.spongepowered.asm.mixin.extensibility.IMixinConfig;
|
||||
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
|
||||
import org.spongepowered.asm.mixin.injection.struct.ReferenceMapper;
|
||||
import org.spongepowered.asm.util.VersionNumber;
|
||||
@@ -53,7 +54,7 @@ import com.google.gson.annotations.SerializedName;
|
||||
/**
|
||||
* Mixin configuration bundle
|
||||
*/
|
||||
class MixinConfig implements Comparable<MixinConfig> {
|
||||
class MixinConfig implements Comparable<MixinConfig>, IMixinConfig {
|
||||
|
||||
/**
|
||||
* Global order of mixin configs, used to determine ordering between configs
|
||||
@@ -350,19 +351,19 @@ class MixinConfig implements Comparable<MixinConfig> {
|
||||
void postApply(String transformedName, ClassNode targetClass) {
|
||||
this.unhandledTargets.remove(transformedName);
|
||||
}
|
||||
|
||||
/**
|
||||
* True if this mixin is <em>required</em> (failure to apply a defined mixin
|
||||
* is an <em>error</em> condition).
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.spongepowered.asm.mixin.transformer.IMixinConfig#isRequired()
|
||||
*/
|
||||
@Override
|
||||
public boolean isRequired() {
|
||||
return this.required;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the file from which this configuration object was
|
||||
* initialised
|
||||
/* (non-Javadoc)
|
||||
* @see org.spongepowered.asm.mixin.transformer.IMixinConfig#getName()
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
@@ -370,10 +371,19 @@ class MixinConfig implements Comparable<MixinConfig> {
|
||||
/**
|
||||
* Get the package containing all mixin classes
|
||||
*/
|
||||
@Override
|
||||
public String getMixinPackage() {
|
||||
return this.mixinPackage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the priority
|
||||
*/
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return this.priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of mixin classes we will be applying
|
||||
*/
|
||||
@@ -396,16 +406,18 @@ class MixinConfig implements Comparable<MixinConfig> {
|
||||
return this.refMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the companion plugin, if available
|
||||
/* (non-Javadoc)
|
||||
* @see org.spongepowered.asm.mixin.transformer.IMixinConfig#getPlugin()
|
||||
*/
|
||||
@Override
|
||||
public IMixinConfigPlugin getPlugin() {
|
||||
return this.plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get targets for this configuration
|
||||
/* (non-Javadoc)
|
||||
* @see org.spongepowered.asm.mixin.transformer.IMixinConfig#getTargets()
|
||||
*/
|
||||
@Override
|
||||
public Set<String> getTargets() {
|
||||
return Collections.<String>unmodifiableSet(this.mixinMapping.keySet());
|
||||
}
|
||||
@@ -494,7 +506,7 @@ class MixinConfig implements Comparable<MixinConfig> {
|
||||
}
|
||||
return (this.priority - other.priority);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Factory method, creates a new mixin configuration bundle from the
|
||||
* specified configFile, which must be accessible on the classpath
|
||||
|
||||
@@ -52,6 +52,7 @@ import org.spongepowered.asm.mixin.MixinEnvironment;
|
||||
import org.spongepowered.asm.mixin.MixinEnvironment.Phase;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.extensibility.IMixinConfig;
|
||||
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
|
||||
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
|
||||
import org.spongepowered.asm.util.ASMHelper;
|
||||
@@ -371,6 +372,14 @@ class MixinInfo extends TreeInfo implements Comparable<MixinInfo>, IMixinInfo {
|
||||
ClassInfo getClassInfo() {
|
||||
return this.classInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent config which declares this mixin
|
||||
*/
|
||||
@Override
|
||||
public IMixinConfig getConfig() {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent config which declares this mixin
|
||||
@@ -430,6 +439,7 @@ class MixinInfo extends TreeInfo implements Comparable<MixinInfo>, IMixinInfo {
|
||||
/**
|
||||
* Get the phase in which this mixin was initialised
|
||||
*/
|
||||
@Override
|
||||
public Phase getPhase() {
|
||||
return this.phase;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ import org.spongepowered.asm.mixin.MixinEnvironment;
|
||||
import org.spongepowered.asm.mixin.MixinEnvironment.Option;
|
||||
import org.spongepowered.asm.mixin.MixinEnvironment.Phase;
|
||||
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
|
||||
import org.spongepowered.asm.mixin.extensibility.IMixinErrorHandler;
|
||||
import org.spongepowered.asm.mixin.extensibility.IMixinErrorHandler.ErrorAction;
|
||||
import org.spongepowered.asm.transformers.TreeTransformer;
|
||||
import org.spongepowered.asm.util.Constants;
|
||||
|
||||
@@ -253,7 +255,12 @@ public class MixinTransformer extends TreeTransformer {
|
||||
/**
|
||||
* Logging level for verbose messages
|
||||
*/
|
||||
private Level verboseLoggingLevel = Level.DEBUG;
|
||||
private Level verboseLoggingLevel = Level.DEBUG;
|
||||
|
||||
/**
|
||||
* Handling an error state, do not process further mixins
|
||||
*/
|
||||
private boolean errorState = false;
|
||||
|
||||
/**
|
||||
* ctor
|
||||
@@ -298,7 +305,7 @@ public class MixinTransformer extends TreeTransformer {
|
||||
*/
|
||||
@Override
|
||||
public byte[] transform(String name, String transformedName, byte[] basicClass) {
|
||||
if (basicClass == null || transformedName == null) {
|
||||
if (basicClass == null || transformedName == null || this.errorState) {
|
||||
return basicClass;
|
||||
}
|
||||
|
||||
@@ -356,17 +363,7 @@ public class MixinTransformer extends TreeTransformer {
|
||||
MixinTransformer.dumpClass(transformedName.replace('.', '/') + ".target", basicClass);
|
||||
}
|
||||
|
||||
MixinInfo mixin = th.getMixin();
|
||||
Phase phase = mixin.getPhase();
|
||||
MixinConfig config = mixin.getParent();
|
||||
this.logger.log(config.isRequired() ? Level.FATAL : Level.WARN, String.format("Mixin failed applying %s -> %s: %s %s",
|
||||
mixin, transformedName, th.getClass().getName(), th.getMessage()), th);
|
||||
|
||||
if (config.isRequired()) {
|
||||
throw new MixinApplyError("Mixin [" + mixin + "] from " + phase + " FAILED for REQUIRED config [" + config + "]", th);
|
||||
}
|
||||
|
||||
th.printStackTrace();
|
||||
this.handleMixinErrorState(transformedName, th);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,6 +561,50 @@ public class MixinTransformer extends TreeTransformer {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMixinErrorState(String targetClassName, InvalidMixinException th) throws MixinApplyError {
|
||||
this.errorState = true;
|
||||
|
||||
MixinInfo mixin = th.getMixin();
|
||||
MixinConfig config = mixin.getParent();
|
||||
ErrorAction action = config.isRequired() ? ErrorAction.ERROR : ErrorAction.WARN;
|
||||
|
||||
for (IMixinErrorHandler handler : this.getErrorHandlers(mixin.getPhase())) {
|
||||
ErrorAction newAction = handler.onError(targetClassName, th, mixin, action);
|
||||
if (newAction != null) {
|
||||
action = newAction;
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(action.logLevel, String.format("Mixin failed applying %s -> %s: %s %s", mixin, targetClassName,
|
||||
th.getClass().getName(), th.getMessage()), th);
|
||||
|
||||
this.errorState = false;
|
||||
|
||||
if (action == ErrorAction.ERROR) {
|
||||
throw new MixinApplyError(String.format("Mixin [%s] from phase [%s] in config [%s] FAILED", mixin, mixin.getPhase(),
|
||||
config), th);
|
||||
}
|
||||
}
|
||||
|
||||
private List<IMixinErrorHandler> getErrorHandlers(Phase phase) {
|
||||
List<IMixinErrorHandler> handlers = new ArrayList<IMixinErrorHandler>();
|
||||
|
||||
for (String handlerClassName : MixinEnvironment.getEnvironment(phase).getErrorHandlerClasses()) {
|
||||
try {
|
||||
this.logger.info("Instancing error handler class {}", handlerClassName);
|
||||
Class<?> handlerClass = Class.forName(handlerClassName, true, Launch.classLoader);
|
||||
IMixinErrorHandler handler = (IMixinErrorHandler)handlerClass.newInstance();
|
||||
if (handler != null) {
|
||||
handlers.add(handler);
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
// skip bad handlers
|
||||
}
|
||||
}
|
||||
|
||||
return handlers;
|
||||
}
|
||||
|
||||
private byte[] writeClass(String transformedName, ClassNode targetClass) {
|
||||
// Collapse tree to bytes
|
||||
byte[] bytes = this.writeClass(targetClass);
|
||||
|
||||
@@ -28,6 +28,9 @@ import java.io.PrintStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
|
||||
@@ -89,6 +92,26 @@ public class PrettyPrinter {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array
|
||||
* @return
|
||||
*/
|
||||
public PrettyPrinter add(Object[] array) {
|
||||
return this.add(array, "%s");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array
|
||||
* @return
|
||||
*/
|
||||
public PrettyPrinter add(Object[] array, String format) {
|
||||
for (Object element : array) {
|
||||
this.add(format, element);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a horizontal rule to the output
|
||||
*
|
||||
@@ -147,4 +170,35 @@ public class PrettyPrinter {
|
||||
private void printHr(PrintStream stream, char... hrChars) {
|
||||
stream.printf("/*%s*/\n", Strings.repeat(new String(hrChars), this.width + 2));
|
||||
}
|
||||
|
||||
public void log(Logger logger) {
|
||||
this.log(logger, Level.INFO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write this printer to the specified logger
|
||||
*
|
||||
* @param logger logger to log to
|
||||
* @param level log level
|
||||
*/
|
||||
public void log(Logger logger, Level level) {
|
||||
this.logHr(logger, level, '*');
|
||||
for (String line : this.lines) {
|
||||
int len = line.length();
|
||||
if (len > 1 && line.charAt(0) == PrettyPrinter.HR) {
|
||||
this.logHr(logger, level, line.charAt(1));
|
||||
} else {
|
||||
if (len > 0 && line.charAt(0) == PrettyPrinter.CENTRE) {
|
||||
String text = line.substring(1);
|
||||
line = String.format("%" + (((this.width - (text.length())) / 2) + text.length()) + "s", text);
|
||||
}
|
||||
logger.log(level, String.format("/* %-" + this.width + "s */", line));
|
||||
}
|
||||
}
|
||||
this.logHr(logger, level, '*');
|
||||
}
|
||||
|
||||
private void logHr(Logger logger, Level level, char... hrChars) {
|
||||
logger.log(level, String.format("/*%s*/", Strings.repeat(new String(hrChars), this.width + 2)));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user