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"/> + + +