/* * This file is part of Mixin, licensed under the MIT License (MIT). * * Copyright (c) SpongePowered * 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; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import org.spongepowered.asm.logging.Level; import org.spongepowered.asm.logging.ILogger; import org.spongepowered.asm.launch.GlobalProperties; import org.spongepowered.asm.launch.GlobalProperties.Keys; import org.spongepowered.asm.launch.MixinBootstrap; import org.objectweb.asm.ClassReader; import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.extensibility.IEnvironmentTokenProvider; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.throwables.MixinException; import org.spongepowered.asm.mixin.transformer.IMixinTransformer; import org.spongepowered.asm.obfuscation.RemapperChain; import org.spongepowered.asm.service.IMixinService; import org.spongepowered.asm.service.ITransformer; import org.spongepowered.asm.service.ITransformerProvider; import org.spongepowered.asm.service.MixinService; import org.spongepowered.asm.service.MixinServiceAbstract; import org.spongepowered.asm.util.Constants; import org.spongepowered.asm.util.IConsumer; import org.spongepowered.asm.util.ITokenProvider; import org.spongepowered.asm.util.JavaVersion; import org.spongepowered.asm.util.LanguageFeatures; import org.spongepowered.asm.util.PrettyPrinter; import org.spongepowered.asm.util.asm.ASM; import org.spongepowered.asm.util.perf.Profiler; import com.google.common.collect.ImmutableList; /** * The mixin environment manages global state information for the mixin * subsystem. */ public final class MixinEnvironment implements ITokenProvider { /** * Environment phase, deliberately not implemented as an enum */ public static final class Phase { /** * Not initialised phase */ static final Phase NOT_INITIALISED = new Phase(-1, "NOT_INITIALISED"); /** * "Pre initialisation" phase, everything before the tweak system begins * to load the game */ public static final Phase PREINIT = new Phase(0, "PREINIT"); /** * "Initialisation" phase, after FML's deobf transformer has loaded */ public static final Phase INIT = new Phase(1, "INIT"); /** * "Default" phase, during runtime */ public static final Phase DEFAULT = new Phase(2, "DEFAULT"); /** * All phases */ static final List phases = ImmutableList.of( Phase.PREINIT, Phase.INIT, Phase.DEFAULT ); /** * Phase ordinal */ final int ordinal; /** * Phase name */ final String name; /** * Environment for this phase */ private MixinEnvironment environment; private Phase(int ordinal, String name) { this.ordinal = ordinal; this.name = name; } @Override public String toString() { return this.name; } /** * Get a phase by name, returns null if no phases exist with * the specified name * * @param name phase name to lookup * @return phase object or null if non existent */ public static Phase forName(String name) { for (Phase phase : Phase.phases) { if (phase.name.equals(name)) { return phase; } } return null; } MixinEnvironment getEnvironment() { if (this.ordinal < 0) { throw new IllegalArgumentException("Cannot access the NOT_INITIALISED environment"); } if (this.environment == null) { this.environment = new MixinEnvironment(this); } return this.environment; } } /** * Represents a "side", client or dedicated server */ public static enum Side { /** * The environment was unable to determine current side */ UNKNOWN { @Override protected boolean detect() { return false; } }, /** * Client-side environment */ CLIENT { @Override protected boolean detect() { String sideName = MixinService.getService().getSideName(); return Constants.SIDE_CLIENT.equals(sideName); } }, /** * (Dedicated) Server-side environment */ SERVER { @Override protected boolean detect() { String sideName = MixinService.getService().getSideName(); return Constants.SIDE_SERVER.equals(sideName) || Constants.SIDE_DEDICATEDSERVER.equals(sideName); } }; protected abstract boolean detect(); } /** * Mixin options */ public static enum Option { /** * Enable all debugging options */ DEBUG_ALL("debug"), /** * Enable post-mixin class export. This causes all classes to be written * to the .mixin.out directory within the runtime directory * after mixins are applied, for debugging purposes. */ DEBUG_EXPORT(Option.DEBUG_ALL, "export"), /** * Export filter, if omitted allows all transformed classes to be * exported. If specified, acts as a filter for class names to export * and only matching classes will be exported. This is useful when using * Fernflower as exporting can be otherwise very slow. The following * wildcards are allowed: * *
*
*
Matches one or more characters except dot (.)
*
**
Matches any number of characters
*
?
Matches exactly one character
*
*/ DEBUG_EXPORT_FILTER(Option.DEBUG_EXPORT, "filter", false), /** * Allow fernflower to be disabled even if it is found on the classpath */ DEBUG_EXPORT_DECOMPILE(Option.DEBUG_EXPORT, Inherit.ALLOW_OVERRIDE, "decompile"), /** * Run fernflower in a separate thread. In general this will allow * export to impact startup time much less (decompiling normally adds * about 20% to load times) with the trade-off that crashes may lead to * undecompiled exports. */ DEBUG_EXPORT_DECOMPILE_THREADED(Option.DEBUG_EXPORT_DECOMPILE, Inherit.ALLOW_OVERRIDE, "async"), /** * By default, if the runtime export decompiler is active, mixin generic * signatures are merged into target classes. However this can cause * problems with some runtime subsystems which attempt to reify generics * using the signature data. Set this option to false to * disable generic signature merging. */ DEBUG_EXPORT_DECOMPILE_MERGESIGNATURES(Option.DEBUG_EXPORT_DECOMPILE, Inherit.ALLOW_OVERRIDE, "mergeGenericSignatures"), /** * Run the CheckClassAdapter on all classes after mixins are applied, * also enables stricter checks on mixins for use at dev-time, promotes * some warning-level messages to exceptions */ DEBUG_VERIFY(Option.DEBUG_ALL, "verify"), /** * Enable verbose mixin logging (elevates all DEBUG level messages to * INFO level) */ DEBUG_VERBOSE(Option.DEBUG_ALL, "verbose"), /** * Elevates failed injections to an error condition, see * {@link Inject#expect} for details */ DEBUG_INJECTORS(Option.DEBUG_ALL, "countInjections"), /** * Enable strict checks */ DEBUG_STRICT(Option.DEBUG_ALL, Inherit.INDEPENDENT, "strict"), /** * If false (default), {@link Unique} public methods merely raise a * warning when encountered and are not merged into the target. If true, * an exception is thrown instead */ DEBUG_UNIQUE(Option.DEBUG_STRICT, "unique"), /** * Enable strict checking for mixin targets */ DEBUG_TARGETS(Option.DEBUG_STRICT, "targets"), /** * Enable the performance profiler for all mixin operations (normally it * is only enabled during mixin prepare operations) */ DEBUG_PROFILER(Option.DEBUG_ALL, Inherit.ALLOW_OVERRIDE, "profiler"), /** * Dumps the bytecode for the target class to disk when mixin * application fails */ DUMP_TARGET_ON_FAILURE("dumpTargetOnFailure"), /** * Enable all checks */ CHECK_ALL("checks"), /** * Checks that all declared interface methods are implemented on a class * after mixin application. */ CHECK_IMPLEMENTS(Option.CHECK_ALL, "interfaces"), /** * If interface check is enabled, "strict mode" (default) applies the * implementation check even to abstract target classes. Setting this * option to false causes abstract targets to be skipped when * generating the implementation report. */ CHECK_IMPLEMENTS_STRICT(Option.CHECK_IMPLEMENTS, Inherit.ALLOW_OVERRIDE, "strict"), /** * Ignore all constraints on mixin annotations, output warnings instead */ IGNORE_CONSTRAINTS("ignoreConstraints"), /** * Enables the hot-swap agent */ HOT_SWAP("hotSwap"), /** * Parent for environment settings */ ENVIRONMENT(Inherit.ALWAYS_FALSE, true, "env"), /** * Force refmap obf type when required */ OBFUSCATION_TYPE(Option.ENVIRONMENT, Inherit.ALWAYS_FALSE, "obf"), /** * Disable refmap when required */ DISABLE_REFMAP(Option.ENVIRONMENT, Inherit.INDEPENDENT, "disableRefMap"), /** * Rather than disabling the refMap, you may wish to remap existing * refMaps at runtime. This can be achieved by setting this property and * supplying values for mixin.env.refMapRemappingFile and * mixin.env.refMapRemappingEnv. Though those properties can be * ignored if starting via GradleStart (this property is also * automatically enabled if loading via GradleStart). */ REFMAP_REMAP(Option.ENVIRONMENT, Inherit.INDEPENDENT, "remapRefMap"), /** * If mixin.env.remapRefMap is enabled, this setting can be * used to override the name of the SRG file to read mappings from. The * mappings must have a source type of searge and a target type * matching the current development environment. If the source type is * not searge then the mixin.env.refMapRemappingEnv * should be set to the correct source environment type. */ REFMAP_REMAP_RESOURCE(Option.ENVIRONMENT, Inherit.INDEPENDENT, "refMapRemappingFile", ""), /** * When using mixin.env.refMapRemappingFile, this setting * overrides the default source environment (searge). However note that * the specified environment type must exist in the orignal refmap. */ REFMAP_REMAP_SOURCE_ENV(Option.ENVIRONMENT, Inherit.INDEPENDENT, "refMapRemappingEnv", "searge"), /** * When mixin.env.remapRefMap is enabled and a refmap is * available for a mixin config, certain injection points are allowed to * fail over to a "permissive" match which ignores the member descriptor * in the refmap. To disable this behaviour, set this property to * false. */ REFMAP_REMAP_ALLOW_PERMISSIVE(Option.ENVIRONMENT, Inherit.INDEPENDENT, "allowPermissiveMatch", true, "true"), /** * Globally ignore the "required" attribute of all configurations */ IGNORE_REQUIRED(Option.ENVIRONMENT, Inherit.INDEPENDENT, "ignoreRequired"), /** * Default compatibility level to operate at */ DEFAULT_COMPATIBILITY_LEVEL(Option.ENVIRONMENT, Inherit.INDEPENDENT, "compatLevel"), /** * Behaviour when the maximum defined {@link At#by} value is exceeded in * a mixin. Currently the behaviour is to warn. In later * versions of Mixin this may be promoted to error. * *

Available values for this option are:

* *
*
ignore
*
Pre-0.7 behaviour, no action is taken when a violation is * encountered
*
warn
*
Current behaviour, a WARN-level message is raised for * violations
*
error
*
Violations throw an exception
*
*/ SHIFT_BY_VIOLATION_BEHAVIOUR(Option.ENVIRONMENT, Inherit.INDEPENDENT, "shiftByViolation", "warn"), /** * Behaviour for initialiser injections, current supported options are * "default" and "safe" */ INITIALISER_INJECTION_MODE("initialiserInjectionMode", "default"), /** * Parent for tunable settings */ TUNABLE(Inherit.ALWAYS_FALSE, true, "tunable"), /** * Tunable for the Mixin ClassReader behaviour, setting this option to * true will cause the Mixin ClassReader to read mixin bytecode * with {@link ClassReader#EXPAND_FRAMES} flag which restores the * behaviour from versions 0.8.6 and below, newer versions default to 0. */ CLASSREADER_EXPAND_FRAMES(Option.TUNABLE, Inherit.INDEPENDENT, "classReaderExpandFrames", true, "false"); /** * Type of inheritance for options */ private enum Inherit { /** * If the parent is set, this option will be set too. */ INHERIT, /** * If the parent is set, this option will be set too. However * setting the option explicitly to false will override the * parent value. */ ALLOW_OVERRIDE, /** * This option ignores the value of the parent option, parent is * only used for grouping. */ INDEPENDENT, /** * This option is always false. */ ALWAYS_FALSE } /** * Prefix for mixin options */ private static final String PREFIX = "mixin"; /** * Parent option to this option, if non-null then this option is enabled * if */ final Option parent; /** * Inheritance behaviour for this option */ final Inherit inheritance; /** * Do not print this option in the masthead output */ final boolean isHidden; /** * Java property name */ final String property; /** * Default value for string properties */ final String defaultValue; /** * Whether this property is boolean or not */ final boolean isFlag; /** * Number of parents */ final int depth; private Option(String property) { this(null, property, true); } private Option(Inherit inheritance, String property) { this(null, inheritance, property, true); } private Option(Inherit inheritance, boolean hidden, String property) { this(null, inheritance, hidden, property, true); } private Option(String property, boolean flag) { this(null, property, flag); } private Option(String property, String defaultStringValue) { this(null, Inherit.INDEPENDENT, property, false, defaultStringValue); } private Option(Option parent, String property) { this(parent, Inherit.INHERIT, property, true); } private Option(Option parent, boolean hidden, String property) { this(parent, Inherit.INHERIT, hidden, property, true); } private Option(Option parent, Inherit inheritance, String property) { this(parent, inheritance, property, true); } private Option(Option parent, Inherit inheritance, boolean hidden, String property) { this(parent, inheritance, hidden, property, true); } private Option(Option parent, String property, boolean isFlag) { this(parent, Inherit.INHERIT, property, isFlag, null); } private Option(Option parent, boolean hidden, String property, boolean isFlag) { this(parent, Inherit.INHERIT, hidden, property, isFlag, null); } private Option(Option parent, Inherit inheritance, String property, boolean isFlag) { this(parent, inheritance, property, isFlag, null); } private Option(Option parent, Inherit inheritance, boolean hidden, String property, boolean isFlag) { this(parent, inheritance, hidden, property, isFlag, null); } private Option(Option parent, String property, String defaultStringValue) { this(parent, Inherit.INHERIT, property, false, defaultStringValue); } private Option(Option parent, boolean hidden, String property, String defaultStringValue) { this(parent, Inherit.INHERIT, hidden, property, false, defaultStringValue); } private Option(Option parent, Inherit inheritance, String property, String defaultStringValue) { this(parent, inheritance, property, false, defaultStringValue); } private Option(Option parent, Inherit inheritance, boolean hidden, String property, String defaultStringValue) { this(parent, inheritance, hidden, property, false, defaultStringValue); } private Option(Option parent, Inherit inheritance, String property, boolean isFlag, String defaultStringValue) { this(parent, inheritance, false, property, isFlag, defaultStringValue); } private Option(Option parent, Inherit inheritance, boolean hidden, String property, boolean isFlag, String defaultStringValue) { this.parent = parent; this.inheritance = inheritance; this.isHidden = hidden; this.property = (parent != null ? parent.property : Option.PREFIX) + "." + property; this.defaultValue = defaultStringValue; this.isFlag = isFlag; int depth = 0; for (; parent != null; depth++) { parent = parent.parent; } this.depth = depth; } Option getParent() { return this.parent; } String getProperty() { return this.property; } @Override public String toString() { return this.isFlag ? String.valueOf(this.getBooleanValue()) : this.getStringValue(); } private boolean getLocalBooleanValue(boolean defaultValue) { return Boolean.parseBoolean(System.getProperty(this.property, Boolean.toString(defaultValue))); } private boolean getInheritedBooleanValue() { return this.parent != null && this.parent.getBooleanValue(); } final boolean getBooleanValue() { if (this.inheritance == Inherit.ALWAYS_FALSE) { return false; } boolean local = this.getLocalBooleanValue(false); if (this.inheritance == Inherit.INDEPENDENT) { return local; } boolean inherited = local || this.getInheritedBooleanValue(); return this.inheritance == Inherit.INHERIT ? inherited : this.getLocalBooleanValue(inherited); } final String getStringValue() { return (this.inheritance == Inherit.INDEPENDENT || this.parent == null || this.parent.getBooleanValue()) ? System.getProperty(this.property, this.defaultValue) : this.defaultValue; } > E getEnumValue(E defaultValue) { String value = System.getProperty(this.property, defaultValue.name()); try { return Enum.valueOf(defaultValue.getDeclaringClass(), value.toUpperCase(Locale.ROOT)); } catch (IllegalArgumentException ex) { return defaultValue; } } } /** * Operational compatibility level for the mixin subsystem */ public static enum CompatibilityLevel { /** * Java 6 (1.6) or above is required */ JAVA_6(6, Opcodes.V1_6, 0), /** * Java 7 (1.7) or above is required */ JAVA_7(7, Opcodes.V1_7, 0) { @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_7; } }, /** * Java 8 (1.8) or above is required */ JAVA_8(8, Opcodes.V1_8, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES) { @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_8; } }, /** * Java 9 or above is required */ JAVA_9(9, Opcodes.V9, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES) { @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_9 && ASM.isAtLeastVersion(6); } }, /** * Java 10 or above is required */ JAVA_10(10, Opcodes.V10, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES) { @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_10 && ASM.isAtLeastVersion(6, 1); } }, /** * Java 11 or above is required */ JAVA_11(11, Opcodes.V11, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS) { @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_11 && ASM.isAtLeastVersion(7); } }, /** * Java 12 or above is required */ JAVA_12(12, Opcodes.V12, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS) { @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_12 && ASM.isAtLeastVersion(7); } }, /** * Java 13 or above is required */ JAVA_13(13, Opcodes.V13, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS) { @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_13 && ASM.isAtLeastVersion(7); } }, /** * Java 14 or above is required. Records are a preview feature in this * release. */ JAVA_14(14, Opcodes.V14, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS | LanguageFeatures.RECORDS) { @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_14 && ASM.isAtLeastVersion(8); } }, /** * Java 15 or above is required. Records and sealed classes are preview * features in this release. */ JAVA_15(15, Opcodes.V15, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS | LanguageFeatures.RECORDS | LanguageFeatures.SEALED_CLASSES) { @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_15 && ASM.isAtLeastVersion(9); } }, /** * Java 16 or above is required */ JAVA_16(16, Opcodes.V16, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS | LanguageFeatures.RECORDS | LanguageFeatures.SEALED_CLASSES) { @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_16 && ASM.isAtLeastVersion(9); } }, /** * Java 17 or above is required */ JAVA_17(17, Opcodes.V17, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS | LanguageFeatures.RECORDS | LanguageFeatures.SEALED_CLASSES) { @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_17 && ASM.isAtLeastVersion(9, 1); } }, /** * Java 18 or above is required */ JAVA_18(18, Opcodes.V18, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS | LanguageFeatures.RECORDS | LanguageFeatures.SEALED_CLASSES) { @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_18 && ASM.isAtLeastVersion(9, 2); } }, /** * Java 19 or above is required */ JAVA_19(19, Opcodes.V19, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS | LanguageFeatures.RECORDS | LanguageFeatures.SEALED_CLASSES) { @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_19 && ASM.isAtLeastVersion(9, 3); } }, /** * Java 20 or above is required */ JAVA_20(20, Opcodes.V20, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS | LanguageFeatures.RECORDS | LanguageFeatures.SEALED_CLASSES) { @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_20 && ASM.isAtLeastVersion(9, 4); } }, /** * Java 21 or above is required */ JAVA_21(21, Opcodes.V21, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS | LanguageFeatures.RECORDS | LanguageFeatures.SEALED_CLASSES) { @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_21 && ASM.isAtLeastVersion(9, 5); } }, /** * Java 22 or above is required */ JAVA_22(22, Opcodes.V22, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS | LanguageFeatures.RECORDS | LanguageFeatures.SEALED_CLASSES) { @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_22 && ASM.isAtLeastVersion(9, 6); } }, /** * Java 23 or above is required */ JAVA_23(23, Opcodes.V23, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS | LanguageFeatures.RECORDS | LanguageFeatures.SEALED_CLASSES) { @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_23 && ASM.isAtLeastVersion(9, 7); } }, /** * Java 23 or above is required */ JAVA_24(24, Opcodes.V24, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS | LanguageFeatures.RECORDS | LanguageFeatures.SEALED_CLASSES) { @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_24 && ASM.isAtLeastVersion(9, 7, 1); } }, /** * Java 25 or above is required */ JAVA_25(25, Opcodes.V25, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS | LanguageFeatures.RECORDS | LanguageFeatures.SEALED_CLASSES) { @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_25 && ASM.isAtLeastVersion(9, 8); } }, ; /** * Default compatibility level to use if not specified by the service */ public static CompatibilityLevel DEFAULT = CompatibilityLevel.JAVA_6; /** * Maximum compatibility level actually supported. Other compatibility * levels might exist but we don't actually have any internal code in * place which supports those features. This is mainly used to indicate * that mixin classes compiled with newer JDKs might have bytecode-level * class features that this version of mixin doesn't understand, even * when the current ASM or JRE do. * *

This is particularly important for the case where a config * declares a higher version (eg. JAVA_14) which has been added to the * enum but no code actually exists within Mixin as a library to handle * language features from that version. In other words adding values to * this enum doesn't magically add support for language features, and * this field should point to the highest known supported * version regardless of other known versions.

* *

This comment mainly added to avoid stuff in the future like * PR #500 which demonstrates that the nature of compatibility levels * in mixin are not understood that well.

*/ public static CompatibilityLevel MAX_SUPPORTED = CompatibilityLevel.JAVA_13; private final int ver; private final int classVersion; private final int languageFeatures; private CompatibilityLevel maxCompatibleLevel; private CompatibilityLevel(int ver, int classVersion, int languageFeatures) { this.ver = ver; this.classVersion = classVersion; this.languageFeatures = languageFeatures; } /** * Get whether this compatibility level is supported in the current * environment */ boolean isSupported() { return true; } /** * Class version expected at this compatibility level * * @deprecated Use getClassVersion */ @Deprecated public int classVersion() { return this.classVersion; } /** * Class version expected at this compatibility level */ public int getClassVersion() { return this.classVersion; } /** * Get the major class version expected at this compatibility level */ public int getClassMajorVersion() { return this.classVersion & 0xFFFF; } /** * Get all supported language features */ public int getLanguageFeatures() { return this.languageFeatures; } /** * Get whether this environment supports non-abstract methods in * interfaces, true in Java 1.8 and above * * @deprecated Use {@link #supports(int)} instead */ @Deprecated public boolean supportsMethodsInInterfaces() { return (this.languageFeatures & LanguageFeatures.METHODS_IN_INTERFACES) != 0; } /** * Get whether the specified {@link LanguageFeatures} is supported by * this runtime. * * @param languageFeatures language feature (or features) to check * @return true if all specified language features are supported */ public boolean supports(int languageFeatures) { return (this.languageFeatures & languageFeatures) == languageFeatures; } /** * Get whether this level is the same or greater than the specified * level * * @param level level to compare to * @return true if this level is equal or higher the supplied level */ public boolean isAtLeast(CompatibilityLevel level) { return level == null || this.ver >= level.ver; } /** * Get whether this level is less than the specified level * * @param level level to compare to * @return true if this level is less than the supplied level */ public boolean isLessThan(CompatibilityLevel level) { return level == null || this.ver < level.ver; } /** * Get whether this level can be elevated to the specified level * * @param level desired level * @return true if this level supports elevation */ public boolean canElevateTo(CompatibilityLevel level) { if (level == null || this.maxCompatibleLevel == null) { return true; } return level.ver <= this.maxCompatibleLevel.ver; } /** * True if this level can support the specified level * * @param level desired level * @return true if the other level can be elevated to this level */ public boolean canSupport(CompatibilityLevel level) { if (level == null) { return true; } return level.canElevateTo(this); } /** * Return the minimum language level required to support the specified * language feature(s). Returns null if no compatibility level * available can support the requested language features. * * @param languageFeatures Language feature(s) to check for * @return Lowest compatibility level which supports the requested * language feature, or null if no levels support the requested * feature */ public static CompatibilityLevel requiredFor(int languageFeatures) { for (CompatibilityLevel level : CompatibilityLevel.values()) { if (level.supports(languageFeatures)) { return level; } } return null; } /** * Return the maximum compatibility level which is actually effective in * the current runtime, taking into account the current JRE and ASM * versions */ public static CompatibilityLevel getMaxEffective() { CompatibilityLevel max = CompatibilityLevel.JAVA_6; for (CompatibilityLevel level : CompatibilityLevel.values()) { if (level.isSupported()) { max = level; } if (level == CompatibilityLevel.MAX_SUPPORTED) { break; } } return max; } static String getSupportedVersions() { StringBuilder sb = new StringBuilder(); boolean comma = false; int rangeStart = 0, rangeEnd = 0; for (CompatibilityLevel level : CompatibilityLevel.values()) { if (level.isSupported()) { if (level.ver == rangeEnd + 1) { rangeEnd = level.ver; } else { if (rangeStart > 0) { sb.append(comma ? "," : "").append(rangeStart); if (rangeEnd > rangeStart) { sb.append(rangeEnd > rangeStart + 1 ? '-' : ',').append(rangeEnd); } comma = true; rangeStart = rangeEnd = level.ver; } rangeStart = rangeEnd = level.ver; } } } if (rangeStart > 0) { sb.append(comma ? "," : "").append(rangeStart); if (rangeEnd > rangeStart) { sb.append(rangeEnd > rangeStart + 1 ? '-' : ',').append(rangeEnd); } } return sb.toString(); } public static CompatibilityLevel forClassVersion(int version) { CompatibilityLevel latest = null; for (CompatibilityLevel level : values()) { if (level.getClassVersion() >= version) { return level; } latest = level; } return latest; } } /** * Mixin features which can be specified in mixin configs as required for * the config to be valid. No support for backward compatibility but should * help in the future to allow mixin configs to specify the features they * require rather than relying purely on minVersion. Only applies to * features added in version 0.8.6 and higher. */ public static enum Feature { /** * Supports the unsafe flag on @At annotations to * facilitate hassle-free constructor injections. */ UNSAFE_INJECTION(true), /** * Support for the use of injector annotations in interface mixins */ INJECTORS_IN_INTERFACE_MIXINS { @Override public boolean isAvailable() { return CompatibilityLevel.getMaxEffective().supports(LanguageFeatures.METHODS_IN_INTERFACES); } @Override public boolean isEnabled() { return MixinEnvironment.getCompatibilityLevel().supports(LanguageFeatures.METHODS_IN_INTERFACES); } }; /** * Existence of the enum constant does not necessarily indicate that the * feature is actually supported by this version, for example if * features can not be supported by future or previous JVM versions and * need to be selectively enabled. This also allows us to add flags in * the future to disable certain features globally for testing reasons. */ private boolean enabled; private Feature() { this(false); } private Feature(boolean enabled) { this.enabled = enabled; } /** * Get whether this feature is available in the current runtime * environment */ public boolean isAvailable() { return true; } /** * Get whether this feature is supported in the current environment and * compatibility level */ public boolean isEnabled() { return this.isAvailable() && this.enabled; } /** * Convenience function which returns a Feature constant based on the * feature id, but returns null instead of throwing an exception. * * @param featureId Feature ID (enum constant name) to check for * @return Feature or null */ public static Feature get(String featureId) { if (featureId == null) { return null; } try { return Feature.valueOf(featureId); } catch (IllegalArgumentException ex) { return null; } } /** * Check whether a particular feature exists in this mixin version, even * if it's not currently available * * @param featureId Feature ID (enum constant name) to check for * @return true if the feature exists */ public static boolean exists(String featureId) { return Feature.get(featureId) != null; } /** * Check whether a particular feature is available and enabled * * @param featureId Feature ID (enum constant name) to check for * @return true if the feature is currently available */ public static boolean isActive(String featureId) { Feature feature = Feature.get(featureId); return feature != null && feature.isEnabled(); } } /** * Wrapper for providing a natural sorting order for providers */ static class TokenProviderWrapper implements Comparable { private static int nextOrder = 0; private final int priority, order; private final IEnvironmentTokenProvider provider; private final MixinEnvironment environment; public TokenProviderWrapper(IEnvironmentTokenProvider provider, MixinEnvironment environment) { this.provider = provider; this.environment = environment; this.order = TokenProviderWrapper.nextOrder++; this.priority = provider.getPriority(); } @Override public int compareTo(TokenProviderWrapper other) { if (other == null) { return 0; } if (other.priority == this.priority) { return other.order - this.order; } return (other.priority - this.priority); } public IEnvironmentTokenProvider getProvider() { return this.provider; } Integer getToken(String token) { return this.provider.getToken(token, this.environment); } } /** * Phase setter callback delegate */ static class PhaseConsumer implements IConsumer { @Override public void accept(Phase phase) { MixinEnvironment.gotoPhase(phase); } } /** * Currently active environment */ private static MixinEnvironment currentEnvironment; /** * Current (active) environment phase, set to NOT_INITIALISED until the * phases have been populated */ private static Phase currentPhase = Phase.NOT_INITIALISED; /** * Current compatibility level */ private static CompatibilityLevel compatibility; /** * Show debug header info on first environment construction */ private static boolean showHeader = true; /** * Logger */ private static final ILogger logger = MixinService.getService().getLogger("mixin"); /** * Active transformer */ private static IMixinTransformer transformer; /** * Service */ private final IMixinService service; /** * The phase for this environment */ private final Phase phase; /** * The blackboard key for this environment's configs */ private final Keys configsKey; /** * This environment's options */ private final boolean[] options; /** * List of token provider classes */ private final Set tokenProviderClasses = new HashSet(); /** * List of token providers in this environment */ private final List tokenProviders = new ArrayList(); /** * Internal tokens defined by this environment */ private final Map internalTokens = new HashMap(); /** * Remappers for this environment */ private final RemapperChain remappers = new RemapperChain(); /** * Detected side */ private Side side; /** * Obfuscation context (refmap key to use in this environment) */ private String obfuscationContext = null; MixinEnvironment(Phase phase) { this.service = MixinService.getService(); this.phase = phase; this.configsKey = Keys.of(GlobalProperties.Keys.CONFIGS + "." + this.phase.name.toLowerCase(Locale.ROOT)); // Sanity check Object version = this.getVersion(); if (version == null || !MixinBootstrap.VERSION.equals(version)) { throw new MixinException("Environment conflict, mismatched versions or you didn't call MixinBootstrap.init()"); } // More sanity check this.service.checkEnv(this); this.options = new boolean[Option.values().length]; for (Option option : Option.values()) { this.options[option.ordinal()] = option.getBooleanValue(); } if (MixinEnvironment.showHeader) { MixinEnvironment.showHeader = false; this.printHeader(version); } } private void printHeader(Object version) { String codeSource = this.getCodeSource(); String serviceName = this.service.getName(); Side side = this.getSide(); MixinEnvironment.logger.info("SpongePowered MIXIN Subsystem Version={} Source={} Service={} Env={}", version, codeSource, serviceName, side); boolean verbose = this.getOption(Option.DEBUG_VERBOSE); if (verbose || this.getOption(Option.DEBUG_EXPORT) || this.getOption(Option.DEBUG_PROFILER)) { PrettyPrinter printer = new PrettyPrinter(32); printer.add("SpongePowered MIXIN%s", verbose ? " (Verbose debugging enabled)" : "").centre().hr(); printer.kv("Code source", codeSource); printer.kv("Internal Version", version); printer.kv("Java Version", "%s (supports compatibility %s)", JavaVersion.current(), CompatibilityLevel.getSupportedVersions()); printer.kv("Default Compatibility Level", MixinEnvironment.getCompatibilityLevel()); printer.kv("Max Effective Compatibility Level", CompatibilityLevel.getMaxEffective()); printer.kv("Detected ASM Version", ASM.getVersionString()); printer.kv("Detected ASM Supports Java", ASM.getClassVersionString()).hr(); printer.kv("Service Name", serviceName); printer.kv("Mixin Service Class", this.service.getClass().getName()); printer.kv("Global Property Service Class", MixinService.getGlobalPropertyService().getClass().getName()); printer.kv("Logger Adapter Type", MixinService.getService().getLogger("mixin").getType()).hr(); for (Option option : Option.values()) { if (option.isHidden) { continue; } StringBuilder indent = new StringBuilder(); for (int i = 0; i < option.depth; i++) { indent.append("- "); } printer.kv(option.property, "%s<%s>", indent, option); } printer.hr(); for (Feature feature : Feature.values()) { printer.kv(feature.name(), "available=<%s> enabled=<%s>", feature.isAvailable(), feature.isEnabled()); } printer.hr().kv("Detected Side", side); printer.print(System.err); } } private String getCodeSource() { try { return this.getClass().getProtectionDomain().getCodeSource().getLocation().toString(); } catch (Throwable th) { return "Unknown"; } } /** * Get logging level info/debug based on verbose setting */ private Level getVerboseLoggingLevel() { return this.getOption(Option.DEBUG_VERBOSE) ? Level.INFO : Level.DEBUG; } /** * Get the phase for this environment * * @return the phase */ public Phase getPhase() { return this.phase; } /** * Get mixin configurations from the blackboard * * @return list of registered mixin configs * @deprecated no replacement */ @Deprecated public List getMixinConfigs() { List mixinConfigs = GlobalProperties.>get(this.configsKey); if (mixinConfigs == null) { mixinConfigs = new ArrayList(); GlobalProperties.put(this.configsKey, mixinConfigs); } return mixinConfigs; } /** * Add a mixin configuration to the blackboard * * @param config Name of configuration resource to add * @return fluent interface * @deprecated use Mixins::addConfiguration instead */ @Deprecated public MixinEnvironment addConfiguration(String config) { MixinEnvironment.logger.warn("MixinEnvironment::addConfiguration is deprecated and will be removed. Use Mixins::addConfiguration instead!"); Mixins.addConfiguration(config, this); return this; } void registerConfig(String config) { List configs = this.getMixinConfigs(); if (!configs.contains(config)) { configs.add(config); } } /** * Add a new token provider class to this environment * * @param providerName Class name of the token provider to add * @return fluent interface */ public MixinEnvironment registerTokenProviderClass(String providerName) { if (!this.tokenProviderClasses.contains(providerName)) { try { @SuppressWarnings("unchecked") Class providerClass = (Class)this.service.getClassProvider().findClass(providerName, true); IEnvironmentTokenProvider provider = providerClass.getDeclaredConstructor().newInstance(); this.registerTokenProvider(provider); } catch (Throwable th) { MixinEnvironment.logger.error("Error instantiating " + providerName, th); } } return this; } /** * Add a new token provider to this environment * * @param provider Token provider to add * @return fluent interface */ public MixinEnvironment registerTokenProvider(IEnvironmentTokenProvider provider) { if (provider != null && !this.tokenProviderClasses.contains(provider.getClass().getName())) { String providerName = provider.getClass().getName(); TokenProviderWrapper wrapper = new TokenProviderWrapper(provider, this); MixinEnvironment.logger.log(this.getVerboseLoggingLevel(), "Adding new token provider {} to {}", providerName, this); this.tokenProviders.add(wrapper); this.tokenProviderClasses.add(providerName); Collections.sort(this.tokenProviders); } return this; } /** * Get a token value from this environment * * @param token Token to fetch * @return token value or null if the token is not present in the * environment */ @Override public Integer getToken(String token) { token = token.toUpperCase(Locale.ROOT); for (TokenProviderWrapper provider : this.tokenProviders) { Integer value = provider.getToken(token); if (value != null) { return value; } } return this.internalTokens.get(token); } /** * Get all registered error handlers for this environment * * @return set of error handler class names * @deprecated use Mixins::getErrorHandlerClasses */ @Deprecated public Set getErrorHandlerClasses() { return Mixins.getErrorHandlerClasses(); } /** * Get the active mixin transformer instance (if any) * * @return active mixin transformer instance */ public Object getActiveTransformer() { return MixinEnvironment.transformer; } /** * Set the mixin transformer instance * * @param transformer Mixin Transformer */ public void setActiveTransformer(IMixinTransformer transformer) { if (transformer != null) { MixinEnvironment.transformer = transformer; } } /** * Allows a third party to set the side if the side is currently UNKNOWN * * @param side Side to set to * @return fluent interface */ public MixinEnvironment setSide(Side side) { if (side != null && this.getSide() == Side.UNKNOWN && side != Side.UNKNOWN) { this.side = side; } return this; } /** * Get (and detect if necessary) the current side * * @return current side (or UNKNOWN if could not be determined) */ public Side getSide() { if (this.side == null) { for (Side side : Side.values()) { if (side.detect()) { this.side = side; break; } } } return this.side != null ? this.side : Side.UNKNOWN; } /** * Get the current mixin subsystem version * * @return current version */ public String getVersion() { return GlobalProperties.get(GlobalProperties.Keys.INIT); } /** * Get the specified option from the current environment * * @param option Option to get * @return Option value */ public boolean getOption(Option option) { return this.options[option.ordinal()]; } /** * Set the specified option for this environment * * @param option Option to set * @param value New option value */ public void setOption(Option option, boolean value) { this.options[option.ordinal()] = value; } /** * Get the specified option from the current environment * * @param option Option to get * @return Option value */ public String getOptionValue(Option option) { return option.getStringValue(); } /** * Get the specified option from the current environment * * @param option Option to get * @param defaultValue value to use if the user-defined value is invalid * @param enum type * @return Option value */ public > E getOption(Option option, E defaultValue) { return option.getEnumValue(defaultValue); } /** * Set the obfuscation context * * @param context new context */ public void setObfuscationContext(String context) { this.obfuscationContext = context; } /** * Get the current obfuscation context */ public String getObfuscationContext() { return this.obfuscationContext; } /** * Get the current obfuscation context */ public String getRefmapObfuscationContext() { String overrideObfuscationType = Option.OBFUSCATION_TYPE.getStringValue(); if (overrideObfuscationType != null) { return overrideObfuscationType; } return this.obfuscationContext; } /** * Get the remapper chain for this environment */ public RemapperChain getRemappers() { return this.remappers; } /** * Invoke a mixin environment audit process */ public void audit() { Object activeTransformer = this.getActiveTransformer(); if (activeTransformer instanceof IMixinTransformer) { ((IMixinTransformer)activeTransformer).audit(this); } } /** * Returns (and generates if necessary) the transformer delegation list for * this environment. * * @return current transformer delegation list (read-only) * @deprecated Do not use this method */ @Deprecated public List getTransformers() { MixinEnvironment.logger.warn("MixinEnvironment::getTransformers is deprecated!"); ITransformerProvider transformers = this.service.getTransformerProvider(); return transformers != null ? (List)transformers.getTransformers() : Collections.emptyList(); } /** * Adds a transformer to the transformer exclusions list * * @param name Class transformer exclusion to add * @deprecated Do not use this method */ @Deprecated public void addTransformerExclusion(String name) { MixinEnvironment.logger.warn("MixinEnvironment::addTransformerExclusion is deprecated!"); ITransformerProvider transformers = this.service.getTransformerProvider(); if (transformers != null) { transformers.addTransformerExclusion(name); } } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return String.format("%s[%s]", this.getClass().getSimpleName(), this.phase); } /** * Get the current phase, triggers initialisation if necessary */ private static Phase getCurrentPhase() { if (MixinEnvironment.currentPhase == Phase.NOT_INITIALISED) { MixinEnvironment.init(Phase.PREINIT); } return MixinEnvironment.currentPhase; } /** * Initialise the mixin environment in the specified phase * * @param phase initial phase */ @SuppressWarnings("deprecation") public static void init(Phase phase) { if (MixinEnvironment.currentPhase == Phase.NOT_INITIALISED) { MixinEnvironment.currentPhase = phase; MixinEnvironment env = MixinEnvironment.getEnvironment(phase); Profiler.setActive(env.getOption(Option.DEBUG_PROFILER)); // AMS - Temp wiring to avoid merging multiphase IMixinService service = MixinService.getService(); if (service instanceof MixinServiceAbstract) { ((MixinServiceAbstract)service).wire(phase, new PhaseConsumer()); } } } /** * Get the mixin environment for the specified phase * * @param phase phase to fetch environment for * @return the environment */ public static MixinEnvironment getEnvironment(Phase phase) { if (phase == null) { return Phase.DEFAULT.getEnvironment(); } return phase.getEnvironment(); } /** * Gets the default environment * * @return the {@link Phase#DEFAULT DEFAULT} environment */ public static MixinEnvironment getDefaultEnvironment() { return MixinEnvironment.getEnvironment(Phase.DEFAULT); } /** * Gets the current environment * * @return the currently active environment */ public static MixinEnvironment getCurrentEnvironment() { if (MixinEnvironment.currentEnvironment == null) { MixinEnvironment.currentEnvironment = MixinEnvironment.getEnvironment(MixinEnvironment.getCurrentPhase()); } return MixinEnvironment.currentEnvironment; } /** * Get the current compatibility level */ public static CompatibilityLevel getCompatibilityLevel() { if (MixinEnvironment.compatibility == null) { CompatibilityLevel minLevel = MixinEnvironment.getMinCompatibilityLevel(); CompatibilityLevel optionLevel = Option.DEFAULT_COMPATIBILITY_LEVEL.getEnumValue(minLevel); MixinEnvironment.compatibility = optionLevel.isAtLeast(minLevel) ? optionLevel : minLevel; } return MixinEnvironment.compatibility; } /** * Get the minimum (default) compatibility level supported by the current * service */ public static CompatibilityLevel getMinCompatibilityLevel() { CompatibilityLevel minLevel = MixinService.getService().getMinCompatibilityLevel(); return minLevel == null ? CompatibilityLevel.DEFAULT : minLevel; } /** * Set desired compatibility level for the entire environment * * @param level Level to set, ignored if less than the current level * @throws IllegalArgumentException if the specified level is not supported * @deprecated set compatibility level in configuration */ @Deprecated public static void setCompatibilityLevel(CompatibilityLevel level) throws IllegalArgumentException { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); if (!"org.spongepowered.asm.mixin.transformer.MixinConfig".equals(stackTrace[2].getClassName())) { MixinEnvironment.logger.warn("MixinEnvironment::setCompatibilityLevel is deprecated and will be removed. Set level via config instead!"); } CompatibilityLevel currentLevel = MixinEnvironment.getCompatibilityLevel(); if (level != currentLevel && level.isAtLeast(currentLevel)) { if (!level.isSupported()) { throw new IllegalArgumentException(String.format( "The requested compatibility level %s could not be set. Level is not supported by the active JRE or ASM version (Java %s, %s)", level, JavaVersion.current(), ASM.getVersionString() )); } IMixinService service = MixinService.getService(); CompatibilityLevel maxLevel = service.getMaxCompatibilityLevel(); if (maxLevel != null && maxLevel.isLessThan(level)) { MixinEnvironment.logger.warn("The requested compatibility level {} is higher than the level supported by the active subsystem '{}'" + " which supports {}. This is not a supported configuration and instability may occur.", level, service.getName(), maxLevel); } MixinEnvironment.compatibility = level; MixinEnvironment.logger.info("Compatibility level set to {}", level); } } /** * Get the performance profiler * * @return profiler * @deprecated use Profiler.getProfiler("mixin") */ @Deprecated public static Profiler getProfiler() { return Profiler.getProfiler("mixin"); } /** * Internal callback * * @param phase phase to go to */ @SuppressWarnings("deprecation") static void gotoPhase(Phase phase) { if (phase == null || phase.ordinal < 0) { throw new IllegalArgumentException("Cannot go to the specified phase, phase is null or invalid"); } IMixinService service = MixinService.getService(); if (phase.ordinal > MixinEnvironment.getCurrentPhase().ordinal) { service.beginPhase(); } MixinEnvironment.currentPhase = phase; MixinEnvironment.currentEnvironment = MixinEnvironment.getEnvironment(MixinEnvironment.getCurrentPhase()); // AMS - Temp wiring to avoid merging multiphase if (service instanceof MixinServiceAbstract && phase == Phase.DEFAULT) { ((MixinServiceAbstract)service).unwire(); } } }