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