Skip to content

Commit

Permalink
Merge pull request #1195 from flapenna/fix-generic-base-class-spy-sta…
Browse files Browse the repository at this point in the history
…ckoverflow

Fix StackOverflowError calling method on spy of class with generic base class
  • Loading branch information
Raibaz committed Dec 29, 2023
2 parents 5fb2636 + 58cde89 commit a162b8b
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 1 deletion.
@@ -1,6 +1,8 @@
package io.mockk.proxy.jvm.advice

import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.util.Arrays

internal object SelfCallEliminator {
Expand All @@ -12,7 +14,15 @@ internal object SelfCallEliminator {
}

private fun checkOverride(method1: Method, method2: Method): Boolean {
return method1.name == method2.name && Arrays.equals(method1.parameterTypes, method2.parameterTypes)
val namesMatch = method1.name == method2.name

val parameterTypesMatch = method1.parameterTypes.contentEquals(method2.parameterTypes) ||
(method1.parameterTypes.size == method2.parameterTypes.size &&
method1.parameterTypes.zip(method2.parameterTypes).all { (type1, type2) ->
type1.isAssignableFrom(type2) || type2.isAssignableFrom(type1)
})

return namesMatch && parameterTypesMatch
}

inline fun <T> apply(self: Any, method: Method, block: () -> T): T {
Expand Down
@@ -0,0 +1,65 @@
package io.mockk.proxy.advice

import io.mockk.proxy.jvm.advice.SelfCallEliminator
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.lang.reflect.Method

class SelfCallEliminatorTest {

@Test
fun `isSelf returns true when ChildClass overrides BaseClass method with type parameter`() {
val childMethod = getMethod("method", ChildClass::class.java, String::class.java) // ChildClass: override fun method(t: String)
val baseMethod = getMethod("method", BaseClass::class.java, Any::class.java) // BaseClass: open fun method(t: T)

SelfCallEliminator.apply(ChildClass::class.java, childMethod) {
assertTrue(SelfCallEliminator.isSelf(ChildClass::class.java, baseMethod))
}

SelfCallEliminator.apply(BaseClass::class.java, baseMethod) {
assertTrue(SelfCallEliminator.isSelf(BaseClass::class.java, childMethod))
}
}

@Test
fun `isSelf returns false when methods are not self`() {
val childMethod = getMethod("method", ChildClass::class.java, Int::class.java) // ChildClass: fun method(t: Int)
val baseMethod = getMethod("method", BaseClass::class.java, Any::class.java) // BaseClass: open fun method(t: T)

SelfCallEliminator.apply(ChildClass::class.java, childMethod) {
assertFalse(SelfCallEliminator.isSelf(ChildClass::class.java, baseMethod))
}

val otherMethod = getMethod("someOtherMethod", ChildClass::class.java, String::class.java) // ChildClass: fun someOtherMethod(t: String)

SelfCallEliminator.apply(ChildClass::class.java, otherMethod) {
assertFalse(SelfCallEliminator.isSelf(ChildClass::class.java, baseMethod))
}
}

private fun getMethod(name: String, clazz: Class<*>, parameterType: Class<*>): Method {
return clazz.getDeclaredMethod(name, parameterType)
}

abstract class BaseClass<T> {
open fun method(t: T) {
println("BaseClass method with type parameter: $t")
}
}

class ChildClass : BaseClass<String>() {
override fun method(t: String) {
super.method(t)
println("ChildClass method with parameter String: $t")
}

fun method(t: Int) {
println("ChildClass method with parameter Int: $t")
}

fun someOtherMethod(t: String) {
println("ChildClass someOtherMethod with parameter String: $t")
}
}
}

0 comments on commit a162b8b

Please sign in to comment.