1 /* 2 * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 */ 4 5 package kotlinx.atomicfu.transformer 6 7 import org.objectweb.asm.Opcodes.* 8 import org.objectweb.asm.Type 9 import org.objectweb.asm.tree.* 10 import kotlin.math.abs 11 12 class FlowAnalyzer( 13 private val start: AbstractInsnNode? 14 ) { 15 private var cur: AbstractInsnNode? = null 16 17 // the depth at which our atomic variable lies now (zero == top of stack), 18 // this ref at one slot below it (and we can choose to merge them!) 19 private var depth = 0 20 abortnull21 private fun abort(msg: String): Nothing = abort(msg, cur) 22 private fun unsupported(): Nothing = abort("Unsupported operation on atomic variable") 23 private fun unrecognized(i: AbstractInsnNode): Nothing = abort("Unrecognized operation ${i.toText()}") 24 25 private fun push(n: Int, forward: Boolean) { 26 if (!forward && abs(depth) < n) unsupported() 27 depth += n 28 } 29 popnull30 private fun pop(n: Int, forward: Boolean) { 31 if (forward && depth < n) unsupported() 32 depth -= n 33 } 34 35 // with stack top containing transformed variables analyses the following sequential data flow until consumed with: 36 // * "astore" -- result is VarInsnNode 37 // * "invokevirtual" on it -- result is MethodInsnNode 38 // All other outcomes produce transformation error executenull39 fun execute(): AbstractInsnNode { 40 var i = start 41 while (i != null) { 42 cur = i 43 executeOne(i)?.let { return it } 44 i = i.next 45 } 46 abort("Flow control falls after the end of the method") 47 } 48 49 // returns instruction preceding pushing arguments to the atomic factory getInitStartnull50 fun getInitStart(stack: Int): AbstractInsnNode { 51 var i = start 52 depth = -stack 53 while (i != null) { 54 executeOne(i, false) 55 if (depth == 0) break 56 i = i.previous 57 } 58 // This may be array initialization in JVM_IR generated bytecode. 59 // Old BE does not empty stack after each array element, 60 // but JVM_IR does, thus, we cannot just assume, that empty stack means initialization start, 61 // instead, we need to find array's ANEWARRAY or NEWARRAY and constant before it. 62 if (i.isArrayStore()) { 63 // Thankfully, in case of nested arrays, JVM_IR puts outer arrays on stack, 64 // So, to support them, we need just to check depth 65 do { 66 while (i != null && i.opcode != ANEWARRAY && i.opcode != NEWARRAY) { 67 executeOne(i, false) 68 i = i.previous 69 } 70 if (i == null) break 71 // Before ANEWARRAY there should be constant integer 72 executeOne(i, false) 73 if (depth == 0) break 74 i = i.previous 75 } while (i != null) 76 } 77 return i ?: abort("Backward flow control falls after the beginning of the method") 78 } 79 getUncheckedCastInsnnull80 fun getUncheckedCastInsn(): AbstractInsnNode? { 81 var i = start 82 depth = 1 83 while (i != null) { 84 cur = i 85 executeOne(i) 86 if (depth == 0 && i is MethodInsnNode && i.owner == "kotlin/jvm/internal/Intrinsics" && i.name == "checkNotNull") { 87 return i 88 } 89 i = i.next 90 } 91 return null 92 } 93 getValueArgInitLastnull94 fun getValueArgInitLast(): AbstractInsnNode { 95 var i = start 96 val valueArgSize = Type.getArgumentTypes((start as MethodInsnNode).desc)[0].size 97 depth = -1 98 while (i != null) { 99 executeOne(i, false) 100 i = i.previous 101 if (depth == -valueArgSize) return i 102 } 103 abort("Backward flow control falls after the beginning of the method") 104 } 105 AbstractInsnNodenull106 private fun AbstractInsnNode?.isArrayStore(): Boolean = when(this?.opcode) { 107 IASTORE, BASTORE, CASTORE, SASTORE, FASTORE, AASTORE -> true 108 else -> false 109 } 110 111 // forward is true when instructions are executed in forward order from top to bottom executeOnenull112 private fun executeOne(i: AbstractInsnNode, forward: Boolean = true): AbstractInsnNode? { 113 when (i) { 114 is LabelNode -> { /* ignore */ } 115 is LineNumberNode -> { /* ignore */ } 116 is FrameNode -> { /* ignore */ } 117 is MethodInsnNode -> { 118 popDesc(i.desc, forward) 119 if (i.opcode == INVOKEVIRTUAL && depth == 0) return i // invoke virtual on atomic field ref 120 if (i.opcode != INVOKESTATIC) pop(1, forward) 121 pushDesc(i.desc, forward) 122 } 123 is FieldInsnNode -> when (i.opcode) { 124 GETSTATIC -> pushDesc(i.desc, forward) 125 PUTSTATIC -> popDesc(i.desc, forward) 126 GETFIELD -> { 127 pop(1, forward) 128 pushDesc(i.desc, forward) 129 } 130 PUTFIELD -> { 131 popDesc(i.desc, forward) 132 pop(1, forward) 133 } 134 else -> unrecognized(i) 135 } 136 is MultiANewArrayInsnNode -> { 137 pop(i.dims, forward) 138 push(1, forward) 139 } 140 is LdcInsnNode -> { 141 when (i.cst) { 142 is Double -> push(2, forward) 143 is Long -> push(2, forward) 144 else -> push(1, forward) 145 } 146 } 147 else -> when (i.opcode) { 148 ASTORE -> { 149 if (depth == 0) return i // stored atomic field ref 150 pop(1, forward) // stored something else 151 } 152 NOP -> { /* nop */ } 153 GOTO, TABLESWITCH, LOOKUPSWITCH, ATHROW, IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL, 154 IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE -> { 155 abort("Unsupported branching/control within atomic operation") 156 } 157 IRETURN, FRETURN, ARETURN, RETURN, LRETURN, DRETURN -> { 158 abort("Unsupported return within atomic operation") 159 } 160 ACONST_NULL -> { 161 push(1, forward) 162 } 163 ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, BIPUSH, SIPUSH -> { 164 push(1, forward) 165 } 166 LCONST_0, LCONST_1 -> { 167 push(2, forward) 168 } 169 FCONST_0, FCONST_1, FCONST_2 -> { 170 push(1, forward) 171 } 172 DCONST_0, DCONST_1 -> { 173 push(2, forward) 174 } 175 ILOAD, FLOAD, ALOAD -> { 176 push(1, forward) 177 } 178 LLOAD, DLOAD -> { 179 push(2, forward) 180 } 181 IALOAD, BALOAD, CALOAD, SALOAD -> { 182 pop(2, forward) 183 push(1, forward) 184 } 185 LALOAD, D2L -> { 186 pop(2, forward) 187 push(2, forward) 188 } 189 FALOAD -> { 190 pop(2, forward) 191 push(1, forward) 192 } 193 DALOAD, L2D -> { 194 pop(2, forward) 195 push(2, forward) 196 } 197 AALOAD -> { 198 pop(1, forward) 199 push(1, forward) 200 } 201 ISTORE, FSTORE -> { 202 pop(1, forward) 203 } 204 LSTORE, DSTORE -> { 205 pop(2, forward) 206 } 207 IASTORE, BASTORE, CASTORE, SASTORE, FASTORE, AASTORE -> { 208 pop(3, forward) 209 } 210 LASTORE, DASTORE -> { 211 pop(4, forward) 212 } 213 POP, MONITORENTER, MONITOREXIT -> { 214 pop(1, forward) 215 } 216 POP2 -> { 217 pop(2, forward) 218 } 219 DUP -> { 220 pop(1, forward) 221 push(2, forward) 222 } 223 DUP_X1 -> { 224 if (depth <= 1) unsupported() 225 push(1, forward) 226 } 227 DUP_X2 -> { 228 if (depth <= 2) unsupported() 229 push(1, forward) 230 } 231 DUP2 -> { 232 pop(2, forward) 233 push(4, forward) 234 } 235 DUP2_X1 -> { 236 if (depth <= 2) unsupported() 237 push(2, forward) 238 } 239 DUP2_X2 -> { 240 if (depth <= 3) unsupported() 241 push(2, forward) 242 } 243 SWAP -> { 244 if (depth <= 1) unsupported() 245 } 246 IADD, ISUB, IMUL, IDIV, IREM, IAND, IOR, IXOR, ISHL, ISHR, IUSHR, L2I, D2I, FCMPL, FCMPG -> { 247 pop(2, forward) 248 push(1, forward) 249 } 250 LADD, LSUB, LMUL, LDIV, LREM, LAND, LOR, LXOR -> { 251 pop(4, forward) 252 push(2, forward) 253 } 254 FADD, FSUB, FMUL, FDIV, FREM, L2F, D2F -> { 255 pop(2, forward) 256 push(1, forward) 257 } 258 DADD, DSUB, DMUL, DDIV, DREM -> { 259 pop(4, forward) 260 push(2, forward) 261 } 262 LSHL, LSHR, LUSHR -> { 263 pop(3, forward) 264 push(2, forward) 265 } 266 INEG, FNEG, I2B, I2C, I2S, IINC -> { 267 pop(1, forward) 268 push(1, forward) 269 } 270 LNEG, DNEG -> { 271 pop(2, forward) 272 push(2, forward) 273 } 274 I2L, F2L -> { 275 pop(1, forward) 276 push(2, forward) 277 } 278 I2F -> { 279 pop(1, forward) 280 push(1, forward) 281 } 282 I2D, F2D -> { 283 pop(1, forward) 284 push(2, forward) 285 } 286 F2I, ARRAYLENGTH, INSTANCEOF -> { 287 pop(1, forward) 288 push(1, forward) 289 } 290 LCMP, DCMPL, DCMPG -> { 291 pop(4, forward) 292 push(1, forward) 293 } 294 NEW -> { 295 push(1, forward) 296 } 297 NEWARRAY, ANEWARRAY -> { 298 pop(1, forward) 299 push(1, forward) 300 } 301 CHECKCAST -> { 302 /* nop for our needs */ 303 } 304 else -> unrecognized(i) 305 } 306 } 307 return null 308 } 309 popDescnull310 private fun popDesc(desc: String, forward: Boolean) { 311 when (desc[0]) { 312 '(' -> { 313 val types = Type.getArgumentTypes(desc) 314 pop(types.indices.sumBy { types[it].size }, forward) 315 } 316 'J', 'D' -> pop(2, forward) 317 else -> pop(1, forward) 318 } 319 } 320 pushDescnull321 private fun pushDesc(desc: String, forward: Boolean) { 322 val index = if (desc[0] == '(') desc.indexOf(')') + 1 else 0 323 when (desc[index]) { 324 'V' -> return 325 'Z', 'C', 'B', 'S', 'I', 'F', '[', 'L' -> { 326 push(1, forward) 327 } 328 'J', 'D' -> { 329 push(2, forward) 330 } 331 } 332 } 333 }