Skip to content

Commit

Permalink
Completely redo kotlin annotation declarations (again)
Browse files Browse the repository at this point in the history
  • Loading branch information
sschr15 committed Sep 9, 2023
1 parent 53b60d7 commit 53dedc5
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 99 deletions.
174 changes: 90 additions & 84 deletions plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,13 @@
import org.jetbrains.java.decompiler.struct.gen.generics.GenericClassDescriptor;
import org.jetbrains.java.decompiler.struct.gen.generics.GenericFieldDescriptor;
import org.jetbrains.java.decompiler.struct.gen.generics.GenericMethodDescriptor;
import org.jetbrains.java.decompiler.util.InterpreterUtil;
import org.jetbrains.java.decompiler.util.Key;
import org.jetbrains.java.decompiler.util.TextBuffer;
import org.jetbrains.java.decompiler.util.TextUtil;
import org.jetbrains.java.decompiler.util.*;
import org.jetbrains.java.decompiler.util.collections.VBStyleCollection;
import org.vineflower.kotlin.expr.KAnnotationExprent;
import org.vineflower.kotlin.metadata.MetadataNameResolver;
import org.vineflower.kotlin.struct.KConstructor;
import org.vineflower.kotlin.struct.KFunction;
import org.vineflower.kotlin.struct.KProperty;
import org.vineflower.kotlin.struct.*;
import org.vineflower.kotlin.util.KTypes;
import org.vineflower.kotlin.util.KUtils;
import org.vineflower.kotlin.util.ProtobufFlags;

import java.io.IOException;
Expand All @@ -57,6 +53,7 @@ public class KotlinWriter implements StatementWriter {
));
private static final String NOT_NULL_ANN_NAME = "org/jetbrains/annotations/NotNull";
private static final String NULLABLE_ANN_NAME = "org/jetbrains/annotations/Nullable";
private static final String REPEATABLE_ANN_NAME = "java/lang/annotation/Repeatable";
private static final Set<String> KT_HARD_KEYWORDS = new HashSet<>(Arrays.asList(
"as",
"break",
Expand Down Expand Up @@ -222,6 +219,10 @@ public void writeClass(ClassNode node, TextBuffer buffer, int indent) {

DecompilerContext.getLogger().startWriteClass(cl.qualifiedName);

KProperty.Data propertyData = KProperty.parse(node);
Map<StructMethod, KFunction> functions = KFunction.parse(node);
KConstructor.Data constructorData = KConstructor.parse(node);

if (DecompilerContext.getOption(IFernflowerPreferences.SOURCE_FILE_COMMENTS)) {
StructSourceFileAttribute sourceFileAttr = node.classStruct
.getAttribute(StructGeneralAttribute.ATTRIBUTE_SOURCE_FILE);
Expand All @@ -238,23 +239,18 @@ public void writeClass(ClassNode node, TextBuffer buffer, int indent) {

if (cl.hasModifier(CodeConstants.ACC_ANNOTATION)) {
// Kotlin's annotation classes are treated quite differently from other classes
writeAnnotationDefinition(node, buffer, indent);
writeAnnotationDefinition(node, buffer, indent, propertyData, functions, constructorData);
return;
}

if (KotlinDecompilationContext.getCurrentType() == KotlinDecompilationContext.KotlinType.FILE) {
writeKotlinFile(node, buffer, indent);
writeKotlinFile(node, buffer, indent, propertyData, functions); // no constructors in top level file
return;
}

KProperty.Data propertyData = KProperty.parse(node);
Map<StructMethod, KFunction> functions = KFunction.parse(node);
KConstructor.Data constructorData = KConstructor.parse(node);

// write class definition
writeClassDefinition(node, buffer, indent, constructorData);


boolean hasContent = false;
boolean enumFields = false;

Expand Down Expand Up @@ -423,11 +419,9 @@ public void writeClass(ClassNode node, TextBuffer buffer, int indent) {
}
}

private void writeKotlinFile(ClassNode node, TextBuffer buffer, int indent) {
private void writeKotlinFile(ClassNode node, TextBuffer buffer, int indent, KProperty.Data propertyData, Map<StructMethod, KFunction> functions) {
ClassWrapper wrapper = node.getWrapper();
StructClass cl = wrapper.getClassStruct();
KProperty.Data propertyData = KProperty.parse(node);
Map<StructMethod, KFunction> functions = KFunction.parse(node);

for (KProperty property : propertyData.properties) {
buffer.append(property.stringify(indent));
Expand Down Expand Up @@ -471,10 +465,23 @@ private void writeKotlinFile(ClassNode node, TextBuffer buffer, int indent) {
}
}

private void writeAnnotationDefinition(ClassesProcessor.ClassNode node, TextBuffer buffer, int indent) {
private void writeAnnotationDefinition(ClassNode node, TextBuffer buffer, int indent, KProperty.Data propertyData, Map<StructMethod, KFunction> functions, KConstructor.Data constructorData) {
ClassWrapper wrapper = node.getWrapper();
StructClass cl = wrapper.getClassStruct();

StructAnnotationAttribute runtimeAnnotations = cl.getAttribute(StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS);
AnnotationExprent repeatableAnnotation = runtimeAnnotations.getAnnotations().stream()
.filter(a -> REPEATABLE_ANN_NAME.equals(a.getClassName()))
.findFirst()
.orElse(null);

String repeatableContainer = null;

if (repeatableAnnotation != null) {
repeatableContainer = ((ConstExprent) repeatableAnnotation.getParValues().get(0)).getValue().toString();
runtimeAnnotations.getAnnotations().remove(repeatableAnnotation);
}

appendAnnotations(buffer, indent, cl, -1);
appendJvmAnnotations(buffer, indent, cl, true, cl.getPool(), TypeAnnotation.CLASS_TYPE_PARAMETER);

Expand All @@ -488,89 +495,88 @@ private void writeAnnotationDefinition(ClassesProcessor.ClassNode node, TextBuff
.append("(")
.appendLineSeparator();

boolean hasCompanion = !cl.getFields().isEmpty();
List<KProperty> nonParameterProperties = new ArrayList<>(propertyData.properties);

if (!cl.getMethods().isEmpty()) {
boolean first = true;
for (StructMethod mt : cl.getMethods()) {
if (mt.hasModifier(CodeConstants.ACC_STATIC)) {
hasCompanion = true;
continue;
}

if (first) {
first = false;
} else {
buffer.append(',').appendLineSeparator();
}

buffer.appendIndent(indent + 1)
.append("val ")
.append(mt.getName())
.append(": ");

String type = mt.getDescriptor().substring(2);
VarType varType = new VarType(type, false);

buffer.append(KTypes.getKotlinType(varType));

if (mt.hasAttribute(StructGeneralAttribute.ATTRIBUTE_ANNOTATION_DEFAULT)) {
buffer.append(" = ");
StructAnnDefaultAttribute attr = mt.getAttribute(StructGeneralAttribute.ATTRIBUTE_ANNOTATION_DEFAULT);
KAnnotationExprent.writeAnnotationValue(attr.getDefaultValue(), buffer);
boolean first = true;
for (KParameter param : constructorData.primary.parameters) {
if (!first) {
buffer.append(",").appendLineSeparator();
}
first = false;
buffer.appendIndent(indent + 1)
.append("val ")
.append(KotlinWriter.toValidKotlinIdentifier(param.name))
.append(": ")
.append(param.type.stringify(indent + 1));

// Because Kotlin really doesn't like making this easy for us, defaults are still passed directly via attributes
KProperty prop = propertyData.properties.stream()
.filter(p -> p.name.equals(param.name))
.findFirst()
.orElseThrow();

nonParameterProperties.remove(prop);

KPropertyAccessor getter = prop.getter;
if (getter != null) {
StructMethod mt = getter.underlyingMethod.methodStruct;
StructAnnDefaultAttribute paramAttr = mt.getAttribute(StructGeneralAttribute.ATTRIBUTE_ANNOTATION_DEFAULT);
if (paramAttr != null) {
Exprent kExpr = KUtils.replaceExprent(paramAttr.getDefaultValue());
Exprent expr = kExpr != null ? kExpr : paramAttr.getDefaultValue();
buffer.append(" = ").append(expr.toJava());
}
}

buffer.appendLineSeparator().appendIndent(indent).append(')');
}

if (hasCompanion || !node.nested.isEmpty()) {
buffer.append(" {").appendLineSeparator();
buffer.appendLineSeparator()
.appendIndent(indent)
.append(")");

// member classes
boolean first = false;
for (ClassNode inner : node.nested) {
if (inner.type == ClassNode.Type.MEMBER) {
StructClass innerCl = inner.classStruct;
boolean isSynthetic = (inner.access & CodeConstants.ACC_SYNTHETIC) != 0 || innerCl.isSynthetic();
boolean hide = isSynthetic && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) ||
wrapper.getHiddenMembers().contains(innerCl.qualifiedName);
if (hide) continue;
boolean appended = false;

if (first) {
buffer.appendLineSeparator();
}
writeClass(inner, buffer, indent + 1);
TextBuffer innerBuffer = new TextBuffer();

first = true;
}
}
for (KProperty prop : nonParameterProperties) {
innerBuffer.append(prop.stringify(indent + 1));
appended = true;
}

if (hasCompanion) {
buffer.appendIndent(indent + 1).append("companion object {").appendLineSeparator();
first = true;
for (KFunction function : functions.values()) {
if (!first || appended) {
innerBuffer.appendLineSeparator();
}
first = false;

for (StructField fd : cl.getFields()) {
if (!fd.hasModifier(CodeConstants.ACC_STATIC)) {
appendComment(buffer, "Illegal field (must be static): " + fd.getName(), indent + 2);
}
innerBuffer.append(function.stringify(indent + 1));
appended = true;
}

writeField(wrapper, cl, fd, buffer, indent + 2);
buffer.appendLineSeparator();
first = true;
for (ClassNode inner : node.nested) {
if (inner.type == ClassNode.Type.MEMBER) {
if (inner.classStruct.qualifiedName.equals(repeatableContainer)) {
// Skip the container class
continue;
}

for (StructMethod mt : cl.getMethods()) {
if (!mt.hasModifier(CodeConstants.ACC_STATIC)) {
continue;
}

writeMethod(node, mt, 0, buffer, indent + 2);
buffer.appendLineSeparator();
if (!first || appended) {
innerBuffer.appendLineSeparator();
}
first = false;

buffer.appendIndent(indent + 1).append('}').appendLineSeparator();
writeClass(inner, innerBuffer, indent + 1);
appended = true;
}
}

buffer.appendIndent(indent).append('}');
if (appended) {
buffer.append(" {")
.appendLineSeparator()
.append(innerBuffer)
.appendIndent(indent)
.append("}");
}

buffer.appendLineSeparator();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ public static Data parse(ClassesProcessor.ClassNode node) {
String desc = resolver.resolve(signature.getDesc());
MethodWrapper method = wrapper.getMethodWrapper("<init>", desc);
if (method == null) {
if (classFlags.kind == ProtoBuf.Class.Kind.ANNOTATION_CLASS) {
// Annotation classes are very odd and don't actually have a constructor under the hood
KConstructor kConstructor = new KConstructor(parameters, flags, null, false, node);
return new Data(null, kConstructor);
}

DecompilerContext.getLogger().writeMessage("Method <init>" + desc + " not found in " + struct.qualifiedName, IFernflowerLogger.Severity.WARN);
continue;
}
Expand Down
22 changes: 7 additions & 15 deletions plugins/kotlin/testData/results/pkg/TestAnnotations.dec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package pkg

import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import kotlin.jvm.internal.RepeatableContainer

class TestAnnotations {
@TestAnnotations.TestAnnotation(first = "test", second = 1)
Expand All @@ -15,34 +14,27 @@ class TestAnnotations {

@Repeatable
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Repeatable(TestAnnotations.RepeatableAnnotation.Container::class)
annotation class RepeatableAnnotation(
val value: java.lang.String
) {
@RepeatableContainer
@Retention(RetentionPolicy.RUNTIME)
annotation class Container(
val value: Array<TestAnnotations.RepeatableAnnotation>
)
}
val value: String
)

@Retention(RetentionPolicy.RUNTIME)
annotation class TestAnnotation(
val first: java.lang.String,
val first: String,
val second: Int
)
}

class 'pkg/TestAnnotations' {
method 'test ()V' {
0 9
0 8
}

method 'test2 ()V' {
0 13
0 12
}
}

Lines mapping:
16 <-> 10
21 <-> 14
16 <-> 9
21 <-> 13

0 comments on commit 53dedc5

Please sign in to comment.