From 8b7fb2eed5d56ce2107fc2004481b27c9e4ae647 Mon Sep 17 00:00:00 2001
From: FirstMegaGame4 <84094287+FirstMegaGame4@users.noreply.github.com>
Date: Fri, 29 Mar 2024 11:33:43 +0100
Subject: [PATCH] Feature: `@Requires` Annotation (#414)
* Add Requires Annotation
* Fix
* Add PackageRequiresStrippingVisitor
* Missing Symbol :/
* Blah Blah Blah Usage Conventions
* Blah Blah Blah License Stuff
* `value` instead of `mods`
* moving it to Top API Level
* Compacting Code => So Long, Stripping Data Containers...
* Removing Deprecated Fields
* Removing Deprecated Inner Classes
* I a word
---
.../java/org/quiltmc/loader/api/Requires.java | 58 +++
.../impl/launch/knot/KnotClassDelegate.java | 14 +-
.../transformer/EnvironmentStrippingData.java | 232 ++----------
.../PackageEnvironmentStrippingData.java | 35 +-
.../transformer/PackageStrippingData.java | 88 +++++
.../impl/transformer/QuiltTransformer.java | 22 +-
.../impl/transformer/StrippingData.java | 341 ++++++++++++++++++
.../test/lambda_strip/LambdaStripTester.java | 14 +-
8 files changed, 550 insertions(+), 254 deletions(-)
create mode 100644 src/main/java/org/quiltmc/loader/api/Requires.java
create mode 100644 src/main/java/org/quiltmc/loader/impl/transformer/PackageStrippingData.java
create mode 100644 src/main/java/org/quiltmc/loader/impl/transformer/StrippingData.java
diff --git a/src/main/java/org/quiltmc/loader/api/Requires.java b/src/main/java/org/quiltmc/loader/api/Requires.java
new file mode 100644
index 000000000..c6ddeadc9
--- /dev/null
+++ b/src/main/java/org/quiltmc/loader/api/Requires.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016 FabricMC
+ * Copyright 2022-2023 QuiltMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.quiltmc.loader.api;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Applied to declare that the annotated element requires specific mods to exist.
+ *
+ * When applied to mod code this will result in quilt-loader removing that element when running without the specified mods.
+ *
+ * When the annotated element is removed, bytecode associated with the element will not be removed. For example, if a
+ * field is removed, its initializer code will not, and will cause an error on execution.
+ *
+ * If an overriding method has this annotation and its overridden method doesn't, unexpected behavior may happen. If an
+ * overridden method has this annotation while the overriding method doesn't, it is safe, but the method can be used
+ * from the overridden class only when specified mods are found.
+ *
+ * When applied to an implemented interface (via {@link ElementType#TYPE_USE}) it will be removed when running without the specified mods.
+ * Overridden interface methods need to be annotated separately in order to be removed.
+ *
+ * @apiNote Even is this annotation is provided by Quilt Loader, some usages of it are not recommended. In example, when
+ * you are using code from other mods thanks to their APIs using entrypoints, you should not implement their entrypoint
+ * interface on the same classes that you are using for vanilla entrypoints. Instead, you should use a separate class in
+ * a separated package, dedicated to the mod integration that you are making. And to continue this example by a great usage
+ * of this annotation, you could use it in a new package-info.java file of the current package dedicated for the
+ * mod integration, in order to trim the complete package, which will provide an additional safety layer by avoiding
+ * errors when loading and / or calling classes and elements that should not when the required mod is not present. */
+@Retention(RetentionPolicy.CLASS)
+@Target({ ElementType.TYPE, ElementType.TYPE_USE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PACKAGE })
+@Documented
+public @interface Requires {
+
+ /** @return required mods */
+ String[] value();
+
+ /** @return True if lambda methods referenced by this method should also be stripped. Has no effect when used to
+ * annotate classes, implements declarations, or fields. */
+ boolean stripLambdas() default true;
+}
diff --git a/src/main/java/org/quiltmc/loader/impl/launch/knot/KnotClassDelegate.java b/src/main/java/org/quiltmc/loader/impl/launch/knot/KnotClassDelegate.java
index a656c5cdf..f63748dbf 100644
--- a/src/main/java/org/quiltmc/loader/impl/launch/knot/KnotClassDelegate.java
+++ b/src/main/java/org/quiltmc/loader/impl/launch/knot/KnotClassDelegate.java
@@ -17,6 +17,8 @@
package org.quiltmc.loader.impl.launch.knot;
import net.fabricmc.api.EnvType;
+
+import org.quiltmc.loader.impl.transformer.PackageStrippingData;
import org.quiltmc.loader.impl.util.LoaderUtil;
import org.objectweb.asm.ClassReader;
import org.quiltmc.loader.api.ModContainer;
@@ -26,7 +28,6 @@
import org.quiltmc.loader.impl.launch.common.QuiltCodeSource;
import org.quiltmc.loader.impl.launch.common.QuiltLauncherBase;
import org.quiltmc.loader.impl.patch.PatchLoader;
-import org.quiltmc.loader.impl.transformer.PackageEnvironmentStrippingData;
import org.quiltmc.loader.impl.util.FileSystemUtil;
import org.quiltmc.loader.impl.util.FileUtil;
import org.quiltmc.loader.impl.util.ManifestUtil;
@@ -51,7 +52,6 @@
import java.security.CodeSource;
import java.security.cert.Certificate;
import java.util.Collections;
-import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@@ -322,9 +322,13 @@ boolean computeCanLoadPackage(String pkgName, boolean allowFromParent) {
// No package-info class file
return true;
}
- PackageEnvironmentStrippingData data = new PackageEnvironmentStrippingData(QuiltLoaderImpl.ASM_VERSION, envType);
- new ClassReader(bytes).accept(data, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES);
- return !data.stripEntirePackage;
+
+ ClassReader reader = new ClassReader(bytes);
+
+ PackageStrippingData strippingData = new PackageStrippingData(QuiltLoaderImpl.ASM_VERSION, envType, modCodeSourceMap);
+ reader.accept(strippingData, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES);
+
+ return !strippingData.stripEntirePackage();
} catch (IOException e) {
throw new RuntimeException("Unable to load " + fileName, e);
}
diff --git a/src/main/java/org/quiltmc/loader/impl/transformer/EnvironmentStrippingData.java b/src/main/java/org/quiltmc/loader/impl/transformer/EnvironmentStrippingData.java
index 7d939803b..32e268bc9 100644
--- a/src/main/java/org/quiltmc/loader/impl/transformer/EnvironmentStrippingData.java
+++ b/src/main/java/org/quiltmc/loader/impl/transformer/EnvironmentStrippingData.java
@@ -16,265 +16,79 @@
package org.quiltmc.loader.impl.transformer;
+import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashSet;
import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Type;
import org.objectweb.asm.TypePath;
-import org.objectweb.asm.TypeReference;
-import org.quiltmc.loader.api.minecraft.ClientOnly;
-import org.quiltmc.loader.api.minecraft.DedicatedServerOnly;
import org.quiltmc.loader.impl.util.QuiltLoaderInternal;
import org.quiltmc.loader.impl.util.QuiltLoaderInternalType;
import net.fabricmc.api.EnvType;
-import net.fabricmc.api.Environment;
-import net.fabricmc.api.EnvironmentInterface;
-import net.fabricmc.api.EnvironmentInterfaces;
-/** Scans a class for Environment and EnvironmentInterface annotations to figure out what needs to be stripped. */
+/** Deprecated. All stuff were moved to {@link StrippingData}. */
+@Deprecated
@QuiltLoaderInternal(QuiltLoaderInternalType.LEGACY_EXPOSED)
-public class EnvironmentStrippingData extends ClassVisitor {
- // Fabric annotations
- private static final String ENVIRONMENT_DESCRIPTOR = Type.getDescriptor(Environment.class);
- private static final String ENVIRONMENT_INTERFACE_DESCRIPTOR = Type.getDescriptor(EnvironmentInterface.class);
- private static final String ENVIRONMENT_INTERFACES_DESCRIPTOR = Type.getDescriptor(EnvironmentInterfaces.class);
-
- // Quilt annotations
- private static final String CLIENT_ONLY_DESCRIPTOR = Type.getDescriptor(ClientOnly.class);
- private static final String SERVER_ONLY_DESCRIPTOR = Type.getDescriptor(DedicatedServerOnly.class);
-
- private final EnvType envType;
- private final String envTypeString;
-
- private boolean stripEntireClass = false;
- private String[] interfaces;
- private final Collection stripInterfaces = new HashSet<>();
- private final Collection stripFields = new HashSet<>();
- private final Collection stripMethods = new HashSet<>();
-
- /** Every method contained in this will also be contained in {@link #stripMethods}. */
- private final Collection stripMethodLambdas = new HashSet<>();
-
- private class FabricEnvironmentAnnotationVisitor extends AnnotationVisitor {
- private final Runnable onEnvMismatch;
-
- private FabricEnvironmentAnnotationVisitor(int api, Runnable onEnvMismatch) {
- super(api);
- this.onEnvMismatch = onEnvMismatch;
- }
-
- @Override
- public void visitEnum(String name, String descriptor, String value) {
- if ("value".equals(name) && !envTypeString.equals(value)) {
- onEnvMismatch.run();
- }
- }
- }
-
- private class QuiltEnvironmentAnnotationVisitor extends AnnotationVisitor {
- private final Runnable onEnvMismatch;
- private final Runnable onEnvMismatchLambdas;
-
- private boolean stripLambdas = true;
-
- private QuiltEnvironmentAnnotationVisitor(int api, Runnable onEnvMismatch, Runnable onEnvMismatchLambda) {
- super(api);
- this.onEnvMismatch = onEnvMismatch;
- this.onEnvMismatchLambdas = onEnvMismatchLambda;
- }
-
- @Override
- public void visit(String name, Object value) {
- if ("stripLambdas".equals(name) && Boolean.FALSE.equals(value)) {
- stripLambdas = false;
- }
- }
-
- @Override
- public void visitEnd() {
- onEnvMismatch.run();
- if (stripLambdas && onEnvMismatchLambdas != null) {
- onEnvMismatchLambdas.run();
- }
- }
- }
-
- private class FabricEnvironmentInterfaceAnnotationVisitor extends AnnotationVisitor {
- private boolean envMismatch;
- private Type itf;
-
- private FabricEnvironmentInterfaceAnnotationVisitor(int api) {
- super(api);
- }
-
- @Override
- public void visitEnum(String name, String descriptor, String value) {
- if ("value".equals(name) && !envTypeString.equals(value)) {
- envMismatch = true;
- }
- }
-
- @Override
- public void visit(String name, Object value) {
- if ("itf".equals(name)) {
- itf = (Type) value;
- }
- }
-
- @Override
- public void visitEnd() {
- if (envMismatch) {
- stripInterfaces.add(itf.getInternalName());
- }
- }
- }
-
- private AnnotationVisitor visitMemberAnnotation(String descriptor, boolean visible, Runnable onEnvMismatch,
- Runnable onEnvMismatchLambdas) {
- if (ENVIRONMENT_DESCRIPTOR.equals(descriptor)) {
- return new FabricEnvironmentAnnotationVisitor(api, onEnvMismatch);
- }
-
- if (CLIENT_ONLY_DESCRIPTOR.equals(descriptor) && envType == EnvType.SERVER) {
- return new QuiltEnvironmentAnnotationVisitor(api, onEnvMismatch, onEnvMismatchLambdas);
- }
-
- if (SERVER_ONLY_DESCRIPTOR.equals(descriptor) && envType == EnvType.CLIENT) {
- return new QuiltEnvironmentAnnotationVisitor(api, onEnvMismatch, onEnvMismatchLambdas);
- }
-
- return null;
- }
+public class EnvironmentStrippingData extends StrippingData {
public EnvironmentStrippingData(int api, EnvType envType) {
- super(api);
- this.envType = envType;
- this.envTypeString = envType.name();
+ super(api, envType, new ArrayList<>());
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
- this.interfaces = interfaces;
+ super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
- if (CLIENT_ONLY_DESCRIPTOR.equals(descriptor)) {
- if (envType == EnvType.SERVER) {
- stripEntireClass = true;
- }
- } else if (SERVER_ONLY_DESCRIPTOR.equals(descriptor)) {
- if (envType == EnvType.CLIENT) {
- stripEntireClass = true;
- }
- } else if (ENVIRONMENT_DESCRIPTOR.equals(descriptor)) {
- return new FabricEnvironmentAnnotationVisitor(api, () -> stripEntireClass = true);
- } else if (ENVIRONMENT_INTERFACE_DESCRIPTOR.equals(descriptor)) {
- return new FabricEnvironmentInterfaceAnnotationVisitor(api);
- } else if (ENVIRONMENT_INTERFACES_DESCRIPTOR.equals(descriptor)) {
- return new AnnotationVisitor(api) {
- @Override
- public AnnotationVisitor visitArray(String name) {
- if ("value".equals(name)) {
- return new AnnotationVisitor(api) {
- @Override
- public AnnotationVisitor visitAnnotation(String name, String descriptor) {
- return new FabricEnvironmentInterfaceAnnotationVisitor(api);
- }
- };
- }
-
- return null;
- }
- };
- }
-
- return null;
+ return super.visitAnnotation(descriptor, visible);
}
@Override
public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {
-
- TypeReference ref = new TypeReference(typeRef);
-
- if (ref.getSort() != TypeReference.CLASS_EXTENDS) {
- return null;
- }
-
- int interfaceIdx = ref.getSuperTypeIndex();
-
- if (interfaceIdx < 0) {
- // Wrongly applied to the super class
- return null;
- }
-
- final EnvType annotationEnv;
-
- if (CLIENT_ONLY_DESCRIPTOR.equals(descriptor)) {
- annotationEnv = EnvType.CLIENT;
- } else if (SERVER_ONLY_DESCRIPTOR.equals(descriptor)) {
- annotationEnv = EnvType.SERVER;
- } else {
- return null;
- }
-
- if (annotationEnv != envType) {
- stripInterfaces.add(interfaces[interfaceIdx]);
- }
-
- return null;
+ return super.visitTypeAnnotation(typeRef, typePath, descriptor, visible);
}
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
- return new FieldVisitor(api) {
- @Override
- public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
- return visitMemberAnnotation(descriptor, visible, () -> stripFields.add(name + descriptor), null);
- }
- };
+ return super.visitField(access, name, descriptor, signature, value);
}
@Override
- public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
- String[] exceptions) {
- String methodId = name + descriptor;
- return new MethodVisitor(api) {
- @Override
- public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
- return visitMemberAnnotation(
- descriptor, visible, () -> stripMethods.add(methodId), () -> stripMethodLambdas.add(methodId)
- );
- }
- };
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
+ return super.visitMethod(access, name, descriptor, signature, exceptions);
}
+ @Override
public boolean stripEntireClass() {
- return stripEntireClass;
+ return super.stripEntireClass();
}
+ @Override
public Collection getStripInterfaces() {
- return stripInterfaces;
+ return super.getStripInterfaces();
}
+ @Override
public Collection getStripFields() {
- return stripFields;
+ return super.getStripFields();
}
+ @Override
public Collection getStripMethods() {
- return stripMethods;
+ return super.getStripMethods();
}
+ @Override
public Collection getStripMethodLambdas() {
- return stripMethodLambdas;
+ return super.getStripMethodLambdas();
}
+ @Override
public boolean isEmpty() {
- return stripInterfaces.isEmpty() && stripFields.isEmpty() && stripMethods.isEmpty();
+ return super.isEmpty();
}
}
diff --git a/src/main/java/org/quiltmc/loader/impl/transformer/PackageEnvironmentStrippingData.java b/src/main/java/org/quiltmc/loader/impl/transformer/PackageEnvironmentStrippingData.java
index 80b41ebf6..9989eba5d 100644
--- a/src/main/java/org/quiltmc/loader/impl/transformer/PackageEnvironmentStrippingData.java
+++ b/src/main/java/org/quiltmc/loader/impl/transformer/PackageEnvironmentStrippingData.java
@@ -16,41 +16,30 @@
package org.quiltmc.loader.impl.transformer;
+import java.util.HashMap;
+
import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.Type;
-import org.quiltmc.loader.api.minecraft.ClientOnly;
-import org.quiltmc.loader.api.minecraft.DedicatedServerOnly;
import org.quiltmc.loader.impl.util.QuiltLoaderInternal;
import org.quiltmc.loader.impl.util.QuiltLoaderInternalType;
import net.fabricmc.api.EnvType;
+/** Deprecated. All stuff were moved to {@link PackageStrippingData}. */
+@Deprecated
@QuiltLoaderInternal(QuiltLoaderInternalType.LEGACY_EXPOSED)
-public class PackageEnvironmentStrippingData extends ClassVisitor {
-
- private static final String CLIENT_ONLY_DESCRIPTOR = Type.getDescriptor(ClientOnly.class);
- private static final String SERVER_ONLY_DESCRIPTOR = Type.getDescriptor(DedicatedServerOnly.class);
-
- private final EnvType envType;
- public boolean stripEntirePackage = false;
+public class PackageEnvironmentStrippingData extends PackageStrippingData {
public PackageEnvironmentStrippingData(int api, EnvType envType) {
- super(api);
- this.envType = envType;
+ super(api, envType, new HashMap<>());
}
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
- if (CLIENT_ONLY_DESCRIPTOR.equals(descriptor)) {
- if (envType == EnvType.SERVER) {
- stripEntirePackage = true;
- }
- } else if (SERVER_ONLY_DESCRIPTOR.equals(descriptor)) {
- if (envType == EnvType.CLIENT) {
- stripEntirePackage = true;
- }
- }
- return null;
+ return super.visitAnnotation(descriptor, visible);
+ }
+
+ @Override
+ public boolean stripEntirePackage() {
+ return super.stripEntirePackage();
}
}
diff --git a/src/main/java/org/quiltmc/loader/impl/transformer/PackageStrippingData.java b/src/main/java/org/quiltmc/loader/impl/transformer/PackageStrippingData.java
new file mode 100644
index 000000000..63b459cea
--- /dev/null
+++ b/src/main/java/org/quiltmc/loader/impl/transformer/PackageStrippingData.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2022, 2023 QuiltMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.quiltmc.loader.impl.transformer;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Type;
+import org.quiltmc.loader.api.Requires;
+import org.quiltmc.loader.api.minecraft.ClientOnly;
+import org.quiltmc.loader.api.minecraft.DedicatedServerOnly;
+import org.quiltmc.loader.impl.util.QuiltLoaderInternal;
+import org.quiltmc.loader.impl.util.QuiltLoaderInternalType;
+
+import net.fabricmc.api.EnvType;
+
+@QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL)
+public class PackageStrippingData extends ClassVisitor {
+
+ private static final String CLIENT_ONLY_DESCRIPTOR = Type.getDescriptor(ClientOnly.class);
+ private static final String SERVER_ONLY_DESCRIPTOR = Type.getDescriptor(DedicatedServerOnly.class);
+ private static final String REQUIRES_DESCRIPTOR = Type.getDescriptor(Requires.class);
+
+ private final EnvType envType;
+ private final List mods;
+
+ private boolean stripEntirePackage = false;
+
+ public PackageStrippingData(int api, EnvType envType, Map modCodeSourceMap) {
+ super(api);
+ this.envType = envType;
+ this.mods = new ArrayList<>(modCodeSourceMap.keySet());
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ if (CLIENT_ONLY_DESCRIPTOR.equals(descriptor)) {
+ if (envType == EnvType.SERVER) {
+ stripEntirePackage = true;
+ }
+ } else if (SERVER_ONLY_DESCRIPTOR.equals(descriptor)) {
+ if (envType == EnvType.CLIENT) {
+ stripEntirePackage = true;
+ }
+ } else if (REQUIRES_DESCRIPTOR.equals(descriptor)) {
+ return new AnnotationVisitor(api) {
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ if ("value".equals(name)) {
+ return new AnnotationVisitor(api) {
+ @Override
+ public void visit(String name, Object value) {
+ if (!mods.contains(String.valueOf(value))) {
+ stripEntirePackage = true;
+ }
+ }
+ };
+ }
+ else {
+ return null;
+ }
+ }
+ };
+ }
+ return null;
+ }
+
+ public boolean stripEntirePackage() {
+ return this.stripEntirePackage;
+ }
+}
diff --git a/src/main/java/org/quiltmc/loader/impl/transformer/QuiltTransformer.java b/src/main/java/org/quiltmc/loader/impl/transformer/QuiltTransformer.java
index 1cdb1d745..ede8c5b1c 100644
--- a/src/main/java/org/quiltmc/loader/impl/transformer/QuiltTransformer.java
+++ b/src/main/java/org/quiltmc/loader/impl/transformer/QuiltTransformer.java
@@ -39,10 +39,10 @@ final class QuiltTransformer {
public static byte @Nullable [] transform(boolean isDevelopment, EnvType envType, TransformCache cache, AccessWidener accessWidener, String name, ModLoadOption mod, byte[] bytes) {
boolean isMinecraftClass = mod.id().equals("minecraft");
boolean transformAccess = isMinecraftClass && QuiltLauncherBase.getLauncher().getMappingConfiguration().requiresPackageAccessHack();
- boolean environmentStrip = !isMinecraftClass || isDevelopment;
+ boolean strip = !isMinecraftClass || isDevelopment;
boolean applyAccessWidener = isMinecraftClass && accessWidener.getTargets().contains(name);
- if (!transformAccess && !environmentStrip && !applyAccessWidener) {
+ if (!transformAccess && !strip && !applyAccessWidener) {
return bytes;
}
@@ -51,21 +51,21 @@ final class QuiltTransformer {
ClassVisitor visitor = null;
int visitorCount = 0;
- if (environmentStrip) {
- EnvironmentStrippingData stripData = new EnvironmentStrippingData(QuiltLoaderImpl.ASM_VERSION, envType);
- classReader.accept(stripData, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES);
+ if (strip) {
+ StrippingData data = new StrippingData(QuiltLoaderImpl.ASM_VERSION, envType, cache.getMods());
+ classReader.accept(data, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES);
- if (stripData.stripEntireClass()) {
+ if (data.stripEntireClass()) {
cache.hideClass(name);
return null;
}
- Collection stripMethods = stripData.getStripMethods();
+ Collection stripMethods = data.getStripMethods();
boolean stripAnyLambdas = false;
- if (!stripData.getStripMethodLambdas().isEmpty()) {
- LambdaStripCalculator calc = new LambdaStripCalculator(QuiltLoaderImpl.ASM_VERSION, stripData.getStripMethodLambdas());
+ if (!data.getStripMethodLambdas().isEmpty()) {
+ LambdaStripCalculator calc = new LambdaStripCalculator(QuiltLoaderImpl.ASM_VERSION, data.getStripMethodLambdas());
classReader.accept(calc, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
Collection additionalStripMethods = calc.computeAdditionalMethodsToStrip();
@@ -77,7 +77,7 @@ final class QuiltTransformer {
}
}
- if (!stripData.isEmpty()) {
+ if (!data.isEmpty()) {
if (stripAnyLambdas) {
// ClassWriter has a (useful) optimisation that copies over the
@@ -90,7 +90,7 @@ final class QuiltTransformer {
classWriter = new ClassWriter(classReader, 0);
}
- visitor = new ClassStripper(QuiltLoaderImpl.ASM_VERSION, classWriter, stripData.getStripInterfaces(), stripData.getStripFields(), stripMethods);
+ visitor = new ClassStripper(QuiltLoaderImpl.ASM_VERSION, classWriter, data.getStripInterfaces(), data.getStripFields(), stripMethods);
visitorCount++;
}
}
diff --git a/src/main/java/org/quiltmc/loader/impl/transformer/StrippingData.java b/src/main/java/org/quiltmc/loader/impl/transformer/StrippingData.java
new file mode 100644
index 000000000..8b62d840c
--- /dev/null
+++ b/src/main/java/org/quiltmc/loader/impl/transformer/StrippingData.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2022, 2023 QuiltMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.quiltmc.loader.impl.transformer;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.TypePath;
+import org.objectweb.asm.TypeReference;
+import org.quiltmc.loader.api.Requires;
+import org.quiltmc.loader.api.minecraft.ClientOnly;
+import org.quiltmc.loader.api.minecraft.DedicatedServerOnly;
+import org.quiltmc.loader.api.plugin.solver.ModLoadOption;
+import org.quiltmc.loader.impl.util.QuiltLoaderInternal;
+import org.quiltmc.loader.impl.util.QuiltLoaderInternalType;
+
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.api.EnvironmentInterface;
+import net.fabricmc.api.EnvironmentInterfaces;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** Scans a class for Environment, EnvironmentInterface and Requires annotations to figure out what needs to be stripped. */
+@QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL)
+public class StrippingData extends ClassVisitor {
+
+ // Fabric annotations
+ private static final String ENVIRONMENT_DESCRIPTOR = Type.getDescriptor(Environment.class);
+ private static final String ENVIRONMENT_INTERFACE_DESCRIPTOR = Type.getDescriptor(EnvironmentInterface.class);
+ private static final String ENVIRONMENT_INTERFACES_DESCRIPTOR = Type.getDescriptor(EnvironmentInterfaces.class);
+
+ // Quilt annotations
+ private static final String CLIENT_ONLY_DESCRIPTOR = Type.getDescriptor(ClientOnly.class);
+ private static final String SERVER_ONLY_DESCRIPTOR = Type.getDescriptor(DedicatedServerOnly.class);
+ private static final String REQUIRES_DESCRIPTOR = Type.getDescriptor(Requires.class);
+
+ private final EnvType envType;
+ private final String envTypeString;
+ private final List mods;
+
+ private boolean stripEntireClass = false;
+ private final Collection stripInterfaces = new HashSet<>();
+ private final Collection stripFields = new HashSet<>();
+ private final Collection stripMethods = new HashSet<>();
+
+ /** Every method contained in this will also be contained in {@link #stripMethods}. */
+ final Collection stripMethodLambdas = new HashSet<>();
+
+ private String[] interfaces;
+
+ private class FabricEnvironmentAnnotationVisitor extends AnnotationVisitor {
+ private final Runnable onEnvMismatch;
+
+ private FabricEnvironmentAnnotationVisitor(int api, Runnable onEnvMismatch) {
+ super(api);
+ this.onEnvMismatch = onEnvMismatch;
+ }
+
+ @Override
+ public void visitEnum(String name, String descriptor, String value) {
+ if ("value".equals(name) && !envTypeString.equals(value)) {
+ onEnvMismatch.run();
+ }
+ }
+ }
+
+ private class FabricEnvironmentInterfaceAnnotationVisitor extends AnnotationVisitor {
+ private boolean envMismatch;
+ private Type itf;
+
+ private FabricEnvironmentInterfaceAnnotationVisitor(int api) {
+ super(api);
+ }
+
+ @Override
+ public void visitEnum(String name, String descriptor, String value) {
+ if ("value".equals(name) && !envTypeString.equals(value)) {
+ envMismatch = true;
+ }
+ }
+
+ @Override
+ public void visit(String name, Object value) {
+ if ("itf".equals(name)) {
+ itf = (Type) value;
+ }
+ }
+
+ @Override
+ public void visitEnd() {
+ if (envMismatch) {
+ stripInterfaces.add(itf.getInternalName());
+ }
+ }
+ }
+
+ private class QuiltEnvironmentAnnotationVisitor extends AnnotationVisitor {
+ private final Runnable onEnvMismatch;
+ private final Runnable onEnvMismatchLambdas;
+
+ private boolean stripLambdas = true;
+
+ private QuiltEnvironmentAnnotationVisitor(int api, Runnable onEnvMismatch, Runnable onEnvMismatchLambdas) {
+ super(api);
+ this.onEnvMismatch = onEnvMismatch;
+ this.onEnvMismatchLambdas = onEnvMismatchLambdas;
+ }
+
+ @Override
+ public void visit(String name, Object value) {
+ if ("stripLambdas".equals(name) && Boolean.FALSE.equals(value)) {
+ stripLambdas = false;
+ }
+ }
+
+ @Override
+ public void visitEnd() {
+ onEnvMismatch.run();
+ if (stripLambdas && onEnvMismatchLambdas != null) {
+ onEnvMismatchLambdas.run();
+ }
+ }
+ }
+
+ private class QuiltRequiresAnnotationVisitor extends AnnotationVisitor {
+ private final Runnable onModsMismatch;
+ private final Runnable onModsMismatchLambdas;
+
+ private boolean stripLambdas = true;
+
+ private QuiltRequiresAnnotationVisitor(int api, Runnable onModsMismatch, Runnable onModsMismatchLambdas) {
+ super(api);
+ this.onModsMismatch = onModsMismatch;
+ this.onModsMismatchLambdas = onModsMismatchLambdas;
+ }
+
+ @Override
+ public void visit(String name, Object value) {
+ if ("stripLambdas".equals(name) && Boolean.FALSE.equals(value)) {
+ stripLambdas = false;
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ if ("value".equals(name)) {
+ return new AnnotationVisitor(api) {
+
+ @Override
+ public void visit(String name, Object value) {
+ if (!mods.contains(String.valueOf(value))) {
+ onModsMismatch.run();
+
+ if (stripLambdas && onModsMismatchLambdas != null) {
+ onModsMismatchLambdas.run();
+ }
+ }
+ }
+ };
+ }
+ else {
+ return null;
+ }
+ }
+ }
+
+ private AnnotationVisitor visitMemberAnnotation(String descriptor, boolean visible, Runnable onMismatch, Runnable onMismatchLambdas) {
+
+ if (ENVIRONMENT_DESCRIPTOR.equals(descriptor)) {
+ return new FabricEnvironmentAnnotationVisitor(api, onMismatch);
+ }
+
+ if (CLIENT_ONLY_DESCRIPTOR.equals(descriptor) && envType == EnvType.SERVER) {
+ return new QuiltEnvironmentAnnotationVisitor(api, onMismatch, onMismatchLambdas);
+ }
+
+ if (SERVER_ONLY_DESCRIPTOR.equals(descriptor) && envType == EnvType.CLIENT) {
+ return new QuiltEnvironmentAnnotationVisitor(api, onMismatch, onMismatchLambdas);
+ }
+
+ if (REQUIRES_DESCRIPTOR.equals(descriptor)) {
+ return new QuiltRequiresAnnotationVisitor(api, onMismatch, onMismatchLambdas);
+ }
+
+ return null;
+ }
+
+ public StrippingData(int api, EnvType envType, List mods) {
+ super(api);
+ this.envType = envType;
+ this.envTypeString = envType.name();
+ this.mods = mods.stream().map(ModLoadOption::id).collect(Collectors.toList());
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ this.interfaces = interfaces;
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ if (CLIENT_ONLY_DESCRIPTOR.equals(descriptor)) {
+ if (envType == EnvType.SERVER) {
+ stripEntireClass = true;
+ }
+ } else if (SERVER_ONLY_DESCRIPTOR.equals(descriptor)) {
+ if (envType == EnvType.CLIENT) {
+ stripEntireClass = true;
+ }
+ } else if (REQUIRES_DESCRIPTOR.equals(descriptor)) {
+ return new QuiltRequiresAnnotationVisitor(api, () -> stripEntireClass = true, null);
+ } else if (ENVIRONMENT_DESCRIPTOR.equals(descriptor)) {
+ return new FabricEnvironmentAnnotationVisitor(api, () -> stripEntireClass = true);
+ } else if (ENVIRONMENT_INTERFACE_DESCRIPTOR.equals(descriptor)) {
+ return new FabricEnvironmentInterfaceAnnotationVisitor(api);
+ } else if (ENVIRONMENT_INTERFACES_DESCRIPTOR.equals(descriptor)) {
+ return new AnnotationVisitor(api) {
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ if ("value".equals(name)) {
+ return new AnnotationVisitor(api) {
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+ return new FabricEnvironmentInterfaceAnnotationVisitor(api);
+ }
+ };
+ }
+
+ return null;
+ }
+ };
+ }
+
+ return null;
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {
+
+ TypeReference ref = new TypeReference(typeRef);
+
+ if (ref.getSort() != TypeReference.CLASS_EXTENDS) {
+ return null;
+ }
+
+ int interfaceIdx = ref.getSuperTypeIndex();
+
+ if (interfaceIdx < 0) {
+ // Wrongly applied to the super class
+ return null;
+ }
+
+ if (REQUIRES_DESCRIPTOR.equals(descriptor)) {
+ return new QuiltRequiresAnnotationVisitor(api, () -> stripInterfaces.add(interfaces[interfaceIdx]), null);
+ }
+
+ final EnvType annotationEnv;
+
+ if (CLIENT_ONLY_DESCRIPTOR.equals(descriptor)) {
+ annotationEnv = EnvType.CLIENT;
+ } else if (SERVER_ONLY_DESCRIPTOR.equals(descriptor)) {
+ annotationEnv = EnvType.SERVER;
+ } else {
+ return null;
+ }
+
+ if (annotationEnv != envType) {
+ stripInterfaces.add(interfaces[interfaceIdx]);
+ }
+
+ return null;
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
+ return new FieldVisitor(api) {
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ return visitMemberAnnotation(descriptor, visible, () -> stripFields.add(name + descriptor), null);
+ }
+ };
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
+ String[] exceptions) {
+ String methodId = name + descriptor;
+ return new MethodVisitor(api) {
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ return visitMemberAnnotation(
+ descriptor, visible, () -> stripMethods.add(methodId), () -> stripMethodLambdas.add(methodId)
+ );
+ }
+ };
+ }
+
+ public boolean stripEntireClass() {
+ return this.stripEntireClass;
+ }
+
+ public Collection getStripInterfaces() {
+ return this.stripInterfaces;
+ }
+
+ public Collection getStripFields() {
+ return this.stripFields;
+ }
+
+ public Collection getStripMethods() {
+ return this.stripMethods;
+ }
+
+ public Collection getStripMethodLambdas() {
+ return this.stripMethodLambdas;
+ }
+
+ public boolean isEmpty() {
+ return this.stripInterfaces.isEmpty() && this.stripFields.isEmpty() && this.stripMethods.isEmpty();
+ }
+}
diff --git a/src/test/java/org/quiltmc/test/lambda_strip/LambdaStripTester.java b/src/test/java/org/quiltmc/test/lambda_strip/LambdaStripTester.java
index abf771c79..a9938d63e 100644
--- a/src/test/java/org/quiltmc/test/lambda_strip/LambdaStripTester.java
+++ b/src/test/java/org/quiltmc/test/lambda_strip/LambdaStripTester.java
@@ -20,6 +20,7 @@
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Paths;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
@@ -29,11 +30,12 @@
import org.objectweb.asm.util.TraceClassVisitor;
import org.quiltmc.loader.impl.QuiltLoaderImpl;
import org.quiltmc.loader.impl.transformer.ClassStripper;
-import org.quiltmc.loader.impl.transformer.EnvironmentStrippingData;
import org.quiltmc.loader.impl.transformer.LambdaStripCalculator;
import net.fabricmc.api.EnvType;
+import org.quiltmc.loader.impl.transformer.StrippingData;
+
public class LambdaStripTester {
// Not a real test, since I'm not quite sure how to check for "no lambda present"
@@ -63,12 +65,12 @@ public static void main(String[] args) throws IOException {
| ClassReader.SKIP_FRAMES
);
- EnvironmentStrippingData stripData = new EnvironmentStrippingData(Opcodes.ASM9, EnvType.SERVER);
- reader.accept(stripData, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES);
+ StrippingData strip = new StrippingData(Opcodes.ASM9, EnvType.SERVER, new ArrayList<>());
+ reader.accept(strip, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES);
- Collection stripMethods = stripData.getStripMethods();
+ Collection stripMethods = strip.getStripMethods();
- LambdaStripCalculator calc = new LambdaStripCalculator(Opcodes.ASM9, stripData.getStripMethodLambdas());
+ LambdaStripCalculator calc = new LambdaStripCalculator(Opcodes.ASM9, strip.getStripMethodLambdas());
reader.accept(calc, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
Collection additionalStripMethods = calc.computeAdditionalMethodsToStrip();
@@ -79,7 +81,7 @@ public static void main(String[] args) throws IOException {
ClassWriter classWriter = new ClassWriter(null, 0);
ClassStripper visitor = new ClassStripper(
- QuiltLoaderImpl.ASM_VERSION, classWriter, stripData.getStripInterfaces(), stripData.getStripFields(),
+ QuiltLoaderImpl.ASM_VERSION, classWriter, strip.getStripInterfaces(), strip.getStripFields(),
stripMethods
);
reader.accept(visitor, 0);