User: joe Date: 23 Jan 24 00:18 Revision: 53c3cca60486df0c2e0afe88f31ed99f0c825f58 Summary: Add CTOR_HEAD injection point TeamCity URL: https://ci.mcdev.io/viewModification.html?tab=vcsModificationFiles&modId=9029&personal=false Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/CtorHeadInjectionPoint.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/injectionPoint/CtorHeadInjectionPoint.kt (revision 53c3cca60486df0c2e0afe88f31ed99f0c825f58) +++ src/main/kotlin/platform/mixin/handlers/injectionPoint/CtorHeadInjectionPoint.kt (revision 53c3cca60486df0c2e0afe88f31ed99f0c825f58) @@ -0,0 +1,194 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 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.handlers.injectionPoint + +import com.demonwav.mcdev.platform.mixin.reference.MixinSelector +import com.demonwav.mcdev.platform.mixin.util.findOrConstructSourceMethod +import com.demonwav.mcdev.platform.mixin.util.findSuperConstructorCall +import com.demonwav.mcdev.platform.mixin.util.isConstructor +import com.demonwav.mcdev.util.createLiteralExpression +import com.demonwav.mcdev.util.enumValueOfOrNull +import com.demonwav.mcdev.util.findContainingClass +import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiExpression +import com.intellij.psi.PsiField +import com.intellij.psi.PsiLiteral +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiMethodCallExpression +import com.intellij.psi.PsiMethodReferenceExpression +import com.intellij.psi.PsiReferenceExpression +import com.intellij.psi.PsiStatement +import com.intellij.psi.codeStyle.CodeStyleManager +import com.intellij.psi.util.PsiUtil +import com.intellij.psi.util.parentOfType +import com.intellij.util.JavaPsiConstructorUtil +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.FieldInsnNode +import org.objectweb.asm.tree.MethodNode + +class CtorHeadInjectionPoint : InjectionPoint() { + override fun onCompleted(editor: Editor, reference: PsiLiteral) { + val at = reference.parentOfType() ?: return + val project = reference.project + at.setDeclaredAttributeValue( + "unsafe", + JavaPsiFacade.getElementFactory(project).createLiteralExpression(true) + ) + CodeStyleManager.getInstance(project).reformat(at) + } + + override fun createNavigationVisitor( + at: PsiAnnotation, + target: MixinSelector?, + targetClass: PsiClass + ): NavigationVisitor { + val args = AtResolver.getArgs(at) + val enforce = args["enforce"]?.let { enumValueOfOrNull(it) } ?: EnforceMode.DEFAULT + return MyNavigationVisitor(enforce) + } + + override fun doCreateCollectVisitor( + at: PsiAnnotation, + target: MixinSelector?, + targetClass: ClassNode, + mode: CollectVisitor.Mode + ): CollectVisitor { + val args = AtResolver.getArgs(at) + val enforce = args["enforce"]?.let { enumValueOfOrNull(it) } ?: EnforceMode.DEFAULT + return MyCollectVisitor(at.project, targetClass, mode, enforce) + } + + override fun createLookup( + targetClass: ClassNode, + result: CollectVisitor.Result + ): LookupElementBuilder? { + return null + } + + private enum class EnforceMode { + DEFAULT, POST_DELEGATE, POST_INIT + } + + private class MyCollectVisitor( + project: Project, + clazz: ClassNode, + mode: Mode, + private val enforce: EnforceMode, + ) : HeadInjectionPoint.MyCollectVisitor(project, clazz, mode) { + override fun accept(methodNode: MethodNode) { + val insns = methodNode.instructions ?: return + + if (!methodNode.isConstructor) { + super.accept(methodNode) + return + } + + val superCtorCall = methodNode.findSuperConstructorCall() ?: run { + super.accept(methodNode) + return + } + + if (enforce == EnforceMode.POST_DELEGATE) { + val insn = superCtorCall.next ?: return + addResult(insn, methodNode.findOrConstructSourceMethod(clazz, project)) + return + } + + // Although Mumfrey's original intention was to target the last *unique* field store, + // i.e. ignore duplicate field stores that occur later, due to a bug in the implementation + // it simply finds the last PUTFIELD whose owner is the target class. Mumfrey now says he + // doesn't want to change the implementation in case of breaking mixins that rely on this + // behavior, so it is now effectively intended, so it's what we'll use here. + val lastFieldStore = generateSequence(insns.last) { it.previous } + .takeWhile { it !== superCtorCall } + .firstOrNull { insn -> + insn.opcode == Opcodes.PUTFIELD && + (insn as FieldInsnNode).owner == clazz.name + } ?: superCtorCall + + val lastFieldStoreNext = lastFieldStore.next ?: return + addResult(lastFieldStoreNext, methodNode.findOrConstructSourceMethod(clazz, project)) + } + } + + private class MyNavigationVisitor(private val enforce: EnforceMode) : NavigationVisitor() { + private var isConstructor = true + private var firstStatement = true + private lateinit var elementToReturn: PsiElement + + override fun visitStart(executableElement: PsiElement) { + isConstructor = executableElement is PsiMethod && executableElement.isConstructor + elementToReturn = executableElement + } + + override fun visitExpression(expression: PsiExpression) { + if (firstStatement) { + elementToReturn = expression + firstStatement = false + } + super.visitExpression(expression) + } + + override fun visitStatement(statement: PsiStatement) { + if (firstStatement) { + elementToReturn = statement + firstStatement = false + } + super.visitStatement(statement) + } + + override fun visitMethodCallExpression(expression: PsiMethodCallExpression) { + super.visitMethodCallExpression(expression) + if (isConstructor) { + if (JavaPsiConstructorUtil.isChainedConstructorCall(expression) || + JavaPsiConstructorUtil.isSuperConstructorCall(expression) + ) { + elementToReturn = expression + } + } + } + + override fun visitReferenceExpression(expression: PsiReferenceExpression) { + super.visitReferenceExpression(expression) + if (isConstructor && + enforce != EnforceMode.POST_DELEGATE && + expression !is PsiMethodReferenceExpression && + PsiUtil.isAccessedForWriting(expression) + ) { + val resolvedField = expression.resolve() + if (resolvedField is PsiField && resolvedField.containingClass == expression.findContainingClass()) { + elementToReturn = expression + } + } + } + + override fun visitEnd(executableElement: PsiElement) { + addResult(elementToReturn) + } + } +} Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/HeadInjectionPoint.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/injectionPoint/HeadInjectionPoint.kt (revision 855d67c62fd4f4ef7f894bd57b9e237752069426) +++ src/main/kotlin/platform/mixin/handlers/injectionPoint/HeadInjectionPoint.kt (revision 53c3cca60486df0c2e0afe88f31ed99f0c825f58) @@ -57,9 +57,9 @@ return null } - private class MyCollectVisitor( - private val project: Project, - private val clazz: ClassNode, + internal open class MyCollectVisitor( + protected val project: Project, + protected val clazz: ClassNode, mode: Mode, ) : CollectVisitor(mode) { override fun accept(methodNode: MethodNode) { Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt (revision 855d67c62fd4f4ef7f894bd57b9e237752069426) +++ src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt (revision 53c3cca60486df0c2e0afe88f31ed99f0c825f58) @@ -294,6 +294,9 @@ result += element } + open fun visitStart(executableElement: PsiElement) { + } + open fun visitEnd(executableElement: PsiElement) { } @@ -304,6 +307,7 @@ override fun visitMethod(method: PsiMethod) { if (!hasVisitedAnything) { + visitStart(method) super.visitMethod(method) visitEnd(method) } @@ -312,6 +316,7 @@ override fun visitAnonymousClass(aClass: PsiAnonymousClass) { // do not recurse into anonymous classes if (!hasVisitedAnything) { + visitStart(aClass) super.visitAnonymousClass(aClass) visitEnd(aClass) } @@ -320,6 +325,7 @@ override fun visitClass(aClass: PsiClass) { // do not recurse into inner classes if (!hasVisitedAnything) { + visitStart(aClass) super.visitClass(aClass) visitEnd(aClass) } @@ -327,6 +333,9 @@ override fun visitMethodReferenceExpression(expression: PsiMethodReferenceExpression) { val hadVisitedAnything = hasVisitedAnything + if (!hadVisitedAnything) { + visitStart(expression) + } super.visitMethodReferenceExpression(expression) if (!hadVisitedAnything) { visitEnd(expression) @@ -336,6 +345,7 @@ override fun visitLambdaExpression(expression: PsiLambdaExpression) { // do not recurse into lambda expressions if (!hasVisitedAnything) { + visitStart(expression) super.visitLambdaExpression(expression) visitEnd(expression) } Index: src/main/kotlin/util/utils.kt =================================================================== --- src/main/kotlin/util/utils.kt (revision 855d67c62fd4f4ef7f894bd57b9e237752069426) +++ src/main/kotlin/util/utils.kt (revision 53c3cca60486df0c2e0afe88f31ed99f0c825f58) @@ -388,3 +388,11 @@ return null } + +inline fun > enumValueOfOrNull(str: String): T? { + return try { + enumValueOf(str) + } catch (e: IllegalArgumentException) { + null + } +} Index: src/main/resources/META-INF/plugin.xml =================================================================== --- src/main/resources/META-INF/plugin.xml (revision 855d67c62fd4f4ef7f894bd57b9e237752069426) +++ src/main/resources/META-INF/plugin.xml (revision 53c3cca60486df0c2e0afe88f31ed99f0c825f58) @@ -151,6 +151,7 @@ +