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 ba08fbc3..0687f384 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 b8f804f1..ca6c1436 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() private val traceAccessors = mutableSetOf() private val fieldDelegates = mutableMapOf() + private val delegatedPropertiesAccessors = mutableMapOf() private val removeMethods = mutableSetOf() 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? + ) : 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? ) : 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 $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 $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 == "")) } - 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 91f30372..de8f94a7 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() - private val fieldDelegates = mutableMapOf() - private val delegatedProperties = mutableMapOf() + private val delegateToOriginalAtomicField = mutableMapOf() + private val topLevelDelegatedFieldAccessorToOriginalField = mutableMapOf() private val atomicArrayConstructors = mutableMapOf() private val traceConstructors = mutableSetOf() private val traceFormatObjects = mutableSetOf() @@ -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 1b24e6d5..4521c09e 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 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