2020
2121package com.demonwav.mcdev.platform.mixin.handlers
2222
23+ import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.AtResolver
2324import com.demonwav.mcdev.platform.mixin.inspection.injector.MethodSignature
2425import com.demonwav.mcdev.platform.mixin.inspection.injector.ParameterGroup
2526import com.demonwav.mcdev.platform.mixin.util.LocalVariables
@@ -28,16 +29,37 @@ import com.demonwav.mcdev.platform.mixin.util.callbackInfoType
2829import com.demonwav.mcdev.platform.mixin.util.getGenericReturnType
2930import com.demonwav.mcdev.platform.mixin.util.hasAccess
3031import com.demonwav.mcdev.platform.mixin.util.toPsiType
32+ import com.demonwav.mcdev.util.McdevDfaUtil
3133import com.demonwav.mcdev.util.Parameter
34+ import com.demonwav.mcdev.util.findAnnotations
3235import com.demonwav.mcdev.util.findModule
3336import com.demonwav.mcdev.util.firstIndexOrNull
3437import com.intellij.psi.JavaPsiFacade
38+ import com.intellij.psi.JavaRecursiveElementWalkingVisitor
3539import com.intellij.psi.PsiAnnotation
40+ import com.intellij.psi.PsiClass
41+ import com.intellij.psi.PsiElement
42+ import com.intellij.psi.PsiExpression
43+ import com.intellij.psi.PsiField
44+ import com.intellij.psi.PsiLambdaExpression
45+ import com.intellij.psi.PsiMember
3646import com.intellij.psi.PsiMethod
3747import com.intellij.psi.PsiQualifiedReference
48+ import com.intellij.psi.PsiStatement
3849import com.intellij.psi.PsiTypes
50+ import com.intellij.psi.controlFlow.ConditionalThrowToInstruction
51+ import com.intellij.psi.controlFlow.ControlFlow
52+ import com.intellij.psi.controlFlow.ControlFlowFactory
53+ import com.intellij.psi.controlFlow.ControlFlowUtil
54+ import com.intellij.psi.controlFlow.LocalsOrMyInstanceFieldsControlFlowPolicy
55+ import com.intellij.psi.controlFlow.ReturnInstruction
56+ import com.intellij.psi.util.PsiTreeUtil
3957import com.intellij.psi.util.parentOfType
58+ import com.intellij.psi.util.parents
59+ import com.intellij.util.takeWhileInclusive
4060import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext
61+ import com.siyeh.ig.psiutils.SideEffectChecker
62+ import java.util.BitSet
4163import org.objectweb.asm.Opcodes
4264import org.objectweb.asm.Type
4365import org.objectweb.asm.tree.ClassNode
@@ -134,4 +156,188 @@ class InjectAnnotationHandler : InjectorAnnotationHandler() {
134156 override val allowCoerce = true
135157
136158 override val mixinExtrasExpressionContextType = ExpressionContext .Type .INJECT
159+
160+ override fun createTargetInlay (
161+ context : MixinAnnotationHandler .TargetInlayContext
162+ ): MixinAnnotationHandler .TargetInlayProperties ? {
163+ val inlayProps = super .createTargetInlay(context) ? : return null
164+ val at = context.annotation.findAttributeValue(" at" )?.findAnnotations()?.getOrNull(context.navigationIndex)
165+ ? : return null
166+ return moveInlayAcrossNonSideEffectCodeToPrettierSpot(context, AtResolver .getShift(at) > 0 , inlayProps)
167+ }
168+
169+ companion object {
170+ /* *
171+ * Note: this doesn't just have to check for side effects, it also has to check for whether where we're shifting
172+ * to is reached iff the injection point is reached.
173+ */
174+ fun moveInlayAcrossNonSideEffectCodeToPrettierSpot (
175+ context : MixinAnnotationHandler .TargetInlayContext ,
176+ isAfter : Boolean ,
177+ inlayProps : MixinAnnotationHandler .TargetInlayProperties
178+ ): MixinAnnotationHandler .TargetInlayProperties {
179+ val targetElement = context.targetElement
180+
181+ if (
182+ inlayProps.placement != MixinAnnotationHandler .TargetInlayPlacement .BEFORE &&
183+ inlayProps.placement != MixinAnnotationHandler .TargetInlayPlacement .AFTER
184+ ) {
185+ return inlayProps
186+ }
187+
188+ val controlFlowBlock = McdevDfaUtil .getControlFlowContext(targetElement) ? : return inlayProps
189+ val project = controlFlowBlock.project
190+ val controlFlow = ControlFlowFactory .getInstance(project)
191+ .getControlFlow(controlFlowBlock, LocalsOrMyInstanceFieldsControlFlowPolicy .getInstance())
192+
193+ val topmostParent = targetElement.parents(withSelf = true )
194+ .takeWhile { it !is PsiClass && (it !is PsiMember || it is PsiField ) && it !is PsiLambdaExpression }
195+ .firstOrNull { it is PsiStatement || it is PsiField }
196+ ? : targetElement.parents(withSelf = true ).takeWhile { it is PsiExpression }.lastOrNull()
197+ ? : targetElement
198+ val parents = targetElement.parents(withSelf = true ).takeWhileInclusive { it != topmostParent }.toList()
199+
200+ // workaround for SideEffectChecker requiring PsiExpression for all methods which take a list
201+ val sideEffects = mutableListOf<PsiElement >()
202+ topmostParent.accept(object : JavaRecursiveElementWalkingVisitor () {
203+ override fun visitElement (element : PsiElement ) {
204+ if (SideEffectChecker .mayHaveSideEffects(element) { it != element }) {
205+ sideEffects + = element
206+ }
207+ super .visitElement(element)
208+ }
209+
210+ override fun visitExpression (expression : PsiExpression ) {
211+ SideEffectChecker .checkSideEffects(expression, sideEffects)
212+ }
213+ })
214+ val sideEffectOffsets = BitSet ()
215+ for (sideEffect in sideEffects) {
216+ val offset = controlFlow.getEndOffset(sideEffect)
217+ if (offset >= 0 ) {
218+ sideEffectOffsets.set(offset)
219+ }
220+ }
221+
222+ var anchor = inlayProps.anchor
223+
224+ if (isAfter) {
225+ val targetOffset = controlFlow.getEndOffset(targetElement)
226+ if (targetOffset >= 0 ) {
227+ val afterAnchor = parents.takeWhile { parent ->
228+ if (! PsiTreeUtil .isAncestor(controlFlowBlock, parent, false )) {
229+ return @takeWhile true
230+ }
231+ val endOffset = controlFlow.getEndOffset(parent)
232+ if (endOffset < 0 ) {
233+ return @takeWhile true
234+ }
235+ isAlwaysReachedAndNotViaSideEffects(
236+ controlFlow,
237+ targetOffset,
238+ endOffset,
239+ sideEffectOffsets,
240+ skipFirst = true
241+ )
242+ }.lastOrNull()
243+ if (afterAnchor != null ) {
244+ anchor = afterAnchor
245+ }
246+ }
247+ } else {
248+ val targetOffset = controlFlow.getStartOffset(targetElement)
249+ if (targetOffset >= 0 ) {
250+ val beforeAnchor = parents.takeWhile { parent ->
251+ if (! PsiTreeUtil .isAncestor(controlFlowBlock, parent, false )) {
252+ return @takeWhile true
253+ }
254+ val startOffset = controlFlow.getStartOffset(parent)
255+ if (startOffset < 0 ) {
256+ return @takeWhile true
257+ }
258+ isAlwaysReachedAndNotViaSideEffects(
259+ controlFlow,
260+ startOffset,
261+ targetOffset,
262+ sideEffectOffsets,
263+ skipFirst = false
264+ )
265+ }.lastOrNull()
266+ if (beforeAnchor != null ) {
267+ anchor = beforeAnchor
268+ }
269+ }
270+ }
271+
272+ val placement = if (anchor is PsiStatement || anchor is PsiField ) {
273+ if (isAfter) {
274+ MixinAnnotationHandler .TargetInlayPlacement .NEXT_LINE
275+ } else {
276+ MixinAnnotationHandler .TargetInlayPlacement .PREVIOUS_LINE
277+ }
278+ } else {
279+ inlayProps.placement
280+ }
281+
282+ return inlayProps.copy(anchor = anchor, placement = placement)
283+ }
284+
285+ private fun isAlwaysReachedAndNotViaSideEffects (
286+ controlFlow : ControlFlow ,
287+ from : Int ,
288+ to : Int ,
289+ sideEffects : BitSet ,
290+ skipFirst : Boolean ,
291+ ): Boolean {
292+ val graph = ControlFlowUtil .getEdges(controlFlow, 0 ).groupBy({ it.myFrom }) { it.myTo }
293+
294+ val visited = BitSet ()
295+ fun checkToUnreachableNotViaFrom (index : Int ): Boolean {
296+ if (index == from) {
297+ return true
298+ }
299+ if (index == to) {
300+ return false
301+ }
302+ if (visited.get(index)) {
303+ return true
304+ }
305+ visited.set(index)
306+ val successors = graph[index] ? : return true
307+ return successors.all { checkToUnreachableNotViaFrom(it) }
308+ }
309+ if (! checkToUnreachableNotViaFrom(0 )) {
310+ return false
311+ }
312+
313+ visited.clear()
314+ fun dfs (index : Int ): Boolean {
315+ if (index == to || visited.get(index)) {
316+ return true
317+ }
318+ if (index < 0 || index >= controlFlow.instructions.size) {
319+ return false
320+ }
321+
322+ visited.set(index)
323+
324+ val insn = controlFlow.instructions[index]
325+ val successors = if (insn is ConditionalThrowToInstruction ) {
326+ listOf (index + 1 )
327+ } else {
328+ graph[index] ? : emptyList()
329+ }
330+
331+ if (! skipFirst || index != from) {
332+ if (sideEffects.get(index) || insn is ReturnInstruction ) {
333+ return false
334+ }
335+ }
336+
337+ return successors.all { dfs(it) }
338+ }
339+
340+ return dfs(from)
341+ }
342+ }
137343}
0 commit comments