diff --git a/docs/md/releasenotes.md b/docs/md/releasenotes.md index 815f2c444..043161aaa 100644 --- a/docs/md/releasenotes.md +++ b/docs/md/releasenotes.md @@ -9,6 +9,7 @@ - Fix potential `NullPointerException` when initializing Kotlin callable references. (`T5899`) - Prevent requiring `--enable-preview` on a JVM for Java 16 class files (write class file version `60.0` instead of `60.65535`). +- Fix potential `NullPointerException` when visiting referenced methods of Kotlin functions. ## Version 8.0.0 diff --git a/src/main/java/proguard/classfile/kotlin/KotlinFunctionMetadata.java b/src/main/java/proguard/classfile/kotlin/KotlinFunctionMetadata.java index 01d3b4a60..0219a0676 100644 --- a/src/main/java/proguard/classfile/kotlin/KotlinFunctionMetadata.java +++ b/src/main/java/proguard/classfile/kotlin/KotlinFunctionMetadata.java @@ -1,7 +1,7 @@ /* * ProGuardCORE -- library to process Java bytecode. * - * Copyright (c) 2002-2020 Guardsquare NV + * Copyright (c) 2002-2021 Guardsquare NV * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -153,9 +153,13 @@ public void versionRequirementAccept(Clazz clazz, } + // TODO: remove unused clazz parameter public void referencedMethodAccept(Clazz clazz, MemberVisitor methodVisitor) { - referencedMethod.accept(clazz, methodVisitor); + if (referencedMethod != null) + { + referencedMethod.accept(referencedMethodClass, methodVisitor); + } } diff --git a/src/main/java/proguard/classfile/kotlin/visitor/KotlinFunctionToMethodVisitor.java b/src/main/java/proguard/classfile/kotlin/visitor/KotlinFunctionToMethodVisitor.java index f5c64d67a..a941f61b4 100644 --- a/src/main/java/proguard/classfile/kotlin/visitor/KotlinFunctionToMethodVisitor.java +++ b/src/main/java/proguard/classfile/kotlin/visitor/KotlinFunctionToMethodVisitor.java @@ -1,7 +1,7 @@ /* * ProGuardCORE -- library to process Java bytecode. * - * Copyright (c) 2002-2020 Guardsquare NV + * Copyright (c) 2002-2021 Guardsquare NV * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,7 +39,6 @@ public void visitAnyFunction(Clazz clazz, KotlinMetadata kotlinMetadata, KotlinFunctionMetadata kotlinFunctionMetadata) { - kotlinFunctionMetadata.referencedMethod.accept(kotlinFunctionMetadata.referencedMethodClass, - memberVisitor); + kotlinFunctionMetadata.referencedMethodAccept(clazz, memberVisitor); } } diff --git a/src/test/kotlin/proguard/classfile/kotlin/KotlinFunctionMetadataTest.kt b/src/test/kotlin/proguard/classfile/kotlin/KotlinFunctionMetadataTest.kt new file mode 100644 index 000000000..51aaf0818 --- /dev/null +++ b/src/test/kotlin/proguard/classfile/kotlin/KotlinFunctionMetadataTest.kt @@ -0,0 +1,116 @@ +/* + * ProGuardCORE -- library to process Java bytecode. + * + * Copyright (c) 2002-2021 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 + +import io.kotest.assertions.throwables.shouldNotThrowAny +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe +import io.mockk.spyk +import io.mockk.verify +import proguard.classfile.Clazz +import proguard.classfile.Member +import proguard.classfile.ProgramClass +import proguard.classfile.kotlin.visitor.AllFunctionVisitor +import proguard.classfile.kotlin.visitor.KotlinFunctionVisitor +import proguard.classfile.visitor.MemberVisitor +import testutils.ClassPoolBuilder +import testutils.KotlinSource + +class KotlinFunctionMetadataTest : FreeSpec({ + + "Given a Kotlin function" - { + val (programClassPool, _) = ClassPoolBuilder.fromSource( + KotlinSource("Test.kt", """fun foo() = "bar"""".trimIndent()) + ) + + val memberVisitor = spyk(object : MemberVisitor { + override fun visitAnyMember(clazz: Clazz, member: Member) { } + }) + + val programClass = programClassPool.getClass("TestKt") as ProgramClass + + programClass.kotlinMetadataAccept( + AllFunctionVisitor(object : KotlinFunctionVisitor { + override fun visitAnyFunction(clazz: Clazz, metadata: KotlinMetadata, func: KotlinFunctionMetadata) { + func.referencedMethodAccept(clazz, memberVisitor) + } + }) + ) + + "Then a member visitor should visit the referenced method" { + verify(exactly = 1) { + memberVisitor.visitProgramMember( + programClass, + withArg { + it.getName(programClass) shouldBe "foo" + it.getDescriptor(programClass) shouldBe "()Ljava/lang/String;" + } + ) + } + } + } + + "Given a Kotlin function with an uninitialized referenced method" - { + val (programClassPool, _) = ClassPoolBuilder.fromSource( + KotlinSource("Test.kt", """fun foo() = "bar"""".trimIndent()) + ) + + val programClass = programClassPool.getClass("TestKt") as ProgramClass + + programClass.kotlinMetadataAccept( + AllFunctionVisitor(object : KotlinFunctionVisitor { + override fun visitAnyFunction(clazz: Clazz, metadata: KotlinMetadata, func: KotlinFunctionMetadata) { + func.referencedMethod = null + } + }) + ) + + "Then referencedMethodAccept should not throw an exception" { + val memberVisitor = spyk(object : MemberVisitor { + override fun visitAnyMember(clazz: Clazz, member: Member) { } + }) + + shouldNotThrowAny { + programClass.kotlinMetadataAccept( + AllFunctionVisitor(object : KotlinFunctionVisitor { + override fun visitAnyFunction(clazz: Clazz, metadata: KotlinMetadata, func: KotlinFunctionMetadata) { + func.referencedMethodAccept(clazz, memberVisitor) + } + }) + ) + } + } + + "Then a member visitor should not visit the referenced method" { + val memberVisitor = spyk(object : MemberVisitor { + override fun visitAnyMember(clazz: Clazz, member: Member) { } + }) + + verify(exactly = 0) { + memberVisitor.visitProgramMember( + programClass, + withArg { + it.getName(programClass) shouldBe "foo" + it.getDescriptor(programClass) shouldBe "()Ljava/lang/String;" + } + ) + } + } + } +}) diff --git a/src/test/kotlin/proguard/classfile/kotlin/visitor/KotlinFunctionToMethodVisitorTest.kt b/src/test/kotlin/proguard/classfile/kotlin/visitor/KotlinFunctionToMethodVisitorTest.kt new file mode 100644 index 000000000..5d025b2b3 --- /dev/null +++ b/src/test/kotlin/proguard/classfile/kotlin/visitor/KotlinFunctionToMethodVisitorTest.kt @@ -0,0 +1,106 @@ +/* + * ProGuardCORE -- library to process Java bytecode. + * + * Copyright (c) 2002-2021 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.assertions.throwables.shouldNotThrowAny +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe +import io.mockk.spyk +import io.mockk.verify +import proguard.classfile.Clazz +import proguard.classfile.Member +import proguard.classfile.ProgramClass +import proguard.classfile.kotlin.KotlinFunctionMetadata +import proguard.classfile.kotlin.KotlinMetadata +import proguard.classfile.visitor.MemberVisitor +import testutils.ClassPoolBuilder +import testutils.KotlinSource + +class KotlinFunctionToMethodVisitorTest : FreeSpec({ + + "Given a Kotlin function" - { + val (programClassPool, _) = ClassPoolBuilder.fromSource( + KotlinSource("Test.kt", """fun foo() = "bar"""".trimIndent()) + ) + + val memberVisitor = spyk(object : MemberVisitor { + override fun visitAnyMember(clazz: Clazz, member: Member) { } + }) + + val programClass = programClassPool.getClass("TestKt") as ProgramClass + + programClass.kotlinMetadataAccept(AllFunctionVisitor(KotlinFunctionToMethodVisitor(memberVisitor))) + + "Then a member visitor should visit the referenced method" { + verify(exactly = 1) { + memberVisitor.visitProgramMember( + programClass, + withArg { + it.getName(programClass) shouldBe "foo" + it.getDescriptor(programClass) shouldBe "()Ljava/lang/String;" + } + ) + } + } + } + + "Given a Kotlin function with an uninitialized referenced method" - { + val (programClassPool, _) = ClassPoolBuilder.fromSource( + KotlinSource("Test.kt", """fun foo() = "bar"""".trimIndent()) + ) + + val programClass = programClassPool.getClass("TestKt") as ProgramClass + + programClass.kotlinMetadataAccept( + AllFunctionVisitor(object : KotlinFunctionVisitor { + override fun visitAnyFunction(clazz: Clazz, metadata: KotlinMetadata, func: KotlinFunctionMetadata) { + func.referencedMethod = null + } + }) + ) + + "Then KotlinFunctionToMethodVisitor should not throw an exception" { + val memberVisitor = spyk(object : MemberVisitor { + override fun visitAnyMember(clazz: Clazz, member: Member) { } + }) + + shouldNotThrowAny { + programClass.kotlinMetadataAccept( + AllFunctionVisitor(KotlinFunctionToMethodVisitor(memberVisitor)) + ) + } + } + + "Then a member visitor should not visit the referenced method" { + val memberVisitor = spyk(object : MemberVisitor { + override fun visitAnyMember(clazz: Clazz, member: Member) { } + }) + + verify(exactly = 0) { + memberVisitor.visitProgramMember( + programClass, + withArg { + it.getName(programClass) shouldBe "foo" + it.getDescriptor(programClass) shouldBe "()Ljava/lang/String;" + } + ) + } + } + } +})