Skip to content

Commit f6b51c9

Browse files
committed
Add opcode warnings. Closes #2530
1 parent bf45f4e commit f6b51c9

8 files changed

Lines changed: 137 additions & 5 deletions

File tree

src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class AtResolver(
8888
private val targetMethod: MethodNode,
8989
) {
9090
companion object {
91-
private fun getInjectionPoint(at: PsiAnnotation): InjectionPoint<*>? {
91+
fun getInjectionPoint(at: PsiAnnotation): InjectionPoint<*>? {
9292
var atCode = at.qualifiedName?.let { InjectionPointAnnotation.atCodeFor(it) }
9393
?: at.findDeclaredAttributeValue("value")?.constantStringValue ?: return null
9494

src/main/kotlin/platform/mixin/handlers/injectionPoint/FieldInjectionPoint.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ class FieldInjectionPoint : QualifiedInjectionPoint<PsiField>() {
7676
}
7777
}
7878

79+
override val validOpcodes = VALID_OPCODES
80+
7981
override fun createNavigationVisitor(
8082
at: PsiAnnotation,
8183
target: MixinSelector?,

src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ abstract class InjectionPoint<T : PsiElement> {
108108

109109
open fun isShiftDiscouraged(shift: Int, at: PsiAnnotation): Boolean = shift != 0
110110

111+
open val validOpcodes: Set<Int> = emptySet()
112+
111113
abstract fun createNavigationVisitor(
112114
at: PsiAnnotation,
113115
target: MixinSelector?,

src/main/kotlin/platform/mixin/inspection/fix/AnnotationAttributeFix.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ import com.intellij.psi.PsiAnnotation
2929
import com.intellij.psi.PsiAnnotationMemberValue
3030
import com.intellij.psi.PsiElement
3131
import com.intellij.psi.PsiFile
32+
import com.intellij.psi.PsiLiteralExpression
3233
import com.intellij.psi.codeStyle.CodeStyleManager
34+
import com.intellij.psi.codeStyle.JavaCodeStyleManager
3335

3436
open class AnnotationAttributeFix(
3537
annotation: PsiAnnotation,
@@ -93,8 +95,14 @@ open class AnnotationAttributeFix(
9395

9496
override fun invoke(project: Project, file: PsiFile, startElement: PsiElement, endElement: PsiElement) {
9597
val annotation = startElement as? PsiAnnotation ?: return
98+
var needsShortenClassReferences = false
99+
96100
for ((key, value) in attributes) {
97101
annotation.setDeclaredAttributeValue(key, value)
102+
103+
if (value != null && value !is PsiLiteralExpression) {
104+
needsShortenClassReferences = true
105+
}
98106
}
99107

100108
// replace single "value = foo" with "foo"
@@ -108,5 +116,9 @@ open class AnnotationAttributeFix(
108116
}
109117

110118
CodeStyleManager.getInstance(project).reformat(annotation.parameterList)
119+
120+
if (needsShortenClassReferences) {
121+
JavaCodeStyleManager.getInstance(project).shortenClassReferences(annotation)
122+
}
111123
}
112124
}

src/main/kotlin/platform/mixin/inspection/injector/CancellableBeforeSuperCallInspection.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ package com.demonwav.mcdev.platform.mixin.inspection.injector
2222

2323
import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler
2424
import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler
25-
import com.demonwav.mcdev.platform.mixin.inspection.MixinCancellableInspection
2625
import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection
2726
import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember
2827
import com.demonwav.mcdev.platform.mixin.util.MixinConstants
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Minecraft Development for IntelliJ
3+
*
4+
* https://mcdev.io/
5+
*
6+
* Copyright (C) 2025 minecraft-dev
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU Lesser General Public License as published
10+
* by the Free Software Foundation, version 3.0 only.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
package com.demonwav.mcdev.platform.mixin.inspection.injector
22+
23+
import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler
24+
import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.AtResolver
25+
import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection
26+
import com.demonwav.mcdev.platform.mixin.inspection.fix.AnnotationAttributeFix
27+
import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember
28+
import com.demonwav.mcdev.platform.mixin.util.MixinConstants
29+
import com.demonwav.mcdev.util.constantValue
30+
import com.intellij.codeInspection.LocalQuickFix
31+
import com.intellij.codeInspection.ProblemsHolder
32+
import com.intellij.openapi.project.Project
33+
import com.intellij.psi.JavaElementVisitor
34+
import com.intellij.psi.JavaPsiFacade
35+
import com.intellij.psi.PsiAnnotation
36+
import com.intellij.psi.PsiExpression
37+
import org.objectweb.asm.util.Printer
38+
39+
class InjectorOpcodeInspection : MixinInspection() {
40+
override fun getStaticDescription() = "Reports missing or invalid usages of opcode"
41+
42+
override fun buildVisitor(holder: ProblemsHolder) = object : JavaElementVisitor() {
43+
override fun visitAnnotation(annotation: PsiAnnotation) {
44+
if (!annotation.hasQualifiedName(MixinConstants.Annotations.AT)) {
45+
return
46+
}
47+
48+
val injectionPoint = AtResolver.getInjectionPoint(annotation) ?: return
49+
val validOpcodes = injectionPoint.validOpcodes
50+
val opcodeAttribute = annotation.findDeclaredAttributeValue("opcode")
51+
val opcode = opcodeAttribute?.constantValue as? Int
52+
53+
if (validOpcodes.isEmpty()) {
54+
if (opcode != null) {
55+
holder.registerProblem(
56+
opcodeAttribute,
57+
"Opcode is ignored by this injection point",
58+
AnnotationAttributeFix(annotation, "opcode" to null)
59+
)
60+
}
61+
} else {
62+
if (opcode == null) {
63+
holder.registerProblem(
64+
annotation.nameReferenceElement ?: annotation,
65+
"Missing opcode",
66+
*listOfNotNull(makeSpecifyOpcodeFix(holder.project, annotation)).toTypedArray()
67+
)
68+
} else if (opcode !in validOpcodes) {
69+
holder.registerProblem(
70+
opcodeAttribute,
71+
"Invalid opcode for this injection point"
72+
)
73+
}
74+
}
75+
}
76+
}
77+
78+
private fun makeSpecifyOpcodeFix(project: Project, at: PsiAnnotation): LocalQuickFix? {
79+
val injector = AtResolver.findInjectorAnnotation(at) ?: return null
80+
val targetMethods =
81+
MixinAnnotationHandler.resolveTarget(injector).filterIsInstance<MethodTargetMember>()
82+
83+
val possibleOpcodes = mutableSetOf<Int>()
84+
85+
for (method in targetMethods) {
86+
val instructions = AtResolver(at, method.classAndMethod.clazz, method.classAndMethod.method)
87+
.resolveInstructions()
88+
for (insn in instructions) {
89+
possibleOpcodes += insn.originalInsn.opcode
90+
}
91+
}
92+
93+
val opcode = possibleOpcodes.singleOrNull()?.takeIf { it != -1 } ?: return null
94+
val opcodeName = Printer.OPCODES[opcode]
95+
val opcodeValue = JavaPsiFacade.getElementFactory(project).createExpressionFromText(
96+
"org.objectweb.asm.Opcodes.$opcodeName",
97+
at
98+
)
99+
return AddOpcodeFix(at, opcodeName, opcodeValue)
100+
}
101+
102+
private class AddOpcodeFix(at: PsiAnnotation, private val opcodeName: String, opcodeValue: PsiExpression) : AnnotationAttributeFix(
103+
at,
104+
"opcode" to opcodeValue
105+
) {
106+
override fun getFamilyName() = "Add opcode = Opcodes.$opcodeName"
107+
override fun getText() = "Add opcode = Opcodes.$opcodeName"
108+
}
109+
}

src/main/kotlin/platform/mixin/inspection/MixinCancellableInspection.kt renamed to src/main/kotlin/platform/mixin/inspection/injector/MixinCancellableInspection.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
* along with this program. If not, see <https://www.gnu.org/licenses/>.
1919
*/
2020

21-
package com.demonwav.mcdev.platform.mixin.inspection
21+
package com.demonwav.mcdev.platform.mixin.inspection.injector
2222

23-
import com.demonwav.mcdev.platform.mixin.inspection.injector.CancellableBeforeSuperCallInspection
23+
import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection
2424
import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.INJECT
2525
import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Classes.CALLBACK_INFO
2626
import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Classes.CALLBACK_INFO_RETURNABLE

src/main/resources/META-INF/plugin.xml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1057,7 +1057,7 @@
10571057
enabledByDefault="true"
10581058
level="WARNING"
10591059
hasStaticDescription="true"
1060-
implementationClass="com.demonwav.mcdev.platform.mixin.inspection.MixinCancellableInspection"/>
1060+
implementationClass="com.demonwav.mcdev.platform.mixin.inspection.injector.MixinCancellableInspection"/>
10611061
<localInspection displayName="Invalid @Mixin Target"
10621062
shortName="InvalidMixinTarget"
10631063
groupName="Mixin"
@@ -1206,6 +1206,14 @@
12061206
level="ERROR"
12071207
hasStaticDescription="true"
12081208
implementationClass="com.demonwav.mcdev.platform.mixin.inspection.injector.InjectIntoConstructorInspection"/>
1209+
<localInspection displayName="Missing or invalid use of opcode"
1210+
shortName="MissingOrInvalidOpcode"
1211+
groupName="Mixin"
1212+
language="JAVA"
1213+
enabledByDefault="true"
1214+
level="WARNING"
1215+
hasStaticDescription="true"
1216+
implementationClass="com.demonwav.mcdev.platform.mixin.inspection.injector.InjectorOpcodeInspection"/>
12091217
<localInspection displayName="Invalid injector method signature"
12101218
shortName="InvalidInjectorMethodSignature"
12111219
groupName="Mixin"

0 commit comments

Comments
 (0)