From cdf64c27ca3cc8317193131ed76df59296789c62 Mon Sep 17 00:00:00 2001 From: James Hamilton Date: Thu, 6 Jan 2022 16:19:42 +0100 Subject: [PATCH] Add support to `MemberDescriptorReferencedClassVisitor` for visiting referenced Kotlin inline class parameters --- docs/md/releasenotes.md | 1 + ...emberDescriptorReferencedClassVisitor.java | 73 +++++++++++++++++- ...berDescriptorReferencedClassVisitorTest.kt | 74 +++++++++++++++++++ 3 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 src/test/kotlin/proguard/classfile/kotlin/visitor/MemberDescriptorReferencedClassVisitorTest.kt diff --git a/docs/md/releasenotes.md b/docs/md/releasenotes.md index e883d943b..41e9fe7d5 100644 --- a/docs/md/releasenotes.md +++ b/docs/md/releasenotes.md @@ -20,6 +20,7 @@ - Convert to/from Kotlin unsigned integers in Kotlin annotation unsigned type arguments. (`T5405`) - Initialize array dimension in Kotlin annotation `ClassValue` type arguments. (`T5406`) - Add support for Kotlin inline class underlying type to Kotlin metadata model. (`T4774`) +- Add support to `MemberDescriptorReferencedClassVisitor` for visiting referenced Kotlin inline class parameters. (`T13653`) ## Version 8.0.4 diff --git a/src/main/java/proguard/classfile/visitor/MemberDescriptorReferencedClassVisitor.java b/src/main/java/proguard/classfile/visitor/MemberDescriptorReferencedClassVisitor.java index 0cd7b2d33..c6f292d2b 100644 --- a/src/main/java/proguard/classfile/visitor/MemberDescriptorReferencedClassVisitor.java +++ b/src/main/java/proguard/classfile/visitor/MemberDescriptorReferencedClassVisitor.java @@ -1,7 +1,7 @@ /* * ProGuardCORE -- library to process Java bytecode. * - * Copyright (c) 2002-2020 Guardsquare NV + * Copyright (c) 2002-2022 Guardsquare NV * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,22 +18,38 @@ package proguard.classfile.visitor; import proguard.classfile.*; +import proguard.classfile.kotlin.KotlinFunctionMetadata; +import proguard.classfile.kotlin.KotlinMetadata; +import proguard.classfile.kotlin.KotlinTypeMetadata; +import proguard.classfile.kotlin.KotlinValueParameterMetadata; +import proguard.classfile.kotlin.visitor.*; +import proguard.classfile.kotlin.visitor.filter.KotlinClassKindFilter; /** * This {@link MemberVisitor} lets a given {@link ClassVisitor} visit all the classes * referenced by the descriptors of the class members that it visits. * + * It also takes into account functions with Kotlin inline class parameters, if + * includeKotlinMetadata = true: in the case of inline classes, in the underlying JVM + * method the actual class will not be referenced since the Kotlin compiler inlines uses. + * * @author Eric Lafortune */ public class MemberDescriptorReferencedClassVisitor implements MemberVisitor { private final ClassVisitor classVisitor; - + private final KotlinFunctionDescriptorReferenceVisitor kotlinFunRefVisitor; public MemberDescriptorReferencedClassVisitor(ClassVisitor classVisitor) { - this.classVisitor = classVisitor; + this(false, classVisitor); + } + + public MemberDescriptorReferencedClassVisitor(boolean includeKotlinMetadata, ClassVisitor classVisitor) + { + this.classVisitor = classVisitor; + this.kotlinFunRefVisitor = includeKotlinMetadata ? new KotlinFunctionDescriptorReferenceVisitor() : null; } @@ -43,12 +59,61 @@ public void visitProgramMember(ProgramClass programClass, ProgramMember programM { // Let the visitor visit the classes referenced in the descriptor string. programMember.referencedClassesAccept(classVisitor); + if (this.kotlinFunRefVisitor != null) + { + programMember.accept(programClass, new MethodToKotlinFunctionVisitor(this.kotlinFunRefVisitor)); + } } - public void visitLibraryMember(LibraryClass programClass, LibraryMember libraryMember) + public void visitLibraryMember(LibraryClass libraryClass, LibraryMember libraryMember) { // Let the visitor visit the classes referenced in the descriptor string. libraryMember.referencedClassesAccept(classVisitor); + if (this.kotlinFunRefVisitor != null) + { + libraryMember.accept(libraryClass, new MethodToKotlinFunctionVisitor(this.kotlinFunRefVisitor)); + } + } + + + private class KotlinFunctionDescriptorReferenceVisitor + implements KotlinFunctionVisitor, + KotlinValueParameterVisitor, + KotlinTypeVisitor + { + + @Override + public void visitAnyFunction(Clazz clazz, + KotlinMetadata kotlinMetadata, + KotlinFunctionMetadata kotlinFunctionMetadata) + { + kotlinFunctionMetadata.valueParametersAccept(clazz, kotlinMetadata, this); + } + + @Override + public void visitAnyValueParameter(Clazz clazz, KotlinValueParameterMetadata kotlinValueParameterMetadata) { } + + @Override + public void visitFunctionValParameter(Clazz clazz, + KotlinMetadata kotlinMetadata, + KotlinFunctionMetadata kotlinFunctionMetadata, + KotlinValueParameterMetadata kotlinValueParameterMetadata) + { + kotlinValueParameterMetadata.typeAccept(clazz, kotlinMetadata, kotlinFunctionMetadata, this); + } + + @Override + public void visitAnyType(Clazz clazz, KotlinTypeMetadata kotlinTypeMetadata) + { + kotlinTypeMetadata.referencedClassAccept( + new ReferencedKotlinMetadataVisitor( + new KotlinClassKindFilter( + metadata -> metadata.flags.isValue, + new KotlinMetadataToClazzVisitor(classVisitor) + ) + ) + ); + } } } diff --git a/src/test/kotlin/proguard/classfile/kotlin/visitor/MemberDescriptorReferencedClassVisitorTest.kt b/src/test/kotlin/proguard/classfile/kotlin/visitor/MemberDescriptorReferencedClassVisitorTest.kt new file mode 100644 index 000000000..ac4db8414 --- /dev/null +++ b/src/test/kotlin/proguard/classfile/kotlin/visitor/MemberDescriptorReferencedClassVisitorTest.kt @@ -0,0 +1,74 @@ +/* + * ProGuardCORE -- library to process Java bytecode. + * + * Copyright (c) 2002-2022 Guardsquare NV + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package proguard.classfile.kotlin.visitor + +import io.kotest.core.spec.style.FreeSpec +import io.mockk.spyk +import io.mockk.verify +import proguard.classfile.visitor.AllMemberVisitor +import proguard.classfile.visitor.ClassVisitor +import proguard.classfile.visitor.MemberDescriptorReferencedClassVisitor +import testutils.ClassPoolBuilder +import testutils.KotlinSource + +class MemberDescriptorReferencedClassVisitorTest : FreeSpec({ + "Given an inline class" - { + val (programClassPool, _) = ClassPoolBuilder.fromSource( + KotlinSource( + "Test.kt", + """ + @JvmInline + value class Password(val s: String) + + fun login(password: Password) { + println(password); + } + """.trimIndent() + ) + ) + + "Then visiting the referenced descriptor methods of login with includeKotlinMetadata true" - { + val visitor = spyk() + programClassPool.classesAccept( + "TestKt", + AllMemberVisitor(MemberDescriptorReferencedClassVisitor(true, visitor)) + ) + + "Should visit the Password class" { + verify(exactly = 1) { + visitor.visitAnyClass(programClassPool.getClass("Password")) + } + } + } + + "Then visiting the referenced descriptor methods of login with includeKotlinMetadata false" - { + val visitor = spyk() + programClassPool.classesAccept( + "TestKt", + AllMemberVisitor(MemberDescriptorReferencedClassVisitor(false, visitor)) + ) + + "Should not visit the Password class" { + verify(exactly = 0) { + visitor.visitAnyClass(programClassPool.getClass("Password")) + } + } + } + } +})