Skip to content
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
128 changes: 59 additions & 69 deletions plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,6 @@ public void writeClass(ClassNode node, TextBuffer buffer, int indent) {
TextBuffer innerBuffer = new TextBuffer();

boolean hasContent = false;
boolean enumFields = false;

List<StructRecordComponent> components = cl.getRecordComponents();

Set<StructField> fieldsToIgnore = new HashSet<>();
Set<StructMethod> methodsToIgnore = new HashSet<>();
Expand Down Expand Up @@ -315,71 +312,37 @@ public void writeClass(ClassNode node, TextBuffer buffer, int indent) {
methodsToIgnore.addAll(companionFunctions.keySet());
}

// FIXME: fields don't have line mappings
// fields
List<StructRecordComponent> components = cl.getRecordComponents();

// Find the last field marked as an enum
int maxEnumIdx = 0;
for (int i = 0; i < cl.getFields().size(); i++) {
StructField fd = cl.getFields().get(i);
boolean isEnum = fd.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM);
if (isEnum) {
maxEnumIdx = i;
}
}

List<StructField> deferredEnumFields = new ArrayList<>();
List<StructField> enumFields = new ArrayList<>();
List<StructField> nonEnumFields = new ArrayList<>();

// Find any regular fields mixed in with the enum fields
// This is invalid but allowed in bytecode.
for (int i = 0; i < cl.getFields().size(); i++) {
StructField fd = cl.getFields().get(i);
for (StructField fd : cl.getFields()) {
boolean isEnum = fd.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM);
if (i < maxEnumIdx && !isEnum) {
deferredEnumFields.add(fd);
if (isEnum) {
enumFields.add(fd);
} else if (!fieldsToIgnore.contains(fd)) {
nonEnumFields.add(fd);
}
}

for (StructField fd : cl.getFields()) {
boolean hide = fd.isSynthetic() && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) ||
wrapper.getHiddenMembers().contains(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor())) ||
deferredEnumFields.contains(fd) ||
fieldsToIgnore.contains(fd);
if (hide) continue;

if (components != null && fd.getAccessFlags() == (CodeConstants.ACC_FINAL | CodeConstants.ACC_PRIVATE) &&
components.stream().anyMatch(c -> c.getName().equals(fd.getName()) && c.getDescriptor().equals(fd.getDescriptor()))) {
// Record component field: skip it
continue;
boolean enums = false;
for (StructField fd : enumFields) {
if (enums) {
innerBuffer.append(',').appendLineSeparator();
}
enums = true;

boolean isEnum = fd.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM);
if (isEnum) {
if (enumFields) {
innerBuffer.append(',').appendLineSeparator();
}
enumFields = true;
} else if (enumFields) {
innerBuffer.append(';');
innerBuffer.appendLineSeparator();
innerBuffer.appendLineSeparator();
enumFields = false;
}

if (propertyData == null || enumFields) { // Enum fields are not considered Kotlin properties
TextBuffer fieldBuffer = new TextBuffer();
writeField(wrapper, cl, fd, fieldBuffer, indent + 1);
fieldBuffer.clearUnassignedBytecodeMappingData();
innerBuffer.append(fieldBuffer);
writeField(innerBuffer, indent, fd, wrapper);
hasContent = true;
}

hasContent = true;
}
if (enums) {
innerBuffer.append(';').appendLineSeparator();
}

if (propertyData != null) {
// Any deferred fields that are property fields should be removed to prevent duplication
deferredEnumFields.removeAll(propertyData.associatedFields());

boolean addedLineAtEnd = false;
for (KProperty prop : propertyData.properties()) {
if (hasContent) {
Expand All @@ -394,7 +357,14 @@ public void writeClass(ClassNode node, TextBuffer buffer, int indent) {
innerBuffer.appendLineSeparator();
}

innerBuffer.append(propBuffer);
StructField fd = prop.underlyingField();
if (fd != null) {
nonEnumFields.remove(fd);
String initializer = fd.hasModifier(CodeConstants.ACC_STATIC) ? "<clinit> ()V" : "<init> ()V";
innerBuffer.append(propBuffer, cl.qualifiedName, initializer);
} else {
innerBuffer.append(propBuffer);
}

if (isMultiline) {
innerBuffer.appendLineSeparator();
Expand All @@ -407,14 +377,25 @@ public void writeClass(ClassNode node, TextBuffer buffer, int indent) {
if (!addedLineAtEnd && !propertyData.properties().isEmpty()) {
innerBuffer.appendLineSeparator();
}
}
} else {
for (StructField fd : nonEnumFields) {
boolean hide = fd.isSynthetic() && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) ||
wrapper.getHiddenMembers().contains(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor()));
if (hide) continue;

// If any fields remaining were deferred but not enum fields, re-add them
for (StructField fd2 : deferredEnumFields) {
TextBuffer fieldBuffer = new TextBuffer();
writeField(wrapper, cl, fd2, fieldBuffer, indent + 1);
fieldBuffer.clearUnassignedBytecodeMappingData();
innerBuffer.append(fieldBuffer);
if (components != null && fd.getAccessFlags() == (CodeConstants.ACC_FINAL | CodeConstants.ACC_PRIVATE) &&
components.stream().anyMatch(c -> c.getName().equals(fd.getName()) && c.getDescriptor().equals(fd.getDescriptor()))) {
// Record component field: skip it
continue;
}

DecompilerContext.getLogger().writeMessage("Non-enum field " + fd.getName() + " is not associated with Kotlin property", IFernflowerLogger.Severity.WARN);

TextBuffer fieldBuffer = new TextBuffer();
writeField(wrapper, wrapper.getClassStruct(), fd, fieldBuffer, indent + 1);
String initializer = fd.hasModifier(CodeConstants.ACC_STATIC) ? "<clinit> ()V" : "<init> ()V";
innerBuffer.append(fieldBuffer, wrapper.getClassStruct().qualifiedName, initializer);
}
}

// methods
Expand Down Expand Up @@ -526,7 +507,13 @@ private void writeKotlinFile(ClassNode node, TextBuffer buffer, int indent, KPro
buffer.appendLineSeparator();
}

buffer.append(propBuffer);
StructField fd = prop.underlyingField();
if (fd != null) {
String initializer = fd.hasModifier(CodeConstants.ACC_STATIC) ? "<clinit> ()V" : "<init> ()V";
buffer.append(propBuffer, cl.qualifiedName, initializer);
} else {
buffer.append(propBuffer);
}

if (isMultiline) {
buffer.appendLineSeparator();
Expand All @@ -543,11 +530,7 @@ private void writeKotlinFile(ClassNode node, TextBuffer buffer, int indent, KPro

for (StructField fd : cl.getFields()) {
if (propertyData.associatedFields().contains(fd)) continue;

TextBuffer fieldBuffer = new TextBuffer();
writeField(wrapper, cl, fd, fieldBuffer, indent);
fieldBuffer.clearUnassignedBytecodeMappingData();
buffer.append(fieldBuffer);
writeField(buffer, indent, fd, wrapper);
}

for (int i = 0; i < cl.getMethods().size(); i++) {
Expand Down Expand Up @@ -827,6 +810,13 @@ private void writeClassDefinition(ClassNode node, TextBuffer buffer, int indent,
buffer.popNewlineGroup();
}

private void writeField(TextBuffer buffer, int indent, StructField fd, ClassWrapper wrapper) {
TextBuffer fieldBuffer = new TextBuffer();
writeField(wrapper, wrapper.getClassStruct(), fd, fieldBuffer, indent + 1);
String initializer = fd.hasModifier(CodeConstants.ACC_STATIC) ? "<clinit> ()V" : "<init> ()V";
buffer.append(fieldBuffer, wrapper.getClassStruct().qualifiedName, initializer);
}

public void writeField(ClassWrapper wrapper, StructClass cl, StructField fd, TextBuffer buffer, int indent) {
boolean isInterface = cl.hasModifier(CodeConstants.ACC_INTERFACE);
boolean isDeprecated = fd.hasAttribute(StructGeneralAttribute.ATTRIBUTE_DEPRECATED);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public TextBuffer stringify(int indent) {

if (initializer != null) {
TextBuffer initializerBuf = initializer.toJava(indent);
initializerBuf.clearUnassignedBytecodeMappingData();
// initializerBuf.clearUnassignedBytecodeMappingData();
if (flags.isDelegated) {
buf.append(" by ")
.append(initializerBuf);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package pkg

public class TestClassicStringInterpolation {
public final val x: Int = 5
public final val x: Int = 5// 16

public fun stringInterpolation(x: Int, y: String) {
System.out.println("$x $y");// 5
Expand All @@ -25,6 +25,10 @@ public class TestClassicStringInterpolation {
}

class 'pkg/TestClassicStringInterpolation' {
method '<init> ()V' {
5 3
}

method 'stringInterpolation (ILjava/lang/String;)V' {
d 6
16 6
Expand Down Expand Up @@ -111,7 +115,10 @@ Lines mapping:
10 <-> 12
13 <-> 15
14 <-> 16
16 <-> 4
18 <-> 19
19 <-> 20
22 <-> 23
23 <-> 24
Not mapped:
3
49 changes: 46 additions & 3 deletions plugins/kotlin/testData/results/pkg/TestConvertedK2JOps.dec
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package pkg

public class TestConvertedK2JOps {
public final val list: List<String> = CollectionsKt.listOf(new java.lang.String[]{"a", "b", "c"})
public final val set: Set<String> = SetsKt.setOf(new java.lang.String[]{"a", "b", "c"})
public final val map: Map<String, String> = MapsKt.mapOf(new Pair[]{TuplesKt.to("a", "b"), TuplesKt.to("c", "d")})
public final val list: List<String> = CollectionsKt.listOf(new java.lang.String[]{"a", "b", "c"})// 4
public final val set: Set<String> = SetsKt.setOf(new java.lang.String[]{"a", "b", "c"})// 5
public final val map: Map<String, String> = MapsKt.mapOf(new Pair[]{TuplesKt.to("a", "b"), TuplesKt.to("c", "d")})// 6
public final val any: Any = new Object()

public fun codeConstructs() {
Expand All @@ -12,6 +12,44 @@ public class TestConvertedK2JOps {
}

class 'pkg/TestConvertedK2JOps' {
method '<init> ()V' {
c 3
d 3
11 3
12 3
16 3
17 3
1a 3
1b 3
1c 3
28 4
29 4
2d 4
2e 4
32 4
33 4
36 4
37 4
38 4
44 5
45 5
46 5
47 5
48 5
49 5
4a 5
4e 5
4f 5
50 5
51 5
52 5
53 5
54 5
57 5
58 5
59 5
}

method 'codeConstructs ()V' {
0 9
1 9
Expand All @@ -26,7 +64,12 @@ class 'pkg/TestConvertedK2JOps' {
}

Lines mapping:
4 <-> 4
5 <-> 5
6 <-> 6
10 <-> 10
12 <-> 11
Not mapped:
3
7
11
Loading