From 5119e1aae26d933d01479b2d637c38093f969ee0 Mon Sep 17 00:00:00 2001 From: mvicsokolova <82594708+mvicsokolova@users.noreply.github.com> Date: Thu, 17 Oct 2024 19:33:01 +0200 Subject: [PATCH] Remove JS Legacy Transformer and disable transformJs flag by default. (#478) Fixes #425 --- README.md | 4 +- .../api/atomicfu-gradle-plugin.api | 13 - .../plugin/gradle/AtomicFUGradlePlugin.kt | 123 +-- .../AtomicfuKotlinCompilerPluginInternal.kt | 3 +- .../api/atomicfu-transformer.api | 51 -- .../transformer/AtomicFUTransformerJS.kt | 728 ------------------ 6 files changed, 22 insertions(+), 900 deletions(-) delete mode 100644 atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformerJS.kt diff --git a/README.md b/README.md index c392b7cc..0ae7a29a 100644 --- a/README.md +++ b/README.md @@ -348,7 +348,8 @@ Here are the valid options: ### JS options -To turn off transformation for Kotlin/JS set option `transformJs` to `false`. +> Starting with version `0.26.0` `transformJs` flag does not take any effect and is disabled by default. +> Please ensure that this flag is not used in the atomicfu configuration of your project, you can safely remove it. Here are all available configuration options (with their defaults): ```groovy @@ -356,7 +357,6 @@ atomicfu { dependenciesVersion = '0.25.0' // set to null to turn-off auto dependencies transformJvm = true // set to false to turn off JVM transformation jvmVariant = "FU" // JVM transformation variant: FU,VH, or BOTH - transformJs = true // set to false to turn off JVM transformation } ``` diff --git a/atomicfu-gradle-plugin/api/atomicfu-gradle-plugin.api b/atomicfu-gradle-plugin/api/atomicfu-gradle-plugin.api index 2d1773f4..f70f4d3a 100644 --- a/atomicfu-gradle-plugin/api/atomicfu-gradle-plugin.api +++ b/atomicfu-gradle-plugin/api/atomicfu-gradle-plugin.api @@ -18,19 +18,6 @@ public final class kotlinx/atomicfu/plugin/gradle/AtomicFUPluginExtension { public final fun setVerbose (Z)V } -public abstract class kotlinx/atomicfu/plugin/gradle/AtomicFUTransformJsTask : org/gradle/api/internal/ConventionTask { - public field inputFiles Lorg/gradle/api/file/FileCollection; - public fun ()V - public abstract fun getDestinationDirectory ()Lorg/gradle/api/file/DirectoryProperty; - public final fun getInputFiles ()Lorg/gradle/api/file/FileCollection; - public final fun getOutputDir ()Ljava/io/File; - public final fun getVerbose ()Z - public final fun setInputFiles (Lorg/gradle/api/file/FileCollection;)V - public final fun setOutputDir (Ljava/io/File;)V - public final fun setVerbose (Z)V - public final fun transform ()V -} - public abstract class kotlinx/atomicfu/plugin/gradle/AtomicFUTransformTask : org/gradle/api/internal/ConventionTask { public field classPath Lorg/gradle/api/file/FileCollection; public field inputFiles Lorg/gradle/api/file/FileCollection; diff --git a/atomicfu-gradle-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/gradle/AtomicFUGradlePlugin.kt b/atomicfu-gradle-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/gradle/AtomicFUGradlePlugin.kt index 9a046dea..ec375def 100644 --- a/atomicfu-gradle-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/gradle/AtomicFUGradlePlugin.kt +++ b/atomicfu-gradle-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/gradle/AtomicFUGradlePlugin.kt @@ -17,8 +17,6 @@ import org.gradle.util.* import org.jetbrains.kotlin.gradle.dsl.* import org.jetbrains.kotlin.gradle.dsl.KotlinCompile import org.jetbrains.kotlin.gradle.plugin.* -import org.jetbrains.kotlin.gradle.targets.js.* -import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget import org.jetbrains.kotlin.gradle.tasks.* import java.io.* import java.util.* @@ -99,7 +97,7 @@ private fun Project.configureDependencies() { } withPluginWhenEvaluatedDependencies("org.jetbrains.kotlin.js") { version -> dependencies.add( - if (config.transformJs) COMPILE_ONLY_CONFIGURATION else IMPLEMENTATION_CONFIGURATION, + if (needsJsIrTransformation(KotlinPlatformType.js)) COMPILE_ONLY_CONFIGURATION else IMPLEMENTATION_CONFIGURATION, getAtomicfuDependencyNotation(Platform.JS, version) ) dependencies.add(TEST_IMPLEMENTATION_CONFIGURATION, getAtomicfuDependencyNotation(Platform.JS, version)) @@ -176,26 +174,22 @@ private fun String.toBooleanStrict(): Boolean = when (this) { else -> throw IllegalArgumentException("The string doesn't represent a boolean value: $this") } -internal fun Project.needsJsIrTransformation(target: KotlinTarget): Boolean = +internal fun Project.needsJsIrTransformation(targetPlatformType: KotlinPlatformType): Boolean = (rootProject.getBooleanProperty(ENABLE_JS_IR_TRANSFORMATION) || rootProject.getBooleanProperty(ENABLE_JS_IR_TRANSFORMATION_LEGACY)) - && target.isJsIrTarget() + && targetPlatformType == KotlinPlatformType.js -internal fun Project.needsJvmIrTransformation(target: KotlinTarget): Boolean = +internal fun Project.needsJvmIrTransformation(targetPlatformType: KotlinPlatformType): Boolean = rootProject.getBooleanProperty(ENABLE_JVM_IR_TRANSFORMATION) && - (target.platformType == KotlinPlatformType.jvm || target.platformType == KotlinPlatformType.androidJvm) + (targetPlatformType == KotlinPlatformType.jvm || targetPlatformType == KotlinPlatformType.androidJvm) -internal fun Project.needsNativeIrTransformation(target: KotlinTarget): Boolean = +internal fun Project.needsNativeIrTransformation(targetPlatformType: KotlinPlatformType): Boolean = rootProject.getBooleanProperty(ENABLE_NATIVE_IR_TRANSFORMATION) && - (target.platformType == KotlinPlatformType.native) - -private fun KotlinTarget.isJsIrTarget() = - (this is KotlinJsTarget && this.irTarget != null) || - (this is KotlinJsIrTarget && this.platformType != KotlinPlatformType.wasm) + (targetPlatformType == KotlinPlatformType.native) private fun Project.isTransitiveAtomicfuDependencyRequired(target: KotlinTarget): Boolean { val platformType = target.platformType return !config.transformJvm && (platformType == KotlinPlatformType.jvm || platformType == KotlinPlatformType.androidJvm) || - (!config.transformJs && platformType == KotlinPlatformType.js) || + (!needsJsIrTransformation(platformType) && platformType == KotlinPlatformType.js) || platformType == KotlinPlatformType.wasm || // Always add the transitive atomicfu dependency for native targets, see #379 platformType == KotlinPlatformType.native @@ -206,7 +200,7 @@ private fun Project.isTransitiveAtomicfuDependencyRequired(target: KotlinTarget) private fun Project.addJsCompilerPluginRuntimeDependency() { if (isCompilerPluginAvailable()) { withKotlinTargets { target -> - if (target.isJsIrTarget()) { + if (needsJsIrTransformation(target.platformType)) { target.compilations.forEach { kotlinCompilation -> kotlinCompilation.dependencies { if (getKotlinVersion().atLeast(1, 7, 10)) { @@ -287,9 +281,6 @@ private fun Project.configureTasks() { withPluginWhenEvaluated("kotlin") { if (config.transformJvm) configureJvmTransformation() } - withPluginWhenEvaluated("org.jetbrains.kotlin.js") { - if (config.transformJs) configureJsTransformation() - } withPluginWhenEvaluated("kotlin-multiplatform") { configureMultiplatformTransformation() } @@ -298,26 +289,20 @@ private fun Project.configureTasks() { private fun Project.configureJvmTransformation() { if (kotlinExtension is KotlinJvmProjectExtension || kotlinExtension is KotlinAndroidProjectExtension) { val target = (kotlinExtension as KotlinSingleTargetExtension<*>).target - if (!needsJvmIrTransformation(target)) { + if (!needsJvmIrTransformation(target.platformType)) { configureTransformationForTarget(target) } } } -private fun Project.configureJsTransformation() { - val target = (kotlinExtension as KotlinJsProjectExtension).js() - if (!needsJsIrTransformation(target)) { - configureTransformationForTarget(target) - } -} - private fun Project.configureMultiplatformTransformation() = withKotlinTargets { target -> - // Skip transformation for common, native and wasm targets or in case IR transformation by the compiler plugin is enabled (for JVM or JS targets) + // Skip transformation for common, native and wasm and js targets or in case IR transformation by the compiler plugin is enabled (for JVM or JS targets) if (target.platformType == KotlinPlatformType.common || target.platformType == KotlinPlatformType.native || target.platformType == KotlinPlatformType.wasm || - needsJvmIrTransformation(target) || needsJsIrTransformation(target) + target.platformType == KotlinPlatformType.js || + needsJvmIrTransformation(target.platformType) || needsJsIrTransformation(target.platformType) ) { return@withKotlinTargets } @@ -367,21 +352,6 @@ private fun Project.configureTransformationForTarget(target: KotlinTarget) { } } else null } - KotlinPlatformType.js -> { - // create transformation task only if transformation is required and JS IR compiler transformation is not enabled - if (config.transformJs && !needsJsIrTransformation(target)) { - project.registerJsTransformTask(compilation) - .configureJsTask( - compilation.compileAllTaskName, - transformedClassesDir, - originalClassesDirs, - config - ) - .also { - compilation.defaultSourceSet.kotlin.compiledBy(it, AtomicFUTransformJsTask::destinationDirectory) - } - } else null - } else -> error("Unsupported transformation platform '${target.platformType}'") } if (transformTask != null) { @@ -427,12 +397,6 @@ private fun Project.registerJvmTransformTask(compilation: KotlinCompilation<*>): AtomicFUTransformTask::class.java ) -private fun Project.registerJsTransformTask(compilation: KotlinCompilation<*>): TaskProvider = - tasks.register( - "transform${compilation.target.name.capitalize()}${compilation.name.capitalize()}Atomicfu", - AtomicFUTransformJsTask::class.java - ) - private fun TaskProvider.configureJvmTask( classpath: FileCollection, classesTaskName: String, @@ -451,21 +415,6 @@ private fun TaskProvider.configureJvmTask( } } -private fun TaskProvider.configureJsTask( - classesTaskName: String, - transformedClassesDir: Provider, - originalClassesDir: FileCollection, - config: AtomicFUPluginExtension -): TaskProvider = - apply { - configure { - it.dependsOn(classesTaskName) - it.inputFiles = originalClassesDir - it.destinationDirectory.value(transformedClassesDir) - it.verbose = config.verbose - } - } - private fun Jar.setupJarManifest(multiRelease: Boolean) { if (multiRelease) { manifest.attributes.apply { @@ -477,7 +426,11 @@ private fun Jar.setupJarManifest(multiRelease: Boolean) { class AtomicFUPluginExtension(pluginVersion: String?) { var dependenciesVersion = pluginVersion var transformJvm = true - var transformJs = true + + @Deprecated("This flag was previously used to enable or disable kotlinx-atomicfu transformations of the final *.js files produced by the JS Legacy backend. " + + "Starting from version 0.26.0 of `kotlinx-atomicfu`, it does not take any effect, is disabled by default and will be removed in the next release. " + + "Please ensure that this flag is not used in the atomicfu configuration of your project, you can safely remove it.") + var transformJs = false var jvmVariant: String = "FU" var verbose: Boolean = false } @@ -529,43 +482,3 @@ abstract class AtomicFUTransformTask : ConventionTask() { } } } - -@CacheableTask -abstract class AtomicFUTransformJsTask : ConventionTask() { - - @get:Inject - internal abstract val providerFactory: ProviderFactory - - @get:Inject - internal abstract val projectLayout: ProjectLayout - - @PathSensitive(PathSensitivity.RELATIVE) - @InputFiles - lateinit var inputFiles: FileCollection - - @Suppress("unused") - @Deprecated( - message = "Replaced with 'destinationDirectory'", - replaceWith = ReplaceWith("destinationDirectory") - ) - @get:Internal - var outputDir: File - get() = destinationDirectory.get().asFile - set(value) { destinationDirectory.value(projectLayout.dir(providerFactory.provider { value })) } - - @get:OutputDirectory - abstract val destinationDirectory: DirectoryProperty - - @Input - var verbose = false - - @TaskAction - fun transform() { - inputFiles.files.forEach { inputDir -> - AtomicFUTransformerJS(inputDir, destinationDirectory.get().asFile).let { t -> - t.verbose = verbose - t.transform() - } - } - } -} diff --git a/atomicfu-gradle-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/gradle/AtomicfuKotlinCompilerPluginInternal.kt b/atomicfu-gradle-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/gradle/AtomicfuKotlinCompilerPluginInternal.kt index 178b7c98..0378b36c 100644 --- a/atomicfu-gradle-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/gradle/AtomicfuKotlinCompilerPluginInternal.kt +++ b/atomicfu-gradle-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/gradle/AtomicfuKotlinCompilerPluginInternal.kt @@ -25,8 +25,9 @@ internal class AtomicfuKotlinCompilerPluginInternal : KotlinCompilerPluginSuppor override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean { val target = kotlinCompilation.target + val targetPlatformType = target.platformType val project = target.project - return project.needsJvmIrTransformation(target) || project.needsJsIrTransformation(target) || project.needsNativeIrTransformation(target) + return project.needsJvmIrTransformation(targetPlatformType) || project.needsJsIrTransformation(targetPlatformType) || project.needsNativeIrTransformation(targetPlatformType) } override fun applyToCompilation( diff --git a/atomicfu-transformer/api/atomicfu-transformer.api b/atomicfu-transformer/api/atomicfu-transformer.api index 0b2b7912..c4faee91 100644 --- a/atomicfu-transformer/api/atomicfu-transformer.api +++ b/atomicfu-transformer/api/atomicfu-transformer.api @@ -84,57 +84,6 @@ public final class kotlinx/atomicfu/transformer/AtomicFUTransformerBase$SourceIn public fun toString ()Ljava/lang/String; } -public final class kotlinx/atomicfu/transformer/AtomicFUTransformerJS : kotlinx/atomicfu/transformer/AtomicFUTransformerBase { - public fun (Ljava/io/File;Ljava/io/File;)V - public fun transform ()V -} - -public final class kotlinx/atomicfu/transformer/AtomicFUTransformerJS$AtomicConstructorDetector : org/mozilla/javascript/ast/NodeVisitor { - public fun (Lkotlinx/atomicfu/transformer/AtomicFUTransformerJS;)V - public fun visit (Lorg/mozilla/javascript/ast/AstNode;)Z -} - -public final class kotlinx/atomicfu/transformer/AtomicFUTransformerJS$AtomicOperationsInliner : org/mozilla/javascript/ast/NodeVisitor { - public fun (Lkotlinx/atomicfu/transformer/AtomicFUTransformerJS;)V - public fun visit (Lorg/mozilla/javascript/ast/AstNode;)Z -} - -public final class kotlinx/atomicfu/transformer/AtomicFUTransformerJS$DelegatedPropertyAccessorsVisitor : org/mozilla/javascript/ast/NodeVisitor { - public fun (Lkotlinx/atomicfu/transformer/AtomicFUTransformerJS;)V - public fun visit (Lorg/mozilla/javascript/ast/AstNode;)Z -} - -public final class kotlinx/atomicfu/transformer/AtomicFUTransformerJS$DependencyEraser : org/mozilla/javascript/ast/NodeVisitor { - public fun (Lkotlinx/atomicfu/transformer/AtomicFUTransformerJS;)V - public fun visit (Lorg/mozilla/javascript/ast/AstNode;)Z -} - -public final class kotlinx/atomicfu/transformer/AtomicFUTransformerJS$FieldDelegatesVisitor : org/mozilla/javascript/ast/NodeVisitor { - public fun (Lkotlinx/atomicfu/transformer/AtomicFUTransformerJS;)V - public fun visit (Lorg/mozilla/javascript/ast/AstNode;)Z -} - -public final class kotlinx/atomicfu/transformer/AtomicFUTransformerJS$ReceiverResolver : org/mozilla/javascript/ast/NodeVisitor { - public fun (Lkotlinx/atomicfu/transformer/AtomicFUTransformerJS;Ljava/lang/String;)V - public final fun getReceiver ()Lorg/mozilla/javascript/ast/AstNode; - public final fun setReceiver (Lorg/mozilla/javascript/ast/AstNode;)V - public fun visit (Lorg/mozilla/javascript/ast/AstNode;)Z -} - -public final class kotlinx/atomicfu/transformer/AtomicFUTransformerJS$TopLevelDelegatedFieldsAccessorVisitor : org/mozilla/javascript/ast/NodeVisitor { - public fun (Lkotlinx/atomicfu/transformer/AtomicFUTransformerJS;)V - public fun visit (Lorg/mozilla/javascript/ast/AstNode;)Z -} - -public final class kotlinx/atomicfu/transformer/AtomicFUTransformerJS$TransformVisitor : org/mozilla/javascript/ast/NodeVisitor { - public fun (Lkotlinx/atomicfu/transformer/AtomicFUTransformerJS;)V - public fun visit (Lorg/mozilla/javascript/ast/AstNode;)Z -} - -public final class kotlinx/atomicfu/transformer/AtomicFUTransformerJSKt { - public static final fun main ([Ljava/lang/String;)V -} - public final class kotlinx/atomicfu/transformer/AtomicFUTransformerKt { public static final fun main ([Ljava/lang/String;)V } diff --git a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformerJS.kt b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformerJS.kt deleted file mode 100644 index de8f94a7..00000000 --- a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformerJS.kt +++ /dev/null @@ -1,728 +0,0 @@ -/* - * 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.mozilla.javascript.* -import org.mozilla.javascript.ast.* -import java.io.File -import java.io.FileReader -import org.mozilla.javascript.Token -import java.util.regex.* - -private const val ATOMIC_CONSTRUCTOR = """(atomic\$(ref|int|long|boolean)\$|Atomic(Ref|Int|Long|Boolean))""" -private const val ATOMIC_CONSTRUCTOR_BINARY_COMPATIBILITY = """(atomic\$(ref|int|long|boolean)\$1)""" // mangled names for declarations left for binary compatibility -private const val ATOMIC_ARRAY_CONSTRUCTOR = """(atomicfu)\$(Atomic(Ref|Int|Long|Boolean)Array)\$(ref|int|long|boolean|ofNulls)""" -private const val MANGLED_VALUE_PROP = "kotlinx\$atomicfu\$value" - -private const val TRACE_CONSTRUCTOR = "atomicfu\\\$Trace" -private const val TRACE_BASE_CLASS = "atomicfu\\\$TraceBase" -private const val TRACE_APPEND = """(atomicfu)\$(Trace)\$(append)\$([1234])""" // [1234] is the number of arguments in the append overload -private const val TRACE_NAMED = "atomicfu\\\$Trace\\\$named" -private const val TRACE_FORMAT = "TraceFormat" -private const val TRACE_FORMAT_CONSTRUCTOR = "atomicfu\\\$$TRACE_FORMAT" -private const val TRACE_FORMAT_FORMAT = "atomicfu\\\$$TRACE_FORMAT\\\$format" - -private const val RECEIVER = """(\$(receiver)(_\d+)?)""" -private const val SCOPE = "scope" -private const val FACTORY = "factory" -private const val REQUIRE = "require" -private const val PROTOTYPE = "prototype" -private const val KOTLINX_ATOMICFU = "'kotlinx-atomicfu'" -private const val KOTLINX_ATOMICFU_PACKAGE = "kotlinx.atomicfu" -private const val KOTLIN_TYPE_CHECK = "Kotlin.isType" -private const val ATOMIC_REF = "AtomicRef" -private const val MODULE_KOTLINX_ATOMICFU = "\\\$module\\\$kotlinx_atomicfu" -private const val ARRAY = "Array" -private const val FILL = "fill" -private const val GET_ELEMENT = "atomicfu\\\$get" -private const val ARRAY_SIZE = "atomicfu\$size" -private const val LENGTH = "length" -private const val LOCKS = "locks" -private const val REENTRANT_LOCK_ATOMICFU_SINGLETON = "$LOCKS.atomicfu\\\$reentrantLock" - - -private val MANGLE_VALUE_REGEX = Regex(".${Pattern.quote(MANGLED_VALUE_PROP)}") -// matches index until the first occurence of ')', parenthesised index expressions not supported -private val ARRAY_GET_ELEMENT_REGEX = Regex(".$GET_ELEMENT\\((.*)\\)") - -class AtomicFUTransformerJS( - inputDir: File, - outputDir: File -) : AtomicFUTransformerBase(inputDir, outputDir) { - private val atomicConstructors = mutableSetOf() - private val delegateToOriginalAtomicField = mutableMapOf() - private val topLevelDelegatedFieldAccessorToOriginalField = mutableMapOf() - private val atomicArrayConstructors = mutableMapOf() - private val traceConstructors = mutableSetOf() - private val traceFormatObjects = mutableSetOf() - - override fun transform() { - info("Transforming to $outputDir") - inputDir.walk().filter { it.isFile }.forEach { file -> - val outBytes = if (file.isJsFile()) { - println("Transforming file: ${file.canonicalPath}") - transformFile(file) - } else { - file.readBytes() - } - file.toOutputFile().mkdirsAndWrite(outBytes) - } - } - - private fun File.isJsFile() = - name.endsWith(".js") && !name.endsWith(".meta.js") - - private fun transformFile(file: File): ByteArray { - val p = Parser(CompilerEnvirons()) - val root = p.parse(FileReader(file), null, 0) - root.visit(DependencyEraser()) - root.visit(AtomicConstructorDetector()) - root.visit(FieldDelegatesVisitor()) - root.visit(DelegatedPropertyAccessorsVisitor()) - root.visit(TopLevelDelegatedFieldsAccessorVisitor()) - root.visit(TransformVisitor()) - root.visit(AtomicOperationsInliner()) - return root.eraseGetValue().toByteArray() - } - - // erase getting value of atomic field - private fun AstNode.eraseGetValue(): String { - var res = this.toSource() - val primitiveGetValue = MANGLE_VALUE_REGEX - val arrayGetElement = ARRAY_GET_ELEMENT_REGEX - while (res.contains(arrayGetElement)) { - res = res.replace(arrayGetElement) { matchResult -> - val greedyToLastClosingParen = matchResult.groupValues[1] - var balance = 1 - var indexEndPos = 0 - for (i in 0 until greedyToLastClosingParen.length) { - val c = greedyToLastClosingParen[i] - if (c == '(') balance++ - if (c == ')') balance-- - if (balance == 0) { - indexEndPos = i - break - } - } - val closingParen = indexEndPos == greedyToLastClosingParen.lastIndex - if (balance == 1) { - "[$greedyToLastClosingParen]" - } else { - "[${greedyToLastClosingParen.substring(0, indexEndPos)}]${greedyToLastClosingParen.substring(indexEndPos + 1)}${if (!closingParen) ")" else ""}" - } - } - } - return res.replace(primitiveGetValue) { "" } - } - - inner class DependencyEraser : NodeVisitor { - private fun isAtomicfuDependency(node: AstNode) = - (node.type == Token.STRING && node.toSource() == KOTLINX_ATOMICFU) - - private fun isAtomicfuModule(node: AstNode) = - (node.type == Token.NAME && node.toSource().matches(Regex(MODULE_KOTLINX_ATOMICFU))) - - override fun visit(node: AstNode): Boolean { - when (node.type) { - Token.ARRAYLIT -> { - // erasing 'kotlinx-atomicfu' from the list of defined dependencies - val elements = (node as ArrayLiteral).elements as MutableList - val it = elements.listIterator() - while (it.hasNext()) { - val arg = it.next() - if (isAtomicfuDependency(arg)) { - it.remove() - } - } - } - Token.FUNCTION -> { - if (node is FunctionNode) { - val it = node.params.listIterator() - while (it.hasNext()) { - // erasing 'kotlinx-atomicfu' module passed as parameter - if (isAtomicfuModule(it.next())) { - it.remove() - } - } - } - } - Token.CALL -> { - if (node is FunctionCall && node.target.toSource() == FACTORY) { - val it = node.arguments.listIterator() - while (it.hasNext()) { - val arg = it.next() - when (arg.type) { - Token.GETELEM -> { - // erasing 'kotlinx-atomicfu' dependency as factory argument - if (isAtomicfuDependency((arg as ElementGet).element)) { - it.remove() - } - } - Token.CALL -> { - // erasing require of 'kotlinx-atomicfu' dependency - if ((arg as FunctionCall).target.toSource() == REQUIRE) { - if (isAtomicfuDependency(arg.arguments[0])) { - it.remove() - } - } - } - } - } - } - } - Token.GETELEM -> { - if (isAtomicfuDependency((node as ElementGet).element)) { - val enclosingNode = node.parent - // erasing the check whether 'kotlinx-atomicfu' is defined - if (enclosingNode.type == Token.TYPEOF) { - if (enclosingNode.parent.parent.type == Token.IF) { - val ifStatement = enclosingNode.parent.parent as IfStatement - val falseKeyword = KeywordLiteral() - falseKeyword.type = Token.FALSE - ifStatement.condition = falseKeyword - val oneLineBlock = Block() - oneLineBlock.addStatement(EmptyLine()) - ifStatement.thenPart = oneLineBlock - } - } - - } - } - Token.BLOCK -> { - // erasing importsForInline for 'kotlinx-atomicfu' - for (stmt in node) { - if (stmt is ExpressionStatement) { - val expr = stmt.expression - if (expr is Assignment && expr.left is ElementGet) { - if (isAtomicfuDependency((expr.left as ElementGet).element)) { - node.replaceChild(stmt, EmptyLine()) - } - } - } - } - } - } - return true - } - } - - inner class AtomicConstructorDetector : NodeVisitor { - private fun kotlinxAtomicfuModuleName(name: String) = "$MODULE_KOTLINX_ATOMICFU.$KOTLINX_ATOMICFU_PACKAGE.$name" - - override fun visit(node: AstNode?): Boolean { - if (node is Block) { - for (stmt in node) { - if (stmt is VariableDeclaration) { - val varInit = stmt.variables[0] as VariableInitializer - if (varInit.initializer is PropertyGet) { - val initializer = varInit.initializer.toSource() - if (initializer.matches(Regex(kotlinxAtomicfuModuleName("""($ATOMIC_CONSTRUCTOR|$ATOMIC_CONSTRUCTOR_BINARY_COMPATIBILITY)""")))) { - atomicConstructors.add(varInit.target.toSource()) - node.replaceChild(stmt, EmptyLine()) - } else if (initializer.matches(Regex(kotlinxAtomicfuModuleName(TRACE_CONSTRUCTOR)))) { - traceConstructors.add(varInit.target.toSource()) - node.replaceChild(stmt, EmptyLine()) - } else if (initializer.matches(Regex(kotlinxAtomicfuModuleName("""($LOCKS|$TRACE_FORMAT_CONSTRUCTOR|$TRACE_BASE_CLASS|$TRACE_NAMED)""")))) { - node.replaceChild(stmt, EmptyLine()) - } - } - } - } - } - if (node is PropertyGet && node.property.toSource().matches(Regex(TRACE_FORMAT_FORMAT))) { - val target = node.target - node.property = Name().also { it.identifier = "emptyProperty" } - if (target is PropertyGet && target.property.toSource().matches(Regex(PROTOTYPE))) { - traceFormatObjects.add(target.target.toSource()) - } - } - if (node is VariableInitializer && node.initializer is PropertyGet) { - val initializer = node.initializer.toSource() - if (initializer.matches(Regex(REENTRANT_LOCK_ATOMICFU_SINGLETON))) { - node.initializer = null - } - if (initializer.matches(Regex(kotlinxAtomicfuModuleName("""($ATOMIC_CONSTRUCTOR|$ATOMIC_CONSTRUCTOR_BINARY_COMPATIBILITY)""")))) { - atomicConstructors.add(node.target.toSource()) - node.initializer = null - } - if (initializer.matches(Regex(kotlinxAtomicfuModuleName(ATOMIC_ARRAY_CONSTRUCTOR)))) { - val initialValue = when (initializer.substringAfterLast('$')) { - "int" -> "0" - "long" -> "0" - "boolean" -> "false" - else -> null - } - atomicArrayConstructors[node.target.toSource()] = initialValue - node.initializer = null - } - return false - } else if (node is Assignment && node.right is PropertyGet) { - val initializer = node.right.toSource() - if (initializer.matches(Regex(REENTRANT_LOCK_ATOMICFU_SINGLETON))) { - node.right = Name().also { it.identifier = "null" } - return false - } - } - return true - } - } - - inner class FieldDelegatesVisitor : NodeVisitor { - override fun visit(node: AstNode?): Boolean { - if (node is FunctionCall) { - val functionName = node.target.toSource() - if (atomicConstructors.contains(functionName)) { - if (node.parent is Assignment) { - val assignment = node.parent as Assignment - val atomicField = assignment.left - val constructorBlock = ((node.parent.parent as? ExpressionStatement)?.parent as? Block) - ?: abort("Incorrect tree structure of the constructor block initializing ${node.parent.toSource()}") - // check if there is a delegate field initialized by the reference to this atomic - for (stmt in constructorBlock) { - if (stmt is ExpressionStatement) { - if (stmt.expression is Assignment) { - val delegateAssignment = stmt.expression as Assignment - val initializer = delegateAssignment.right - if (initializer.toSource() == atomicField.toSource()) { - if (delegateAssignment.right is PropertyGet) { // initialization of a class field - // delegate${owner_class} to original atomic field - val delegateFieldName = (delegateAssignment.left as PropertyGet).property.toSource() - val ownerClassName = constructorBlock.enclosingFunction.functionName.identifier - delegateToOriginalAtomicField["$delegateFieldName\$$ownerClassName"] = - (atomicField as PropertyGet).property - } else { // top-level delegated fields - val delegateFieldName = delegateAssignment.left.toSource() - delegateToOriginalAtomicField[delegateFieldName] = atomicField as Name - } - } - } - } - } - } - } - } - return true - } - } - - inner class DelegatedPropertyAccessorsVisitor : NodeVisitor { - override fun visit(node: AstNode?): Boolean { - // find ObjectLiteral with accessors of the delegated field (get: FunctionNode, set: FunctionNode) - // redirect getter/setter from generated delegate field to the original atomic field - if (node is ObjectLiteral && node.parent is FunctionCall && - ((node.elements.size == 2 && node.elements[1].left.toSource() == "get") || - (node.elements.size == 3 && node.elements[1].left.toSource() == "get" && node.elements[2].left.toSource() == "set"))) { - // check that these are accessors of the atomic delegate field (check only getter) - if (node.elements[1].right is FunctionNode) { - val getter = node.elements[1].right as FunctionNode - if (getter.body.hasChildren() && getter.body.firstChild is ReturnStatement) { - val returnStmt = getter.body.firstChild as ReturnStatement - if (returnStmt.returnValue is PropertyGet && (returnStmt.returnValue as PropertyGet).property.toSource() == MANGLED_VALUE_PROP) { - val delegateField = ((returnStmt.returnValue as PropertyGet).target as PropertyGet).property.toSource() - val ownerClassName = ((node.parent as FunctionCall).arguments[0] as PropertyGet).target.toSource() - val key = "$delegateField\$$ownerClassName" - delegateToOriginalAtomicField[key]?.let { atomicField -> - // get() = a$delegate.value -> _a.value - getter.replaceAccessedField(true, atomicField) - if (node.elements.size == 3) { - // set(v: T) { a$delegate.value = v } -> { _a.value = v } - val setter = node.elements[2].right as FunctionNode - setter.replaceAccessedField(false, atomicField) - } - } - } - } - } - } - if (node is ObjectLiteral && node.parent is FunctionCall && ((node.elements.size == 1 && node.elements[0].left.toSource() == "get") || - node.elements.size == 2 && node.elements[0].left.toSource() == "get" && node.elements[1].left.toSource() == "set")) { - val parent = node.parent as FunctionCall - if (parent.arguments.size == 3 && parent.arguments[1] is StringLiteral) { - val topLevelDelegatedFieldName = (parent.arguments[1] as StringLiteral).value - if (topLevelDelegatedFieldName in delegateToOriginalAtomicField) { - val originalAtomicFieldName = delegateToOriginalAtomicField[topLevelDelegatedFieldName]!! - val getterName = node.elements[0].right.toSource() - topLevelDelegatedFieldAccessorToOriginalField[getterName] = originalAtomicFieldName - if (node.elements.size == 2) { - val setterName = node.elements[1].right.toSource() - topLevelDelegatedFieldAccessorToOriginalField[setterName] = originalAtomicFieldName - } - } - } - } - return true - } - } - - private fun FunctionNode.replaceAccessedField(isGetter: Boolean, newField: Name) { - val propertyGet = if (isGetter) { - (body.firstChild as ReturnStatement).returnValue as PropertyGet - } else { - ((body.firstChild as ExpressionStatement).expression as Assignment).left as PropertyGet - } - if (propertyGet.target is PropertyGet) { // class member - (propertyGet.target as PropertyGet).property = newField - } else { // top-level field - propertyGet.target = newField - } - } - - inner class TopLevelDelegatedFieldsAccessorVisitor : NodeVisitor { - override fun visit(node: AstNode?): Boolean { - if (node is FunctionNode && node.name.toString() in topLevelDelegatedFieldAccessorToOriginalField) { - val accessorName = node.name.toString() - val atomicField = topLevelDelegatedFieldAccessorToOriginalField[accessorName]!! - // function get_topLevelDelegatedField() = a.value -> _a.value - // function set_topLevelDelegatedField(v: T) { a.value = v } -> { _a.value = v } - node.replaceAccessedField(accessorName.startsWith("get"), atomicField) - } - return true - } - } - - inner class TransformVisitor : NodeVisitor { - override fun visit(node: AstNode): Boolean { - // remove atomic constructors from classes fields - if (node is FunctionCall) { - val functionName = node.target.toSource() - if (atomicConstructors.contains(functionName)) { - if (node.parent is Assignment) { - val valueNode = node.arguments[0] - (node.parent as Assignment).right = valueNode - } - return true - } else if (atomicArrayConstructors.contains(functionName)) { - val arrayConstructor = Name() - arrayConstructor.identifier = ARRAY - node.target = arrayConstructor - atomicArrayConstructors[functionName]?.let { - val arrayConsCall = FunctionCall() - arrayConsCall.target = node.target - arrayConsCall.arguments = node.arguments - val target = PropertyGet() - val fill = Name() - fill.identifier = FILL - target.target = arrayConsCall - target.property = fill - node.target = target - val initialValue = Name() - initialValue.identifier = it - node.arguments = listOf(initialValue) - } - return true - } else if (node.target is PropertyGet) { - if ((node.target as PropertyGet).target is FunctionCall) { - val atomicOperationTarget = node.target as PropertyGet - val funcCall = atomicOperationTarget.target as FunctionCall - if (funcCall.target is PropertyGet) { - val getterCall = (funcCall.target as PropertyGet).property - if (Regex(GET_ELEMENT).matches(getterCall.toSource())) { - val getter = getArrayElement(funcCall) - atomicOperationTarget.target = getter - } - } - } - } - } - // remove value property call - if (node is PropertyGet) { - if (node.property.toSource() == MANGLED_VALUE_PROP) { - // check whether atomic operation is performed on the type casted atomic field - node.target.eraseAtomicFieldFromUncheckedCast()?.let { node.target = it } - // A.a.value - if (node.target.type == Token.GETPROP) { - val clearField = node.target as PropertyGet - val targetNode = clearField.target - val clearProperety = clearField.property - node.setLeftAndRight(targetNode, clearProperety) - } - // other cases with $receiver.kotlinx$atomicfu$value in inline functions - else if (node.target.toSource().matches(Regex(RECEIVER))) { - val receiverName = node.target.toSource() // $receiver_i - val rr = ReceiverResolver(receiverName) - node.enclosingFunction.visit(rr) - rr.receiver?.let { node.target = it } - } - } - // replace Atomic*Array.size call with `length` property on the pure type js array - if (node.property.toSource() == ARRAY_SIZE) { - node.property = Name().also { it.identifier = LENGTH } - } - } - if (node is Block) { - for (stmt in node) { - if (stmt is ExpressionStatement) { - if (stmt.expression is Assignment) { - // erase field initialisation - val assignment = stmt.expression as Assignment - if (assignment.right is FunctionCall) { - val functionName = (assignment.right as FunctionCall).target.toSource() - if (traceConstructors.contains(functionName)) { - node.replaceChild(stmt, EmptyLine()) - } - } - } - if (stmt.expression is FunctionCall) { - // erase append(text) call - val funcNode = (stmt.expression as FunctionCall).target - if (funcNode is PropertyGet && funcNode.property.toSource().matches(Regex(TRACE_APPEND))) { - node.replaceChild(stmt, EmptyLine()) - } - } - } - } - } - if (node is Assignment && node.left is PropertyGet) { - val left = node.left as PropertyGet - if (traceFormatObjects.contains(left.target.toSource())) { - if (node.right is FunctionCall) { - // TraceFormatObject initialization - (node.right as FunctionCall).arguments = listOf(Name().also { it.identifier = "null" }) - } - } - } - // remove TraceFormatObject constructor definition - if (node is FunctionNode && traceFormatObjects.contains(node.name)) { - val body = node.body - for (stmt in body) { body.replaceChild(stmt, EmptyLine()) } - } - // remove TraceFormat from TraceFormatObject interfaces - if (node is Assignment && node.left is PropertyGet && node.right is ObjectLiteral) { - val left = node.left as PropertyGet - val metadata = node.right as ObjectLiteral - if (traceFormatObjects.contains(left.target.toSource())) { - for (e in metadata.elements) { - if (e.right is ArrayLiteral) { - val array = (e.right as ArrayLiteral).toSource() - if (array.contains(TRACE_FORMAT)) { - (e.right as ArrayLiteral).elements = emptyList() - } - } - } - } - } - return true - } - - private fun getArrayElement(getterCall: FunctionCall): AstNode { - val index = getterCall.arguments[0] - val arrayField = (getterCall.target as PropertyGet).target - // whether this field is static or not - val isStatic = arrayField !is PropertyGet - val arrName = if (isStatic) arrayField else (arrayField as PropertyGet).property - val getter = ElementGet(arrName, index) - return if (isStatic) { //intArr[index] - getter - } else { //A.intArr[0] - val call = PropertyGet() - call.target = (arrayField as PropertyGet).target - val name = Name() - name.identifier = getter.toSource() - call.property = name - call - } - } - } - - - // receiver data flow - inner class ReceiverResolver(private val receiverName: String) : NodeVisitor { - var receiver: AstNode? = null - override fun visit(node: AstNode): Boolean { - if (node is VariableInitializer) { - if (node.target.toSource() == receiverName) { - receiver = node.initializer - return false - } - } - return true - } - } - - inner class AtomicOperationsInliner : NodeVisitor { - override fun visit(node: AstNode?): Boolean { - // inline atomic operations - if (node is FunctionCall) { - if (node.target is PropertyGet) { - val funcName = (node.target as PropertyGet).property - var field = (node.target as PropertyGet).target - if (field.toSource().matches(Regex(RECEIVER))) { - val receiverName = field.toSource() // $receiver_i - val rr = ReceiverResolver(receiverName) - node.enclosingFunction.visit(rr) - if (rr.receiver != null) { - field = rr.receiver - } - } - field.eraseAtomicFieldFromUncheckedCast()?.let { field = it } - val args = node.arguments - val inlined = node.inlineAtomicOperation(funcName.toSource(), field, args) - return !inlined - } - } - return true - } - } - - private fun AstNode.eraseAtomicFieldFromUncheckedCast(): AstNode? { - if (this is ParenthesizedExpression && expression is ConditionalExpression) { - val testExpression = (expression as ConditionalExpression).testExpression - if (testExpression is FunctionCall && testExpression.target.toSource() == KOTLIN_TYPE_CHECK) { - // type check - val typeToCast = testExpression.arguments[1] - if ((typeToCast as Name).identifier == ATOMIC_REF) { - // unchecked type cast -> erase atomic field itself - return (testExpression.arguments[0] as Assignment).right - } - } - } - return null - } - - private fun AstNode.isThisNode(): Boolean { - return when(this) { - is PropertyGet -> { - target.isThisNode() - } - is FunctionCall -> { - target.isThisNode() - } - else -> { - (this.type == Token.THIS) - } - } - } - - private fun PropertyGet.resolvePropName(): String { - val target = this.target - return if (target is PropertyGet) { - "${target.resolvePropName()}.${property.toSource()}" - } else { - property.toSource() - } - } - - private fun AstNode.scopedSource(): String { - if (this.isThisNode()) { - if (this is PropertyGet) { - val property = resolvePropName() - return "$SCOPE.$property" - } else if (this is FunctionCall && this.target is PropertyGet) { - // check that this function call is getting array element - if (this.target is PropertyGet) { - val funcName = (this.target as PropertyGet).property.toSource() - if (Regex(GET_ELEMENT).matches(funcName)) { - val property = (this.target as PropertyGet).resolvePropName() - return "$SCOPE.$property(${this.arguments[0].toSource()})" - } - } - } else if (this.type == Token.THIS) { - return SCOPE - } - } - return this.toSource() - } - - private fun FunctionCall.inlineAtomicOperation( - funcName: String, - field: AstNode, - args: List - ): Boolean { - val f = field.scopedSource() - val code = when (funcName) { - "atomicfu\$getAndSet" -> { - val arg = args[0].toSource() - "(function($SCOPE) {var oldValue = $f; $f = $arg; return oldValue;})()" - } - "atomicfu\$compareAndSet" -> { - val expected = args[0].scopedSource() - val updated = args[1].scopedSource() - val equals = if (expected == "null") "==" else "===" - "(function($SCOPE) {return $f $equals $expected ? function() { $f = $updated; return true }() : false})()" - } - "atomicfu\$getAndIncrement" -> { - "(function($SCOPE) {return $f++;})()" - } - - "atomicfu\$getAndIncrement\$long" -> { - "(function($SCOPE) {var oldValue = $f; $f = $f.inc(); return oldValue;})()" - } - - "atomicfu\$getAndDecrement" -> { - "(function($SCOPE) {return $f--;})()" - } - - "atomicfu\$getAndDecrement\$long" -> { - "(function($SCOPE) {var oldValue = $f; $f = $f.dec(); return oldValue;})()" - } - - "atomicfu\$getAndAdd" -> { - val arg = args[0].scopedSource() - "(function($SCOPE) {var oldValue = $f; $f += $arg; return oldValue;})()" - } - - "atomicfu\$getAndAdd\$long" -> { - val arg = args[0].scopedSource() - "(function($SCOPE) {var oldValue = $f; $f = $f.add($arg); return oldValue;})()" - } - - "atomicfu\$addAndGet" -> { - val arg = args[0].scopedSource() - "(function($SCOPE) {$f += $arg; return $f;})()" - } - - "atomicfu\$addAndGet\$long" -> { - val arg = args[0].scopedSource() - "(function($SCOPE) {$f = $f.add($arg); return $f;})()" - } - - "atomicfu\$incrementAndGet" -> { - "(function($SCOPE) {return ++$f;})()" - } - - "atomicfu\$incrementAndGet\$long" -> { - "(function($SCOPE) {return $f = $f.inc();})()" - } - - "atomicfu\$decrementAndGet" -> { - "(function($SCOPE) {return --$f;})()" - } - - "atomicfu\$decrementAndGet\$long" -> { - "(function($SCOPE) {return $f = $f.dec();})()" - } - else -> null - } - if (code != null) { - this.setImpl(code) - return true - } - return false - } - - private fun FunctionCall.setImpl(code: String) { - val p = Parser(CompilerEnvirons()) - val node = p.parse(code, null, 0) - if (node.firstChild != null) { - val expr = (node.firstChild as ExpressionStatement).expression - this.target = (expr as FunctionCall).target - val thisNode = Parser(CompilerEnvirons()).parse("this", null, 0) - this.arguments = listOf((thisNode.firstChild as ExpressionStatement).expression) - } - } -} - -private class EmptyLine : EmptyExpression() { - override fun toSource(depth: Int) = "\n" -} - -fun main(args: Array) { - if (args.size !in 1..2) { - println("Usage: AtomicFUTransformerKt []") - return - } - val t = AtomicFUTransformerJS(File(args[0]), File(args[1])) - t.transform() -} \ No newline at end of file