Skip to content

Commit

Permalink
feature: implement extensive inspections for `MethodHandles#tableSwit…
Browse files Browse the repository at this point in the history
…ch` (#91)
  • Loading branch information
SirYwell committed Jun 15, 2024
1 parent 484883a commit 297ef36
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 44 deletions.
5 changes: 5 additions & 0 deletions src/main/kotlin/de/sirywell/methodhandleplugin/TriState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ enum class TriState {
if (this != other) return UNKNOWN
return this
}

fun sharpenTowardsNo(other: TriState): TriState {
if (this == NO || other == NO) return NO
return this.join(other)
}
}

fun Boolean?.toTriState() = when (this) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -436,15 +436,39 @@ class MethodHandlesMerger(private val ssaAnalyzer: SsaAnalyzer) {
targetsExprs: List<PsiExpression>,
block: SsaConstruction.Block
): MethodHandleType {
if (targetsExprs.isEmpty()) {
emitProblem(fallbackExpr.parent, message("problem.merging.tableSwitch.noCases"))
}
val fallback = ssaAnalyzer.mhType(fallbackExpr, block) ?: bottomType
var error = checkFirstParameter(fallback.signature.parameterList, fallbackExpr)
val targets = targetsExprs.map { ssaAnalyzer.mhType(it, block) ?: bottomType }
if (fallback.signature is TopSignature) return topType
if (targets.any { it.signature is TopSignature }) return topType
for ((index, target) in targets.withIndex()) {
error = error or checkFirstParameter(target.signature.parameterList, targetsExprs[index])
}
val cases = targets.map { it.signature }
// TODO actually join all signatures
// TODO ensure first param is int
if (cases.any { it.join(fallback.signature) == TopSignature }) return topType
return MethodHandleType(fallback.signature)
var prev = fallback.signature
for ((index, case) in cases.withIndex()) {
val (signature, identical) = prev.joinIdentical(case)
if (identical == TriState.NO) {
emitProblem(targetsExprs[index], message("problem.merging.tableSwitch.notIdentical"))
error = true
}
prev = signature
}
if (error) {
return MethodHandleType(TopSignature)
}
return MethodHandleType(prev)
}

private fun checkFirstParameter(parameterList: ParameterList, context: PsiExpression): Boolean {
if (parameterList.compareSize(1) == PartialOrder.LT
|| parameterList[0].match(PsiTypes.intType()) == TriState.NO
) {
emitProblem(context, message("problem.merging.tableSwitch.leadingInt"))
return true
}
return false
}

fun tryFinally(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ sealed interface ParameterList {
fun parameterType(index: Int): Type
operator fun get(index: Int) = parameterType(index)

fun join(parameterList: ParameterList): ParameterList
fun join(parameterList: ParameterList) = joinIdentical(parameterList).first

Check warning on line 16 in src/main/kotlin/de/sirywell/methodhandleplugin/type/ParameterList.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused symbol

Function "join" is never used

fun joinIdentical(parameterList: ParameterList): Pair<ParameterList, TriState>

fun hasSize(size: Int): TriState {
return when (compareSize(size)) {
Expand Down Expand Up @@ -46,7 +48,7 @@ sealed interface ParameterList {

data object BotParameterList : ParameterList {
override fun parameterType(index: Int) = BotType
override fun join(parameterList: ParameterList) = parameterList
override fun joinIdentical(parameterList: ParameterList) = parameterList to TriState.UNKNOWN
override fun dropFirst(n: Int) = this
override fun removeAt(index: Int, n: Int) = this
override fun addAllAt(index: Int, parameterList: ParameterList): ParameterList {
Expand All @@ -71,7 +73,7 @@ data object BotParameterList : ParameterList {

data object TopParameterList : ParameterList {
override fun parameterType(index: Int) = TopType
override fun join(parameterList: ParameterList) = this
override fun joinIdentical(parameterList: ParameterList) = this to TriState.UNKNOWN
override fun dropFirst(n: Int) = this
override fun removeAt(index: Int, n: Int) = this
override fun addAllAt(index: Int, parameterList: ParameterList) = this
Expand All @@ -94,29 +96,32 @@ data class CompleteParameterList(val parameterTypes: List<Type>) : ParameterList
return parameterTypes.getOrElse(index) { TopType }
}

override fun join(parameterList: ParameterList): ParameterList {
override fun joinIdentical(parameterList: ParameterList): Pair<ParameterList, TriState> {
return when (parameterList) {
BotParameterList -> this
TopParameterList -> TopParameterList
BotParameterList -> this to TriState.UNKNOWN
TopParameterList -> TopParameterList to TriState.UNKNOWN
is CompleteParameterList -> {
if (size != parameterList.size) {
TopParameterList
TopParameterList to TriState.NO
} else {
CompleteParameterList(parameterTypes.zip(parameterList.parameterTypes).map { (a, b) -> a.join(b) })
val params = parameterTypes.zip(parameterList.parameterTypes).map { (a, b) -> a.joinIdentical(b) }
val identical = paramsAreIdentical(params)
CompleteParameterList(params.map { it.first }) to identical
}
}

is IncompleteParameterList -> {
if (size < parameterList.knownParameterTypes.lastKey()) {
// this list is definitely smaller than the other list, therefore incompatible
TopParameterList
TopParameterList to TriState.NO
} else {
// join the known types, keep the rest (others would be BotType anyway)
val params = parameterTypes.toMutableList()
val params = parameterTypes.map { it to TriState.UNKNOWN }.toMutableList()
parameterList.knownParameterTypes.forEach { (index, type) ->
params[index] = params[index].join(type)
params[index] = parameterTypes[index].joinIdentical(type)
}
CompleteParameterList(params)
val identical = paramsAreIdentical(params)
CompleteParameterList(params.map { it.first }) to identical
}
}
}
Expand Down Expand Up @@ -178,16 +183,23 @@ data class IncompleteParameterList(val knownParameterTypes: SortedMap<Int, Type>
return knownParameterTypes[index] ?: if (index < 0) TopType else BotType
}

override fun join(parameterList: ParameterList): ParameterList {
override fun joinIdentical(parameterList: ParameterList): Pair<ParameterList, TriState> {
return when (parameterList) {
BotParameterList -> this
TopParameterList -> TopParameterList
is CompleteParameterList -> parameterList.join(this) // do not reimplement code here for no reason
BotParameterList -> this to TriState.UNKNOWN
TopParameterList -> TopParameterList to TriState.UNKNOWN
is CompleteParameterList -> parameterList.joinIdentical(this) // do not reimplement code here for no reason
is IncompleteParameterList -> {
val new = (knownParameterTypes.entries + (parameterList.knownParameterTypes.entries))
.stream()
.collect(Collectors.toMap({ it.key }, { it.value }, Type::join))
IncompleteParameterList(new.toSortedMap())
.collect(Collectors.toMap({ it.key }, { it.value to TriState.UNKNOWN },
{ (type, _), (other, _) -> type.joinIdentical(other) })
)
val identical = if (paramsAreIdentical(new.values) == TriState.NO) {
TriState.NO
} else {
TriState.YES
}
IncompleteParameterList(new.mapValues { it.value.first }.toSortedMap()) to identical
}
}
}
Expand Down Expand Up @@ -258,5 +270,15 @@ private fun ParameterList.toMap(): SortedMap<Int, Type> {
}
}

private fun paramsAreIdentical(params: Iterable<Pair<Type, TriState>>) =
if (params.any { it.second == TriState.NO }) {
TriState.NO
} else if (params.all { it.second == TriState.YES }) {
TriState.YES
} else {
TriState.UNKNOWN
}


private fun <E> List<E>.toIndexedMap() = this.mapIndexed { index, e -> index to e }.toMap().toSortedMap()
private fun <K, V> emptySortedMap() = Collections.emptySortedMap<K, V>()
21 changes: 9 additions & 12 deletions src/main/kotlin/de/sirywell/methodhandleplugin/type/Signature.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import de.sirywell.methodhandleplugin.TriState

sealed interface Signature {

fun join(signature: Signature): Signature
fun join(signature: Signature) = joinIdentical(signature).first

fun joinIdentical(signature: Signature): Pair<Signature, TriState>

fun withReturnType(returnType: Type): Signature

Expand All @@ -23,7 +25,7 @@ sealed interface Signature {
}

data object BotSignature : Signature {
override fun join(signature: Signature) = signature
override fun joinIdentical(signature: Signature) = signature to TriState.UNKNOWN

override fun withReturnType(returnType: Type) = CompleteSignature(returnType, parameterList, TriState.NO)

Expand All @@ -42,7 +44,7 @@ data object BotSignature : Signature {
}

data object TopSignature : Signature {
override fun join(signature: Signature) = this
override fun joinIdentical(signature: Signature) = this to TriState.UNKNOWN

override fun withReturnType(returnType: Type) = this

Expand All @@ -65,15 +67,10 @@ data class CompleteSignature(
override val parameterList: ParameterList,
override val varargs: TriState
) : Signature {
override fun join(signature: Signature): Signature {
if (signature is TopSignature) return signature
if (signature is BotSignature) return this
val other = signature as CompleteSignature
return CompleteSignature(
returnType.join(signature.returnType),
parameterList.join(other.parameterList),
varargs.join(other.varargs)
)
override fun joinIdentical(signature: Signature): Pair<Signature, TriState> {
val (ret, rIdentical) = returnType.joinIdentical(signature.returnType)
val (params, pIdentical) = parameterList.joinIdentical(signature.parameterList)
return CompleteSignature(ret, params, TriState.NO) to rIdentical.sharpenTowardsNo(pIdentical)
}

override fun withReturnType(returnType: Type): Signature {
Expand Down
21 changes: 13 additions & 8 deletions src/main/kotlin/de/sirywell/methodhandleplugin/type/Type.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import de.sirywell.methodhandleplugin.toTriState

sealed interface Type {

fun join(other: Type): Type
fun join(other: Type) = joinIdentical(other).first

fun joinIdentical(other: Type): Pair<Type, TriState>

fun erase(manager: PsiManager, scope: GlobalSearchScope): Type

Expand All @@ -23,7 +25,7 @@ sealed interface Type {
}

data object BotType : Type {
override fun join(other: Type) = other
override fun joinIdentical(other: Type) = other to TriState.UNKNOWN
override fun erase(manager: PsiManager, scope: GlobalSearchScope) = this
override fun match(psiType: PsiType) = TriState.UNKNOWN

Expand All @@ -33,7 +35,7 @@ data object BotType : Type {
}

data object TopType : Type {
override fun join(other: Type) = this
override fun joinIdentical(other: Type) = this to TriState.UNKNOWN
override fun erase(manager: PsiManager, scope: GlobalSearchScope) = this
override fun match(psiType: PsiType) = TriState.UNKNOWN

Expand All @@ -52,12 +54,15 @@ data class ExactType(val psiType: PsiType) : Type {
val intType = ExactType(PsiTypes.intType())
}

override fun join(other: Type): Type {
override fun joinIdentical(other: Type): Pair<Type, TriState> {
return when (other) {
is BotType -> this
is TopType -> other
is ExactType -> if (other.psiType == psiType) this else TopType

is ExactType -> if (other.psiType == psiType) {
this to TriState.YES
} else {
TopType to TriState.NO
}
is BotType -> this to TriState.UNKNOWN
is TopType -> other to TriState.UNKNOWN
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/messages/MethodHandleMessages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,7 @@ problem.merging.general.effectivelyIdenticalParametersExpected=Method parameters
problem.merging.permute.invalidReorderIndex=Reorder index value {2} is out of bounds [{0}, {1}).
problem.merging.permute.invalidReorderIndexType=Reorder index value points to type {0} but expects type {1}.
problem.merging.permute.reorderLengthMismatch=Reorder array length ({0}) does not match parameter count of new type ({1}).
problem.merging.tableSwitch.noCases='tableSwitch' requires at least one case, but none were given.
problem.merging.tableSwitch.leadingInt=The passed MethodHandle requires a leading parameter of type 'int'.
problem.merging.tableSwitch.notIdentical=The passed MethodHandle type is not compatible with the previous handle(s).
problem.invocation.returnType.mustBeVoid=MethodHandle returns void but is called in a non-void context.
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ class MethodHandleInspectionsTest : LightJavaCodeInsightFixtureTestCase() {

fun testMethodHandlesIdentity() = doTypeCheckingTest()

fun testMethodHandlesTableSwitch() = doTypeCheckingTest()

fun testMethodHandlesThrowException() = doTypeCheckingTest()

fun testMethodHandlesZero() = doInspectionTest()
Expand Down
28 changes: 28 additions & 0 deletions src/test/testData/MethodHandlesTableSwitch.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import java.lang.invoke.MethodHandle;
import static java.lang.invoke.MethodHandles.*;
import static java.lang.invoke.MethodType.*;

class MethodHandlesTableSwitch {
void switching() {
// missing cases
<info descr="(int)int">MethodHandle ts0 = <info descr="(int)int">tableSwitch(<info descr="(int)int">identity(int.class)</info>)</info>;</info>
// that works
<info descr="(int)int">MethodHandle ts1 = <info descr="(int)int">tableSwitch(<info descr="(int)int">identity(int.class)</info>, <info descr="(int)int">identity(int.class)</info>)</info>;</info>
// no leading int param
<info descr="TopSignature">MethodHandle ts2 = <info descr="TopSignature">tableSwitch(<info descr="(String)String">identity(String.class)</info>, <info descr="(String)String">identity(String.class)</info>)</info>;</info>
// parameter list differs in length
<info descr="TopSignature">MethodHandle ts3 = <info descr="TopSignature">tableSwitch(<info descr="(int)void">empty(<info descr="(int)void">methodType(void.class, int.class)</info>)</info>, <info descr="(int,String)void">empty(<info descr="(int,String)void">methodType(void.class, int.class, String.class)</info>)</info>)</info>;</info>
// parameter list is empty
<info descr="TopSignature">MethodHandle ts4 = <info descr="TopSignature">tableSwitch(<info descr="()void">empty(<info descr="()void">methodType(void.class)</info>)</info>, <info descr="()void">empty(<info descr="()void">methodType(void.class)</info>)</info>)</info>;</info>
// parameter list differs in types
<info descr="TopSignature">MethodHandle ts5 = <info descr="TopSignature">tableSwitch(<info descr="(int,CharSequence)void">empty(<info descr="(int,CharSequence)void">methodType(void.class, int.class, CharSequence.class)</info>)</info>, <info descr="(int,String)void">empty(<info descr="(int,String)void">methodType(void.class, int.class, String.class)</info>)</info>)</info>;</info>
// return types differ
<info descr="TopSignature">MethodHandle ts6 = <info descr="TopSignature">tableSwitch(<info descr="(int)String">empty(<info descr="(int)String">methodType(String.class, int.class)</info>)</info>, <info descr="(int)CharSequence">empty(<info descr="(int)CharSequence">methodType(CharSequence.class, int.class)</info>)</info>)</info>;</info>
// that works but is more complex
<info descr="(int,String)int">MethodHandle ts7 = <info descr="(int,String)int">tableSwitch(
<info descr="(int,String)int">empty(<info descr="(int,String)int">methodType(int.class, int.class, String.class)</info>)</info>,
<info descr="(int,String)int">empty(<info descr="(int,String)int">methodType(int.class, int.class, String.class)</info>)</info>,
<info descr="(int,String)int">empty(<info descr="(int,String)int">methodType(int.class, int.class, String.class)</info>)</info>
)</info>;</info>
}
}

0 comments on commit 297ef36

Please sign in to comment.