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 }