Preliminary work on getting records and sealed classes right

Signed-off-by: liach <liach@users.noreply.github.com>
This commit is contained in:
liach 2021-09-18 13:31:17 -05:00
parent defbde19d8
commit 099ddebbd0
4 changed files with 405 additions and 294 deletions

View File

@ -16,34 +16,45 @@
package net.fabricmc.mappingpoet;
import static net.fabricmc.mappingpoet.FieldBuilder.parseAnnotation;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.TypeReference;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.InnerClassNode;
import org.objectweb.asm.tree.MethodNode;
import net.fabricmc.mappingpoet.signature.AnnotationAwareDescriptors;
import net.fabricmc.mappingpoet.signature.AnnotationAwareSignatures;
import net.fabricmc.mappingpoet.signature.ClassSignature;
import net.fabricmc.mappingpoet.signature.ClassStaticContext;
import net.fabricmc.mappingpoet.signature.TypeAnnotationMapping;
import net.fabricmc.mappingpoet.signature.TypeAnnotationStorage;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.TypeReference;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.InnerClassNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.MethodNode;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import static net.fabricmc.mappingpoet.FieldBuilder.parseAnnotation;
public class ClassBuilder {
static final Handle OBJ_MTH_BOOTSTRAP = new Handle(
Opcodes.H_INVOKESTATIC,
"java/lang/runtime/ObjectMethods",
"bootstrap",
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;",
false
);
private final MappingsStore mappings;
private final ClassNode classNode;
@ -51,21 +62,24 @@ public class ClassBuilder {
private final TypeSpec.Builder builder;
private final List<ClassBuilder> innerClasses = new ArrayList<>();
private final Function<String, Collection<String>> superGetter;
private final Predicate<String> sealChecker;
private final ClassStaticContext context;
private final ClassSignature signature; // not really signature
private final TypeAnnotationMapping typeAnnotations;
private boolean annotationClass;
private boolean enumClass;
private boolean recordClass;
private boolean instanceInner = false;
// only nonnull if any class in the inner class chain creates a generic <T> decl
// omits L and ;
private String receiverSignature;
public ClassBuilder(MappingsStore mappings, ClassNode classNode, Function<String, Collection<String>> superGetter, ClassStaticContext context) {
public ClassBuilder(MappingsStore mappings, ClassNode classNode, Function<String, Collection<String>> superGetter, Predicate<String> sealChecker, ClassStaticContext context) {
this.mappings = mappings;
this.classNode = classNode;
this.superGetter = superGetter;
this.sealChecker = sealChecker;
this.context = context;
this.typeAnnotations = setupAnnotations();
this.signature = setupSignature();
@ -144,6 +158,9 @@ public class ClassBuilder {
} else if (classNode.superName.equals("java/lang/Enum")) {
enumClass = true;
builder = TypeSpec.enumBuilder(name);
} else if (classNode.superName.equals("java/lang/Record")) {
recordClass = true;
builder = TypeSpec.recordBuilder(name);
} else {
builder = TypeSpec.classBuilder(name)
.superclass(signature.superclass());
@ -162,7 +179,10 @@ public class ClassBuilder {
}
return builder
.addModifiers(new ModifierBuilder(classNode.access).getModifiers(enumClass ? ModifierBuilder.Type.ENUM : ModifierBuilder.Type.CLASS));
.addModifiers(new ModifierBuilder(classNode.access)
.checkUnseal(classNode, sealChecker)
.getModifiers(ModifierBuilder.getType(enumClass, recordClass))
);
}
private void addInterfaces() {
@ -197,6 +217,7 @@ public class ClassBuilder {
private void addMethods() {
if (classNode.methods == null) return;
methodsLoop:
for (MethodNode method : classNode.methods) {
if ((method.access & Opcodes.ACC_SYNTHETIC) != 0 || (method.access & Opcodes.ACC_MANDATED) != 0) {
continue;
@ -217,6 +238,21 @@ public class ClassBuilder {
formalParamStartIndex = 2; // 0 String 1 int
}
}
if (recordClass) {
// skip record sugars
if (method.name.equals("equals") && method.desc.equals("(Ljava/lang/Object;)Z")
|| method.name.equals("toString") && method.desc.equals("()Ljava/lang/String;")
|| method.name.equals("hashCode") && method.desc.equals("()I")) {
for (AbstractInsnNode insn : method.instructions) {
if (insn instanceof InvokeDynamicInsnNode indy
&& indy.bsm.equals(OBJ_MTH_BOOTSTRAP)
&& indy.name.equals(method.name))
continue methodsLoop;
}
}
// todo test component getters
}
if (instanceInner) {
if (method.name.equals("<init>")) {
formalParamStartIndex = 1; // 0 this$0
@ -229,6 +265,19 @@ public class ClassBuilder {
private void addFields() {
if (classNode.fields == null) return;
for (FieldNode field : classNode.fields) {
if (recordClass && !Modifier.isStatic(field.access)) {
if (!Modifier.isFinal(field.access) || !Modifier.isPrivate(field.access)) {
System.out.println("abnormal instance field " + field.name + " in record " + getClassName() + ", skipping");
} else {
var fieldBuilder = new FieldBuilder(mappings, classNode, field, context);
var paramBuilder = ParameterSpec.builder(fieldBuilder.calculateType(), field.name);
fieldBuilder.addJavaDoc(paramBuilder);
fieldBuilder.addDirectAnnotations(paramBuilder);
builder.addRecordComponent(paramBuilder.build());
}
continue;
}
if ((field.access & Opcodes.ACC_SYNTHETIC) != 0 || (field.access & Opcodes.ACC_MANDATED) != 0) {
continue; // hide synthetic stuff
}
@ -285,7 +334,10 @@ public class ClassBuilder {
classBuilder.builder.addModifiers(javax.lang.model.element.Modifier.STATIC);
} else {
classBuilder.builder.modifiers.remove(javax.lang.model.element.Modifier.PUBLIC); // this modifier may come from class access
classBuilder.builder.addModifiers(new ModifierBuilder(innerClassNode.access).getModifiers(classBuilder.enumClass ? ModifierBuilder.Type.ENUM : ModifierBuilder.Type.CLASS));
classBuilder.builder.addModifiers(new ModifierBuilder(innerClassNode.access)
.checkUnseal(classBuilder.classNode, sealChecker)
.getModifiers(ModifierBuilder.getType(classBuilder.enumClass, classBuilder.recordClass))
);
if (!Modifier.isStatic(innerClassNode.access)) {
classBuilder.instanceInner = true;
}

View File

@ -16,20 +16,21 @@
package net.fabricmc.mappingpoet;
import java.util.AbstractMap;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ArrayTypeName;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import net.fabricmc.mapping.util.EntryTriple;
import net.fabricmc.mappingpoet.signature.AnnotationAwareDescriptors;
import net.fabricmc.mappingpoet.signature.AnnotationAwareSignatures;
import net.fabricmc.mappingpoet.signature.ClassStaticContext;
import net.fabricmc.mappingpoet.signature.TypeAnnotationBank;
import net.fabricmc.mappingpoet.signature.TypeAnnotationMapping;
import net.fabricmc.mappingpoet.signature.TypeAnnotationStorage;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.TypePath;
@ -38,19 +39,18 @@ import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import net.fabricmc.mapping.util.EntryTriple;
import net.fabricmc.mappingpoet.signature.AnnotationAwareDescriptors;
import net.fabricmc.mappingpoet.signature.AnnotationAwareSignatures;
import net.fabricmc.mappingpoet.signature.ClassStaticContext;
import net.fabricmc.mappingpoet.signature.TypeAnnotationBank;
import net.fabricmc.mappingpoet.signature.TypeAnnotationMapping;
import net.fabricmc.mappingpoet.signature.TypeAnnotationStorage;
import java.util.AbstractMap;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class FieldBuilder {
private final MappingsStore mappings;
private final ClassNode classNode;
private final FieldNode fieldNode;
private final FieldSpec.Builder builder;
private final FieldSpec.Builder builder; // todo extract interface to build both record param/field easily
private final TypeAnnotationMapping annotations;
private final ClassStaticContext context;
@ -471,6 +471,10 @@ public class FieldBuilder {
mappings.addFieldDoc(builder::addJavadoc, new EntryTriple(classNode.name, fieldNode.name, fieldNode.desc));
}
void addJavaDoc(ParameterSpec.Builder paramBuilder) {
mappings.addFieldDoc(paramBuilder::addJavadoc, new EntryTriple(classNode.name, fieldNode.name, fieldNode.desc));
}
private void addDirectAnnotations() {
addDirectAnnotations(fieldNode.invisibleAnnotations);
addDirectAnnotations(fieldNode.visibleAnnotations);
@ -485,7 +489,21 @@ public class FieldBuilder {
}
}
private TypeName calculateType() {
void addDirectAnnotations(ParameterSpec.Builder paramBuilder) {
addDirectAnnotations(paramBuilder, fieldNode.invisibleAnnotations);
addDirectAnnotations(paramBuilder, fieldNode.visibleAnnotations);
}
private void addDirectAnnotations(ParameterSpec.Builder paramBuilder, List<AnnotationNode> regularAnnotations) {
if (regularAnnotations == null) {
return;
}
for (AnnotationNode annotation : regularAnnotations) {
paramBuilder.addAnnotation(parseAnnotation(annotation));
}
}
TypeName calculateType() {
if (fieldNode.signature != null) {
return AnnotationAwareSignatures.parseFieldSignature(fieldNode.signature, annotations, context);
}

View File

@ -16,6 +16,14 @@
package net.fabricmc.mappingpoet;
import com.squareup.javapoet.JavaFile;
import net.fabricmc.mappingpoet.signature.ClassStaticContext;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InnerClassNode;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@ -33,22 +41,16 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import com.squareup.javapoet.JavaFile;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InnerClassNode;
import net.fabricmc.mappingpoet.signature.ClassStaticContext;
public class Main {
public static void main(String[] args) {
@ -90,7 +92,7 @@ public class Main {
public static void generate(Path mappings, Path inputJar, Path outputDirectory, Path librariesDir) {
final MappingsStore mapping = new MappingsStore(mappings);
Map<String, ClassBuilder> classes = new HashMap<>();
forEachClass(inputJar, (superGetter, classNode, context) -> writeClass(mapping, classNode, classes, superGetter, context), librariesDir);
forEachClass(inputJar, (superGetter, classNode, context, sealChecker) -> writeClass(mapping, classNode, classes, superGetter, sealChecker, context), librariesDir);
classes.values().stream()
.filter(classBuilder -> !classBuilder.getClassName().contains("$"))
@ -111,6 +113,7 @@ public class Main {
private static void forEachClass(Path jar, ClassNodeConsumer classNodeConsumer, Path librariesDir) {
List<ClassNode> classes = new ArrayList<>();
Map<String, Collection<String>> supers = new HashMap<>();
Set<String> sealedClasses = new HashSet<>(); // their subclsses/impls need non-sealed modifier
Map<String, Boolean> instanceInnerClasses = new ConcurrentHashMap<>();
@ -149,6 +152,10 @@ public class Main {
}
}
if (classNode.permittedSubclasses != null) {
sealedClasses.add(classNode.name);
}
classes.add(classNode);
}
}
@ -161,7 +168,7 @@ public class Main {
ClassStaticContext innerClassContext = new InnerClassStats(instanceInnerClasses);
Function<String, Collection<String>> superGetter = k -> supers.getOrDefault(k, Collections.emptyList());
classes.forEach(node -> classNodeConsumer.accept(superGetter, node, innerClassContext));
classes.forEach(node -> classNodeConsumer.accept(superGetter, node, innerClassContext, sealedClasses::contains));
}
private static void scanInnerClasses(Map<String, Boolean> instanceInnerClasses, Path librariesDir) {
@ -218,7 +225,7 @@ public class Main {
return ch >= '0' && ch <= '9';
}
private static void writeClass(MappingsStore mappings, ClassNode classNode, Map<String, ClassBuilder> existingClasses, Function<String, Collection<String>> superGetter, ClassStaticContext context) {
private static void writeClass(MappingsStore mappings, ClassNode classNode, Map<String, ClassBuilder> existingClasses, Function<String, Collection<String>> superGetter, Predicate<String> sealChecker, ClassStaticContext context) {
String name = classNode.name;
{
//Block anonymous class and their nested classes
@ -232,7 +239,7 @@ public class Main {
}
}
ClassBuilder classBuilder = new ClassBuilder(mappings, classNode, superGetter, context);
ClassBuilder classBuilder = new ClassBuilder(mappings, classNode, superGetter, sealChecker, context);
if (name.contains("$")) {
String parentClass = name.substring(0, name.lastIndexOf("$"));
@ -249,7 +256,7 @@ public class Main {
@FunctionalInterface
private interface ClassNodeConsumer {
void accept(Function<String, Collection<String>> superGetter, ClassNode node, ClassStaticContext staticContext);
void accept(Function<String, Collection<String>> superGetter, ClassNode node, ClassStaticContext staticContext, Predicate<String> sealedChecker);
}
private static final class InnerClassStats implements ClassStaticContext {

View File

@ -16,19 +16,44 @@
package net.fabricmc.mappingpoet;
import java.util.ArrayList;
import java.util.List;
import org.objectweb.asm.tree.ClassNode;
import javax.lang.model.element.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class ModifierBuilder {
private final int access;
private boolean needsUnseal;
public ModifierBuilder(int access) {
this.access = access;
}
public ModifierBuilder checkUnseal(ClassNode node, Predicate<String> sealChecker) {
if (java.lang.reflect.Modifier.isFinal(node.access)) {
return this;
}
if (node.interfaces != null) {
for (String itf : node.interfaces) {
if (sealChecker.test(itf)) {
needsUnseal = true;
return this;
}
}
}
if (node.superName != null && sealChecker.test(node.superName)) {
needsUnseal = true;
}
return this;
}
public Modifier[] getModifiers(Type type) {
List<Modifier> modifiers = new ArrayList<>();
@ -57,7 +82,7 @@ public class ModifierBuilder {
modifiers.add(Modifier.DEFAULT);
}
if (java.lang.reflect.Modifier.isFinal(access) && type != Type.ENUM) {
if (java.lang.reflect.Modifier.isFinal(access) && type != Type.ENUM && type != Type.RECORD) {
modifiers.add(Modifier.FINAL);
}
if (java.lang.reflect.Modifier.isTransient(access) && type == Type.FIELD) {
@ -76,12 +101,21 @@ public class ModifierBuilder {
modifiers.add(Modifier.STRICTFP);
}
if (needsUnseal && type == Type.CLASS) {
modifiers.add(Modifier.NON_SEALED);
}
return modifiers.toArray(new Modifier[]{});
}
public static Type getType(boolean enumType, boolean recordType) {
return enumType ? Type.ENUM : recordType ? Type.RECORD : Type.CLASS;
}
public enum Type {
CLASS,
ENUM,
RECORD,
METHOD,
FIELD,
PARAM