diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index cbb6cfd46..17d333b89 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -5,12 +5,12 @@ jobs:
strategy:
fail-fast: false
matrix:
- java: [17-jdk, 21-jdk]
+ java: [21-jdk]
runs-on: ubuntu-24.04
container:
image: eclipse-temurin:${{ matrix.java }}
options: --user root
steps:
- uses: actions/checkout@v4
- - uses: gradle/wrapper-validation-action@v2
+ - uses: gradle/actions/wrapper-validation@v4
- run: ./gradlew build publishToMavenLocal --stacktrace --warning-mode fail
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index de6e243fe..400278fc0 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -8,7 +8,7 @@ jobs:
options: --user root
steps:
- uses: actions/checkout@v4
- - uses: gradle/wrapper-validation-action@v2
+ - uses: gradle/actions/wrapper-validation@v4
- run: ./gradlew build publish --stacktrace
env:
MAVEN_URL: ${{ secrets.MAVEN_URL }}
diff --git a/HEADER.txt b/HEADER.txt
index 3f213880a..08c1f7a11 100644
--- a/HEADER.txt
+++ b/HEADER.txt
@@ -1,22 +1,24 @@
-This file is part of ${name}, licensed under the MIT License (MIT).
-
-Copyright (c) ${organization} <${url}>
-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.
\ No newline at end of file
+/*
+ * 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.
+ */
diff --git a/build.gradle b/build.gradle
index 47d4c7df7..1da302e1d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,21 +5,20 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'gradle.plugin.com.hierynomus.gradle.plugins:license-gradle-plugin:0.16.1'
classpath 'com.guardsquare:proguard-gradle:' + (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_11) ? '7.5.0' : '7.1.0')
}
}
plugins {
- //id "me.modmuss50.remotesign" version "0.4.0"
- id "io.github.goooler.shadow" version "8.1.7"
+ // id "me.modmuss50.remotesign" version "0.5.0"
+ id "com.gradleup.shadow" version "9.0.0-beta17"
+ id "dev.lukebemish.central-portal-publishing" version "0.1.7"
}
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
// Apply plugin
apply plugin: 'java'
-apply plugin: 'com.github.hierynomus.license'
apply plugin: 'checkstyle'
apply plugin: 'maven-publish'
apply plugin: 'eclipse'
@@ -27,7 +26,7 @@ apply plugin: 'idea'
apply plugin: 'potemkin-modules'
// Default tasks
-defaultTasks 'licenseFormat', 'check', 'build'
+defaultTasks 'check', 'build'
// Basic project information
group = 'io.github.legacymoddingmc'
@@ -298,22 +297,13 @@ processResources {
}
// License header formatting
-license {
- ext {
- name = "Mixin"
- organization = "SpongePowered"
- url = "https://www.spongepowered.org"
- }
- include '**/*.java'
- header file("HEADER.txt")
- sourceSets = project.sourceSets
- ignoreFailures false
- strictCheck true
- mapping {
- java = 'SLASHSTAR_STYLE'
- }
+def validateHeaders = tasks.register("validateHeaders", org.spongepowered.asm.mixin.build.ValidateLicenseHeadersTasks) {
+ header = resources.text.fromFile("HEADER.txt")
+ source = sourceSets.collect { it.allJava }
}
+tasks.check.dependsOn validateHeaders
+
checkstyle {
configProperties = [
"name" : project.name,
@@ -353,6 +343,7 @@ project.sourceSets.each { set -> {
compileModularityJava {
inputs.files(modularityInputs)
+ options.javaModuleVersion = project.version as String
doFirst {
options.compilerArgs = [
'--module-path', classpath.asPath,
@@ -457,15 +448,23 @@ ext.excludePomDeps = [
'junit',
'mockito-core',
'mixin-asm-debug-all',
- 'log4j-core'
+ 'log4j-core',
+ 'gson'
]
+centralPortalPublishing.bundle('mixin') {
+ username = ENV.MAVEN_CENTRAL_USERNAME
+ password = ENV.MAVEN_CENTRAL_PASSWORD
+}
+
+tasks.publish.dependsOn tasks.named("publishMixinCentralPortalBundle")
+
publishing {
publications {
developer(MavenPublication) { publication ->
- groupId project.group
- artifactId project.base.archivesName.get()
- version project.version
+ groupId = project.group
+ artifactId = project.base.archivesName.get()
+ version = project.version
artifact sourceJar
artifact javadocJar
@@ -547,24 +546,15 @@ publishing {
repositories {
if (ENV.MAVEN_URL) {
maven {
- url ENV.MAVEN_URL
+ url = ENV.MAVEN_URL
credentials {
- username ENV.MAVEN_USERNAME
- password ENV.MAVEN_PASSWORD
+ username = ENV.MAVEN_USERNAME
+ password = ENV.MAVEN_PASSWORD
}
}
}
- if (ENV.MAVEN_CENTRAL_URL) {
- repositories.maven {
- name "central"
- url ENV.MAVEN_CENTRAL_URL
- credentials {
- username ENV.MAVEN_CENTRAL_USERNAME
- password ENV.MAVEN_CENTRAL_PASSWORD
- }
- }
- }
+ centralPortalPublishing.portalBundle(':', 'mixin')
}
}
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index e7001a077..6450ea907 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -4,8 +4,8 @@ plugins {
repositories {
maven {
- name 'sponge'
- url 'https://repo.spongepowered.org/repository/maven-public'
+ name = 'sponge'
+ url = 'https://repo.spongepowered.org/repository/maven-public'
}
}
diff --git a/buildSrc/src/main/groovy/org/spongepowered/asm/mixin/build/ValidateLicenseHeadersTasks.groovy b/buildSrc/src/main/groovy/org/spongepowered/asm/mixin/build/ValidateLicenseHeadersTasks.groovy
new file mode 100644
index 000000000..2b30598c5
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/spongepowered/asm/mixin/build/ValidateLicenseHeadersTasks.groovy
@@ -0,0 +1,56 @@
+/*
+ * 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.build
+
+import org.gradle.api.resources.TextResource
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.SourceTask
+import org.gradle.api.tasks.TaskAction
+
+abstract class ValidateLicenseHeadersTasks extends SourceTask {
+ @Input
+ TextResource header
+
+ @TaskAction
+ void run() {
+ def headerStr = header.asString()
+ def javaSources = getSource().matching(pattern -> pattern.include("**/*.java")).files
+
+ def failedFiles = []
+
+ for (def source : javaSources) {
+ if (!source.text.startsWith(headerStr)) {
+ failedFiles.add(source.name)
+ logger.warn("File {} does not have the correct license header", source.path)
+ }
+ }
+
+ if (!failedFiles.isEmpty()) {
+ throw new RuntimeException("License header validation failed for the following files:\n" +
+ failedFiles.join("\n"))
+ }
+ }
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index e6441136f..1b33c55ba 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index a4413138c..ff23a68d7 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
index b740cf133..23d15a936 100755
--- a/gradlew
+++ b/gradlew
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+# SPDX-License-Identifier: Apache-2.0
+#
##############################################################################
#
@@ -84,7 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
-APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -112,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -203,7 +205,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
-# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
@@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
- org.gradle.wrapper.GradleWrapperMain \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
diff --git a/gradlew.bat b/gradlew.bat
index 25da30dbd..db3a6ac20 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -68,11 +70,11 @@ goto fail
:execute
@rem Setup the command line
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+set CLASSPATH=
@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
diff --git a/src/agent/java/org/spongepowered/tools/agent/MixinAgent.java b/src/agent/java/org/spongepowered/tools/agent/MixinAgent.java
index 7821ded06..82e9d8727 100644
--- a/src/agent/java/org/spongepowered/tools/agent/MixinAgent.java
+++ b/src/agent/java/org/spongepowered/tools/agent/MixinAgent.java
@@ -78,7 +78,7 @@ public byte[] transform(ClassLoader loader, String className, Class> classBein
try {
MixinAgent.log(Level.INFO, "Redefining class {}", className);
- return MixinAgent.this.classTransformer.transformClassBytes(null, className, classfileBuffer);
+ return MixinAgent.this.classTransformer.transformClassBytes(null, className.replace('/', '.'), classfileBuffer);
} catch (Throwable th) {
MixinAgent.log(Level.ERROR, "Error while re-transforming class {}", className, th);
return MixinAgent.ERROR_BYTECODE;
diff --git a/src/main/java/org/spongepowered/asm/mixin/FabricUtil.java b/src/main/java/org/spongepowered/asm/mixin/FabricUtil.java
index f68c3ed3f..5d800b6b2 100644
--- a/src/main/java/org/spongepowered/asm/mixin/FabricUtil.java
+++ b/src/main/java/org/spongepowered/asm/mixin/FabricUtil.java
@@ -49,10 +49,20 @@ public final class FabricUtil {
*/
public static final int COMPATIBILITY_0_14_0 = 14000; // 0.14.0+mixin.0.8.6
+ /**
+ * Fabric compatibility version 0.16.5
+ */
+ public static final int COMPATIBILITY_0_16_5 = 16005; // 0.16.5+mixin.0.8.7
+
+ /**
+ * Fabric compatibility version 0.17.0
+ */
+ public static final int COMPATIBILITY_0_17_0 = 17000; // 0.17.0+mixin.0.8.7
+
/**
* Latest compatibility version
*/
- public static final int COMPATIBILITY_LATEST = COMPATIBILITY_0_14_0;
+ public static final int COMPATIBILITY_LATEST = COMPATIBILITY_0_17_0;
public static String getModId(IMixinConfig config) {
return getModId(config, "(unknown)");
diff --git a/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java b/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java
index d9f2e913d..d2c282276 100644
--- a/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java
+++ b/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java
@@ -625,11 +625,10 @@ final String getStringValue() {
? System.getProperty(this.property, this.defaultValue) : this.defaultValue;
}
- @SuppressWarnings("unchecked")
> E getEnumValue(E defaultValue) {
String value = System.getProperty(this.property, defaultValue.name());
try {
- return (E)Enum.valueOf(defaultValue.getClass(), value.toUpperCase(Locale.ROOT));
+ return Enum.valueOf(defaultValue.getDeclaringClass(), value.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException ex) {
return defaultValue;
}
@@ -861,6 +860,48 @@ boolean isSupported() {
return JavaVersion.current() >= JavaVersion.JAVA_22 && ASM.getMaxSupportedClassVersion() >= 66;
}
+ },
+
+ /**
+ * 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);
+ }
+
},
;
diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java b/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java
index e87f3e871..200794f97 100644
--- a/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java
+++ b/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java
@@ -45,6 +45,7 @@
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodNode;
+import org.spongepowered.asm.mixin.FabricUtil;
import org.spongepowered.asm.mixin.MixinEnvironment;
import org.spongepowered.asm.mixin.MixinEnvironment.Option;
import org.spongepowered.asm.mixin.injection.callback.CallbackInjector;
@@ -552,14 +553,20 @@ static final class Shift extends InjectionPoint {
private final InjectionPoint input;
private final int shift;
+ private final boolean respectSpecifier;
public Shift(InjectionPoint input, int shift) {
+ this(input, shift, FabricUtil.COMPATIBILITY_LATEST);
+ }
+
+ public Shift(InjectionPoint input, int shift, int fabricCompatibility) {
if (input == null) {
throw new IllegalArgumentException("Must supply an input injection point for SHIFT");
}
this.input = input;
this.shift = shift;
+ this.respectSpecifier = fabricCompatibility >= FabricUtil.COMPATIBILITY_0_16_5;
}
/* (non-Javadoc)
@@ -606,6 +613,11 @@ public boolean find(String desc, InsnList insns, Collection no
return nodes.size() > 0;
}
+
+ @Override
+ public Specifier getSpecifier(Specifier defaultSpecifier) {
+ return this.respectSpecifier ? this.input.getSpecifier(defaultSpecifier) : super.getSpecifier(defaultSpecifier);
+ }
}
/**
@@ -871,15 +883,17 @@ private static InjectionPoint create(IMixinContext context, InjectionPointData d
private static InjectionPoint shift(IInjectionPointContext context, InjectionPoint point,
At.Shift shift, int by) {
+
+ int fabricCompatibility = FabricUtil.getCompatibility(context);
if (point != null) {
if (shift == At.Shift.BEFORE) {
- return InjectionPoint.before(point);
+ return new InjectionPoint.Shift(point, -1, fabricCompatibility);
} else if (shift == At.Shift.AFTER) {
- return InjectionPoint.after(point);
+ return new InjectionPoint.Shift(point, 1, fabricCompatibility);
} else if (shift == At.Shift.BY) {
InjectionPoint.validateByValue(context.getMixin(), context.getMethod(), context.getAnnotationNode(), point, by);
- return InjectionPoint.shift(point, by);
+ return new InjectionPoint.Shift(point, by, fabricCompatibility);
}
}
diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java
index 6e813add7..273dac285 100644
--- a/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java
+++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java
@@ -446,9 +446,11 @@ protected AbstractInsnNode invokeHandler(InsnList insns) {
* @return injected insn node
*/
protected AbstractInsnNode invokeHandler(InsnList insns, MethodNode handler) {
- boolean isInterface = Bytecode.hasFlag(classNode, Opcodes.ACC_INTERFACE);
- boolean isPrivate = (handler.access & Opcodes.ACC_PRIVATE) != 0;
- int invokeOpcode = this.isStatic ? Opcodes.INVOKESTATIC : isInterface ? Opcodes.INVOKEINTERFACE : isPrivate ? Opcodes.INVOKESPECIAL : Opcodes.INVOKEVIRTUAL;
+ boolean isPrivate = Bytecode.hasFlag(handler, Opcodes.ACC_PRIVATE);
+ boolean isSynthetic = Bytecode.hasFlag(handler, Opcodes.ACC_SYNTHETIC);
+ int invokeOpcode = this.isStatic ? Opcodes.INVOKESTATIC :
+ this.isInterface ? (isSynthetic && isPrivate ? Opcodes.INVOKESPECIAL : Opcodes.INVOKEINTERFACE) :
+ isPrivate ? Opcodes.INVOKESPECIAL : Opcodes.INVOKEVIRTUAL;
MethodInsnNode insn = new MethodInsnNode(invokeOpcode, this.classNode.name, handler.name, handler.desc, isInterface);
insns.add(insn);
this.info.addCallbackInvocation(handler);
diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/modify/LocalVariableDiscriminator.java b/src/main/java/org/spongepowered/asm/mixin/injection/modify/LocalVariableDiscriminator.java
index 676665b31..5438c8c6f 100644
--- a/src/main/java/org/spongepowered/asm/mixin/injection/modify/LocalVariableDiscriminator.java
+++ b/src/main/java/org/spongepowered/asm/mixin/injection/modify/LocalVariableDiscriminator.java
@@ -153,30 +153,30 @@ private Local[] initLocals(Target target, boolean argsOnly, AbstractInsnNode nod
if (!argsOnly) {
LocalVariableNode[] locals = Locals.getLocalsAt(target.classNode, target.method, node, org.spongepowered.asm.mixin.FabricUtil.getCompatibility(info));
if (locals != null) {
- Local[] lvt = new Local[locals.length];
- for (int l = 0; l < locals.length; l++) {
- if (locals[l] != null) {
- lvt[l] = new Local(locals[l].name, Type.getType(locals[l].desc));
- }
- }
- return lvt;
+ return getLocals(locals);
}
}
-
- Local[] lvt = new Local[this.baseArgIndex + Bytecode.getArgsSize(target.arguments)];
- if (!this.isStatic) {
- lvt[0] = new Local("this", Type.getObjectType(target.classNode.name));
- }
- for (int local = this.baseArgIndex, arg = 0; local < lvt.length; local++) {
- Type argType = target.arguments[arg++];
- lvt[local] = new Local("arg" + local, argType);
- if (argType.getSize() == 2) {
- lvt[++local] = null;
+
+ int fabricCompatibility = org.spongepowered.asm.mixin.FabricUtil.getCompatibility(info);
+ // Before 0.17.0, the names of arg variables were inconsistent. With argsOnly = true, they were "arg" + lvIndex
+ // whereas for argsOnly = false, they were "arg" + paramIndex. We pick the latter always now which is the slightly
+ // saner option, but we need to pick the former here when backwards compat is needed.
+ boolean fallbackToLvIndex = fabricCompatibility < org.spongepowered.asm.mixin.FabricUtil.COMPATIBILITY_0_17_0;
+ LocalVariableNode[] initialLocals = Locals.getInitialMethodLocals(target.method, target.classNode, fabricCompatibility, fallbackToLvIndex);
+
+ return getLocals(initialLocals);
+ }
+
+ private Local[] getLocals(LocalVariableNode[] initialLocals) {
+ Local[] lvt = new Local[initialLocals.length];
+ for (int l = 0; l < initialLocals.length; l++) {
+ if (initialLocals[l] != null) {
+ lvt[l] = new Local(initialLocals[l].name, Type.getType(initialLocals[l].desc));
}
}
return lvt;
}
-
+
private void initOrdinals() {
Map ordinalMap = new HashMap();
for (int l = 0; l < this.locals.length; l++) {
diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/dynamic/DynamicSelectorDesc.java b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/dynamic/DynamicSelectorDesc.java
index f3df61903..f0b53d444 100644
--- a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/dynamic/DynamicSelectorDesc.java
+++ b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/dynamic/DynamicSelectorDesc.java
@@ -503,7 +503,7 @@ public MatchResult match(ElementNode node) {
if (node == null || this.disabled) {
return MatchResult.NONE;
} else if (node.isField()) {
- return this.matches(node.getOwner(), node.getName(), node.getDesc(), this.returnType.getInternalName());
+ return this.matches(node.getOwner(), node.getName(), node.getDesc(), this.returnType.getDescriptor());
} else {
return this.matches(node.getOwner(), node.getName(), node.getDesc(), this.methodDesc);
}
diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/MemberInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/MemberInfo.java
index dcbdecf00..70545d4c0 100644
--- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/MemberInfo.java
+++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/MemberInfo.java
@@ -883,16 +883,6 @@ public static MemberInfo parse(final String input, final ISelectorContext contex
if (context != null) {
name = context.remap(name);
}
-
- int lastDotPos = name.lastIndexOf('.');
- int semiColonPos = name.indexOf(';');
- if (lastDotPos > -1) {
- owner = name.substring(0, lastDotPos).replace('.', '/');
- name = name.substring(lastDotPos + 1);
- } else if (semiColonPos > -1 && name.startsWith("L")) {
- owner = name.substring(1, semiColonPos).replace('.', '/');
- name = name.substring(semiColonPos + 1);
- }
int parenPos = name.indexOf('(');
int colonPos = name.indexOf(':');
@@ -904,6 +894,16 @@ public static MemberInfo parse(final String input, final ISelectorContext contex
name = name.substring(0, colonPos);
}
+ int lastDotPos = name.lastIndexOf('.');
+ int semiColonPos = name.indexOf(';');
+ if (lastDotPos > -1) {
+ owner = name.substring(0, lastDotPos).replace('.', '/');
+ name = name.substring(lastDotPos + 1);
+ } else if (semiColonPos > -1 && name.startsWith("L")) {
+ owner = name.substring(1, semiColonPos).replace('.', '/');
+ name = name.substring(semiColonPos + 1);
+ }
+
if ((name.indexOf('/') > -1 || name.indexOf('.') > -1) && owner == null) {
owner = name;
name = "";
diff --git a/src/main/java/org/spongepowered/asm/mixin/struct/MemberRef.java b/src/main/java/org/spongepowered/asm/mixin/struct/MemberRef.java
index 985cec013..1420da952 100644
--- a/src/main/java/org/spongepowered/asm/mixin/struct/MemberRef.java
+++ b/src/main/java/org/spongepowered/asm/mixin/struct/MemberRef.java
@@ -331,6 +331,15 @@ public void setHandle(int tag, String owner, String name, String desc, boolean i
*/
public abstract void setDesc(String desc);
+ /**
+ * Whether the owner of this member is a mixin.
+ * @return Whether the owner of this member is a mixin.
+ */
+ public boolean ownerIsMixin() {
+ String owner = getOwner();
+ return !owner.startsWith("[") && ClassInfo.isMixin(owner);
+ }
+
@Override
public String toString() {
return String.format("%s for %s.%s%s%s", Bytecode.getOpcodeName(this.getOpcode()), this.getOwner(), this.getName(), this.isField() ? ":" : "",
diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/ClassInfo.java b/src/main/java/org/spongepowered/asm/mixin/transformer/ClassInfo.java
index 66fa2a278..504b611e8 100644
--- a/src/main/java/org/spongepowered/asm/mixin/transformer/ClassInfo.java
+++ b/src/main/java/org/spongepowered/asm/mixin/transformer/ClassInfo.java
@@ -24,6 +24,8 @@
*/
package org.spongepowered.asm.mixin.transformer;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -907,6 +909,59 @@ private ClassInfo(ClassNode classNode) {
}
}
+ private ClassInfo(Class> cls) {
+ this.name = getName(cls);
+ this.superName = cls.getSuperclass() != null ? getName(cls.getSuperclass()) : ClassInfo.JAVA_LANG_OBJECT;
+ this.initialisers = new HashSet();
+ this.methods = new HashSet();
+ this.fields = new HashSet();
+ this.isInterface = cls.isInterface();
+ Class>[] interfaces = cls.getInterfaces();
+ this.interfaces = new HashSet(interfaces.length);
+ this.isMixin = false;
+ this.mixin = null;
+ this.mixins = Collections.emptySet();
+
+ for (Class> iface : interfaces) {
+ this.interfaces.add(getName(iface));
+ }
+
+ for (Constructor> ctor : cls.getDeclaredConstructors()) {
+ if ((ctor.getModifiers() & (Modifier.PROTECTED | Modifier.PUBLIC)) == 0) {
+ continue;
+ }
+
+ this.initialisers.add(new Method(ctor.getName(), org.objectweb.asm.Type.getConstructorDescriptor(ctor), ctor.getModifiers()));
+ }
+
+ for (java.lang.reflect.Method method : cls.getDeclaredMethods()) {
+ if ((method.getModifiers() & (Modifier.PROTECTED | Modifier.PUBLIC)) == 0) {
+ continue;
+ }
+
+ this.methods.add(new Method(method.getName(), org.objectweb.asm.Type.getMethodDescriptor(method), method.getModifiers()));
+ }
+
+ for (java.lang.reflect.Field field : cls.getDeclaredFields()) {
+ if ((field.getModifiers() & (Modifier.PROTECTED | Modifier.PUBLIC)) == 0) {
+ continue;
+ }
+
+ this.fields.add(new Field(field.getName(), org.objectweb.asm.Type.getDescriptor(field.getType()), field.getModifiers()));
+ }
+
+ this.isProbablyStatic = cls.getEnclosingClass() == null || Modifier.isStatic(cls.getModifiers());
+ this.methodMapper = null;
+
+ this.access = cls.getModifiers();
+ this.isInner = cls.getEnclosingClass() != null;
+ this.outerName = cls.getDeclaringClass() != null ? getName(cls.getDeclaringClass()) : null;
+ }
+
+ private static String getName(Class> cls) {
+ return cls.getName().replace('.', '/');
+ }
+
void addInterface(String iface) {
this.interfaces.add(iface);
this.getSignature().addInterface(iface);
@@ -2023,11 +2078,15 @@ public static ClassInfo forName(String className) {
className = className.replace('.', '/');
ClassInfo info = ClassInfo.cache.get(className);
- if (info == null) {
+ if (!ClassInfo.cache.containsKey(className)) {
try {
- int flags = MixinEnvironment.getCurrentEnvironment().getOption(Option.CLASSREADER_EXPAND_FRAMES) ? ClassReader.EXPAND_FRAMES : 0;
- ClassNode classNode = MixinService.getService().getBytecodeProvider().getClassNode(className, true, flags);
- info = new ClassInfo(classNode);
+ if (className.startsWith("java/")) { // this would ideally check the platform class loader for other jdk classes, but needs extra api to do so
+ info = new ClassInfo(Class.forName(className.replace('/', '.'), false, ClassInfo.class.getClassLoader()));
+ } else {
+ int flags = MixinEnvironment.getCurrentEnvironment().getOption(Option.CLASSREADER_EXPAND_FRAMES) ? ClassReader.EXPAND_FRAMES : 0;
+ ClassNode classNode = MixinService.getService().getBytecodeProvider().getClassNode(className, true, flags);
+ info = new ClassInfo(classNode);
+ }
} catch (Exception ex) {
ClassInfo.logger.catching(Level.TRACE, ex);
ClassInfo.logger.warn("Error loading class: {} ({}: {})", className, ex.getClass().getName(), ex.getMessage());
@@ -2231,4 +2290,13 @@ private static ClassInfo getCommonSuperClass(ClassInfo type1, ClassInfo type2, b
return type1;
}
+ public static boolean isMixin(String className) {
+ ClassInfo cachedInfo = ClassInfo.fromCache(className);
+ if (cachedInfo == null) {
+ // Mixin infos are forcefully cached, so this isn't one
+ return false;
+ }
+ return cachedInfo.isMixin();
+ }
+
}
diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/IMixinTransformer.java b/src/main/java/org/spongepowered/asm/mixin/transformer/IMixinTransformer.java
index 769ab93fc..3098177e9 100644
--- a/src/main/java/org/spongepowered/asm/mixin/transformer/IMixinTransformer.java
+++ b/src/main/java/org/spongepowered/asm/mixin/transformer/IMixinTransformer.java
@@ -101,6 +101,15 @@ public interface IMixinTransformer {
* @return true if the class was transformed
*/
public abstract boolean transformClass(MixinEnvironment environment, String name, ClassNode classNode);
+
+ /**
+ * Determines whether mixin could transform the provided class, without actually running the transformation
+ *
+ * @param environment Current environment
+ * @param name Class transformed name
+ * @return true if the class could be transformed
+ */
+ public abstract boolean couldTransformClass(MixinEnvironment environment, String name);
/**
* Generate the specified mixin-synthetic class
diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/InnerClassGenerator.java b/src/main/java/org/spongepowered/asm/mixin/transformer/InnerClassGenerator.java
index 4a5a56b9d..d70c0537a 100644
--- a/src/main/java/org/spongepowered/asm/mixin/transformer/InnerClassGenerator.java
+++ b/src/main/java/org/spongepowered/asm/mixin/transformer/InnerClassGenerator.java
@@ -25,10 +25,12 @@
package org.spongepowered.asm.mixin.transformer;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
+import org.objectweb.asm.tree.InnerClassNode;
import org.spongepowered.asm.logging.ILogger;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassVisitor;
@@ -57,7 +59,7 @@ final class InnerClassGenerator implements IClassGenerator {
* Information about an inner class instance. Implements {@link Remapper} so
* that it can participate in the remapping process.
*/
- static class InnerClassInfo extends Remapper implements ISyntheticClassInfo {
+ class InnerClassInfo extends Remapper implements ISyntheticClassInfo {
/**
* Mixin which provides this class
@@ -155,10 +157,21 @@ String getNestHostName() {
void accept(final ClassVisitor classVisitor) throws ClassNotFoundException, IOException {
ClassNode classNode = MixinService.getService().getBytecodeProvider().getClassNode(this.originalName);
+ this.readInnerClasses(classNode);
classNode.accept(classVisitor);
this.loadCounter++;
}
+ private void readInnerClasses(ClassNode classNode) {
+ // Read possibly nested inner classes
+ for (InnerClassNode inner : classNode.innerClasses) {
+ if ((inner.outerName != null && this.findRemappedName(inner.outerName) != null)
+ || inner.name.startsWith(this.mixin.getClassRef() + "$")) {
+ InnerClassGenerator.this.registerInnerClass(this.owner, this.targetClassInfo, inner.name);
+ }
+ }
+ }
+
/**
* Used to remap fields which have been renamed while being applied to
* the target class or by an implementation of IRemapper.
@@ -198,10 +211,9 @@ public String mapMethodName(String owner, String name, String desc) {
*/
@Override
public String map(String key) {
- if (this.originalName.equals(key)) {
- return this.name;
- } else if (this.ownerName.equals(key)) {
- return this.targetClassInfo.getName();
+ String remappedName = this.findRemappedName(key);
+ if (remappedName != null) {
+ return remappedName;
}
return key;
}
@@ -213,6 +225,15 @@ public String map(String key) {
public String toString() {
return this.name;
}
+
+ private String findRemappedName(String originalName) {
+ if (this.ownerName.equals(originalName)) {
+ return this.targetClassInfo.getName();
+ }
+ return InnerClassGenerator.this.innerClassNames.get(
+ InnerClassGenerator.innerClassCoordinate(this.owner, this.targetClassInfo, originalName)
+ );
+ }
}
@@ -252,21 +273,6 @@ public void visitSource(String source, String debug) {
av.visitEnd();
}
- /* (non-Javadoc)
- * @see org.objectweb.asm.commons.RemappingClassAdapter
- * #visitInnerClass(java.lang.String, java.lang.String,
- * java.lang.String, int)
- */
- @Override
- public void visitInnerClass(String name, String outerName, String innerName, int access) {
- if (name.startsWith(this.info.getOriginalName() + "$")) {
- throw new InvalidMixinException(this.info.getOwner(), "Found unsupported nested inner class " + name + " in "
- + this.info.getOriginalName());
- }
-
- super.visitInnerClass(name, outerName, innerName, access);
- }
-
}
/**
@@ -338,7 +344,7 @@ public String getName() {
* @param innerClassName Original inner class name
*/
void registerInnerClass(MixinInfo owner, ClassInfo targetClass, String innerClassName) {
- String coordinate = String.format("%s:%s:%s", owner, innerClassName, targetClass.getName());
+ String coordinate = InnerClassGenerator.innerClassCoordinate(owner, targetClass, innerClassName);
String uniqueName = this.innerClassNames.get(coordinate);
if (uniqueName != null) {
return;
@@ -421,7 +427,8 @@ private static String getUniqueReference(String originalName, ClassInfo targetCl
if (name.matches("^[0-9]+$")) {
name = "Anonymous";
}
- return String.format("%s$%s$%s", targetClass, name, UUID.randomUUID().toString().replace("-", ""));
+ UUID uuid = UUID.nameUUIDFromBytes(originalName.getBytes(StandardCharsets.UTF_8));
+ return String.format("%s$%s$%s", targetClass, name, uuid.toString().replace("-", ""));
}
/**
@@ -457,4 +464,8 @@ private static ClassVisitor createRemappingAdapter(ClassVisitor cv, Remapper rem
return InnerClassGenerator.clRemapper.getConstructor(ClassVisitor.class, Remapper.class).newInstance(cv, remapper);
}
+ private static String innerClassCoordinate(MixinInfo owner, ClassInfo targetClass, String innerClassName) {
+ return String.format("%s:%s:%s", owner.getClassRef(), innerClassName, targetClass.getName());
+ }
+
}
diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java
index d3a550f5e..c6aac93bf 100644
--- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java
+++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java
@@ -1394,9 +1394,11 @@ static Config create(String configFile, MixinEnvironment outer, IMixinConfigSour
if (resource == null) {
throw new IllegalArgumentException(String.format("The specified resource '%s' was invalid or could not be read", configFile));
}
- MixinConfig config = new Gson().fromJson(new InputStreamReader(resource), MixinConfig.class);
- if (config.onLoad(service, configFile, outer, source)) {
- return config.getHandle();
+ try (InputStreamReader reader = new InputStreamReader(resource)) {
+ MixinConfig config = new Gson().fromJson(reader, MixinConfig.class);
+ if (config.onLoad(service, configFile, outer, source)) {
+ return config.getHandle();
+ }
}
return null;
} catch (IllegalArgumentException ex) {
diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessor.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessor.java
index 6f3116190..e044d2e05 100644
--- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessor.java
+++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessor.java
@@ -25,7 +25,9 @@
package org.spongepowered.asm.mixin.transformer;
import org.objectweb.asm.tree.ClassNode;
+import org.spongepowered.asm.logging.ILogger;
import org.spongepowered.asm.mixin.transformer.MixinConfig.IListener;
+import org.spongepowered.asm.service.MixinService;
/**
* Coprocessors are parts of the mixin pipeline which aren't involved in
@@ -33,11 +35,10 @@
* mixins.
*
* Such tasks as {@link MixinCoprocessorAccessor making accessor mixins
- * loadable (and transforming the accessors therein)}, {@link
- * MixinCoprocessorSyntheticInner exposing synthetic inner classes to all
- * consumers} and {@link MixinCoprocessorNestHost applying nest member
- * attributes to nest hosts which may themselves not be mixin targets} are
- * handled by different coprocessors.
+ * loadable (and transforming the accessors therein)}, and {@link
+ * MixinCoprocessorNestHost applying nest member attributes to nest hosts
+ * which may themselves not be mixin targets} are handled by different
+ * coprocessors.
*
* These classes were previously encapsulated in a single companion class
* called MixinPostProcessor, but the mixture of responsibilities of
@@ -153,6 +154,25 @@ ProcessResult process(String className, ClassNode classNode) {
return ProcessResult.NONE;
}
+ private static final ILogger logger = MixinService.getService().getLogger("mixin");
+ private boolean willLogUnimplementedCouldTransform = true;
+
+ /**
+ * Determine ahead-of-time whether a given class could be transformed ({@link ProcessResult#TRANSFORMED},
+ * {@link ProcessResult#PASSTHROUGH_TRANSFORMED}, or modification in
+ * {@link MixinCoprocessor#postProcess(String, ClassNode)}) by processing by this coprocessor.
+ *
+ * @param className Name of the target class
+ * @return true if the coprocessor might transform the class when processed
+ */
+ public boolean couldTransform(String className) {
+ if (willLogUnimplementedCouldTransform) {
+ willLogUnimplementedCouldTransform = false;
+ logger.error("MixinCoprocessor {} ({}) does not implement couldTransform, which may lead to unnecessary transformation", getName(), getClass().getName());
+ }
+ return true;
+ }
+
/**
* Perform postprocessing actions on the supplied class. This is called for
* all classes. For passthrough classes and classes which are not mixin
diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorAccessor.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorAccessor.java
index 423c16394..970f0a7c2 100644
--- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorAccessor.java
+++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorAccessor.java
@@ -126,6 +126,11 @@ ProcessResult process(String className, ClassNode classNode) {
return ProcessResult.PASSTHROUGH_TRANSFORMED;
}
+ @Override
+ public boolean couldTransform(String className) {
+ return MixinEnvironment.getCompatibilityLevel().supports(LanguageFeatures.METHODS_IN_INTERFACES) && this.accessorMixins.containsKey(className);
+ }
+
private Method getAccessorMethod(MixinInfo mixin, MethodNode methodNode, ClassInfo targetClass) throws MixinTransformerError {
Method method = mixin.getClassInfo().findMethod(methodNode, ClassInfo.INCLUDE_ALL);
diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorNestHost.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorNestHost.java
index 592446392..1cfba2779 100644
--- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorNestHost.java
+++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorNestHost.java
@@ -105,4 +105,8 @@ boolean postProcess(String className, ClassNode classNode) {
return true;
}
+ @Override
+ public boolean couldTransform(String className) {
+ return this.nestHosts.containsKey(className);
+ }
}
diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorPassthrough.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorPassthrough.java
index a970320a6..0c648e4d7 100644
--- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorPassthrough.java
+++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorPassthrough.java
@@ -64,4 +64,8 @@ ProcessResult process(String className, ClassNode classNode) {
return this.loadable.contains(className) ? ProcessResult.PASSTHROUGH_NONE : ProcessResult.NONE;
}
+ @Override
+ public boolean couldTransform(String className) {
+ return false;
+ }
}
diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorSyntheticInner.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorSyntheticInner.java
deleted file mode 100644
index 8291dc129..000000000
--- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorSyntheticInner.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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.transformer;
-
-import java.util.HashSet;
-import java.util.Set;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.FieldNode;
-import org.objectweb.asm.tree.MethodNode;
-
-/**
- * Coprocessor which handles synthetic inner classes in mixins. Since synthetic
- * inner classes are usually just switch tables, it is reasonably safe to share
- * them amongst all mixin targets. Therefore the class is simply transformed so
- * that all members are public.
- *
- * Nest based-access control could potentially be used where a mixin has a
- * single target, and Java 11 or higher is in use, but at the moment it's more
- * straightforward to just make everything public.
- */
-class MixinCoprocessorSyntheticInner extends MixinCoprocessor {
-
- /**
- * Synthetic inner classes in mixins
- */
- private final Set syntheticInnerClasses = new HashSet();
-
- MixinCoprocessorSyntheticInner() {
- }
-
- @Override
- String getName() {
- return "syntheticinner";
- }
-
- @Override
- public void onInit(MixinInfo mixin) {
- for (String innerClass : mixin.getSyntheticInnerClasses()) {
- this.registerSyntheticInner(innerClass.replace('/', '.'));
- }
- }
-
- void registerSyntheticInner(String className) {
- this.syntheticInnerClasses.add(className);
- }
-
- /**
- * "Pass through" a synthetic inner class. Transforms package-private
- * members in the class into public so that they are accessible from their
- * new home in the target class
- */
- @Override
- ProcessResult process(String className, ClassNode classNode) {
- if (!this.syntheticInnerClasses.contains(className)) {
- return ProcessResult.NONE;
- }
-
- classNode.access |= Opcodes.ACC_PUBLIC;
-
- for (FieldNode field : classNode.fields) {
- if ((field.access & (Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED)) == 0) {
- field.access |= Opcodes.ACC_PUBLIC;
- }
- }
-
- for (MethodNode method : classNode.methods) {
- if ((method.access & (Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED)) == 0) {
- method.access |= Opcodes.ACC_PUBLIC;
- }
- }
-
- return ProcessResult.PASSTHROUGH_TRANSFORMED;
- }
-
-}
diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessors.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessors.java
index 1656338c1..a5afb34f3 100644
--- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessors.java
+++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessors.java
@@ -67,6 +67,21 @@ ProcessResult process(String className, ClassNode classNode) {
return result;
}
+ /**
+ * Determine ahead-of-time whether a given class could be transformed by any registered coprocessor.
+ *
+ * @param className Name of the target class
+ * @return true if the coprocessors might transform the class when processed
+ */
+ public boolean processingCouldTransform(String className) {
+ for (MixinCoprocessor coprocessor : this) {
+ if (coprocessor.couldTransform(className)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Perform postprocessing actions on the supplied class using all registered
* coprocessors.
diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInfo.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInfo.java
index 41701876b..a26ac474f 100644
--- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInfo.java
+++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInfo.java
@@ -224,14 +224,9 @@ class State {
* Interfaces soft-implemented using {@link Implements}
*/
protected final List softImplements = new ArrayList();
-
- /**
- * Synthetic inner classes
- */
- protected final Set syntheticInnerClasses = new HashSet();
/**
- * Non-synthetic inner classes
+ * Inner classes
*/
protected final Set innerClasses = new HashSet();
@@ -286,10 +281,6 @@ List extends InterfaceInfo> getSoftImplements() {
return this.softImplements;
}
- Set getSyntheticInnerClasses() {
- return this.syntheticInnerClasses;
- }
-
Set getInnerClasses() {
return this.innerClasses;
}
@@ -420,18 +411,13 @@ void readImplementations(SubType type) {
}
/**
- * Read inner class definitions for the class and locate any synthetic
- * inner classes so that we can add them to the passthrough set in our
- * parent config.
+ * Read inner class definitions for the class and locate any of our inner classes.
*/
void readInnerClasses() {
for (InnerClassNode inner : this.validationClassNode.innerClasses) {
- ClassInfo innerClass = ClassInfo.forName(inner.name);
if ((inner.outerName != null && inner.outerName.equals(this.classInfo.getName()))
|| inner.name.startsWith(this.validationClassNode.name + "$")) {
- if (innerClass.isProbablyStatic() && innerClass.isSynthetic()) {
- this.syntheticInnerClasses.add(inner.name);
- } else if (!innerClass.isMixin()) {
+ if (!ClassInfo.isMixin(inner.name)) {
this.innerClasses.add(inner.name);
}
}
@@ -464,7 +450,7 @@ class Reloaded extends State {
*/
@Override
protected void validateChanges(SubType type, List targetClasses) {
- if (!this.syntheticInnerClasses.equals(this.previous.syntheticInnerClasses)) {
+ if (!this.innerClasses.equals(this.previous.innerClasses)) {
throw new MixinReloadException(MixinInfo.this, "Cannot change inner classes");
}
if (!this.interfaces.equals(this.previous.interfaces)) {
@@ -1024,9 +1010,6 @@ private ClassInfo getTargetClass(DeclaredTarget target) throws InvalidMixinExcep
return null;
}
this.type.validateTarget(target.name, targetInfo);
- if (target.isPrivate && targetInfo.isReallyPublic() && !this.isVirtual()) {
- this.handleTargetError(String.format("@Mixin target %s is public in %s and should be specified in value", target.name, this), true);
- }
return targetInfo;
}
@@ -1238,16 +1221,9 @@ public List getTargetClasses() {
List getSoftImplements() {
return Collections.unmodifiableList(this.getState().getSoftImplements());
}
-
- /**
- * Get the synthetic inner classes for this mixin
- */
- Set getSyntheticInnerClasses() {
- return Collections.unmodifiableSet(this.getState().getSyntheticInnerClasses());
- }
/**
- * Get the user-defined inner classes for this mixin
+ * Get the inner classes for this mixin
*/
Set getInnerClasses() {
return Collections.unmodifiableSet(this.getState().getInnerClasses());
diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinPreProcessorStandard.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinPreProcessorStandard.java
index 5be4f8f22..85cf936ba 100644
--- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinPreProcessorStandard.java
+++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinPreProcessorStandard.java
@@ -783,6 +783,10 @@ protected void transformField(FieldInsnNode fieldNode) {
}
protected void transformMemberReference(MemberRef ref) {
+ if (!ClassInfo.isMixin(ref.getOwner())) {
+ // No need to transform anything
+ return;
+ }
IActivity activity = this.activities.begin("%s::%s:%s", ref.getOwner(), ref.getName(), ref.getDesc());
Section metaTimer = this.profiler.begin("meta");
diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinProcessor.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinProcessor.java
index 2cfa27c33..1a2985a51 100644
--- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinProcessor.java
+++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinProcessor.java
@@ -226,7 +226,6 @@ public String getErrorMessage(IMixinInfo mixin, IMixinConfig config, Phase phase
this.hotSwapper = hotSwapper;
this.coprocessors.add(new MixinCoprocessorPassthrough());
- this.coprocessors.add(new MixinCoprocessorSyntheticInner());
this.coprocessors.add(new MixinCoprocessorAccessor(this.sessionId));
this.coprocessors.add(nestHostCoprocessor);
@@ -251,7 +250,7 @@ public void audit(MixinEnvironment environment) {
for (String target : unhandled) {
try {
auditLogger.info("Force-loading class {}", target);
- this.service.getClassProvider().findClass(target, true);
+ this.service.getClassProvider().findClass(target, false);
} catch (ClassNotFoundException ex) {
auditLogger.error("Could not force-load " + target, ex);
}
@@ -274,25 +273,14 @@ synchronized boolean applyMixins(MixinEnvironment environment, String name, Clas
return false;
}
- boolean locked = this.lock.push().check();
Section mixinTimer = this.profiler.begin("mixin");
-
- if (locked) {
- for (MixinConfig config : this.pendingConfigs) {
- if (config.hasPendingMixinsFor(name)) {
- ReEntrantTransformerError error = new ReEntrantTransformerError("Re-entrance error.");
- MixinProcessor.logger.warn("Re-entrance detected during prepare phase, this will cause serious problems.", error);
- throw error;
- }
- }
- } else {
- try {
- this.checkSelect(environment);
- } catch (Exception ex) {
- this.lock.pop();
- mixinTimer.end();
- throw new MixinException(ex);
- }
+ boolean locked;
+
+ try {
+ locked = lockAndSelect(environment, name);
+ } catch (Throwable th) {
+ mixinTimer.end();
+ throw th;
}
boolean transformed = false;
@@ -397,6 +385,65 @@ synchronized boolean applyMixins(MixinEnvironment environment, String name, Clas
return transformed;
}
+ synchronized boolean couldTransformClass(MixinEnvironment environment, String name) {
+ if (environment != MixinEnvironment.getCurrentEnvironment()) {
+ throw new MixinException("Current environment must match the supplied environment");
+ }
+
+ if (name == null || this.errorState) {
+ return false;
+ }
+
+ lockAndSelect(environment, name);
+
+ try {
+ if (this.coprocessors.processingCouldTransform(name)) {
+ return true;
+ }
+
+ for (MixinConfig config : this.configs) {
+ if (config.packageMatch(name)) {
+ // If the class is in a mixin package, it may be transformed
+ return true;
+ }
+ }
+
+ for (MixinConfig config : this.configs) {
+ if (config.hasMixinsFor(name)) {
+ // If any config has mixins for the class, it may be transformed
+ return true;
+ }
+ }
+
+ return false;
+ } finally {
+ this.lock.pop();
+ }
+ }
+
+ private boolean lockAndSelect(MixinEnvironment environment, String name) {
+ boolean locked = this.lock.push().check();
+
+ if (locked) {
+ for (MixinConfig config : this.pendingConfigs) {
+ if (config.hasPendingMixinsFor(name)) {
+ ReEntrantTransformerError error = new ReEntrantTransformerError("Re-entrance error.");
+ MixinProcessor.logger.warn("Re-entrance detected during prepare phase, this will cause serious problems.", error);
+ throw error;
+ }
+ }
+ } else {
+ try {
+ this.checkSelect(environment);
+ } catch (Exception ex) {
+ this.lock.pop();
+ throw new MixinException(ex);
+ }
+ }
+
+ return locked;
+ }
+
private String getInvalidClassError(String name, ClassNode targetClassNode, MixinConfig ownedByConfig) {
if (ownedByConfig.getClasses().contains(name)) {
return String.format("Illegal classload request for %s. Mixin is defined in %s and cannot be referenced directly", name, ownedByConfig);
diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java
index a97fc0b78..e66c19798 100644
--- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java
+++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java
@@ -544,7 +544,7 @@ private void validateMethod(MethodNode method) {
}
}
- if (Bytecode.isVirtual(method)) {
+ if (this.getEnvironment().getOption(Option.DEBUG_VERIFY) && Bytecode.isVirtual(method)) {
Method superMethod = this.targetClassInfo.findMethodInHierarchy(method, SearchType.SUPER_CLASSES_ONLY, Traversal.ALL, 0);
if (superMethod != null && superMethod.isFinal()) {
throw new InvalidMixinException(this.mixin, String.format("%s%s in %s overrides a final method from %s",
@@ -600,7 +600,7 @@ private void transformMethodRef(MethodNode method, Iterator it
} else if (this.detachedSuper || this.inheritsFromMixin) {
if (methodRef.getOpcode() == Opcodes.INVOKESPECIAL) {
this.updateStaticBinding(method, methodRef);
- } else if (methodRef.getOpcode() == Opcodes.INVOKEVIRTUAL && ClassInfo.forName(methodRef.getOwner()).isMixin()) {
+ } else if (methodRef.getOpcode() == Opcodes.INVOKEVIRTUAL && methodRef.ownerIsMixin()) {
this.updateDynamicBinding(method, methodRef);
}
}
@@ -637,9 +637,11 @@ private void transformFieldRef(MethodNode method, Iterator ite
if (field != null && field.isRenamed() && field.getOriginalName().equals(fieldRef.getName()) && field.isStatic()) {
fieldRef.setName(field.getName());
}
+ } else if (this.innerClasses.containsKey(fieldRef.getOwner())) {
+ fieldRef.setOwner(this.innerClasses.get(fieldRef.getOwner()));
} else {
- ClassInfo fieldOwner = ClassInfo.forName(fieldRef.getOwner());
- if (fieldOwner.isMixin()) {
+ if (ClassInfo.isMixin(fieldRef.getOwner())) {
+ ClassInfo fieldOwner = ClassInfo.forName(fieldRef.getOwner());
ClassInfo actualOwner = this.targetClassInfo.findCorrespondingType(fieldOwner);
fieldRef.setOwner(actualOwner != null ? actualOwner.getName() : this.getTarget().getClassRef());
}
@@ -903,7 +905,7 @@ private void updateBinding(MethodNode method, MemberRef methodRef, Traversal tra
+ " but is mixin.");
}
methodRef.setOwner(superMethod.getImplementor().getName());
- } else if (ClassInfo.forName(methodRef.getOwner()).isMixin()) {
+ } else if (methodRef.ownerIsMixin()) {
throw new MixinTransformerError("Error resolving " + methodRef + " in " + this);
}
}
@@ -1011,11 +1013,8 @@ private String transformSingleDescriptor(String desc, boolean isObject) {
descriptorActivity.end();
return desc;
}
- ClassInfo typeInfo = ClassInfo.forName(type);
- if (typeInfo == null) {
- throw new ClassMetadataNotFoundException(type.replace('/', '.'));
- }
- if (!typeInfo.isMixin() || typeInfo.isLoadable()) {
+ ClassInfo typeInfo;
+ if (!ClassInfo.isMixin(type) || (typeInfo = ClassInfo.forName(type)).isLoadable()) {
descriptorActivity.end();
return desc;
}
diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTransformer.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTransformer.java
index c6bde16de..2c2df6ed1 100644
--- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTransformer.java
+++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTransformer.java
@@ -229,7 +229,10 @@ public boolean computeFramesForClass(MixinEnvironment environment, String name,
* @return Transformed bytecode
*/
@Override
- public byte[] transformClass(MixinEnvironment environment, String name, byte[] classBytes) {
+ public synchronized byte[] transformClass(MixinEnvironment environment, String name, byte[] classBytes) {
+ if (!couldTransformClass(environment, name)) {
+ return classBytes;
+ }
ClassNode classNode = this.readClass(name, classBytes);
if (this.processor.applyMixins(environment, name, classNode)) {
return this.writeClass(classNode);
@@ -246,10 +249,22 @@ public byte[] transformClass(MixinEnvironment environment, String name, byte[] c
* @return true if the class was transformed
*/
@Override
- public boolean transformClass(MixinEnvironment environment, String name, ClassNode classNode) {
+ public synchronized boolean transformClass(MixinEnvironment environment, String name, ClassNode classNode) {
return this.processor.applyMixins(environment, name, classNode);
}
-
+
+ /**
+ * Determines whether mixin could transform the provided class, without actually running the transformation
+ *
+ * @param environment Current environment
+ * @param name Class transformed name
+ * @return true if the class could be transformed
+ */
+ @Override
+ public synchronized boolean couldTransformClass(MixinEnvironment environment, String name) {
+ return this.processor.couldTransformClass(environment, name);
+ }
+
/**
* Generate the specified mixin-synthetic class
*
@@ -258,7 +273,7 @@ public boolean transformClass(MixinEnvironment environment, String name, ClassNo
* @return Generated bytecode or null if no class was generated
*/
@Override
- public byte[] generateClass(MixinEnvironment environment, String name) {
+ public synchronized byte[] generateClass(MixinEnvironment environment, String name) {
ClassNode classNode = MixinTransformer.createEmptyClass(name);
if (this.generator.generateClass(environment, name, classNode)) {
return this.writeClass(classNode);
@@ -273,7 +288,7 @@ public byte[] generateClass(MixinEnvironment environment, String name) {
* @return True if the class was generated successfully
*/
@Override
- public boolean generateClass(MixinEnvironment environment, String name, ClassNode classNode) {
+ public synchronized boolean generateClass(MixinEnvironment environment, String name, ClassNode classNode) {
return this.generator.generateClass(environment, name, classNode);
}
diff --git a/src/main/java/org/spongepowered/asm/util/Annotations.java b/src/main/java/org/spongepowered/asm/util/Annotations.java
index 259986d8b..5bff4b6e7 100644
--- a/src/main/java/org/spongepowered/asm/util/Annotations.java
+++ b/src/main/java/org/spongepowered/asm/util/Annotations.java
@@ -552,19 +552,14 @@ public static T getValue(AnnotationNode annotation, String key, Class> ann
*/
@SuppressWarnings("unchecked")
public static T getValue(AnnotationNode annotation, String key) {
- boolean getNextValue = false;
-
if (annotation == null || annotation.values == null) {
return null;
}
// Keys and value are stored in successive pairs, search for the key and if found return the following entry
- for (Object value : annotation.values) {
- if (getNextValue) {
- return (T) value;
- }
- if (value.equals(key)) {
- getNextValue = true;
+ for (int i = 0; i < annotation.values.size() - 1; i += 2) {
+ if (annotation.values.get(i).equals(key)) {
+ return (T) annotation.values.get(i + 1);
}
}
diff --git a/src/main/java/org/spongepowered/asm/util/Bytecode.java b/src/main/java/org/spongepowered/asm/util/Bytecode.java
index eae400113..8a451ee26 100644
--- a/src/main/java/org/spongepowered/asm/util/Bytecode.java
+++ b/src/main/java/org/spongepowered/asm/util/Bytecode.java
@@ -30,9 +30,9 @@
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
-import java.util.ListIterator;
import java.util.Map;
+import com.google.common.collect.Iterators;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
@@ -41,6 +41,7 @@
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import org.objectweb.asm.util.CheckClassAdapter;
+import org.objectweb.asm.util.Printer;
import org.objectweb.asm.util.TraceClassVisitor;
import org.spongepowered.asm.util.asm.ASM;
import org.spongepowered.asm.util.asm.MarkerNode;
@@ -464,7 +465,7 @@ public static String describeNode(AbstractInsnNode node, boolean listFormat) {
} else if (node instanceof InvokeDynamicInsnNode) {
InvokeDynamicInsnNode idc = (InvokeDynamicInsnNode)node;
out += String.format("[%s] %s%s { %s %s::%s%s }", Bytecode.getOpcodeName(node), idc.name, idc.desc,
- Bytecode.getOpcodeName(idc.bsm.getTag(), "H_GETFIELD", 1), idc.bsm.getOwner(), idc.bsm.getName(), idc.bsm.getDesc());
+ Bytecode.getOpcodeName(idc.bsm.getTag(), Printer.HANDLE_TAG), idc.bsm.getOwner(), idc.bsm.getName(), idc.bsm.getDesc());
} else if (node instanceof LineNumberNode) {
LineNumberNode ln = (LineNumberNode)node;
out += String.format("LINE=[%d] LABEL=[%s]", ln.line, ln.start.getLabel());
@@ -473,7 +474,7 @@ public static String describeNode(AbstractInsnNode node, boolean listFormat) {
} else if (node instanceof IntInsnNode) {
out += (((IntInsnNode)node).operand);
} else if (node instanceof FrameNode) {
- out += String.format("[%s] ", Bytecode.getOpcodeName(((FrameNode)node).type, "H_INVOKEINTERFACE", -1));
+ out += String.format("[%s] ", getFrameTypeName((FrameNode) node));
} else if (node instanceof TypeInsnNode) {
out += String.format("[%s] %s", Bytecode.getOpcodeName(node), ((TypeInsnNode)node).desc);
} else {
@@ -483,53 +484,56 @@ public static String describeNode(AbstractInsnNode node, boolean listFormat) {
}
/**
- * Uses reflection to find an approximate constant name match for the
- * supplied node's opcode
+ * Finds a constant name match for the supplied node's opcode
*
* @param node Node to query for opcode
- * @return Approximate opcode name (approximate because some constants in
- * the {@link Opcodes} class have the same value as opcodes
+ * @return opcode name
*/
public static String getOpcodeName(AbstractInsnNode node) {
return node != null ? Bytecode.getOpcodeName(node.getOpcode()) : "";
}
/**
- * Uses reflection to find an approximate constant name match for the
- * supplied opcode
+ * Finds a constant name match for the supplied node's opcode
*
* @param opcode Opcode to look up
- * @return Approximate opcode name (approximate because some constants in
- * the {@link Opcodes} class have the same value as opcodes
+ * @return opcode name
*/
public static String getOpcodeName(int opcode) {
- return Bytecode.getOpcodeName(opcode, "UNINITIALIZED_THIS", 1);
+ return Bytecode.getOpcodeName(opcode, Printer.OPCODES);
}
- private static String getOpcodeName(int opcode, String start, int min) {
- if (opcode >= min) {
- boolean found = false;
-
- try {
- for (java.lang.reflect.Field f : Opcodes.class.getDeclaredFields()) {
- if (!found && !f.getName().equals(start)) {
- continue;
- }
- found = true;
- if (f.getType() == Integer.TYPE && f.getInt(null) == opcode) {
- return f.getName();
- }
- }
- } catch (Exception ex) {
- // derp
- }
- }
-
- return opcode >= 0 ? String.valueOf(opcode) : "UNKNOWN";
+ private static String getOpcodeName(int opcode, String[] names) {
+ if (opcode < 0) {
+ return "UNKNOWN";
+ }
+ if (opcode < names.length) {
+ return names[opcode];
+ }
+ return String.valueOf(opcode);
+ }
+
+ private static String getFrameTypeName(FrameNode node) {
+ switch (node.type) {
+ case Opcodes.F_NEW:
+ return "F_NEW";
+ case Opcodes.F_FULL:
+ return "F_FULL";
+ case Opcodes.F_APPEND:
+ return "F_APPEND";
+ case Opcodes.F_CHOP:
+ return "F_CHOP";
+ case Opcodes.F_SAME:
+ return "F_SAME";
+ case Opcodes.F_SAME1:
+ return "F_SAME1";
+ default:
+ return "UNKNOWN";
+ }
}
/**
- * Uses reflection to find a matching constant in the {@link Opcodes}
+ * Finds a matching constant in the {@link Opcodes}
* interface for the specified opcode name. Supported formats are raw
* numeric values, bare constant names (eg. ACONST_NULL) or
* qualified names (eg. Opcodes.ACONST_NULL). Returns the value if
@@ -556,24 +560,14 @@ public static int parseOpcodeName(String opcodeName) {
return -1;
}
- return Bytecode.parseOpcodeName(opcodeName, "UNINITIALIZED_THIS", 1);
+ return Bytecode.parseOpcodeName(opcodeName, Printer.OPCODES);
}
- private static int parseOpcodeName(String opcodeName, String start, int min) {
- boolean found = false;
-
- try {
- for (java.lang.reflect.Field f : Opcodes.class.getDeclaredFields()) {
- if (!found && !f.getName().equals(start)) {
- continue;
- }
- found = true;
- if (f.getType() == Integer.TYPE && f.getName().equalsIgnoreCase(opcodeName)) {
- return f.getInt(null);
- }
+ private static int parseOpcodeName(String name, String[] names) {
+ for (int i = 0; i < names.length; i++) {
+ if (name.equalsIgnoreCase(names[i])) {
+ return i;
}
- } catch (Exception ex) {
- // well this is embarrassing
}
return -1;
@@ -1256,16 +1250,13 @@ public static String getUnboxingMethod(Type type) {
* @param b Incoming method
*/
public static void compareBridgeMethods(MethodNode a, MethodNode b) {
- ListIterator ia = a.instructions.iterator();
- ListIterator ib = b.instructions.iterator();
+ Iterator ia = Iterators.filter(a.instructions.iterator(), Bytecode::isRealInsn);
+ Iterator ib = Iterators.filter(b.instructions.iterator(), Bytecode::isRealInsn);
int index = 0;
for (; ia.hasNext() && ib.hasNext(); index++) {
AbstractInsnNode na = ia.next();
AbstractInsnNode nb = ib.next();
- if (na instanceof LabelNode) {
- continue;
- }
if (na instanceof MethodInsnNode) {
MethodInsnNode ma = (MethodInsnNode)na;
@@ -1292,11 +1283,18 @@ public static void compareBridgeMethods(MethodNode a, MethodNode b) {
}
}
- if (ia.hasNext() || ib.hasNext()) {
- throw new SyntheticBridgeException(Problem.BAD_LENGTH, a.name, a.desc, index, null, null);
+ if (ia.hasNext()) {
+ throw new SyntheticBridgeException(Problem.BAD_LENGTH, a.name, a.desc, index, ia.next(), null);
+ }
+ if (ib.hasNext()) {
+ throw new SyntheticBridgeException(Problem.BAD_LENGTH, a.name, a.desc, index, null, ib.next());
}
}
+ private static boolean isRealInsn(AbstractInsnNode insn) {
+ return insn.getOpcode() != -1;
+ }
+
/**
* Perform a naïve merge of ClassNode members onto a target ClassNode
*
diff --git a/src/main/java/org/spongepowered/asm/util/JavaVersion.java b/src/main/java/org/spongepowered/asm/util/JavaVersion.java
index 9382f27b3..b900669de 100644
--- a/src/main/java/org/spongepowered/asm/util/JavaVersion.java
+++ b/src/main/java/org/spongepowered/asm/util/JavaVersion.java
@@ -116,6 +116,21 @@ public abstract class JavaVersion {
* Version number for Java 22
*/
public static final double JAVA_22 = 22.0;
+
+ /**
+ * Version number for Java 23
+ */
+ public static final double JAVA_23 = 23.0;
+
+ /**
+ * Version number for Java 24
+ */
+ public static final double JAVA_24 = 24.0;
+
+ /**
+ * Version number for Java 25
+ */
+ public static final double JAVA_25 = 25.0;
private static double current = 0.0;
diff --git a/src/main/java/org/spongepowered/asm/util/Locals.java b/src/main/java/org/spongepowered/asm/util/Locals.java
index 0fcc960c1..441c2ed6c 100644
--- a/src/main/java/org/spongepowered/asm/util/Locals.java
+++ b/src/main/java/org/spongepowered/asm/util/Locals.java
@@ -270,10 +270,117 @@ private Locals() {
// utility class
}
+ /**
+ * Constructs the initial local variable frame for a method, containing only "this" (if non-static)
+ * and the method parameters with their actual names extracted from the LVT when available.
+ *
+ * @param method the method to construct locals for
+ * @param classNode the class containing the method
+ * @param fabricCompatibility the compatibility level to use
+ * @return an array of LocalVariableNodes representing the initial method frame
+ */
+ public static LocalVariableNode[] getInitialMethodLocals(MethodNode method, ClassNode classNode, int fabricCompatibility) {
+ return getInitialMethodLocals(method, classNode, fabricCompatibility, false);
+ }
+
+ /**
+ * Constructs the initial local variable frame for a method, containing only "this" (if non-static)
+ * and the method parameters with their actual names extracted from the LVT when available.
+ *
+ * @param method the method to construct locals for
+ * @param classNode the class containing the method
+ * @param fabricCompatibility the compatibility level to use
+ * @param fallbackToLvIndex whether the fallback names should use the LVT index of the variable (for backwards compatibility)
+ * @return an array of LocalVariableNodes representing the initial method frame
+ */
+ public static LocalVariableNode[] getInitialMethodLocals(MethodNode method, ClassNode classNode, int fabricCompatibility, boolean fallbackToLvIndex) {
+ boolean isStatic = Bytecode.isStatic(method);
+ Type[] argTypes = Type.getArgumentTypes(method.desc);
+ int initialFrameSize = Bytecode.getFirstNonArgLocalIndex(method);
+ LocalVariableNode[] frame = new LocalVariableNode[initialFrameSize];
+
+ int local = 0;
+
+ // Try to extract parameter names from LVT if compatibility level is high enough
+ String[] paramNames = fabricCompatibility >= org.spongepowered.asm.mixin.FabricUtil.COMPATIBILITY_0_17_0
+ ? getParameterNames(method, isStatic)
+ : new String[argTypes.length];
+
+ // Initialise implicit "this" reference in non-static methods
+ if (!isStatic) {
+ frame[local++] = new LocalVariableNode("this", Type.getObjectType(classNode.name).getDescriptor(), null, null, null, 0);
+ }
+
+ // Initialise method arguments
+ for (int index = 0; index < argTypes.length; index++) {
+ Type argType = argTypes[index];
+ String paramName = paramNames[index];
+ if (paramName == null) {
+ if (fallbackToLvIndex) {
+ paramName = "arg" + local;
+ } else {
+ paramName = "arg" + index;
+ }
+ }
+ frame[local] = new LocalVariableNode(paramName, argType.getDescriptor(), null, null, null, local);
+ local += argType.getSize();
+ }
+
+ return frame;
+ }
+
+ /**
+ * Attempts to extract parameter names from the method's local variable table.
+ * This is used as a fallback when detailed LVT information is not available at a specific
+ * instruction, but we still want to use actual parameter names instead of generic "arg" names.
+ *
+ * @param method the method to extract parameter names from
+ * @param isStatic whether the method is static (affects the starting local index)
+ * @return an array of parameter names, with null entries where names are unavailable
+ */
+ private static String[] getParameterNames(MethodNode method, boolean isStatic) {
+ Type[] argTypes = Type.getArgumentTypes(method.desc);
+ if (argTypes.length == 0) {
+ return new String[0];
+ }
+
+ // Always allocate array for all parameters
+ String[] paramNames = new String[argTypes.length];
+
+ // Build map of local variable index to parameter index
+ Map indexToParam = new HashMap<>();
+ int localIndex = isStatic ? 0 : 1; // Skip "this" for non-static methods
+ for (int arg = 0; arg < argTypes.length; arg++) {
+ indexToParam.put(localIndex, arg);
+ localIndex += argTypes[arg].getSize();
+ }
+
+ // Single pass through local variable table to extract names
+ if (method.localVariables != null) {
+ for (LocalVariableNode lvNode : method.localVariables) {
+ Integer paramIndex = indexToParam.get(lvNode.index);
+ if (paramIndex != null) {
+ paramNames[paramIndex] = lvNode.name;
+ }
+ }
+ }
+
+ // Merge in names from parameters field where LVT didn't provide them
+ if (method.parameters != null) {
+ for (int i = 0; i < Math.min(argTypes.length, method.parameters.size()); i++) {
+ if (paramNames[i] == null) {
+ paramNames[i] = method.parameters.get(i).name;
+ }
+ }
+ }
+
+ return paramNames;
+ }
+
/**
* Injects appropriate LOAD opcodes into the supplied InsnList for each
* entry in the supplied locals array starting at pos
- *
+ *
* @param locals Local types (can contain nulls for uninitialised, TOP, or
* RETURN values in locals)
* @param insns Instruction List to inject into
@@ -334,7 +441,7 @@ public static void loadLocals(Type[] locals, InsnList insns, int pos, int limit)
*/
public static LocalVariableNode[] getLocalsAt(ClassNode classNode, MethodNode method, AbstractInsnNode node, int fabricCompatibility) {
if (fabricCompatibility >= org.spongepowered.asm.mixin.FabricUtil.COMPATIBILITY_0_10_0) {
- return Locals.getLocalsAt(classNode, method, node, Settings.DEFAULT);
+ return Locals.getLocalsAt(classNode, method, node, Settings.DEFAULT, fabricCompatibility);
} else {
return getLocalsAt092(classNode, method, node);
}
@@ -343,16 +450,16 @@ public static LocalVariableNode[] getLocalsAt(ClassNode classNode, MethodNode me
/**
* Attempts to identify available locals at an arbitrary point in the
* bytecode specified by node.
- *
+ *
* This method builds an approximate view of the locals available at an
* arbitrary point in the bytecode by examining the following features in
- * the bytecode:
+ * the bytecode:
*
* - Any available stack map frames
* - STORE opcodes
* - The local variable table
*
- *
+ *
* Inference proceeds by walking the bytecode from the start of the
* method looking for stack frames and STORE opcodes. When either of these
* is encountered, an attempt is made to cross-reference the values in the
@@ -363,14 +470,14 @@ public static LocalVariableNode[] getLocalsAt(ClassNode classNode, MethodNode me
* simulated locals array are spaced according to their size (unlike the
* representation in FrameNode) and this TOP, NULL and UNINTITIALIZED_THIS
* opcodes will be represented as null values in the simulated frame.
- *
+ *
* This code does not currently simulate the prescribed JVM behaviour
* where overwriting the second slot of a DOUBLE or LONG actually
* invalidates the DOUBLE or LONG stored in the previous location, so we
* have to hope (for now) that this behaviour isn't emitted by the compiler
* or any upstream transformers. I may have to re-think this strategy if
* this situation is encountered in the wild.
- *
+ *
* @param classNode ClassNode containing the method, used to initialise the
* implicit "this" reference in simple methods with no stack frames
* @param method MethodNode to explore
@@ -384,6 +491,10 @@ public static LocalVariableNode[] getLocalsAt(ClassNode classNode, MethodNode me
* specified location
*/
public static LocalVariableNode[] getLocalsAt(ClassNode classNode, MethodNode method, AbstractInsnNode node, Settings settings) {
+ return getLocalsAt(classNode, method, node, settings, org.spongepowered.asm.mixin.FabricUtil.COMPATIBILITY_LATEST);
+ }
+
+ private static LocalVariableNode[] getLocalsAt(ClassNode classNode, MethodNode method, AbstractInsnNode node, Settings settings, int fabricCompatibility) {
for (int i = 0; i < 3 && (node instanceof LabelNode || node instanceof LineNumberNode); i++) {
AbstractInsnNode nextNode = Locals.nextNode(method.instructions, node);
if (nextNode instanceof FrameNode) { // Do not ffwd over frames
@@ -402,25 +513,16 @@ public static LocalVariableNode[] getLocalsAt(ClassNode classNode, MethodNode me
}
List frames = methodInfo.getFrames();
+ // Initialize the frame with "this" and method parameters
+ LocalVariableNode[] initialLocals = getInitialMethodLocals(method, classNode, fabricCompatibility);
LocalVariableNode[] frame = new LocalVariableNode[method.maxLocals];
- int local = 0, index = 0;
+ System.arraycopy(initialLocals, 0, frame, 0, initialLocals.length);
- // Initialise implicit "this" reference in non-static methods
- if ((method.access & Opcodes.ACC_STATIC) == 0) {
- frame[local++] = new LocalVariableNode("this", Type.getObjectType(classNode.name).toString(), null, null, null, 0);
- }
-
- // Initialise method arguments
- for (Type argType : Type.getArgumentTypes(method.desc)) {
- frame[local] = new LocalVariableNode("arg" + index++, argType.toString(), null, null, null, local);
- local += argType.getSize();
- }
-
- final int initialFrameSize = local;
- int frameSize = local;
+ final int initialFrameSize = initialLocals.length;
+ int frameSize = initialFrameSize;
int frameIndex = -1;
- int lastFrameSize = local;
- int knownFrameSize = local;
+ int lastFrameSize = initialFrameSize;
+ int knownFrameSize = initialFrameSize;
VarInsnNode storeInsn = null;
for (Iterator iter = method.instructions.iterator(); iter.hasNext();) {
diff --git a/src/main/java/org/spongepowered/asm/util/throwables/SyntheticBridgeException.java b/src/main/java/org/spongepowered/asm/util/throwables/SyntheticBridgeException.java
index 0138c6bdd..17042a836 100644
--- a/src/main/java/org/spongepowered/asm/util/throwables/SyntheticBridgeException.java
+++ b/src/main/java/org/spongepowered/asm/util/throwables/SyntheticBridgeException.java
@@ -125,15 +125,15 @@ public void printAnalysis(IMixinContext context, MethodNode mda, MethodNode mdb)
printer.add().kv("Method", this.name + this.desc).kv("Problem Type", this.problem).add().hr();
String merged = Annotations.getValue(Annotations.getVisible(mda, MixinMerged.class), "mixin");
String owner = merged != null ? merged : context.getTargetClassRef().replace('/', '.');
- this.printMethod(printer.add("Existing method").add().kv("Owner", owner).add(), mda).hr();
- this.printMethod(printer.add("Incoming method").add().kv("Owner", context.getClassRef().replace('/', '.')).add(), mdb).hr();
+ this.printMethod(printer.add("Existing method").add().kv("Owner", owner).add(), mda, a).hr();
+ this.printMethod(printer.add("Incoming method").add().kv("Owner", context.getClassRef().replace('/', '.')).add(), mdb, b).hr();
this.printProblem(printer, context, mda, mdb).print(System.err);
}
- private PrettyPrinter printMethod(PrettyPrinter printer, MethodNode method) {
- int index = 0;
- for (Iterator iter = method.instructions.iterator(); iter.hasNext(); index++) {
- printer.kv(index == this.index ? ">>>>" : "", Bytecode.describeNode(iter.next()));
+ private PrettyPrinter printMethod(PrettyPrinter printer, MethodNode method, AbstractInsnNode marker) {
+ for (Iterator iter = method.instructions.iterator(); iter.hasNext();) {
+ AbstractInsnNode insn = iter.next();
+ printer.kv(insn == marker ? ">>>>" : "", Bytecode.describeNode(insn));
}
return printer.add();
}