Skip to content

Commit

Permalink
Fix StringBuilder-based single character issues
Browse files Browse the repository at this point in the history
  • Loading branch information
sschr15 committed Aug 6, 2024
1 parent 89ab540 commit b551d6e
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 4 deletions.
1 change: 1 addition & 0 deletions plugins/kotlin/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/build/
/out/
/testData/classes/kt/
/testData/classes/ktold/
bin
19 changes: 19 additions & 0 deletions plugins/kotlin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ archivesBaseName = 'vineflower-kotlin'

sourceSets {
testDataKotlin.kotlin.srcDirs files("testData/src/kt/")
testDataOldKotlin.kotlin.srcDirs files("testData/src/ktold/")
}

configurations {
testDataOldKotlinImplementation.extendsFrom testDataKotlinImplementation
}

dependencies {
Expand All @@ -35,6 +40,20 @@ compileTestDataKotlinKotlin {
}
testDataClasses.dependsOn(testDataKotlinClasses)

compileTestDataOldKotlinKotlin {
destinationDirectory = file("testData/classes/ktold")
kotlinOptions {
compilerOptions {
freeCompilerArgs = [ // Kotlin 2.0 defaults to invokedynamic implementations. Use class implementations instead.
"-Xlambdas=class",
"-Xsam-conversions=class",
"-Xstring-concat=inline",
]
}
}
}
testDataClasses.dependsOn(testDataOldKotlinClasses)

jar {
enabled = false
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.vineflower.kotlin.expr;

import org.jetbrains.java.decompiler.main.DecompilerContext;
import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
import org.jetbrains.java.decompiler.modules.decompiler.exps.*;
import org.jetbrains.java.decompiler.modules.decompiler.vars.CheckTypesResult;
Expand Down Expand Up @@ -201,10 +202,24 @@ public TextBuffer toJava(int indent) {
case STR_CONCAT -> {
buf.append('"');
for (Exprent expr : lstOperands) {
if (expr instanceof ConstExprent constExpr && VarType.VARTYPE_STRING.equals(constExpr.getExprType())) {
boolean ascii = DecompilerContext.getOption(IFernflowerPreferences.ASCII_STRING_CHARACTERS);
String value = ConstExprent.convertStringToJava((String) constExpr.getValue(), ascii);
buf.append(value.replace("$", "\\$"));
if (expr instanceof ConstExprent constExpr) {
// Strings can be directly placed into the resulting string, but other constants are a little more touchy.
// Kotlin will inline any primitive constants. If any are found, warn on the presence of such cases as that
// implies that either the compiler or decompiler is not working as expected.
if (VarType.VARTYPE_STRING.equals(constExpr.getConstType())) {
boolean ascii = DecompilerContext.getOption(IFernflowerPreferences.ASCII_STRING_CHARACTERS);
String value = ConstExprent.convertStringToJava((String) constExpr.getValue(), ascii);
buf.append(value.replace("$", "\\$"));
} else if (VarType.VARTYPE_CHAR.equals(constExpr.getConstType())) {
// The compiler uses `StringBuilder.append(char)` on single characters when using StringBuilder
// instead of makeConcatWithConstants. Inline the character directly - intentional behavior.
buf.append((char) (int) constExpr.getValue());
} else {
if (VarType.isPrimitive(constExpr.getConstType())) {
DecompilerContext.getLogger().writeMessage("Primitive constant type in string concatenation: " + constExpr.getConstType(), IFernflowerLogger.Severity.WARN);
}
buf.append("${").append(constExpr.toJava(indent)).append("}");
}
} else if (expr instanceof VarExprent var) {
buf.append("$").append(var.toJava(indent));
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.nio.file.Path;

import static org.jetbrains.java.decompiler.SingleClassesTestBase.TestDefinition.Version.KOTLIN;
import static org.jetbrains.java.decompiler.SingleClassesTestBase.TestDefinition.Version.KOTLIN_OLD;

public class KotlinTests extends SingleClassesTestBase {

Expand Down Expand Up @@ -86,5 +87,6 @@ private void registerKotlinTests() {
register(KOTLIN, "TestConstructors");
register(KOTLIN, "TestContracts");
register(KOTLIN, "TestStringInterpolation");
register(KOTLIN_OLD, "TestClassicStringInterpolation");
}
}
118 changes: 118 additions & 0 deletions plugins/kotlin/testData/results/pkg/TestClassicStringInterpolation.dec
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package pkg

class TestClassicStringInterpolation {
public final val x: Int = 5


public fun stringInterpolation(x: Int, y: String) {
System.out.println("$x $y");// 5
}// 6

public fun testConstant(x: Int) {
System.out.println("$x 5");// 9
}// 10

public fun testExpression(x: Int) {
System.out.println("$x ${x + 1}");// 13
}// 14

public fun testProperty() {
System.out.println("${this.x}!");// 18
}// 19

public fun testLiteralClass() {
System.out.println("${TestClassicStringInterpolation::class.java}!");// 22
}// 23
}

class 'pkg/TestClassicStringInterpolation' {
method 'stringInterpolation (ILjava/lang/String;)V' {
d 7
16 7
1a 7
1b 7
1c 7
1d 7
1e 7
1f 7
21 7
22 7
23 7
24 8
}

method 'testConstant (I)V' {
7 11
10 11
11 11
12 11
13 11
14 11
15 11
17 11
18 11
19 11
1a 12
}

method 'testExpression (I)V' {
7 15
10 15
11 15
12 15
16 15
17 15
18 15
19 15
1a 15
1b 15
1d 15
1e 15
1f 15
20 16
}

method 'testProperty ()V' {
7 19
8 19
9 19
a 19
13 19
14 19
15 19
16 19
17 19
18 19
1a 19
1b 19
1c 19
1d 20
}

method 'testLiteralClass ()V' {
7 23
8 23
11 23
12 23
13 23
14 23
15 23
16 23
18 23
19 23
1a 23
1b 24
}
}

Lines mapping:
5 <-> 8
6 <-> 9
9 <-> 12
10 <-> 13
13 <-> 16
14 <-> 17
18 <-> 20
19 <-> 21
22 <-> 24
23 <-> 25
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package pkg

class TestClassicStringInterpolation {
fun stringInterpolation(x: Int, y: String) {
println("$x $y")
}

fun testConstant(x: Int) {
println("$x ${5}")
}

fun testExpression(x: Int) {
println("$x ${x + 1}")
}

val x = 5
fun testProperty() {
println("$x!")
}

fun testLiteralClass() {
println("${TestClassicStringInterpolation::class.java}!")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ public enum Version {
JAVA_21_PREVIEW(21, "preview", "Preview"),
GROOVY("groovy", "Groovy"),
KOTLIN("kt", "Kotlin"),
KOTLIN_OLD("ktold", "Kotlin (old defaults)"),
SCALA("scala", "Scala"),
JASM("jasm", "Custom (jasm)"),
;
Expand Down

0 comments on commit b551d6e

Please sign in to comment.