Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to dasm 2 #33

Merged
merged 6 commits into from
Feb 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,8 @@ forge*changelog.txt

# Fabric
.fabric
.mixin.out
.mixin.out

# Dasm
.dasm.out

2 changes: 1 addition & 1 deletion .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ runs {
sourceSets.main.resources { srcDir 'src/generated/resources' }

repositories {
mavenLocal()
maven { setUrl("https://oss.sonatype.org/content/repositories/snapshots/") }
maven { setUrl("https://jitpack.io") }
maven { setUrl("https://maven.fabricmc.net/") }
Expand All @@ -235,7 +236,7 @@ dependencies {
targetConfiguration = "testArchivesOutput"
}

libraries("com.github.OpenCubicChunks:dasm:c08e10a63a") {
libraries("io.github.notstirred:dasm:2.0.1") {
transitive = false
}
libraries("io.github.opencubicchunks:regionlib:0.63.0-SNAPSHOT")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,67 +1,70 @@
package io.github.opencubicchunks.cubicchunks.mixin;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.annotation.Nullable;

import io.github.notstirred.dasm.annotation.AnnotationParser;
import io.github.notstirred.dasm.api.annotations.transform.ApplicationStage;
import io.github.notstirred.dasm.api.provider.MappingsProvider;
import io.github.notstirred.dasm.exception.DasmException;
import io.github.notstirred.dasm.exception.wrapped.DasmWrappedExceptions;
import io.github.notstirred.dasm.transformer.Transformer;
import io.github.notstirred.dasm.transformer.data.ClassTransform;
import io.github.notstirred.dasm.transformer.data.MethodTransform;
import io.github.notstirred.dasm.util.CachingClassProvider;
import io.github.notstirred.dasm.util.ClassNodeProvider;
import io.github.notstirred.dasm.util.Either;
import io.github.opencubicchunks.cubicchunks.CubicChunks;
import io.github.opencubicchunks.dasm.AnnotationParser;
import io.github.opencubicchunks.dasm.Transformer;
import io.github.opencubicchunks.dasm.api.provider.CachingClassProvider;
import io.github.opencubicchunks.dasm.api.provider.ClassProvider;
import io.github.opencubicchunks.dasm.api.provider.MappingsProvider;
import io.github.opencubicchunks.dasm.api.transform.TransformFrom;
import io.github.opencubicchunks.dasm.transformer.redirect.RedirectSet;
import io.github.opencubicchunks.dasm.transformer.target.TargetClass;
import net.neoforged.fml.loading.FMLEnvironment;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import org.spongepowered.asm.mixin.transformer.ClassInfo;
import org.spongepowered.asm.service.MixinService;

public class ASMConfigPlugin implements IMixinConfigPlugin {
private final Map<String, Boolean> dasmTransformedInPreApply = new ConcurrentHashMap<>();
private final Transformer transformer;
private final AnnotationParser annotationParser;

private final Map<String, Either<ClassTransform, Collection<MethodTransform>>> preApplyTargets = new HashMap<>();
private final Map<String, Either<ClassTransform, Collection<MethodTransform>>> postApplyTargets = new HashMap<>();

public ASMConfigPlugin() {
boolean developmentEnvironment = false;
try {
developmentEnvironment = !FMLEnvironment.production;
} catch (Throwable ignored) {
}
MappingsProvider mappings = new MappingsProvider() {

@Override public String mapFieldName(String owner, String fieldName, String descriptor) {
return fieldName;
}

@Override public String mapMethodName(String owner, String methodName, String descriptor) {
return methodName;
}

@Override public String mapClassName(String className) {
return className;
}
};
MappingsProvider mappings = MappingsProvider.IDENTITY;

// TODO: breaks on fabric (remapped at runtime)
ClassProvider classProvider = new CachingClassProvider(s -> {
ClassNodeProvider classProvider = new CachingClassProvider(s -> {
try (var classStream = ASMConfigPlugin.class.getClassLoader().getResourceAsStream(s.replace(".", "/") + ".class")) {
return classStream.readAllBytes();
return Optional.ofNullable(classStream.readAllBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
});
this.transformer = new Transformer(mappings, classProvider, developmentEnvironment);
this.annotationParser = new AnnotationParser(classProvider, GeneralSet.class);
this.transformer = new Transformer(classProvider, mappings);
this.annotationParser = new AnnotationParser(classProvider);
}

@Override public void onLoad(String mixinPackage) {
Expand All @@ -72,6 +75,47 @@ public ASMConfigPlugin() {
}

@Override public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
try {
ClassNode targetClass = MixinService.getService().getBytecodeProvider().getClassNode(targetClassName);
ClassNode mixinClass = MixinService.getService().getBytecodeProvider().getClassNode(mixinClassName);
// rename the mixin class to get dasm to generate owners correctly
mixinClass.name = targetClass.name;

// PRE_APPLY
this.annotationParser.findRedirectSets(mixinClass);
var methodTransformsMixin = this.annotationParser.buildMethodTargets(mixinClass, "cc_dasm$");
this.annotationParser.findRedirectSets(targetClass);
var classTransform = this.annotationParser.buildClassTarget(targetClass);
var methodTransformsTarget = this.annotationParser.buildMethodTargets(targetClass, "cc_dasm$");

var methodTransforms = Stream.of(methodTransformsTarget, methodTransformsMixin)
.filter(Optional::isPresent).map(Optional::get)
.flatMap(Collection::stream).toList();

String key = mixinClassName + "|" + targetClassName;
if (classTransform.isPresent()) {
// TODO: nice error
assert methodTransformsMixin.isEmpty() && methodTransforms.isEmpty() : "Whole class transform WITH method transforms?";
ClassTransform transform = classTransform.get();
if (transform.stage() == ApplicationStage.PRE_APPLY) {
this.preApplyTargets.put(key, Either.left(transform));
} else {
this.postApplyTargets.put(key, Either.left(transform));
}
} else {
Collection<MethodTransform> preTransforms = this.preApplyTargets.computeIfAbsent(key, k -> Either.right(new ArrayList<>())).right().get();
Collection<MethodTransform> postTransforms = this.postApplyTargets.computeIfAbsent(key, k -> Either.right(new ArrayList<>())).right().get();
methodTransforms.forEach(transform -> {
if (transform.stage() == ApplicationStage.PRE_APPLY) {
preTransforms.add(transform);
} else {
postTransforms.add(transform);
}
});
}
} catch (ClassNotFoundException | IOException | DasmWrappedExceptions e) {
throw new RuntimeException(e);
}
return true;
}

Expand All @@ -83,14 +127,46 @@ public ASMConfigPlugin() {
}

@Override public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
var wasTransformed = transformClass(targetClassName, targetClass, mixinClassName, TransformFrom.ApplicationStage.PRE_APPLY);
boolean wasTransformed;
try {
wasTransformed = transformClass(targetClassName, targetClass, mixinClassName, ApplicationStage.PRE_APPLY);
} catch (DasmException e) {
throw new RuntimeException(e);
}
dasmTransformedInPreApply.put(mixinClassName + "|" + targetClassName, wasTransformed);

try {
// ugly hack to add class metadata to mixin
// based on https://github.com/Chocohead/OptiFabric/blob/54fc2ef7533e43d1982e14bc3302bcf156f590d8/src/main/java/me/modmuss50/optifabric/compat/fabricrendererapi
// /RendererMixinPlugin.java#L25:L44
Method addMethod = ClassInfo.class.getDeclaredMethod("addMethod", MethodNode.class, boolean.class);
addMethod.setAccessible(true);

ClassInfo ci = ClassInfo.forName(targetClassName);
Set<String> existingMethods = ci.getMethods().stream().map(x -> x.getName() + x.getDesc()).collect(Collectors.toSet());
for (MethodNode method : targetClass.methods) {
if (!existingMethods.contains(method.name + method.desc)) {
addMethod.invoke(ci, method, false);
}
}
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}

@Override public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
// Apply POST_APPLY dasm transforms
boolean wasTransformed = transformClass(targetClassName, targetClass, mixinClassName, TransformFrom.ApplicationStage.POST_APPLY);

boolean wasTransformed;
try {
wasTransformed = transformClass(targetClassName, targetClass, mixinClassName, ApplicationStage.POST_APPLY);
} catch (DasmException e) {
throw new RuntimeException(e);
}
for (MethodNode methodNode : targetClass.methods) {
if (methodNode.name.contains("cc_dasm$")) {
int asd = 0;
}
}
// If no DASM transformation happened to this class, we can skip removing the prefixed methods
if (!(wasTransformed | dasmTransformedInPreApply.get(mixinClassName + "|" + targetClassName)))
return;
Expand All @@ -117,41 +193,54 @@ record PrefixMethodPair(MethodNode dasmAddedMethod, MethodNode mixinAddedMethod)
if (prefixMethodPair.mixinAddedMethod != null) {
targetClass.methods.remove(prefixMethodPair.mixinAddedMethod);
}
prefixMethodPair.dasmAddedMethod.name = prefixMethodPair.dasmAddedMethod.name.substring(
prefixMethodPair.dasmAddedMethod.name.indexOf("$") + 1
);

prefixMethodPair.dasmAddedMethod.name = prefixMethodPair.dasmAddedMethod.name
.replace("__init__", "<init>")
.replace("__clinit__", "<clinit>");

// remove the prefix
prefixMethodPair.dasmAddedMethod.name = prefixMethodPair.dasmAddedMethod.name.substring("cc_dasm$".length());
});

ClassWriter classWriter = new ClassWriter(0);
targetClass.accept(classWriter);
try {
Path path = Path.of(".dasm.out/" + "POSTIER_APPLY" + "/" + targetClassName.replace('.', '/') + ".class").toAbsolutePath();
Files.createDirectories(path.getParent());
Files.write(path, classWriter.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
}
}

/**
* @return Whether any transformation was done to the targetClass
*/
private boolean transformClass(String targetClassName, ClassNode targetClass, String mixinClassName, TransformFrom.ApplicationStage stage) {
ClassNode mixinClass;
try {
mixinClass = MixinService.getService().getBytecodeProvider().getClassNode(mixinClassName);
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
private boolean transformClass(String targetClassName, ClassNode targetClass, String mixinClassName, ApplicationStage stage) throws DasmException {
Either<ClassTransform, Collection<MethodTransform>> target = null;
switch (stage) {
case PRE_APPLY -> target = preApplyTargets.get(mixinClassName + "|" + targetClassName);
case POST_APPLY -> target = postApplyTargets.get(mixinClassName + "|" + targetClassName);
}

var target = new TargetClass(targetClassName);
Set<RedirectSet> redirectSets = new HashSet<>();

this.annotationParser.findRedirectSets(targetClassName, mixinClass, redirectSets);
this.annotationParser.buildClassTarget(mixinClass, target, stage, "cc_dasm$");
this.annotationParser.findRedirectSets(targetClassName, targetClass, redirectSets);
this.annotationParser.buildClassTarget(targetClass, target, stage, "cc_dasm$");
redirectSets.forEach(target::addRedirectSet);

if (target.targetMethods().isEmpty() && target.wholeClass() == null) {
if (target == null) {
return false;
}

if (target.wholeClass() != null) {
this.transformer.transformClass(targetClass, target);
if (target.isLeft()) {
this.transformer.transform(targetClass, target.left().get());
} else {
this.transformer.transformClass(targetClass, target);
this.transformer.transform(targetClass, target.right().get());
}
ClassWriter classWriter = new ClassWriter(0);
targetClass.accept(classWriter);
try {
Path path = Path.of(".dasm.out/" + stage + "/" + targetClassName.replace('.', '/') + ".class").toAbsolutePath();
Files.createDirectories(path.getParent());
Files.write(path, classWriter.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
}

return true;
}
}
Original file line number Diff line number Diff line change
@@ -1,53 +1,56 @@
package io.github.opencubicchunks.cubicchunks.mixin;

import io.github.notstirred.dasm.api.annotations.redirect.redirects.FieldRedirect;
import io.github.notstirred.dasm.api.annotations.redirect.redirects.MethodRedirect;
import io.github.notstirred.dasm.api.annotations.redirect.redirects.TypeRedirect;
import io.github.notstirred.dasm.api.annotations.redirect.sets.RedirectSet;
import io.github.notstirred.dasm.api.annotations.selector.FieldSig;
import io.github.notstirred.dasm.api.annotations.selector.MethodSig;
import io.github.notstirred.dasm.api.annotations.selector.Ref;
import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloPos;
import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess;
import io.github.opencubicchunks.cubicchunks.world.level.cube.ImposterProtoCube;
import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube;
import io.github.opencubicchunks.cubicchunks.world.level.cube.ProtoCube;
import io.github.opencubicchunks.dasm.api.Ref;
import io.github.opencubicchunks.dasm.api.redirect.DasmRedirectSet;
import io.github.opencubicchunks.dasm.api.redirect.FieldRedirect;
import io.github.opencubicchunks.dasm.api.redirect.MethodRedirect;
import io.github.opencubicchunks.dasm.api.redirect.TypeRedirect;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ImposterProtoChunk;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.ProtoChunk;

@DasmRedirectSet
@RedirectSet
public interface CubeAccessAndDescendantsSet extends GeneralSet {
@TypeRedirect(from = @Ref(ChunkAccess.class), to = @Ref(CubeAccess.class))
abstract class ChunkAccessToCubeAccessRedirects {
@FieldRedirect("cloPos") protected ChunkPos chunkPos;
abstract class ChunkAccess_to_CubeAccess_redirects {
@FieldRedirect(@FieldSig(type = @Ref(ChunkPos.class), name = "chunkPos")) protected CloPos cloPos;

@MethodRedirect("cc_getCloPos") public native ChunkPos getPos();
@MethodRedirect(@MethodSig("getPos()Lnet/minecraft/world/level/ChunkPos;")) public native CloPos cc_getCloPos();
}

@TypeRedirect(from = @Ref(LevelChunk.class), to = @Ref(LevelCube.class))
abstract class LevelChunkToLevelCubeRedirects { }
abstract class LevelChunk_to_LevelCube_redirects { }

@TypeRedirect(
from = @Ref(string = "net.minecraft.world.level.chunk.LevelChunk$BoundTickingBlockEntity"),
to = @Ref(string = "io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube$BoundTickingBlockEntity")
)
abstract class LevelChunk$BoundTickingBlockEntityToLevelCube$BoundTickingBlockEntityRedirects { }
abstract class LevelChunk$BoundTickingBlockEntity_to_LevelCube$BoundTickingBlockEntity_redirects { }

@TypeRedirect(
from = @Ref(string = "net.minecraft.world.level.chunk.LevelChunk$PostLoadProcessor"),
to = @Ref(string = "io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube$PostLoadProcessor")
)
abstract class LevelChunk$PostLoadProcessorToLevelCube$PostLoadProcessorRedirects { }
abstract class LevelChunk$PostLoadProcessor_to_LevelCube$PostLoadProcessor_redirects { }

@TypeRedirect(
from = @Ref(string = "net.minecraft.world.level.chunk.LevelChunk$RebindableTickingBlockEntityWrapper"),
to = @Ref(string = "io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube$RebindableTickingBlockEntityWrapper")
)
abstract class LevelChunk$RebindableTickingBlockEntityWrapperToLevelCube$RebindableTickingBlockEntityWrapperRedirects { }
abstract class LevelChunk$RebindableTickingBlockEntityWrapper_to_LevelCube$RebindableTickingBlockEntityWrapper_redirects { }

@TypeRedirect(from = @Ref(ProtoChunk.class), to = @Ref(ProtoCube.class))
abstract class ProtoChunkToProtoCubeRedirects { }
abstract class ProtoChunk_to_ProtoCube_redirects { }

@TypeRedirect(from = @Ref(ImposterProtoChunk.class), to = @Ref(ImposterProtoCube.class))
abstract class ImposterProtoChunkToImposterProtoCubeRedirects { }
abstract class ImposterProtoChunk_to_ImposterProtoCube_redirects { }
}
Loading
Loading