aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSokolovaMaria <marysok17@gmail.com>2022-02-07 16:15:40 +0300
committerGitHub <noreply@github.com>2022-02-07 16:15:40 +0300
commitdcf8566fb1b5a89b3dad5d9d0fdb887aed123c1b (patch)
tree91a1f4a43e49bf3163bad4ecb8f331d205034b3d
parent25bf2042a04f9681c39a3d2a504e82f640672b64 (diff)
downloadkotlinx.atomicfu-dcf8566fb1b5a89b3dad5d9d0fdb887aed123c1b.tar.gz
Bug fixes for delegated fields support (#179)
* Js transformer: checking owner class of the delegated fields * JVM transformer: does not rely on the generated name of the field delegate * Top-level delegated properties for JVM and JS Co-authored-by: SokolovaMaria <maria.sokolova@jetbrains.com>
-rw-r--r--atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AsmUtil.kt13
-rw-r--r--atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt171
-rw-r--r--atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformerJS.kt104
-rw-r--r--atomicfu/src/commonTest/kotlin/kotlinx/atomicfu/test/DelegatedPropertiesTest.kt112
4 files changed, 308 insertions, 92 deletions
diff --git a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AsmUtil.kt b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AsmUtil.kt
index ba08fbc..0687f38 100644
--- a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AsmUtil.kt
+++ b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AsmUtil.kt
@@ -4,7 +4,9 @@
package kotlinx.atomicfu.transformer
+import org.objectweb.asm.*
import org.objectweb.asm.Opcodes.*
+import org.objectweb.asm.Type.*
import org.objectweb.asm.tree.*
import org.objectweb.asm.util.*
@@ -69,12 +71,23 @@ fun AbstractInsnNode.isGetField(owner: String) =
fun AbstractInsnNode.isGetStatic(owner: String) =
this is FieldInsnNode && this.opcode == GETSTATIC && this.owner == owner
+fun AbstractInsnNode.isGetFieldOrGetStatic() =
+ this is FieldInsnNode && (this.opcode == GETFIELD || this.opcode == GETSTATIC)
+
fun AbstractInsnNode.isAreturn() =
this.opcode == ARETURN
fun AbstractInsnNode.isReturn() =
this.opcode == RETURN
+fun AbstractInsnNode.isTypeReturn(type: Type) =
+ opcode == when (type) {
+ INT_TYPE -> IRETURN
+ LONG_TYPE -> LRETURN
+ BOOLEAN_TYPE -> IRETURN
+ else -> ARETURN
+ }
+
fun AbstractInsnNode.isInvokeVirtual() =
this.opcode == INVOKEVIRTUAL
diff --git a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt
index b8f804f..ca6c143 100644
--- a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt
+++ b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt
@@ -91,7 +91,6 @@ private val TRACE_APPEND_3 = MethodId(TRACE_BASE_CLS, "append", getMethodDescrip
private val TRACE_APPEND_4 = MethodId(TRACE_BASE_CLS, "append", getMethodDescriptor(VOID_TYPE, OBJECT_TYPE, OBJECT_TYPE, OBJECT_TYPE, OBJECT_TYPE), INVOKEVIRTUAL)
private val TRACE_DEFAULT_ARGS = "I${OBJECT_TYPE.descriptor}"
private const val DEFAULT = "\$default"
-private const val DELEGATE = "\$delegate"
private val TRACE_FACTORY = MethodId(TRACE_KT, TRACE, "(IL$AFU_PKG/$TRACE_FORMAT;)L$AFU_PKG/$TRACE_BASE;", INVOKESTATIC)
private val TRACE_PARTIAL_ARGS_FACTORY = MethodId(TRACE_KT, "$TRACE$DEFAULT", "(IL$AFU_PKG/$TRACE_FORMAT;$TRACE_DEFAULT_ARGS)L$AFU_PKG/$TRACE_BASE;", INVOKESTATIC)
@@ -188,6 +187,7 @@ class AtomicFUTransformer(
private val traceFields = mutableSetOf<FieldId>()
private val traceAccessors = mutableSetOf<MethodId>()
private val fieldDelegates = mutableMapOf<FieldId, FieldInfo>()
+ private val delegatedPropertiesAccessors = mutableMapOf<FieldId, MethodId>()
private val removeMethods = mutableSetOf<MethodId>()
override fun transform() {
@@ -341,6 +341,10 @@ class AtomicFUTransformer(
// check for copying atomic values into delegate fields and register potential delegate fields
return DelegateFieldsCollectorMV(access, name, desc, signature, exceptions)
}
+ // collect accessors of potential delegated properties
+ if (methodType.argumentTypes.isEmpty()) {
+ return DelegatedFieldAccessorCollectorMV(className, methodType.returnType, access, name, desc, signature, exceptions)
+ }
return null
}
}
@@ -396,6 +400,43 @@ class AtomicFUTransformer(
}
}
+ private inner class DelegatedFieldAccessorCollectorMV(
+ private val className: String, private val returnType: Type,
+ access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?
+ ) : MethodNode(ASM5, access, name, desc, signature, exceptions) {
+ override fun visitEnd() {
+ // check for pattern of a delegated property getter
+ // getfield/getstatic a$delegate: Atomic*
+ // astore_i ...
+ // aload_i
+ // invokevirtual Atomic*.getValue()
+ // ireturn
+ var cur = instructions.first
+ while (cur != null && !(cur.isGetFieldOrGetStatic() && getType((cur as FieldInsnNode).desc) in AFU_TYPES)) {
+ cur = cur.next
+ }
+ if (cur != null && cur.next.opcode == ASTORE) {
+ val fi = cur as FieldInsnNode
+ val fieldDelegate = FieldId(className, fi.name, fi.desc)
+ val atomicType = getType(fi.desc)
+ val v = (cur.next as VarInsnNode).`var`
+ while (!(cur is VarInsnNode && cur.opcode == ALOAD && cur.`var` == v)) {
+ cur = cur.next
+ }
+ val invokeVirtual = cur.next
+ if (invokeVirtual.opcode == INVOKEVIRTUAL && (invokeVirtual as MethodInsnNode).name == GET_VALUE && invokeVirtual.owner == atomicType.internalName) {
+ // followed by RETURN operation
+ val next = invokeVirtual.nextUseful
+ val ret = if (next?.opcode == CHECKCAST) next.nextUseful else next
+ if (ret != null && ret.isTypeReturn(returnType)) {
+ // register delegated property accessor
+ delegatedPropertiesAccessors[fieldDelegate] = MethodId(className, name, desc, accessToInvokeOpcode(access))
+ }
+ }
+ }
+ }
+ }
+
private inner class DelegateFieldsCollectorMV(
access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?
) : MethodNode(ASM9, access, name, desc, signature, exceptions) {
@@ -408,7 +449,7 @@ class AtomicFUTransformer(
insn.checkGetFieldOrGetStatic()?.let { getfieldId ->
val next = insn.next
(next as? FieldInsnNode)?.checkPutFieldOrPutStatic()?.let { delegateFieldId ->
- if (delegateFieldId.name.endsWith(DELEGATE)) {
+ if (getfieldId in fields && delegateFieldId in fields) {
// original atomic value is copied to the synthetic delegate atomic field <delegated field name>$delegate
val originalField = fields[getfieldId]!!
fieldDelegates[delegateFieldId] = originalField
@@ -420,11 +461,12 @@ class AtomicFUTransformer(
val methodId = MethodId(insn.owner, insn.name, insn.desc, insn.opcode)
if (methodId in FACTORIES) {
(insn.nextUseful as? FieldInsnNode)?.checkPutFieldOrPutStatic()?.let { delegateFieldId ->
- if (delegateFieldId.name.endsWith(DELEGATE)) {
+ val fieldType = getType(insn.desc).returnType
+ if (fieldType in AFU_TYPES) {
+ val isStatic = insn.nextUseful!!.opcode == PUTSTATIC
// delegate field is initialized by a factory invocation
- val fieldType = getType(insn.desc).returnType
// for volatile delegated properties store FieldInfo of the delegate field itself
- fieldDelegates[delegateFieldId] = FieldInfo(delegateFieldId, fieldType)
+ fieldDelegates[delegateFieldId] = FieldInfo(delegateFieldId, fieldType, isStatic)
}
}
}
@@ -447,6 +489,8 @@ class AtomicFUTransformer(
return if (fieldId in fields) fieldId else null
}
+ private fun FieldId.isFieldDelegate() = this in fieldDelegates && delegatedPropertiesAccessors.contains(this)
+
private inner class TransformerCV(
cv: ClassVisitor?,
private val vh: Boolean,
@@ -478,8 +522,8 @@ class AtomicFUTransformer(
val fieldType = getType(desc)
if (fieldType.sort == OBJECT && fieldType.internalName in AFU_CLASSES) {
val fieldId = FieldId(className, name, desc)
- // skip delegate field
- if (fieldId in fieldDelegates && (fieldId != fieldDelegates[fieldId]!!.fieldId)) {
+ // skip field delegates except volatile delegated properties (e.g. val a: Int by atomic(0))
+ if (fieldId.isFieldDelegate() && (fieldId != fieldDelegates[fieldId]!!.fieldId)) {
transformed = true
return null
}
@@ -727,7 +771,7 @@ class AtomicFUTransformer(
private fun FieldInsnNode.checkCopyToDelegate(): AbstractInsnNode? {
val fieldId = FieldId(owner, name, desc)
- if (fieldId in fieldDelegates) {
+ if (fieldId.isFieldDelegate()) {
// original atomic value is copied to the synthetic delegate atomic field <delegated field name>$delegate
val originalField = fieldDelegates[fieldId]!!
val getField = previous as FieldInsnNode
@@ -753,51 +797,29 @@ class AtomicFUTransformer(
if (iv.name == GET_VALUE || iv.name == SET_VALUE) {
check(!f.isArray || onArrayElement) { "getValue/setValue can only be called on elements of arrays" }
val setInsn = iv.name == SET_VALUE
- if (!onArrayElement) {
- val primitiveType = f.getPrimitiveType(vh)
- val owner = if (!vh && f.isStatic) f.refVolatileClassName else f.owner
- if (!vh && f.isStatic) {
- val getOwnerClass = FieldInsnNode(
- GETSTATIC,
- f.owner,
- f.staticRefVolatileField,
- getObjectType(owner).descriptor
- )
- instructions.insert(ld, getOwnerClass)
- }
- instructions.remove(ld) // drop getstatic (we don't need field updater)
- val j = FieldInsnNode(
- when {
- iv.name == GET_VALUE -> if (f.isStatic && vh) GETSTATIC else GETFIELD
- else -> if (f.isStatic && vh) PUTSTATIC else PUTFIELD
- }, owner, f.name, primitiveType.descriptor
- )
- instructions.set(iv, j) // replace invokevirtual with get/setfield
- return j.next
+ if (!onArrayElement) return getPureTypeField(ld, f, iv)
+ var methodType = getMethodType(iv.desc)
+ if (f.typeInfo.originalType != f.typeInfo.transformedType && !vh) {
+ val ret = f.typeInfo.transformedType.elementType
+ iv.desc = if (setInsn) getMethodDescriptor(methodType.returnType, ret) else getMethodDescriptor(ret, *methodType.argumentTypes)
+ methodType = getMethodType(iv.desc)
+ }
+ iv.name = iv.name.substring(0, 3)
+ if (!vh) {
+ // map to j.u.c.a.Atomic*Array get or set
+ iv.owner = descToName(f.fuType.descriptor)
+ iv.desc = getMethodDescriptor(methodType.returnType, INT_TYPE, *methodType.argumentTypes)
} else {
- var methodType = getMethodType(iv.desc)
- if (f.typeInfo.originalType != f.typeInfo.transformedType && !vh) {
- val ret = f.typeInfo.transformedType.elementType
- iv.desc = if (setInsn) getMethodDescriptor(methodType.returnType, ret) else getMethodDescriptor(ret, *methodType.argumentTypes)
- methodType = getMethodType(iv.desc)
- }
- iv.name = iv.name.substring(0, 3)
- if (!vh) {
- // map to j.u.c.a.Atomic*Array get or set
- iv.owner = descToName(f.fuType.descriptor)
- iv.desc = getMethodDescriptor(methodType.returnType, INT_TYPE, *methodType.argumentTypes)
- } else {
- // map to VarHandle get or set
- iv.owner = descToName(VH_TYPE.descriptor)
- iv.desc = getMethodDescriptor(
- methodType.returnType,
- f.getPrimitiveType(vh),
- INT_TYPE,
- *methodType.argumentTypes
- )
- }
- return iv
+ // map to VarHandle get or set
+ iv.owner = descToName(VH_TYPE.descriptor)
+ iv.desc = getMethodDescriptor(
+ methodType.returnType,
+ f.getPrimitiveType(vh),
+ INT_TYPE,
+ *methodType.argumentTypes
+ )
}
+ return iv
}
if (f.isArray && iv.name == GET_SIZE) {
if (!vh) {
@@ -860,6 +882,29 @@ class AtomicFUTransformer(
return iv.next
}
+ private fun getPureTypeField(ld: FieldInsnNode, f: FieldInfo, iv: MethodInsnNode): AbstractInsnNode? {
+ val primitiveType = f.getPrimitiveType(vh)
+ val owner = if (!vh && f.isStatic) f.refVolatileClassName else f.owner
+ if (!vh && f.isStatic) {
+ val getOwnerClass = FieldInsnNode(
+ GETSTATIC,
+ f.owner,
+ f.staticRefVolatileField,
+ getObjectType(owner).descriptor
+ )
+ instructions.insert(ld, getOwnerClass)
+ }
+ instructions.remove(ld) // drop getfield/getstatic of the atomic field
+ val j = FieldInsnNode(
+ when {
+ iv.name == GET_VALUE -> if (f.isStatic && vh) GETSTATIC else GETFIELD
+ else -> if (f.isStatic && vh) PUTSTATIC else PUTFIELD
+ }, owner, f.name, primitiveType.descriptor
+ )
+ instructions.set(iv, j) // replace invokevirtual with get/setfield
+ return j.next
+ }
+
private fun vhOperation(iv: MethodInsnNode, typeInfo: TypeInfo, f: FieldInfo) {
val methodType = getMethodType(iv.desc)
val args = methodType.argumentTypes
@@ -1310,7 +1355,7 @@ class AtomicFUTransformer(
is FieldInsnNode -> {
val fieldId = FieldId(i.owner, i.name, i.desc)
if ((i.opcode == GETFIELD || i.opcode == GETSTATIC) && fieldId in fields) {
- if (fieldId in fieldDelegates && i.next.opcode == ASTORE) {
+ if (fieldId.isFieldDelegate() && i.next.opcode == ASTORE) {
return transformDelegatedFieldAccessor(i, fieldId)
}
(i.next as? FieldInsnNode)?.checkCopyToDelegate()?.let { return it } // atomic field is copied to delegate field
@@ -1343,29 +1388,25 @@ class AtomicFUTransformer(
return i.next
}
- private fun transformDelegatedFieldAccessor(i: FieldInsnNode, fieldId: FieldId): AbstractInsnNode {
+ private fun transformDelegatedFieldAccessor(i: FieldInsnNode, fieldId: FieldId): AbstractInsnNode? {
val f = fieldDelegates[fieldId]!!
- val astore = (i.next as? VarInsnNode) ?: abort("Method $name does not match the pattern of a delegated field accessor")
- val v = astore.`var`
- var cur: AbstractInsnNode = i
+ val v = (i.next as VarInsnNode).`var`
+ // remove instructions [astore_v .. aload_v]
+ var cur: AbstractInsnNode = i.next
while (!(cur is VarInsnNode && cur.opcode == ALOAD && cur.`var` == v)) {
val next = cur.next
instructions.remove(cur)
cur = next
}
- val invokeVirtual = FlowAnalyzer(cur.next).execute()
- instructions.remove(cur)
- check(invokeVirtual.isAtomicGetValueOrSetValue()) { "Aload of the field delegate $f should be followed with Atomic*.getValue()/setValue() invocation" }
- val accessorName = (invokeVirtual as MethodInsnNode).name.substring(0, 3)
- val isGetter = accessorName == "get"
- val primitiveType = f.getPrimitiveType(vh)
- val j = FieldInsnNode(if (isGetter) GETFIELD else PUTFIELD, f.owner, f.name, primitiveType.descriptor)
- instructions.set(invokeVirtual, j)
+ val iv = FlowAnalyzer(cur.next).execute()
+ check(iv.isAtomicGetValueOrSetValue()) { "Aload of the field delegate $f should be followed with Atomic*.getValue()/setValue() invocation" }
+ val isGetter = (iv as MethodInsnNode).name == GET_VALUE
+ instructions.remove(cur) // remove aload_v
localVariables.removeIf {
!(getType(it.desc).internalName == f.owner ||
(!isGetter && getType(it.desc) == getType(desc).argumentTypes.first() && it.name == "<set-?>"))
}
- return j.next
+ return getPureTypeField(i, f, iv)
}
private fun AbstractInsnNode.isAtomicGetFieldOrGetStatic() =
diff --git a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformerJS.kt b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformerJS.kt
index 91f3037..de8f94a 100644
--- a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformerJS.kt
+++ b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformerJS.kt
@@ -52,8 +52,8 @@ class AtomicFUTransformerJS(
outputDir: File
) : AtomicFUTransformerBase(inputDir, outputDir) {
private val atomicConstructors = mutableSetOf<String>()
- private val fieldDelegates = mutableMapOf<String, String>()
- private val delegatedProperties = mutableMapOf<String, String>()
+ private val delegateToOriginalAtomicField = mutableMapOf<String, Name>()
+ private val topLevelDelegatedFieldAccessorToOriginalField = mutableMapOf<String, Name>()
private val atomicArrayConstructors = mutableMapOf<String, String?>()
private val traceConstructors = mutableSetOf<String>()
private val traceFormatObjects = mutableSetOf<String>()
@@ -81,6 +81,7 @@ class AtomicFUTransformerJS(
root.visit(AtomicConstructorDetector())
root.visit(FieldDelegatesVisitor())
root.visit(DelegatedPropertyAccessorsVisitor())
+ root.visit(TopLevelDelegatedFieldsAccessorVisitor())
root.visit(TransformVisitor())
root.visit(AtomicOperationsInliner())
return root.eraseGetValue().toByteArray()
@@ -283,12 +284,17 @@ class AtomicFUTransformerJS(
if (stmt is ExpressionStatement) {
if (stmt.expression is Assignment) {
val delegateAssignment = stmt.expression as Assignment
- if (delegateAssignment.right is PropertyGet) {
- val initializer = delegateAssignment.right as PropertyGet
- if (initializer.toSource() == atomicField.toSource()) {
- // register field delegate and the original atomic field
- fieldDelegates[(delegateAssignment.left as PropertyGet).property.toSource()] =
- (atomicField as PropertyGet).property.toSource()
+ 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
}
}
}
@@ -303,22 +309,74 @@ class AtomicFUTransformerJS(
inner class DelegatedPropertyAccessorsVisitor : NodeVisitor {
override fun visit(node: AstNode?): Boolean {
- if (node is PropertyGet) {
- if (node.target is PropertyGet) {
- if ((node.target as PropertyGet).property.toSource() in fieldDelegates && node.property.toSource() == MANGLED_VALUE_PROP) {
- if (node.parent is ReturnStatement) {
- val getter = ((((node.parent.parent as? Block)?.parent as? FunctionNode)?.parent as? ObjectProperty)?.parent as? ObjectLiteral)
- ?: abort("Incorrect tree structure of the accessor for the property delegated " +
- "to the atomic field ${fieldDelegates[node.target.toSource()]}")
- val definePropertyCall = getter.parent as FunctionCall
- val stringLiteral = definePropertyCall.arguments[1] as? StringLiteral
- ?: abort ("Object.defineProperty invocation should take a property name as the second argument")
- val delegatedProperty = stringLiteral.value.toString()
- delegatedProperties[delegatedProperty] = (node.target as PropertyGet).property.toSource()
+ // 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
}
@@ -388,12 +446,6 @@ class AtomicFUTransformerJS(
rr.receiver?.let { node.target = it }
}
}
- if (node.property.toSource() in delegatedProperties) {
- // replace delegated property name with the name of the original atomic field
- val fieldDelegate = delegatedProperties[node.property.toSource()]
- val originalField = fieldDelegates[fieldDelegate]!!
- node.property = Name().apply { identifier = originalField }
- }
// 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 }
diff --git a/atomicfu/src/commonTest/kotlin/kotlinx/atomicfu/test/DelegatedPropertiesTest.kt b/atomicfu/src/commonTest/kotlin/kotlinx/atomicfu/test/DelegatedPropertiesTest.kt
index 1b24e6d..4521c09 100644
--- a/atomicfu/src/commonTest/kotlin/kotlinx/atomicfu/test/DelegatedPropertiesTest.kt
+++ b/atomicfu/src/commonTest/kotlin/kotlinx/atomicfu/test/DelegatedPropertiesTest.kt
@@ -5,8 +5,27 @@ package kotlinx.atomicfu.test
import kotlinx.atomicfu.atomic
import kotlin.test.*
-class DelegatedProperties {
+private val topLevelIntOriginalAtomic = atomic(77)
+var topLevelIntDelegatedProperty: Int by topLevelIntOriginalAtomic
+
+private val _topLevelLong = atomic(55555555555)
+var topLevelDelegatedPropertyLong: Long by _topLevelLong
+
+private val _topLevelBoolean = atomic(false)
+var topLevelDelegatedPropertyBoolean: Boolean by _topLevelBoolean
+
+private val _topLevelRef = atomic(listOf("a", "b"))
+var topLevelDelegatedPropertyRef: List<String> by _topLevelRef
+
+var vTopLevelInt by atomic(77)
+
+var vTopLevelLong by atomic(777777777)
+var vTopLevelBoolean by atomic(false)
+
+var vTopLevelRef by atomic(listOf("a", "b"))
+
+class DelegatedProperties {
private val _a = atomic(42)
var a: Int by _a
@@ -99,6 +118,76 @@ class DelegatedProperties {
assertEquals(99, vRef.b.n)
}
+ @Test
+ fun testTopLevelDelegatedPropertiesInt() {
+ assertEquals(77, topLevelIntDelegatedProperty)
+ topLevelIntOriginalAtomic.compareAndSet(77, 56)
+ assertEquals(56, topLevelIntDelegatedProperty)
+ topLevelIntDelegatedProperty = 88
+ topLevelIntOriginalAtomic.compareAndSet(88, 66)
+ assertEquals(66, topLevelIntOriginalAtomic.value)
+ assertEquals(66, topLevelIntDelegatedProperty)
+ }
+
+ @Test
+ fun testTopLevelDelegatedPropertiesLong() {
+ assertEquals(55555555555, topLevelDelegatedPropertyLong)
+ _topLevelLong.getAndIncrement()
+ assertEquals(55555555556, topLevelDelegatedPropertyLong)
+ topLevelDelegatedPropertyLong = 7777777777777
+ assertTrue(_topLevelLong.compareAndSet(7777777777777, 66666666666))
+ assertEquals(66666666666, _topLevelLong.value)
+ assertEquals(66666666666, topLevelDelegatedPropertyLong)
+ }
+
+ @Test
+ fun testTopLevelDelegatedPropertiesBoolean() {
+ assertEquals(false, topLevelDelegatedPropertyBoolean)
+ _topLevelBoolean.lazySet(true)
+ assertEquals(true, topLevelDelegatedPropertyBoolean)
+ topLevelDelegatedPropertyBoolean = false
+ assertTrue(_topLevelBoolean.compareAndSet(false, true))
+ assertEquals(true, _topLevelBoolean.value)
+ assertEquals(true, topLevelDelegatedPropertyBoolean)
+ }
+
+ @Test
+ fun testTopLevelDelegatedPropertiesRef() {
+ assertEquals("b", topLevelDelegatedPropertyRef[1])
+ _topLevelRef.lazySet(listOf("c"))
+ assertEquals("c", topLevelDelegatedPropertyRef[0])
+ topLevelDelegatedPropertyRef = listOf("d", "e")
+ assertEquals("e", _topLevelRef.value[1])
+ }
+
+ @Test
+ fun testVolatileTopLevelInt() {
+ assertEquals(77, vTopLevelInt)
+ vTopLevelInt = 55
+ assertEquals(110, vTopLevelInt * 2)
+ }
+
+ @Test
+ fun testVolatileTopLevelLong() {
+ assertEquals(777777777, vTopLevelLong)
+ vTopLevelLong = 55
+ assertEquals(55, vTopLevelLong)
+ }
+
+ @Test
+ fun testVolatileTopLevelBoolean() {
+ assertEquals(false, vTopLevelBoolean)
+ vTopLevelBoolean = true
+ assertEquals(true, vTopLevelBoolean)
+ }
+
+ @Test
+ fun testVolatileTopLevelRef() {
+ assertEquals("a", vTopLevelRef[0])
+ vTopLevelRef = listOf("c")
+ assertEquals("c", vTopLevelRef[0])
+ }
+
class A (val b: B)
class B (val n: Int)
}
@@ -144,4 +233,25 @@ class ExposedDelegatedPropertiesAccessorsTest {
cl.vInt = 99
assertEquals(99, cl.vInt)
}
+}
+
+class ClashedNamesTest {
+ private class A1 {
+ val _a = atomic(0)
+ val a: Int by _a
+ }
+
+ private class A2 {
+ val _a = atomic(0)
+ val a: Int by _a
+ }
+
+ @Test
+ fun testClashedDelegatedPropertiesNames() {
+ val a1Class = A1()
+ val a2Class = A2()
+ a1Class._a.compareAndSet(0, 77)
+ assertEquals(77, a1Class.a)
+ assertEquals(0, a2Class.a)
+ }
} \ No newline at end of file