Skip to content

Commit 2b08f85

Browse files
committed
fix groovy failing on client only imports
1 parent 94a9144 commit 2b08f85

File tree

3 files changed

+181
-1
lines changed

3 files changed

+181
-1
lines changed
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package com.cleanroommc.groovyscript.core.mixin.groovy;
2+
3+
import com.cleanroommc.groovyscript.sandbox.transformer.AsmDecompileHelper;
4+
import org.codehaus.groovy.ast.*;
5+
import org.codehaus.groovy.classgen.VariableScopeVisitor;
6+
import org.codehaus.groovy.control.ResolveVisitor;
7+
import org.codehaus.groovy.control.SourceUnit;
8+
import org.spongepowered.asm.mixin.Mixin;
9+
import org.spongepowered.asm.mixin.Overwrite;
10+
import org.spongepowered.asm.mixin.Shadow;
11+
12+
import java.lang.reflect.Modifier;
13+
import java.util.HashMap;
14+
import java.util.Iterator;
15+
import java.util.Map;
16+
17+
@Mixin(value = ResolveVisitor.class, remap = false)
18+
public abstract class ResolveVisitorMixin extends ClassCodeExpressionTransformer {
19+
20+
@Shadow
21+
private ClassNode currentClass;
22+
23+
@Shadow
24+
private ImportNode currentImport;
25+
26+
@Shadow
27+
protected abstract boolean resolve(ClassNode type, boolean testModuleImports, boolean testDefaultImports, boolean testStaticInnerClasses);
28+
29+
@Shadow
30+
private Map<GenericsType.GenericsTypeName, GenericsType> genericParameterNames;
31+
32+
@Shadow
33+
protected abstract void resolveGenericsHeader(GenericsType[] types);
34+
35+
@Shadow
36+
int phase;
37+
38+
@Shadow
39+
protected abstract void resolveOrFail(ClassNode type, ASTNode node);
40+
41+
@Shadow
42+
protected abstract void resolveOrFail(ClassNode type, String msg, ASTNode node, boolean preferImports);
43+
44+
@Shadow
45+
protected abstract void checkCyclicInheritance(ClassNode node, ClassNode type);
46+
47+
@Shadow
48+
protected abstract void checkGenericsCyclicInheritance(GenericsType[] genericsTypes);
49+
50+
@Shadow
51+
private SourceUnit source;
52+
53+
/**
54+
* @author brachy
55+
* @reason groovy tries to find client only classes from imports even on server
56+
*/
57+
@Overwrite
58+
public void visitClass(ClassNode node) {
59+
ClassNode oldNode = currentClass;
60+
currentClass = node;
61+
62+
ModuleNode module = node.getModule();
63+
if (!module.hasImportsResolved()) {
64+
for (ImportNode importNode : module.getImports()) {
65+
currentImport = importNode;
66+
ClassNode type = importNode.getType();
67+
if (resolve(type, false, false, true)) {
68+
currentImport = null;
69+
continue;
70+
}
71+
currentImport = null;
72+
// Mixin: added check
73+
if (!AsmDecompileHelper.removedSidedClasses.contains(type.getName())) {
74+
addError("unable to resolve class " + type.getName(), type);
75+
}
76+
}
77+
for (ImportNode importNode : module.getStarImports()) {
78+
if (importNode.getLineNumber() > 0) {
79+
currentImport = importNode;
80+
String importName = importNode.getPackageName();
81+
importName = importName.substring(0, importName.length() - 1);
82+
ClassNode type = ClassHelper.makeWithoutCaching(importName);
83+
if (resolve(type, false, false, true)) {
84+
importNode.setType(type);
85+
}
86+
currentImport = null;
87+
}
88+
}
89+
for (ImportNode importNode : module.getStaticImports().values()) {
90+
ClassNode type = importNode.getType();
91+
if (!resolve(type, true, true, true)) {
92+
// Mixin: added check
93+
if (!AsmDecompileHelper.removedSidedClasses.contains(type.getName())) {
94+
addError("unable to resolve class " + type.getName(), type);
95+
}
96+
}
97+
}
98+
for (ImportNode importNode : module.getStaticStarImports().values()) {
99+
ClassNode type = importNode.getType();
100+
if (!resolve(type, true, true, true)) {
101+
// Mixin: added check
102+
if (!AsmDecompileHelper.removedSidedClasses.contains(type.getName())) {
103+
addError("unable to resolve class " + type.getName(), type);
104+
}
105+
}
106+
}
107+
108+
module.setImportsResolved(true);
109+
}
110+
111+
//
112+
113+
if (!(node instanceof InnerClassNode) || Modifier.isStatic(node.getModifiers())) {
114+
genericParameterNames = new HashMap<>();
115+
}
116+
resolveGenericsHeader(node.getGenericsTypes());
117+
switch (phase) { // GROOVY-9866, GROOVY-10466
118+
case 0:
119+
case 1:
120+
ClassNode sn = node.getUnresolvedSuperClass();
121+
if (sn != null) {
122+
resolveOrFail(sn, "", node, true);
123+
}
124+
for (ClassNode in : node.getInterfaces()) {
125+
resolveOrFail(in, "", node, true);
126+
}
127+
128+
if (sn != null) checkCyclicInheritance(node, sn);
129+
for (ClassNode in : node.getInterfaces()) {
130+
checkCyclicInheritance(node, in);
131+
}
132+
checkGenericsCyclicInheritance(node.getGenericsTypes());
133+
case 2:
134+
// VariableScopeVisitor visits anon. inner class body inline, so resolve now
135+
for (Iterator<InnerClassNode> it = node.getInnerClasses(); it.hasNext();) {
136+
InnerClassNode cn = it.next();
137+
if (cn.isAnonymous()) {
138+
MethodNode enclosingMethod = cn.getEnclosingMethod();
139+
if (enclosingMethod != null) {
140+
resolveGenericsHeader(enclosingMethod.getGenericsTypes()); // GROOVY-6977
141+
}
142+
resolveOrFail(cn.getUnresolvedSuperClass(false), cn); // GROOVY-9642
143+
}
144+
}
145+
if (phase == 1) break; // resolve other class headers before members, et al.
146+
147+
// initialize scopes/variables now that imports and super types are resolved
148+
new VariableScopeVisitor(source).visitClass(node);
149+
150+
visitPackage(node.getPackage());
151+
visitImports(node.getModule());
152+
153+
node.visitContents(this);
154+
visitObjectInitializerStatements(node);
155+
visitAnnotations(node); // GROOVY-10750, GROOVY-11206
156+
}
157+
currentClass = oldNode;
158+
}
159+
}

src/main/java/com/cleanroommc/groovyscript/sandbox/transformer/AsmDecompileHelper.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package com.cleanroommc.groovyscript.sandbox.transformer;
22

3+
import com.cleanroommc.groovyscript.api.GroovyLog;
34
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
5+
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
46
import net.minecraft.launchwrapper.IClassTransformer;
57
import net.minecraft.launchwrapper.Launch;
8+
import net.minecraftforge.fml.common.asm.ASMTransformerWrapper;
9+
import net.minecraftforge.fml.common.asm.transformers.SideTransformer;
610
import net.minecraftforge.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;
711
import net.minecraftforge.fml.relauncher.FMLLaunchHandler;
812
import net.minecraftforge.fml.relauncher.SideOnly;
@@ -20,6 +24,7 @@
2024
import java.util.ArrayList;
2125
import java.util.List;
2226
import java.util.Map;
27+
import java.util.Set;
2328

2429

2530
public class AsmDecompileHelper {
@@ -31,6 +36,8 @@ public class AsmDecompileHelper {
3136
private static Constructor<?> decompilerConstructor;
3237
private static Field resultField;
3338

39+
public static final Set<String> removedSidedClasses = new ObjectOpenHashSet<>();
40+
3441
private static final Map<String, SoftReference<ClassStub>> stubCache = new Object2ObjectOpenHashMap<>();
3542

3643
static {
@@ -73,6 +80,7 @@ public static ClassStub getDecompiledClass(groovyjarjarasm.asm.ClassVisitor clas
7380
byte[] bytes = Launch.classLoader.getClassBytes(className);
7481
if (bytes == null) return null;
7582
bytes = transform(className, bytes);
83+
if (bytes == null) return null;
7684
groovyjarjarasm.asm.ClassReader classReader = new groovyjarjarasm.asm.ClassReader(bytes);
7785
groovyjarjarasm.asm.ClassVisitor decompiler = makeGroovyDecompiler();
7886
classReader.accept(decompiler, ClassReader.SKIP_FRAMES);
@@ -96,7 +104,19 @@ public static byte[] transform(String className, byte[] bytes) {
96104
String untransformed = FMLDeobfuscatingRemapper.INSTANCE.unmap(className.replace('.', '/')).replace('/', '.');
97105
String transformed = FMLDeobfuscatingRemapper.INSTANCE.map(className.replace('.', '/')).replace('/', '.');
98106
for (IClassTransformer transformer : Launch.classLoader.getTransformers()) {
99-
bytes = transformer.transform(untransformed, transformed, bytes);
107+
try {
108+
bytes = transformer.transform(untransformed, transformed, bytes);
109+
} catch (Exception e) {
110+
// groovy tries to load classes from imports, some of those are client only classes
111+
// so with this cursed thing we check if it was removed by SideTransformer, which very likely means the class is loaded on the wrong side
112+
// we add that class to a set, for a mixin to check
113+
if (transformer instanceof SideTransformer || (transformer instanceof ASMTransformerWrapper.TransformerWrapper wrapper && wrapper.toString().contains("net.minecraftforge.fml.common.asm.transformers.SideTransformer"))) {
114+
removedSidedClasses.add(className);
115+
} else {
116+
GroovyLog.get().error("Failed to apply transformer {} to class {}", transformer, className);
117+
}
118+
return null;
119+
}
100120
}
101121
return bytes;
102122
}

src/main/resources/mixin.groovyscript.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"groovy.MetaClassImplMixin",
3131
"groovy.ModuleNodeAccessor",
3232
"groovy.ModuleNodeMixin",
33+
"groovy.ResolveVisitorMixin",
3334
"loot.LoadTableEventMixin",
3435
"loot.LootPoolAccessor",
3536
"loot.LootTableAccessor",

0 commit comments

Comments
 (0)