Skip to content

Commit

Permalink
Fix handling of primitive type classes
Browse files Browse the repository at this point in the history
In cases when metadata contained `@Serializer(forClass = int.class)` `int.class` part was treated as a class element value, and previously, in the refactored method, its internal className was calculated to be null as primitive class names were not taken into account apparently. It led to stacktrace like this:
```
java.lang.NullPointerException: Cannot invoke "String.length()" because "className" is null
	at com.guardsquare.proguard.kotlin.printer.internal.Context.className(Context.java:83)
	at com.guardsquare.proguard.kotlin.printer.internal.AnnotationPrinter.visitClassElementValue(AnnotationPrinter.java:138)
        ...
```
This diff adds a primitive type class check to have the name derived correctly.
  • Loading branch information
olesya.subbotina authored and mrjameshamilton committed Apr 11, 2024
1 parent 41c8a82 commit c65a182
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@

import com.guardsquare.proguard.kotlin.printer.KotlinMetadataPrinter;
import com.guardsquare.proguard.kotlin.printer.internal.visitor.ConstantToStringVisitor;
import java.util.HashMap;
import java.util.Map;
import proguard.classfile.Clazz;
import proguard.classfile.TypeConstants;
import proguard.classfile.attribute.Attribute;
import proguard.classfile.attribute.annotation.Annotation;
import proguard.classfile.attribute.annotation.AnnotationElementValue;
Expand Down Expand Up @@ -40,7 +43,18 @@ public class AnnotationPrinter
private final KotlinMetadataPrinter printer;
private final boolean inline;
private int level = 0;

private static final Map<Character, String> primitiveTypeToClass = new HashMap<>();

static {
primitiveTypeToClass.put(TypeConstants.BOOLEAN, "Boolean::class");
primitiveTypeToClass.put(TypeConstants.BYTE, "Byte::class");
primitiveTypeToClass.put(TypeConstants.CHAR, "Char::class");
primitiveTypeToClass.put(TypeConstants.SHORT, "Short::class");
primitiveTypeToClass.put(TypeConstants.INT, "Int::class");
primitiveTypeToClass.put(TypeConstants.FLOAT, "Float::class");
primitiveTypeToClass.put(TypeConstants.LONG, "Long::class");
primitiveTypeToClass.put(TypeConstants.DOUBLE, "Double::class");
}

public AnnotationPrinter(KotlinMetadataPrinter printer)
{
Expand Down Expand Up @@ -130,15 +144,20 @@ public void visitEnumConstantElementValue(Clazz clazz,
printer.print("." + enumConstantElementValue.getConstantName(clazz));
}


@Override
public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue)
{
visitAnyElementValue(clazz, annotation, classElementValue);
printer.print(printer.getContext().className(ClassUtil.internalClassNameFromType(classElementValue.getClassName(clazz)), "."));
String internalClassName;
String className = classElementValue.getClassName(clazz);
if (ClassUtil.isInternalPrimitiveType(className)) {
internalClassName = primitiveTypeToClass.get(className.charAt(0));
} else {
internalClassName = ClassUtil.internalClassNameFromType(className);
}
printer.print(printer.getContext().className(internalClassName, "."));
}


@Override
public void visitAnnotationElementValue(Clazz clazz,
Annotation annotation,
Expand Down
55 changes: 55 additions & 0 deletions kmp-library/src/test/kotlin/PrimitiveInAnnotationTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import com.guardsquare.proguard.kotlin.printer.KotlinMetadataPrinter
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import proguard.classfile.kotlin.visitor.ReferencedKotlinMetadataVisitor
import proguard.testutils.ClassPoolBuilder
import proguard.testutils.KotlinSource

class PrimitiveInAnnotationTest : FunSpec({
test("Primitive type class in annotation") {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
import kotlin.reflect.KClass

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Serializer(val forClass: KClass<*>)

@Serializer(forClass = Int::class)
class Test {
var myInt: Int = 0
}
""".trimIndent()
),
)

programClassPool.classesAccept(
ReferencedKotlinMetadataVisitor(
KotlinMetadataPrinter(
programClassPool
)
)
)

val testKtMetadata = programClassPool.getClass("Test").processingInfo as String

testKtMetadata.trimEnd() shouldBe """
/**
* Kotlin class (metadata version 1.8.0).
* From Java class: Test
*/
@Serializer(forClass = Int::class)
class Test {
// Properties
var myInt: Int
// backing field: int myInt
get // getter method: public final int getMyInt()
set() // setter method: public final void setMyInt(int)
}
""".trimIndent()
}
})

0 comments on commit c65a182

Please sign in to comment.