Skip to content

Commit f30ccba

Browse files
SokolovaMariaelizarov
authored andcommitted
Supported unchecked cast erasure (including array elements)
1 parent c65515b commit f30ccba

File tree

3 files changed

+93
-2
lines changed

3 files changed

+93
-2
lines changed

atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ private val INT_ARRAY_TYPE = getType("[I")
2828
private val LONG_ARRAY_TYPE = getType("[J")
2929
private val BOOLEAN_ARRAY_TYPE = getType("[Z")
3030
private val REF_ARRAY_TYPE = getType("[Ljava/lang/Object;")
31+
private val REF_TYPE = getType("L$AFU_PKG/AtomicRef;")
32+
private val ATOMIC_ARRAY_TYPE = getType("L$AFU_PKG/AtomicArray;")
3133

3234
private val AFU_CLASSES: Map<String, TypeInfo> = mapOf(
3335
"$AFU_PKG/AtomicInt" to TypeInfo(getObjectType("$JUCA_PKG/AtomicIntegerFieldUpdater"), INT_TYPE, INT_TYPE),
@@ -798,12 +800,29 @@ class AtomicFUTransformer(
798800
iv.desc = getMethodDescriptor(ret, OBJECT_TYPE, *args)
799801
}
800802

803+
private fun tryEraseUncheckedCast(getter: AbstractInsnNode) {
804+
if (getter.next.opcode == DUP && getter.next.next.opcode == IFNONNULL) {
805+
// unchecked cast upon AtomicRef var is performed
806+
// erase compiler check for this var being not null:
807+
// (remove all insns from ld till the non null branch label)
808+
val ifnonnull = (getter.next.next as JumpInsnNode)
809+
var i: AbstractInsnNode = getter.next
810+
while (!(i is LabelNode && i.label == ifnonnull.label.label)) {
811+
val next = i.next
812+
instructions.remove(i)
813+
i = next
814+
}
815+
}
816+
}
817+
801818
private fun fixupLoadedAtomicVar(f: FieldInfo, ld: FieldInsnNode): AbstractInsnNode? {
819+
if (f.fieldType == REF_TYPE) tryEraseUncheckedCast(ld)
802820
val j = FlowAnalyzer(ld.next).execute()
803821
return fixupOperationOnAtomicVar(j, f, ld, null)
804822
}
805823

806824
private fun fixupLoadedArrayElement(f: FieldInfo, ld: FieldInsnNode, getter: MethodInsnNode): AbstractInsnNode? {
825+
if (f.fieldType == ATOMIC_ARRAY_TYPE) tryEraseUncheckedCast(getter)
807826
// contains array field load (in vh case: + swap and pure type array load) and array element index
808827
// this array element information is only used in case the reference to this element is stored (copied and inserted at the point of loading)
809828
val arrayElementInfo = mutableListOf<AbstractInsnNode>()
@@ -880,6 +899,7 @@ class AtomicFUTransformer(
880899
}
881900

882901
private fun fixupArrayElementLoad(f: FieldInfo, ld: FieldInsnNode, otherLd: VarInsnNode, arrayElementInfo: List<AbstractInsnNode>): AbstractInsnNode? {
902+
if (f.fieldType == ATOMIC_ARRAY_TYPE) tryEraseUncheckedCast(otherLd)
883903
// index instructions from array element info: drop owner class load instruction (in vh case together with preceding getting VH + swap)
884904
val index = arrayElementInfo.drop(if (vh) 3 else 1)
885905
// previously stored array element reference is loaded -> arrayElementInfo should be cloned and inserted at the point of this load

atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformerJS.kt

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ private const val SCOPE = "scope"
2020
private const val FACTORY = "factory"
2121
private const val REQUIRE = "require"
2222
private const val KOTLINX_ATOMICFU = "'kotlinx-atomicfu'"
23+
private const val KOTLIN_TYPE_CHECK = "Kotlin.isType"
24+
private const val ATOMIC_REF = "AtomicRef"
2325
private const val MODULE_KOTLINX_ATOMICFU = "\$module\$kotlinx_atomicfu"
2426
private const val ARRAY = "Array"
2527
private const val FILL = "fill"
@@ -266,8 +268,10 @@ class AtomicFUTransformerJS(
266268
}
267269
}
268270
// remove value property call
269-
if (node.type == Token.GETPROP) {
270-
if ((node as PropertyGet).property.toSource() == MANGLED_VALUE_PROP) {
271+
if (node is PropertyGet) {
272+
if (node.property.toSource() == MANGLED_VALUE_PROP) {
273+
// check whether atomic operation is performed on the type casted atomic field
274+
node.target.eraseAtomicFieldFromUncheckedCast()?.let { node.target = it }
271275
// A.a.value
272276
if (node.target.type == Token.GETPROP) {
273277
val clearField = node.target as PropertyGet
@@ -335,6 +339,7 @@ class AtomicFUTransformerJS(
335339
field = rr.receiver
336340
}
337341
}
342+
field.eraseAtomicFieldFromUncheckedCast()?.let { field = it }
338343
val args = node.arguments
339344
val inlined = node.inlineAtomicOperation(funcName.toSource(), field, args)
340345
return !inlined
@@ -344,6 +349,21 @@ class AtomicFUTransformerJS(
344349
}
345350
}
346351

352+
private fun AstNode.eraseAtomicFieldFromUncheckedCast(): AstNode? {
353+
if (this is ParenthesizedExpression && expression is ConditionalExpression) {
354+
val testExpression = (expression as ConditionalExpression).testExpression
355+
if (testExpression is FunctionCall && testExpression.target.toSource() == KOTLIN_TYPE_CHECK) {
356+
// type check
357+
val typeToCast = testExpression.arguments[1]
358+
if ((typeToCast as Name).identifier == ATOMIC_REF) {
359+
// unchecked type cast -> erase atomic field itself
360+
return (testExpression.arguments[0] as Assignment).right
361+
}
362+
}
363+
}
364+
return null
365+
}
366+
347367
private fun AstNode.isThisNode(): Boolean {
348368
return when(this) {
349369
is PropertyGet -> {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package kotlinx.atomicfu.test
2+
3+
import kotlinx.atomicfu.AtomicRef
4+
import kotlinx.atomicfu.atomic
5+
import kotlinx.atomicfu.atomicArrayOfNulls
6+
import kotlin.test.Test
7+
import kotlin.test.assertEquals
8+
import kotlin.test.assertTrue
9+
10+
private val topLevelS = atomic<Any>(listOf("A", "B"))
11+
12+
class UncheckedCastTest {
13+
private val s = atomic<Any>("AAA")
14+
private val bs = atomic<Any?>(null)
15+
16+
@Test
17+
fun testAtomicValUncheckedCast() {
18+
assertEquals((s as AtomicRef<String>).value, "AAA")
19+
bs.lazySet(mapOf(1 to listOf(Box(1), Box(2))))
20+
assertEquals((bs as AtomicRef<Map<Int, List<Box>>>).value[1]!![0].b * 10, 10)
21+
}
22+
23+
@Test
24+
fun testTopLevelValUnchekedCast() {
25+
assertEquals((topLevelS as AtomicRef<List<String>>).value[1], "B")
26+
}
27+
28+
private data class Box(val b: Int)
29+
30+
private inline fun <T> AtomicRef<T>.getString(): String =
31+
(this as AtomicRef<String>).value
32+
33+
@Test
34+
fun testInlineFunc() {
35+
assertEquals("AAA", s.getString())
36+
}
37+
38+
private val a = atomicArrayOfNulls<Any?>(10)
39+
40+
@Test
41+
fun testArrayValueUncheckedCast() {
42+
a[0].value = "OK"
43+
assertEquals("OK", (a[0] as AtomicRef<String>).value)
44+
}
45+
46+
@Test
47+
fun testArrayValueUncheckedCastInlineFunc() {
48+
a[0].value = "OK"
49+
assertEquals("OK", a[0].getString())
50+
}
51+
}

0 commit comments

Comments
 (0)