aboutsummaryrefslogtreecommitdiff
path: root/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AsmUtil.kt
blob: 0687f384625fbf470d66c72bf61985ba1b6189b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/*
 * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
 */

package kotlinx.atomicfu.transformer

import org.objectweb.asm.*
import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.Type.*
import org.objectweb.asm.tree.*
import org.objectweb.asm.util.*

val AbstractInsnNode.line: Int?
    get() {
        var cur = this
        while (true) {
            if (cur is LineNumberNode) return cur.line
            cur = cur.previous ?: return null
        }
    }

fun AbstractInsnNode.atIndex(insnList: InsnList?): String {
    var cur = insnList?.first
    var index = 1
    while (cur != null && cur != this) {
        if (!cur.isUseless()) index++
        cur = cur.next
    }
    if (cur == null) return ""
    return "inst #$index: "
}

val AbstractInsnNode.nextUseful: AbstractInsnNode?
    get() {
        var cur: AbstractInsnNode? = next
        while (cur.isUseless()) cur = cur!!.next
        return cur
    }

val AbstractInsnNode?.thisOrPrevUseful: AbstractInsnNode?
    get() {
        var cur: AbstractInsnNode? = this
        while (cur.isUseless()) cur = cur!!.previous
        return cur
    }

fun getInsnOrNull(from: AbstractInsnNode?, to: AbstractInsnNode?, predicate: (AbstractInsnNode) -> Boolean): AbstractInsnNode? {
    var cur: AbstractInsnNode? = from?.next
    while (cur != null && cur != to && !predicate(cur)) cur = cur.next
    return cur
}

private fun AbstractInsnNode?.isUseless() = this is LabelNode || this is LineNumberNode || this is FrameNode

fun InsnList.listUseful(limit: Int = Int.MAX_VALUE): List<AbstractInsnNode> {
    val result = ArrayList<AbstractInsnNode>(limit)
    var cur = first
    while (cur != null && result.size < limit) {
        if (!cur.isUseless()) result.add(cur)
        cur = cur.next
    }
    return result
}

fun AbstractInsnNode.isAload(index: Int) =
    this is VarInsnNode && this.opcode == ALOAD && this.`var` == index

fun AbstractInsnNode.isGetField(owner: String) =
    this is FieldInsnNode && this.opcode == GETFIELD && this.owner == owner

fun AbstractInsnNode.isGetStatic(owner: String) =
    this is FieldInsnNode && this.opcode == GETSTATIC && this.owner == owner

fun AbstractInsnNode.isGetFieldOrGetStatic() =
    this is FieldInsnNode && (this.opcode == GETFIELD || this.opcode == GETSTATIC)

fun AbstractInsnNode.isAreturn() =
    this.opcode == ARETURN

fun AbstractInsnNode.isReturn() =
    this.opcode == RETURN

fun AbstractInsnNode.isTypeReturn(type: Type) =
    opcode == when (type) {
        INT_TYPE -> IRETURN
        LONG_TYPE -> LRETURN
        BOOLEAN_TYPE -> IRETURN
        else -> ARETURN
    }

fun AbstractInsnNode.isInvokeVirtual() =
        this.opcode == INVOKEVIRTUAL

@Suppress("UNCHECKED_CAST")
fun MethodNode.localVar(v: Int, node: AbstractInsnNode): LocalVariableNode? =
    (localVariables as List<LocalVariableNode>).firstOrNull { it.index == v && verifyLocalVarScopeStart(v, node, it.start)}

// checks that the store instruction is followed by the label equal to the local variable scope start from the local variables table
private fun verifyLocalVarScopeStart(v: Int, node: AbstractInsnNode, scopeStart: LabelNode): Boolean {
    var i = node.next
    while (i != null) {
        // check that no other variable is stored into the same slot v before finding the scope start label
        if (i is VarInsnNode && i.`var` == v) return false
        if (i is LabelNode && i === scopeStart) return true
        i = i.next
    }
    return false
}

inline fun forVarLoads(v: Int, start: LabelNode, end: LabelNode, block: (VarInsnNode) -> AbstractInsnNode?) {
    var cur: AbstractInsnNode? = start
    while (cur != null && cur !== end) {
        if (cur is VarInsnNode && cur.opcode == ALOAD && cur.`var` == v) {
            cur = block(cur)
        } else
            cur = cur.next
    }
}

fun nextVarLoad(v: Int, start: AbstractInsnNode): VarInsnNode {
    var cur: AbstractInsnNode? = start
    while (cur != null) {
        when (cur.opcode) {
            GOTO, TABLESWITCH, LOOKUPSWITCH, ATHROW, IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL,
            IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE,
            IRETURN, FRETURN, ARETURN, RETURN, LRETURN, DRETURN -> {
                abort("Unsupported branching/control while searching for load of spilled variable #$v", cur)
            }
            ALOAD -> {
                if ((cur as VarInsnNode).`var` == v) return cur
            }
        }
        cur = cur.next
    }
    abort("Flow control falls after the end of the method while searching for load of spilled variable #$v")
}

fun accessToInvokeOpcode(access: Int) =
    if (access and ACC_STATIC != 0) INVOKESTATIC else INVOKEVIRTUAL

fun AbstractInsnNode.toText(): String {
    val printer = Textifier()
    accept(TraceMethodVisitor(printer))
    return (printer.getText()[0] as String).trim()
}

val String.ownerPackageName get() = substringBeforeLast('/')