From 80a5ada08ed74c5c5c1a8a093f1bf0eba7d08f27 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Sun, 21 Jul 2024 20:20:31 +0200 Subject: [PATCH] Dynamically resolve field type changes --- .../patch/fixes/BytecodeFixerUpper.java | 34 +++++++++++++++---- .../fixes/FieldTypePatchTransformer.java | 2 +- .../DynamicInheritedInjectionPointPatch.java | 4 +-- .../adapter/patch/util/AdapterUtil.java | 5 +-- .../patch/util/provider/ClassLookup.java | 8 +++++ .../mixin/BytecodeFixerUpperTestFrontend.java | 6 ++-- .../test/mixin/DynamicMixinPatchTest.java | 15 +++++++- .../test/mixin/MinecraftMixinPatchTest.java | 24 ++++++++++--- .../test/mixin/CrossbowAttackGoalMixin.java | 21 ++++++++++++ 9 files changed, 99 insertions(+), 20 deletions(-) create mode 100644 test/src/testClasses/java/org/sinytra/adapter/test/mixin/CrossbowAttackGoalMixin.java diff --git a/definition/src/main/java/org/sinytra/adapter/patch/fixes/BytecodeFixerUpper.java b/definition/src/main/java/org/sinytra/adapter/patch/fixes/BytecodeFixerUpper.java index bc71778..07dcfff 100644 --- a/definition/src/main/java/org/sinytra/adapter/patch/fixes/BytecodeFixerUpper.java +++ b/definition/src/main/java/org/sinytra/adapter/patch/fixes/BytecodeFixerUpper.java @@ -3,26 +3,33 @@ import com.mojang.datafixers.util.Pair; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; +import org.objectweb.asm.tree.FieldNode; +import org.sinytra.adapter.patch.util.provider.ClassLookup; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; public final class BytecodeFixerUpper { public static final List DEFAULT_PROVIDERS = List.of( SupplierTypeAdapter.INSTANCE ); - private final Map>> newFieldTypes; private final List fieldTypeAdapters; private final List dynamicTypeAdapters; private final BytecodeFixerJarGenerator generator; + private final ClassLookup cleanLookup; + private final ClassLookup dirtyLookup; - public BytecodeFixerUpper(Map>> newFieldTypes, List fieldTypeAdapters) { - this(newFieldTypes, fieldTypeAdapters, DEFAULT_PROVIDERS); + private final Map> fieldTypeChangesCache = new ConcurrentHashMap<>(); + + public BytecodeFixerUpper(ClassLookup cleanLookup, ClassLookup dirtyLookup, List fieldTypeAdapters) { + this(cleanLookup, dirtyLookup, fieldTypeAdapters, DEFAULT_PROVIDERS); } - public BytecodeFixerUpper(Map>> newFieldTypes, List fieldTypeAdapters, List dynamicTypeAdapters) { - this.newFieldTypes = newFieldTypes; + public BytecodeFixerUpper(ClassLookup cleanLookup, ClassLookup dirtyLookup, List fieldTypeAdapters, List dynamicTypeAdapters) { + this.cleanLookup = cleanLookup; + this.dirtyLookup = dirtyLookup; this.fieldTypeAdapters = fieldTypeAdapters; this.dynamicTypeAdapters = dynamicTypeAdapters; this.generator = new BytecodeFixerJarGenerator(); @@ -33,8 +40,21 @@ public BytecodeFixerJarGenerator getGenerator() { } public Pair getFieldTypeChange(String owner, String name) { - Map> fields = this.newFieldTypes.get(owner); - return fields != null ? fields.get(name) : null; + String key = owner + ":" + name; + return fieldTypeChangesCache.computeIfAbsent(key, k -> { + FieldNode cleanField = this.cleanLookup.findField(owner, name).orElse(null); + if (cleanField == null) { + return null; + } + FieldNode dirtyField = this.dirtyLookup.findField(owner, name).orElse(null); + if (dirtyField == null) { + return null; + } + if (!cleanField.desc.equals(dirtyField.desc)) { + return Pair.of(Type.getType(cleanField.desc), Type.getType(dirtyField.desc)); + } + return null; + }); } @Nullable diff --git a/definition/src/main/java/org/sinytra/adapter/patch/fixes/FieldTypePatchTransformer.java b/definition/src/main/java/org/sinytra/adapter/patch/fixes/FieldTypePatchTransformer.java index 961dd90..67b19ef 100644 --- a/definition/src/main/java/org/sinytra/adapter/patch/fixes/FieldTypePatchTransformer.java +++ b/definition/src/main/java/org/sinytra/adapter/patch/fixes/FieldTypePatchTransformer.java @@ -6,8 +6,8 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.patch.api.*; import org.sinytra.adapter.patch.analysis.selector.FieldMatcher; +import org.sinytra.adapter.patch.api.*; import org.sinytra.adapter.patch.util.AdapterUtil; import java.util.Collection; diff --git a/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynamic/DynamicInheritedInjectionPointPatch.java b/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynamic/DynamicInheritedInjectionPointPatch.java index 53e5b40..53c8332 100644 --- a/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynamic/DynamicInheritedInjectionPointPatch.java +++ b/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynamic/DynamicInheritedInjectionPointPatch.java @@ -4,10 +4,10 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.*; import org.sinytra.adapter.patch.analysis.MethodCallAnalyzer; -import org.sinytra.adapter.patch.api.*; -import org.sinytra.adapter.patch.fixes.BytecodeFixerUpper; import org.sinytra.adapter.patch.analysis.selector.AnnotationHandle; import org.sinytra.adapter.patch.analysis.selector.AnnotationValueHandle; +import org.sinytra.adapter.patch.api.*; +import org.sinytra.adapter.patch.fixes.BytecodeFixerUpper; import org.sinytra.adapter.patch.util.MethodQualifier; import org.slf4j.Logger; diff --git a/definition/src/main/java/org/sinytra/adapter/patch/util/AdapterUtil.java b/definition/src/main/java/org/sinytra/adapter/patch/util/AdapterUtil.java index ce9f2ef..7c5633e 100644 --- a/definition/src/main/java/org/sinytra/adapter/patch/util/AdapterUtil.java +++ b/definition/src/main/java/org/sinytra/adapter/patch/util/AdapterUtil.java @@ -10,13 +10,14 @@ import org.objectweb.asm.commons.InstructionAdapter; import org.objectweb.asm.tree.*; import org.objectweb.asm.util.Textifier; +import org.objectweb.asm.util.TraceFieldVisitor; import org.objectweb.asm.util.TraceMethodVisitor; import org.sinytra.adapter.patch.analysis.LocalVariableLookup; +import org.sinytra.adapter.patch.analysis.selector.AnnotationHandle; +import org.sinytra.adapter.patch.analysis.selector.AnnotationValueHandle; import org.sinytra.adapter.patch.api.MethodContext; import org.sinytra.adapter.patch.api.MixinConstants; import org.sinytra.adapter.patch.api.PatchEnvironment; -import org.sinytra.adapter.patch.analysis.selector.AnnotationHandle; -import org.sinytra.adapter.patch.analysis.selector.AnnotationValueHandle; import org.slf4j.Logger; import org.spongepowered.asm.mixin.gen.AccessorInfo; diff --git a/definition/src/main/java/org/sinytra/adapter/patch/util/provider/ClassLookup.java b/definition/src/main/java/org/sinytra/adapter/patch/util/provider/ClassLookup.java index cedf3ef..4ee5978 100644 --- a/definition/src/main/java/org/sinytra/adapter/patch/util/provider/ClassLookup.java +++ b/definition/src/main/java/org/sinytra/adapter/patch/util/provider/ClassLookup.java @@ -1,6 +1,7 @@ package org.sinytra.adapter.patch.util.provider; import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.MethodNode; import java.util.Optional; @@ -14,4 +15,11 @@ default Optional findMethod(String owner, String name, String desc) .filter(mtd -> mtd.name.equals(name) && mtd.desc.equals(desc)) .findFirst(); } + + default Optional findField(String owner, String name) { + return getClass(owner).stream() + .flatMap(cls -> cls.fields.stream()) + .filter(fd -> fd.name.equals(name)) + .findFirst(); + } } diff --git a/test/src/test/java/org/sinytra/adapter/patch/test/mixin/BytecodeFixerUpperTestFrontend.java b/test/src/test/java/org/sinytra/adapter/patch/test/mixin/BytecodeFixerUpperTestFrontend.java index 81c11a9..977a03e 100644 --- a/test/src/test/java/org/sinytra/adapter/patch/test/mixin/BytecodeFixerUpperTestFrontend.java +++ b/test/src/test/java/org/sinytra/adapter/patch/test/mixin/BytecodeFixerUpperTestFrontend.java @@ -6,9 +6,9 @@ import org.sinytra.adapter.patch.fixes.BytecodeFixerUpper; import org.sinytra.adapter.patch.fixes.SimpleTypeAdapter; import org.sinytra.adapter.patch.fixes.TypeAdapter; +import org.sinytra.adapter.patch.util.provider.ClassLookup; import java.util.List; -import java.util.Map; public class BytecodeFixerUpperTestFrontend { private static final List FIELD_TYPE_ADAPTERS = List.of( @@ -18,8 +18,8 @@ public class BytecodeFixerUpperTestFrontend { private final BytecodeFixerUpper bfu; - public BytecodeFixerUpperTestFrontend() { - this.bfu = new BytecodeFixerUpper(Map.of(), FIELD_TYPE_ADAPTERS); + public BytecodeFixerUpperTestFrontend(ClassLookup cleanLookup, ClassLookup dirtyLookup) { + this.bfu = new BytecodeFixerUpper(cleanLookup, dirtyLookup, FIELD_TYPE_ADAPTERS); } public BytecodeFixerUpper unwrap() { diff --git a/test/src/test/java/org/sinytra/adapter/patch/test/mixin/DynamicMixinPatchTest.java b/test/src/test/java/org/sinytra/adapter/patch/test/mixin/DynamicMixinPatchTest.java index 2e07afb..1d2c715 100644 --- a/test/src/test/java/org/sinytra/adapter/patch/test/mixin/DynamicMixinPatchTest.java +++ b/test/src/test/java/org/sinytra/adapter/patch/test/mixin/DynamicMixinPatchTest.java @@ -5,7 +5,9 @@ import org.sinytra.adapter.patch.api.Patch; import org.sinytra.adapter.patch.api.PatchEnvironment; import org.sinytra.adapter.patch.api.RefmapHolder; +import org.sinytra.adapter.patch.fixes.FieldTypeUsageTransformer; import org.sinytra.adapter.patch.transformer.dynfix.DynamicInjectionPointPatch; +import org.sinytra.adapter.patch.util.provider.ClassLookup; import org.spongepowered.asm.mixin.FabricUtil; import java.util.List; @@ -14,6 +16,7 @@ public class DynamicMixinPatchTest extends MinecraftMixinPatchTest { private static final List DYNAMIC_PATCHES = List.of( Patch.builder() .transform(new DynamicInjectionPointPatch()) + .transform(new FieldTypeUsageTransformer()) .build() ); @@ -204,10 +207,20 @@ void testModifiedWrapOperationTarget2() throws Exception { ); } + @Test + void testModifiedFieldType() throws Exception { + assertSameField( + "org/sinytra/adapter/test/mixin/CrossbowAttackGoalMixin", + "mob" + ); + } + @Override protected LoadResult load(String className, List allowedMethods) throws Exception { final ClassNode patched = loadClass(className); patched.methods.removeIf(m -> !allowedMethods.contains(m.name)); + ClassLookup cleanLookup = createCleanLookup(); + ClassLookup dirtyLookup = createDirtyLookup(); final PatchEnvironment env = PatchEnvironment.create( new RefmapHolder() { @Override @@ -221,7 +234,7 @@ public void copyEntries(String from, String to) { }, createCleanLookup(), createDirtyLookup(), - new BytecodeFixerUpperTestFrontend().unwrap(), + new BytecodeFixerUpperTestFrontend(cleanLookup, dirtyLookup).unwrap(), FabricUtil.COMPATIBILITY_LATEST ); DYNAMIC_PATCHES.forEach(p -> p.apply(patched, env)); diff --git a/test/src/test/java/org/sinytra/adapter/patch/test/mixin/MinecraftMixinPatchTest.java b/test/src/test/java/org/sinytra/adapter/patch/test/mixin/MinecraftMixinPatchTest.java index 9f69326..624fb6c 100644 --- a/test/src/test/java/org/sinytra/adapter/patch/test/mixin/MinecraftMixinPatchTest.java +++ b/test/src/test/java/org/sinytra/adapter/patch/test/mixin/MinecraftMixinPatchTest.java @@ -5,11 +5,11 @@ import org.assertj.core.api.Assertions; import org.objectweb.asm.ClassReader; import org.objectweb.asm.tree.*; +import org.sinytra.adapter.patch.analysis.selector.AnnotationHandle; +import org.sinytra.adapter.patch.analysis.selector.AnnotationValueHandle; import org.sinytra.adapter.patch.api.MixinClassGenerator; import org.sinytra.adapter.patch.api.MixinConstants; import org.sinytra.adapter.patch.api.PatchEnvironment; -import org.sinytra.adapter.patch.analysis.selector.AnnotationHandle; -import org.sinytra.adapter.patch.analysis.selector.AnnotationValueHandle; import org.sinytra.adapter.patch.util.AdapterUtil; import org.sinytra.adapter.patch.util.provider.ClassLookup; import org.sinytra.adapter.patch.util.provider.ZipClassLookup; @@ -30,8 +30,7 @@ import java.util.stream.StreamSupport; import java.util.zip.ZipFile; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; public abstract class MinecraftMixinPatchTest { private static final Logger LOGGER = LogUtils.getLogger(); @@ -93,6 +92,23 @@ protected final void assertSameCode( Stream.of(assertions).forEach(c -> c.accept(patched, expected, result.env())); } + protected final void assertSameField( + String className, + String testName + ) throws Exception { + final LoadResult result = load(className, List.of(testName)); + final FieldNode patched = result.patched.fields + .stream().filter(m -> m.name.equals(testName)) + .findFirst().orElseThrow(); + final FieldNode expected = result.expected.fields + .stream().filter(m -> m.name.equals(testName + "Expected")) + .findFirst().orElseThrow(); + + LOGGER.info("Patched field node: \n{}", patched); + + assertEquals(patched.desc, expected.desc, "Field types differ"); + } + public static class InsnComparator implements Comparator { @Override public int compare(AbstractInsnNode o1, AbstractInsnNode o2) { diff --git a/test/src/testClasses/java/org/sinytra/adapter/test/mixin/CrossbowAttackGoalMixin.java b/test/src/testClasses/java/org/sinytra/adapter/test/mixin/CrossbowAttackGoalMixin.java new file mode 100644 index 0000000..ae595ae --- /dev/null +++ b/test/src/testClasses/java/org/sinytra/adapter/test/mixin/CrossbowAttackGoalMixin.java @@ -0,0 +1,21 @@ +package org.sinytra.adapter.test.mixin; + +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.ai.goal.Goal; +import net.minecraft.world.entity.ai.goal.RangedCrossbowAttackGoal; +import net.minecraft.world.entity.monster.CrossbowAttackMob; +import net.minecraft.world.entity.monster.Monster; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(RangedCrossbowAttackGoal.class) +public abstract class CrossbowAttackGoalMixin extends Goal { + @Shadow + @Final + private T mob; + + @Shadow + @Final + private Mob mobExpected; +}