Skip to content

Commit

Permalink
Fix Kotlin function referenced method visitor accept
Browse files Browse the repository at this point in the history
* Add null check
* Use referencedMethodClass instead of clazz in KotlinFunctionMetadata
(since it might be different from clazz)
  • Loading branch information
mrjameshamilton committed Aug 12, 2021
1 parent 58e4fe0 commit ef282b2
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 5 deletions.
1 change: 1 addition & 0 deletions docs/md/releasenotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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);
}
}


Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -39,7 +39,6 @@ public void visitAnyFunction(Clazz clazz,
KotlinMetadata kotlinMetadata,
KotlinFunctionMetadata kotlinFunctionMetadata)
{
kotlinFunctionMetadata.referencedMethod.accept(kotlinFunctionMetadata.referencedMethodClass,
memberVisitor);
kotlinFunctionMetadata.referencedMethodAccept(clazz, memberVisitor);
}
}
Original file line number Diff line number Diff line change
@@ -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<MemberVisitor>(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<MemberVisitor>(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<MemberVisitor>(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;"
}
)
}
}
}
})
Original file line number Diff line number Diff line change
@@ -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<MemberVisitor>(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<MemberVisitor>(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<MemberVisitor>(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;"
}
)
}
}
}
})

0 comments on commit ef282b2

Please sign in to comment.