User: joe
Date: 23 Sep 23 17:55
Revision: 6ab3c78b668f2b725b48cf68ed49ab4be1db13ce
Summary:
Add inspection for incorrect @WrapOperation Operation.call arguments
TeamCity URL: https://ci.mcdev.io/viewModification.html?tab=vcsModificationFiles&modId=8750&personal=false
Index: src/main/kotlin/platform/mixin/inspection/mixinextras/WrongOperationParametersInspection.kt
===================================================================
--- src/main/kotlin/platform/mixin/inspection/mixinextras/WrongOperationParametersInspection.kt (revision 6ab3c78b668f2b725b48cf68ed49ab4be1db13ce)
+++ src/main/kotlin/platform/mixin/inspection/mixinextras/WrongOperationParametersInspection.kt (revision 6ab3c78b668f2b725b48cf68ed49ab4be1db13ce)
@@ -0,0 +1,89 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2023 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.demonwav.mcdev.platform.mixin.inspection.mixinextras
+
+import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection
+import com.demonwav.mcdev.platform.mixin.util.MixinConstants
+import com.demonwav.mcdev.platform.mixin.util.isAssignable
+import com.demonwav.mcdev.util.McdevDfaUtil
+import com.intellij.codeInspection.ProblemsHolder
+import com.intellij.psi.JavaElementVisitor
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiClassType
+import com.intellij.psi.PsiField
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiMethodCallExpression
+import com.intellij.psi.util.PsiTreeUtil
+
+class WrongOperationParametersInspection : MixinInspection() {
+ override fun getStaticDescription() = "Operation.call called with the wrong parameter types"
+
+ override fun buildVisitor(holder: ProblemsHolder) = object : JavaElementVisitor() {
+ override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
+ if (expression.methodExpression.referenceName != "call") {
+ return
+ }
+ if (expression.resolveMethod()?.containingClass?.qualifiedName != MixinConstants.MixinExtras.OPERATION) {
+ return
+ }
+
+ val containingMethod = PsiTreeUtil.getParentOfType(
+ expression,
+ PsiMethod::class.java,
+ true,
+ PsiClass::class.java,
+ PsiField::class.java
+ ) ?: return
+
+ if (!containingMethod.hasAnnotation(MixinConstants.MixinExtras.WRAP_OPERATION)) {
+ return
+ }
+
+ val (operationIndex, operationParam) = containingMethod.parameterList.parameters.asSequence()
+ .withIndex()
+ .firstOrNull { (_, param) ->
+ (param.type as? PsiClassType)?.resolve()?.qualifiedName == MixinConstants.MixinExtras.OPERATION
+ } ?: return
+ val expectedParamTypes = containingMethod.parameterList.parameters.asSequence()
+ .take(operationIndex)
+ .map { it.type }
+ .toList()
+
+ val qualifier = expression.methodExpression.qualifierExpression ?: return
+ if (!McdevDfaUtil.isSometimesEqualToParameter(qualifier, operationParam)) {
+ return
+ }
+
+ if (expression.argumentList.expressionCount == expectedParamTypes.size) {
+ val allValid = expression.argumentList.expressions.zip(expectedParamTypes).all { (expr, expectedType) ->
+ val exprType = McdevDfaUtil.getDataflowType(expr) ?: return@all true
+ isAssignable(expectedType, exprType, false)
+ }
+ if (allValid) {
+ return
+ }
+ }
+
+ val problemElement = expression.argumentList
+ holder.registerProblem(problemElement, "Operation.call called with the wrong parameter types")
+ }
+ }
+}
Index: src/main/kotlin/platform/mixin/inspection/suppress/MixinClassCastInspectionSuppressor.kt
===================================================================
--- src/main/kotlin/platform/mixin/inspection/suppress/MixinClassCastInspectionSuppressor.kt (revision 449a4b79ff378b209b2be82c6dc8e53d8a7f2c8a)
+++ src/main/kotlin/platform/mixin/inspection/suppress/MixinClassCastInspectionSuppressor.kt (revision 6ab3c78b668f2b725b48cf68ed49ab4be1db13ce)
@@ -22,12 +22,11 @@
import com.demonwav.mcdev.platform.mixin.action.FindMixinsAction
import com.demonwav.mcdev.platform.mixin.util.isAssignable
+import com.demonwav.mcdev.util.McdevDfaUtil
import com.intellij.codeInspection.InspectionSuppressor
import com.intellij.codeInspection.SuppressQuickFix
-import com.intellij.codeInspection.dataFlow.CommonDataflow
import com.intellij.codeInspection.dataFlow.TypeConstraint
import com.intellij.codeInspection.dataFlow.TypeConstraints
-import com.intellij.codeInspection.dataFlow.types.DfReferenceType
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.JavaTokenType
@@ -35,7 +34,6 @@
import com.intellij.psi.PsiBinaryExpression
import com.intellij.psi.PsiClassType
import com.intellij.psi.PsiElement
-import com.intellij.psi.PsiExpression
import com.intellij.psi.PsiInstanceOfExpression
import com.intellij.psi.PsiType
import com.intellij.psi.PsiTypeCastExpression
@@ -62,7 +60,7 @@
while (operand is PsiTypeCastExpression) {
operand = PsiUtil.skipParenthesizedExprDown(operand.operand) ?: return false
}
- val realType = getRealType(operand) ?: return false
+ val realType = McdevDfaUtil.tryGetDataflowType(operand) ?: return false
return isAssignable(realType, castType)
}
@@ -72,8 +70,8 @@
element.operationSign.tokenType == JavaTokenType.NE
)
) {
- val rightType = element.rOperand?.let(this::getTypeConstraint) ?: return false
- val leftType = getTypeConstraint(element.lOperand) ?: return false
+ val rightType = element.rOperand?.let(McdevDfaUtil::getTypeConstraint) ?: return false
+ val leftType = McdevDfaUtil.getTypeConstraint(element.lOperand) ?: return false
val isTypeWarning = leftType.meet(rightType) == TypeConstraints.BOTTOM
if (isTypeWarning) {
val leftWithMixins = addMixinsToTypeConstraint(element.project, leftType)
@@ -87,7 +85,7 @@
val castExpression = element.parent as? PsiTypeCastExpression ?: return false
val castType = castExpression.type ?: return false
- val realType = getRealType(castExpression) ?: return false
+ val realType = McdevDfaUtil.tryGetDataflowType(castExpression) ?: return false
return isAssignable(castType, realType)
}
@@ -118,14 +116,6 @@
return typeConstraint.join(mixinTypes.reduce(TypeConstraint::join))
}
- private fun getRealType(expression: PsiExpression): PsiType? {
- return getTypeConstraint(expression)?.getPsiType(expression.project)
- }
-
- private fun getTypeConstraint(expression: PsiExpression): TypeConstraint? {
- return (CommonDataflow.getDfType(expression) as? DfReferenceType)?.constraint
- }
-
override fun getSuppressActions(element: PsiElement?, toolId: String): Array =
SuppressQuickFix.EMPTY_ARRAY
Index: src/main/kotlin/platform/mixin/util/Mixin.kt
===================================================================
--- src/main/kotlin/platform/mixin/util/Mixin.kt (revision 449a4b79ff378b209b2be82c6dc8e53d8a7f2c8a)
+++ src/main/kotlin/platform/mixin/util/Mixin.kt (revision 6ab3c78b668f2b725b48cf68ed49ab4be1db13ce)
@@ -47,6 +47,7 @@
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.InheritanceUtil
import com.intellij.psi.util.PsiModificationTracker
+import com.intellij.psi.util.TypeConversionUtil
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
@@ -155,7 +156,7 @@
return JavaPsiFacade.getElementFactory(project).createType(psiClass, boxedType)
}
-fun isAssignable(left: PsiType, right: PsiType): Boolean {
+fun isAssignable(left: PsiType, right: PsiType, allowPrimitiveConversion: Boolean = true): Boolean {
return when {
left is PsiIntersectionType -> left.conjuncts.all { isAssignable(it, right) }
right is PsiIntersectionType -> right.conjuncts.any { isAssignable(left, it) }
@@ -164,8 +165,11 @@
left is PsiArrayType -> right is PsiArrayType && isAssignable(left.componentType, right.componentType)
else -> {
if (left !is PsiClassType || right !is PsiClassType) {
- return false
+ if (!allowPrimitiveConversion && (left is PsiPrimitiveType || right is PsiPrimitiveType)) {
+ return left == right
- }
+ }
+ return TypeConversionUtil.isAssignable(left, right)
+ }
val leftClass = left.resolve() ?: return false
val rightClass = right.resolve() ?: return false
@@ -193,10 +197,14 @@
}
val mixins = FindMixinsAction.findMixins(rightClass, rightClass.project) ?: return false
- return mixins.any { isClassAssignable(leftClass, it) }
+ if (mixins.any { isClassAssignable(leftClass, it) }) {
+ return true
- }
+ }
+
+ return isClassAssignable(leftClass, rightClass)
- }
-}
+ }
+ }
+}
private fun isClassAssignable(leftClass: PsiClass, rightClass: PsiClass): Boolean {
var result = false
Index: src/main/kotlin/platform/mixin/util/MixinConstants.kt
===================================================================
--- src/main/kotlin/platform/mixin/util/MixinConstants.kt (revision 449a4b79ff378b209b2be82c6dc8e53d8a7f2c8a)
+++ src/main/kotlin/platform/mixin/util/MixinConstants.kt (revision 6ab3c78b668f2b725b48cf68ed49ab4be1db13ce)
@@ -78,5 +78,6 @@
object MixinExtras {
const val OPERATION = "com.llamalad7.mixinextras.injector.wrapoperation.Operation"
+ const val WRAP_OPERATION = "com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation"
}
}
Index: src/main/kotlin/util/McdevDfaUtil.kt
===================================================================
--- src/main/kotlin/util/McdevDfaUtil.kt (revision 6ab3c78b668f2b725b48cf68ed49ab4be1db13ce)
+++ src/main/kotlin/util/McdevDfaUtil.kt (revision 6ab3c78b668f2b725b48cf68ed49ab4be1db13ce)
@@ -0,0 +1,122 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2023 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.demonwav.mcdev.util
+
+import com.intellij.codeInspection.dataFlow.CommonDataflow
+import com.intellij.codeInspection.dataFlow.TypeConstraint
+import com.intellij.codeInspection.dataFlow.interpreter.RunnerResult
+import com.intellij.codeInspection.dataFlow.interpreter.StandardDataFlowInterpreter
+import com.intellij.codeInspection.dataFlow.java.ControlFlowAnalyzer
+import com.intellij.codeInspection.dataFlow.java.anchor.JavaExpressionAnchor
+import com.intellij.codeInspection.dataFlow.jvm.JvmDfaMemoryStateImpl
+import com.intellij.codeInspection.dataFlow.jvm.descriptors.PlainDescriptor
+import com.intellij.codeInspection.dataFlow.lang.DfaAnchor
+import com.intellij.codeInspection.dataFlow.lang.DfaListener
+import com.intellij.codeInspection.dataFlow.memory.DfaMemoryState
+import com.intellij.codeInspection.dataFlow.types.DfReferenceType
+import com.intellij.codeInspection.dataFlow.value.DfaValue
+import com.intellij.codeInspection.dataFlow.value.DfaValueFactory
+import com.intellij.psi.PsiAnnotation
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiEnumConstantInitializer
+import com.intellij.psi.PsiExpression
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiParameter
+import com.intellij.psi.PsiType
+import com.intellij.psi.impl.light.LightParameter
+import com.intellij.psi.util.PsiUtil
+
+object McdevDfaUtil {
+ // Copy of package-private DfaUtil.getDataflowContext
+ fun getDataflowContext(expression: PsiExpression): PsiElement? {
+ var element = expression.parent
+ while (true) {
+ if (element == null || element is PsiAnnotation) {
+ return null
+ }
+ if (element is PsiMethod && !element.isConstructor) {
+ val containingClass = element.containingClass
+ if (containingClass != null &&
+ (!PsiUtil.isLocalOrAnonymousClass(containingClass) || containingClass is PsiEnumConstantInitializer)
+ ) {
+ return element.body
+ }
+ }
+ if (element is PsiClass && !PsiUtil.isLocalOrAnonymousClass(element)) {
+ return element
+ }
+ element = element.parent
+ }
+ }
+
+ fun isSometimesEqualToParameter(expression: PsiExpression, parameter: PsiParameter): Boolean {
+ val block = getDataflowContext(expression) ?: return false
+ val factory = DfaValueFactory(expression.project)
+ val flow = ControlFlowAnalyzer.buildFlow(block, factory, true) ?: return false
+
+ val memState = JvmDfaMemoryStateImpl(factory)
+ val stableParam = PlainDescriptor.createVariableValue(
+ factory,
+ LightParameter("stableParam", parameter.type, block)
+ )
+ val param = PlainDescriptor.createVariableValue(factory, parameter)
+ memState.applyCondition(stableParam.eq(param))
+
+ var isSometimesEqual = false
+
+ val interpreter = StandardDataFlowInterpreter(
+ flow,
+ object : DfaListener {
+ override fun beforePush(
+ args: Array,
+ value: DfaValue,
+ anchor: DfaAnchor,
+ state: DfaMemoryState
+ ) {
+ if (anchor is JavaExpressionAnchor && anchor.expression equivalentTo expression) {
+ if (state.areEqual(stableParam, value)) {
+ isSometimesEqual = true
+ }
+ }
+ }
+ }
+ )
+
+ if (interpreter.interpret(memState) != RunnerResult.OK) {
+ return false
+ }
+
+ return isSometimesEqual
+ }
+
+ fun getDataflowType(expression: PsiExpression): PsiType? {
+ return tryGetDataflowType(expression) ?: expression.type
+ }
+
+ fun tryGetDataflowType(expression: PsiExpression): PsiType? {
+ return getTypeConstraint(expression)?.getPsiType(expression.project)
+ }
+
+ fun getTypeConstraint(expression: PsiExpression): TypeConstraint? {
+ return (CommonDataflow.getDfType(expression) as? DfReferenceType)?.constraint
+ }
+}
Index: src/main/resources/META-INF/plugin.xml
===================================================================
--- src/main/resources/META-INF/plugin.xml (revision 449a4b79ff378b209b2be82c6dc8e53d8a7f2c8a)
+++ src/main/resources/META-INF/plugin.xml (revision 6ab3c78b668f2b725b48cf68ed49ab4be1db13ce)
@@ -967,6 +967,16 @@
hasStaticDescription="true"
implementationClass="com.demonwav.mcdev.platform.mixin.inspection.shadow.ShadowFinalInspection"/>
+
+
+