1 /* <lambda>null2 * 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.mozilla.javascript.* 8 import org.mozilla.javascript.ast.* 9 import java.io.File 10 import java.io.FileReader 11 import org.mozilla.javascript.Token 12 import java.util.regex.* 13 14 private const val ATOMIC_CONSTRUCTOR = """(atomic\$(ref|int|long|boolean)\$|Atomic(Ref|Int|Long|Boolean))""" 15 private const val ATOMIC_ARRAY_CONSTRUCTOR = """Atomic(Ref|Int|Long|Boolean)Array\$(ref|int|long|boolean|ofNulls)""" 16 private const val MANGLED_VALUE_PROP = "kotlinx\$atomicfu\$value" 17 18 private const val RECEIVER = "\$receiver" 19 private const val SCOPE = "scope" 20 private const val FACTORY = "factory" 21 private const val REQUIRE = "require" 22 private const val KOTLINX_ATOMICFU = "'kotlinx-atomicfu'" 23 private const val KOTLINX_ATOMICFU_PACKAGE = "kotlinx.atomicfu" 24 private const val KOTLIN_TYPE_CHECK = "Kotlin.isType" 25 private const val ATOMIC_REF = "AtomicRef" 26 private const val MODULE_KOTLINX_ATOMICFU = "\\\$module\\\$kotlinx_atomicfu" 27 private const val ARRAY = "Array" 28 private const val FILL = "fill" 29 private const val GET_ELEMENT = "get\\\$atomicfu" 30 private const val LOCKS = "locks" 31 private const val REENTRANT_LOCK_ATOMICFU_SINGLETON = "$LOCKS.reentrantLock\\\$atomicfu" 32 33 private val MANGLE_VALUE_REGEX = Regex(".${Pattern.quote(MANGLED_VALUE_PROP)}") 34 // matches index until the first occurence of ')', parenthesised index expressions not supported 35 private val ARRAY_GET_ELEMENT_REGEX = Regex(".$GET_ELEMENT\\((.*)\\)") 36 37 class AtomicFUTransformerJS( 38 inputDir: File, 39 outputDir: File 40 ) : AtomicFUTransformerBase(inputDir, outputDir) { 41 private val atomicConstructors = mutableSetOf<String>() 42 private val atomicArrayConstructors = mutableMapOf<String, String?>() 43 44 override fun transform() { 45 info("Transforming to $outputDir") 46 inputDir.walk().filter { it.isFile }.forEach { file -> 47 val outBytes = if (file.isJsFile()) { 48 println("Transforming file: ${file.canonicalPath}") 49 transformFile(file) 50 } else { 51 file.readBytes() 52 } 53 file.toOutputFile().mkdirsAndWrite(outBytes) 54 } 55 } 56 57 private fun File.isJsFile() = 58 name.endsWith(".js") && !name.endsWith(".meta.js") 59 60 private fun transformFile(file: File): ByteArray { 61 val p = Parser(CompilerEnvirons()) 62 val root = p.parse(FileReader(file), null, 0) 63 root.visit(DependencyEraser()) 64 root.visit(AtomicConstructorDetector()) 65 root.visit(TransformVisitor()) 66 root.visit(AtomicOperationsInliner()) 67 return root.eraseGetValue().toByteArray() 68 } 69 70 // erase getting value of atomic field 71 private fun AstNode.eraseGetValue(): String { 72 var res = this.toSource() 73 val primitiveGetValue = MANGLE_VALUE_REGEX 74 val arrayGetElement = ARRAY_GET_ELEMENT_REGEX 75 while (res.contains(arrayGetElement)) { 76 res = res.replace(arrayGetElement) { matchResult -> 77 val greedyToLastClosingParen = matchResult.groupValues[1] 78 var balance = 1 79 var indexEndPos = 0 80 for (i in 0 until greedyToLastClosingParen.length) { 81 val c = greedyToLastClosingParen[i] 82 if (c == '(') balance++ 83 if (c == ')') balance-- 84 if (balance == 0) { 85 indexEndPos = i 86 break 87 } 88 } 89 val closingParen = indexEndPos == greedyToLastClosingParen.lastIndex 90 if (balance == 1) { 91 "[$greedyToLastClosingParen]" 92 } else { 93 "[${greedyToLastClosingParen.substring(0, indexEndPos)}]${greedyToLastClosingParen.substring(indexEndPos + 1)}${if (!closingParen) ")" else ""}" 94 } 95 } 96 } 97 return res.replace(primitiveGetValue) { "" } 98 } 99 100 inner class DependencyEraser : NodeVisitor { 101 private fun isAtomicfuDependency(node: AstNode) = 102 (node.type == Token.STRING && node.toSource() == KOTLINX_ATOMICFU) 103 104 private fun isAtomicfuModule(node: AstNode) = 105 (node.type == Token.NAME && node.toSource().matches(Regex(MODULE_KOTLINX_ATOMICFU))) 106 107 override fun visit(node: AstNode): Boolean { 108 when (node.type) { 109 Token.ARRAYLIT -> { 110 // erasing 'kotlinx-atomicfu' from the list of defined dependencies 111 val elements = (node as ArrayLiteral).elements as MutableList 112 val it = elements.listIterator() 113 while (it.hasNext()) { 114 val arg = it.next() 115 if (isAtomicfuDependency(arg)) { 116 it.remove() 117 } 118 } 119 } 120 Token.FUNCTION -> { 121 // erasing 'kotlinx-atomicfu' module passed as parameter 122 if (node is FunctionNode) { 123 val it = node.params.listIterator() 124 while (it.hasNext()) { 125 if (isAtomicfuModule(it.next())) { 126 it.remove() 127 } 128 } 129 } 130 } 131 Token.CALL -> { 132 if (node is FunctionCall && node.target.toSource() == FACTORY) { 133 val it = node.arguments.listIterator() 134 while (it.hasNext()) { 135 val arg = it.next() 136 when (arg.type) { 137 Token.GETELEM -> { 138 // erasing 'kotlinx-atomicfu' dependency as factory argument 139 if (isAtomicfuDependency((arg as ElementGet).element)) { 140 it.remove() 141 } 142 } 143 Token.CALL -> { 144 // erasing require of 'kotlinx-atomicfu' dependency 145 if ((arg as FunctionCall).target.toSource() == REQUIRE) { 146 if (isAtomicfuDependency(arg.arguments[0])) { 147 it.remove() 148 } 149 } 150 } 151 } 152 } 153 } 154 } 155 Token.GETELEM -> { 156 if (isAtomicfuDependency((node as ElementGet).element)) { 157 val enclosingNode = node.parent 158 // erasing the check whether 'kotlinx-atomicfu' is defined 159 if (enclosingNode.type == Token.TYPEOF) { 160 if (enclosingNode.parent.parent.type == Token.IF) { 161 val ifStatement = enclosingNode.parent.parent as IfStatement 162 val falseKeyword = KeywordLiteral() 163 falseKeyword.type = Token.FALSE 164 ifStatement.condition = falseKeyword 165 val oneLineBlock = Block() 166 oneLineBlock.addStatement(EmptyLine()) 167 ifStatement.thenPart = oneLineBlock 168 } 169 } 170 171 } 172 } 173 Token.BLOCK -> { 174 // erasing importsForInline for 'kotlinx-atomicfu' 175 for (stmt in node) { 176 if (stmt is ExpressionStatement) { 177 val expr = stmt.expression 178 if (expr is Assignment && expr.left is ElementGet) { 179 if (isAtomicfuDependency((expr.left as ElementGet).element)) { 180 node.replaceChild(stmt, EmptyLine()) 181 } 182 } 183 } 184 } 185 } 186 } 187 return true 188 } 189 } 190 191 inner class AtomicConstructorDetector : NodeVisitor { 192 private fun kotlinxAtomicfuModuleName(name: String) = "$MODULE_KOTLINX_ATOMICFU.$KOTLINX_ATOMICFU_PACKAGE.$name" 193 194 override fun visit(node: AstNode?): Boolean { 195 if (node is Block) { 196 for (stmt in node) { 197 if (stmt is VariableDeclaration) { 198 val varInit = stmt.variables[0] as VariableInitializer 199 if (varInit.initializer is PropertyGet) { 200 val initializer = varInit.initializer.toSource() 201 if (initializer.matches(Regex(kotlinxAtomicfuModuleName(ATOMIC_CONSTRUCTOR)))) { 202 atomicConstructors.add(varInit.target.toSource()) 203 node.replaceChild(stmt, EmptyLine()) 204 } else if (initializer.matches(Regex(kotlinxAtomicfuModuleName(LOCKS)))){ 205 node.replaceChild(stmt, EmptyLine()) 206 } 207 } 208 } 209 } 210 } 211 if (node is VariableInitializer && node.initializer is PropertyGet) { 212 val initializer = node.initializer.toSource() 213 if (initializer.matches(Regex(REENTRANT_LOCK_ATOMICFU_SINGLETON))) { 214 node.initializer = null 215 } 216 if (initializer.matches(Regex(kotlinxAtomicfuModuleName(ATOMIC_CONSTRUCTOR)))) { 217 atomicConstructors.add(node.target.toSource()) 218 node.initializer = null 219 } 220 if (initializer.matches(Regex(kotlinxAtomicfuModuleName(ATOMIC_ARRAY_CONSTRUCTOR)))) { 221 val initialValue = when (initializer.substringAfterLast('$')) { 222 "int" -> "0" 223 "long" -> "0" 224 "boolean" -> "false" 225 else -> null 226 } 227 atomicArrayConstructors[node.target.toSource()] = initialValue 228 node.initializer = null 229 } 230 return false 231 } else if (node is Assignment && node.right is PropertyGet) { 232 val initializer = node.right.toSource() 233 if (initializer.matches(Regex(REENTRANT_LOCK_ATOMICFU_SINGLETON))) { 234 node.right = Name().also { it.identifier = "null" } 235 return false 236 } 237 } 238 return true 239 } 240 } 241 242 inner class TransformVisitor : NodeVisitor { 243 override fun visit(node: AstNode): Boolean { 244 // remove atomic constructors from classes fields 245 if (node is FunctionCall) { 246 val functionName = node.target.toSource() 247 if (atomicConstructors.contains(functionName)) { 248 if (node.parent is Assignment) { 249 val valueNode = node.arguments[0] 250 (node.parent as InfixExpression).right = valueNode 251 } 252 return true 253 } else if (atomicArrayConstructors.contains(functionName)) { 254 val arrayConstructor = Name() 255 arrayConstructor.identifier = ARRAY 256 node.target = arrayConstructor 257 atomicArrayConstructors[functionName]?.let { 258 val arrayConsCall = FunctionCall() 259 arrayConsCall.target = node.target 260 arrayConsCall.arguments = node.arguments 261 val target = PropertyGet() 262 val fill = Name() 263 fill.identifier = FILL 264 target.target = arrayConsCall 265 target.property = fill 266 node.target = target 267 val initialValue = Name() 268 initialValue.identifier = it 269 node.arguments = listOf(initialValue) 270 } 271 return true 272 } else if (node.target is PropertyGet) { 273 if ((node.target as PropertyGet).target is FunctionCall) { 274 val atomicOperationTarget = node.target as PropertyGet 275 val funcCall = atomicOperationTarget.target as FunctionCall 276 if (funcCall.target is PropertyGet) { 277 val getterCall = (funcCall.target as PropertyGet).property 278 if (Regex(GET_ELEMENT).matches(getterCall.toSource())) { 279 val getter = getArrayElement(funcCall) 280 atomicOperationTarget.target = getter 281 } 282 } 283 } 284 } 285 } 286 // remove value property call 287 if (node is PropertyGet) { 288 if (node.property.toSource() == MANGLED_VALUE_PROP) { 289 // check whether atomic operation is performed on the type casted atomic field 290 node.target.eraseAtomicFieldFromUncheckedCast()?.let { node.target = it } 291 // A.a.value 292 if (node.target.type == Token.GETPROP) { 293 val clearField = node.target as PropertyGet 294 val targetNode = clearField.target 295 val clearProperety = clearField.property 296 node.setLeftAndRight(targetNode, clearProperety) 297 } 298 // other cases with $receiver.kotlinx$atomicfu$value in inline functions 299 else if (node.target.toSource() == RECEIVER) { 300 val rr = ReceiverResolver() 301 node.enclosingFunction.visit(rr) 302 rr.receiver?.let { node.target = it } 303 } 304 } 305 } 306 return true 307 } 308 309 private fun getArrayElement(getterCall: FunctionCall): AstNode { 310 val index = getterCall.arguments[0] 311 val arrayField = (getterCall.target as PropertyGet).target 312 // whether this field is static or not 313 val isStatic = arrayField !is PropertyGet 314 val arrName = if (isStatic) arrayField else (arrayField as PropertyGet).property 315 val getter = ElementGet(arrName, index) 316 return if (isStatic) { //intArr[index] 317 getter 318 } else { //A.intArr[0] 319 val call = PropertyGet() 320 call.target = (arrayField as PropertyGet).target 321 val name = Name() 322 name.identifier = getter.toSource() 323 call.property = name 324 call 325 } 326 } 327 } 328 329 330 // receiver data flow 331 inner class ReceiverResolver : NodeVisitor { 332 var receiver: AstNode? = null 333 override fun visit(node: AstNode): Boolean { 334 if (node is VariableInitializer) { 335 if (node.target.toSource() == RECEIVER) { 336 receiver = node.initializer 337 return false 338 } 339 } 340 return true 341 } 342 } 343 344 inner class AtomicOperationsInliner : NodeVisitor { 345 override fun visit(node: AstNode?): Boolean { 346 // inline atomic operations 347 if (node is FunctionCall) { 348 if (node.target is PropertyGet) { 349 val funcName = (node.target as PropertyGet).property 350 var field = (node.target as PropertyGet).target 351 if (field.toSource() == RECEIVER) { 352 val rr = ReceiverResolver() 353 node.enclosingFunction.visit(rr) 354 if (rr.receiver != null) { 355 field = rr.receiver 356 } 357 } 358 field.eraseAtomicFieldFromUncheckedCast()?.let { field = it } 359 val args = node.arguments 360 val inlined = node.inlineAtomicOperation(funcName.toSource(), field, args) 361 return !inlined 362 } 363 } 364 return true 365 } 366 } 367 368 private fun AstNode.eraseAtomicFieldFromUncheckedCast(): AstNode? { 369 if (this is ParenthesizedExpression && expression is ConditionalExpression) { 370 val testExpression = (expression as ConditionalExpression).testExpression 371 if (testExpression is FunctionCall && testExpression.target.toSource() == KOTLIN_TYPE_CHECK) { 372 // type check 373 val typeToCast = testExpression.arguments[1] 374 if ((typeToCast as Name).identifier == ATOMIC_REF) { 375 // unchecked type cast -> erase atomic field itself 376 return (testExpression.arguments[0] as Assignment).right 377 } 378 } 379 } 380 return null 381 } 382 383 private fun AstNode.isThisNode(): Boolean { 384 return when(this) { 385 is PropertyGet -> { 386 target.isThisNode() 387 } 388 is FunctionCall -> { 389 target.isThisNode() 390 } 391 else -> { 392 (this.type == Token.THIS) 393 } 394 } 395 } 396 397 private fun PropertyGet.resolvePropName(): String { 398 val target = this.target 399 return if (target is PropertyGet) { 400 "${target.resolvePropName()}.${property.toSource()}" 401 } else { 402 property.toSource() 403 } 404 } 405 406 private fun AstNode.scopedSource(): String { 407 if (this.isThisNode()) { 408 if (this is PropertyGet) { 409 val property = resolvePropName() 410 return "$SCOPE.$property" 411 } else if (this is FunctionCall && this.target is PropertyGet) { 412 // check that this function call is getting array element 413 if (this.target is PropertyGet) { 414 val funcName = (this.target as PropertyGet).property.toSource() 415 if (Regex(GET_ELEMENT).matches(funcName)) { 416 val property = (this.target as PropertyGet).resolvePropName() 417 return "$SCOPE.$property(${this.arguments[0].toSource()})" 418 } 419 } 420 } else if (this.type == Token.THIS) { 421 return SCOPE 422 } 423 } 424 return this.toSource() 425 } 426 427 private fun FunctionCall.inlineAtomicOperation( 428 funcName: String, 429 field: AstNode, 430 args: List<AstNode> 431 ): Boolean { 432 val f = field.scopedSource() 433 val code = when (funcName) { 434 "getAndSet\$atomicfu" -> { 435 val arg = args[0].toSource() 436 "(function($SCOPE) {var oldValue = $f; $f = $arg; return oldValue;})()" 437 } 438 "compareAndSet\$atomicfu" -> { 439 val expected = args[0].scopedSource() 440 val updated = args[1].scopedSource() 441 val equals = if (expected == "null") "==" else "===" 442 "(function($SCOPE) {return $f $equals $expected ? function() { $f = $updated; return true }() : false})()" 443 } 444 "getAndIncrement\$atomicfu" -> { 445 "(function($SCOPE) {return $f++;})()" 446 } 447 448 "getAndIncrement\$atomicfu\$long" -> { 449 "(function($SCOPE) {var oldValue = $f; $f = $f.inc(); return oldValue;})()" 450 } 451 452 "getAndDecrement\$atomicfu" -> { 453 "(function($SCOPE) {return $f--;})()" 454 } 455 456 "getAndDecrement\$atomicfu\$long" -> { 457 "(function($SCOPE) {var oldValue = $f; $f = $f.dec(); return oldValue;})()" 458 } 459 460 "getAndAdd\$atomicfu" -> { 461 val arg = args[0].scopedSource() 462 "(function($SCOPE) {var oldValue = $f; $f += $arg; return oldValue;})()" 463 } 464 465 "getAndAdd\$atomicfu\$long" -> { 466 val arg = args[0].scopedSource() 467 "(function($SCOPE) {var oldValue = $f; $f = $f.add($arg); return oldValue;})()" 468 } 469 470 "addAndGet\$atomicfu" -> { 471 val arg = args[0].scopedSource() 472 "(function($SCOPE) {$f += $arg; return $f;})()" 473 } 474 475 "addAndGet\$atomicfu\$long" -> { 476 val arg = args[0].scopedSource() 477 "(function($SCOPE) {$f = $f.add($arg); return $f;})()" 478 } 479 480 "incrementAndGet\$atomicfu" -> { 481 "(function($SCOPE) {return ++$f;})()" 482 } 483 484 "incrementAndGet\$atomicfu\$long" -> { 485 "(function($SCOPE) {return $f = $f.inc();})()" 486 } 487 488 "decrementAndGet\$atomicfu" -> { 489 "(function($SCOPE) {return --$f;})()" 490 } 491 492 "decrementAndGet\$atomicfu\$long" -> { 493 "(function($SCOPE) {return $f = $f.dec();})()" 494 } 495 else -> null 496 } 497 if (code != null) { 498 this.setImpl(code) 499 return true 500 } 501 return false 502 } 503 504 private fun FunctionCall.setImpl(code: String) { 505 val p = Parser(CompilerEnvirons()) 506 val node = p.parse(code, null, 0) 507 if (node.firstChild != null) { 508 val expr = (node.firstChild as ExpressionStatement).expression 509 this.target = (expr as FunctionCall).target 510 val thisNode = Parser(CompilerEnvirons()).parse("this", null, 0) 511 this.arguments = listOf((thisNode.firstChild as ExpressionStatement).expression) 512 } 513 } 514 } 515 516 private class EmptyLine: EmptyExpression() { toSourcenull517 override fun toSource(depth: Int) = "\n" 518 } 519 520 fun main(args: Array<String>) { 521 if (args.size !in 1..2) { 522 println("Usage: AtomicFUTransformerKt <dir> [<output>]") 523 return 524 } 525 val t = AtomicFUTransformerJS(File(args[0]), File(args[1])) 526 t.transform() 527 }