diff --git a/base/src/main/java/proguard/optimize/evaluation/SimpleEnumUseSimplifier.java b/base/src/main/java/proguard/optimize/evaluation/SimpleEnumUseSimplifier.java index 1ada73573..2bdccb099 100644 --- a/base/src/main/java/proguard/optimize/evaluation/SimpleEnumUseSimplifier.java +++ b/base/src/main/java/proguard/optimize/evaluation/SimpleEnumUseSimplifier.java @@ -36,6 +36,8 @@ import proguard.evaluation.value.*; import proguard.optimize.info.SimpleEnumMarker; +import static proguard.classfile.ClassConstants.NAME_JAVA_LANG_OBJECT; + /** * This AttributeVisitor simplifies the use of enums in the code attributes that * it visits. @@ -277,6 +279,15 @@ public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute c invokedMethodName, invokedMethodType); } + else if (isObjectCloneCallPoppingSimpleEnumArray( + offset, + stackEntryIndex, + invokedMethodName, + invokedMethodType, + clazz.getRefClassName(constantInstruction.constantIndex))) + { + replaceObjectCloneCall(clazz, offset, constantInstruction, invokedMethodName, invokedMethodType); + } // Fall through to check the parameters. } @@ -567,6 +578,26 @@ private boolean isPoppingSimpleEnumArray(int offset, int stackEntryIndex) ClassUtil.internalArrayTypeDimensionCount(referenceValue.getType()) == 1; } + /** + * Returns whether the instruction at the given offset is popping a simple enum array + * and the class reference is java.lang.Object. + *
+ * This accounts for the Kotlin compiler generating the following call in the `values()` method:
+ *
+ * invokevirtual [Ljava/lang/Object;->clone();
+ *
+ * where the Java compiler would generate:
+ *
+ * invokevirtual [LMyEnum;->clone();
+ *
+ */
+ private boolean isObjectCloneCallPoppingSimpleEnumArray(int offset, int stackEntryIndex, String invokedMethodName, String invokedMethodType, String classRef)
+ {
+ return invokedMethodName.equals("clone") &&
+ invokedMethodType.equals("()Ljava/lang/Object;") &&
+ ClassUtil.internalClassNameFromClassType(classRef).equals(NAME_JAVA_LANG_OBJECT) &&
+ isPoppingSimpleEnumArray(offset, stackEntryIndex);
+ }
/**
* Returns whether the given class is not null and a simple enum class.
@@ -603,6 +634,29 @@ private void replaceSupportedMethod(Clazz clazz,
}
}
+ /**
+ * Replaces a Kotlin generated clone() invocation with the correct integer array invocation.
+ */
+ private void replaceObjectCloneCall(Clazz clazz,
+ int offset,
+ Instruction instruction,
+ String name,
+ String type)
+ {
+ if (name.equals("clone") && type.equals("()Ljava/lang/Object;"))
+ {
+ ConstantPoolEditor cpe = new ConstantPoolEditor((ProgramClass) clazz);
+ Instruction[] replacementInstructions = new Instruction[] {
+ new ConstantInstruction(
+ Instruction.OP_INVOKEVIRTUAL,
+ cpe.addMethodrefConstant("[I", "clone", "()Ljava/lang/Object;", null, null)
+ )
+ };
+
+ replaceInstructions(clazz, offset, instruction, replacementInstructions);
+ }
+ }
+
/**
* Replaces the instruction at the given offset by the given instructions.
diff --git a/base/src/test/kotlin/proguard/optimize/EnumSimplifierTest.kt b/base/src/test/kotlin/proguard/optimize/EnumSimplifierTest.kt
new file mode 100644
index 000000000..f0a3b4e26
--- /dev/null
+++ b/base/src/test/kotlin/proguard/optimize/EnumSimplifierTest.kt
@@ -0,0 +1,109 @@
+package proguard.optimize
+
+import io.kotest.core.spec.style.FreeSpec
+import io.kotest.matchers.shouldBe
+import proguard.classfile.ClassPool
+import proguard.classfile.ProgramClass
+import proguard.classfile.ProgramMember
+import proguard.classfile.attribute.visitor.AllAttributeVisitor
+import proguard.classfile.editor.MemberReferenceFixer
+import proguard.classfile.util.ClassPoolClassLoader
+import proguard.classfile.visitor.AllMemberVisitor
+import proguard.classfile.visitor.AllMethodVisitor
+import proguard.classfile.visitor.MemberNameFilter
+import proguard.classfile.visitor.MemberVisitor
+import proguard.optimize.evaluation.SimpleEnumClassSimplifier
+import proguard.optimize.evaluation.SimpleEnumDescriptorSimplifier
+import proguard.optimize.evaluation.SimpleEnumUseSimplifier
+import proguard.optimize.info.ProgramClassOptimizationInfoSetter
+import proguard.optimize.info.ProgramMemberOptimizationInfoSetter
+import proguard.optimize.info.SimpleEnumFilter
+import proguard.optimize.info.SimpleEnumMarker
+import proguard.testutils.ClassPoolBuilder
+import proguard.testutils.JavaSource
+import proguard.testutils.KotlinSource
+import proguard.testutils.and
+import proguard.testutils.match
+
+class EnumSimplifierTest : FreeSpec({
+ "Test simplification of simple Kotlin enum (#349)" {
+ val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
+ KotlinSource(
+ "TestEnum.kt", "enum class TestEnum { A, B, C }"
+ )
+ )
+
+ simplifyEnum(libraryClassPool, programClassPool)
+ }
+
+ "Test simplification of simple Java enum" {
+ val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
+ JavaSource(
+ "TestEnum.java", "public enum TestEnum { A, B, C, }"
+ )
+ )
+
+ simplifyEnum(libraryClassPool, programClassPool)
+ }
+})
+
+private fun simplifyEnum(libraryClassPool: ClassPool, programClassPool: ClassPool) {
+ // Setup the OptimizationInfo on the classes
+ val keepMarker = KeepMarker()
+ libraryClassPool.classesAccept(keepMarker)
+ libraryClassPool.classesAccept(AllMemberVisitor(keepMarker))
+ programClassPool.classesAccept(ProgramClassOptimizationInfoSetter())
+ programClassPool.classesAccept(AllMemberVisitor(ProgramMemberOptimizationInfoSetter()))
+
+ programClassPool.classAccept("TestEnum", SimpleEnumMarker(true))
+
+ val enumClazz = programClassPool.getClass("TestEnum")
+
+ SimpleEnumMarker.isSimpleEnum(enumClazz) shouldBe true
+
+ // Application of enum simplification similar to the Optimizer.
+
+ // Simplify the use of the enum classes in code.
+ programClassPool.classesAccept(
+ AllMethodVisitor(
+ AllAttributeVisitor(
+ SimpleEnumUseSimplifier()
+ )
+ )
+ )
+
+ // Simplify the static initializers of simple enum classes.
+ programClassPool.classesAccept(
+ SimpleEnumFilter(
+ SimpleEnumClassSimplifier()
+ )
+ )
+
+ // Simplify the use of the enum classes in descriptors.
+ programClassPool.classesAccept(
+ SimpleEnumDescriptorSimplifier()
+ )
+
+ // Update references to class members with simple enum classes.
+ programClassPool.classesAccept(MemberReferenceFixer(false))
+
+ // Trigger class loading, that would previously result in a verify error.
+ val classPoolClassLoader = ClassPoolClassLoader(programClassPool)
+ val clazz = classPoolClassLoader.findClass("TestEnum")
+ clazz.fields.single { it.name.startsWith("A") }.get(null) shouldBe 1
+
+ enumClazz.methodsAccept(
+ MemberNameFilter(
+ "values*",
+ object : MemberVisitor {
+ override fun visitProgramMember(programClass: ProgramClass, programMember: ProgramMember) {
+ with(programClass and programMember) {
+ match {
+ invokevirtual("[I", "clone", "()Ljava/lang/Object;")
+ } shouldBe true
+ }
+ }
+ }
+ )
+ )
+}
diff --git a/docs/md/manual/releasenotes.md b/docs/md/manual/releasenotes.md
index ab775b99e..b7a02fa77 100644
--- a/docs/md/manual/releasenotes.md
+++ b/docs/md/manual/releasenotes.md
@@ -3,6 +3,7 @@
### Bugfixes
- Fix "NoClassDefFoundError: Failed resolution of: Lorg/apache/logging/log4j/LogManager" when using GSON optimization or `-addconfigurationdebugging`. (#326)
+- Fix "VerifyError" when optimizing Kotlin simple enums. (#349)
## Version 7.3.2