Skip to content

Commit 196b345

Browse files
authored
Show the field name in the trace when calling a method on the final field (#327)
1 parent dcb2fa9 commit 196b345

13 files changed

+316
-335
lines changed

src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/AtomicReferenceNames.kt

Lines changed: 4 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,8 @@ package org.jetbrains.kotlinx.lincheck.strategy.managed
1313
import kotlinx.atomicfu.AtomicArray
1414
import kotlinx.atomicfu.AtomicBooleanArray
1515
import kotlinx.atomicfu.AtomicIntArray
16-
import org.jetbrains.kotlinx.lincheck.allDeclaredFieldWithSuperclasses
1716
import org.jetbrains.kotlinx.lincheck.strategy.managed.AtomicReferenceMethodType.*
18-
import org.jetbrains.kotlinx.lincheck.strategy.managed.AtomicReferenceNames.AtomicReferenceOwnerWithName.*
19-
import org.jetbrains.kotlinx.lincheck.strategy.managed.AtomicReferenceNames.TraverseResult.*
20-
import java.lang.reflect.Modifier
17+
import org.jetbrains.kotlinx.lincheck.strategy.managed.OwnerWithName.*
2118
import java.util.*
2219
import java.util.concurrent.atomic.AtomicIntegerArray
2320
import java.util.concurrent.atomic.AtomicLongArray
@@ -37,16 +34,16 @@ internal object AtomicReferenceNames {
3734
atomicReference: Any,
3835
parameters: Array<Any?>
3936
): AtomicReferenceMethodType {
40-
val receiverAndName = getAtomicReferenceReceiverAndName(testObject, atomicReference)
37+
val receiverAndName = FieldSearchHelper.findFinalFieldWithOwner(testObject, atomicReference)
4138
return if (receiverAndName != null) {
4239
if (isAtomicArrayIndexMethodCall(atomicReference, parameters)) {
4340
when (receiverAndName) {
44-
is InstanceOwnerWithName -> InstanceFieldAtomicArrayMethod(receiverAndName.receiver, receiverAndName.fieldName, parameters[0] as Int)
41+
is InstanceOwnerWithName -> InstanceFieldAtomicArrayMethod(receiverAndName.owner, receiverAndName.fieldName, parameters[0] as Int)
4542
is StaticOwnerWithName -> StaticFieldAtomicArrayMethod(receiverAndName.clazz, receiverAndName.fieldName, parameters[0] as Int)
4643
}
4744
} else {
4845
when (receiverAndName) {
49-
is InstanceOwnerWithName -> AtomicReferenceInstanceMethod(receiverAndName.receiver, receiverAndName.fieldName)
46+
is InstanceOwnerWithName -> AtomicReferenceInstanceMethod(receiverAndName.owner, receiverAndName.fieldName)
5047
is StaticOwnerWithName -> AtomicReferenceStaticMethod(receiverAndName.clazz, receiverAndName.fieldName)
5148
}
5249
}
@@ -69,70 +66,7 @@ internal object AtomicReferenceNames {
6966
atomicReference is AtomicBooleanArray
7067
}
7168

72-
private fun getAtomicReferenceReceiverAndName(testObject: Any, reference: Any): AtomicReferenceOwnerWithName? =
73-
runCatching {
74-
val visitedObjects: MutableSet<Any> = Collections.newSetFromMap(IdentityHashMap())
75-
return when (val result = findObjectField(testObject, reference, visitedObjects)) {
76-
is FieldName -> result.fieldName
77-
MultipleFieldsMatching, NotFound -> null
78-
}
79-
}.getOrElse { exception ->
80-
exception.printStackTrace()
81-
null
82-
}
83-
84-
private sealed interface TraverseResult {
85-
data object NotFound : TraverseResult
86-
data class FieldName(val fieldName: AtomicReferenceOwnerWithName) : TraverseResult
87-
data object MultipleFieldsMatching : TraverseResult
88-
}
89-
90-
private fun findObjectField(testObject: Any?, value: Any, visitedObjects: MutableSet<Any>): TraverseResult {
91-
if (testObject == null) return NotFound
92-
var fieldName: AtomicReferenceOwnerWithName? = null
93-
// We take all the fields from the hierarchy.
94-
// If two or more fields match (===) the AtomicReference object, we fall back to the default behavior,
95-
// so there is no problem that we can receive some fields of the same name and the same type.
96-
for (field in testObject::class.java.allDeclaredFieldWithSuperclasses) {
97-
if (field.type.isPrimitive || !field.trySetAccessible()) continue
98-
val fieldValue = field.get(testObject)
9969

100-
if (fieldValue in visitedObjects) continue
101-
visitedObjects += testObject
102-
103-
if (fieldValue === value) {
104-
if (fieldName != null) return MultipleFieldsMatching
105-
106-
fieldName = if (Modifier.isStatic(field.modifiers)) {
107-
StaticOwnerWithName(field.name, testObject::class.java)
108-
} else {
109-
InstanceOwnerWithName(field.name, testObject)
110-
}
111-
continue
112-
}
113-
when (val result = findObjectField(fieldValue, value, visitedObjects)) {
114-
is FieldName -> {
115-
if (fieldName != null) {
116-
return MultipleFieldsMatching
117-
} else {
118-
fieldName = result.fieldName
119-
}
120-
}
121-
122-
MultipleFieldsMatching -> return result
123-
NotFound -> {}
124-
}
125-
}
126-
return if (fieldName != null) FieldName(fieldName) else NotFound
127-
}
128-
129-
private sealed class AtomicReferenceOwnerWithName(val fieldName: String) {
130-
class StaticOwnerWithName(fieldName: String, val clazz: Class<*>) :
131-
AtomicReferenceOwnerWithName(fieldName)
132-
133-
class InstanceOwnerWithName(fieldName: String, val receiver: Any) :
134-
AtomicReferenceOwnerWithName(fieldName)
135-
}
13670
}
13771

13872
/**
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Lincheck
3+
*
4+
* Copyright (C) 2019 - 2024 JetBrains s.r.o.
5+
*
6+
* This Source Code Form is subject to the terms of the
7+
* Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
8+
* with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
9+
*/
10+
11+
package org.jetbrains.kotlinx.lincheck.strategy.managed
12+
13+
import kotlinx.atomicfu.AtomicArray
14+
import kotlinx.atomicfu.AtomicRef
15+
import org.jetbrains.kotlinx.lincheck.allDeclaredFieldWithSuperclasses
16+
import org.jetbrains.kotlinx.lincheck.strategy.managed.FieldSearchHelper.TraverseResult.*
17+
import org.jetbrains.kotlinx.lincheck.strategy.managed.OwnerWithName.*
18+
import org.jetbrains.kotlinx.lincheck.util.readFieldViaUnsafe
19+
import sun.misc.Unsafe
20+
import java.lang.reflect.Modifier
21+
import java.util.*
22+
import java.util.concurrent.atomic.AtomicReference
23+
import java.util.concurrent.atomic.AtomicReferenceArray
24+
25+
26+
/**
27+
* Utility class that helps to determine if the provided field stored in only one
28+
* final field of a tested object.
29+
*/
30+
internal object FieldSearchHelper {
31+
32+
/**
33+
* Determines if the [value] is stored in the only one field of the [testObject] and this
34+
* field is final.
35+
* In case the [value] is not found or accessible by multiple fields, the function returns `null`.
36+
*/
37+
internal fun findFinalFieldWithOwner(testObject: Any, value: Any): OwnerWithName? = runCatching {
38+
val visitedObjects: MutableSet<Any> = Collections.newSetFromMap(IdentityHashMap())
39+
return when (val result = findObjectField(testObject, value, visitedObjects)) {
40+
is FieldName -> result.field
41+
MultipleFieldsMatching, NotFound, FoundInNonFinalField -> null
42+
}
43+
}.getOrElse { exception ->
44+
exception.printStackTrace()
45+
null
46+
}
47+
48+
private sealed interface TraverseResult {
49+
data object NotFound : TraverseResult
50+
data class FieldName(val field: OwnerWithName) : TraverseResult
51+
data object MultipleFieldsMatching : TraverseResult
52+
data object FoundInNonFinalField: TraverseResult
53+
}
54+
55+
private fun findObjectField(testObject: Any?, value: Any, visitedObjects: MutableSet<Any>): TraverseResult {
56+
if (testObject == null) return NotFound
57+
var fieldName: OwnerWithName? = null
58+
// We take all the fields from the hierarchy.
59+
// If two or more fields match (===) the AtomicReference object, we fall back to the default behavior,
60+
// so there is no problem that we can receive some fields of the same name and the same type.
61+
for (field in testObject::class.java.allDeclaredFieldWithSuperclasses) {
62+
if (field.type.isPrimitive) continue
63+
val fieldValue = readFieldViaUnsafe(testObject, field, Unsafe::getObject)
64+
65+
if (fieldValue in visitedObjects) continue
66+
visitedObjects += testObject
67+
68+
if (fieldValue === value) {
69+
if (fieldName != null) return MultipleFieldsMatching
70+
if (!Modifier.isFinal(field.modifiers)) return FoundInNonFinalField
71+
72+
fieldName = if (Modifier.isStatic(field.modifiers)) {
73+
StaticOwnerWithName(field.name, testObject::class.java)
74+
} else {
75+
InstanceOwnerWithName(field.name, testObject)
76+
}
77+
continue
78+
}
79+
when (val result = findObjectField(fieldValue, value, visitedObjects)) {
80+
is FieldName -> {
81+
if (fieldName != null) {
82+
return MultipleFieldsMatching
83+
} else {
84+
fieldName = result.field
85+
}
86+
}
87+
88+
MultipleFieldsMatching, FoundInNonFinalField -> return result
89+
NotFound -> {}
90+
}
91+
}
92+
return if (fieldName != null) FieldName(fieldName) else NotFound
93+
}
94+
95+
}
96+
97+
internal sealed class OwnerWithName(val fieldName: String) {
98+
class StaticOwnerWithName(fieldName: String, val clazz: Class<*>) :
99+
OwnerWithName(fieldName)
100+
101+
class InstanceOwnerWithName(fieldName: String, val owner: Any) :
102+
OwnerWithName(fieldName)
103+
}

src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import sun.nio.ch.lincheck.*
2424
import kotlinx.coroutines.*
2525
import org.jetbrains.kotlinx.lincheck.strategy.managed.AtomicFieldUpdaterNames.getAtomicFieldUpdaterName
2626
import org.jetbrains.kotlinx.lincheck.strategy.managed.AtomicReferenceMethodType.*
27+
import org.jetbrains.kotlinx.lincheck.strategy.managed.FieldSearchHelper.findFinalFieldWithOwner
2728
import org.jetbrains.kotlinx.lincheck.strategy.managed.ObjectLabelFactory.adornedStringRepresentation
2829
import org.jetbrains.kotlinx.lincheck.strategy.managed.ObjectLabelFactory.cleanObjectNumeration
2930
import org.jetbrains.kotlinx.lincheck.strategy.managed.UnsafeName.*
@@ -1293,10 +1294,23 @@ abstract class ManagedStrategy(
12931294
/**
12941295
* Returns beautiful string representation of the [owner].
12951296
* If the [owner] is `this` of the current method, then returns `null`.
1297+
* Otherwise, we try to find if this [owner] is stored in only one field in the testObject
1298+
* and this field is final. If such field is found we construct beautiful representation for
1299+
* this field owner (if it's not a current `this`, again) and the field name.
1300+
* Otherwise, return beautiful representation for the provided [owner].
12961301
*/
12971302
private fun findOwnerName(owner: Any): String? {
1303+
// If the current owner is this - no owner needed.
12981304
if (isOwnerCurrentContext(owner)) return null
1299-
return adornedStringRepresentation(owner)
1305+
val fieldWithOwner = findFinalFieldWithOwner(runner.testInstance, owner) ?: return adornedStringRepresentation(owner)
1306+
// If such a field is found - construct representation with its owner and name.
1307+
return if (fieldWithOwner is OwnerWithName.InstanceOwnerWithName) {
1308+
val fieldOwner = fieldWithOwner.owner
1309+
val fieldName = fieldWithOwner.fieldName
1310+
if (!isOwnerCurrentContext(fieldOwner)) {
1311+
"${adornedStringRepresentation(fieldOwner)}.$fieldName"
1312+
} else fieldName
1313+
} else null
13001314
}
13011315

13021316
/**

src/jvm/test/org/jetbrains/kotlinx/lincheck_test/representation/AtomicReferencesNamesTests.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@ import java.util.concurrent.atomic.*
1414

1515
class AtomicReferencesNamesTest : BaseFailingTest("atomic_references_names_trace.txt") {
1616

17-
private var atomicReference = AtomicReference(Node(1))
18-
private var atomicInteger = AtomicInteger(0)
19-
private var atomicLong = AtomicLong(0L)
20-
private var atomicBoolean = AtomicBoolean(true)
17+
private val atomicReference = AtomicReference(Node(1))
18+
private val atomicInteger = AtomicInteger(0)
19+
private val atomicLong = AtomicLong(0L)
20+
private val atomicBoolean = AtomicBoolean(true)
2121

22-
private var atomicReferenceArray = AtomicReferenceArray(arrayOf(Node(1)))
23-
private var atomicIntegerArray = AtomicIntegerArray(intArrayOf(0))
24-
private var atomicLongArray = AtomicLongArray(longArrayOf(0L))
22+
private val atomicReferenceArray = AtomicReferenceArray(arrayOf(Node(1)))
23+
private val atomicIntegerArray = AtomicIntegerArray(intArrayOf(0))
24+
private val atomicLongArray = AtomicLongArray(longArrayOf(0L))
2525

26-
private var wrapper = AtomicReferenceWrapper()
26+
private val wrapper = AtomicReferenceWrapper()
2727

2828
override fun actionsForTrace() {
2929
atomicReference.compareAndSet(atomicReference.get(), Node(2))

src/jvm/test/org/jetbrains/kotlinx/lincheck_test/representation/InterleavingAnalysisPresentInSpinCycleFirstIterationTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ import java.util.concurrent.atomic.AtomicInteger
2828
class InterleavingAnalysisPresentInSpinCycleFirstIterationTest {
2929

3030
// Counter thar causes spin-lock in spinLock operation
31-
private var counter = AtomicInteger(0)
31+
private val counter = AtomicInteger(0)
3232
// Trigger to increment and decrement in spin-cycle to check in causeSpinLock operation
33-
private var shouldAlwaysBeZero = AtomicInteger(0)
34-
private var illegalInterleavingFound = AtomicBoolean(false)
33+
private val shouldAlwaysBeZero = AtomicInteger(0)
34+
private val illegalInterleavingFound = AtomicBoolean(false)
3535

3636
@Operation
3737
fun causeSpinLock() {

0 commit comments

Comments
 (0)