From 7e186530a174459e5e18b42f49f53c46fa7f4d30 Mon Sep 17 00:00:00 2001 From: Oleg Golberg Date: Sun, 24 Dec 2023 09:51:13 -0500 Subject: [PATCH] Exclude polymorphic methods See https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/MethodHandle.html. Fixes /~https://github.com/open-toast/expediter/issues/37. --- .../com/toasttab/expediter/Expediter.kt | 9 +++- .../expediter/types/PolymorphicMethods.kt | 51 +++++++++++++++++++ .../expediter/types/PolymorphicMethodsTest.kt | 27 ++++++++++ .../expediter/test/caller/Caller.java | 21 +++++++- 4 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 core/src/main/kotlin/com/toasttab/expediter/types/PolymorphicMethods.kt create mode 100644 core/src/test/kotlin/com/toasttab/expediter/types/PolymorphicMethodsTest.kt diff --git a/core/src/main/kotlin/com/toasttab/expediter/Expediter.kt b/core/src/main/kotlin/com/toasttab/expediter/Expediter.kt index c989d4b..99bb1f0 100644 --- a/core/src/main/kotlin/com/toasttab/expediter/Expediter.kt +++ b/core/src/main/kotlin/com/toasttab/expediter/Expediter.kt @@ -28,6 +28,7 @@ import com.toasttab.expediter.types.MemberType import com.toasttab.expediter.types.MethodAccessType import com.toasttab.expediter.types.OptionalResolvedTypeHierarchy import com.toasttab.expediter.types.PlatformType +import com.toasttab.expediter.types.PolymorphicMethods import com.toasttab.expediter.types.ResolvedTypeHierarchy import com.toasttab.expediter.types.Type import com.toasttab.expediter.types.members @@ -151,7 +152,13 @@ class Expediter( val member = chain.resolveMember(access) if (member == null) { - Issue.MissingMember(type.name, access) + if (access.accessType == MethodAccessType.VIRTUAL && PolymorphicMethods.contains(access.targetType, access.ref.name)) { + // if invoking a polymorphic method, assume the invocation is valid + // because it's validated by the java compiler + null + } else { + Issue.MissingMember(type.name, access) + } } else { val resolvedAccess = access.withDeclaringType(member.declaringType.name) diff --git a/core/src/main/kotlin/com/toasttab/expediter/types/PolymorphicMethods.kt b/core/src/main/kotlin/com/toasttab/expediter/types/PolymorphicMethods.kt new file mode 100644 index 0000000..5864bca --- /dev/null +++ b/core/src/main/kotlin/com/toasttab/expediter/types/PolymorphicMethods.kt @@ -0,0 +1,51 @@ +package com.toasttab.expediter.types + +/** + * This object enumerates signature-polymorphic methods, see the javadoc for MethodHandle. + */ +object PolymorphicMethods { + private val methods = mapOf( + "java/lang/invoke/MethodHandle" to setOf( + "invoke", + "invokeExact" + ), + + "java/lang/invoke/VarHandle" to setOf( + "get", + "set", + "getVolatile", + "setVolatile", + "getOpaque", + "setOpaque", + "getAcquire", + "setRelease", + "compareAndSet", + "compareAndExchange", + "compareAndExchangeAcquire", + "compareAndExchangeRelease", + "weakCompareAndSetPlain", + "weakCompareAndSet", + "weakCompareAndSetAcquire", + "weakCompareAndSetRelease", + "getAndSet", + "getAndSetAcquire", + "getAndSetRelease", + "getAndAdd", + "getAndAddAcquire", + "getAndAddRelease", + "getAndBitwiseOr", + "getAndBitwiseOrAcquire", + "getAndBitwiseOrRelease", + "getAndBitwiseAnd", + "getAndBitwiseAndAcquire", + "getAndBitwiseAndRelease", + "getAndBitwiseXor", + "getAndBitwiseXorAcquire", + "getAndBitwiseXorRelease" + ) + ) + + fun contains(className: String, methodName: String): Boolean { + return methods[className]?.contains(methodName) ?: false + } +} diff --git a/core/src/test/kotlin/com/toasttab/expediter/types/PolymorphicMethodsTest.kt b/core/src/test/kotlin/com/toasttab/expediter/types/PolymorphicMethodsTest.kt new file mode 100644 index 0000000..9f9c012 --- /dev/null +++ b/core/src/test/kotlin/com/toasttab/expediter/types/PolymorphicMethodsTest.kt @@ -0,0 +1,27 @@ +package com.toasttab.expediter.types + +import org.junit.jupiter.api.Test +import strikt.api.expectThat +import strikt.assertions.isTrue +import java.lang.invoke.MethodHandle +import java.lang.invoke.VarHandle +import java.lang.reflect.Modifier + +class PolymorphicMethodsTest { + @Test + fun `MethodHandle and VarHandle methods annotated with PolymorphicSignature`() { + for (cls in listOf(MethodHandle::class.java, VarHandle::class.java)) { + val name = cls.name.replace('.', '/') + + for (method in polymorphicMethodsOf(cls)) { + expectThat(PolymorphicMethods.contains(name, method.name)).isTrue() + } + } + } + + private fun polymorphicMethodsOf(cls: Class<*>) = cls.methods.filter { m -> + Modifier.isPublic(m.modifiers) && m.annotations.any { + it.annotationClass.java.name == "java.lang.invoke.MethodHandle\$PolymorphicSignature" + } + } +} diff --git a/tests/src/main/java/com/toasttab/expediter/test/caller/Caller.java b/tests/src/main/java/com/toasttab/expediter/test/caller/Caller.java index 3e17876..38ce33c 100644 --- a/tests/src/main/java/com/toasttab/expediter/test/caller/Caller.java +++ b/tests/src/main/java/com/toasttab/expediter/test/caller/Caller.java @@ -24,7 +24,10 @@ import com.toasttab.expediter.test.ParamParam; import com.toasttab.expediter.test.Var; -import java.util.stream.Stream; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; public final class Caller extends Base { Foo foo; @@ -105,4 +108,20 @@ void missingTypeMethodArg() { boolean missingTypeInstanceof(Object o) { return o instanceof ParamParam[]; } + + void methodHandle() throws Throwable { + MethodHandle h1 = MethodHandles.publicLookup().findVirtual(String.class, "substring", MethodType.methodType(String.class, int.class, int.class)); + + h1.invokeExact("xxx", 1, 2); + + MethodHandle h2 = MethodHandles.publicLookup().findStatic(String.class, "valueOf", MethodType.methodType(String.class, long.class)); + + h2.invokeExact(1L); + } + + void varHandle() throws Throwable { + VarHandle vh = MethodHandles.publicLookup().findVarHandle(int[].class, "length", int.class); + + vh.get(new int[0]); + } }