From 8b73ca2cd6563ad5a57b425d914c9994ba6abc06 Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Fri, 18 Oct 2024 01:22:38 +0200 Subject: [PATCH 01/29] Add static memory snapshot collecting and restoring --- .../kotlinx/lincheck/ObjectTraverser.kt | 1 - .../org/jetbrains/kotlinx/lincheck/Utils.kt | 43 ++++++++++++++++++- .../kotlinx/lincheck/strategy/Strategy.kt | 15 +++++-- .../strategy/managed/ManagedStrategy.kt | 14 ++++++ .../kotlinx/lincheck/util/UnsafeHolder.kt | 30 +++++++++++++ 5 files changed, 96 insertions(+), 7 deletions(-) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/ObjectTraverser.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/ObjectTraverser.kt index 737efa6bd..009dd9d94 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/ObjectTraverser.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/ObjectTraverser.kt @@ -22,7 +22,6 @@ package org.jetbrains.kotlinx.lincheck import org.jetbrains.kotlinx.lincheck.strategy.managed.ObjectLabelFactory.getObjectNumber import org.jetbrains.kotlinx.lincheck.util.* -import java.lang.reflect.Modifier import java.math.BigDecimal import java.math.BigInteger import kotlin.coroutines.Continuation diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt index 69eb5c4d6..06a8f4631 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt @@ -10,13 +10,12 @@ package org.jetbrains.kotlinx.lincheck import kotlinx.coroutines.* -import sun.nio.ch.lincheck.* import org.jetbrains.kotlinx.lincheck.runner.* import org.jetbrains.kotlinx.lincheck.strategy.managed.* import org.jetbrains.kotlinx.lincheck.transformation.LincheckClassFileTransformer import org.jetbrains.kotlinx.lincheck.util.UnsafeHolder import org.jetbrains.kotlinx.lincheck.verifier.* -import org.jetbrains.kotlinx.lincheck.util.* +import sun.nio.ch.lincheck.* import java.io.PrintWriter import java.io.StringWriter import java.lang.ref.* @@ -213,6 +212,46 @@ internal val Throwable.text: String get() { return writer.buffer.toString() } +/** + * Finds a public/protected/private/internal field in the class and its superclasses/interfaces by name. + * + * @param fieldName the name of the field to find. + * @return the [java.lang.reflect.Field] object if found, or `null` if not found. + */ +fun Class<*>.findField(fieldName: String): Field { + // Search in the class hierarchy + var clazz: Class<*>? = this + while (clazz != null) { + // Check class itself + try { + return clazz.getDeclaredField(fieldName) + } + catch (_: NoSuchFieldException) {} + + // Check interfaces + for (interfaceClass in clazz.interfaces) { + try { + return interfaceClass.getDeclaredField(fieldName) + } + catch (_: NoSuchFieldException) {} + } + + // Move up the hierarchy + clazz = clazz.superclass + } + + throw NoSuchFieldException("Class '${this.name}' does not have field '$fieldName'") +} + +@Suppress("DEPRECATION") +fun getFieldOffset(field: Field): Long { + return if (Modifier.isStatic(field.modifiers)) { + UnsafeHolder.UNSAFE.staticFieldOffset(field) + } + else { + UnsafeHolder.UNSAFE.objectFieldOffset(field) + } +} @Suppress("DEPRECATION") internal fun findFieldNameByOffset(targetType: Class<*>, offset: Long): String? { diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt index d388e70a5..6f46a160d 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt @@ -90,6 +90,8 @@ abstract class Strategy protected constructor( override fun close() { runner.close() } + + open fun restoreStaticMemorySnapshot() {} } /** @@ -101,15 +103,20 @@ abstract class Strategy protected constructor( * @return the failure, if detected, null otherwise. */ fun Strategy.runIteration(invocations: Int, verifier: Verifier): LincheckFailure? { + var failure: LincheckFailure? = null + for (invocation in 0 until invocations) { if (!nextInvocation()) - return null + break val result = runInvocation() - val failure = verify(result, verifier) + restoreStaticMemorySnapshot() + failure = verify(result, verifier) if (failure != null) - return failure + break } - return null + + restoreStaticMemorySnapshot() + return failure } /** diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt index fa8fd92d1..042593d36 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt @@ -82,6 +82,9 @@ abstract class ManagedStrategy( // Tracker of the thread parking. protected abstract val parkingTracker: ParkingTracker + // Snapshot of the memory, reachable from static fields + protected val staticMemorySnapshot = SnapshotTracker() + // InvocationResult that was observed by the strategy during the execution (e.g., a deadlock). @Volatile protected var suddenInvocationResult: InvocationResult? = null @@ -216,12 +219,19 @@ abstract class ManagedStrategy( parkingTracker.reset() } + override fun restoreStaticMemorySnapshot() { + // TODO: what is the appropriate location to call this function? + staticMemorySnapshot.restoreValues() + super.restoreStaticMemorySnapshot() + } + /** * Runs the current invocation. */ override fun runInvocation(): InvocationResult { while (true) { initializeInvocation() + staticMemorySnapshot.restoreValues() val result = runner.run() // In case the runner detects a deadlock, some threads can still manipulate the current strategy, // so we're not interested in suddenInvocationResult in this case @@ -746,6 +756,7 @@ abstract class ManagedStrategy( // The following call checks all the static fields. if (isStatic) { LincheckJavaAgent.ensureClassHierarchyIsTransformed(className.canonicalClassName) + staticMemorySnapshot.addHierarchy(className.canonicalClassName, fieldName) } // Optimization: do not track final field reads if (isFinal) { @@ -817,6 +828,9 @@ abstract class ManagedStrategy( if (!objectTracker.shouldTrackObjectAccess(obj ?: StaticObject)) { return@runInIgnoredSection false } + if (isStatic) { + staticMemorySnapshot.addHierarchy(className.canonicalClassName, fieldName) + } // Optimization: do not track final field writes if (isFinal) { return@runInIgnoredSection false diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/UnsafeHolder.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/UnsafeHolder.kt index 7bda522fd..613531f82 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/UnsafeHolder.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/UnsafeHolder.kt @@ -79,4 +79,34 @@ internal fun getArrayElementOffsetViaUnsafe(arr: Any, index: Int): Long { val baseOffset = UnsafeHolder.UNSAFE.arrayBaseOffset(clazz).toLong() val indexScale = UnsafeHolder.UNSAFE.arrayIndexScale(clazz).toLong() return baseOffset + index * indexScale +} + +@Suppress("DEPRECATION") +internal inline fun writeFieldViaUnsafe(obj: Any?, field: Field, value: Any?, setter: Unsafe.(Any?, Long, Any?) -> Unit) { + if (Modifier.isStatic(field.modifiers)) { + val base = UnsafeHolder.UNSAFE.staticFieldBase(field) + val offset = UnsafeHolder.UNSAFE.staticFieldOffset(field) + return UnsafeHolder.UNSAFE.setter(base, offset, value) + } else { + val offset = UnsafeHolder.UNSAFE.objectFieldOffset(field) + return UnsafeHolder.UNSAFE.setter(obj, offset, value) + } +} + +@Suppress("NAME_SHADOWING") +internal fun writeField(obj: Any?, field: Field, value: Any?) { + if (!field.type.isPrimitive) { + return writeFieldViaUnsafe(obj, field, value, Unsafe::putObject) + } + return when (field.type) { + Boolean::class.javaPrimitiveType -> writeFieldViaUnsafe(obj, field, value) { obj, field, value -> putBoolean(obj, field, value as Boolean) } + Byte::class.javaPrimitiveType -> writeFieldViaUnsafe(obj, field, value) { obj, field, value -> putByte(obj, field, value as Byte) } + Char::class.javaPrimitiveType -> writeFieldViaUnsafe(obj, field, value) { obj, field, value -> putChar(obj, field, value as Char) } + Short::class.javaPrimitiveType -> writeFieldViaUnsafe(obj, field, value) { obj, field, value -> putShort(obj, field, value as Short) } + Int::class.javaPrimitiveType -> writeFieldViaUnsafe(obj, field, value) { obj, field, value -> putInt(obj, field, value as Int) } + Long::class.javaPrimitiveType -> writeFieldViaUnsafe(obj, field, value) { obj, field, value -> putLong(obj, field, value as Long) } + Double::class.javaPrimitiveType -> writeFieldViaUnsafe(obj, field, value) { obj, field, value -> putDouble(obj, field, value as Double) } + Float::class.javaPrimitiveType -> writeFieldViaUnsafe(obj, field, value) { obj, field, value -> putFloat(obj, field, value as Float) } + else -> error("No more types expected") + } } \ No newline at end of file From ca7d02acab62923f1404dec2fc3ff3cd17485681 Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Fri, 18 Oct 2024 01:23:44 +0200 Subject: [PATCH 02/29] Add the class which implements the logic for static memory snapshot --- .../strategy/managed/SnapshotTracker.kt | 178 ++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt new file mode 100644 index 000000000..312c9b48f --- /dev/null +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt @@ -0,0 +1,178 @@ +/* + * Lincheck + * + * Copyright (C) 2019 - 2024 JetBrains s.r.o. + * + * This Source Code Form is subject to the terms of the + * Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed + * with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.jetbrains.kotlinx.lincheck.strategy.managed + +import org.jetbrains.kotlinx.lincheck.allDeclaredFieldWithSuperclasses +import org.jetbrains.kotlinx.lincheck.canonicalClassName +import org.jetbrains.kotlinx.lincheck.findField +import org.jetbrains.kotlinx.lincheck.getFieldOffset +import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.ArrayCellNode +import org.jetbrains.kotlinx.lincheck.util.readField +import org.jetbrains.kotlinx.lincheck.util.writeField +import java.lang.reflect.Field +import java.lang.reflect.Modifier + + +/** + * Manages a snapshot of the global static state. + * + * This class only tracks static memory and memory reachable from it, + * referenced from the test code. So the whole static state is not recorded, but only a subset of that, + * which is named *snapshot*. + */ +class SnapshotTracker { + private val snapshotRoots = mutableListOf() + private val trackedStaticFields = mutableSetOf() + + // TODO: is `className` always required to be declaringClassName (both for static and non-static fields)? + // right now `declaringClassName` is not calculated, which is incorrect, but I will fix it later + private data class NodeDescriptor( + val className: String, // name of the class which contains the field + val field: Field, + val offset: Long // for arrays, offset will be element index + ) { + override fun toString(): String { + return "NodeDescriptor(className=$className, field=${field}, offset=$offset)" + } + } + + private abstract class MemoryNode( + val descriptor: NodeDescriptor, + val initialValue: Any?, + val fields: MutableList = mutableListOf() /* list of fields/array cells that are inside this object */ + ) + private class StaticFieldNode(descriptor: NodeDescriptor, initialValue: Any?) : MemoryNode(descriptor, initialValue) + private class RegularFieldNode(descriptor: NodeDescriptor, initialValue: Any?) : MemoryNode(descriptor, initialValue) + private class ArrayCellNode(descriptor: NodeDescriptor, initialValue: Any?) : MemoryNode(descriptor, initialValue) + + /** + * Remembers the current value of the static variable passed to it and the values of all its fields. + * The values that will be observed from the provided static variable will be restored on + * subsequent call to [restoreValues]. + * + * If the provided static variable is already present in the graph, then nothing will happen. + */ + fun addHierarchy(className: String, fieldName: String) { + if (className.startsWith("java.")) return + check(className == className.canonicalClassName) { "Class name must be canonical" } + + val clazz = Class.forName(className) + val field = clazz.findField(fieldName) + val descriptor = NodeDescriptor(className, field, getFieldOffset(field)) + val initialValue = readField(null, descriptor.field) + + check(Modifier.isStatic(field.modifiers)) { "Root field in the snapshot hierarchy must be static" } + if (descriptor in trackedStaticFields) return + + val root = StaticFieldNode(descriptor, initialValue) + snapshotRoots.add(root) + addToGraph(root) + } + + /** + * Traverses all top-level static variables of the snapshot and restores all + * reachable fields to initial values recorded by previous calls to [addHierarchy]. + */ + fun restoreValues() { +// println("Restoring all memory reachable from static state to snapshot values") + + val visitedNodes = mutableSetOf() + snapshotRoots.forEach { rootNode -> + restoreValues(rootNode, null, visitedNodes) + } + } + + + private fun addToGraph(node: MemoryNode, visitedObjects: MutableSet = mutableSetOf()) { + if (node.initialValue in visitedObjects) return + + if (node is StaticFieldNode) { + if (trackedStaticFields.contains(node.descriptor)) return + trackedStaticFields.add(node.descriptor) + } + + val nodeClass = node.descriptor.field.let { + if (node is ArrayCellNode) it.type.componentType + else it.type + } + + if (nodeClass.isPrimitive || nodeClass.isEnum || node.initialValue == null) return + +// println( +// "Adding to hierarchy: ${nodeClass.canonicalName}::${node.descriptor.field.name} =" + +// node.initialValue + if (nodeClass.isArray && node !is ArrayCellNode) (node.initialValue as Array<*>).contentToString() else "" +// ) + + visitedObjects.add(node.initialValue) + + if (nodeClass.isArray && node !is ArrayCellNode) { + val array = node.initialValue as Array<*> + + for (index in array.indices) { + val childDescriptor = NodeDescriptor( + nodeClass.canonicalName, + node.descriptor.field, + index.toLong() + ) + + val childNode = ArrayCellNode(childDescriptor, array[index]) + + node.fields.add(childNode) + addToGraph(childNode, visitedObjects) + } + } + else { + nodeClass.allDeclaredFieldWithSuperclasses.forEach { field -> + val childDescriptor = NodeDescriptor(nodeClass.canonicalName, field, getFieldOffset(field)) + + val childNode = if (Modifier.isStatic(field.modifiers)) { + StaticFieldNode(childDescriptor, readField(null, childDescriptor.field)) + } else { + RegularFieldNode(childDescriptor, readField(node.initialValue, childDescriptor.field)) + } + + node.fields.add(childNode) + addToGraph(childNode, visitedObjects) + } + } + } + + private fun restoreValues(node: MemoryNode, parent: MemoryNode? = null, visitedNodes: MutableSet) { + if (node in visitedNodes) return + visitedNodes.add(node) + + if (!Modifier.isFinal(node.descriptor.field.modifiers)) { + val obj: Any? = + if (node is StaticFieldNode) null + else { + check(parent != null) { "Regular field in snapshot hierarchy must have a parent node" } + parent.initialValue + } + +// println( +// "Write to ${node.descriptor.className}::${node.descriptor.field.name} =" + +// node.initialValue + if (node.descriptor.field.type.isArray && node !is ArrayCellNode) (node.initialValue as Array<*>).contentToString() else "" +// ) + + if (node is ArrayCellNode) { + @Suppress("UNCHECKED_CAST") + val array = obj as Array + val index = node.descriptor.offset.toInt() + array[index] = node.initialValue + } + else { + writeField(obj, node.descriptor.field, node.initialValue) + } + } + + node.fields.forEach{ childNode -> restoreValues(childNode, node, visitedNodes) } + } +} \ No newline at end of file From 75126a689d15517b0cad00e8564969b442824419 Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Mon, 21 Oct 2024 22:05:21 +0200 Subject: [PATCH 03/29] Add tests and fixes to snapshot algorithm --- .../kotlinx/lincheck/CTestConfiguration.kt | 1 + .../org/jetbrains/kotlinx/lincheck/Utils.kt | 10 ++ .../kotlinx/lincheck/strategy/Strategy.kt | 8 ++ .../managed/ManagedCTestConfiguration.kt | 2 + .../strategy/managed/ManagedOptions.kt | 9 ++ .../strategy/managed/ManagedStrategy.kt | 19 +++- .../strategy/managed/SnapshotTracker.kt | 96 +++++++++++-------- .../modelchecking/ModelCheckingCTest.java | 5 + .../ModelCheckingCTestConfiguration.kt | 5 +- .../modelchecking/ModelCheckingOptions.kt | 1 + .../kotlinx/lincheck/util/UnsafeHolder.kt | 24 ++++- .../snapshot/SnapshotAbstractTest.kt | 23 +++++ .../modelchecking/snapshot/StaticArrayTest.kt | 74 ++++++++++++++ .../modelchecking/snapshot/StaticEnumTest.kt | 50 ++++++++++ .../snapshot/StaticObjectAsFieldTest.kt | 48 ++++++++++ .../snapshot/StaticObjectCycleTest.kt | 42 ++++++++ .../snapshot/StaticObjectTest.kt | 34 +++++++ 17 files changed, 405 insertions(+), 46 deletions(-) create mode 100644 src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/SnapshotAbstractTest.kt create mode 100644 src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticArrayTest.kt create mode 100644 src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticEnumTest.kt create mode 100644 src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectAsFieldTest.kt create mode 100644 src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectCycleTest.kt create mode 100644 src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectTest.kt diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/CTestConfiguration.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/CTestConfiguration.kt index af395ccbb..26d9a3afb 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/CTestConfiguration.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/CTestConfiguration.kt @@ -95,6 +95,7 @@ internal fun createFromTestClassAnnotations(testClass: Class<*>): List, offset: Long): String? { // Extract the private offset value and find the matching field. diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt index 6f46a160d..7c3869349 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt @@ -91,7 +91,15 @@ abstract class Strategy protected constructor( runner.close() } + /** + * Restores recorded values of all memory reachable from static state. + */ open fun restoreStaticMemorySnapshot() {} + + /** + * Records values of all memory locations, reachable from `className::fieldName` static variable. + */ + open fun updateStaticMemorySnapshot(className: String, fieldName: String) {} } /** diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedCTestConfiguration.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedCTestConfiguration.kt index 788e8120d..a7ef09df9 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedCTestConfiguration.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedCTestConfiguration.kt @@ -28,6 +28,7 @@ abstract class ManagedCTestConfiguration( val checkObstructionFreedom: Boolean, val hangingDetectionThreshold: Int, val invocationsPerIteration: Int, + val restoreStaticMemory: Boolean, val guarantees: List, minimizeFailedScenario: Boolean, sequentialSpecification: Class<*>, @@ -52,6 +53,7 @@ abstract class ManagedCTestConfiguration( const val DEFAULT_CHECK_OBSTRUCTION_FREEDOM = false const val DEFAULT_HANGING_DETECTION_THRESHOLD = 101 const val LIVELOCK_EVENTS_THRESHOLD = 10001 + const val DEFAULT_RESTORE_STATIC_MEMORY = false val DEFAULT_GUARANTEES = listOf() } } diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedOptions.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedOptions.kt index 555b2901b..46fb55193 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedOptions.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedOptions.kt @@ -14,6 +14,7 @@ import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedCTestConfiguration import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedCTestConfiguration.Companion.DEFAULT_GUARANTEES import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedCTestConfiguration.Companion.DEFAULT_HANGING_DETECTION_THRESHOLD import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedCTestConfiguration.Companion.DEFAULT_INVOCATIONS +import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedCTestConfiguration.Companion.DEFAULT_RESTORE_STATIC_MEMORY import java.util.* /** @@ -23,6 +24,7 @@ abstract class ManagedOptions, CTEST : CTestConfigurat protected var invocationsPerIteration = DEFAULT_INVOCATIONS protected var checkObstructionFreedom = DEFAULT_CHECK_OBSTRUCTION_FREEDOM protected var hangingDetectionThreshold = DEFAULT_HANGING_DETECTION_THRESHOLD + protected var restoreStaticMemory = DEFAULT_RESTORE_STATIC_MEMORY protected val guarantees: MutableList = ArrayList(DEFAULT_GUARANTEES) /** @@ -50,6 +52,13 @@ abstract class ManagedOptions, CTEST : CTestConfigurat this.hangingDetectionThreshold = hangingDetectionThreshold } + /** + * Set to `true` to activate the static memory snapshot tracking and restoring algorithm. + */ + fun restoreStaticMemory(restoreStaticMemory: Boolean): OPT = applyAndCast { + this.restoreStaticMemory = restoreStaticMemory + } + /** * Add a guarantee that methods in some classes are either correct in terms of concurrent execution or irrelevant. * These guarantees can be used for optimization. For example, we can add a guarantee that all the methods diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt index 042593d36..cd0ca591c 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt @@ -221,8 +221,17 @@ abstract class ManagedStrategy( override fun restoreStaticMemorySnapshot() { // TODO: what is the appropriate location to call this function? - staticMemorySnapshot.restoreValues() - super.restoreStaticMemorySnapshot() + if (testCfg.restoreStaticMemory) { + staticMemorySnapshot.restoreValues() + super.restoreStaticMemorySnapshot() + } + } + + override fun updateStaticMemorySnapshot(className: String, fieldName: String) { + if (testCfg.restoreStaticMemory) { + staticMemorySnapshot.addHierarchy(className, fieldName) + super.updateStaticMemorySnapshot(className, fieldName) + } } /** @@ -231,7 +240,7 @@ abstract class ManagedStrategy( override fun runInvocation(): InvocationResult { while (true) { initializeInvocation() - staticMemorySnapshot.restoreValues() + restoreStaticMemorySnapshot() val result = runner.run() // In case the runner detects a deadlock, some threads can still manipulate the current strategy, // so we're not interested in suddenInvocationResult in this case @@ -756,7 +765,7 @@ abstract class ManagedStrategy( // The following call checks all the static fields. if (isStatic) { LincheckJavaAgent.ensureClassHierarchyIsTransformed(className.canonicalClassName) - staticMemorySnapshot.addHierarchy(className.canonicalClassName, fieldName) + updateStaticMemorySnapshot(className.canonicalClassName, fieldName) } // Optimization: do not track final field reads if (isFinal) { @@ -829,7 +838,7 @@ abstract class ManagedStrategy( return@runInIgnoredSection false } if (isStatic) { - staticMemorySnapshot.addHierarchy(className.canonicalClassName, fieldName) + updateStaticMemorySnapshot(className.canonicalClassName, fieldName) } // Optimization: do not track final field writes if (isFinal) { diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt index 312c9b48f..f2ab21e33 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt @@ -10,13 +10,16 @@ package org.jetbrains.kotlinx.lincheck.strategy.managed -import org.jetbrains.kotlinx.lincheck.allDeclaredFieldWithSuperclasses import org.jetbrains.kotlinx.lincheck.canonicalClassName import org.jetbrains.kotlinx.lincheck.findField +import org.jetbrains.kotlinx.lincheck.getArrayLength import org.jetbrains.kotlinx.lincheck.getFieldOffset import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.ArrayCellNode -import org.jetbrains.kotlinx.lincheck.util.readField -import org.jetbrains.kotlinx.lincheck.util.writeField +import org.jetbrains.kotlinx.lincheck.util.allDeclaredFieldWithSuperclasses +import org.jetbrains.kotlinx.lincheck.util.readArrayElementViaUnsafe +import org.jetbrains.kotlinx.lincheck.util.readFieldViaUnsafe +import org.jetbrains.kotlinx.lincheck.util.writeArrayElementViaUnsafe +import org.jetbrains.kotlinx.lincheck.util.writeFieldViaUnsafe import java.lang.reflect.Field import java.lang.reflect.Modifier @@ -30,7 +33,7 @@ import java.lang.reflect.Modifier */ class SnapshotTracker { private val snapshotRoots = mutableListOf() - private val trackedStaticFields = mutableSetOf() + private val trackedStaticFields = mutableMapOf>() // CLASS_NAME -> { OFFSETS_OF_STATIC_FIELDS } // TODO: is `className` always required to be declaringClassName (both for static and non-static fields)? // right now `declaringClassName` is not calculated, which is incorrect, but I will fix it later @@ -61,19 +64,20 @@ class SnapshotTracker { * If the provided static variable is already present in the graph, then nothing will happen. */ fun addHierarchy(className: String, fieldName: String) { - if (className.startsWith("java.")) return check(className == className.canonicalClassName) { "Class name must be canonical" } val clazz = Class.forName(className) val field = clazz.findField(fieldName) - val descriptor = NodeDescriptor(className, field, getFieldOffset(field)) - val initialValue = readField(null, descriptor.field) - + val offset = getFieldOffset(field) + val descriptor = NodeDescriptor(className, field, offset) + val initialValue = readFieldViaUnsafe(null, descriptor.field) check(Modifier.isStatic(field.modifiers)) { "Root field in the snapshot hierarchy must be static" } - if (descriptor in trackedStaticFields) return + + if (!trackStaticField(className, offset)) return val root = StaticFieldNode(descriptor, initialValue) snapshotRoots.add(root) + addToGraph(root) } @@ -91,60 +95,74 @@ class SnapshotTracker { } + @OptIn(ExperimentalStdlibApi::class) private fun addToGraph(node: MemoryNode, visitedObjects: MutableSet = mutableSetOf()) { if (node.initialValue in visitedObjects) return - if (node is StaticFieldNode) { - if (trackedStaticFields.contains(node.descriptor)) return - trackedStaticFields.add(node.descriptor) - } - - val nodeClass = node.descriptor.field.let { + val nodeClass: Class<*> = node.descriptor.field.let { if (node is ArrayCellNode) it.type.componentType else it.type } - if (nodeClass.isPrimitive || nodeClass.isEnum || node.initialValue == null) return - +// val initValue = if (node.initialValue == null || nodeClass.isPrimitive) node.initialValue +// else "${node.initialValue.javaClass.simpleName}@${node.initialValue.hashCode().toHexString()}" +// // println( -// "Adding to hierarchy: ${nodeClass.canonicalName}::${node.descriptor.field.name} =" + -// node.initialValue + if (nodeClass.isArray && node !is ArrayCellNode) (node.initialValue as Array<*>).contentToString() else "" +// "Added to hierarchy: ${nodeClass.name}::${node.descriptor.field.name} =" + +// initValue + if (node.initialValue is Array<*> && node !is ArrayCellNode) (node.initialValue as Array<*>).contentToString() else "" // ) + if (nodeClass.isPrimitive || nodeClass.isEnum || node.initialValue == null) return + visitedObjects.add(node.initialValue) if (nodeClass.isArray && node !is ArrayCellNode) { - val array = node.initialValue as Array<*> + val array = node.initialValue - for (index in array.indices) { + for (index in 0..getArrayLength(array) - 1) { val childDescriptor = NodeDescriptor( - nodeClass.canonicalName, + nodeClass.name, node.descriptor.field, index.toLong() ) + val childNode = ArrayCellNode(childDescriptor, readArrayElementViaUnsafe(array, index)) - val childNode = ArrayCellNode(childDescriptor, array[index]) - - node.fields.add(childNode) - addToGraph(childNode, visitedObjects) + processChildNode(node, childNode, visitedObjects) } } else { nodeClass.allDeclaredFieldWithSuperclasses.forEach { field -> - val childDescriptor = NodeDescriptor(nodeClass.canonicalName, field, getFieldOffset(field)) - + val childDescriptor = NodeDescriptor(nodeClass.name, field, getFieldOffset(field)) val childNode = if (Modifier.isStatic(field.modifiers)) { - StaticFieldNode(childDescriptor, readField(null, childDescriptor.field)) + StaticFieldNode(childDescriptor, readFieldViaUnsafe(null, childDescriptor.field)) } else { - RegularFieldNode(childDescriptor, readField(node.initialValue, childDescriptor.field)) + RegularFieldNode(childDescriptor, readFieldViaUnsafe(node.initialValue, childDescriptor.field)) } - node.fields.add(childNode) - addToGraph(childNode, visitedObjects) + processChildNode(node, childNode, visitedObjects) } } } + private fun processChildNode(parentNode: MemoryNode, childNode: MemoryNode, visitedObjects: MutableSet) { + if ( + childNode is StaticFieldNode && + !trackStaticField(childNode.descriptor.className, childNode.descriptor.offset) + ) return + + parentNode.fields.add(childNode) + addToGraph(childNode, visitedObjects) + } + + /** + * @return `true` if static field located in class [className] by [offset] was not tracked before, `false` otherwise. + */ + private fun trackStaticField(className: String, offset: Long): Boolean { + trackedStaticFields.putIfAbsent(className, HashSet()) + return trackedStaticFields[className]!!.add(offset) + } + + @OptIn(ExperimentalStdlibApi::class) private fun restoreValues(node: MemoryNode, parent: MemoryNode? = null, visitedNodes: MutableSet) { if (node in visitedNodes) return visitedNodes.add(node) @@ -157,19 +175,21 @@ class SnapshotTracker { parent.initialValue } +// val initValue = if (node.initialValue == null || node.descriptor.field.type.isPrimitive) node.initialValue +// else "${node.initialValue.javaClass.simpleName}@${node.initialValue.hashCode().toHexString()}" +// // println( // "Write to ${node.descriptor.className}::${node.descriptor.field.name} =" + -// node.initialValue + if (node.descriptor.field.type.isArray && node !is ArrayCellNode) (node.initialValue as Array<*>).contentToString() else "" +// initValue + if (node.initialValue is Array<*> && node !is ArrayCellNode) (node.initialValue as Array<*>).contentToString() else "" // ) - if (node is ArrayCellNode) { - @Suppress("UNCHECKED_CAST") - val array = obj as Array + if (node.descriptor.field.type.isArray && node is ArrayCellNode) { + val array = obj!! val index = node.descriptor.offset.toInt() - array[index] = node.initialValue + writeArrayElementViaUnsafe(array, index, node.initialValue) } else { - writeField(obj, node.descriptor.field, node.initialValue) + writeFieldViaUnsafe(obj, node.descriptor.field, node.initialValue) } } diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingCTest.java b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingCTest.java index eb826294c..11ff9358a 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingCTest.java +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingCTest.java @@ -111,6 +111,11 @@ @Deprecated(message = "Does nothing, because equals/hashcode don't always improve performance of verification") boolean requireStateEquivalenceImplCheck() default false; + /** + * Should the managed strategy use the static memory tracking and restoring algorithm. + */ + boolean restoreStaticMemory() default DEFAULT_RESTORE_STATIC_MEMORY; + /** * If this feature is enabled and an invalid interleaving has been found, * *lincheck* tries to minimize the corresponding scenario in order to diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingCTestConfiguration.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingCTestConfiguration.kt index 2fa92c3a2..01520615c 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingCTestConfiguration.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingCTestConfiguration.kt @@ -24,8 +24,8 @@ import java.lang.reflect.* class ModelCheckingCTestConfiguration(testClass: Class<*>, iterations: Int, threads: Int, actorsPerThread: Int, actorsBefore: Int, actorsAfter: Int, generatorClass: Class, verifierClass: Class, checkObstructionFreedom: Boolean, hangingDetectionThreshold: Int, invocationsPerIteration: Int, - guarantees: List, minimizeFailedScenario: Boolean, - sequentialSpecification: Class<*>, timeoutMs: Long, + restoreStaticMemory: Boolean, guarantees: List, + minimizeFailedScenario: Boolean, sequentialSpecification: Class<*>, timeoutMs: Long, customScenarios: List ) : ManagedCTestConfiguration( testClass = testClass, @@ -39,6 +39,7 @@ class ModelCheckingCTestConfiguration(testClass: Class<*>, iterations: Int, thre checkObstructionFreedom = checkObstructionFreedom, hangingDetectionThreshold = hangingDetectionThreshold, invocationsPerIteration = invocationsPerIteration, + restoreStaticMemory = restoreStaticMemory, guarantees = guarantees, minimizeFailedScenario = minimizeFailedScenario, sequentialSpecification = sequentialSpecification, diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingOptions.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingOptions.kt index 1d2101db7..b572f36ba 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingOptions.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingOptions.kt @@ -29,6 +29,7 @@ class ModelCheckingOptions : ManagedOptions writeFieldViaUnsafe(obj, field, value) { obj, field, value -> putFloat(obj, field, value as Float) } else -> error("No more types expected") } +} + +internal fun writeArrayElementViaUnsafe(arr: Any, index: Int, value: Any?): Any? { + val offset = getArrayElementOffset(arr, index) + val componentType = arr::class.java.componentType + + if (!componentType.isPrimitive) { + return UnsafeHolder.UNSAFE.putObject(arr, offset, value) + } + + return when (componentType) { + Boolean::class.javaPrimitiveType -> UnsafeHolder.UNSAFE.putBoolean(arr, offset, value as Boolean) + Byte::class.javaPrimitiveType -> UnsafeHolder.UNSAFE.putByte(arr, offset, value as Byte) + Char::class.javaPrimitiveType -> UnsafeHolder.UNSAFE.putChar(arr, offset, value as Char) + Short::class.javaPrimitiveType -> UnsafeHolder.UNSAFE.putShort(arr, offset, value as Short) + Int::class.javaPrimitiveType -> UnsafeHolder.UNSAFE.putInt(arr, offset, value as Int) + Long::class.javaPrimitiveType -> UnsafeHolder.UNSAFE.putLong(arr, offset, value as Long) + Double::class.javaPrimitiveType -> UnsafeHolder.UNSAFE.putDouble(arr, offset, value as Double) + Float::class.javaPrimitiveType -> UnsafeHolder.UNSAFE.putFloat(arr, offset, value as Float) + else -> error("No more primitive types expected") + } } \ No newline at end of file diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/SnapshotAbstractTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/SnapshotAbstractTest.kt new file mode 100644 index 000000000..1f8bc0119 --- /dev/null +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/SnapshotAbstractTest.kt @@ -0,0 +1,23 @@ +/* + * Lincheck + * + * Copyright (C) 2019 - 2024 JetBrains s.r.o. + * + * This Source Code Form is subject to the terms of the + * Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed + * with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot + +import org.jetbrains.kotlinx.lincheck.check +import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingOptions +import org.junit.Test + +abstract class SnapshotAbstractTest { + @Test + fun testModelChecking() = ModelCheckingOptions() + .iterations(1) + .restoreStaticMemory(true) + .check(this::class) +} \ No newline at end of file diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticArrayTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticArrayTest.kt new file mode 100644 index 000000000..897c63b3c --- /dev/null +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticArrayTest.kt @@ -0,0 +1,74 @@ +/* + * Lincheck + * + * Copyright (C) 2019 - 2024 JetBrains s.r.o. + * + * This Source Code Form is subject to the terms of the + * Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed + * with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot + +import org.jetbrains.kotlinx.lincheck.annotations.Operation +import org.junit.After +import org.junit.Before +import kotlin.random.Random + + +private var intArray = intArrayOf(1, 2, 3) + +class StaticIntArrayTest : SnapshotAbstractTest() { + private var ref = intArray + private var values = intArray.copyOf() + + @Operation + fun modify() { + intArray[0]++ + } + + @After + fun checkStaticStateRestored() { + check(intArray == ref) + check(ref.contentEquals(values)) + } +} + + +private class X(var value: Int) { + @OptIn(ExperimentalStdlibApi::class) + override fun toString() = "X@${this.hashCode().toHexString()}($value)" +} + +private var objArray = arrayOf(X(1), X(2), X(3)) + +class StaticObjectArrayTest : SnapshotAbstractTest() { + private var ref: Array? = null + private var elements: Array? = null + private var values: Array? = null + + @Operation + fun modify() { + objArray[0].value++ + objArray[1].value-- + objArray[2] = X(Random.nextInt()) + } + + @Before + fun saveInitStaticState() { + ref = objArray + + elements = Array(3) { null } + objArray.forEachIndexed { index, x -> elements!![index] = x } + + values = Array(3) { 0 } + objArray.forEachIndexed { index, x -> values!![index] = x.value } + } + + @After + fun checkStaticStateRestored() { + check(objArray == ref) + check(objArray.contentEquals(elements)) + check(objArray.map { it.value }.toTypedArray().contentEquals(values)) + } +} \ No newline at end of file diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticEnumTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticEnumTest.kt new file mode 100644 index 000000000..dbb2b1a55 --- /dev/null +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticEnumTest.kt @@ -0,0 +1,50 @@ +/* + * Lincheck + * + * Copyright (C) 2019 - 2024 JetBrains s.r.o. + * + * This Source Code Form is subject to the terms of the + * Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed + * with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot + +import org.jetbrains.kotlinx.lincheck.annotations.Operation +import org.junit.After +import org.junit.Before + + +private enum class Values { + A, B, C; +} +private class EnumHolder(var x: Values, var y: Values) + +private var global = EnumHolder(Values.A, Values.B) + +class StaticEnumTest : SnapshotAbstractTest() { + private var initA: EnumHolder = global + private var initX: Values = global.x + private var initY: Values = global.y + + @Operation + fun modifyFields() { + // modified fields of the initial instance + global.x = Values.B + global.y = Values.C + + // assign different instance to the variable + global = EnumHolder(Values.C, Values.C) + } + + @Before + fun saveInitStaticState() { + } + + @After + fun checkStaticStateRestored() { + check(global == initA) + check(global.x == initX) + check(global.y == initY) + } +} \ No newline at end of file diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectAsFieldTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectAsFieldTest.kt new file mode 100644 index 000000000..cf6487b13 --- /dev/null +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectAsFieldTest.kt @@ -0,0 +1,48 @@ +/* + * Lincheck + * + * Copyright (C) 2019 - 2024 JetBrains s.r.o. + * + * This Source Code Form is subject to the terms of the + * Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed + * with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot + +import org.jetbrains.kotlinx.lincheck.annotations.Operation +import org.junit.After + + +private object Static1 { + var f1: Static2? = Static2 + var f2: Int = 0 +} + +private object Static2 { + var f1: Int = 1 + var f2: String = "abc" +} + +class StaticObjectAsFieldTest : SnapshotAbstractTest() { + private val initS1f1 = Static1.f1 + private val initS1f2 = Static1.f2 + + private val initS2f1 = Static2.f1 + private val initS2f2 = Static2.f2 + + @Operation + fun modify() { + Static2.f1 = 10 + Static2.f2 = "cba" + + Static1.f1 = null + Static1.f2 = 10 + } + + @After + fun checkStaticStateRestored() { + check(Static1.f1 == initS1f1 && Static1.f2 == initS1f2) + check(Static2.f1 == initS2f1 && Static2.f2 == initS2f2) + } +} \ No newline at end of file diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectCycleTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectCycleTest.kt new file mode 100644 index 000000000..718608eea --- /dev/null +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectCycleTest.kt @@ -0,0 +1,42 @@ +/* + * Lincheck + * + * Copyright (C) 2019 - 2024 JetBrains s.r.o. + * + * This Source Code Form is subject to the terms of the + * Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed + * with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot + +import org.jetbrains.kotlinx.lincheck.annotations.Operation +import org.junit.After + +private class A(var b: B) +private class B(var a: A? = null) + +private var globalA = A(B()) + +class StaticObjectCycleTest : SnapshotAbstractTest() { + companion object { + init { + globalA.b.a = globalA + } + } + + private var initA = globalA + private var initB = globalA.b + + @Operation + fun modify() { + globalA = A(B()) + } + + @After + fun checkStaticStateRestored() { + check(globalA == initA) + check(globalA.b == initB) + check(globalA.b.a == globalA) + } +} \ No newline at end of file diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectTest.kt new file mode 100644 index 000000000..674b3171b --- /dev/null +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectTest.kt @@ -0,0 +1,34 @@ +/* + * Lincheck + * + * Copyright (C) 2019 - 2024 JetBrains s.r.o. + * + * This Source Code Form is subject to the terms of the + * Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed + * with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot + +import org.jetbrains.kotlinx.lincheck.annotations.Operation +import org.junit.After +import java.util.concurrent.atomic.AtomicInteger + + +private var staticInt = AtomicInteger(1) + +class StaticObjectTest : SnapshotAbstractTest() { + @Operation + fun modify() { + staticInt.getAndIncrement() + } + + private var ref = staticInt + private var value = staticInt.get() + + @After + fun checkStaticStateRestored() { + check(staticInt == ref) + check(staticInt.get() == value) + } +} \ No newline at end of file From 3e6ec07450c5b53cb4f4b6215d57900d4141f740 Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Mon, 21 Oct 2024 22:10:39 +0200 Subject: [PATCH 04/29] Remove comments --- .../strategy/managed/SnapshotTracker.kt | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt index f2ab21e33..4c0715409 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt @@ -86,8 +86,6 @@ class SnapshotTracker { * reachable fields to initial values recorded by previous calls to [addHierarchy]. */ fun restoreValues() { -// println("Restoring all memory reachable from static state to snapshot values") - val visitedNodes = mutableSetOf() snapshotRoots.forEach { rootNode -> restoreValues(rootNode, null, visitedNodes) @@ -104,14 +102,6 @@ class SnapshotTracker { else it.type } -// val initValue = if (node.initialValue == null || nodeClass.isPrimitive) node.initialValue -// else "${node.initialValue.javaClass.simpleName}@${node.initialValue.hashCode().toHexString()}" -// -// println( -// "Added to hierarchy: ${nodeClass.name}::${node.descriptor.field.name} =" + -// initValue + if (node.initialValue is Array<*> && node !is ArrayCellNode) (node.initialValue as Array<*>).contentToString() else "" -// ) - if (nodeClass.isPrimitive || nodeClass.isEnum || node.initialValue == null) return visitedObjects.add(node.initialValue) @@ -175,14 +165,6 @@ class SnapshotTracker { parent.initialValue } -// val initValue = if (node.initialValue == null || node.descriptor.field.type.isPrimitive) node.initialValue -// else "${node.initialValue.javaClass.simpleName}@${node.initialValue.hashCode().toHexString()}" -// -// println( -// "Write to ${node.descriptor.className}::${node.descriptor.field.name} =" + -// initValue + if (node.initialValue is Array<*> && node !is ArrayCellNode) (node.initialValue as Array<*>).contentToString() else "" -// ) - if (node.descriptor.field.type.isArray && node is ArrayCellNode) { val array = obj!! val index = node.descriptor.offset.toInt() From 760b0764b9806a8e4f3e2ad07f9ce2a6d54c99e6 Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Wed, 30 Oct 2024 19:03:19 +0100 Subject: [PATCH 05/29] Make snapshot algorithm lazy --- .../kotlinx/lincheck/strategy/Strategy.kt | 6 +- .../strategy/managed/ManagedStrategy.kt | 19 +- .../strategy/managed/SnapshotTracker.kt | 216 ++++++++---------- 3 files changed, 109 insertions(+), 132 deletions(-) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt index 7c3869349..0049c5cd5 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt @@ -96,10 +96,8 @@ abstract class Strategy protected constructor( */ open fun restoreStaticMemorySnapshot() {} - /** - * Records values of all memory locations, reachable from `className::fieldName` static variable. - */ - open fun updateStaticMemorySnapshot(className: String, fieldName: String) {} + open fun updateStaticMemorySnapshot(obj: Any?, className: String, fieldName: String) {} + open fun updateStaticMemorySnapshot(array: Any, index: Int) {} } /** diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt index cd0ca591c..c8674722d 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt @@ -227,10 +227,17 @@ abstract class ManagedStrategy( } } - override fun updateStaticMemorySnapshot(className: String, fieldName: String) { + override fun updateStaticMemorySnapshot(obj: Any?, className: String, fieldName: String) { if (testCfg.restoreStaticMemory) { - staticMemorySnapshot.addHierarchy(className, fieldName) - super.updateStaticMemorySnapshot(className, fieldName) + staticMemorySnapshot.trackField(obj, className, fieldName) + super.updateStaticMemorySnapshot(obj, className, fieldName) + } + } + + override fun updateStaticMemorySnapshot(array: Any, index: Int) { + if (testCfg.restoreStaticMemory) { + staticMemorySnapshot.trackArrayCell(array, index) + super.updateStaticMemorySnapshot(array, index) } } @@ -765,7 +772,7 @@ abstract class ManagedStrategy( // The following call checks all the static fields. if (isStatic) { LincheckJavaAgent.ensureClassHierarchyIsTransformed(className.canonicalClassName) - updateStaticMemorySnapshot(className.canonicalClassName, fieldName) + updateStaticMemorySnapshot(obj, className.canonicalClassName, fieldName) } // Optimization: do not track final field reads if (isFinal) { @@ -801,6 +808,7 @@ abstract class ManagedStrategy( if (!objectTracker.shouldTrackObjectAccess(array)) { return@runInIgnoredSection false } + updateStaticMemorySnapshot(array, index) val iThread = currentThread val tracePoint = if (collectTrace) { ReadTracePoint( @@ -838,7 +846,7 @@ abstract class ManagedStrategy( return@runInIgnoredSection false } if (isStatic) { - updateStaticMemorySnapshot(className.canonicalClassName, fieldName) + updateStaticMemorySnapshot(obj, className.canonicalClassName, fieldName) } // Optimization: do not track final field writes if (isFinal) { @@ -869,6 +877,7 @@ abstract class ManagedStrategy( if (!objectTracker.shouldTrackObjectAccess(array)) { return@runInIgnoredSection false } + updateStaticMemorySnapshot(array, index) val iThread = currentThread val tracePoint = if (collectTrace) { WriteTracePoint( diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt index 4c0715409..92b894140 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt @@ -10,18 +10,21 @@ package org.jetbrains.kotlinx.lincheck.strategy.managed -import org.jetbrains.kotlinx.lincheck.canonicalClassName import org.jetbrains.kotlinx.lincheck.findField -import org.jetbrains.kotlinx.lincheck.getArrayLength import org.jetbrains.kotlinx.lincheck.getFieldOffset -import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.ArrayCellNode -import org.jetbrains.kotlinx.lincheck.util.allDeclaredFieldWithSuperclasses +import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.Descriptor.ArrayCellDescriptor +import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.Descriptor.FieldDescriptor +import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.MemoryNode.ArrayCellNode +import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.MemoryNode.RegularFieldNode +import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.MemoryNode.StaticFieldNode import org.jetbrains.kotlinx.lincheck.util.readArrayElementViaUnsafe import org.jetbrains.kotlinx.lincheck.util.readFieldViaUnsafe import org.jetbrains.kotlinx.lincheck.util.writeArrayElementViaUnsafe import org.jetbrains.kotlinx.lincheck.util.writeFieldViaUnsafe import java.lang.reflect.Field import java.lang.reflect.Modifier +import java.util.Collections +import java.util.IdentityHashMap /** @@ -32,149 +35,116 @@ import java.lang.reflect.Modifier * which is named *snapshot*. */ class SnapshotTracker { - private val snapshotRoots = mutableListOf() - private val trackedStaticFields = mutableMapOf>() // CLASS_NAME -> { OFFSETS_OF_STATIC_FIELDS } - - // TODO: is `className` always required to be declaringClassName (both for static and non-static fields)? - // right now `declaringClassName` is not calculated, which is incorrect, but I will fix it later - private data class NodeDescriptor( - val className: String, // name of the class which contains the field - val field: Field, - val offset: Long // for arrays, offset will be element index - ) { - override fun toString(): String { - return "NodeDescriptor(className=$className, field=${field}, offset=$offset)" - } - } - - private abstract class MemoryNode( - val descriptor: NodeDescriptor, - val initialValue: Any?, - val fields: MutableList = mutableListOf() /* list of fields/array cells that are inside this object */ - ) - private class StaticFieldNode(descriptor: NodeDescriptor, initialValue: Any?) : MemoryNode(descriptor, initialValue) - private class RegularFieldNode(descriptor: NodeDescriptor, initialValue: Any?) : MemoryNode(descriptor, initialValue) - private class ArrayCellNode(descriptor: NodeDescriptor, initialValue: Any?) : MemoryNode(descriptor, initialValue) - - /** - * Remembers the current value of the static variable passed to it and the values of all its fields. - * The values that will be observed from the provided static variable will be restored on - * subsequent call to [restoreValues]. - * - * If the provided static variable is already present in the graph, then nothing will happen. - */ - fun addHierarchy(className: String, fieldName: String) { - check(className == className.canonicalClassName) { "Class name must be canonical" } - - val clazz = Class.forName(className) - val field = clazz.findField(fieldName) - val offset = getFieldOffset(field) - val descriptor = NodeDescriptor(className, field, offset) - val initialValue = readFieldViaUnsafe(null, descriptor.field) - check(Modifier.isStatic(field.modifiers)) { "Root field in the snapshot hierarchy must be static" } + private val trackedObjects = IdentityHashMap>() - if (!trackStaticField(className, offset)) return - - val root = StaticFieldNode(descriptor, initialValue) - snapshotRoots.add(root) - - addToGraph(root) + private sealed class Descriptor { + class FieldDescriptor(val field: Field, val offset: Long) : Descriptor() + class ArrayCellDescriptor(val index: Int) : Descriptor() } - /** - * Traverses all top-level static variables of the snapshot and restores all - * reachable fields to initial values recorded by previous calls to [addHierarchy]. - */ - fun restoreValues() { - val visitedNodes = mutableSetOf() - snapshotRoots.forEach { rootNode -> - restoreValues(rootNode, null, visitedNodes) - } + private sealed class MemoryNode( + val descriptor: Descriptor, + val initialValue: Any? + ) { + class RegularFieldNode(descriptor: FieldDescriptor, initialValue: Any?) : MemoryNode(descriptor, initialValue) + class StaticFieldNode(descriptor: FieldDescriptor, initialValue: Any?) : MemoryNode(descriptor, initialValue) + class ArrayCellNode(descriptor: ArrayCellDescriptor, initialValue: Any?) : MemoryNode(descriptor, initialValue) } - - @OptIn(ExperimentalStdlibApi::class) - private fun addToGraph(node: MemoryNode, visitedObjects: MutableSet = mutableSetOf()) { - if (node.initialValue in visitedObjects) return - - val nodeClass: Class<*> = node.descriptor.field.let { - if (node is ArrayCellNode) it.type.componentType - else it.type + fun trackField(obj: Any?, className: String, fieldName: String) { + val clazz: Class<*> = Class.forName(className).let { + if (obj != null) it + else it.findField(fieldName).declaringClass } - if (nodeClass.isPrimitive || nodeClass.isEnum || node.initialValue == null) return - - visitedObjects.add(node.initialValue) + val nodesList = + if (obj != null) trackedObjects[obj] + else trackedObjects.putIfAbsent(clazz, mutableListOf()) - if (nodeClass.isArray && node !is ArrayCellNode) { - val array = node.initialValue - - for (index in 0..getArrayLength(array) - 1) { - val childDescriptor = NodeDescriptor( - nodeClass.name, - node.descriptor.field, - index.toLong() - ) - val childNode = ArrayCellNode(childDescriptor, readArrayElementViaUnsafe(array, index)) - - processChildNode(node, childNode, visitedObjects) - } - } - else { - nodeClass.allDeclaredFieldWithSuperclasses.forEach { field -> - val childDescriptor = NodeDescriptor(nodeClass.name, field, getFieldOffset(field)) - val childNode = if (Modifier.isStatic(field.modifiers)) { - StaticFieldNode(childDescriptor, readFieldViaUnsafe(null, childDescriptor.field)) - } else { - RegularFieldNode(childDescriptor, readFieldViaUnsafe(node.initialValue, childDescriptor.field)) - } + if ( + nodesList == null || // parent object is not tracked + nodesList + .map { it.descriptor } + .filterIsInstance() + .any { it.field.name == fieldName } // field is already tracked + ) return - processChildNode(node, childNode, visitedObjects) - } + val field = clazz.findField(fieldName) + val childNode = createMemoryNode( + obj, + FieldDescriptor(field, getFieldOffset(field)) + ) + + nodesList.add(childNode) + if (isTrackableObject(childNode.initialValue)) { + trackedObjects.putIfAbsent(childNode.initialValue, mutableListOf()) } } - private fun processChildNode(parentNode: MemoryNode, childNode: MemoryNode, visitedObjects: MutableSet) { + fun trackArrayCell(array: Any, index: Int) { + val nodesList = trackedObjects[array] + if ( - childNode is StaticFieldNode && - !trackStaticField(childNode.descriptor.className, childNode.descriptor.offset) + nodesList == null || // array is not tracked + nodesList.any { it is ArrayCellNode && (it.descriptor as ArrayCellDescriptor).index == index } // this array cell is already tracked ) return - parentNode.fields.add(childNode) - addToGraph(childNode, visitedObjects) + val childNode = createMemoryNode(array, ArrayCellDescriptor(index)) + + nodesList.add(childNode) + if (isTrackableObject(childNode.initialValue)) { + trackedObjects.putIfAbsent(childNode.initialValue, mutableListOf()) + } } - /** - * @return `true` if static field located in class [className] by [offset] was not tracked before, `false` otherwise. - */ - private fun trackStaticField(className: String, offset: Long): Boolean { - trackedStaticFields.putIfAbsent(className, HashSet()) - return trackedStaticFields[className]!!.add(offset) + fun restoreValues() { + val visitedObjects = Collections.newSetFromMap(IdentityHashMap()) + trackedObjects.keys + .filterIsInstance>() + .forEach { restoreValues(it, visitedObjects) } } @OptIn(ExperimentalStdlibApi::class) - private fun restoreValues(node: MemoryNode, parent: MemoryNode? = null, visitedNodes: MutableSet) { - if (node in visitedNodes) return - visitedNodes.add(node) - - if (!Modifier.isFinal(node.descriptor.field.modifiers)) { - val obj: Any? = - if (node is StaticFieldNode) null - else { - check(parent != null) { "Regular field in snapshot hierarchy must have a parent node" } - parent.initialValue + private fun restoreValues(obj: Any, visitedObjects: MutableSet) { + if (obj in visitedObjects) return + visitedObjects.add(obj) + + trackedObjects[obj]!! + .forEach { node -> + if (node is ArrayCellNode) { + val index = (node.descriptor as ArrayCellDescriptor).index + writeArrayElementViaUnsafe(obj, index, node.initialValue) + } + else if (!Modifier.isFinal((node.descriptor as FieldDescriptor).field.modifiers)) { + writeFieldViaUnsafe( + if (node is StaticFieldNode) null else obj, + node.descriptor.field, + node.initialValue + ) } - if (node.descriptor.field.type.isArray && node is ArrayCellNode) { - val array = obj!! - val index = node.descriptor.offset.toInt() - writeArrayElementViaUnsafe(array, index, node.initialValue) + if (isTrackableObject(node.initialValue)) { + restoreValues(node.initialValue!!, visitedObjects) + } } - else { - writeFieldViaUnsafe(obj, node.descriptor.field, node.initialValue) + } + + + private fun isTrackableObject(value: Any?): Boolean { + return ( + value != null && + !value.javaClass.isPrimitive && + !value.javaClass.isEnum + ) + } + + private fun createMemoryNode(obj: Any?, descriptor: Descriptor): MemoryNode { + return when (descriptor) { + is FieldDescriptor -> { + if (obj is Class<*>) StaticFieldNode(descriptor, readFieldViaUnsafe(null, descriptor.field)) + else RegularFieldNode(descriptor, readFieldViaUnsafe(obj, descriptor.field)) } + is ArrayCellDescriptor -> ArrayCellNode(descriptor, readArrayElementViaUnsafe(obj!!, descriptor.index)) } - - node.fields.forEach{ childNode -> restoreValues(childNode, node, visitedNodes) } } } \ No newline at end of file From 7edbd56b7285a5b285569bee1858df172e97bf7f Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Wed, 30 Oct 2024 19:21:45 +0100 Subject: [PATCH 06/29] Fix bugs --- .../kotlinx/lincheck/strategy/managed/ManagedStrategy.kt | 7 +++---- .../kotlinx/lincheck/strategy/managed/SnapshotTracker.kt | 4 +++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt index c8674722d..bcdee9b85 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt @@ -227,6 +227,7 @@ abstract class ManagedStrategy( } } + // TODO: fix bug with the fact, that non-transformed classes are impossible to track lazily override fun updateStaticMemorySnapshot(obj: Any?, className: String, fieldName: String) { if (testCfg.restoreStaticMemory) { staticMemorySnapshot.trackField(obj, className, fieldName) @@ -770,9 +771,9 @@ abstract class ManagedStrategy( isStatic: Boolean, isFinal: Boolean) = runInIgnoredSection { // We need to ensure all the classes related to the reading object are instrumented. // The following call checks all the static fields. + updateStaticMemorySnapshot(obj, className.canonicalClassName, fieldName) if (isStatic) { LincheckJavaAgent.ensureClassHierarchyIsTransformed(className.canonicalClassName) - updateStaticMemorySnapshot(obj, className.canonicalClassName, fieldName) } // Optimization: do not track final field reads if (isFinal) { @@ -845,9 +846,7 @@ abstract class ManagedStrategy( if (!objectTracker.shouldTrackObjectAccess(obj ?: StaticObject)) { return@runInIgnoredSection false } - if (isStatic) { - updateStaticMemorySnapshot(obj, className.canonicalClassName, fieldName) - } + updateStaticMemorySnapshot(obj, className.canonicalClassName, fieldName) // Optimization: do not track final field writes if (isFinal) { return@runInIgnoredSection false diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt index 92b894140..9e3d8a82a 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt @@ -59,7 +59,7 @@ class SnapshotTracker { val nodesList = if (obj != null) trackedObjects[obj] - else trackedObjects.putIfAbsent(clazz, mutableListOf()) + else trackedObjects.getOrPut(clazz) { mutableListOf() } if ( nodesList == null || // parent object is not tracked @@ -69,6 +69,7 @@ class SnapshotTracker { .any { it.field.name == fieldName } // field is already tracked ) return + println("SNAPSHOT: obj=$obj, className=$className, fieldName=$fieldName") val field = clazz.findField(fieldName) val childNode = createMemoryNode( obj, @@ -89,6 +90,7 @@ class SnapshotTracker { nodesList.any { it is ArrayCellNode && (it.descriptor as ArrayCellDescriptor).index == index } // this array cell is already tracked ) return + println("SNAPSHOT: array=$array, index=$index") val childNode = createMemoryNode(array, ArrayCellDescriptor(index)) nodesList.add(childNode) From feefb3d7a9399889aba94a982c7f977cec4d34e4 Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Fri, 1 Nov 2024 18:50:36 +0100 Subject: [PATCH 07/29] Add energetic traverse for non-transformed classes (such as 'AtomicInteger', etc) --- .../kotlinx/lincheck/ObjectTraverser.kt | 1 + .../org/jetbrains/kotlinx/lincheck/Utils.kt | 67 +++++++++++++ .../strategy/managed/SnapshotTracker.kt | 95 +++++++++++++++---- .../transformation/LincheckJavaAgent.kt | 53 +---------- .../kotlinx/lincheck/util/AtomicMethods.kt | 2 - 5 files changed, 149 insertions(+), 69 deletions(-) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/ObjectTraverser.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/ObjectTraverser.kt index 009dd9d94..ffa170053 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/ObjectTraverser.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/ObjectTraverser.kt @@ -22,6 +22,7 @@ package org.jetbrains.kotlinx.lincheck import org.jetbrains.kotlinx.lincheck.strategy.managed.ObjectLabelFactory.getObjectNumber import org.jetbrains.kotlinx.lincheck.util.* +import org.jetbrains.kotlinx.lincheck.util.readFieldViaUnsafe import java.math.BigDecimal import java.math.BigInteger import kotlin.coroutines.Continuation diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt index c0c76f433..8591211b1 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt @@ -25,6 +25,57 @@ import java.util.* import kotlin.coroutines.* import kotlin.coroutines.intrinsics.* +fun shouldTransformClass(className: String): Boolean { + // We do not need to instrument most standard Java classes. + // It is fine to inject the Lincheck analysis only into the + // `java.util.*` ones, ignored the known atomic constructs. + if (className.startsWith("java.")) { + if (className.startsWith("java.util.concurrent.") && className.contains("Atomic")) return false + if (className.startsWith("java.util.")) return true + if (className.startsWith("com.sun.")) return false + return false + } + if (className.startsWith("sun.")) return false + if (className.startsWith("javax.")) return false + if (className.startsWith("jdk.")) return false + // We do not need to instrument most standard Kotlin classes. + // However, we need to inject the Lincheck analysis into the classes + // related to collections, iterators, random and coroutines. + if (className.startsWith("kotlin.")) { + if (className.startsWith("kotlin.collections.")) return true + if (className.startsWith("kotlin.jvm.internal.Array") && className.contains("Iterator")) return true + if (className.startsWith("kotlin.ranges.")) return true + if (className.startsWith("kotlin.random.")) return true + if (className.startsWith("kotlin.coroutines.jvm.internal.")) return false + if (className.startsWith("kotlin.coroutines.")) return true + return false + } + if (className.startsWith("kotlinx.atomicfu.")) return false + // We need to skip the classes related to the debugger support in Kotlin coroutines. + if (className.startsWith("kotlinx.coroutines.debug.")) return false + if (className == "kotlinx.coroutines.DebugKt") return false + // We should never transform the coverage-related classes. + if (className.startsWith("com.intellij.rt.coverage.")) return false + // We can also safely do not instrument some libraries for performance reasons. + if (className.startsWith("com.esotericsoftware.kryo.")) return false + if (className.startsWith("net.bytebuddy.")) return false + if (className.startsWith("net.rubygrapefruit.platform.")) return false + if (className.startsWith("io.mockk.")) return false + if (className.startsWith("it.unimi.dsi.fastutil.")) return false + if (className.startsWith("worker.org.gradle.")) return false + if (className.startsWith("org.objectweb.asm.")) return false + if (className.startsWith("org.gradle.")) return false + if (className.startsWith("org.slf4j.")) return false + if (className.startsWith("org.apache.commons.lang.")) return false + if (className.startsWith("org.junit.")) return false + if (className.startsWith("junit.framework.")) return false + // Finally, we should never instrument the Lincheck classes. + if (className.startsWith("org.jetbrains.kotlinx.lincheck.")) return false + if (className.startsWith("sun.nio.ch.lincheck.")) return false + // All the classes that were not filtered out are eligible for transformation. + return true +} + fun List.isSuffixOf(list: List): Boolean { if (size > list.size) return false for (i in indices) { @@ -212,6 +263,22 @@ internal val Throwable.text: String get() { return writer.buffer.toString() } +/** + * Returns all found fields in the hierarchy. + * Multiple fields with the same name and the same type may be returned + * if they appear in the subclass and a parent class. + */ +internal val Class<*>.allDeclaredFieldWithSuperclasses get(): List { + val fields: MutableList = ArrayList() + var currentClass: Class<*>? = this + while (currentClass != null) { + val declaredFields: Array = currentClass.declaredFields + fields.addAll(declaredFields) + currentClass = currentClass.superclass + } + return fields +} + /** * Finds a public/protected/private/internal field in the class and its superclasses/interfaces by name. * diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt index 9e3d8a82a..91388f034 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt @@ -12,6 +12,7 @@ package org.jetbrains.kotlinx.lincheck.strategy.managed import org.jetbrains.kotlinx.lincheck.findField import org.jetbrains.kotlinx.lincheck.getFieldOffset +import org.jetbrains.kotlinx.lincheck.shouldTransformClass import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.Descriptor.ArrayCellDescriptor import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.Descriptor.FieldDescriptor import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.MemoryNode.ArrayCellNode @@ -19,6 +20,7 @@ import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.MemoryNod import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.MemoryNode.StaticFieldNode import org.jetbrains.kotlinx.lincheck.util.readArrayElementViaUnsafe import org.jetbrains.kotlinx.lincheck.util.readFieldViaUnsafe +import org.jetbrains.kotlinx.lincheck.util.traverseObjectGraph import org.jetbrains.kotlinx.lincheck.util.writeArrayElementViaUnsafe import org.jetbrains.kotlinx.lincheck.util.writeFieldViaUnsafe import java.lang.reflect.Field @@ -51,12 +53,14 @@ class SnapshotTracker { class ArrayCellNode(descriptor: ArrayCellDescriptor, initialValue: Any?) : MemoryNode(descriptor, initialValue) } - fun trackField(obj: Any?, className: String, fieldName: String) { - val clazz: Class<*> = Class.forName(className).let { - if (obj != null) it - else it.findField(fieldName).declaringClass - } - + @OptIn(ExperimentalStdlibApi::class) + private fun trackSingleField( + obj: Any?, + clazz: Class<*>, + field: Field, + fieldValue: Any?, + callback: (() -> Unit)? = null + ) { val nodesList = if (obj != null) trackedObjects[obj] else trackedObjects.getOrPut(clazz) { mutableListOf() } @@ -66,23 +70,44 @@ class SnapshotTracker { nodesList .map { it.descriptor } .filterIsInstance() - .any { it.field.name == fieldName } // field is already tracked + .any { it.field.name == /*fieldName*/ field.name } // field is already tracked ) return - println("SNAPSHOT: obj=$obj, className=$className, fieldName=$fieldName") - val field = clazz.findField(fieldName) + println("SNAPSHOT: obj=${if (obj == null) "null" else obj.javaClass.simpleName + "@" + System.identityHashCode(obj).toHexString()}, className=${clazz.simpleName}, fieldName=${field.name}") val childNode = createMemoryNode( obj, - FieldDescriptor(field, getFieldOffset(field)) + FieldDescriptor(field, getFieldOffset(field)), + fieldValue ) nodesList.add(childNode) if (isTrackableObject(childNode.initialValue)) { trackedObjects.putIfAbsent(childNode.initialValue, mutableListOf()) + callback?.invoke() } } - fun trackArrayCell(array: Any, index: Int) { + fun trackField(obj: Any?, className: String, fieldName: String) { + val clazz: Class<*> = Class.forName(className).let { + if (obj != null) it + else it.findField(fieldName).declaringClass + } + val field = clazz.findField(fieldName) + val readResult = runCatching { readFieldViaUnsafe(obj, field) } + + if (readResult.isSuccess) { + val fieldValue = readResult.getOrNull() + + trackSingleField(obj, clazz, field, fieldValue) { + if (isIgnoredClassInstance(fieldValue)) { + trackHierarchy(fieldValue!!) + } + } + } + } + + @OptIn(ExperimentalStdlibApi::class) + private fun trackSingleArrayCell(array: Any, index: Int, elementValue: Any?, callback: (() -> Unit)? = null) { val nodesList = trackedObjects[array] if ( @@ -90,12 +115,27 @@ class SnapshotTracker { nodesList.any { it is ArrayCellNode && (it.descriptor as ArrayCellDescriptor).index == index } // this array cell is already tracked ) return - println("SNAPSHOT: array=$array, index=$index") - val childNode = createMemoryNode(array, ArrayCellDescriptor(index)) + println("SNAPSHOT: array=${array.javaClass.simpleName}@${System.identityHashCode(array).toHexString()}, index=$index") + val childNode = createMemoryNode(array, ArrayCellDescriptor(index), elementValue) nodesList.add(childNode) if (isTrackableObject(childNode.initialValue)) { trackedObjects.putIfAbsent(childNode.initialValue, mutableListOf()) + callback?.invoke() + } + } + + fun trackArrayCell(array: Any, index: Int) { + val readResult = runCatching { readArrayElementViaUnsafe(array, index) } + + if (readResult.isSuccess) { + val elementValue = readResult.getOrNull() + + trackSingleArrayCell(array, index, elementValue) { + if (isIgnoredClassInstance(elementValue)) { + trackHierarchy(elementValue!!) + } + } } } @@ -106,6 +146,27 @@ class SnapshotTracker { .forEach { restoreValues(it, visitedObjects) } } + @OptIn(ExperimentalStdlibApi::class) + private fun trackHierarchy(obj: Any) { + // TODO: iterate over object hierarchy + println("Track hierarchy for object: ${obj.javaClass.simpleName}@${System.identityHashCode(obj).toHexString()}") + traverseObjectGraph( + obj, + onArrayElement = { array, index, elementValue -> + trackSingleArrayCell(array, index, elementValue) + return@traverseObjectGraph elementValue + }, + onField = { owner, field, fieldValue -> + trackSingleField(owner, owner.javaClass, field, fieldValue) + return@traverseObjectGraph fieldValue + } + ) + } + + private fun isIgnoredClassInstance(obj: Any?): Boolean { + return obj != null && !shouldTransformClass(obj.javaClass.name) + } + @OptIn(ExperimentalStdlibApi::class) private fun restoreValues(obj: Any, visitedObjects: MutableSet) { if (obj in visitedObjects) return @@ -140,13 +201,13 @@ class SnapshotTracker { ) } - private fun createMemoryNode(obj: Any?, descriptor: Descriptor): MemoryNode { + private fun createMemoryNode(obj: Any?, descriptor: Descriptor, value: Any?): MemoryNode { return when (descriptor) { is FieldDescriptor -> { - if (obj is Class<*>) StaticFieldNode(descriptor, readFieldViaUnsafe(null, descriptor.field)) - else RegularFieldNode(descriptor, readFieldViaUnsafe(obj, descriptor.field)) + if (obj == null) StaticFieldNode(descriptor, /*readFieldViaUnsafe(null, descriptor.field)*/ value) + else RegularFieldNode(descriptor, /*readFieldViaUnsafe(obj, descriptor.field)*/ value) } - is ArrayCellDescriptor -> ArrayCellNode(descriptor, readArrayElementViaUnsafe(obj!!, descriptor.index)) + is ArrayCellDescriptor -> ArrayCellNode(descriptor, /*readArrayElementViaUnsafe(obj!!, descriptor.index)*/ value) } } } \ No newline at end of file diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckJavaAgent.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckJavaAgent.kt index f5eec2e4c..4c81cc065 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckJavaAgent.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckJavaAgent.kt @@ -13,13 +13,13 @@ package org.jetbrains.kotlinx.lincheck.transformation import net.bytebuddy.agent.ByteBuddyAgent import org.jetbrains.kotlinx.lincheck.canonicalClassName import org.jetbrains.kotlinx.lincheck.runInIgnoredSection +import org.jetbrains.kotlinx.lincheck.shouldTransformClass import org.jetbrains.kotlinx.lincheck.transformation.InstrumentationMode.MODEL_CHECKING import org.jetbrains.kotlinx.lincheck.transformation.InstrumentationMode.STRESS -import org.jetbrains.kotlinx.lincheck.transformation.LincheckClassFileTransformer.shouldTransform import org.jetbrains.kotlinx.lincheck.transformation.LincheckClassFileTransformer.isEagerlyInstrumentedClass +import org.jetbrains.kotlinx.lincheck.transformation.LincheckClassFileTransformer.shouldTransform import org.jetbrains.kotlinx.lincheck.transformation.LincheckClassFileTransformer.transformedClassesStress import org.jetbrains.kotlinx.lincheck.transformation.LincheckJavaAgent.INSTRUMENT_ALL_CLASSES -import org.jetbrains.kotlinx.lincheck.transformation.LincheckJavaAgent.instrumentation import org.jetbrains.kotlinx.lincheck.transformation.LincheckJavaAgent.instrumentationMode import org.jetbrains.kotlinx.lincheck.transformation.LincheckJavaAgent.instrumentedClasses import org.jetbrains.kotlinx.lincheck.util.readFieldViaUnsafe @@ -403,54 +403,7 @@ internal object LincheckClassFileTransformer : ClassFileTransformer { if (isEagerlyInstrumentedClass(className)) { return true } - // We do not need to instrument most standard Java classes. - // It is fine to inject the Lincheck analysis only into the - // `java.util.*` ones, ignored the known atomic constructs. - if (className.startsWith("java.")) { - if (className.startsWith("java.util.concurrent.") && className.contains("Atomic")) return false - if (className.startsWith("java.util.")) return true - if (className.startsWith("com.sun.")) return false - return false - } - if (className.startsWith("sun.")) return false - if (className.startsWith("javax.")) return false - if (className.startsWith("jdk.")) return false - // We do not need to instrument most standard Kotlin classes. - // However, we need to inject the Lincheck analysis into the classes - // related to collections, iterators, random and coroutines. - if (className.startsWith("kotlin.")) { - if (className.startsWith("kotlin.collections.")) return true - if (className.startsWith("kotlin.jvm.internal.Array") && className.contains("Iterator")) return true - if (className.startsWith("kotlin.ranges.")) return true - if (className.startsWith("kotlin.random.")) return true - if (className.startsWith("kotlin.coroutines.jvm.internal.")) return false - if (className.startsWith("kotlin.coroutines.")) return true - return false - } - if (className.startsWith("kotlinx.atomicfu.")) return false - // We need to skip the classes related to the debugger support in Kotlin coroutines. - if (className.startsWith("kotlinx.coroutines.debug.")) return false - if (className == "kotlinx.coroutines.DebugKt") return false - // We should never transform the coverage-related classes. - if (className.startsWith("com.intellij.rt.coverage.")) return false - // We can also safely do not instrument some libraries for performance reasons. - if (className.startsWith("com.esotericsoftware.kryo.")) return false - if (className.startsWith("net.bytebuddy.")) return false - if (className.startsWith("net.rubygrapefruit.platform.")) return false - if (className.startsWith("io.mockk.")) return false - if (className.startsWith("it.unimi.dsi.fastutil.")) return false - if (className.startsWith("worker.org.gradle.")) return false - if (className.startsWith("org.objectweb.asm.")) return false - if (className.startsWith("org.gradle.")) return false - if (className.startsWith("org.slf4j.")) return false - if (className.startsWith("org.apache.commons.lang.")) return false - if (className.startsWith("org.junit.")) return false - if (className.startsWith("junit.framework.")) return false - // Finally, we should never instrument the Lincheck classes. - if (className.startsWith("org.jetbrains.kotlinx.lincheck.")) return false - if (className.startsWith("sun.nio.ch.lincheck.")) return false - // All the classes that were not filtered out are eligible for transformation. - return true + return shouldTransformClass(className) } // We should always eagerly transform the following classes. diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/AtomicMethods.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/AtomicMethods.kt index 72fbc1c48..6c73c0b43 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/AtomicMethods.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/AtomicMethods.kt @@ -92,7 +92,6 @@ internal fun isAtomicFUClass(className: String) = className == "kotlinx.atomicfu.AtomicInt" || className == "kotlinx.atomicfu.AtomicLong" - internal fun isAtomicMethod(className: String, methodName: String) = isAtomicClass(className) && methodName in atomicMethods @@ -113,7 +112,6 @@ internal fun isAtomicFUArray(receiver: Any?) = receiver is kotlinx.atomicfu.AtomicIntArray || receiver is kotlinx.atomicfu.AtomicLongArray - internal fun isAtomicArrayClass(className: String) = // java.util.concurrent className == "java.util.concurrent.atomic.AtomicReferenceArray" || From 128050aafb5c424e56d72b0e0b0cf0bf0a753f90 Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Fri, 1 Nov 2024 20:20:39 +0100 Subject: [PATCH 08/29] Refactor code --- .../strategy/managed/SnapshotTracker.kt | 92 ++++++++++--------- .../lincheck_test/AbstractLincheckTest.kt | 4 +- .../snapshot/SnapshotAbstractTest.kt | 4 + 3 files changed, 55 insertions(+), 45 deletions(-) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt index 91388f034..5f83077fc 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt @@ -53,6 +53,51 @@ class SnapshotTracker { class ArrayCellNode(descriptor: ArrayCellDescriptor, initialValue: Any?) : MemoryNode(descriptor, initialValue) } + fun trackField(obj: Any?, className: String, fieldName: String) { + if (obj != null && obj !in trackedObjects) return + + val clazz: Class<*> = Class.forName(className).let { + if (obj != null) it + else it.findField(fieldName).declaringClass + } + val field = clazz.findField(fieldName) + val readResult = runCatching { readFieldViaUnsafe(obj, field) } + + if (readResult.isSuccess) { + val fieldValue = readResult.getOrNull() + + trackSingleField(obj, clazz, field, fieldValue) { + if (isIgnoredClassInstance(fieldValue)) { + trackHierarchy(fieldValue!!) + } + } + } + } + + fun trackArrayCell(array: Any, index: Int) { + if (array !in trackedObjects) return + + val readResult = runCatching { readArrayElementViaUnsafe(array, index) } + + if (readResult.isSuccess) { + val elementValue = readResult.getOrNull() + + trackSingleArrayCell(array, index, elementValue) { + if (isIgnoredClassInstance(elementValue)) { + trackHierarchy(elementValue!!) + } + } + } + } + + fun restoreValues() { + val visitedObjects = Collections.newSetFromMap(IdentityHashMap()) + trackedObjects.keys + .filterIsInstance>() + .forEach { restoreValues(it, visitedObjects) } + } + + @OptIn(ExperimentalStdlibApi::class) private fun trackSingleField( obj: Any?, @@ -73,7 +118,7 @@ class SnapshotTracker { .any { it.field.name == /*fieldName*/ field.name } // field is already tracked ) return - println("SNAPSHOT: obj=${if (obj == null) "null" else obj.javaClass.simpleName + "@" + System.identityHashCode(obj).toHexString()}, className=${clazz.simpleName}, fieldName=${field.name}") +// println("SNAPSHOT: obj=${if (obj == null) "null" else obj.javaClass.simpleName + "@" + System.identityHashCode(obj).toHexString()}, className=${clazz.simpleName}, fieldName=${field.name}") val childNode = createMemoryNode( obj, FieldDescriptor(field, getFieldOffset(field)), @@ -87,25 +132,6 @@ class SnapshotTracker { } } - fun trackField(obj: Any?, className: String, fieldName: String) { - val clazz: Class<*> = Class.forName(className).let { - if (obj != null) it - else it.findField(fieldName).declaringClass - } - val field = clazz.findField(fieldName) - val readResult = runCatching { readFieldViaUnsafe(obj, field) } - - if (readResult.isSuccess) { - val fieldValue = readResult.getOrNull() - - trackSingleField(obj, clazz, field, fieldValue) { - if (isIgnoredClassInstance(fieldValue)) { - trackHierarchy(fieldValue!!) - } - } - } - } - @OptIn(ExperimentalStdlibApi::class) private fun trackSingleArrayCell(array: Any, index: Int, elementValue: Any?, callback: (() -> Unit)? = null) { val nodesList = trackedObjects[array] @@ -115,7 +141,7 @@ class SnapshotTracker { nodesList.any { it is ArrayCellNode && (it.descriptor as ArrayCellDescriptor).index == index } // this array cell is already tracked ) return - println("SNAPSHOT: array=${array.javaClass.simpleName}@${System.identityHashCode(array).toHexString()}, index=$index") +// println("SNAPSHOT: array=${array.javaClass.simpleName}@${System.identityHashCode(array).toHexString()}, index=$index") val childNode = createMemoryNode(array, ArrayCellDescriptor(index), elementValue) nodesList.add(childNode) @@ -125,31 +151,9 @@ class SnapshotTracker { } } - fun trackArrayCell(array: Any, index: Int) { - val readResult = runCatching { readArrayElementViaUnsafe(array, index) } - - if (readResult.isSuccess) { - val elementValue = readResult.getOrNull() - - trackSingleArrayCell(array, index, elementValue) { - if (isIgnoredClassInstance(elementValue)) { - trackHierarchy(elementValue!!) - } - } - } - } - - fun restoreValues() { - val visitedObjects = Collections.newSetFromMap(IdentityHashMap()) - trackedObjects.keys - .filterIsInstance>() - .forEach { restoreValues(it, visitedObjects) } - } - @OptIn(ExperimentalStdlibApi::class) private fun trackHierarchy(obj: Any) { - // TODO: iterate over object hierarchy - println("Track hierarchy for object: ${obj.javaClass.simpleName}@${System.identityHashCode(obj).toHexString()}") +// println("Track hierarchy for object: ${obj.javaClass.simpleName}@${System.identityHashCode(obj).toHexString()}") traverseObjectGraph( obj, onArrayElement = { array, index, elementValue -> diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/AbstractLincheckTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/AbstractLincheckTest.kt index 8bed18304..abfa5a14e 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/AbstractLincheckTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/AbstractLincheckTest.kt @@ -21,7 +21,9 @@ import kotlin.reflect.* abstract class AbstractLincheckTest( private vararg val expectedFailures: KClass ) { - open fun > O.customize() {} + open fun > O.customize() { + if (this is ModelCheckingOptions) restoreStaticMemory(true) + } private fun > O.runInternalTest() { val failure: LincheckFailure? = checkImpl(this@AbstractLincheckTest::class.java) diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/SnapshotAbstractTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/SnapshotAbstractTest.kt index 1f8bc0119..9ff1af415 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/SnapshotAbstractTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/SnapshotAbstractTest.kt @@ -14,10 +14,14 @@ import org.jetbrains.kotlinx.lincheck.check import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingOptions import org.junit.Test +// TODO: make each test check values on each invocation and not on test end abstract class SnapshotAbstractTest { @Test fun testModelChecking() = ModelCheckingOptions() .iterations(1) + .actorsBefore(0) + .actorsAfter(0) + .actorsPerThread(3) .restoreStaticMemory(true) .check(this::class) } \ No newline at end of file From c9f7ee2fdd1ba2ac3ec9d67f3c3cd791e2504f1c Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Thu, 7 Nov 2024 20:49:46 +0100 Subject: [PATCH 09/29] Add energetic snapshot recording for non-transformed classes --- .../src/sun/nio/ch/lincheck/EventTracker.java | 3 + .../src/sun/nio/ch/lincheck/Injections.java | 8 + .../kotlinx/lincheck/strategy/Strategy.kt | 3 - .../strategy/managed/ManagedStrategy.kt | 40 ++--- .../strategy/managed/SnapshotTracker.kt | 39 +++-- .../transformation/LincheckClassVisitor.kt | 75 ++++++++- .../SharedMemoryAccessTransformer.kt | 55 +------ .../SnapshotTrackerTransformer.kt | 147 ++++++++++++++++++ .../lincheck_test/AbstractLincheckTest.kt | 2 +- .../modelchecking/snapshot/InnerClassTest.kt | 58 +++++++ .../snapshot/SnapshotAbstractTest.kt | 9 +- .../modelchecking/snapshot/StaticArrayTest.kt | 68 ++++---- .../snapshot/StaticCollectionTest.kt | 86 ++++++++++ .../modelchecking/snapshot/StaticEnumTest.kt | 38 +++-- .../snapshot/StaticObjectAsFieldTest.kt | 33 ++-- .../snapshot/StaticObjectCycleTest.kt | 29 ++-- .../snapshot/StaticObjectTest.kt | 31 ++-- 17 files changed, 552 insertions(+), 172 deletions(-) create mode 100644 src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SnapshotTrackerTransformer.kt create mode 100644 src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/InnerClassTest.kt create mode 100644 src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticCollectionTest.kt diff --git a/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java b/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java index d41ada7a5..003ab9e17 100644 --- a/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java +++ b/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java @@ -32,6 +32,9 @@ public interface EventTracker { void beforeNewObjectCreation(String className); void afterNewObjectCreation(Object obj); + void updateSnapshotOnFieldAccess(Object obj, String className, String fieldName, int codeLocation); + void updateSnapshotOnArrayElementAccess(Object array, int index, int codeLocation); + boolean beforeReadField(Object obj, String className, String fieldName, int codeLocation, boolean isStatic, boolean isFinal); boolean beforeReadArrayElement(Object array, int index, int codeLocation); diff --git a/bootstrap/src/sun/nio/ch/lincheck/Injections.java b/bootstrap/src/sun/nio/ch/lincheck/Injections.java index 5bbfb0b87..568ff9e6c 100644 --- a/bootstrap/src/sun/nio/ch/lincheck/Injections.java +++ b/bootstrap/src/sun/nio/ch/lincheck/Injections.java @@ -294,6 +294,14 @@ public static void afterNewObjectCreation(Object obj) { getEventTracker().afterNewObjectCreation(obj); } + public static void updateSnapshotOnFieldAccess(Object obj, String className, String fieldName, int codeLocation) { + getEventTracker().updateSnapshotOnFieldAccess(obj, className, fieldName, codeLocation); + } + + public static void updateSnapshotOnArrayElementAccess(Object array, int index, int codeLocation) { + getEventTracker().updateSnapshotOnArrayElementAccess(array, index, codeLocation); + } + /** * Called from the instrumented code to replace [java.lang.Object.hashCode] method call with some * deterministic value. diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt index 0049c5cd5..8eb904f6d 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt @@ -95,9 +95,6 @@ abstract class Strategy protected constructor( * Restores recorded values of all memory reachable from static state. */ open fun restoreStaticMemorySnapshot() {} - - open fun updateStaticMemorySnapshot(obj: Any?, className: String, fieldName: String) {} - open fun updateStaticMemorySnapshot(array: Any, index: Int) {} } /** diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt index bcdee9b85..9a34be219 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt @@ -227,21 +227,6 @@ abstract class ManagedStrategy( } } - // TODO: fix bug with the fact, that non-transformed classes are impossible to track lazily - override fun updateStaticMemorySnapshot(obj: Any?, className: String, fieldName: String) { - if (testCfg.restoreStaticMemory) { - staticMemorySnapshot.trackField(obj, className, fieldName) - super.updateStaticMemorySnapshot(obj, className, fieldName) - } - } - - override fun updateStaticMemorySnapshot(array: Any, index: Int) { - if (testCfg.restoreStaticMemory) { - staticMemorySnapshot.trackArrayCell(array, index) - super.updateStaticMemorySnapshot(array, index) - } - } - /** * Runs the current invocation. */ @@ -771,7 +756,6 @@ abstract class ManagedStrategy( isStatic: Boolean, isFinal: Boolean) = runInIgnoredSection { // We need to ensure all the classes related to the reading object are instrumented. // The following call checks all the static fields. - updateStaticMemorySnapshot(obj, className.canonicalClassName, fieldName) if (isStatic) { LincheckJavaAgent.ensureClassHierarchyIsTransformed(className.canonicalClassName) } @@ -809,7 +793,6 @@ abstract class ManagedStrategy( if (!objectTracker.shouldTrackObjectAccess(array)) { return@runInIgnoredSection false } - updateStaticMemorySnapshot(array, index) val iThread = currentThread val tracePoint = if (collectTrace) { ReadTracePoint( @@ -846,7 +829,6 @@ abstract class ManagedStrategy( if (!objectTracker.shouldTrackObjectAccess(obj ?: StaticObject)) { return@runInIgnoredSection false } - updateStaticMemorySnapshot(obj, className.canonicalClassName, fieldName) // Optimization: do not track final field writes if (isFinal) { return@runInIgnoredSection false @@ -876,7 +858,6 @@ abstract class ManagedStrategy( if (!objectTracker.shouldTrackObjectAccess(array)) { return@runInIgnoredSection false } - updateStaticMemorySnapshot(array, index) val iThread = currentThread val tracePoint = if (collectTrace) { WriteTracePoint( @@ -940,6 +921,27 @@ abstract class ManagedStrategy( } } + /** + * Tracks a specific field of an [obj], if the [obj] is either `null` (which means that field is static), + * or one this objects which contains it is already stored. + */ + override fun updateSnapshotOnFieldAccess(obj: Any?, className: String, fieldName: String, codeLocation: Int) = runInIgnoredSection { + if (testCfg.restoreStaticMemory) { + val location = CodeLocations.stackTrace(codeLocation).toString() + staticMemorySnapshot.trackField(obj, className, fieldName, location) + } + } + + /** + * Tracks a specific [array] element at [index], if the [array] is already tracked. + */ + override fun updateSnapshotOnArrayElementAccess(array: Any, index: Int, codeLocation: Int) = runInIgnoredSection { + if (testCfg.restoreStaticMemory) { + val location = CodeLocations.stackTrace(codeLocation).toString() + staticMemorySnapshot.trackArrayCell(array, index, location) + } + } + private fun methodGuaranteeType(owner: Any?, className: String, methodName: String): ManagedGuaranteeType? = runInIgnoredSection { userDefinedGuarantees?.forEach { guarantee -> val ownerName = owner?.javaClass?.canonicalName ?: className diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt index 5f83077fc..91711d644 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt @@ -53,7 +53,9 @@ class SnapshotTracker { class ArrayCellNode(descriptor: ArrayCellDescriptor, initialValue: Any?) : MemoryNode(descriptor, initialValue) } - fun trackField(obj: Any?, className: String, fieldName: String) { + @OptIn(ExperimentalStdlibApi::class) + fun trackField(obj: Any?, className: String, fieldName: String, @Suppress("UNUSED_PARAMETER") location: String = "") { + //println("Consider ($location): obj=${obj?.javaClass?.simpleName}${if (obj != null) "@" + System.identityHashCode(obj).toHexString() else ""}, className=$className, fieldName=$fieldName") if (obj != null && obj !in trackedObjects) return val clazz: Class<*> = Class.forName(className).let { @@ -67,14 +69,16 @@ class SnapshotTracker { val fieldValue = readResult.getOrNull() trackSingleField(obj, clazz, field, fieldValue) { - if (isIgnoredClassInstance(fieldValue)) { + if (shouldTrackEnergetically(fieldValue)) { trackHierarchy(fieldValue!!) } } } } - fun trackArrayCell(array: Any, index: Int) { + @OptIn(ExperimentalStdlibApi::class) + fun trackArrayCell(array: Any, index: Int, @Suppress("UNUSED_PARAMETER") location: String = "") { + //println("Consider ($location): array=${array.javaClass.simpleName}@${System.identityHashCode(array).toHexString()}, index=$index") if (array !in trackedObjects) return val readResult = runCatching { readArrayElementViaUnsafe(array, index) } @@ -83,7 +87,7 @@ class SnapshotTracker { val elementValue = readResult.getOrNull() trackSingleArrayCell(array, index, elementValue) { - if (isIgnoredClassInstance(elementValue)) { + if (shouldTrackEnergetically(elementValue)) { trackHierarchy(elementValue!!) } } @@ -91,6 +95,7 @@ class SnapshotTracker { } fun restoreValues() { + //println("====== START RESTORING ======") val visitedObjects = Collections.newSetFromMap(IdentityHashMap()) trackedObjects.keys .filterIsInstance>() @@ -115,10 +120,10 @@ class SnapshotTracker { nodesList .map { it.descriptor } .filterIsInstance() - .any { it.field.name == /*fieldName*/ field.name } // field is already tracked + .any { it.field.name == field.name } // field is already tracked ) return -// println("SNAPSHOT: obj=${if (obj == null) "null" else obj.javaClass.simpleName + "@" + System.identityHashCode(obj).toHexString()}, className=${clazz.simpleName}, fieldName=${field.name}") + //println("SNAPSHOT: obj=${if (obj == null) "null" else obj.javaClass.simpleName + "@" + System.identityHashCode(obj).toHexString()}, className=${clazz.name}, fieldName=${field.name}") val childNode = createMemoryNode( obj, FieldDescriptor(field, getFieldOffset(field)), @@ -141,7 +146,7 @@ class SnapshotTracker { nodesList.any { it is ArrayCellNode && (it.descriptor as ArrayCellDescriptor).index == index } // this array cell is already tracked ) return -// println("SNAPSHOT: array=${array.javaClass.simpleName}@${System.identityHashCode(array).toHexString()}, index=$index") + //println("SNAPSHOT: array=${array.javaClass.simpleName}@${System.identityHashCode(array).toHexString()}, index=$index") val childNode = createMemoryNode(array, ArrayCellDescriptor(index), elementValue) nodesList.add(childNode) @@ -153,7 +158,7 @@ class SnapshotTracker { @OptIn(ExperimentalStdlibApi::class) private fun trackHierarchy(obj: Any) { -// println("Track hierarchy for object: ${obj.javaClass.simpleName}@${System.identityHashCode(obj).toHexString()}") + //println("Track hierarchy for object: ${obj.javaClass.simpleName}@${System.identityHashCode(obj).toHexString()}") traverseObjectGraph( obj, onArrayElement = { array, index, elementValue -> @@ -167,22 +172,30 @@ class SnapshotTracker { ) } - private fun isIgnoredClassInstance(obj: Any?): Boolean { - return obj != null && !shouldTransformClass(obj.javaClass.name) + private fun shouldTrackEnergetically(obj: Any?): Boolean { + // TODO: We should filter out some standard library classes that we don't care about (like, PrintStream: System.out/in/err) + // and only traverse energetically user classes and important std classes like AtomicInteger, etc. + return ( + obj != null && + (!shouldTransformClass(obj.javaClass.name) && !obj.javaClass.name.startsWith("org.jetbrains.kotlinx.lincheck")) + ) } @OptIn(ExperimentalStdlibApi::class) private fun restoreValues(obj: Any, visitedObjects: MutableSet) { if (obj in visitedObjects) return + //println("RESTORE: obj=${if (obj is Class<*>) obj.simpleName else obj.javaClass.simpleName}@${System.identityHashCode(obj).toHexString()}:") visitedObjects.add(obj) trackedObjects[obj]!! .forEach { node -> if (node is ArrayCellNode) { val index = (node.descriptor as ArrayCellDescriptor).index + //println("\tRESTORE: arr=${obj.javaClass.simpleName}@${System.identityHashCode(obj).toHexString()}, index=$index, value=${node.initialValue}") writeArrayElementViaUnsafe(obj, index, node.initialValue) } else if (!Modifier.isFinal((node.descriptor as FieldDescriptor).field.modifiers)) { + //println("\tRESTORE: obj=${if (obj is Class<*>) obj.simpleName else obj.javaClass.simpleName}@${System.identityHashCode(obj).toHexString()}, field=${node.descriptor.field.name}, value=${node.initialValue}") writeFieldViaUnsafe( if (node is StaticFieldNode) null else obj, node.descriptor.field, @@ -208,10 +221,10 @@ class SnapshotTracker { private fun createMemoryNode(obj: Any?, descriptor: Descriptor, value: Any?): MemoryNode { return when (descriptor) { is FieldDescriptor -> { - if (obj == null) StaticFieldNode(descriptor, /*readFieldViaUnsafe(null, descriptor.field)*/ value) - else RegularFieldNode(descriptor, /*readFieldViaUnsafe(obj, descriptor.field)*/ value) + if (obj == null) StaticFieldNode(descriptor, value) + else RegularFieldNode(descriptor, value) } - is ArrayCellDescriptor -> ArrayCellNode(descriptor, /*readArrayElementViaUnsafe(obj!!, descriptor.index)*/ value) + is ArrayCellDescriptor -> ArrayCellNode(descriptor, value) } } } \ No newline at end of file diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckClassVisitor.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckClassVisitor.kt index 84810546f..70a998273 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckClassVisitor.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckClassVisitor.kt @@ -17,8 +17,8 @@ import org.objectweb.asm.Type.* import org.objectweb.asm.commons.* import org.jetbrains.kotlinx.lincheck.transformation.InstrumentationMode.* import org.jetbrains.kotlinx.lincheck.transformation.transformers.* +import org.objectweb.asm.commons.InstructionAdapter.OBJECT_TYPE import sun.nio.ch.lincheck.* -import kotlin.collections.HashSet internal class LincheckClassVisitor( private val instrumentationMode: InstrumentationMode, @@ -91,6 +91,12 @@ internal class LincheckClassVisitor( } if (methodName == "") { mv = ObjectCreationTransformer(fileName, className, methodName, mv.newAdapter()) + mv = run { + val st = SnapshotTrackerTransformer(fileName, className, methodName, mv.newAdapter()) + val aa = AnalyzerAdapter(className, access, methodName, desc, st) + st.analyzer = aa + aa + } return mv } /* Wrap `ClassLoader::loadClass` calls into ignored sections @@ -145,8 +151,11 @@ internal class LincheckClassVisitor( // which should be put in front of the byte-code transformer chain, // so that it can correctly analyze the byte-code and compute required type-information mv = run { - val sv = SharedMemoryAccessTransformer(fileName, className, methodName, mv.newAdapter()) + val st = SnapshotTrackerTransformer(fileName, className, methodName, mv.newAdapter()) + val sv = SharedMemoryAccessTransformer(fileName, className, methodName, st.newAdapter()) val aa = AnalyzerAdapter(className, access, methodName, desc, sv) + + st.analyzer = aa sv.analyzer = aa aa } @@ -160,6 +169,68 @@ internal class LincheckClassVisitor( } +internal open class ManagedStrategyWithAnalyzerClassVisitor( + fileName: String, + className: String, + methodName: String, + adapter: GeneratorAdapter +) : ManagedStrategyMethodVisitor(fileName, className, methodName, adapter) { + + lateinit var analyzer: AnalyzerAdapter + + + /* + * For an array access instruction (either load or store), + * tries to obtain the type of the read/written array element. + * + * If the type can be determined from the opcode of the instruction itself + * (e.g., IALOAD/IASTORE) returns it immediately. + * + * Otherwise, queries the analyzer to determine the type of the array in the respective stack slot. + * This is used in two cases: + * - for `BALOAD` and `BASTORE` instructions, since they are used to access both boolean and byte arrays; + * - for `AALOAD` and `AASTORE` instructions, to get the class name of the array elements. + */ + protected fun getArrayElementType(opcode: Int): Type = when (opcode) { + // Load + IALOAD -> INT_TYPE + FALOAD -> FLOAT_TYPE + CALOAD -> CHAR_TYPE + SALOAD -> SHORT_TYPE + LALOAD -> LONG_TYPE + DALOAD -> DOUBLE_TYPE + BALOAD -> getArrayAccessTypeFromStack(2) ?: BYTE_TYPE + AALOAD -> getArrayAccessTypeFromStack(2) ?: OBJECT_TYPE + // Store + IASTORE -> INT_TYPE + FASTORE -> FLOAT_TYPE + CASTORE -> CHAR_TYPE + SASTORE -> SHORT_TYPE + LASTORE -> LONG_TYPE + DASTORE -> DOUBLE_TYPE + BASTORE -> getArrayAccessTypeFromStack(3) ?: BYTE_TYPE + AASTORE -> getArrayAccessTypeFromStack(3) ?: OBJECT_TYPE + else -> throw IllegalStateException("Unexpected opcode: $opcode") + } + + /* + * Tries to obtain the type of array elements by inspecting the type of the array itself. + * To do this, the method queries the analyzer to get the type of accessed array + * which should lie on the stack. + * If the analyzer does not know the type, then return null + * (according to the ASM docs, this can happen, for example, when the visited instruction is unreachable). + */ + protected fun getArrayAccessTypeFromStack(position: Int): Type? { + if (analyzer.stack == null) return null + val arrayDesc = analyzer.stack[analyzer.stack.size - position] + check(arrayDesc is String) + val arrayType = getType(arrayDesc) + check(arrayType.sort == ARRAY) + check(arrayType.dimensions > 0) + return getType(arrayDesc.substring(1)) + } +} + internal open class ManagedStrategyMethodVisitor( protected val fileName: String, protected val className: String, diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SharedMemoryAccessTransformer.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SharedMemoryAccessTransformer.kt index de1020306..620763e22 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SharedMemoryAccessTransformer.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SharedMemoryAccessTransformer.kt @@ -28,9 +28,7 @@ internal class SharedMemoryAccessTransformer( className: String, methodName: String, adapter: GeneratorAdapter, -) : ManagedStrategyMethodVisitor(fileName, className, methodName, adapter) { - - lateinit var analyzer: AnalyzerAdapter +) : ManagedStrategyWithAnalyzerClassVisitor(fileName, className, methodName, adapter) { override fun visitFieldInsn(opcode: Int, owner: String, fieldName: String, desc: String) = adapter.run { if (isCoroutineInternalClass(owner) || isCoroutineStateMachineClass(owner)) { @@ -270,55 +268,4 @@ internal class SharedMemoryAccessTransformer( invokeStatic(Injections::afterRead) // STACK: value } - - /* - * For an array access instruction (either load or store), - * tries to obtain the type of the read/written array element. - * - * If the type can be determined from the opcode of the instruction itself - * (e.g., IALOAD/IASTORE) returns it immediately. - * - * Otherwise, queries the analyzer to determine the type of the array in the respective stack slot. - * This is used in two cases: - * - for `BALOAD` and `BASTORE` instructions, since they are used to access both boolean and byte arrays; - * - for `AALOAD` and `AASTORE` instructions, to get the class name of the array elements. - */ - private fun getArrayElementType(opcode: Int): Type = when (opcode) { - // Load - IALOAD -> INT_TYPE - FALOAD -> FLOAT_TYPE - CALOAD -> CHAR_TYPE - SALOAD -> SHORT_TYPE - LALOAD -> LONG_TYPE - DALOAD -> DOUBLE_TYPE - BALOAD -> getArrayAccessTypeFromStack(2) ?: BYTE_TYPE - AALOAD -> getArrayAccessTypeFromStack(2) ?: OBJECT_TYPE - // Store - IASTORE -> INT_TYPE - FASTORE -> FLOAT_TYPE - CASTORE -> CHAR_TYPE - SASTORE -> SHORT_TYPE - LASTORE -> LONG_TYPE - DASTORE -> DOUBLE_TYPE - BASTORE -> getArrayAccessTypeFromStack(3) ?: BYTE_TYPE - AASTORE -> getArrayAccessTypeFromStack(3) ?: OBJECT_TYPE - else -> throw IllegalStateException("Unexpected opcode: $opcode") - } - - /* - * Tries to obtain the type of array elements by inspecting the type of the array itself. - * To do this, the method queries the analyzer to get the type of accessed array - * which should lie on the stack. - * If the analyzer does not know the type, then return null - * (according to the ASM docs, this can happen, for example, when the visited instruction is unreachable). - */ - private fun getArrayAccessTypeFromStack(position: Int): Type? { - if (analyzer.stack == null) return null - val arrayDesc = analyzer.stack[analyzer.stack.size - position] - check(arrayDesc is String) - val arrayType = getType(arrayDesc) - check(arrayType.sort == ARRAY) - check(arrayType.dimensions > 0) - return getType(arrayDesc.substring(1)) - } } \ No newline at end of file diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SnapshotTrackerTransformer.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SnapshotTrackerTransformer.kt new file mode 100644 index 000000000..ba6246532 --- /dev/null +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SnapshotTrackerTransformer.kt @@ -0,0 +1,147 @@ +/* + * Lincheck + * + * Copyright (C) 2019 - 2024 JetBrains s.r.o. + * + * This Source Code Form is subject to the terms of the + * Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed + * with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.jetbrains.kotlinx.lincheck.transformation.transformers + +import org.jetbrains.kotlinx.lincheck.canonicalClassName +import org.jetbrains.kotlinx.lincheck.transformation.* +import org.objectweb.asm.Opcodes.* +import org.objectweb.asm.Type.* +import org.objectweb.asm.commons.GeneratorAdapter +import sun.nio.ch.lincheck.Injections + +internal class SnapshotTrackerTransformer( + fileName: String, + className: String, + methodName: String, + adapter: GeneratorAdapter, +) : ManagedStrategyWithAnalyzerClassVisitor(fileName, className, methodName, adapter) { + + override fun visitFieldInsn(opcode: Int, owner: String, fieldName: String, desc: String) = adapter.run { + if ( + isCoroutineInternalClass(owner) || + isCoroutineStateMachineClass(owner) || + // when initializing our own fields in constructor, we do not want to track that as snapshot modification + (methodName == "" && className == owner) + ) { + visitFieldInsn(opcode, owner, fieldName, desc) + return + } + + when (opcode) { + GETSTATIC, PUTSTATIC -> { + // STACK: [ | value] + invokeIfInTestingCode( + original = { + visitFieldInsn(opcode, owner, fieldName, desc) + }, + code = { + // STACK: [ | value] + pushNull() + push(owner.canonicalClassName) + push(fieldName) + loadNewCodeLocationId() + // STACK: [ | value], null, className, fieldName, codeLocation + invokeStatic(Injections::updateSnapshotOnFieldAccess) + // STACK: [ | value] + visitFieldInsn(opcode, owner, fieldName, desc) + // STACK: [ | value] + } + ) + } + + GETFIELD, PUTFIELD -> { + // STACK: obj, [value] + invokeIfInTestingCode( + original = { + visitFieldInsn(opcode, owner, fieldName, desc) + }, + code = { + val valueType = getType(desc) + val valueLocal = newLocal(valueType) // we cannot use DUP as long/double require DUP2 + + // STACK: obj, [value] + if (opcode == PUTFIELD) storeLocal(valueLocal) + // STACK: obj + dup() + // STACK: obj, obj + push(owner.canonicalClassName) + push(fieldName) + loadNewCodeLocationId() + // STACK: obj, obj, className, fieldName, codeLocation + invokeStatic(Injections::updateSnapshotOnFieldAccess) + // STACK: obj + if (opcode == PUTFIELD) loadLocal(valueLocal) + // STACK: obj, [value] + visitFieldInsn(opcode, owner, fieldName, desc) + // STACK: [ | value] + } + ) + } + + else -> { + visitFieldInsn(opcode, owner, fieldName, desc) + } + } + } + + override fun visitInsn(opcode: Int) = adapter.run { + when (opcode) { + AALOAD, LALOAD, FALOAD, DALOAD, IALOAD, BALOAD, CALOAD, SALOAD -> { + invokeIfInTestingCode( + original = { + visitInsn(opcode) + }, + code = { + // STACK: array, index + dup2() + // STACK: array, index, array, index + loadNewCodeLocationId() + // STACK: array, index, array, index, codeLocation + invokeStatic(Injections::updateSnapshotOnArrayElementAccess) + // STACK: array, index + visitInsn(opcode) + // STACK: value + } + ) + } + + AASTORE, IASTORE, FASTORE, BASTORE, CASTORE, SASTORE, LASTORE, DASTORE -> { + invokeIfInTestingCode( + original = { + visitInsn(opcode) + }, + code = { + val arrayElementType = getArrayElementType(opcode) + val valueLocal = newLocal(arrayElementType) // we cannot use DUP as long/double require DUP2 + + // STACK: array, index, value + storeLocal(valueLocal) + // STACK: array, index + dup2() + // STACK: array, index, array, index + loadNewCodeLocationId() + // STACK: array, index, array, index, codeLocation + invokeStatic(Injections::updateSnapshotOnArrayElementAccess) + // STACK: array, index + loadLocal(valueLocal) + // STACK: array, index, value + visitInsn(opcode) + // STACK: + } + ) + } + + else -> { + visitInsn(opcode) + } + } + } +} \ No newline at end of file diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/AbstractLincheckTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/AbstractLincheckTest.kt index abfa5a14e..074a68356 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/AbstractLincheckTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/AbstractLincheckTest.kt @@ -22,7 +22,7 @@ abstract class AbstractLincheckTest( private vararg val expectedFailures: KClass ) { open fun > O.customize() { - if (this is ModelCheckingOptions) restoreStaticMemory(true) + if (this is ModelCheckingOptions) restoreStaticMemory(false) } private fun > O.runInternalTest() { diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/InnerClassTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/InnerClassTest.kt new file mode 100644 index 000000000..72508e20c --- /dev/null +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/InnerClassTest.kt @@ -0,0 +1,58 @@ +package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot + +import org.jetbrains.kotlinx.lincheck.ExceptionResult +import org.jetbrains.kotlinx.lincheck.Options +import org.jetbrains.kotlinx.lincheck.annotations.Operation +import org.jetbrains.kotlinx.lincheck.execution.* +import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingOptions +import org.jetbrains.kotlinx.lincheck.verifier.Verifier + + +private class Outer { + class C(@JvmField var value: Int) + + @JvmField + var c = C(1) + + inner class Inner { + val linkToOuterValue: C + init { + linkToOuterValue = this@Outer.c + } + + fun changeA() { + linkToOuterValue.value = 2 + } + } +} + +private val a = Outer() + +class InnerClassTest : SnapshotAbstractTest() { + class InnerClassVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : Verifier { + override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { + results?.parallelResults?.forEach { threadsResults -> + threadsResults.forEach { result -> + if (result is ExceptionResult) { + throw result.throwable + } + } + } + + check(a.c.value == 1) + return true + } + } + + override fun > O.customize() { + iterations(1) + if (this is ModelCheckingOptions) invocationsPerIteration(1) + verifier(InnerClassVerifier::class.java) + } + + @Operation + fun test() { + val b = a.Inner() + b.changeA() + } +} \ No newline at end of file diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/SnapshotAbstractTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/SnapshotAbstractTest.kt index 9ff1af415..d99f3394f 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/SnapshotAbstractTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/SnapshotAbstractTest.kt @@ -10,18 +10,23 @@ package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot +import org.jetbrains.kotlinx.lincheck.LoggingLevel +import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.check import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingOptions import org.junit.Test -// TODO: make each test check values on each invocation and not on test end abstract class SnapshotAbstractTest { + open fun > O.customize() {} + @Test fun testModelChecking() = ModelCheckingOptions() +// .logLevel(LoggingLevel.INFO) .iterations(1) .actorsBefore(0) .actorsAfter(0) - .actorsPerThread(3) + .actorsPerThread(2) .restoreStaticMemory(true) + .apply { customize() } .check(this::class) } \ No newline at end of file diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticArrayTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticArrayTest.kt index 897c63b3c..9c2e0c51d 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticArrayTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticArrayTest.kt @@ -10,28 +10,37 @@ package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot +import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.annotations.Operation -import org.junit.After -import org.junit.Before +import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult +import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario +import org.jetbrains.kotlinx.lincheck.verifier.Verifier import kotlin.random.Random private var intArray = intArrayOf(1, 2, 3) class StaticIntArrayTest : SnapshotAbstractTest() { - private var ref = intArray - private var values = intArray.copyOf() + companion object { + private var ref = intArray + private var values = intArray.copyOf() + } + class StaticIntArrayVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : Verifier { + override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { + check(intArray == ref) + check(ref.contentEquals(values)) + return true + } + } + + override fun > O.customize() { + verifier(StaticIntArrayVerifier::class.java) + } @Operation fun modify() { intArray[0]++ } - - @After - fun checkStaticStateRestored() { - check(intArray == ref) - check(ref.contentEquals(values)) - } } @@ -43,9 +52,24 @@ private class X(var value: Int) { private var objArray = arrayOf(X(1), X(2), X(3)) class StaticObjectArrayTest : SnapshotAbstractTest() { - private var ref: Array? = null - private var elements: Array? = null - private var values: Array? = null + companion object { + private var ref: Array = objArray + private var elements: Array = Array(3) { null }.also { objArray.forEachIndexed { index, x -> it[index] = x } } + private var values: Array = Array(3) { 0 }.also { objArray.forEachIndexed { index, x -> it[index] = x.value } } + } + + class StaticObjectArrayVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : Verifier { + override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { + check(objArray == ref) + check(objArray.contentEquals(elements)) + check(objArray.map { it.value }.toTypedArray().contentEquals(values)) + return true + } + } + + override fun > O.customize() { + verifier(StaticObjectArrayVerifier::class.java) + } @Operation fun modify() { @@ -53,22 +77,4 @@ class StaticObjectArrayTest : SnapshotAbstractTest() { objArray[1].value-- objArray[2] = X(Random.nextInt()) } - - @Before - fun saveInitStaticState() { - ref = objArray - - elements = Array(3) { null } - objArray.forEachIndexed { index, x -> elements!![index] = x } - - values = Array(3) { 0 } - objArray.forEachIndexed { index, x -> values!![index] = x.value } - } - - @After - fun checkStaticStateRestored() { - check(objArray == ref) - check(objArray.contentEquals(elements)) - check(objArray.map { it.value }.toTypedArray().contentEquals(values)) - } } \ No newline at end of file diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticCollectionTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticCollectionTest.kt new file mode 100644 index 000000000..bfcc67652 --- /dev/null +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticCollectionTest.kt @@ -0,0 +1,86 @@ +/* + * Lincheck + * + * Copyright (C) 2019 - 2024 JetBrains s.r.o. + * + * This Source Code Form is subject to the terms of the + * Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed + * with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot + +import org.jetbrains.kotlinx.lincheck.ExceptionResult +import org.jetbrains.kotlinx.lincheck.Options +import org.jetbrains.kotlinx.lincheck.annotations.Operation +import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult +import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario +import org.jetbrains.kotlinx.lincheck.execution.parallelResults +import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingOptions +import org.jetbrains.kotlinx.lincheck.verifier.Verifier +import kotlin.random.Random + +private class Wrapper(var value: Int) +private var staticSet = mutableSetOf() +private var staticMap = mutableMapOf() + +class StaticCollectionTest : SnapshotAbstractTest() { + companion object { + private val ref = staticSet + private val a = Wrapper(1) + private val b = Wrapper(2) + private val c = Wrapper(3) + init { + staticSet.addAll(listOf(a, b, c)) + staticMap.put(1, b) + } + } + + class StaticCollectionVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : Verifier { + override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { + results?.parallelResults?.forEach { threadsResults -> + threadsResults.forEach { result -> + if (result is ExceptionResult) { + throw result.throwable + } + } + } + + check(staticMap.size == 1 && staticMap.entries.containsAll(mapOf(1 to b).entries)) + + check(staticSet == ref) + check(staticSet.size == 3 && staticSet.containsAll(listOf(a, b, c))) + check(a.value == 1 && b.value == 2 && c.value == 3) + + return true + } + } + + override fun > O.customize() { + iterations(100) + if (this is ModelCheckingOptions) invocationsPerIteration(1) + threads(1) + actorsPerThread(10) + verifier(StaticCollectionVerifier::class.java) + } + + @Operation + fun addElement() { + staticSet.add(Wrapper(Random.nextInt())) + } + + @Operation + fun removeElement() { + staticSet.remove(staticSet.randomOrNull() ?: return) + } + + @Operation + fun updateElement() { + staticSet.randomOrNull()?.value = Random.nextInt() + } + + @Operation + fun clear() { + staticSet.clear() + } +} \ No newline at end of file diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticEnumTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticEnumTest.kt index dbb2b1a55..359911207 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticEnumTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticEnumTest.kt @@ -10,9 +10,11 @@ package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot +import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.annotations.Operation -import org.junit.After -import org.junit.Before +import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult +import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario +import org.jetbrains.kotlinx.lincheck.verifier.Verifier private enum class Values { @@ -23,9 +25,24 @@ private class EnumHolder(var x: Values, var y: Values) private var global = EnumHolder(Values.A, Values.B) class StaticEnumTest : SnapshotAbstractTest() { - private var initA: EnumHolder = global - private var initX: Values = global.x - private var initY: Values = global.y + companion object { + private var initA: EnumHolder = global + private var initX: Values = global.x + private var initY: Values = global.y + } + + class StaticEnumVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : Verifier { + override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { + check(global == initA) + check(global.x == initX) + check(global.y == initY) + return true + } + } + + override fun > O.customize() { + verifier(StaticEnumVerifier::class.java) + } @Operation fun modifyFields() { @@ -36,15 +53,4 @@ class StaticEnumTest : SnapshotAbstractTest() { // assign different instance to the variable global = EnumHolder(Values.C, Values.C) } - - @Before - fun saveInitStaticState() { - } - - @After - fun checkStaticStateRestored() { - check(global == initA) - check(global.x == initX) - check(global.y == initY) - } } \ No newline at end of file diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectAsFieldTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectAsFieldTest.kt index cf6487b13..f39af0ab3 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectAsFieldTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectAsFieldTest.kt @@ -10,8 +10,11 @@ package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot +import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.annotations.Operation -import org.junit.After +import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult +import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario +import org.jetbrains.kotlinx.lincheck.verifier.Verifier private object Static1 { @@ -25,11 +28,25 @@ private object Static2 { } class StaticObjectAsFieldTest : SnapshotAbstractTest() { - private val initS1f1 = Static1.f1 - private val initS1f2 = Static1.f2 + companion object { + private val initS1f1 = Static1.f1 + private val initS1f2 = Static1.f2 - private val initS2f1 = Static2.f1 - private val initS2f2 = Static2.f2 + private val initS2f1 = Static2.f1 + private val initS2f2 = Static2.f2 + } + + class StaticObjectAsFieldVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : Verifier { + override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { + check(Static1.f1 == initS1f1 && Static1.f2 == initS1f2) + check(Static2.f1 == initS2f1 && Static2.f2 == initS2f2) + return true + } + } + + override fun > O.customize() { + verifier(StaticObjectAsFieldVerifier::class.java) + } @Operation fun modify() { @@ -39,10 +56,4 @@ class StaticObjectAsFieldTest : SnapshotAbstractTest() { Static1.f1 = null Static1.f2 = 10 } - - @After - fun checkStaticStateRestored() { - check(Static1.f1 == initS1f1 && Static1.f2 == initS1f2) - check(Static2.f1 == initS2f1 && Static2.f2 == initS2f2) - } } \ No newline at end of file diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectCycleTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectCycleTest.kt index 718608eea..0e014186e 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectCycleTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectCycleTest.kt @@ -10,8 +10,11 @@ package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot +import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.annotations.Operation -import org.junit.After +import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult +import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario +import org.jetbrains.kotlinx.lincheck.verifier.Verifier private class A(var b: B) private class B(var a: A? = null) @@ -20,23 +23,29 @@ private var globalA = A(B()) class StaticObjectCycleTest : SnapshotAbstractTest() { companion object { + private var initA = globalA + private var initB = globalA.b + init { globalA.b.a = globalA } } - private var initA = globalA - private var initB = globalA.b + class StaticObjectCycleVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : Verifier { + override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { + check(globalA == initA) + check(globalA.b == initB) + check(globalA.b.a == globalA) + return true + } + } + + override fun > O.customize() { + verifier(StaticObjectCycleVerifier::class.java) + } @Operation fun modify() { globalA = A(B()) } - - @After - fun checkStaticStateRestored() { - check(globalA == initA) - check(globalA.b == initB) - check(globalA.b.a == globalA) - } } \ No newline at end of file diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectTest.kt index 674b3171b..26f78fdc9 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectTest.kt @@ -10,25 +10,36 @@ package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot +import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.annotations.Operation -import org.junit.After +import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult +import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario +import org.jetbrains.kotlinx.lincheck.verifier.Verifier import java.util.concurrent.atomic.AtomicInteger private var staticInt = AtomicInteger(1) class StaticObjectTest : SnapshotAbstractTest() { - @Operation - fun modify() { - staticInt.getAndIncrement() + companion object { + private var ref: AtomicInteger = staticInt + private var value: Int = staticInt.get() } - private var ref = staticInt - private var value = staticInt.get() + class StaticObjectVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : Verifier { + override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { + check(staticInt == ref) + check(staticInt.get() == value) + return true + } + } - @After - fun checkStaticStateRestored() { - check(staticInt == ref) - check(staticInt.get() == value) + override fun > O.customize() { + verifier(StaticObjectVerifier::class.java) + } + + @Operation + fun modify() { + staticInt.getAndIncrement() } } \ No newline at end of file From b7a488d276502200120b627f73be6aa0255e0bde Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Fri, 8 Nov 2024 20:43:59 +0100 Subject: [PATCH 10/29] Enable snapshot restoring for all tests, fix representation tests --- .../org/jetbrains/kotlinx/lincheck/Utils.kt | 38 +++++++++- .../managed/ManagedCTestConfiguration.kt | 2 +- .../strategy/managed/SnapshotTracker.kt | 27 +++++-- .../lincheck_test/AbstractLincheckTest.kt | 5 +- .../modelchecking/snapshot/InnerClassTest.kt | 13 +--- .../snapshot/SnapshotAbstractTest.kt | 18 ++++- .../modelchecking/snapshot/StaticArrayTest.kt | 8 ++- .../snapshot/StaticCollectionTest.kt | 19 +---- .../modelchecking/snapshot/StaticEnumTest.kt | 4 +- .../snapshot/StaticObjectAsFieldTest.kt | 4 +- .../snapshot/StaticObjectCycleTest.kt | 4 +- .../snapshot/StaticObjectTest.kt | 4 +- .../atomic_references_names_trace.txt | 70 +++++++++---------- .../varhandle_boolean_representation.txt | 32 ++++----- .../varhandle_byte_representation.txt | 4 +- .../varhandle_char_representation.txt | 4 +- .../varhandle_double_representation.txt | 4 +- .../varhandle_float_representation.txt | 4 +- .../varhandle_int_representation.txt | 4 +- .../varhandle_long_representation.txt | 4 +- .../varhandle_short_representation.txt | 4 +- 21 files changed, 159 insertions(+), 117 deletions(-) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt index 8591211b1..e732aabb8 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt @@ -14,6 +14,14 @@ import org.jetbrains.kotlinx.lincheck.runner.* import org.jetbrains.kotlinx.lincheck.strategy.managed.* import org.jetbrains.kotlinx.lincheck.transformation.LincheckClassFileTransformer import org.jetbrains.kotlinx.lincheck.util.UnsafeHolder +import org.jetbrains.kotlinx.lincheck.util.isAtomicArray +import org.jetbrains.kotlinx.lincheck.util.isAtomicFUArray +import org.jetbrains.kotlinx.lincheck.util.isAtomicArray +import org.jetbrains.kotlinx.lincheck.util.isAtomicFUArray +import org.jetbrains.kotlinx.lincheck.util.isAtomicFieldUpdater +import org.jetbrains.kotlinx.lincheck.util.isUnsafeClass +import org.jetbrains.kotlinx.lincheck.util.readArrayElementViaUnsafe +import org.jetbrains.kotlinx.lincheck.util.readFieldViaUnsafe import org.jetbrains.kotlinx.lincheck.verifier.* import sun.nio.ch.lincheck.* import java.io.PrintWriter @@ -22,9 +30,13 @@ import java.lang.ref.* import java.lang.reflect.* import java.lang.reflect.Method import java.util.* +import java.util.concurrent.atomic.AtomicIntegerArray +import java.util.concurrent.atomic.AtomicLongArray +import java.util.concurrent.atomic.AtomicReferenceArray import kotlin.coroutines.* import kotlin.coroutines.intrinsics.* +// TODO: put this back to agent class fun shouldTransformClass(className: String): Boolean { // We do not need to instrument most standard Java classes. // It is fine to inject the Lincheck analysis only into the @@ -328,7 +340,31 @@ internal fun getArrayElementOffset(arr: Any, index: Int): Long { return baseOffset + index * indexScale } -internal fun getArrayLength(arr: Any): Int = java.lang.reflect.Array.getLength(arr) +internal fun getArrayLength(arr: Any): Int { + return when { + arr is Array<*> -> arr.size + arr is IntArray -> arr.size + arr is DoubleArray -> arr.size + arr is FloatArray -> arr.size + arr is LongArray -> arr.size + arr is ShortArray -> arr.size + arr is ByteArray -> arr.size + arr is BooleanArray -> arr.size + arr is CharArray -> arr.size + isAtomicArray(arr) -> getAtomicArrayLength(arr) + else -> error("Argument is not an array") + } +} + +internal fun getAtomicArrayLength(arr: Any): Int { + return when { + arr is AtomicReferenceArray<*> -> arr.length() + arr is AtomicIntegerArray -> arr.length() + arr is AtomicLongArray -> arr.length() + isAtomicFUArray(arr) -> arr.javaClass.getMethod("getSize").invoke(arr) as Int + else -> error("Argument is not atomic array") + } +} @Suppress("DEPRECATION") internal fun findFieldNameByOffset(targetType: Class<*>, offset: Long): String? { diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedCTestConfiguration.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedCTestConfiguration.kt index a7ef09df9..5deac7fd6 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedCTestConfiguration.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedCTestConfiguration.kt @@ -53,7 +53,7 @@ abstract class ManagedCTestConfiguration( const val DEFAULT_CHECK_OBSTRUCTION_FREEDOM = false const val DEFAULT_HANGING_DETECTION_THRESHOLD = 101 const val LIVELOCK_EVENTS_THRESHOLD = 10001 - const val DEFAULT_RESTORE_STATIC_MEMORY = false + const val DEFAULT_RESTORE_STATIC_MEMORY = true val DEFAULT_GUARANTEES = listOf() } } diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt index 91711d644..e6c6add26 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt @@ -12,7 +12,6 @@ package org.jetbrains.kotlinx.lincheck.strategy.managed import org.jetbrains.kotlinx.lincheck.findField import org.jetbrains.kotlinx.lincheck.getFieldOffset -import org.jetbrains.kotlinx.lincheck.shouldTransformClass import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.Descriptor.ArrayCellDescriptor import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.Descriptor.FieldDescriptor import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.MemoryNode.ArrayCellNode @@ -27,6 +26,9 @@ import java.lang.reflect.Field import java.lang.reflect.Modifier import java.util.Collections import java.util.IdentityHashMap +import java.util.concurrent.atomic.AtomicIntegerArray +import java.util.concurrent.atomic.AtomicLongArray +import java.util.concurrent.atomic.AtomicReferenceArray /** @@ -53,6 +55,8 @@ class SnapshotTracker { class ArrayCellNode(descriptor: ArrayCellDescriptor, initialValue: Any?) : MemoryNode(descriptor, initialValue) } + fun size(): Int = (trackedObjects.keys.filter { it !is Class<*> } + trackedObjects.values.flatMap { it }.mapNotNull { it.initialValue }).toSet().size + @OptIn(ExperimentalStdlibApi::class) fun trackField(obj: Any?, className: String, fieldName: String, @Suppress("UNUSED_PARAMETER") location: String = "") { //println("Consider ($location): obj=${obj?.javaClass?.simpleName}${if (obj != null) "@" + System.identityHashCode(obj).toHexString() else ""}, className=$className, fieldName=$fieldName") @@ -123,7 +127,7 @@ class SnapshotTracker { .any { it.field.name == field.name } // field is already tracked ) return - //println("SNAPSHOT: obj=${if (obj == null) "null" else obj.javaClass.simpleName + "@" + System.identityHashCode(obj).toHexString()}, className=${clazz.name}, fieldName=${field.name}") + //println("SNAPSHOT: obj=${if (obj == null) "null" else obj.javaClass.simpleName + "@" + System.identityHashCode(obj).toHexString()}, className=${clazz.name}, fieldName=${field.name}, value=${fieldValue}") val childNode = createMemoryNode( obj, FieldDescriptor(field, getFieldOffset(field)), @@ -146,7 +150,7 @@ class SnapshotTracker { nodesList.any { it is ArrayCellNode && (it.descriptor as ArrayCellDescriptor).index == index } // this array cell is already tracked ) return - //println("SNAPSHOT: array=${array.javaClass.simpleName}@${System.identityHashCode(array).toHexString()}, index=$index") + //println("SNAPSHOT: array=${array.javaClass.simpleName}@${System.identityHashCode(array).toHexString()}, index=$index, value=${elementValue}") val childNode = createMemoryNode(array, ArrayCellDescriptor(index), elementValue) nodesList.add(childNode) @@ -174,10 +178,10 @@ class SnapshotTracker { private fun shouldTrackEnergetically(obj: Any?): Boolean { // TODO: We should filter out some standard library classes that we don't care about (like, PrintStream: System.out/in/err) - // and only traverse energetically user classes and important std classes like AtomicInteger, etc. + // and only traverse energetically important std classes like AtomicInteger, etc. return ( obj != null && - (!shouldTransformClass(obj.javaClass.name) && !obj.javaClass.name.startsWith("org.jetbrains.kotlinx.lincheck")) + (obj.javaClass.name.startsWith("java.util.concurrent") && obj.javaClass.name.contains("Atomic")) ) } @@ -192,7 +196,18 @@ class SnapshotTracker { if (node is ArrayCellNode) { val index = (node.descriptor as ArrayCellDescriptor).index //println("\tRESTORE: arr=${obj.javaClass.simpleName}@${System.identityHashCode(obj).toHexString()}, index=$index, value=${node.initialValue}") - writeArrayElementViaUnsafe(obj, index, node.initialValue) + + when (obj) { + // TODO: add tests for java atomic and atomicfu arrays + // TODO: add write support for atomicfu arrays +// is kotlinx.atomicfu.AtomicIntArray -> { +// obj.get(index).value = node.initialValue as Int +// } + is AtomicReferenceArray<*> -> @Suppress("UNCHECKED_CAST") (obj as AtomicReferenceArray).set(index, node.initialValue) + is AtomicIntegerArray -> obj.set(index, node.initialValue as Int) + is AtomicLongArray -> obj.set(index, node.initialValue as Long) + else -> writeArrayElementViaUnsafe(obj, index, node.initialValue) + } } else if (!Modifier.isFinal((node.descriptor as FieldDescriptor).field.modifiers)) { //println("\tRESTORE: obj=${if (obj is Class<*>) obj.simpleName else obj.javaClass.simpleName}@${System.identityHashCode(obj).toHexString()}, field=${node.descriptor.field.name}, value=${node.initialValue}") diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/AbstractLincheckTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/AbstractLincheckTest.kt index 074a68356..22d413037 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/AbstractLincheckTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/AbstractLincheckTest.kt @@ -13,7 +13,6 @@ import org.jetbrains.kotlinx.lincheck.* import org.jetbrains.kotlinx.lincheck.strategy.* import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingOptions import org.jetbrains.kotlinx.lincheck.strategy.stress.* -import org.jetbrains.kotlinx.lincheck.verifier.* import org.jetbrains.kotlinx.lincheck_test.util.* import org.junit.* import kotlin.reflect.* @@ -21,9 +20,7 @@ import kotlin.reflect.* abstract class AbstractLincheckTest( private vararg val expectedFailures: KClass ) { - open fun > O.customize() { - if (this is ModelCheckingOptions) restoreStaticMemory(false) - } + open fun > O.customize() {} private fun > O.runInternalTest() { val failure: LincheckFailure? = checkImpl(this@AbstractLincheckTest::class.java) diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/InnerClassTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/InnerClassTest.kt index 72508e20c..b8119b8af 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/InnerClassTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/InnerClassTest.kt @@ -1,11 +1,9 @@ package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot -import org.jetbrains.kotlinx.lincheck.ExceptionResult import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.annotations.Operation import org.jetbrains.kotlinx.lincheck.execution.* import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingOptions -import org.jetbrains.kotlinx.lincheck.verifier.Verifier private class Outer { @@ -29,16 +27,9 @@ private class Outer { private val a = Outer() class InnerClassTest : SnapshotAbstractTest() { - class InnerClassVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : Verifier { + class InnerClassVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : SnapshotVerifier() { override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { - results?.parallelResults?.forEach { threadsResults -> - threadsResults.forEach { result -> - if (result is ExceptionResult) { - throw result.throwable - } - } - } - + checkForExceptions(results) check(a.c.value == 1) return true } diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/SnapshotAbstractTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/SnapshotAbstractTest.kt index d99f3394f..04ab3b5e0 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/SnapshotAbstractTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/SnapshotAbstractTest.kt @@ -10,14 +10,30 @@ package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot +import org.jetbrains.kotlinx.lincheck.ExceptionResult import org.jetbrains.kotlinx.lincheck.LoggingLevel import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.check +import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult +import org.jetbrains.kotlinx.lincheck.execution.parallelResults import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingOptions +import org.jetbrains.kotlinx.lincheck.verifier.Verifier import org.junit.Test abstract class SnapshotAbstractTest { - open fun > O.customize() {} + abstract class SnapshotVerifier() : Verifier { + protected fun checkForExceptions(results: ExecutionResult?) { + results?.parallelResults?.forEach { threadsResults -> + threadsResults.forEach { result -> + if (result is ExceptionResult) { + throw result.throwable + } + } + } + } + } + + protected open fun > O.customize() {} @Test fun testModelChecking() = ModelCheckingOptions() diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticArrayTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticArrayTest.kt index 9c2e0c51d..231b389a8 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticArrayTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticArrayTest.kt @@ -14,7 +14,6 @@ import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.annotations.Operation import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario -import org.jetbrains.kotlinx.lincheck.verifier.Verifier import kotlin.random.Random @@ -25,8 +24,10 @@ class StaticIntArrayTest : SnapshotAbstractTest() { private var ref = intArray private var values = intArray.copyOf() } - class StaticIntArrayVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : Verifier { + + class StaticIntArrayVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : SnapshotVerifier() { override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { + checkForExceptions(results) check(intArray == ref) check(ref.contentEquals(values)) return true @@ -58,8 +59,9 @@ class StaticObjectArrayTest : SnapshotAbstractTest() { private var values: Array = Array(3) { 0 }.also { objArray.forEachIndexed { index, x -> it[index] = x.value } } } - class StaticObjectArrayVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : Verifier { + class StaticObjectArrayVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : SnapshotVerifier() { override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { + checkForExceptions(results) check(objArray == ref) check(objArray.contentEquals(elements)) check(objArray.map { it.value }.toTypedArray().contentEquals(values)) diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticCollectionTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticCollectionTest.kt index bfcc67652..673247aba 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticCollectionTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticCollectionTest.kt @@ -10,19 +10,15 @@ package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot -import org.jetbrains.kotlinx.lincheck.ExceptionResult import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.annotations.Operation import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario -import org.jetbrains.kotlinx.lincheck.execution.parallelResults import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingOptions -import org.jetbrains.kotlinx.lincheck.verifier.Verifier import kotlin.random.Random private class Wrapper(var value: Int) private var staticSet = mutableSetOf() -private var staticMap = mutableMapOf() class StaticCollectionTest : SnapshotAbstractTest() { companion object { @@ -32,26 +28,15 @@ class StaticCollectionTest : SnapshotAbstractTest() { private val c = Wrapper(3) init { staticSet.addAll(listOf(a, b, c)) - staticMap.put(1, b) } } - class StaticCollectionVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : Verifier { + class StaticCollectionVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : SnapshotVerifier() { override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { - results?.parallelResults?.forEach { threadsResults -> - threadsResults.forEach { result -> - if (result is ExceptionResult) { - throw result.throwable - } - } - } - - check(staticMap.size == 1 && staticMap.entries.containsAll(mapOf(1 to b).entries)) - + checkForExceptions(results) check(staticSet == ref) check(staticSet.size == 3 && staticSet.containsAll(listOf(a, b, c))) check(a.value == 1 && b.value == 2 && c.value == 3) - return true } } diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticEnumTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticEnumTest.kt index 359911207..70431eb57 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticEnumTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticEnumTest.kt @@ -14,7 +14,6 @@ import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.annotations.Operation import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario -import org.jetbrains.kotlinx.lincheck.verifier.Verifier private enum class Values { @@ -31,8 +30,9 @@ class StaticEnumTest : SnapshotAbstractTest() { private var initY: Values = global.y } - class StaticEnumVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : Verifier { + class StaticEnumVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : SnapshotVerifier() { override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { + checkForExceptions(results) check(global == initA) check(global.x == initX) check(global.y == initY) diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectAsFieldTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectAsFieldTest.kt index f39af0ab3..f2ecc8be6 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectAsFieldTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectAsFieldTest.kt @@ -14,7 +14,6 @@ import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.annotations.Operation import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario -import org.jetbrains.kotlinx.lincheck.verifier.Verifier private object Static1 { @@ -36,8 +35,9 @@ class StaticObjectAsFieldTest : SnapshotAbstractTest() { private val initS2f2 = Static2.f2 } - class StaticObjectAsFieldVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : Verifier { + class StaticObjectAsFieldVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : SnapshotVerifier() { override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { + checkForExceptions(results) check(Static1.f1 == initS1f1 && Static1.f2 == initS1f2) check(Static2.f1 == initS2f1 && Static2.f2 == initS2f2) return true diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectCycleTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectCycleTest.kt index 0e014186e..774956af4 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectCycleTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectCycleTest.kt @@ -14,7 +14,6 @@ import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.annotations.Operation import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario -import org.jetbrains.kotlinx.lincheck.verifier.Verifier private class A(var b: B) private class B(var a: A? = null) @@ -31,8 +30,9 @@ class StaticObjectCycleTest : SnapshotAbstractTest() { } } - class StaticObjectCycleVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : Verifier { + class StaticObjectCycleVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : SnapshotVerifier() { override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { + checkForExceptions(results) check(globalA == initA) check(globalA.b == initB) check(globalA.b.a == globalA) diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectTest.kt index 26f78fdc9..c7ae360aa 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectTest.kt @@ -14,7 +14,6 @@ import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.annotations.Operation import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario -import org.jetbrains.kotlinx.lincheck.verifier.Verifier import java.util.concurrent.atomic.AtomicInteger @@ -26,8 +25,9 @@ class StaticObjectTest : SnapshotAbstractTest() { private var value: Int = staticInt.get() } - class StaticObjectVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : Verifier { + class StaticObjectVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : SnapshotVerifier() { override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { + checkForExceptions(results) check(staticInt == ref) check(staticInt.get() == value) return true diff --git a/src/jvm/test/resources/expected_logs/atomic_references_names_trace.txt b/src/jvm/test/resources/expected_logs/atomic_references_names_trace.txt index aced75030..91f5e5e8e 100644 --- a/src/jvm/test/resources/expected_logs/atomic_references_names_trace.txt +++ b/src/jvm/test/resources/expected_logs/atomic_references_names_trace.txt @@ -13,38 +13,38 @@ The following interleaving leads to the error: | ----------- | Detailed trace: -| --------------------------------------------------------------------------------------------------------------------------------------------- | -| Thread 1 | -| --------------------------------------------------------------------------------------------------------------------------------------------- | -| operation() | -| atomicReference.get(): Node#1 at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:29) | -| atomicReference.compareAndSet(Node#1,Node#2): true at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:29) | -| atomicReference.set(Node#3) at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:30) | -| atomicInteger.get(): 0 at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:32) | -| atomicInteger.compareAndSet(0,2): true at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:32) | -| atomicInteger.set(3) at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:33) | -| atomicLong.get(): 0 at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:35) | -| atomicLong.compareAndSet(0,2): true at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:35) | -| atomicLong.set(3) at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:36) | -| atomicBoolean.get(): true at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:38) | -| atomicBoolean.compareAndSet(true,true): true at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:38) | -| atomicBoolean.set(false) at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:39) | -| atomicReferenceArray[0].get(): Node#4 at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:41) | -| atomicReferenceArray[0].compareAndSet(Node#4,Node#5): true at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:41) | -| atomicReferenceArray[0].set(Node#6) at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:42) | -| atomicIntegerArray[0].get(): 0 at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:44) | -| atomicIntegerArray[0].compareAndSet(0,1): true at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:44) | -| atomicIntegerArray[0].set(2) at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:45) | -| atomicLongArray[0].get(): 0 at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:47) | -| atomicLongArray[0].compareAndSet(0,1): true at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:47) | -| atomicLongArray[0].set(2) at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:48) | -| wrapper.reference.set(Node#7) at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:50) | -| wrapper.array[0].compareAndSet(1,2): false at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:51) | -| AtomicReferencesNamesTest.staticValue.compareAndSet(0,2): true at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:53) | -| AtomicReferencesNamesTest.staticValue.set(0) at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:54) | -| AtomicReferenceWrapper.staticValue.compareAndSet(1,2): false at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:56) | -| AtomicReferenceWrapper.staticValue.set(3) at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:57) | -| AtomicReferencesNamesTest.staticArray[1].compareAndSet(0,1): false at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:59) | -| AtomicReferenceWrapper.staticArray[1].compareAndSet(0,1): false at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:60) | -| result: void | -| --------------------------------------------------------------------------------------------------------------------------------------------- | +| -------------------------------------------------------------------------------------------------------------------------------------------- | +| Thread 1 | +| -------------------------------------------------------------------------------------------------------------------------------------------- | +| operation() | +| atomicReference.get(): Node#1 at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:29) | +| atomicReference.compareAndSet(Node#1,Node#2): true at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:29) | +| atomicReference.set(Node#3) at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:30) | +| atomicInteger.get(): 0 at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:32) | +| atomicInteger.compareAndSet(0,2): true at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:32) | +| atomicInteger.set(3) at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:33) | +| atomicLong.get(): 0 at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:35) | +| atomicLong.compareAndSet(0,2): true at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:35) | +| atomicLong.set(3) at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:36) | +| atomicBoolean.get(): true at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:38) | +| atomicBoolean.compareAndSet(true,true): true at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:38) | +| atomicBoolean.set(false) at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:39) | +| atomicReferenceArray[0].get(): Node#4 at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:41) | +| atomicReferenceArray[0].compareAndSet(Node#4,Node#5): true at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:41) | +| atomicReferenceArray[0].set(Node#6) at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:42) | +| atomicIntegerArray[0].get(): 0 at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:44) | +| atomicIntegerArray[0].compareAndSet(0,1): true at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:44) | +| atomicIntegerArray[0].set(2) at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:45) | +| atomicLongArray[0].get(): 0 at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:47) | +| atomicLongArray[0].compareAndSet(0,1): true at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:47) | +| atomicLongArray[0].set(2) at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:48) | +| wrapper.reference.set(Node#7) at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:50) | +| wrapper.array[0].compareAndSet(1,2): false at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:51) | +| AtomicReferencesNamesTest.staticValue.compareAndSet(0,2): true at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:53) | +| AtomicReferencesNamesTest.staticValue.set(0) at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:54) | +| AtomicReferenceWrapper.staticValue.compareAndSet(1,2): true at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:56) | +| AtomicReferenceWrapper.staticValue.set(3) at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:57) | +| AtomicReferencesNamesTest.staticArray[1].compareAndSet(0,1): true at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:59) | +| AtomicReferenceWrapper.staticArray[1].compareAndSet(0,1): true at AtomicReferencesNamesTest.operation(AtomicReferencesNamesTests.kt:60) | +| result: void | +| -------------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/src/jvm/test/resources/expected_logs/var_handle/varhandle_boolean_representation.txt b/src/jvm/test/resources/expected_logs/var_handle/varhandle_boolean_representation.txt index 7258d7d6c..70b79d682 100644 --- a/src/jvm/test/resources/expected_logs/var_handle/varhandle_boolean_representation.txt +++ b/src/jvm/test/resources/expected_logs/var_handle/varhandle_boolean_representation.txt @@ -13,19 +13,19 @@ The following interleaving leads to the error: | ----------- | Detailed trace: -| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Thread 1 | -| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| operation() | -| number.READ: false at VarHandleBooleanRepresentationTest.operation(VarHandleRepresentationTests.kt:216) | -| number.compareAndSet(false,false): true at VarHandleBooleanRepresentationTest.operation(VarHandleRepresentationTests.kt:216) | -| number.set(true) at VarHandleBooleanRepresentationTest.operation(VarHandleRepresentationTests.kt:217) | -| VarHandleBooleanRepresentationTest.staticNumber.READ: false at VarHandleBooleanRepresentationTest.operation(VarHandleRepresentationTests.kt:219) | -| VarHandleBooleanRepresentationTest.staticNumber.compareAndSet(false,false): true at VarHandleBooleanRepresentationTest.operation(VarHandleRepresentationTests.kt:219) | -| VarHandleBooleanRepresentationTest.staticNumber.set(false) at VarHandleBooleanRepresentationTest.operation(VarHandleRepresentationTests.kt:220) | -| array.READ: BooleanArray#1 at VarHandleBooleanRepresentationTest.operation(VarHandleRepresentationTests.kt:222) | -| BooleanArray#1[1].compareAndSet(false,false): true at VarHandleBooleanRepresentationTest.operation(VarHandleRepresentationTests.kt:222) | -| array.READ: BooleanArray#1 at VarHandleBooleanRepresentationTest.operation(VarHandleRepresentationTests.kt:223) | -| BooleanArray#1[1].set(true) at VarHandleBooleanRepresentationTest.operation(VarHandleRepresentationTests.kt:223) | -| result: void | -| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Thread 1 | +| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| operation() | +| number.READ: false at VarHandleBooleanRepresentationTest.operation(VarHandleRepresentationTests.kt:216) | +| number.compareAndSet(false,false): true at VarHandleBooleanRepresentationTest.operation(VarHandleRepresentationTests.kt:216) | +| number.set(true) at VarHandleBooleanRepresentationTest.operation(VarHandleRepresentationTests.kt:217) | +| VarHandleBooleanRepresentationTest.staticNumber.READ: true at VarHandleBooleanRepresentationTest.operation(VarHandleRepresentationTests.kt:219) | +| VarHandleBooleanRepresentationTest.staticNumber.compareAndSet(true,false): true at VarHandleBooleanRepresentationTest.operation(VarHandleRepresentationTests.kt:219) | +| VarHandleBooleanRepresentationTest.staticNumber.set(false) at VarHandleBooleanRepresentationTest.operation(VarHandleRepresentationTests.kt:220) | +| array.READ: BooleanArray#1 at VarHandleBooleanRepresentationTest.operation(VarHandleRepresentationTests.kt:222) | +| BooleanArray#1[1].compareAndSet(false,false): true at VarHandleBooleanRepresentationTest.operation(VarHandleRepresentationTests.kt:222) | +| array.READ: BooleanArray#1 at VarHandleBooleanRepresentationTest.operation(VarHandleRepresentationTests.kt:223) | +| BooleanArray#1[1].set(true) at VarHandleBooleanRepresentationTest.operation(VarHandleRepresentationTests.kt:223) | +| result: void | +| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/src/jvm/test/resources/expected_logs/var_handle/varhandle_byte_representation.txt b/src/jvm/test/resources/expected_logs/var_handle/varhandle_byte_representation.txt index 032774f9a..0a573ceac 100644 --- a/src/jvm/test/resources/expected_logs/var_handle/varhandle_byte_representation.txt +++ b/src/jvm/test/resources/expected_logs/var_handle/varhandle_byte_representation.txt @@ -20,8 +20,8 @@ Detailed trace: | number.READ: 1 at VarHandleByteRepresentationTest.operation(VarHandleRepresentationTests.kt:142) | | number.compareAndSet(1,1): true at VarHandleByteRepresentationTest.operation(VarHandleRepresentationTests.kt:142) | | number.set(2) at VarHandleByteRepresentationTest.operation(VarHandleRepresentationTests.kt:143) | -| VarHandleByteRepresentationTest.staticNumber.READ: 3 at VarHandleByteRepresentationTest.operation(VarHandleRepresentationTests.kt:145) | -| VarHandleByteRepresentationTest.staticNumber.compareAndSet(3,1): true at VarHandleByteRepresentationTest.operation(VarHandleRepresentationTests.kt:145) | +| VarHandleByteRepresentationTest.staticNumber.READ: 2 at VarHandleByteRepresentationTest.operation(VarHandleRepresentationTests.kt:145) | +| VarHandleByteRepresentationTest.staticNumber.compareAndSet(2,1): true at VarHandleByteRepresentationTest.operation(VarHandleRepresentationTests.kt:145) | | VarHandleByteRepresentationTest.staticNumber.set(3) at VarHandleByteRepresentationTest.operation(VarHandleRepresentationTests.kt:146) | | array.READ: ByteArray#1 at VarHandleByteRepresentationTest.operation(VarHandleRepresentationTests.kt:148) | | ByteArray#1[1].compareAndSet(3,1): false at VarHandleByteRepresentationTest.operation(VarHandleRepresentationTests.kt:148) | diff --git a/src/jvm/test/resources/expected_logs/var_handle/varhandle_char_representation.txt b/src/jvm/test/resources/expected_logs/var_handle/varhandle_char_representation.txt index 78673fe2c..625ae7d04 100644 --- a/src/jvm/test/resources/expected_logs/var_handle/varhandle_char_representation.txt +++ b/src/jvm/test/resources/expected_logs/var_handle/varhandle_char_representation.txt @@ -20,8 +20,8 @@ Detailed trace: | number.READ: '1' at VarHandleCharRepresentationTest.operation(VarHandleRepresentationTests.kt:179) | | number.compareAndSet('1','1'): true at VarHandleCharRepresentationTest.operation(VarHandleRepresentationTests.kt:179) | | number.set('2') at VarHandleCharRepresentationTest.operation(VarHandleRepresentationTests.kt:180) | -| VarHandleCharRepresentationTest.staticNumber.READ: '3' at VarHandleCharRepresentationTest.operation(VarHandleRepresentationTests.kt:182) | -| VarHandleCharRepresentationTest.staticNumber.compareAndSet('3','1'): true at VarHandleCharRepresentationTest.operation(VarHandleRepresentationTests.kt:182) | +| VarHandleCharRepresentationTest.staticNumber.READ: '2' at VarHandleCharRepresentationTest.operation(VarHandleRepresentationTests.kt:182) | +| VarHandleCharRepresentationTest.staticNumber.compareAndSet('2','1'): true at VarHandleCharRepresentationTest.operation(VarHandleRepresentationTests.kt:182) | | VarHandleCharRepresentationTest.staticNumber.set('3') at VarHandleCharRepresentationTest.operation(VarHandleRepresentationTests.kt:183) | | array.READ: CharArray#1 at VarHandleCharRepresentationTest.operation(VarHandleRepresentationTests.kt:185) | | CharArray#1[1].compareAndSet('3','1'): false at VarHandleCharRepresentationTest.operation(VarHandleRepresentationTests.kt:185) | diff --git a/src/jvm/test/resources/expected_logs/var_handle/varhandle_double_representation.txt b/src/jvm/test/resources/expected_logs/var_handle/varhandle_double_representation.txt index 59ba6afda..c21798dad 100644 --- a/src/jvm/test/resources/expected_logs/var_handle/varhandle_double_representation.txt +++ b/src/jvm/test/resources/expected_logs/var_handle/varhandle_double_representation.txt @@ -20,8 +20,8 @@ Detailed trace: | number.READ: 1.0 at VarHandleDoubleRepresentationTest.operation(VarHandleRepresentationTests.kt:327) | | number.compareAndSet(1.0,1.0): true at VarHandleDoubleRepresentationTest.operation(VarHandleRepresentationTests.kt:327) | | number.set(2.0) at VarHandleDoubleRepresentationTest.operation(VarHandleRepresentationTests.kt:328) | -| VarHandleDoubleRepresentationTest.staticNumber.READ: 3.0 at VarHandleDoubleRepresentationTest.operation(VarHandleRepresentationTests.kt:330) | -| VarHandleDoubleRepresentationTest.staticNumber.compareAndSet(3.0,1.0): true at VarHandleDoubleRepresentationTest.operation(VarHandleRepresentationTests.kt:330) | +| VarHandleDoubleRepresentationTest.staticNumber.READ: 2.0 at VarHandleDoubleRepresentationTest.operation(VarHandleRepresentationTests.kt:330) | +| VarHandleDoubleRepresentationTest.staticNumber.compareAndSet(2.0,1.0): true at VarHandleDoubleRepresentationTest.operation(VarHandleRepresentationTests.kt:330) | | VarHandleDoubleRepresentationTest.staticNumber.set(3.0) at VarHandleDoubleRepresentationTest.operation(VarHandleRepresentationTests.kt:331) | | array.READ: DoubleArray#1 at VarHandleDoubleRepresentationTest.operation(VarHandleRepresentationTests.kt:333) | | DoubleArray#1[1].compareAndSet(3.0,1.0): false at VarHandleDoubleRepresentationTest.operation(VarHandleRepresentationTests.kt:333) | diff --git a/src/jvm/test/resources/expected_logs/var_handle/varhandle_float_representation.txt b/src/jvm/test/resources/expected_logs/var_handle/varhandle_float_representation.txt index d9d34d5ea..c4863d63f 100644 --- a/src/jvm/test/resources/expected_logs/var_handle/varhandle_float_representation.txt +++ b/src/jvm/test/resources/expected_logs/var_handle/varhandle_float_representation.txt @@ -20,8 +20,8 @@ Detailed trace: | number.READ: 1.0 at VarHandleFloatRepresentationTest.operation(VarHandleRepresentationTests.kt:290) | | number.compareAndSet(1.0,1.0): true at VarHandleFloatRepresentationTest.operation(VarHandleRepresentationTests.kt:290) | | number.set(2.0) at VarHandleFloatRepresentationTest.operation(VarHandleRepresentationTests.kt:291) | -| VarHandleFloatRepresentationTest.staticNumber.READ: 3.0 at VarHandleFloatRepresentationTest.operation(VarHandleRepresentationTests.kt:293) | -| VarHandleFloatRepresentationTest.staticNumber.compareAndSet(3.0,1.0): true at VarHandleFloatRepresentationTest.operation(VarHandleRepresentationTests.kt:293) | +| VarHandleFloatRepresentationTest.staticNumber.READ: 2.0 at VarHandleFloatRepresentationTest.operation(VarHandleRepresentationTests.kt:293) | +| VarHandleFloatRepresentationTest.staticNumber.compareAndSet(2.0,1.0): true at VarHandleFloatRepresentationTest.operation(VarHandleRepresentationTests.kt:293) | | VarHandleFloatRepresentationTest.staticNumber.set(3.0) at VarHandleFloatRepresentationTest.operation(VarHandleRepresentationTests.kt:294) | | array.READ: FloatArray#1 at VarHandleFloatRepresentationTest.operation(VarHandleRepresentationTests.kt:296) | | FloatArray#1[1].compareAndSet(3.0,1.0): false at VarHandleFloatRepresentationTest.operation(VarHandleRepresentationTests.kt:296) | diff --git a/src/jvm/test/resources/expected_logs/var_handle/varhandle_int_representation.txt b/src/jvm/test/resources/expected_logs/var_handle/varhandle_int_representation.txt index 533825f6d..5eb62b287 100644 --- a/src/jvm/test/resources/expected_logs/var_handle/varhandle_int_representation.txt +++ b/src/jvm/test/resources/expected_logs/var_handle/varhandle_int_representation.txt @@ -20,8 +20,8 @@ Detailed trace: | number.READ: 1 at VarHandleIntRepresentationTest.operation(VarHandleRepresentationTests.kt:70) | | number.compareAndSet(1,2): true at VarHandleIntRepresentationTest.operation(VarHandleRepresentationTests.kt:70) | | number.set(3) at VarHandleIntRepresentationTest.operation(VarHandleRepresentationTests.kt:71) | -| VarHandleIntRepresentationTest.staticNumber.READ: 3 at VarHandleIntRepresentationTest.operation(VarHandleRepresentationTests.kt:73) | -| VarHandleIntRepresentationTest.staticNumber.compareAndSet(3,2): true at VarHandleIntRepresentationTest.operation(VarHandleRepresentationTests.kt:73) | +| VarHandleIntRepresentationTest.staticNumber.READ: 1 at VarHandleIntRepresentationTest.operation(VarHandleRepresentationTests.kt:73) | +| VarHandleIntRepresentationTest.staticNumber.compareAndSet(1,2): true at VarHandleIntRepresentationTest.operation(VarHandleRepresentationTests.kt:73) | | VarHandleIntRepresentationTest.staticNumber.set(3) at VarHandleIntRepresentationTest.operation(VarHandleRepresentationTests.kt:74) | | array.READ: IntArray#1 at VarHandleIntRepresentationTest.operation(VarHandleRepresentationTests.kt:76) | | IntArray#1[1].compareAndSet(1,1): true at VarHandleIntRepresentationTest.operation(VarHandleRepresentationTests.kt:76) | diff --git a/src/jvm/test/resources/expected_logs/var_handle/varhandle_long_representation.txt b/src/jvm/test/resources/expected_logs/var_handle/varhandle_long_representation.txt index 6562e4810..43a04a483 100644 --- a/src/jvm/test/resources/expected_logs/var_handle/varhandle_long_representation.txt +++ b/src/jvm/test/resources/expected_logs/var_handle/varhandle_long_representation.txt @@ -20,8 +20,8 @@ Detailed trace: | number.READ: 1 at VarHandleLongRepresentationTest.operation(VarHandleRepresentationTests.kt:253) | | number.compareAndSet(1,1): true at VarHandleLongRepresentationTest.operation(VarHandleRepresentationTests.kt:253) | | number.set(2) at VarHandleLongRepresentationTest.operation(VarHandleRepresentationTests.kt:254) | -| VarHandleLongRepresentationTest.staticNumber.READ: 3 at VarHandleLongRepresentationTest.operation(VarHandleRepresentationTests.kt:256) | -| VarHandleLongRepresentationTest.staticNumber.compareAndSet(3,1): true at VarHandleLongRepresentationTest.operation(VarHandleRepresentationTests.kt:256) | +| VarHandleLongRepresentationTest.staticNumber.READ: 2 at VarHandleLongRepresentationTest.operation(VarHandleRepresentationTests.kt:256) | +| VarHandleLongRepresentationTest.staticNumber.compareAndSet(2,1): true at VarHandleLongRepresentationTest.operation(VarHandleRepresentationTests.kt:256) | | VarHandleLongRepresentationTest.staticNumber.set(3) at VarHandleLongRepresentationTest.operation(VarHandleRepresentationTests.kt:257) | | array.READ: LongArray#1 at VarHandleLongRepresentationTest.operation(VarHandleRepresentationTests.kt:259) | | LongArray#1[1].compareAndSet(3,1): false at VarHandleLongRepresentationTest.operation(VarHandleRepresentationTests.kt:259) | diff --git a/src/jvm/test/resources/expected_logs/var_handle/varhandle_short_representation.txt b/src/jvm/test/resources/expected_logs/var_handle/varhandle_short_representation.txt index eb91b96d9..e68841103 100644 --- a/src/jvm/test/resources/expected_logs/var_handle/varhandle_short_representation.txt +++ b/src/jvm/test/resources/expected_logs/var_handle/varhandle_short_representation.txt @@ -20,8 +20,8 @@ Detailed trace: | number.READ: 1 at VarHandleShortRepresentationTest.operation(VarHandleRepresentationTests.kt:105) | | number.compareAndSet(1,1): true at VarHandleShortRepresentationTest.operation(VarHandleRepresentationTests.kt:105) | | number.set(2) at VarHandleShortRepresentationTest.operation(VarHandleRepresentationTests.kt:106) | -| VarHandleShortRepresentationTest.staticNumber.READ: 3 at VarHandleShortRepresentationTest.operation(VarHandleRepresentationTests.kt:108) | -| VarHandleShortRepresentationTest.staticNumber.compareAndSet(3,1): true at VarHandleShortRepresentationTest.operation(VarHandleRepresentationTests.kt:108) | +| VarHandleShortRepresentationTest.staticNumber.READ: 2 at VarHandleShortRepresentationTest.operation(VarHandleRepresentationTests.kt:108) | +| VarHandleShortRepresentationTest.staticNumber.compareAndSet(2,1): true at VarHandleShortRepresentationTest.operation(VarHandleRepresentationTests.kt:108) | | VarHandleShortRepresentationTest.staticNumber.set(3) at VarHandleShortRepresentationTest.operation(VarHandleRepresentationTests.kt:109) | | array.READ: ShortArray#1 at VarHandleShortRepresentationTest.operation(VarHandleRepresentationTests.kt:111) | | ShortArray#1[1].compareAndSet(3,1): false at VarHandleShortRepresentationTest.operation(VarHandleRepresentationTests.kt:111) | From b733f255a9a89a9083ca15f8f99a0e4354c773f9 Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Fri, 8 Nov 2024 20:49:22 +0100 Subject: [PATCH 11/29] Add minor refactoring --- .../{SnapshotAbstractTest.kt => AbstractSnapshotTest.kt} | 3 +-- .../{InnerClassTest.kt => InnerClassConstructorTest.kt} | 2 +- .../strategy/modelchecking/snapshot/StaticArrayTest.kt | 4 ++-- ...taticCollectionTest.kt => StaticCollectionSnapshotTest.kt} | 2 +- .../snapshot/{StaticEnumTest.kt => StaticEnumSnapshotTest.kt} | 2 +- ...bjectAsFieldTest.kt => StaticObjectAsFieldSnapshotTest.kt} | 2 +- ...ticObjectCycleTest.kt => StaticObjectCycleSnapshotTest.kt} | 2 +- .../{StaticObjectTest.kt => StaticObjectSnapshotTest.kt} | 2 +- 8 files changed, 9 insertions(+), 10 deletions(-) rename src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/{SnapshotAbstractTest.kt => AbstractSnapshotTest.kt} (94%) rename src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/{InnerClassTest.kt => InnerClassConstructorTest.kt} (95%) rename src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/{StaticCollectionTest.kt => StaticCollectionSnapshotTest.kt} (97%) rename src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/{StaticEnumTest.kt => StaticEnumSnapshotTest.kt} (96%) rename src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/{StaticObjectAsFieldTest.kt => StaticObjectAsFieldSnapshotTest.kt} (96%) rename src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/{StaticObjectCycleTest.kt => StaticObjectCycleSnapshotTest.kt} (95%) rename src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/{StaticObjectTest.kt => StaticObjectSnapshotTest.kt} (95%) diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/SnapshotAbstractTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/AbstractSnapshotTest.kt similarity index 94% rename from src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/SnapshotAbstractTest.kt rename to src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/AbstractSnapshotTest.kt index 04ab3b5e0..135232ba9 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/SnapshotAbstractTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/AbstractSnapshotTest.kt @@ -11,7 +11,6 @@ package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot import org.jetbrains.kotlinx.lincheck.ExceptionResult -import org.jetbrains.kotlinx.lincheck.LoggingLevel import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.check import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult @@ -20,7 +19,7 @@ import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelChecki import org.jetbrains.kotlinx.lincheck.verifier.Verifier import org.junit.Test -abstract class SnapshotAbstractTest { +abstract class AbstractSnapshotTest { abstract class SnapshotVerifier() : Verifier { protected fun checkForExceptions(results: ExecutionResult?) { results?.parallelResults?.forEach { threadsResults -> diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/InnerClassTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/InnerClassConstructorTest.kt similarity index 95% rename from src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/InnerClassTest.kt rename to src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/InnerClassConstructorTest.kt index b8119b8af..6fe14249e 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/InnerClassTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/InnerClassConstructorTest.kt @@ -26,7 +26,7 @@ private class Outer { private val a = Outer() -class InnerClassTest : SnapshotAbstractTest() { +class InnerClassConstructorTest : AbstractSnapshotTest() { class InnerClassVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : SnapshotVerifier() { override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { checkForExceptions(results) diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticArrayTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticArrayTest.kt index 231b389a8..5cf64e43a 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticArrayTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticArrayTest.kt @@ -19,7 +19,7 @@ import kotlin.random.Random private var intArray = intArrayOf(1, 2, 3) -class StaticIntArrayTest : SnapshotAbstractTest() { +class StaticIntArraySnapshotTest : AbstractSnapshotTest() { companion object { private var ref = intArray private var values = intArray.copyOf() @@ -52,7 +52,7 @@ private class X(var value: Int) { private var objArray = arrayOf(X(1), X(2), X(3)) -class StaticObjectArrayTest : SnapshotAbstractTest() { +class StaticObjectArraySnapshotTest : AbstractSnapshotTest() { companion object { private var ref: Array = objArray private var elements: Array = Array(3) { null }.also { objArray.forEachIndexed { index, x -> it[index] = x } } diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticCollectionTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticCollectionSnapshotTest.kt similarity index 97% rename from src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticCollectionTest.kt rename to src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticCollectionSnapshotTest.kt index 673247aba..9f3dffdd5 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticCollectionTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticCollectionSnapshotTest.kt @@ -20,7 +20,7 @@ import kotlin.random.Random private class Wrapper(var value: Int) private var staticSet = mutableSetOf() -class StaticCollectionTest : SnapshotAbstractTest() { +class StaticCollectionSnapshotTest : AbstractSnapshotTest() { companion object { private val ref = staticSet private val a = Wrapper(1) diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticEnumTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticEnumSnapshotTest.kt similarity index 96% rename from src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticEnumTest.kt rename to src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticEnumSnapshotTest.kt index 70431eb57..2814f1371 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticEnumTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticEnumSnapshotTest.kt @@ -23,7 +23,7 @@ private class EnumHolder(var x: Values, var y: Values) private var global = EnumHolder(Values.A, Values.B) -class StaticEnumTest : SnapshotAbstractTest() { +class StaticEnumSnapshotTest : AbstractSnapshotTest() { companion object { private var initA: EnumHolder = global private var initX: Values = global.x diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectAsFieldTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectAsFieldSnapshotTest.kt similarity index 96% rename from src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectAsFieldTest.kt rename to src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectAsFieldSnapshotTest.kt index f2ecc8be6..9aa545289 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectAsFieldTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectAsFieldSnapshotTest.kt @@ -26,7 +26,7 @@ private object Static2 { var f2: String = "abc" } -class StaticObjectAsFieldTest : SnapshotAbstractTest() { +class StaticObjectAsFieldSnapshotTest : AbstractSnapshotTest() { companion object { private val initS1f1 = Static1.f1 private val initS1f2 = Static1.f2 diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectCycleTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectCycleSnapshotTest.kt similarity index 95% rename from src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectCycleTest.kt rename to src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectCycleSnapshotTest.kt index 774956af4..06e602a59 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectCycleTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectCycleSnapshotTest.kt @@ -20,7 +20,7 @@ private class B(var a: A? = null) private var globalA = A(B()) -class StaticObjectCycleTest : SnapshotAbstractTest() { +class StaticObjectCycleSnapshotTest : AbstractSnapshotTest() { companion object { private var initA = globalA private var initB = globalA.b diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectSnapshotTest.kt similarity index 95% rename from src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectTest.kt rename to src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectSnapshotTest.kt index c7ae360aa..f3156fcf8 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectSnapshotTest.kt @@ -19,7 +19,7 @@ import java.util.concurrent.atomic.AtomicInteger private var staticInt = AtomicInteger(1) -class StaticObjectTest : SnapshotAbstractTest() { +class StaticObjectSnapshotTest : AbstractSnapshotTest() { companion object { private var ref: AtomicInteger = staticInt private var value: Int = staticInt.get() From c187f061147a848fc7684518233daa937154abd6 Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Tue, 12 Nov 2024 17:41:57 +0100 Subject: [PATCH 12/29] Add tests for java atomics and atomicfu classes for snapshot restoring --- .../org/jetbrains/kotlinx/lincheck/Utils.kt | 52 -------- .../strategy/managed/ManagedStrategy.kt | 15 +++ .../strategy/managed/SnapshotTracker.kt | 26 ++-- .../transformation/LincheckClassVisitor.kt | 2 +- .../transformation/LincheckJavaAgent.kt | 50 +++++++- .../SnapshotTrackerTransformer.kt | 1 + .../snapshot/AbstractSnapshotTest.kt | 7 +- ...taticArrayTest.kt => ArraySnapshotTest.kt} | 6 +- .../snapshot/AtomicFUSnapshotTest.kt | 119 ++++++++++++++++++ ...pshotTest.kt => CollectionSnapshotTest.kt} | 64 +++++++--- ...numSnapshotTest.kt => EnumSnapshotTest.kt} | 6 +- .../snapshot/InnerClassConstructorTest.kt | 5 +- .../snapshot/JavaAtomicsSnapshotTest.kt | 100 +++++++++++++++ ...otTest.kt => ObjectAsFieldSnapshotTest.kt} | 6 +- ...shotTest.kt => ObjectCycleSnapshotTest.kt} | 6 +- .../snapshot/StaticObjectSnapshotTest.kt | 45 ------- 16 files changed, 365 insertions(+), 145 deletions(-) rename src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/{StaticArrayTest.kt => ArraySnapshotTest.kt} (93%) create mode 100644 src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/AtomicFUSnapshotTest.kt rename src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/{StaticCollectionSnapshotTest.kt => CollectionSnapshotTest.kt} (51%) rename src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/{StaticEnumSnapshotTest.kt => EnumSnapshotTest.kt} (89%) create mode 100644 src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/JavaAtomicsSnapshotTest.kt rename src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/{StaticObjectAsFieldSnapshotTest.kt => ObjectAsFieldSnapshotTest.kt} (89%) rename src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/{StaticObjectCycleSnapshotTest.kt => ObjectCycleSnapshotTest.kt} (87%) delete mode 100644 src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectSnapshotTest.kt diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt index e732aabb8..883ae4ad1 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt @@ -36,58 +36,6 @@ import java.util.concurrent.atomic.AtomicReferenceArray import kotlin.coroutines.* import kotlin.coroutines.intrinsics.* -// TODO: put this back to agent class -fun shouldTransformClass(className: String): Boolean { - // We do not need to instrument most standard Java classes. - // It is fine to inject the Lincheck analysis only into the - // `java.util.*` ones, ignored the known atomic constructs. - if (className.startsWith("java.")) { - if (className.startsWith("java.util.concurrent.") && className.contains("Atomic")) return false - if (className.startsWith("java.util.")) return true - if (className.startsWith("com.sun.")) return false - return false - } - if (className.startsWith("sun.")) return false - if (className.startsWith("javax.")) return false - if (className.startsWith("jdk.")) return false - // We do not need to instrument most standard Kotlin classes. - // However, we need to inject the Lincheck analysis into the classes - // related to collections, iterators, random and coroutines. - if (className.startsWith("kotlin.")) { - if (className.startsWith("kotlin.collections.")) return true - if (className.startsWith("kotlin.jvm.internal.Array") && className.contains("Iterator")) return true - if (className.startsWith("kotlin.ranges.")) return true - if (className.startsWith("kotlin.random.")) return true - if (className.startsWith("kotlin.coroutines.jvm.internal.")) return false - if (className.startsWith("kotlin.coroutines.")) return true - return false - } - if (className.startsWith("kotlinx.atomicfu.")) return false - // We need to skip the classes related to the debugger support in Kotlin coroutines. - if (className.startsWith("kotlinx.coroutines.debug.")) return false - if (className == "kotlinx.coroutines.DebugKt") return false - // We should never transform the coverage-related classes. - if (className.startsWith("com.intellij.rt.coverage.")) return false - // We can also safely do not instrument some libraries for performance reasons. - if (className.startsWith("com.esotericsoftware.kryo.")) return false - if (className.startsWith("net.bytebuddy.")) return false - if (className.startsWith("net.rubygrapefruit.platform.")) return false - if (className.startsWith("io.mockk.")) return false - if (className.startsWith("it.unimi.dsi.fastutil.")) return false - if (className.startsWith("worker.org.gradle.")) return false - if (className.startsWith("org.objectweb.asm.")) return false - if (className.startsWith("org.gradle.")) return false - if (className.startsWith("org.slf4j.")) return false - if (className.startsWith("org.apache.commons.lang.")) return false - if (className.startsWith("org.junit.")) return false - if (className.startsWith("junit.framework.")) return false - // Finally, we should never instrument the Lincheck classes. - if (className.startsWith("org.jetbrains.kotlinx.lincheck.")) return false - if (className.startsWith("sun.nio.ch.lincheck.")) return false - // All the classes that were not filtered out are eligible for transformation. - return true -} - fun List.isSuffixOf(list: List): Boolean { if (size > list.size) return false for (i in indices) { diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt index 9a34be219..7d7ad7b6b 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt @@ -754,6 +754,8 @@ abstract class ManagedStrategy( */ override fun beforeReadField(obj: Any?, className: String, fieldName: String, codeLocation: Int, isStatic: Boolean, isFinal: Boolean) = runInIgnoredSection { +// println("FIELD R: obj=${prettyName(obj)}, className=$className, fieldName=$fieldName, loc=${CodeLocations.stackTrace(codeLocation).toString()}") +// updateSnapshotOnFieldAccess(obj, className.canonicalClassName, fieldName, codeLocation) // We need to ensure all the classes related to the reading object are instrumented. // The following call checks all the static fields. if (isStatic) { @@ -790,6 +792,8 @@ abstract class ManagedStrategy( /** Returns true if a switch point is created. */ override fun beforeReadArrayElement(array: Any, index: Int, codeLocation: Int): Boolean = runInIgnoredSection { +// println("FIELD Rarr: arr=${prettyName(array)}, index=$index, loc=${CodeLocations.stackTrace(codeLocation).toString()}") +// updateSnapshotOnArrayElementAccess(array, index, codeLocation) if (!objectTracker.shouldTrackObjectAccess(array)) { return@runInIgnoredSection false } @@ -823,8 +827,16 @@ abstract class ManagedStrategy( loopDetector.afterRead(value) } + @OptIn(ExperimentalStdlibApi::class) + private fun prettyName(obj: Any?): String { + if (obj == null) return "null" + return "${obj.javaClass.name}@${System.identityHashCode(obj).toHexString()}" + } + override fun beforeWriteField(obj: Any?, className: String, fieldName: String, value: Any?, codeLocation: Int, isStatic: Boolean, isFinal: Boolean): Boolean = runInIgnoredSection { +// println("FIELD W: obj=${prettyName(obj)}, className=$className, fieldName=$fieldName, value=${prettyName(value)}, loc=${CodeLocations.stackTrace(codeLocation).toString()}") +// updateSnapshotOnFieldAccess(obj, className.canonicalClassName, fieldName, codeLocation) objectTracker.registerObjectLink(fromObject = obj ?: StaticObject, toObject = value) if (!objectTracker.shouldTrackObjectAccess(obj ?: StaticObject)) { return@runInIgnoredSection false @@ -853,7 +865,10 @@ abstract class ManagedStrategy( return@runInIgnoredSection true } + @OptIn(ExperimentalStdlibApi::class) override fun beforeWriteArrayElement(array: Any, index: Int, value: Any?, codeLocation: Int): Boolean = runInIgnoredSection { +// println("FIELD Warr: arr=${prettyName(array)}, index=$index, value=${prettyName(value)}, loc=${CodeLocations.stackTrace(codeLocation).toString()}") +// updateSnapshotOnArrayElementAccess(array, index, codeLocation) objectTracker.registerObjectLink(fromObject = array, toObject = value) if (!objectTracker.shouldTrackObjectAccess(array)) { return@runInIgnoredSection false diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt index e6c6add26..ad37de6a4 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt @@ -17,6 +17,7 @@ import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.Descripto import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.MemoryNode.ArrayCellNode import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.MemoryNode.RegularFieldNode import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.MemoryNode.StaticFieldNode +import org.jetbrains.kotlinx.lincheck.util.isAtomicJava import org.jetbrains.kotlinx.lincheck.util.readArrayElementViaUnsafe import org.jetbrains.kotlinx.lincheck.util.readFieldViaUnsafe import org.jetbrains.kotlinx.lincheck.util.traverseObjectGraph @@ -71,6 +72,7 @@ class SnapshotTracker { if (readResult.isSuccess) { val fieldValue = readResult.getOrNull() + //println("Consider: value=${fieldValue}") trackSingleField(obj, clazz, field, fieldValue) { if (shouldTrackEnergetically(fieldValue)) { @@ -89,6 +91,7 @@ class SnapshotTracker { if (readResult.isSuccess) { val elementValue = readResult.getOrNull() + //println("Consider: element=${elementValue}") trackSingleArrayCell(array, index, elementValue) { if (shouldTrackEnergetically(elementValue)) { @@ -106,7 +109,6 @@ class SnapshotTracker { .forEach { restoreValues(it, visitedObjects) } } - @OptIn(ExperimentalStdlibApi::class) private fun trackSingleField( obj: Any?, @@ -167,11 +169,15 @@ class SnapshotTracker { obj, onArrayElement = { array, index, elementValue -> trackSingleArrayCell(array, index, elementValue) - return@traverseObjectGraph elementValue + elementValue }, onField = { owner, field, fieldValue -> - trackSingleField(owner, owner.javaClass, field, fieldValue) - return@traverseObjectGraph fieldValue + // optimization to track only `value` field of java atomic classes + if (isAtomicJava(owner) && field.name != "value") null + else { + trackSingleField(owner, owner.javaClass, field, fieldValue) + fieldValue + } } ) } @@ -179,9 +185,9 @@ class SnapshotTracker { private fun shouldTrackEnergetically(obj: Any?): Boolean { // TODO: We should filter out some standard library classes that we don't care about (like, PrintStream: System.out/in/err) // and only traverse energetically important std classes like AtomicInteger, etc. + if (obj == null) return false return ( - obj != null && - (obj.javaClass.name.startsWith("java.util.concurrent") && obj.javaClass.name.contains("Atomic")) + obj.javaClass.name.startsWith("java.util.concurrent.") && obj.javaClass.name.contains("Atomic") ) } @@ -198,11 +204,8 @@ class SnapshotTracker { //println("\tRESTORE: arr=${obj.javaClass.simpleName}@${System.identityHashCode(obj).toHexString()}, index=$index, value=${node.initialValue}") when (obj) { - // TODO: add tests for java atomic and atomicfu arrays - // TODO: add write support for atomicfu arrays -// is kotlinx.atomicfu.AtomicIntArray -> { -// obj.get(index).value = node.initialValue as Int -// } + // No need to add support for writing to atomicfu array elements, + // because atomicfu arrays are compiled to atomic java arrays is AtomicReferenceArray<*> -> @Suppress("UNCHECKED_CAST") (obj as AtomicReferenceArray).set(index, node.initialValue) is AtomicIntegerArray -> obj.set(index, node.initialValue as Int) is AtomicLongArray -> obj.set(index, node.initialValue as Long) @@ -224,7 +227,6 @@ class SnapshotTracker { } } - private fun isTrackableObject(value: Any?): Boolean { return ( value != null && diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckClassVisitor.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckClassVisitor.kt index 70a998273..0ce8808b9 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckClassVisitor.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckClassVisitor.kt @@ -152,7 +152,7 @@ internal class LincheckClassVisitor( // so that it can correctly analyze the byte-code and compute required type-information mv = run { val st = SnapshotTrackerTransformer(fileName, className, methodName, mv.newAdapter()) - val sv = SharedMemoryAccessTransformer(fileName, className, methodName, st.newAdapter()) + val sv = SharedMemoryAccessTransformer(fileName, className, methodName, st.newAdapter()) val aa = AnalyzerAdapter(className, access, methodName, desc, sv) st.analyzer = aa diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckJavaAgent.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckJavaAgent.kt index 4c81cc065..4d5e26e98 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckJavaAgent.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckJavaAgent.kt @@ -13,7 +13,6 @@ package org.jetbrains.kotlinx.lincheck.transformation import net.bytebuddy.agent.ByteBuddyAgent import org.jetbrains.kotlinx.lincheck.canonicalClassName import org.jetbrains.kotlinx.lincheck.runInIgnoredSection -import org.jetbrains.kotlinx.lincheck.shouldTransformClass import org.jetbrains.kotlinx.lincheck.transformation.InstrumentationMode.MODEL_CHECKING import org.jetbrains.kotlinx.lincheck.transformation.InstrumentationMode.STRESS import org.jetbrains.kotlinx.lincheck.transformation.LincheckClassFileTransformer.isEagerlyInstrumentedClass @@ -403,7 +402,54 @@ internal object LincheckClassFileTransformer : ClassFileTransformer { if (isEagerlyInstrumentedClass(className)) { return true } - return shouldTransformClass(className) + // We do not need to instrument most standard Java classes. + // It is fine to inject the Lincheck analysis only into the + // `java.util.*` ones, ignored the known atomic constructs. + if (className.startsWith("java.")) { + if (className.startsWith("java.util.concurrent.") && className.contains("Atomic")) return false + if (className.startsWith("java.util.")) return true + if (className.startsWith("com.sun.")) return false + return false + } + if (className.startsWith("sun.")) return false + if (className.startsWith("javax.")) return false + if (className.startsWith("jdk.")) return false + // We do not need to instrument most standard Kotlin classes. + // However, we need to inject the Lincheck analysis into the classes + // related to collections, iterators, random and coroutines. + if (className.startsWith("kotlin.")) { + if (className.startsWith("kotlin.collections.")) return true + if (className.startsWith("kotlin.jvm.internal.Array") && className.contains("Iterator")) return true + if (className.startsWith("kotlin.ranges.")) return true + if (className.startsWith("kotlin.random.")) return true + if (className.startsWith("kotlin.coroutines.jvm.internal.")) return false + if (className.startsWith("kotlin.coroutines.")) return true + return false + } + if (className.startsWith("kotlinx.atomicfu.")) return false + // We need to skip the classes related to the debugger support in Kotlin coroutines. + if (className.startsWith("kotlinx.coroutines.debug.")) return false + if (className == "kotlinx.coroutines.DebugKt") return false + // We should never transform the coverage-related classes. + if (className.startsWith("com.intellij.rt.coverage.")) return false + // We can also safely do not instrument some libraries for performance reasons. + if (className.startsWith("com.esotericsoftware.kryo.")) return false + if (className.startsWith("net.bytebuddy.")) return false + if (className.startsWith("net.rubygrapefruit.platform.")) return false + if (className.startsWith("io.mockk.")) return false + if (className.startsWith("it.unimi.dsi.fastutil.")) return false + if (className.startsWith("worker.org.gradle.")) return false + if (className.startsWith("org.objectweb.asm.")) return false + if (className.startsWith("org.gradle.")) return false + if (className.startsWith("org.slf4j.")) return false + if (className.startsWith("org.apache.commons.lang.")) return false + if (className.startsWith("org.junit.")) return false + if (className.startsWith("junit.framework.")) return false + // Finally, we should never instrument the Lincheck classes. + if (className.startsWith("org.jetbrains.kotlinx.lincheck.")) return false + if (className.startsWith("sun.nio.ch.lincheck.")) return false + // All the classes that were not filtered out are eligible for transformation. + return true } // We should always eagerly transform the following classes. diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SnapshotTrackerTransformer.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SnapshotTrackerTransformer.kt index ba6246532..d59f6cf4e 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SnapshotTrackerTransformer.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SnapshotTrackerTransformer.kt @@ -31,6 +31,7 @@ internal class SnapshotTrackerTransformer( // when initializing our own fields in constructor, we do not want to track that as snapshot modification (methodName == "" && className == owner) ) { + //println("Do not transform access to field '$fieldName' with owner '$owner' of type '$desc' while in class '$className' and method '$methodName' in file '$fileName'.'") visitFieldInsn(opcode, owner, fieldName, desc) return } diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/AbstractSnapshotTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/AbstractSnapshotTest.kt index 135232ba9..2bdf2297a 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/AbstractSnapshotTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/AbstractSnapshotTest.kt @@ -11,10 +11,10 @@ package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot import org.jetbrains.kotlinx.lincheck.ExceptionResult -import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.check import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult import org.jetbrains.kotlinx.lincheck.execution.parallelResults +import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedOptions import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingOptions import org.jetbrains.kotlinx.lincheck.verifier.Verifier import org.junit.Test @@ -32,11 +32,10 @@ abstract class AbstractSnapshotTest { } } - protected open fun > O.customize() {} + protected open fun > O.customize() {} @Test - fun testModelChecking() = ModelCheckingOptions() -// .logLevel(LoggingLevel.INFO) + open fun testModelChecking() = ModelCheckingOptions() .iterations(1) .actorsBefore(0) .actorsAfter(0) diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticArrayTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/ArraySnapshotTest.kt similarity index 93% rename from src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticArrayTest.kt rename to src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/ArraySnapshotTest.kt index 5cf64e43a..559811584 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticArrayTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/ArraySnapshotTest.kt @@ -10,10 +10,10 @@ package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot -import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.annotations.Operation import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario +import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedOptions import kotlin.random.Random @@ -34,7 +34,7 @@ class StaticIntArraySnapshotTest : AbstractSnapshotTest() { } } - override fun > O.customize() { + override fun > O.customize() { verifier(StaticIntArrayVerifier::class.java) } @@ -69,7 +69,7 @@ class StaticObjectArraySnapshotTest : AbstractSnapshotTest() { } } - override fun > O.customize() { + override fun > O.customize() { verifier(StaticObjectArrayVerifier::class.java) } diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/AtomicFUSnapshotTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/AtomicFUSnapshotTest.kt new file mode 100644 index 000000000..38f02e631 --- /dev/null +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/AtomicFUSnapshotTest.kt @@ -0,0 +1,119 @@ +/* + * Lincheck + * + * Copyright (C) 2019 - 2024 JetBrains s.r.o. + * + * This Source Code Form is subject to the terms of the + * Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed + * with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot + +import org.jetbrains.kotlinx.lincheck.annotations.Operation +import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult +import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario +import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedOptions +import kotlin.random.Random + +class AtomicFUSnapshotTest : AbstractSnapshotTest() { + companion object { + private class Wrapper(var x: Int) + + // TODO: atomicfu classes like AtomicInt, AtomicRef are compiled to pure field + java atomic field updater. + // Because of this, methods, such as `AtomicInt::getAndIncrement()`, will not be tracked as modification of `value` field. + // Tracking of atomicfu classes should be implemented energetically, the same way as for java atomics + // in order to handle modification that do not reference `value` field directly. + // If energetic traversal does not help/not possible to reach `value` field, then such indirect methods should be handled separately, + // the same way as reflexivity, var-handles, and unsafe by tracking the creation of java afu and retrieving the name of the associated field. + private val atomicFUInt = kotlinx.atomicfu.atomic(1) + private val atomicFURef = kotlinx.atomicfu.atomic(Wrapper(1)) + + private val atomicFUIntArray = kotlinx.atomicfu.AtomicIntArray(3) + private val atomicFURefArray = kotlinx.atomicfu.atomicArrayOfNulls(3) + + init { + for (i in 0..atomicFUIntArray.size - 1) { + atomicFUIntArray[i].value = i + 1 + } + + for (i in 0..atomicFURefArray.size - 1) { + atomicFURefArray[i].value = Wrapper(i + 1) + } + } + + // remember values to restore + private val atomicFURefValue = atomicFURef.value + private val atomicFUIntValues: List = mutableListOf().apply { + for (i in 0..atomicFUIntArray.size - 1) { + add(atomicFUIntArray[i].value) + } + } + private val atomicFURefArrayValues: List = mutableListOf().apply { + for (i in 0..atomicFURefArray.size - 1) { + add(atomicFURefArray[i].value!!) + } + } + } + + class AtomicFUSnapshotVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : SnapshotVerifier() { + override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { + checkForExceptions(results) + + check(atomicFUInt.value == 1) + + check(atomicFURef.value == atomicFURefValue) + check(atomicFURef.value.x == 1) + + for (i in 0..atomicFUIntArray.size - 1) { + check(atomicFUIntArray[i].value == atomicFUIntValues[i]) + } + + for (i in 0..atomicFURefArray.size - 1) { + check(atomicFURefArray[i].value == atomicFURefArrayValues[i]) + check(atomicFURefArray[i].value!!.x == i + 1) + } + + return true + } + } + + override fun > O.customize() { + verifier(AtomicFUSnapshotVerifier::class.java) + threads(1) + iterations(100) + invocationsPerIteration(1) + actorsPerThread(10) + } + + @Operation + fun modifyAtomicFUInt() { + // TODO: `atomicFUInt.incrementAndGet()` this is not recognized as `value` field modification right now + atomicFUInt.value = Random.nextInt() + } + + @Operation + fun modifyAtomicFURef() { + atomicFURef.value = Wrapper(Random.nextInt()) + } + + @Operation + fun modifyAtomicFURefValue() { + atomicFURef.value.x = Random.nextInt() + } + + @Operation + fun modifyAtomicFUIntArray() { + atomicFUIntArray[Random.nextInt(0, atomicFUIntArray.size)].value = Random.nextInt() + } + + @Operation + fun modifyAtomicFURefArray() { + atomicFURefArray[Random.nextInt(0, atomicFURefArray.size)].value = Wrapper(Random.nextInt()) + } + + @Operation + fun modifyAtomicFURefArrayValues() { + atomicFURefArray[Random.nextInt(0, atomicFURefArray.size)].value!!.x = Random.nextInt() + } +} \ No newline at end of file diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticCollectionSnapshotTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/CollectionSnapshotTest.kt similarity index 51% rename from src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticCollectionSnapshotTest.kt rename to src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/CollectionSnapshotTest.kt index 9f3dffdd5..fd4682f87 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticCollectionSnapshotTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/CollectionSnapshotTest.kt @@ -10,18 +10,51 @@ package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot -import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.annotations.Operation +import org.jetbrains.kotlinx.lincheck.check import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario +import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedOptions import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingOptions +import org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot.SetSnapshotTest.Companion.Wrapper import kotlin.random.Random -private class Wrapper(var value: Int) -private var staticSet = mutableSetOf() -class StaticCollectionSnapshotTest : AbstractSnapshotTest() { +abstract class CollectionSnapshotTest : AbstractSnapshotTest() { + + override fun testModelChecking() = ModelCheckingOptions() + .actorsBefore(0) + .actorsAfter(0) + .iterations(100) + .invocationsPerIteration(1) + .threads(1) + .actorsPerThread(10) + .restoreStaticMemory(true) + .apply { customize() } + .check(this::class) + + @Operation + open fun addElement() {} + + @Operation + open fun removeElement() {} + + @Operation + open fun updateElement() {} + + @Operation + open fun clear() {} + + @Operation + open fun reassign() {} +} + +class SetSnapshotTest : CollectionSnapshotTest() { companion object { + private class Wrapper(var value: Int) + private var staticSet = mutableSetOf() + + // remember values for restoring private val ref = staticSet private val a = Wrapper(1) private val b = Wrapper(2) @@ -31,7 +64,7 @@ class StaticCollectionSnapshotTest : AbstractSnapshotTest() { } } - class StaticCollectionVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : SnapshotVerifier() { + class SetVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : SnapshotVerifier() { override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { checkForExceptions(results) check(staticSet == ref) @@ -41,31 +74,32 @@ class StaticCollectionSnapshotTest : AbstractSnapshotTest() { } } - override fun > O.customize() { - iterations(100) - if (this is ModelCheckingOptions) invocationsPerIteration(1) - threads(1) - actorsPerThread(10) - verifier(StaticCollectionVerifier::class.java) + override fun > O.customize() { + verifier(SetVerifier::class.java) } @Operation - fun addElement() { + override fun addElement() { staticSet.add(Wrapper(Random.nextInt())) } @Operation - fun removeElement() { + override fun removeElement() { staticSet.remove(staticSet.randomOrNull() ?: return) } @Operation - fun updateElement() { + override fun updateElement() { staticSet.randomOrNull()?.value = Random.nextInt() } @Operation - fun clear() { + override fun clear() { staticSet.clear() } + + @Operation + override fun reassign() { + staticSet = mutableSetOf() + } } \ No newline at end of file diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticEnumSnapshotTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/EnumSnapshotTest.kt similarity index 89% rename from src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticEnumSnapshotTest.kt rename to src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/EnumSnapshotTest.kt index 2814f1371..6a2856325 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticEnumSnapshotTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/EnumSnapshotTest.kt @@ -10,10 +10,10 @@ package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot -import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.annotations.Operation import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario +import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedOptions private enum class Values { @@ -23,7 +23,7 @@ private class EnumHolder(var x: Values, var y: Values) private var global = EnumHolder(Values.A, Values.B) -class StaticEnumSnapshotTest : AbstractSnapshotTest() { +class EnumSnapshotTest : AbstractSnapshotTest() { companion object { private var initA: EnumHolder = global private var initX: Values = global.x @@ -40,7 +40,7 @@ class StaticEnumSnapshotTest : AbstractSnapshotTest() { } } - override fun > O.customize() { + override fun > O.customize() { verifier(StaticEnumVerifier::class.java) } diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/InnerClassConstructorTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/InnerClassConstructorTest.kt index 6fe14249e..5f866dd85 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/InnerClassConstructorTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/InnerClassConstructorTest.kt @@ -3,6 +3,7 @@ package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.annotations.Operation import org.jetbrains.kotlinx.lincheck.execution.* +import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedOptions import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingOptions @@ -35,9 +36,9 @@ class InnerClassConstructorTest : AbstractSnapshotTest() { } } - override fun > O.customize() { + override fun > O.customize() { iterations(1) - if (this is ModelCheckingOptions) invocationsPerIteration(1) + invocationsPerIteration(1) verifier(InnerClassVerifier::class.java) } diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/JavaAtomicsSnapshotTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/JavaAtomicsSnapshotTest.kt new file mode 100644 index 000000000..540ee68d3 --- /dev/null +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/JavaAtomicsSnapshotTest.kt @@ -0,0 +1,100 @@ +/* + * Lincheck + * + * Copyright (C) 2019 - 2024 JetBrains s.r.o. + * + * This Source Code Form is subject to the terms of the + * Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed + * with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot + +import org.jetbrains.kotlinx.lincheck.annotations.Operation +import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult +import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario +import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedOptions +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicIntegerArray +import java.util.concurrent.atomic.AtomicReference +import java.util.concurrent.atomic.AtomicReferenceArray +import kotlin.random.Random + + +class JavaAtomicsSnapshotTest : AbstractSnapshotTest() { + companion object { + private class Wrapper(var x: Int) + + private var atomicInt = AtomicInteger(1) + private var atomicRef = AtomicReference(Wrapper(1)) + private var atomicIntArray = AtomicIntegerArray(3).apply { for (i in 0..length() - 1) set(i, i + 1); } + private var atomicRefArray = AtomicReferenceArray(3).apply { for (i in 0..length() - 1) set(i, Wrapper(i + 1)); } + + // remember values to restore + private var intRef: AtomicInteger = atomicInt + private var intValue: Int = atomicInt.get() + private var refRef: AtomicReference = atomicRef + private var refValue: Wrapper = atomicRef.get() + private var intArrayRef: AtomicIntegerArray = atomicIntArray + private var refArrayRef: AtomicReferenceArray = atomicRefArray + private var refArrayValues: List = mutableListOf().apply { for (i in 0..atomicRefArray.length() - 1) add(atomicRefArray.get(i)) } + } + + class StaticObjectVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : SnapshotVerifier() { + override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { + checkForExceptions(results) + check(atomicInt == intRef) + check(atomicInt.get() == intValue) + + check(atomicRef == refRef) + check(atomicRef.get() == refValue) + check(atomicRef.get().x == 1) + + check(atomicIntArray == intArrayRef) + for (i in 0..atomicIntArray.length() - 1) { + check(atomicIntArray.get(i) == i + 1) + } + + check(atomicRefArray == refArrayRef) + for (i in 0..atomicRefArray.length() - 1) { + val wrapper = atomicRefArray.get(i) + check(wrapper == refArrayValues[i]) + check(wrapper.x == i + 1) + } + return true + } + } + + override fun > O.customize() { + verifier(StaticObjectVerifier::class.java) + threads(1) + iterations(100) + invocationsPerIteration(1) + actorsPerThread(10) + } + + @Operation + fun modifyInt() { + atomicInt.getAndIncrement() + } + + @Operation + fun modifyRef() { + atomicRef.set(Wrapper(atomicInt.get() + 1)) + } + + @Operation + fun modifyIntArray() { + atomicIntArray.getAndIncrement(Random.nextInt(0, atomicIntArray.length())) + } + + @Operation + fun modifyRefArray() { + atomicRefArray.set(Random.nextInt(0, atomicRefArray.length()), Wrapper(Random.nextInt())) + } + + @Operation + fun modifyRefArrayValues() { + atomicRefArray.get(Random.nextInt(0, atomicRefArray.length())).x = Random.nextInt() + } +} \ No newline at end of file diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectAsFieldSnapshotTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/ObjectAsFieldSnapshotTest.kt similarity index 89% rename from src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectAsFieldSnapshotTest.kt rename to src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/ObjectAsFieldSnapshotTest.kt index 9aa545289..5077e7e98 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectAsFieldSnapshotTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/ObjectAsFieldSnapshotTest.kt @@ -10,10 +10,10 @@ package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot -import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.annotations.Operation import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario +import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedOptions private object Static1 { @@ -26,7 +26,7 @@ private object Static2 { var f2: String = "abc" } -class StaticObjectAsFieldSnapshotTest : AbstractSnapshotTest() { +class ObjectAsFieldSnapshotTest : AbstractSnapshotTest() { companion object { private val initS1f1 = Static1.f1 private val initS1f2 = Static1.f2 @@ -44,7 +44,7 @@ class StaticObjectAsFieldSnapshotTest : AbstractSnapshotTest() { } } - override fun > O.customize() { + override fun > O.customize() { verifier(StaticObjectAsFieldVerifier::class.java) } diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectCycleSnapshotTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/ObjectCycleSnapshotTest.kt similarity index 87% rename from src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectCycleSnapshotTest.kt rename to src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/ObjectCycleSnapshotTest.kt index 06e602a59..3577b4fff 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectCycleSnapshotTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/ObjectCycleSnapshotTest.kt @@ -10,17 +10,17 @@ package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot -import org.jetbrains.kotlinx.lincheck.Options import org.jetbrains.kotlinx.lincheck.annotations.Operation import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario +import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedOptions private class A(var b: B) private class B(var a: A? = null) private var globalA = A(B()) -class StaticObjectCycleSnapshotTest : AbstractSnapshotTest() { +class ObjectCycleSnapshotTest : AbstractSnapshotTest() { companion object { private var initA = globalA private var initB = globalA.b @@ -40,7 +40,7 @@ class StaticObjectCycleSnapshotTest : AbstractSnapshotTest() { } } - override fun > O.customize() { + override fun > O.customize() { verifier(StaticObjectCycleVerifier::class.java) } diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectSnapshotTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectSnapshotTest.kt deleted file mode 100644 index f3156fcf8..000000000 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/StaticObjectSnapshotTest.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Lincheck - * - * Copyright (C) 2019 - 2024 JetBrains s.r.o. - * - * This Source Code Form is subject to the terms of the - * Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot - -import org.jetbrains.kotlinx.lincheck.Options -import org.jetbrains.kotlinx.lincheck.annotations.Operation -import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult -import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario -import java.util.concurrent.atomic.AtomicInteger - - -private var staticInt = AtomicInteger(1) - -class StaticObjectSnapshotTest : AbstractSnapshotTest() { - companion object { - private var ref: AtomicInteger = staticInt - private var value: Int = staticInt.get() - } - - class StaticObjectVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : SnapshotVerifier() { - override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { - checkForExceptions(results) - check(staticInt == ref) - check(staticInt.get() == value) - return true - } - } - - override fun > O.customize() { - verifier(StaticObjectVerifier::class.java) - } - - @Operation - fun modify() { - staticInt.getAndIncrement() - } -} \ No newline at end of file From 5ef20241f853838ea96fb8a244cefccc6789bc41 Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Thu, 14 Nov 2024 12:20:00 +0100 Subject: [PATCH 13/29] Add collections tests --- .../strategy/managed/SnapshotTracker.kt | 6 +- .../snapshot/CollectionSnapshotTest.kt | 226 +++++++++++++++++- 2 files changed, 228 insertions(+), 4 deletions(-) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt index ad37de6a4..b1754e4cd 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt @@ -183,11 +183,11 @@ class SnapshotTracker { } private fun shouldTrackEnergetically(obj: Any?): Boolean { - // TODO: We should filter out some standard library classes that we don't care about (like, PrintStream: System.out/in/err) - // and only traverse energetically important std classes like AtomicInteger, etc. if (obj == null) return false return ( - obj.javaClass.name.startsWith("java.util.concurrent.") && obj.javaClass.name.contains("Atomic") + // TODO: in further development of snapshot restoring feature this check should be removed + // (and only check for java atomic classes), see https://github.com/JetBrains/lincheck/pull/418#issue-2595977113 + obj.javaClass.name.startsWith("java.util.") ) } diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/CollectionSnapshotTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/CollectionSnapshotTest.kt index fd4682f87..eac2a72d6 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/CollectionSnapshotTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/CollectionSnapshotTest.kt @@ -10,13 +10,16 @@ package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot +import org.jetbrains.kotlinx.lincheck.LoggingLevel import org.jetbrains.kotlinx.lincheck.annotations.Operation import org.jetbrains.kotlinx.lincheck.check import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedOptions import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingOptions -import org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot.SetSnapshotTest.Companion.Wrapper +import java.util.PriorityQueue +import java.util.Queue +import java.util.concurrent.ConcurrentHashMap import kotlin.random.Random @@ -102,4 +105,225 @@ class SetSnapshotTest : CollectionSnapshotTest() { override fun reassign() { staticSet = mutableSetOf() } +} + +class MapSnapshotTest : CollectionSnapshotTest() { + companion object { + private class Wrapper(var value: Int) + private var staticMap = mutableMapOf() + + // remember values for restoring + private val ref = staticMap + private val a = Wrapper(1) + private val b = Wrapper(2) + private val c = Wrapper(3) + init { + staticMap.put(1, a) + staticMap.put(2, b) + staticMap.put(3, c) + } + } + + class MapVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : SnapshotVerifier() { + override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { + checkForExceptions(results) + check(staticMap == ref) + check(staticMap.size == 3 && staticMap[1] == a && staticMap[2] == b && staticMap[3] == c) + check(a.value == 1 && b.value == 2 && c.value == 3) + return true + } + } + + override fun > O.customize() { + verifier(MapVerifier::class.java) + } + + @Operation + override fun addElement() { + staticMap.put(Random.nextInt(), Wrapper(Random.nextInt())) + } + + @Operation + override fun removeElement() { + staticMap.remove(staticMap.keys.randomOrNull() ?: return) + } + + @Operation + override fun updateElement() { + staticMap.entries.randomOrNull()?.value?.value = Random.nextInt() + } + + @Operation + override fun clear() { + staticMap.clear() + } + + @Operation + override fun reassign() { + staticMap = mutableMapOf() + } +} + +class ListSnapshotTest : CollectionSnapshotTest() { + companion object { + private class Wrapper(var value: Int) + private var staticList = mutableListOf() + + // remember values for restoring + private val ref = staticList + private val a = Wrapper(1) + private val b = Wrapper(2) + private val c = Wrapper(3) + init { + staticList.addAll(listOf(a, b, c)) + } + } + + class ListVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : SnapshotVerifier() { + override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { + checkForExceptions(results) + check(staticList == ref) + check(staticList.size == 3 && staticList[0] == a && staticList[1] == b && staticList[2] == c) + check(a.value == 1 && b.value == 2 && c.value == 3) + return true + } + } + + override fun > O.customize() { + verifier(ListVerifier::class.java) + } + + @Operation + override fun addElement() { + staticList.add(Wrapper(Random.nextInt())) + } + + @Operation + override fun removeElement() { + staticList.remove(staticList.randomOrNull() ?: return) + } + + @Operation + override fun updateElement() { + staticList.randomOrNull()?.value = Random.nextInt() + } + + @Operation + override fun clear() { + staticList.clear() + } + + @Operation + override fun reassign() { + staticList = mutableListOf() + } +} + +class PriorityQueueSnapshotTest : CollectionSnapshotTest() { + companion object { + private var staticQueue: Queue = PriorityQueue() + + // remember values for restoring + private val ref = staticQueue + init { + staticQueue.addAll(listOf(3, 1, 2)) + } + } + + class PriorityQueueVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : SnapshotVerifier() { + override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { + checkForExceptions(results) + check(staticQueue == ref) + check(staticQueue.size == 3 && staticQueue.containsAll(listOf(1, 2, 3))) + var i = 1 + while (staticQueue.isNotEmpty()) { + check(staticQueue.remove() == i++) + } + staticQueue.addAll(listOf(3, 1, 2)) + return true + } + } + + override fun > O.customize() { + verifier(PriorityQueueVerifier::class.java) + } + + @Operation + override fun addElement() { + staticQueue.add(Random.nextInt()) + } + + @Operation + override fun removeElement() { + staticQueue.remove(staticQueue.randomOrNull() ?: return) + } + + @Operation + override fun clear() { + staticQueue.clear() + } + + @Operation + override fun reassign() { + staticQueue = PriorityQueue() + } +} + +class ConcurrentMapSnapshotTest : CollectionSnapshotTest() { + companion object { + private class Wrapper(var value: Int) + private var staticCMap = ConcurrentHashMap() + + // remember values for restoring + private val ref = staticCMap + private val a = Wrapper(1) + private val b = Wrapper(2) + private val c = Wrapper(3) + init { + staticCMap.putAll(mapOf( + 1 to a, + 2 to b, + 3 to c + )) + } + } + + class ConcurrentMapVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : SnapshotVerifier() { + override fun verifyResults(scenario: ExecutionScenario?, results: ExecutionResult?): Boolean { + checkForExceptions(results) + check(staticCMap == ref) + check(staticCMap.size == 3 && staticCMap[1] == a && staticCMap[2] == b && staticCMap[3] == c) + check(a.value == 1 && b.value == 2 && c.value == 3) + return true + } + } + + override fun > O.customize() { + verifier(ConcurrentMapVerifier::class.java) + } + + @Operation + override fun addElement() { + staticCMap.put(Random.nextInt(), Wrapper(Random.nextInt())) + } + + @Operation + override fun updateElement() { + staticCMap.values.randomOrNull()?.value = Random.nextInt() + } + + @Operation + override fun removeElement() { + staticCMap.remove(staticCMap.keys.randomOrNull() ?: return) + } + + @Operation + override fun clear() { + staticCMap.clear() + } + + @Operation + override fun reassign() { + staticCMap = ConcurrentHashMap() + } } \ No newline at end of file From 4c08faa630680f257f240ecd7a77ff3220a271bb Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Fri, 15 Nov 2024 20:10:54 +0100 Subject: [PATCH 14/29] Add tracking for fields of objects the same type as constructor owner --- .../src/sun/nio/ch/lincheck/EventTracker.java | 1 + .../src/sun/nio/ch/lincheck/Injections.java | 13 ++++ .../strategy/managed/ManagedStrategy.kt | 10 +++ .../strategy/managed/SnapshotTracker.kt | 6 +- .../SnapshotTrackerTransformer.kt | 68 ++++++++++++++++++- .../ConstructorNotThisModificationTest.kt | 53 +++++++++++++++ 6 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/ConstructorNotThisModificationTest.kt diff --git a/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java b/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java index 003ab9e17..d5dbdeaf2 100644 --- a/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java +++ b/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java @@ -34,6 +34,7 @@ public interface EventTracker { void updateSnapshotOnFieldAccess(Object obj, String className, String fieldName, int codeLocation); void updateSnapshotOnArrayElementAccess(Object array, int index, int codeLocation); + void updateSnapshotWithEnergeticTracking(Object[] objs); boolean beforeReadField(Object obj, String className, String fieldName, int codeLocation, boolean isStatic, boolean isFinal); diff --git a/bootstrap/src/sun/nio/ch/lincheck/Injections.java b/bootstrap/src/sun/nio/ch/lincheck/Injections.java index 568ff9e6c..35640e752 100644 --- a/bootstrap/src/sun/nio/ch/lincheck/Injections.java +++ b/bootstrap/src/sun/nio/ch/lincheck/Injections.java @@ -294,14 +294,27 @@ public static void afterNewObjectCreation(Object obj) { getEventTracker().afterNewObjectCreation(obj); } + /** + * Called from instrumented code on field access. + */ public static void updateSnapshotOnFieldAccess(Object obj, String className, String fieldName, int codeLocation) { getEventTracker().updateSnapshotOnFieldAccess(obj, className, fieldName, codeLocation); } + /** + * Called from instrumented code on array element access. + */ public static void updateSnapshotOnArrayElementAccess(Object array, int index, int codeLocation) { getEventTracker().updateSnapshotOnArrayElementAccess(array, index, codeLocation); } + /** + * Called from instrumented code on constructor invocation, where passed objects are subtypes of the constructor class type. + */ + public static void updateSnapshotWithEnergeticTracking(Object[] objs) { + getEventTracker().updateSnapshotWithEnergeticTracking(objs); + } + /** * Called from the instrumented code to replace [java.lang.Object.hashCode] method call with some * deterministic value. diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt index 7d7ad7b6b..5ee6f86ac 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt @@ -957,6 +957,16 @@ abstract class ManagedStrategy( } } + /** + * Tracks all objects in [objs] energetically. + * Required as a trick to overcome issue with leaking this in constructors, see https://github.com/JetBrains/lincheck/issues/424. + */ + override fun updateSnapshotWithEnergeticTracking(objs: Array) = runInIgnoredSection { + if (testCfg.restoreStaticMemory) { + staticMemorySnapshot.trackObjects(objs) + } + } + private fun methodGuaranteeType(owner: Any?, className: String, methodName: String): ManagedGuaranteeType? = runInIgnoredSection { userDefinedGuarantees?.forEach { guarantee -> val ownerName = owner?.javaClass?.canonicalName ?: className diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt index b1754e4cd..149df627b 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt @@ -101,6 +101,10 @@ class SnapshotTracker { } } + fun trackObjects(objs: Array) { + objs.filterNotNull().forEach { trackHierarchy(it) } + } + fun restoreValues() { //println("====== START RESTORING ======") val visitedObjects = Collections.newSetFromMap(IdentityHashMap()) @@ -186,7 +190,7 @@ class SnapshotTracker { if (obj == null) return false return ( // TODO: in further development of snapshot restoring feature this check should be removed - // (and only check for java atomic classes), see https://github.com/JetBrains/lincheck/pull/418#issue-2595977113 + // (and only check for java atomic classes inserted), see https://github.com/JetBrains/lincheck/pull/418#issue-2595977113 obj.javaClass.name.startsWith("java.util.") ) } diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SnapshotTrackerTransformer.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SnapshotTrackerTransformer.kt index d59f6cf4e..576f1bac4 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SnapshotTrackerTransformer.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SnapshotTrackerTransformer.kt @@ -24,6 +24,73 @@ internal class SnapshotTrackerTransformer( adapter: GeneratorAdapter, ) : ManagedStrategyWithAnalyzerClassVisitor(fileName, className, methodName, adapter) { + /** + * Searches for invocations of constructors `className::(args)` and inserts bytecode + * that will extract all objects from `args` which types are subtypes of the class `className`. + * Snapshot tracker then tracks the extracted objects energetically. + * + * This is a hack solution to the problem of impossibility of identifying whether + * some object on stack in the constructor is a `this` reference or not. + * So for instructions `GETFIELD`/`PUTFIELD` in constructors handler of `visitFieldInsn(...)` method below + * we ignore the accesses to fields which owners are the same type as the constructor owner. + * The optimization is done to avoid 'leaking this' problem during bytecode verification. + * But sometimes `GETFIELD`/`PUTFIELD` are invoked on fields with owner objects are the same type as a constructor owner, + * but which are not `this`. This can happen when: + * - a non-static object is accessed which was passed to constructor arguments + * - a non-static object is accessed which was created locally in constructor + * + * We only case about the 1nd case, because for the 3nd case when dealing with + * a locally constructed object it can't be reached from static memory, so no need to track its fields. + * The 2nd case where such object is passed via constructor argument is handled here. + */ + override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) = adapter.run { + if (name == "") { + val matchedArguments = getArgumentTypes(desc).toList() + .mapIndexed { index, type -> + /* TODO: change to type.className.isSubclassOf(owner) */ + if (type.className == owner.canonicalClassName) index + else null + } + .filterNotNull() + .toIntArray() + + if (matchedArguments.isEmpty()) { + visitMethodInsn(opcode, owner, name, desc, itf) + return + } + + invokeIfInTestingCode( + original = { visitMethodInsn(opcode, owner, name, desc, itf) }, + code = { + // STACK: args + val arguments = storeArguments(desc) + val matchedLocals = arguments.filterIndexed { index, _ -> matchedArguments.contains(index) } + // STACK: + push(matchedLocals.size) + // STACK: length + visitTypeInsn(ANEWARRAY, "java/lang/Object") + // STACK: array + matchedLocals.forEachIndexed { index, local -> + // STACK: array + visitInsn(DUP) + push(index) + loadLocal(local) + // STACK: array, array, index, obj + visitInsn(AASTORE) + // STACK: array + } + // STACK: array + invokeStatic(Injections::updateSnapshotWithEnergeticTracking) + // STACK: + loadLocals(arguments) + // STACK: args + visitMethodInsn(opcode, owner, name, desc, itf) + } + ) + } + else visitMethodInsn(opcode, owner, name, desc, itf) + } + override fun visitFieldInsn(opcode: Int, owner: String, fieldName: String, desc: String) = adapter.run { if ( isCoroutineInternalClass(owner) || @@ -31,7 +98,6 @@ internal class SnapshotTrackerTransformer( // when initializing our own fields in constructor, we do not want to track that as snapshot modification (methodName == "" && className == owner) ) { - //println("Do not transform access to field '$fieldName' with owner '$owner' of type '$desc' while in class '$className' and method '$methodName' in file '$fileName'.'") visitFieldInsn(opcode, owner, fieldName, desc) return } diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/ConstructorNotThisModificationTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/ConstructorNotThisModificationTest.kt new file mode 100644 index 000000000..35d65493a --- /dev/null +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/ConstructorNotThisModificationTest.kt @@ -0,0 +1,53 @@ +/* + * Lincheck + * + * Copyright (C) 2019 - 2024 JetBrains s.r.o. + * + * This Source Code Form is subject to the terms of the + * Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed + * with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot + +import org.jetbrains.kotlinx.lincheck.annotations.Operation +import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult +import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario +import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedOptions + + +class ConstructorNotThisModificationTest : AbstractSnapshotTest() { + class ConstructorNotThisModificationVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : SnapshotVerifier() { + override fun verifyResults( + scenario: ExecutionScenario?, + results: ExecutionResult? + ): Boolean { + checkForExceptions(results) + check(staticNode.a == 1) + return true + } + } + + override fun > O.customize() { + verifier(ConstructorNotThisModificationVerifier::class.java) + threads(1) + invocationsPerIteration(1) + actorsPerThread(1) + } + + class Node(var a: Int) { + constructor(other: Node) : this(2) { + other.a = 0 // this change should be tracked because no 'leaking this' problem exists here + } + } + + companion object { + val staticNode = Node(1) + } + + @Operation + @Suppress("UNUSED_VARIABLE") + fun modify() { + val localNode = Node(staticNode) + } +} \ No newline at end of file From eb9a9a25f0c57e5b5bdf55dac8f38273ffc7dd8a Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Tue, 26 Nov 2024 14:17:04 +0100 Subject: [PATCH 15/29] Optimize tracking of constructors' arguments --- .../org/jetbrains/kotlinx/lincheck/Utils.kt | 6 - .../strategy/managed/SnapshotTracker.kt | 123 ++++++++++++++++-- .../kotlinx/lincheck/util/ObjectGraph.kt | 13 +- 3 files changed, 116 insertions(+), 26 deletions(-) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt index 883ae4ad1..42b7e4377 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt @@ -16,12 +16,6 @@ import org.jetbrains.kotlinx.lincheck.transformation.LincheckClassFileTransforme import org.jetbrains.kotlinx.lincheck.util.UnsafeHolder import org.jetbrains.kotlinx.lincheck.util.isAtomicArray import org.jetbrains.kotlinx.lincheck.util.isAtomicFUArray -import org.jetbrains.kotlinx.lincheck.util.isAtomicArray -import org.jetbrains.kotlinx.lincheck.util.isAtomicFUArray -import org.jetbrains.kotlinx.lincheck.util.isAtomicFieldUpdater -import org.jetbrains.kotlinx.lincheck.util.isUnsafeClass -import org.jetbrains.kotlinx.lincheck.util.readArrayElementViaUnsafe -import org.jetbrains.kotlinx.lincheck.util.readFieldViaUnsafe import org.jetbrains.kotlinx.lincheck.verifier.* import sun.nio.ch.lincheck.* import java.io.PrintWriter diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt index 149df627b..d9b32f70d 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt @@ -12,17 +12,10 @@ package org.jetbrains.kotlinx.lincheck.strategy.managed import org.jetbrains.kotlinx.lincheck.findField import org.jetbrains.kotlinx.lincheck.getFieldOffset -import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.Descriptor.ArrayCellDescriptor -import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.Descriptor.FieldDescriptor -import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.MemoryNode.ArrayCellNode -import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.MemoryNode.RegularFieldNode -import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.MemoryNode.StaticFieldNode -import org.jetbrains.kotlinx.lincheck.util.isAtomicJava -import org.jetbrains.kotlinx.lincheck.util.readArrayElementViaUnsafe -import org.jetbrains.kotlinx.lincheck.util.readFieldViaUnsafe -import org.jetbrains.kotlinx.lincheck.util.traverseObjectGraph -import org.jetbrains.kotlinx.lincheck.util.writeArrayElementViaUnsafe -import org.jetbrains.kotlinx.lincheck.util.writeFieldViaUnsafe +import org.jetbrains.kotlinx.lincheck.isPrimitiveWrapper +import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.Descriptor.* +import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.MemoryNode.* +import org.jetbrains.kotlinx.lincheck.util.* import java.lang.reflect.Field import java.lang.reflect.Modifier import java.util.Collections @@ -58,6 +51,54 @@ class SnapshotTracker { fun size(): Int = (trackedObjects.keys.filter { it !is Class<*> } + trackedObjects.values.flatMap { it }.mapNotNull { it.initialValue }).toSet().size +// fun printRoots() { +// val rootSizes = mutableMapOf() +// val mappedFromRoots = mutableMapOf>() +// trackedObjects.keys.filterIsInstance>().forEach { root -> +// var size = 0 +// val countedObjs = mutableSetOf() +// mappedFromRoots[root] = mutableListOf() +// +// trackedObjects[root]!!.forEach nodesTraverse@{ node -> +// if (node.initialValue == null) return@nodesTraverse +// +// mappedFromRoots[root]!!.add(node.initialValue) +// size++ +// +// traverseObjectGraph( +// node.initialValue, +// onArrayElement = { _, _, value -> +// if (value in trackedObjects && value !in countedObjs) { +// countedObjs.add(value!!) +// mappedFromRoots[root]!!.add(value) +// size++ +// } +// value +// }, +// onField = { _, _, value -> +// if (value in trackedObjects && value !in countedObjs) { +// countedObjs.add(value!!) +// mappedFromRoots[root]!!.add(value) +// size++ +// } +// value +// } +// ) +// } +// +// rootSizes[root] = size +// } +// +// val sizesSorted = rootSizes.entries +// .sortedBy { -it.value } // Sort by values +// .associate { it.key to it.value } // Convert back to a map +// mappedFromRoots.entries +// .sortedBy { -it.value.size } +// .toList() +// +//// println("Snapshot roots (${sizesSorted.size}): $sizesSorted") +// } + @OptIn(ExperimentalStdlibApi::class) fun trackField(obj: Any?, className: String, fieldName: String, @Suppress("UNUSED_PARAMETER") location: String = "") { //println("Consider ($location): obj=${obj?.javaClass?.simpleName}${if (obj != null) "@" + System.identityHashCode(obj).toHexString() else ""}, className=$className, fieldName=$fieldName") @@ -102,7 +143,60 @@ class SnapshotTracker { } fun trackObjects(objs: Array) { - objs.filterNotNull().forEach { trackHierarchy(it) } + val processField = { owner: Any, field: Field, fieldValue: Any? -> + trackSingleField(owner, owner.javaClass, field, fieldValue) { + if (shouldTrackEnergetically(fieldValue) /* also checks for `fieldValue != null` */) { + trackHierarchy(fieldValue!!) + } + } + } + + objs + // leave only those objects from constructor arguments that were tracked before the call to constructors itself + .filter { it != null && it in trackedObjects } + .forEach { obj -> + // We want to track the following values: + // 1. objects themselves (already tracked because of the filtering) + // 2. 1st layer of fields of these objects (tracking the whole hierarchy is too expensive, and full laziness does not work, + // because of the JVM class verificator limitations, see https://github.com/JetBrains/lincheck/issues/424, thus, we collect + // fields which afterward can be used for further lazy tracking) + // 3. values that are subclasses of the objects' class and their 1st layer of fields + // (we are not sure if they are going to require restoring, but we still add them preventively, + // again, because there is verificator limitation on tracking such values lazily) + traverseObjectGraph( + obj!!, + // `obj` cannot be an array because it is the same type as some class, which constructor was called, and arrays have not constructors + onArrayElement = null, + onField = { owner, field, fieldValue -> + when { + // add 1st layer of fields of `obj` (2) + obj == owner -> { + processField(owner, field, fieldValue) + // track subclasses of `obj` and their 1st layer of fields (bullet-point 3) recursively + if (fieldValue?.javaClass?.isInstance(obj) == true) { + // allow traversing further, because `obj`'s 1st layer contains an object which is a subclass of `obj` + // the `owner` of `fieldValue` is already added to `trackedObjects` (because `owner == obj`) + fieldValue + } + else { + null + } + } + + // track subclasses of `obj` and their 1st layer of fields (3) + fieldValue != null && fieldValue.javaClass.isInstance(obj) -> { + // track the `owner` of `fieldValue`, because otherwise we will not be able to + // restore the initial value of `fieldValue` object + trackedObjects.putIfAbsent(owner, mutableListOf()) + processField(owner, field, fieldValue) + fieldValue + } + + else -> null + } + } + ) + } } fun restoreValues() { @@ -233,9 +327,10 @@ class SnapshotTracker { private fun isTrackableObject(value: Any?): Boolean { return ( - value != null && + value != null && !value.javaClass.isPrimitive && - !value.javaClass.isEnum + !value.javaClass.isEnum && + !value.isPrimitiveWrapper ) } diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/ObjectGraph.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/ObjectGraph.kt index f523143ff..47c58e2a7 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/ObjectGraph.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/ObjectGraph.kt @@ -41,11 +41,11 @@ import java.util.* * @param onArrayElement callback for array elements traversal, accepts `(array, index, elementValue)`. * Returns an object to be traversed next. */ -internal inline fun traverseObjectGraph( +internal fun traverseObjectGraph( root: Any, traverseStaticFields: Boolean = false, - onField: (obj: Any, field: Field, value: Any?) -> Any?, - onArrayElement: (array: Any, index: Int, element: Any?) -> Any?, + onField: ((obj: Any, field: Field, value: Any?) -> Any?)?, + onArrayElement: ((array: Any, index: Int, element: Any?) -> Any?)?, ) { val queue = ArrayDeque() val visitedObjects = Collections.newSetFromMap(IdentityHashMap()) @@ -75,12 +75,13 @@ internal inline fun traverseObjectGraph( val currentObj = queue.removeFirst() when { - currentObj.javaClass.isArray || isAtomicArray(currentObj) -> { + onArrayElement != null && + (currentObj.javaClass.isArray || isAtomicArray(currentObj)) -> { traverseArrayElements(currentObj) { _ /* currentObj */, index, elementValue -> processNextObject(onArrayElement(currentObj, index, elementValue)) } } - else -> { + onField != null -> { traverseObjectFields(currentObj, traverseStaticFields = traverseStaticFields ) { _ /* currentObj */, field, fieldValue -> @@ -103,7 +104,7 @@ internal inline fun traverseObjectGraph( * * @see traverseObjectGraph */ -internal inline fun traverseObjectGraph( +internal fun traverseObjectGraph( root: Any, traverseStaticFields: Boolean = false, onObject: (obj: Any) -> Any? From 0803b6dd2261479df3bf5ba88f1fcb4ab22eeb8a Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Tue, 26 Nov 2024 14:52:40 +0100 Subject: [PATCH 16/29] Remove println's and comments --- .../strategy/managed/ManagedStrategy.kt | 7 +- .../strategy/managed/SnapshotTracker.kt | 68 +------------------ 2 files changed, 5 insertions(+), 70 deletions(-) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt index 5ee6f86ac..fec479b27 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt @@ -220,7 +220,6 @@ abstract class ManagedStrategy( } override fun restoreStaticMemorySnapshot() { - // TODO: what is the appropriate location to call this function? if (testCfg.restoreStaticMemory) { staticMemorySnapshot.restoreValues() super.restoreStaticMemorySnapshot() @@ -942,8 +941,7 @@ abstract class ManagedStrategy( */ override fun updateSnapshotOnFieldAccess(obj: Any?, className: String, fieldName: String, codeLocation: Int) = runInIgnoredSection { if (testCfg.restoreStaticMemory) { - val location = CodeLocations.stackTrace(codeLocation).toString() - staticMemorySnapshot.trackField(obj, className, fieldName, location) + staticMemorySnapshot.trackField(obj, className, fieldName) } } @@ -952,8 +950,7 @@ abstract class ManagedStrategy( */ override fun updateSnapshotOnArrayElementAccess(array: Any, index: Int, codeLocation: Int) = runInIgnoredSection { if (testCfg.restoreStaticMemory) { - val location = CodeLocations.stackTrace(codeLocation).toString() - staticMemorySnapshot.trackArrayCell(array, index, location) + staticMemorySnapshot.trackArrayCell(array, index) } } diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt index d9b32f70d..55f3c66da 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt @@ -49,59 +49,8 @@ class SnapshotTracker { class ArrayCellNode(descriptor: ArrayCellDescriptor, initialValue: Any?) : MemoryNode(descriptor, initialValue) } - fun size(): Int = (trackedObjects.keys.filter { it !is Class<*> } + trackedObjects.values.flatMap { it }.mapNotNull { it.initialValue }).toSet().size - -// fun printRoots() { -// val rootSizes = mutableMapOf() -// val mappedFromRoots = mutableMapOf>() -// trackedObjects.keys.filterIsInstance>().forEach { root -> -// var size = 0 -// val countedObjs = mutableSetOf() -// mappedFromRoots[root] = mutableListOf() -// -// trackedObjects[root]!!.forEach nodesTraverse@{ node -> -// if (node.initialValue == null) return@nodesTraverse -// -// mappedFromRoots[root]!!.add(node.initialValue) -// size++ -// -// traverseObjectGraph( -// node.initialValue, -// onArrayElement = { _, _, value -> -// if (value in trackedObjects && value !in countedObjs) { -// countedObjs.add(value!!) -// mappedFromRoots[root]!!.add(value) -// size++ -// } -// value -// }, -// onField = { _, _, value -> -// if (value in trackedObjects && value !in countedObjs) { -// countedObjs.add(value!!) -// mappedFromRoots[root]!!.add(value) -// size++ -// } -// value -// } -// ) -// } -// -// rootSizes[root] = size -// } -// -// val sizesSorted = rootSizes.entries -// .sortedBy { -it.value } // Sort by values -// .associate { it.key to it.value } // Convert back to a map -// mappedFromRoots.entries -// .sortedBy { -it.value.size } -// .toList() -// -//// println("Snapshot roots (${sizesSorted.size}): $sizesSorted") -// } - @OptIn(ExperimentalStdlibApi::class) - fun trackField(obj: Any?, className: String, fieldName: String, @Suppress("UNUSED_PARAMETER") location: String = "") { - //println("Consider ($location): obj=${obj?.javaClass?.simpleName}${if (obj != null) "@" + System.identityHashCode(obj).toHexString() else ""}, className=$className, fieldName=$fieldName") + fun trackField(obj: Any?, className: String, fieldName: String) { if (obj != null && obj !in trackedObjects) return val clazz: Class<*> = Class.forName(className).let { @@ -113,8 +62,6 @@ class SnapshotTracker { if (readResult.isSuccess) { val fieldValue = readResult.getOrNull() - //println("Consider: value=${fieldValue}") - trackSingleField(obj, clazz, field, fieldValue) { if (shouldTrackEnergetically(fieldValue)) { trackHierarchy(fieldValue!!) @@ -124,16 +71,13 @@ class SnapshotTracker { } @OptIn(ExperimentalStdlibApi::class) - fun trackArrayCell(array: Any, index: Int, @Suppress("UNUSED_PARAMETER") location: String = "") { - //println("Consider ($location): array=${array.javaClass.simpleName}@${System.identityHashCode(array).toHexString()}, index=$index") + fun trackArrayCell(array: Any, index: Int) { if (array !in trackedObjects) return val readResult = runCatching { readArrayElementViaUnsafe(array, index) } if (readResult.isSuccess) { val elementValue = readResult.getOrNull() - //println("Consider: element=${elementValue}") - trackSingleArrayCell(array, index, elementValue) { if (shouldTrackEnergetically(elementValue)) { trackHierarchy(elementValue!!) @@ -200,7 +144,6 @@ class SnapshotTracker { } fun restoreValues() { - //println("====== START RESTORING ======") val visitedObjects = Collections.newSetFromMap(IdentityHashMap()) trackedObjects.keys .filterIsInstance>() @@ -227,7 +170,6 @@ class SnapshotTracker { .any { it.field.name == field.name } // field is already tracked ) return - //println("SNAPSHOT: obj=${if (obj == null) "null" else obj.javaClass.simpleName + "@" + System.identityHashCode(obj).toHexString()}, className=${clazz.name}, fieldName=${field.name}, value=${fieldValue}") val childNode = createMemoryNode( obj, FieldDescriptor(field, getFieldOffset(field)), @@ -250,7 +192,6 @@ class SnapshotTracker { nodesList.any { it is ArrayCellNode && (it.descriptor as ArrayCellDescriptor).index == index } // this array cell is already tracked ) return - //println("SNAPSHOT: array=${array.javaClass.simpleName}@${System.identityHashCode(array).toHexString()}, index=$index, value=${elementValue}") val childNode = createMemoryNode(array, ArrayCellDescriptor(index), elementValue) nodesList.add(childNode) @@ -262,7 +203,6 @@ class SnapshotTracker { @OptIn(ExperimentalStdlibApi::class) private fun trackHierarchy(obj: Any) { - //println("Track hierarchy for object: ${obj.javaClass.simpleName}@${System.identityHashCode(obj).toHexString()}") traverseObjectGraph( obj, onArrayElement = { array, index, elementValue -> @@ -285,6 +225,7 @@ class SnapshotTracker { return ( // TODO: in further development of snapshot restoring feature this check should be removed // (and only check for java atomic classes inserted), see https://github.com/JetBrains/lincheck/pull/418#issue-2595977113 + // right it is need for collections to be restored properly (because of missing support for `System.arrayCopy()` and other similar methods) obj.javaClass.name.startsWith("java.util.") ) } @@ -292,14 +233,12 @@ class SnapshotTracker { @OptIn(ExperimentalStdlibApi::class) private fun restoreValues(obj: Any, visitedObjects: MutableSet) { if (obj in visitedObjects) return - //println("RESTORE: obj=${if (obj is Class<*>) obj.simpleName else obj.javaClass.simpleName}@${System.identityHashCode(obj).toHexString()}:") visitedObjects.add(obj) trackedObjects[obj]!! .forEach { node -> if (node is ArrayCellNode) { val index = (node.descriptor as ArrayCellDescriptor).index - //println("\tRESTORE: arr=${obj.javaClass.simpleName}@${System.identityHashCode(obj).toHexString()}, index=$index, value=${node.initialValue}") when (obj) { // No need to add support for writing to atomicfu array elements, @@ -311,7 +250,6 @@ class SnapshotTracker { } } else if (!Modifier.isFinal((node.descriptor as FieldDescriptor).field.modifiers)) { - //println("\tRESTORE: obj=${if (obj is Class<*>) obj.simpleName else obj.javaClass.simpleName}@${System.identityHashCode(obj).toHexString()}, field=${node.descriptor.field.name}, value=${node.initialValue}") writeFieldViaUnsafe( if (node is StaticFieldNode) null else obj, node.descriptor.field, From 1b6bbefadeeac9abca535f2ab41318d3bc687472 Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Wed, 27 Nov 2024 14:27:31 +0100 Subject: [PATCH 17/29] Fix todo with subclass check in SnapshotTrackerTransformer --- .../strategy/managed/ManagedStrategy.kt | 8 ------ .../transformation/LincheckClassVisitor.kt | 6 ++--- .../transformation/SafeClassWriter.java | 5 ++++ .../SnapshotTrackerTransformer.kt | 25 +++++++++++++++++-- ...kt => ConstructorArgumentsTrackingTest.kt} | 15 ++++++++--- 5 files changed, 42 insertions(+), 17 deletions(-) rename src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/{ConstructorNotThisModificationTest.kt => ConstructorArgumentsTrackingTest.kt} (75%) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt index fec479b27..36f83c983 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt @@ -753,8 +753,6 @@ abstract class ManagedStrategy( */ override fun beforeReadField(obj: Any?, className: String, fieldName: String, codeLocation: Int, isStatic: Boolean, isFinal: Boolean) = runInIgnoredSection { -// println("FIELD R: obj=${prettyName(obj)}, className=$className, fieldName=$fieldName, loc=${CodeLocations.stackTrace(codeLocation).toString()}") -// updateSnapshotOnFieldAccess(obj, className.canonicalClassName, fieldName, codeLocation) // We need to ensure all the classes related to the reading object are instrumented. // The following call checks all the static fields. if (isStatic) { @@ -791,8 +789,6 @@ abstract class ManagedStrategy( /** Returns true if a switch point is created. */ override fun beforeReadArrayElement(array: Any, index: Int, codeLocation: Int): Boolean = runInIgnoredSection { -// println("FIELD Rarr: arr=${prettyName(array)}, index=$index, loc=${CodeLocations.stackTrace(codeLocation).toString()}") -// updateSnapshotOnArrayElementAccess(array, index, codeLocation) if (!objectTracker.shouldTrackObjectAccess(array)) { return@runInIgnoredSection false } @@ -834,8 +830,6 @@ abstract class ManagedStrategy( override fun beforeWriteField(obj: Any?, className: String, fieldName: String, value: Any?, codeLocation: Int, isStatic: Boolean, isFinal: Boolean): Boolean = runInIgnoredSection { -// println("FIELD W: obj=${prettyName(obj)}, className=$className, fieldName=$fieldName, value=${prettyName(value)}, loc=${CodeLocations.stackTrace(codeLocation).toString()}") -// updateSnapshotOnFieldAccess(obj, className.canonicalClassName, fieldName, codeLocation) objectTracker.registerObjectLink(fromObject = obj ?: StaticObject, toObject = value) if (!objectTracker.shouldTrackObjectAccess(obj ?: StaticObject)) { return@runInIgnoredSection false @@ -866,8 +860,6 @@ abstract class ManagedStrategy( @OptIn(ExperimentalStdlibApi::class) override fun beforeWriteArrayElement(array: Any, index: Int, value: Any?, codeLocation: Int): Boolean = runInIgnoredSection { -// println("FIELD Warr: arr=${prettyName(array)}, index=$index, value=${prettyName(value)}, loc=${CodeLocations.stackTrace(codeLocation).toString()}") -// updateSnapshotOnArrayElementAccess(array, index, codeLocation) objectTracker.registerObjectLink(fromObject = array, toObject = value) if (!objectTracker.shouldTrackObjectAccess(array)) { return@runInIgnoredSection false diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckClassVisitor.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckClassVisitor.kt index 0ce8808b9..91e8f0ceb 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckClassVisitor.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckClassVisitor.kt @@ -22,7 +22,7 @@ import sun.nio.ch.lincheck.* internal class LincheckClassVisitor( private val instrumentationMode: InstrumentationMode, - classVisitor: ClassVisitor + private val classVisitor: SafeClassWriter ) : ClassVisitor(ASM_API, classVisitor) { private val ideaPluginEnabled = ideaPluginEnabled() private var classVersion = 0 @@ -92,7 +92,7 @@ internal class LincheckClassVisitor( if (methodName == "") { mv = ObjectCreationTransformer(fileName, className, methodName, mv.newAdapter()) mv = run { - val st = SnapshotTrackerTransformer(fileName, className, methodName, mv.newAdapter()) + val st = SnapshotTrackerTransformer(fileName, className, methodName, mv.newAdapter(), classVisitor::isInstanceOf) val aa = AnalyzerAdapter(className, access, methodName, desc, st) st.analyzer = aa aa @@ -151,7 +151,7 @@ internal class LincheckClassVisitor( // which should be put in front of the byte-code transformer chain, // so that it can correctly analyze the byte-code and compute required type-information mv = run { - val st = SnapshotTrackerTransformer(fileName, className, methodName, mv.newAdapter()) + val st = SnapshotTrackerTransformer(fileName, className, methodName, mv.newAdapter(), classVisitor::isInstanceOf) val sv = SharedMemoryAccessTransformer(fileName, className, methodName, st.newAdapter()) val aa = AnalyzerAdapter(className, access, methodName, desc, sv) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/SafeClassWriter.java b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/SafeClassWriter.java index 52adc124f..5cb0ef130 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/SafeClassWriter.java +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/SafeClassWriter.java @@ -35,6 +35,7 @@ import java.io.IOException; import java.io.InputStream; +import java.util.Objects; /** @@ -53,6 +54,10 @@ public SafeClassWriter(ClassReader cr, ClassLoader loader, final int flags) { this.loader = loader != null ? loader : ClassLoader.getSystemClassLoader(); } + public boolean isInstanceOf(final String actualType, final String expectedSuperType) { + return Objects.equals(getCommonSuperClass(actualType, expectedSuperType), expectedSuperType); + } + @Override protected String getCommonSuperClass(final String type1, final String type2) { try { diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SnapshotTrackerTransformer.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SnapshotTrackerTransformer.kt index 576f1bac4..f39e0c7b3 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SnapshotTrackerTransformer.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SnapshotTrackerTransformer.kt @@ -13,6 +13,7 @@ package org.jetbrains.kotlinx.lincheck.transformation.transformers import org.jetbrains.kotlinx.lincheck.canonicalClassName import org.jetbrains.kotlinx.lincheck.transformation.* import org.objectweb.asm.Opcodes.* +import org.objectweb.asm.Type import org.objectweb.asm.Type.* import org.objectweb.asm.commons.GeneratorAdapter import sun.nio.ch.lincheck.Injections @@ -22,6 +23,8 @@ internal class SnapshotTrackerTransformer( className: String, methodName: String, adapter: GeneratorAdapter, + // `SafeClassWriter::isInstanceOf` method which checks the subclassing without loading the classes to VM + private val isInstanceOf: (actualType: String, expectedSuperType: String) -> Boolean ) : ManagedStrategyWithAnalyzerClassVisitor(fileName, className, methodName, adapter) { /** @@ -47,8 +50,11 @@ internal class SnapshotTrackerTransformer( if (name == "") { val matchedArguments = getArgumentTypes(desc).toList() .mapIndexed { index, type -> - /* TODO: change to type.className.isSubclassOf(owner) */ - if (type.className == owner.canonicalClassName) index + if ( + !isArray(type) && + !isPrimitive(type) && + isInstanceOf(type.className.replace('.', '/'), owner) + ) index else null } .filterNotNull() @@ -211,4 +217,19 @@ internal class SnapshotTrackerTransformer( } } } + + private fun isArray(type: Type): Boolean = type.sort == Type.ARRAY + + private fun isPrimitive(type: Type): Boolean { + val sort = type.sort + return sort == Type.BOOLEAN || + sort == Type.CHAR || + sort == Type.BYTE || + sort == Type.SHORT || + sort == Type.INT || + sort == Type.FLOAT || + sort == Type.LONG || + sort == Type.DOUBLE || + sort == Type.VOID + } } \ No newline at end of file diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/ConstructorNotThisModificationTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/ConstructorArgumentsTrackingTest.kt similarity index 75% rename from src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/ConstructorNotThisModificationTest.kt rename to src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/ConstructorArgumentsTrackingTest.kt index 35d65493a..93ec164a4 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/ConstructorNotThisModificationTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/ConstructorArgumentsTrackingTest.kt @@ -16,7 +16,7 @@ import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedOptions -class ConstructorNotThisModificationTest : AbstractSnapshotTest() { +class ConstructorArgumentsTrackingTest : AbstractSnapshotTest() { class ConstructorNotThisModificationVerifier(@Suppress("UNUSED_PARAMETER") sequentialSpecification: Class<*>) : SnapshotVerifier() { override fun verifyResults( scenario: ExecutionScenario?, @@ -24,6 +24,7 @@ class ConstructorNotThisModificationTest : AbstractSnapshotTest() { ): Boolean { checkForExceptions(results) check(staticNode.a == 1) + check(staticSubNode.a == 2) return true } } @@ -35,19 +36,25 @@ class ConstructorNotThisModificationTest : AbstractSnapshotTest() { actorsPerThread(1) } - class Node(var a: Int) { - constructor(other: Node) : this(2) { + open class Node(var a: Int) { + constructor(other: Node, other2: SubNode) : this(2) { other.a = 0 // this change should be tracked because no 'leaking this' problem exists here + + val castedOther2 = other2 as Node + castedOther2.a = 0 // this change should be tracked because no 'leaking this' problem exists here } } + class SubNode(a: Int) : Node(a) + companion object { val staticNode = Node(1) + val staticSubNode = SubNode(2) } @Operation @Suppress("UNUSED_VARIABLE") fun modify() { - val localNode = Node(staticNode) + val localNode = Node(staticNode, staticSubNode) } } \ No newline at end of file From 86d5bd2ecb19f02b7b7531f2fa235b4a0bd39847 Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Wed, 27 Nov 2024 15:03:38 +0100 Subject: [PATCH 18/29] Add VarHandles to excludes in heirarchy tracking --- .../strategy/managed/SnapshotTracker.kt | 61 +++---------------- 1 file changed, 7 insertions(+), 54 deletions(-) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt index 55f3c66da..1fbb16de3 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt @@ -87,60 +87,11 @@ class SnapshotTracker { } fun trackObjects(objs: Array) { - val processField = { owner: Any, field: Field, fieldValue: Any? -> - trackSingleField(owner, owner.javaClass, field, fieldValue) { - if (shouldTrackEnergetically(fieldValue) /* also checks for `fieldValue != null` */) { - trackHierarchy(fieldValue!!) - } - } - } - + // in case this works too slowly, an optimization could be used + // see https://github.com/JetBrains/lincheck/pull/418/commits/0d708b84dd2bfd5dbfa961549dda128d91dc3a5b#diff-a684b1d7deeda94bbf907418b743ae2c0ec0a129760d3b87d00cdf5adfab56c4R146-R199 objs - // leave only those objects from constructor arguments that were tracked before the call to constructors itself .filter { it != null && it in trackedObjects } - .forEach { obj -> - // We want to track the following values: - // 1. objects themselves (already tracked because of the filtering) - // 2. 1st layer of fields of these objects (tracking the whole hierarchy is too expensive, and full laziness does not work, - // because of the JVM class verificator limitations, see https://github.com/JetBrains/lincheck/issues/424, thus, we collect - // fields which afterward can be used for further lazy tracking) - // 3. values that are subclasses of the objects' class and their 1st layer of fields - // (we are not sure if they are going to require restoring, but we still add them preventively, - // again, because there is verificator limitation on tracking such values lazily) - traverseObjectGraph( - obj!!, - // `obj` cannot be an array because it is the same type as some class, which constructor was called, and arrays have not constructors - onArrayElement = null, - onField = { owner, field, fieldValue -> - when { - // add 1st layer of fields of `obj` (2) - obj == owner -> { - processField(owner, field, fieldValue) - // track subclasses of `obj` and their 1st layer of fields (bullet-point 3) recursively - if (fieldValue?.javaClass?.isInstance(obj) == true) { - // allow traversing further, because `obj`'s 1st layer contains an object which is a subclass of `obj` - // the `owner` of `fieldValue` is already added to `trackedObjects` (because `owner == obj`) - fieldValue - } - else { - null - } - } - - // track subclasses of `obj` and their 1st layer of fields (3) - fieldValue != null && fieldValue.javaClass.isInstance(obj) -> { - // track the `owner` of `fieldValue`, because otherwise we will not be able to - // restore the initial value of `fieldValue` object - trackedObjects.putIfAbsent(owner, mutableListOf()) - processField(owner, field, fieldValue) - fieldValue - } - - else -> null - } - } - ) - } + .forEach { trackHierarchy(it!!) } } fun restoreValues() { @@ -212,6 +163,8 @@ class SnapshotTracker { onField = { owner, field, fieldValue -> // optimization to track only `value` field of java atomic classes if (isAtomicJava(owner) && field.name != "value") null + // do not traverse fields of var-handles + else if (owner.javaClass.typeName == "java.lang.invoke.VarHandle") null else { trackSingleField(owner, owner.javaClass, field, fieldValue) fieldValue @@ -224,8 +177,8 @@ class SnapshotTracker { if (obj == null) return false return ( // TODO: in further development of snapshot restoring feature this check should be removed - // (and only check for java atomic classes inserted), see https://github.com/JetBrains/lincheck/pull/418#issue-2595977113 - // right it is need for collections to be restored properly (because of missing support for `System.arrayCopy()` and other similar methods) + // (and only check for java atomic classes should be inserted), see https://github.com/JetBrains/lincheck/pull/418#issue-2595977113 + // right now it is needed for collections to be restored properly (because of missing support for `System.arrayCopy()` and other similar methods) obj.javaClass.name.startsWith("java.util.") ) } From 8eac5dcf0cf74056469a645e5c626e25a913713a Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Wed, 27 Nov 2024 15:08:09 +0100 Subject: [PATCH 19/29] Clean up code (remove comments and useless annotations) --- .../kotlinx/lincheck/strategy/managed/ManagedStrategy.kt | 7 ------- .../kotlinx/lincheck/strategy/managed/SnapshotTracker.kt | 6 ------ 2 files changed, 13 deletions(-) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt index 36f83c983..236b60399 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt @@ -822,12 +822,6 @@ abstract class ManagedStrategy( loopDetector.afterRead(value) } - @OptIn(ExperimentalStdlibApi::class) - private fun prettyName(obj: Any?): String { - if (obj == null) return "null" - return "${obj.javaClass.name}@${System.identityHashCode(obj).toHexString()}" - } - override fun beforeWriteField(obj: Any?, className: String, fieldName: String, value: Any?, codeLocation: Int, isStatic: Boolean, isFinal: Boolean): Boolean = runInIgnoredSection { objectTracker.registerObjectLink(fromObject = obj ?: StaticObject, toObject = value) @@ -858,7 +852,6 @@ abstract class ManagedStrategy( return@runInIgnoredSection true } - @OptIn(ExperimentalStdlibApi::class) override fun beforeWriteArrayElement(array: Any, index: Int, value: Any?, codeLocation: Int): Boolean = runInIgnoredSection { objectTracker.registerObjectLink(fromObject = array, toObject = value) if (!objectTracker.shouldTrackObjectAccess(array)) { diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt index 1fbb16de3..73cbca21c 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt @@ -49,7 +49,6 @@ class SnapshotTracker { class ArrayCellNode(descriptor: ArrayCellDescriptor, initialValue: Any?) : MemoryNode(descriptor, initialValue) } - @OptIn(ExperimentalStdlibApi::class) fun trackField(obj: Any?, className: String, fieldName: String) { if (obj != null && obj !in trackedObjects) return @@ -70,7 +69,6 @@ class SnapshotTracker { } } - @OptIn(ExperimentalStdlibApi::class) fun trackArrayCell(array: Any, index: Int) { if (array !in trackedObjects) return @@ -101,7 +99,6 @@ class SnapshotTracker { .forEach { restoreValues(it, visitedObjects) } } - @OptIn(ExperimentalStdlibApi::class) private fun trackSingleField( obj: Any?, clazz: Class<*>, @@ -134,7 +131,6 @@ class SnapshotTracker { } } - @OptIn(ExperimentalStdlibApi::class) private fun trackSingleArrayCell(array: Any, index: Int, elementValue: Any?, callback: (() -> Unit)? = null) { val nodesList = trackedObjects[array] @@ -152,7 +148,6 @@ class SnapshotTracker { } } - @OptIn(ExperimentalStdlibApi::class) private fun trackHierarchy(obj: Any) { traverseObjectGraph( obj, @@ -183,7 +178,6 @@ class SnapshotTracker { ) } - @OptIn(ExperimentalStdlibApi::class) private fun restoreValues(obj: Any, visitedObjects: MutableSet) { if (obj in visitedObjects) return visitedObjects.add(obj) From 709a6d3d07350e196794009f1882086ab0f323f8 Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Mon, 2 Dec 2024 14:17:11 +0100 Subject: [PATCH 20/29] Add some PR fixes --- .../src/sun/nio/ch/lincheck/EventTracker.java | 3 - .../src/sun/nio/ch/lincheck/Injections.java | 14 -- .../kotlinx/lincheck/CTestConfiguration.kt | 1 - .../org/jetbrains/kotlinx/lincheck/Utils.kt | 60 ----- .../kotlinx/lincheck/strategy/Strategy.kt | 17 +- .../managed/AtomicFieldUpdaterNames.kt | 2 +- .../strategy/managed/FieldSearchHelper.kt | 2 +- .../managed/ManagedCTestConfiguration.kt | 1 - .../strategy/managed/ManagedStrategy.kt | 39 +-- .../strategy/managed/SnapshotTracker.kt | 59 ++--- .../lincheck/strategy/managed/UnsafeNames.kt | 2 +- .../strategy/managed/VarHandleNames.kt | 2 +- .../modelchecking/ModelCheckingCTest.java | 5 - .../ModelCheckingCTestConfiguration.kt | 5 +- .../modelchecking/ModelCheckingOptions.kt | 1 - .../transformation/LincheckClassVisitor.kt | 74 +----- .../transformation/TransformationUtils.kt | 19 ++ ...ctorArgumentsSnapshotTrackerTransformer.kt | 98 ++++++++ .../SharedMemoryAccessTransformer.kt | 63 ++++- .../SnapshotTrackerTransformer.kt | 235 ------------------ .../kotlinx/lincheck/util/ObjectGraph.kt | 2 +- .../kotlinx/lincheck/util/UnsafeHolder.kt | 29 ++- 22 files changed, 272 insertions(+), 461 deletions(-) create mode 100644 src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/ConstructorArgumentsSnapshotTrackerTransformer.kt delete mode 100644 src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SnapshotTrackerTransformer.kt diff --git a/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java b/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java index d5dbdeaf2..841ca8ce0 100644 --- a/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java +++ b/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java @@ -32,10 +32,7 @@ public interface EventTracker { void beforeNewObjectCreation(String className); void afterNewObjectCreation(Object obj); - void updateSnapshotOnFieldAccess(Object obj, String className, String fieldName, int codeLocation); - void updateSnapshotOnArrayElementAccess(Object array, int index, int codeLocation); void updateSnapshotWithEnergeticTracking(Object[] objs); - boolean beforeReadField(Object obj, String className, String fieldName, int codeLocation, boolean isStatic, boolean isFinal); boolean beforeReadArrayElement(Object array, int index, int codeLocation); diff --git a/bootstrap/src/sun/nio/ch/lincheck/Injections.java b/bootstrap/src/sun/nio/ch/lincheck/Injections.java index 35640e752..f69d5e629 100644 --- a/bootstrap/src/sun/nio/ch/lincheck/Injections.java +++ b/bootstrap/src/sun/nio/ch/lincheck/Injections.java @@ -294,20 +294,6 @@ public static void afterNewObjectCreation(Object obj) { getEventTracker().afterNewObjectCreation(obj); } - /** - * Called from instrumented code on field access. - */ - public static void updateSnapshotOnFieldAccess(Object obj, String className, String fieldName, int codeLocation) { - getEventTracker().updateSnapshotOnFieldAccess(obj, className, fieldName, codeLocation); - } - - /** - * Called from instrumented code on array element access. - */ - public static void updateSnapshotOnArrayElementAccess(Object array, int index, int codeLocation) { - getEventTracker().updateSnapshotOnArrayElementAccess(array, index, codeLocation); - } - /** * Called from instrumented code on constructor invocation, where passed objects are subtypes of the constructor class type. */ diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/CTestConfiguration.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/CTestConfiguration.kt index 26d9a3afb..af395ccbb 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/CTestConfiguration.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/CTestConfiguration.kt @@ -95,7 +95,6 @@ internal fun createFromTestClassAnnotations(testClass: Class<*>): List.findField(fieldName: String): Field { throw NoSuchFieldException("Class '${this.name}' does not have field '$fieldName'") } -@Suppress("DEPRECATION") -fun getFieldOffset(field: Field): Long { - return if (Modifier.isStatic(field.modifiers)) { - UnsafeHolder.UNSAFE.staticFieldOffset(field) - } - else { - UnsafeHolder.UNSAFE.objectFieldOffset(field) - } -} - -internal fun getArrayElementOffset(arr: Any, index: Int): Long { - val clazz = arr::class.java - val baseOffset = UnsafeHolder.UNSAFE.arrayBaseOffset(clazz).toLong() - val indexScale = UnsafeHolder.UNSAFE.arrayIndexScale(clazz).toLong() - - return baseOffset + index * indexScale -} - -internal fun getArrayLength(arr: Any): Int { - return when { - arr is Array<*> -> arr.size - arr is IntArray -> arr.size - arr is DoubleArray -> arr.size - arr is FloatArray -> arr.size - arr is LongArray -> arr.size - arr is ShortArray -> arr.size - arr is ByteArray -> arr.size - arr is BooleanArray -> arr.size - arr is CharArray -> arr.size - isAtomicArray(arr) -> getAtomicArrayLength(arr) - else -> error("Argument is not an array") - } -} - -internal fun getAtomicArrayLength(arr: Any): Int { - return when { - arr is AtomicReferenceArray<*> -> arr.length() - arr is AtomicIntegerArray -> arr.length() - arr is AtomicLongArray -> arr.length() - isAtomicFUArray(arr) -> arr.javaClass.getMethod("getSize").invoke(arr) as Int - else -> error("Argument is not atomic array") - } -} - -@Suppress("DEPRECATION") -internal fun findFieldNameByOffset(targetType: Class<*>, offset: Long): String? { - // Extract the private offset value and find the matching field. - for (field in targetType.declaredFields) { - try { - if (Modifier.isNative(field.modifiers)) continue - val fieldOffset = if (Modifier.isStatic(field.modifiers)) UnsafeHolder.UNSAFE.staticFieldOffset(field) - else UnsafeHolder.UNSAFE.objectFieldOffset(field) - if (fieldOffset == offset) return field.name - } catch (t: Throwable) { - t.printStackTrace() - } - } - return null // Field not found -} - /** * Thrown in case when `cause` exception is unexpected by Lincheck internal logic. */ diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt index 8eb904f6d..8f210daf2 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt @@ -11,6 +11,7 @@ package org.jetbrains.kotlinx.lincheck.strategy import org.jetbrains.kotlinx.lincheck.runner.* import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario +import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedStrategy import org.jetbrains.kotlinx.lincheck.strategy.managed.Trace import org.jetbrains.kotlinx.lincheck.verifier.Verifier import java.io.Closeable @@ -90,11 +91,6 @@ abstract class Strategy protected constructor( override fun close() { runner.close() } - - /** - * Restores recorded values of all memory reachable from static state. - */ - open fun restoreStaticMemorySnapshot() {} } /** @@ -112,13 +108,18 @@ fun Strategy.runIteration(invocations: Int, verifier: Verifier): LincheckFailure if (!nextInvocation()) break val result = runInvocation() - restoreStaticMemorySnapshot() - failure = verify(result, verifier) + try { + failure = verify(result, verifier) + } + finally { + if (this is ManagedStrategy) { + restoreStaticMemorySnapshot() + } + } if (failure != null) break } - restoreStaticMemorySnapshot() return failure } diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/AtomicFieldUpdaterNames.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/AtomicFieldUpdaterNames.kt index 53dbae8ec..a1ef39392 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/AtomicFieldUpdaterNames.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/AtomicFieldUpdaterNames.kt @@ -10,8 +10,8 @@ package org.jetbrains.kotlinx.lincheck.strategy.managed -import org.jetbrains.kotlinx.lincheck.findFieldNameByOffset import org.jetbrains.kotlinx.lincheck.util.UnsafeHolder.UNSAFE +import org.jetbrains.kotlinx.lincheck.util.findFieldNameByOffset import java.util.concurrent.atomic.AtomicIntegerFieldUpdater import java.util.concurrent.atomic.AtomicLongFieldUpdater import java.util.concurrent.atomic.AtomicReferenceFieldUpdater diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/FieldSearchHelper.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/FieldSearchHelper.kt index 4a6d342c5..ce13fca98 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/FieldSearchHelper.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/FieldSearchHelper.kt @@ -57,7 +57,7 @@ internal object FieldSearchHelper { traverseObjectGraph( testObject, traverseStaticFields = true, - onArrayElement = { _, _, _ -> null }, // do not traverse array elements further + onArrayElement = null, // do not traverse array elements further onField = { ownerObject, field, fieldValue -> when { field.type.isPrimitive || fieldValue == null -> null diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedCTestConfiguration.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedCTestConfiguration.kt index 5deac7fd6..7699ea0d9 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedCTestConfiguration.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedCTestConfiguration.kt @@ -28,7 +28,6 @@ abstract class ManagedCTestConfiguration( val checkObstructionFreedom: Boolean, val hangingDetectionThreshold: Int, val invocationsPerIteration: Int, - val restoreStaticMemory: Boolean, val guarantees: List, minimizeFailedScenario: Boolean, sequentialSpecification: Class<*>, diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt index 236b60399..5f7036804 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt @@ -219,11 +219,11 @@ abstract class ManagedStrategy( parkingTracker.reset() } - override fun restoreStaticMemorySnapshot() { - if (testCfg.restoreStaticMemory) { - staticMemorySnapshot.restoreValues() - super.restoreStaticMemorySnapshot() - } + /** + * Restores recorded values of all memory reachable from static state. + */ + fun restoreStaticMemorySnapshot() { + staticMemorySnapshot.restoreValues() } /** @@ -232,8 +232,13 @@ abstract class ManagedStrategy( override fun runInvocation(): InvocationResult { while (true) { initializeInvocation() - restoreStaticMemorySnapshot() - val result = runner.run() + val result: InvocationResult + try { + result = runner.run() + } + finally { + restoreStaticMemorySnapshot() + } // In case the runner detects a deadlock, some threads can still manipulate the current strategy, // so we're not interested in suddenInvocationResult in this case // and immediately return RunnerTimeoutInvocationResult. @@ -753,6 +758,7 @@ abstract class ManagedStrategy( */ override fun beforeReadField(obj: Any?, className: String, fieldName: String, codeLocation: Int, isStatic: Boolean, isFinal: Boolean) = runInIgnoredSection { + updateSnapshotOnFieldAccess(obj, className.canonicalClassName, fieldName) // We need to ensure all the classes related to the reading object are instrumented. // The following call checks all the static fields. if (isStatic) { @@ -789,6 +795,7 @@ abstract class ManagedStrategy( /** Returns true if a switch point is created. */ override fun beforeReadArrayElement(array: Any, index: Int, codeLocation: Int): Boolean = runInIgnoredSection { + updateSnapshotOnArrayElementAccess(array, index) if (!objectTracker.shouldTrackObjectAccess(array)) { return@runInIgnoredSection false } @@ -824,6 +831,7 @@ abstract class ManagedStrategy( override fun beforeWriteField(obj: Any?, className: String, fieldName: String, value: Any?, codeLocation: Int, isStatic: Boolean, isFinal: Boolean): Boolean = runInIgnoredSection { + updateSnapshotOnFieldAccess(obj, className.canonicalClassName, fieldName) objectTracker.registerObjectLink(fromObject = obj ?: StaticObject, toObject = value) if (!objectTracker.shouldTrackObjectAccess(obj ?: StaticObject)) { return@runInIgnoredSection false @@ -853,6 +861,7 @@ abstract class ManagedStrategy( } override fun beforeWriteArrayElement(array: Any, index: Int, value: Any?, codeLocation: Int): Boolean = runInIgnoredSection { + updateSnapshotOnArrayElementAccess(array, index) objectTracker.registerObjectLink(fromObject = array, toObject = value) if (!objectTracker.shouldTrackObjectAccess(array)) { return@runInIgnoredSection false @@ -924,19 +933,15 @@ abstract class ManagedStrategy( * Tracks a specific field of an [obj], if the [obj] is either `null` (which means that field is static), * or one this objects which contains it is already stored. */ - override fun updateSnapshotOnFieldAccess(obj: Any?, className: String, fieldName: String, codeLocation: Int) = runInIgnoredSection { - if (testCfg.restoreStaticMemory) { - staticMemorySnapshot.trackField(obj, className, fieldName) - } + fun updateSnapshotOnFieldAccess(obj: Any?, className: String, fieldName: String) = runInIgnoredSection { + staticMemorySnapshot.trackField(obj, className, fieldName) } /** * Tracks a specific [array] element at [index], if the [array] is already tracked. */ - override fun updateSnapshotOnArrayElementAccess(array: Any, index: Int, codeLocation: Int) = runInIgnoredSection { - if (testCfg.restoreStaticMemory) { - staticMemorySnapshot.trackArrayCell(array, index) - } + fun updateSnapshotOnArrayElementAccess(array: Any, index: Int) = runInIgnoredSection { + staticMemorySnapshot.trackArrayCell(array, index) } /** @@ -944,9 +949,7 @@ abstract class ManagedStrategy( * Required as a trick to overcome issue with leaking this in constructors, see https://github.com/JetBrains/lincheck/issues/424. */ override fun updateSnapshotWithEnergeticTracking(objs: Array) = runInIgnoredSection { - if (testCfg.restoreStaticMemory) { - staticMemorySnapshot.trackObjects(objs) - } + staticMemorySnapshot.trackObjects(objs) } private fun methodGuaranteeType(owner: Any?, className: String, methodName: String): ManagedGuaranteeType? = runInIgnoredSection { diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt index 73cbca21c..8ab575fc3 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt @@ -11,9 +11,7 @@ package org.jetbrains.kotlinx.lincheck.strategy.managed import org.jetbrains.kotlinx.lincheck.findField -import org.jetbrains.kotlinx.lincheck.getFieldOffset import org.jetbrains.kotlinx.lincheck.isPrimitiveWrapper -import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.Descriptor.* import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.MemoryNode.* import org.jetbrains.kotlinx.lincheck.util.* import java.lang.reflect.Field @@ -35,18 +33,14 @@ import java.util.concurrent.atomic.AtomicReferenceArray class SnapshotTracker { private val trackedObjects = IdentityHashMap>() - private sealed class Descriptor { - class FieldDescriptor(val field: Field, val offset: Long) : Descriptor() - class ArrayCellDescriptor(val index: Int) : Descriptor() - } - private sealed class MemoryNode( - val descriptor: Descriptor, val initialValue: Any? ) { - class RegularFieldNode(descriptor: FieldDescriptor, initialValue: Any?) : MemoryNode(descriptor, initialValue) - class StaticFieldNode(descriptor: FieldDescriptor, initialValue: Any?) : MemoryNode(descriptor, initialValue) - class ArrayCellNode(descriptor: ArrayCellDescriptor, initialValue: Any?) : MemoryNode(descriptor, initialValue) + abstract class FieldNode(val field: Field, initialValue: Any?) : MemoryNode(initialValue) + + class RegularFieldNode(field: Field, initialValue: Any?) : FieldNode(field, initialValue) + class StaticFieldNode(field: Field, initialValue: Any?) : FieldNode(field, initialValue) + class ArrayCellNode(val index: Int, initialValue: Any?) : MemoryNode(initialValue) } fun trackField(obj: Any?, className: String, fieldName: String) { @@ -113,16 +107,11 @@ class SnapshotTracker { if ( nodesList == null || // parent object is not tracked nodesList - .map { it.descriptor } - .filterIsInstance() + .filterIsInstance() .any { it.field.name == field.name } // field is already tracked ) return - val childNode = createMemoryNode( - obj, - FieldDescriptor(field, getFieldOffset(field)), - fieldValue - ) + val childNode = createFieldNode(obj, field, fieldValue) nodesList.add(childNode) if (isTrackableObject(childNode.initialValue)) { @@ -136,10 +125,10 @@ class SnapshotTracker { if ( nodesList == null || // array is not tracked - nodesList.any { it is ArrayCellNode && (it.descriptor as ArrayCellDescriptor).index == index } // this array cell is already tracked + nodesList.any { it is ArrayCellNode && it.index == index } // this array cell is already tracked ) return - val childNode = createMemoryNode(array, ArrayCellDescriptor(index), elementValue) + val childNode = createArrayCellNode(index, elementValue) nodesList.add(childNode) if (isTrackableObject(childNode.initialValue)) { @@ -185,21 +174,22 @@ class SnapshotTracker { trackedObjects[obj]!! .forEach { node -> if (node is ArrayCellNode) { - val index = (node.descriptor as ArrayCellDescriptor).index + val index = node.index + val initialValue = node.initialValue when (obj) { // No need to add support for writing to atomicfu array elements, // because atomicfu arrays are compiled to atomic java arrays - is AtomicReferenceArray<*> -> @Suppress("UNCHECKED_CAST") (obj as AtomicReferenceArray).set(index, node.initialValue) - is AtomicIntegerArray -> obj.set(index, node.initialValue as Int) - is AtomicLongArray -> obj.set(index, node.initialValue as Long) - else -> writeArrayElementViaUnsafe(obj, index, node.initialValue) + is AtomicReferenceArray<*> -> @Suppress("UNCHECKED_CAST") (obj as AtomicReferenceArray).set(index, initialValue) + is AtomicIntegerArray -> obj.set(index, initialValue as Int) + is AtomicLongArray -> obj.set(index, initialValue as Long) + else -> writeArrayElementViaUnsafe(obj, index, initialValue) } } - else if (!Modifier.isFinal((node.descriptor as FieldDescriptor).field.modifiers)) { + else if (!Modifier.isFinal((node as FieldNode).field.modifiers)) { writeFieldViaUnsafe( if (node is StaticFieldNode) null else obj, - node.descriptor.field, + node.field, node.initialValue ) } @@ -219,13 +209,12 @@ class SnapshotTracker { ) } - private fun createMemoryNode(obj: Any?, descriptor: Descriptor, value: Any?): MemoryNode { - return when (descriptor) { - is FieldDescriptor -> { - if (obj == null) StaticFieldNode(descriptor, value) - else RegularFieldNode(descriptor, value) - } - is ArrayCellDescriptor -> ArrayCellNode(descriptor, value) - } + private fun createFieldNode(obj: Any?, field: Field, value: Any?): MemoryNode { + return if (obj == null) StaticFieldNode(field, value) + else RegularFieldNode(field, value) + } + + private fun createArrayCellNode(index: Int, value: Any?): MemoryNode { + return ArrayCellNode(index, value) } } \ No newline at end of file diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/UnsafeNames.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/UnsafeNames.kt index 90912729e..da1bd9fb6 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/UnsafeNames.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/UnsafeNames.kt @@ -10,9 +10,9 @@ package org.jetbrains.kotlinx.lincheck.strategy.managed -import org.jetbrains.kotlinx.lincheck.findFieldNameByOffset import org.jetbrains.kotlinx.lincheck.strategy.managed.UnsafeName.* import org.jetbrains.kotlinx.lincheck.util.UnsafeHolder +import org.jetbrains.kotlinx.lincheck.util.findFieldNameByOffset /** * Helper object to provide the field name and the owner of the Unsafe method call. diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/VarHandleNames.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/VarHandleNames.kt index ba15855bf..fd45e3bd4 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/VarHandleNames.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/VarHandleNames.kt @@ -10,8 +10,8 @@ package org.jetbrains.kotlinx.lincheck.strategy.managed -import org.jetbrains.kotlinx.lincheck.findFieldNameByOffset import org.jetbrains.kotlinx.lincheck.strategy.managed.VarHandleMethodType.* +import org.jetbrains.kotlinx.lincheck.util.findFieldNameByOffset import org.jetbrains.kotlinx.lincheck.util.readFieldViaUnsafe import sun.misc.Unsafe import java.lang.reflect.Field diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingCTest.java b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingCTest.java index 11ff9358a..eb826294c 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingCTest.java +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingCTest.java @@ -111,11 +111,6 @@ @Deprecated(message = "Does nothing, because equals/hashcode don't always improve performance of verification") boolean requireStateEquivalenceImplCheck() default false; - /** - * Should the managed strategy use the static memory tracking and restoring algorithm. - */ - boolean restoreStaticMemory() default DEFAULT_RESTORE_STATIC_MEMORY; - /** * If this feature is enabled and an invalid interleaving has been found, * *lincheck* tries to minimize the corresponding scenario in order to diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingCTestConfiguration.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingCTestConfiguration.kt index 01520615c..3a31ec2f2 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingCTestConfiguration.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingCTestConfiguration.kt @@ -23,8 +23,8 @@ import java.lang.reflect.* */ class ModelCheckingCTestConfiguration(testClass: Class<*>, iterations: Int, threads: Int, actorsPerThread: Int, actorsBefore: Int, actorsAfter: Int, generatorClass: Class, verifierClass: Class, - checkObstructionFreedom: Boolean, hangingDetectionThreshold: Int, invocationsPerIteration: Int, - restoreStaticMemory: Boolean, guarantees: List, + checkObstructionFreedom: Boolean, hangingDetectionThreshold: Int, + invocationsPerIteration: Int, guarantees: List, minimizeFailedScenario: Boolean, sequentialSpecification: Class<*>, timeoutMs: Long, customScenarios: List ) : ManagedCTestConfiguration( @@ -39,7 +39,6 @@ class ModelCheckingCTestConfiguration(testClass: Class<*>, iterations: Int, thre checkObstructionFreedom = checkObstructionFreedom, hangingDetectionThreshold = hangingDetectionThreshold, invocationsPerIteration = invocationsPerIteration, - restoreStaticMemory = restoreStaticMemory, guarantees = guarantees, minimizeFailedScenario = minimizeFailedScenario, sequentialSpecification = sequentialSpecification, diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingOptions.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingOptions.kt index b572f36ba..1d2101db7 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingOptions.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingOptions.kt @@ -29,7 +29,6 @@ class ModelCheckingOptions : ManagedOptions") { mv = ObjectCreationTransformer(fileName, className, methodName, mv.newAdapter()) mv = run { - val st = SnapshotTrackerTransformer(fileName, className, methodName, mv.newAdapter(), classVisitor::isInstanceOf) - val aa = AnalyzerAdapter(className, access, methodName, desc, st) - st.analyzer = aa + val st = ConstructorArgumentsSnapshotTrackerTransformer(fileName, className, methodName, mv.newAdapter(), classVisitor::isInstanceOf) + val sv = SharedMemoryAccessTransformer(fileName, className, methodName, st.newAdapter()) + val aa = AnalyzerAdapter(className, access, methodName, desc, sv) + + sv.analyzer = aa aa } return mv @@ -151,11 +152,10 @@ internal class LincheckClassVisitor( // which should be put in front of the byte-code transformer chain, // so that it can correctly analyze the byte-code and compute required type-information mv = run { - val st = SnapshotTrackerTransformer(fileName, className, methodName, mv.newAdapter(), classVisitor::isInstanceOf) + val st = ConstructorArgumentsSnapshotTrackerTransformer(fileName, className, methodName, mv.newAdapter(), classVisitor::isInstanceOf) val sv = SharedMemoryAccessTransformer(fileName, className, methodName, st.newAdapter()) val aa = AnalyzerAdapter(className, access, methodName, desc, sv) - st.analyzer = aa sv.analyzer = aa aa } @@ -169,68 +169,6 @@ internal class LincheckClassVisitor( } -internal open class ManagedStrategyWithAnalyzerClassVisitor( - fileName: String, - className: String, - methodName: String, - adapter: GeneratorAdapter -) : ManagedStrategyMethodVisitor(fileName, className, methodName, adapter) { - - lateinit var analyzer: AnalyzerAdapter - - - /* - * For an array access instruction (either load or store), - * tries to obtain the type of the read/written array element. - * - * If the type can be determined from the opcode of the instruction itself - * (e.g., IALOAD/IASTORE) returns it immediately. - * - * Otherwise, queries the analyzer to determine the type of the array in the respective stack slot. - * This is used in two cases: - * - for `BALOAD` and `BASTORE` instructions, since they are used to access both boolean and byte arrays; - * - for `AALOAD` and `AASTORE` instructions, to get the class name of the array elements. - */ - protected fun getArrayElementType(opcode: Int): Type = when (opcode) { - // Load - IALOAD -> INT_TYPE - FALOAD -> FLOAT_TYPE - CALOAD -> CHAR_TYPE - SALOAD -> SHORT_TYPE - LALOAD -> LONG_TYPE - DALOAD -> DOUBLE_TYPE - BALOAD -> getArrayAccessTypeFromStack(2) ?: BYTE_TYPE - AALOAD -> getArrayAccessTypeFromStack(2) ?: OBJECT_TYPE - // Store - IASTORE -> INT_TYPE - FASTORE -> FLOAT_TYPE - CASTORE -> CHAR_TYPE - SASTORE -> SHORT_TYPE - LASTORE -> LONG_TYPE - DASTORE -> DOUBLE_TYPE - BASTORE -> getArrayAccessTypeFromStack(3) ?: BYTE_TYPE - AASTORE -> getArrayAccessTypeFromStack(3) ?: OBJECT_TYPE - else -> throw IllegalStateException("Unexpected opcode: $opcode") - } - - /* - * Tries to obtain the type of array elements by inspecting the type of the array itself. - * To do this, the method queries the analyzer to get the type of accessed array - * which should lie on the stack. - * If the analyzer does not know the type, then return null - * (according to the ASM docs, this can happen, for example, when the visited instruction is unreachable). - */ - protected fun getArrayAccessTypeFromStack(position: Int): Type? { - if (analyzer.stack == null) return null - val arrayDesc = analyzer.stack[analyzer.stack.size - position] - check(arrayDesc is String) - val arrayType = getType(arrayDesc) - check(arrayType.sort == ARRAY) - check(arrayType.dimensions > 0) - return getType(arrayDesc.substring(1)) - } -} - internal open class ManagedStrategyMethodVisitor( protected val fileName: String, protected val className: String, diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/TransformationUtils.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/TransformationUtils.kt index 7f7cd072b..58a9cda87 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/TransformationUtils.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/TransformationUtils.kt @@ -306,6 +306,25 @@ internal inline fun GeneratorAdapter.invokeInIgnoredSection( ) } +/** + * @param type asm type descriptor. + * @return whether [type] is a java array type (primitive or reference). + */ +internal fun isArray(type: Type): Boolean = type.sort == Type.ARRAY + +/** + * @param type asm type descriptor. + * @return whether [type] is a non-reference primitive type (e.g. `int`, `boolean`, etc.). + */ +internal fun isPrimitive(type: Type): Boolean { + return when (type.sort) { + Type.BOOLEAN, Type.CHAR, Type.BYTE, + Type.SHORT, Type.INT, Type.FLOAT, + Type.LONG, Type.DOUBLE, Type.VOID -> true + else -> false + } +} + private val isCoroutineStateMachineClassMap = ConcurrentHashMap() internal fun isCoroutineStateMachineClass(internalClassName: String): Boolean { diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/ConstructorArgumentsSnapshotTrackerTransformer.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/ConstructorArgumentsSnapshotTrackerTransformer.kt new file mode 100644 index 000000000..88b8281c4 --- /dev/null +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/ConstructorArgumentsSnapshotTrackerTransformer.kt @@ -0,0 +1,98 @@ +/* + * Lincheck + * + * Copyright (C) 2019 - 2024 JetBrains s.r.o. + * + * This Source Code Form is subject to the terms of the + * Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed + * with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.jetbrains.kotlinx.lincheck.transformation.transformers + +import org.jetbrains.kotlinx.lincheck.transformation.* +import org.objectweb.asm.Opcodes.* +import org.objectweb.asm.Type +import org.objectweb.asm.Type.* +import org.objectweb.asm.commons.GeneratorAdapter +import sun.nio.ch.lincheck.Injections + +internal class ConstructorArgumentsSnapshotTrackerTransformer( + fileName: String, + className: String, + methodName: String, + adapter: GeneratorAdapter, + // `SafeClassWriter::isInstanceOf` method which checks the subclassing without loading the classes to VM + private val isInstanceOf: (actualType: String, expectedSuperType: String) -> Boolean +) : ManagedStrategyMethodVisitor(fileName, className, methodName, adapter) { + + /** + * Searches for invocations of constructors `className::(args)` and inserts bytecode + * that will extract all objects from `args` which types are subtypes of the class `className`. + * Snapshot tracker then tracks the extracted objects energetically. + * + * This is a hack solution to the problem of impossibility of identifying whether + * some object on stack in the constructor is a `this` reference or not. + * So for instructions `GETFIELD`/`PUTFIELD` in constructors handler of `visitFieldInsn(...)` method below + * we ignore the accesses to fields which owners are the same type as the constructor owner. + * The optimization is done to avoid 'leaking this' problem during bytecode verification. + * But sometimes `GETFIELD`/`PUTFIELD` are invoked on fields with owner objects are the same type as a constructor owner, + * but which are not `this`. This can happen when: + * - a non-static object is accessed which was passed to constructor arguments + * - a non-static object is accessed which was created locally in constructor + * + * We only case about the 1nd case, because for the 3nd case when dealing with + * a locally constructed object it can't be reached from static memory, so no need to track its fields. + * The 2nd case where such object is passed via constructor argument is handled here. + */ + override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) = adapter.run { + if (name == "") { + val matchedArguments = getArgumentTypes(desc).toList() + .mapIndexed { index, type -> + if ( + !isArray(type) && + !isPrimitive(type) && + isInstanceOf(type.className.replace('.', '/'), owner) + ) index + else null + } + .filterNotNull() + .toIntArray() + + if (matchedArguments.isEmpty()) { + visitMethodInsn(opcode, owner, name, desc, itf) + return + } + + invokeIfInTestingCode( + original = { visitMethodInsn(opcode, owner, name, desc, itf) }, + code = { + // STACK: args + val arguments = storeArguments(desc) + val matchedLocals = arguments.filterIndexed { index, _ -> matchedArguments.contains(index) } + // STACK: + push(matchedLocals.size) + // STACK: length + visitTypeInsn(ANEWARRAY, "java/lang/Object") + // STACK: array + matchedLocals.forEachIndexed { index, local -> + // STACK: array + visitInsn(DUP) + push(index) + loadLocal(local) + // STACK: array, array, index, obj + visitInsn(AASTORE) + // STACK: array + } + // STACK: array + invokeStatic(Injections::updateSnapshotWithEnergeticTracking) + // STACK: + loadLocals(arguments) + // STACK: args + visitMethodInsn(opcode, owner, name, desc, itf) + } + ) + } + else visitMethodInsn(opcode, owner, name, desc, itf) + } +} \ No newline at end of file diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SharedMemoryAccessTransformer.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SharedMemoryAccessTransformer.kt index 620763e22..b39f32176 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SharedMemoryAccessTransformer.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SharedMemoryAccessTransformer.kt @@ -28,10 +28,17 @@ internal class SharedMemoryAccessTransformer( className: String, methodName: String, adapter: GeneratorAdapter, -) : ManagedStrategyWithAnalyzerClassVisitor(fileName, className, methodName, adapter) { +) : ManagedStrategyMethodVisitor(fileName, className, methodName, adapter) { + + lateinit var analyzer: AnalyzerAdapter override fun visitFieldInsn(opcode: Int, owner: String, fieldName: String, desc: String) = adapter.run { - if (isCoroutineInternalClass(owner) || isCoroutineStateMachineClass(owner)) { + if ( + isCoroutineInternalClass(owner) || + isCoroutineStateMachineClass(owner) || + // when initializing our own fields in constructor, we do not want to track that + (methodName == "" && className == owner) + ) { visitFieldInsn(opcode, owner, fieldName, desc) return } @@ -268,4 +275,56 @@ internal class SharedMemoryAccessTransformer( invokeStatic(Injections::afterRead) // STACK: value } + + + /* + * For an array access instruction (either load or store), + * tries to obtain the type of the read/written array element. + * + * If the type can be determined from the opcode of the instruction itself + * (e.g., IALOAD/IASTORE) returns it immediately. + * + * Otherwise, queries the analyzer to determine the type of the array in the respective stack slot. + * This is used in two cases: + * - for `BALOAD` and `BASTORE` instructions, since they are used to access both boolean and byte arrays; + * - for `AALOAD` and `AASTORE` instructions, to get the class name of the array elements. + */ + private fun getArrayElementType(opcode: Int): Type = when (opcode) { + // Load + IALOAD -> INT_TYPE + FALOAD -> FLOAT_TYPE + CALOAD -> CHAR_TYPE + SALOAD -> SHORT_TYPE + LALOAD -> LONG_TYPE + DALOAD -> DOUBLE_TYPE + BALOAD -> getArrayAccessTypeFromStack(2) ?: BYTE_TYPE + AALOAD -> getArrayAccessTypeFromStack(2) ?: OBJECT_TYPE + // Store + IASTORE -> INT_TYPE + FASTORE -> FLOAT_TYPE + CASTORE -> CHAR_TYPE + SASTORE -> SHORT_TYPE + LASTORE -> LONG_TYPE + DASTORE -> DOUBLE_TYPE + BASTORE -> getArrayAccessTypeFromStack(3) ?: BYTE_TYPE + AASTORE -> getArrayAccessTypeFromStack(3) ?: OBJECT_TYPE + else -> throw IllegalStateException("Unexpected opcode: $opcode") + } + + /* + * Tries to obtain the type of array elements by inspecting the type of the array itself. + * To do this, the method queries the analyzer to get the type of accessed array + * which should lie on the stack. + * If the analyzer does not know the type, then return null + * (according to the ASM docs, this can happen, for example, when the visited instruction is unreachable). + */ + private fun getArrayAccessTypeFromStack(position: Int): Type? { + if (analyzer.stack == null) return null + val arrayDesc = analyzer.stack[analyzer.stack.size - position] + check(arrayDesc is String) + val arrayType = getType(arrayDesc) + check(arrayType.sort == ARRAY) + check(arrayType.dimensions > 0) + return getType(arrayDesc.substring(1)) + } } \ No newline at end of file diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SnapshotTrackerTransformer.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SnapshotTrackerTransformer.kt deleted file mode 100644 index f39e0c7b3..000000000 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SnapshotTrackerTransformer.kt +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Lincheck - * - * Copyright (C) 2019 - 2024 JetBrains s.r.o. - * - * This Source Code Form is subject to the terms of the - * Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package org.jetbrains.kotlinx.lincheck.transformation.transformers - -import org.jetbrains.kotlinx.lincheck.canonicalClassName -import org.jetbrains.kotlinx.lincheck.transformation.* -import org.objectweb.asm.Opcodes.* -import org.objectweb.asm.Type -import org.objectweb.asm.Type.* -import org.objectweb.asm.commons.GeneratorAdapter -import sun.nio.ch.lincheck.Injections - -internal class SnapshotTrackerTransformer( - fileName: String, - className: String, - methodName: String, - adapter: GeneratorAdapter, - // `SafeClassWriter::isInstanceOf` method which checks the subclassing without loading the classes to VM - private val isInstanceOf: (actualType: String, expectedSuperType: String) -> Boolean -) : ManagedStrategyWithAnalyzerClassVisitor(fileName, className, methodName, adapter) { - - /** - * Searches for invocations of constructors `className::(args)` and inserts bytecode - * that will extract all objects from `args` which types are subtypes of the class `className`. - * Snapshot tracker then tracks the extracted objects energetically. - * - * This is a hack solution to the problem of impossibility of identifying whether - * some object on stack in the constructor is a `this` reference or not. - * So for instructions `GETFIELD`/`PUTFIELD` in constructors handler of `visitFieldInsn(...)` method below - * we ignore the accesses to fields which owners are the same type as the constructor owner. - * The optimization is done to avoid 'leaking this' problem during bytecode verification. - * But sometimes `GETFIELD`/`PUTFIELD` are invoked on fields with owner objects are the same type as a constructor owner, - * but which are not `this`. This can happen when: - * - a non-static object is accessed which was passed to constructor arguments - * - a non-static object is accessed which was created locally in constructor - * - * We only case about the 1nd case, because for the 3nd case when dealing with - * a locally constructed object it can't be reached from static memory, so no need to track its fields. - * The 2nd case where such object is passed via constructor argument is handled here. - */ - override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) = adapter.run { - if (name == "") { - val matchedArguments = getArgumentTypes(desc).toList() - .mapIndexed { index, type -> - if ( - !isArray(type) && - !isPrimitive(type) && - isInstanceOf(type.className.replace('.', '/'), owner) - ) index - else null - } - .filterNotNull() - .toIntArray() - - if (matchedArguments.isEmpty()) { - visitMethodInsn(opcode, owner, name, desc, itf) - return - } - - invokeIfInTestingCode( - original = { visitMethodInsn(opcode, owner, name, desc, itf) }, - code = { - // STACK: args - val arguments = storeArguments(desc) - val matchedLocals = arguments.filterIndexed { index, _ -> matchedArguments.contains(index) } - // STACK: - push(matchedLocals.size) - // STACK: length - visitTypeInsn(ANEWARRAY, "java/lang/Object") - // STACK: array - matchedLocals.forEachIndexed { index, local -> - // STACK: array - visitInsn(DUP) - push(index) - loadLocal(local) - // STACK: array, array, index, obj - visitInsn(AASTORE) - // STACK: array - } - // STACK: array - invokeStatic(Injections::updateSnapshotWithEnergeticTracking) - // STACK: - loadLocals(arguments) - // STACK: args - visitMethodInsn(opcode, owner, name, desc, itf) - } - ) - } - else visitMethodInsn(opcode, owner, name, desc, itf) - } - - override fun visitFieldInsn(opcode: Int, owner: String, fieldName: String, desc: String) = adapter.run { - if ( - isCoroutineInternalClass(owner) || - isCoroutineStateMachineClass(owner) || - // when initializing our own fields in constructor, we do not want to track that as snapshot modification - (methodName == "" && className == owner) - ) { - visitFieldInsn(opcode, owner, fieldName, desc) - return - } - - when (opcode) { - GETSTATIC, PUTSTATIC -> { - // STACK: [ | value] - invokeIfInTestingCode( - original = { - visitFieldInsn(opcode, owner, fieldName, desc) - }, - code = { - // STACK: [ | value] - pushNull() - push(owner.canonicalClassName) - push(fieldName) - loadNewCodeLocationId() - // STACK: [ | value], null, className, fieldName, codeLocation - invokeStatic(Injections::updateSnapshotOnFieldAccess) - // STACK: [ | value] - visitFieldInsn(opcode, owner, fieldName, desc) - // STACK: [ | value] - } - ) - } - - GETFIELD, PUTFIELD -> { - // STACK: obj, [value] - invokeIfInTestingCode( - original = { - visitFieldInsn(opcode, owner, fieldName, desc) - }, - code = { - val valueType = getType(desc) - val valueLocal = newLocal(valueType) // we cannot use DUP as long/double require DUP2 - - // STACK: obj, [value] - if (opcode == PUTFIELD) storeLocal(valueLocal) - // STACK: obj - dup() - // STACK: obj, obj - push(owner.canonicalClassName) - push(fieldName) - loadNewCodeLocationId() - // STACK: obj, obj, className, fieldName, codeLocation - invokeStatic(Injections::updateSnapshotOnFieldAccess) - // STACK: obj - if (opcode == PUTFIELD) loadLocal(valueLocal) - // STACK: obj, [value] - visitFieldInsn(opcode, owner, fieldName, desc) - // STACK: [ | value] - } - ) - } - - else -> { - visitFieldInsn(opcode, owner, fieldName, desc) - } - } - } - - override fun visitInsn(opcode: Int) = adapter.run { - when (opcode) { - AALOAD, LALOAD, FALOAD, DALOAD, IALOAD, BALOAD, CALOAD, SALOAD -> { - invokeIfInTestingCode( - original = { - visitInsn(opcode) - }, - code = { - // STACK: array, index - dup2() - // STACK: array, index, array, index - loadNewCodeLocationId() - // STACK: array, index, array, index, codeLocation - invokeStatic(Injections::updateSnapshotOnArrayElementAccess) - // STACK: array, index - visitInsn(opcode) - // STACK: value - } - ) - } - - AASTORE, IASTORE, FASTORE, BASTORE, CASTORE, SASTORE, LASTORE, DASTORE -> { - invokeIfInTestingCode( - original = { - visitInsn(opcode) - }, - code = { - val arrayElementType = getArrayElementType(opcode) - val valueLocal = newLocal(arrayElementType) // we cannot use DUP as long/double require DUP2 - - // STACK: array, index, value - storeLocal(valueLocal) - // STACK: array, index - dup2() - // STACK: array, index, array, index - loadNewCodeLocationId() - // STACK: array, index, array, index, codeLocation - invokeStatic(Injections::updateSnapshotOnArrayElementAccess) - // STACK: array, index - loadLocal(valueLocal) - // STACK: array, index, value - visitInsn(opcode) - // STACK: - } - ) - } - - else -> { - visitInsn(opcode) - } - } - } - - private fun isArray(type: Type): Boolean = type.sort == Type.ARRAY - - private fun isPrimitive(type: Type): Boolean { - val sort = type.sort - return sort == Type.BOOLEAN || - sort == Type.CHAR || - sort == Type.BYTE || - sort == Type.SHORT || - sort == Type.INT || - sort == Type.FLOAT || - sort == Type.LONG || - sort == Type.DOUBLE || - sort == Type.VOID - } -} \ No newline at end of file diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/ObjectGraph.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/ObjectGraph.kt index 47c58e2a7..e2313e72e 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/ObjectGraph.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/ObjectGraph.kt @@ -223,4 +223,4 @@ internal fun getAtomicArrayLength(arr: Any): Int { isAtomicFUArray(arr) -> arr.javaClass.getMethod("getSize").invoke(arr) as Int else -> error("Argument is not atomic array") } -} +} \ No newline at end of file diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/UnsafeHolder.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/UnsafeHolder.kt index 027790298..c45fb9ba1 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/UnsafeHolder.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/UnsafeHolder.kt @@ -10,7 +10,6 @@ package org.jetbrains.kotlinx.lincheck.util -import org.jetbrains.kotlinx.lincheck.getArrayElementOffset import sun.misc.Unsafe import java.lang.reflect.Field import java.lang.reflect.Modifier @@ -113,7 +112,7 @@ internal fun writeFieldViaUnsafe(obj: Any?, field: Field, value: Any?) { } internal fun writeArrayElementViaUnsafe(arr: Any, index: Int, value: Any?): Any? { - val offset = getArrayElementOffset(arr, index) + val offset = getArrayElementOffsetViaUnsafe(arr, index) val componentType = arr::class.java.componentType if (!componentType.isPrimitive) { @@ -131,4 +130,30 @@ internal fun writeArrayElementViaUnsafe(arr: Any, index: Int, value: Any?): Any? Float::class.javaPrimitiveType -> UnsafeHolder.UNSAFE.putFloat(arr, offset, value as Float) else -> error("No more primitive types expected") } +} + +@Suppress("DEPRECATION") +internal fun getFieldOffsetViaUnsafe(field: Field): Long { + return if (Modifier.isStatic(field.modifiers)) { + UnsafeHolder.UNSAFE.staticFieldOffset(field) + } + else { + UnsafeHolder.UNSAFE.objectFieldOffset(field) + } +} + +@Suppress("DEPRECATION") +internal fun findFieldNameByOffset(targetType: Class<*>, offset: Long): String? { + // Extract the private offset value and find the matching field. + for (field in targetType.declaredFields) { + try { + if (Modifier.isNative(field.modifiers)) continue + val fieldOffset = if (Modifier.isStatic(field.modifiers)) UnsafeHolder.UNSAFE.staticFieldOffset(field) + else UnsafeHolder.UNSAFE.objectFieldOffset(field) + if (fieldOffset == offset) return field.name + } catch (t: Throwable) { + t.printStackTrace() + } + } + return null // Field not found } \ No newline at end of file From 7f5e6ea75de822615d43ca2241e73d0ef012cf61 Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Mon, 2 Dec 2024 14:44:30 +0100 Subject: [PATCH 21/29] Add more fixes to the PR --- .../src/sun/nio/ch/lincheck/EventTracker.java | 2 +- .../src/sun/nio/ch/lincheck/Injections.java | 4 +-- .../org/jetbrains/kotlinx/lincheck/Utils.kt | 21 ++++++++----- .../strategy/managed/ManagedStrategy.kt | 4 +-- .../strategy/managed/SnapshotTracker.kt | 30 +++++++++++-------- ...ctorArgumentsSnapshotTrackerTransformer.kt | 21 +++---------- .../kotlinx/lincheck/util/ObjectGraph.kt | 7 +---- .../snapshot/AtomicFUSnapshotTest.kt | 15 ++++++---- 8 files changed, 51 insertions(+), 53 deletions(-) diff --git a/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java b/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java index 841ca8ce0..b46fdd9f1 100644 --- a/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java +++ b/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java @@ -32,7 +32,7 @@ public interface EventTracker { void beforeNewObjectCreation(String className); void afterNewObjectCreation(Object obj); - void updateSnapshotWithEnergeticTracking(Object[] objs); + void updateSnapshotWithEagerTracking(Object[] objs); boolean beforeReadField(Object obj, String className, String fieldName, int codeLocation, boolean isStatic, boolean isFinal); boolean beforeReadArrayElement(Object array, int index, int codeLocation); diff --git a/bootstrap/src/sun/nio/ch/lincheck/Injections.java b/bootstrap/src/sun/nio/ch/lincheck/Injections.java index f69d5e629..f48e705bc 100644 --- a/bootstrap/src/sun/nio/ch/lincheck/Injections.java +++ b/bootstrap/src/sun/nio/ch/lincheck/Injections.java @@ -297,8 +297,8 @@ public static void afterNewObjectCreation(Object obj) { /** * Called from instrumented code on constructor invocation, where passed objects are subtypes of the constructor class type. */ - public static void updateSnapshotWithEnergeticTracking(Object[] objs) { - getEventTracker().updateSnapshotWithEnergeticTracking(objs); + public static void updateSnapshotWithEagerTracking(Object[] objs) { + getEventTracker().updateSnapshotWithEagerTracking(objs); } /** diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt index 26c90257e..0a4093825 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt @@ -13,9 +13,7 @@ import kotlinx.coroutines.* import org.jetbrains.kotlinx.lincheck.runner.* import org.jetbrains.kotlinx.lincheck.strategy.managed.* import org.jetbrains.kotlinx.lincheck.transformation.LincheckClassFileTransformer -import org.jetbrains.kotlinx.lincheck.util.UnsafeHolder -import org.jetbrains.kotlinx.lincheck.util.isAtomicArray -import org.jetbrains.kotlinx.lincheck.util.isAtomicFUArray +import org.jetbrains.kotlinx.lincheck.util.readFieldViaUnsafe import org.jetbrains.kotlinx.lincheck.verifier.* import sun.nio.ch.lincheck.* import java.io.PrintWriter @@ -24,9 +22,6 @@ import java.lang.ref.* import java.lang.reflect.* import java.lang.reflect.Method import java.util.* -import java.util.concurrent.atomic.AtomicIntegerArray -import java.util.concurrent.atomic.AtomicLongArray -import java.util.concurrent.atomic.AtomicReferenceArray import kotlin.coroutines.* import kotlin.coroutines.intrinsics.* @@ -239,7 +234,7 @@ internal val Class<*>.allDeclaredFieldWithSuperclasses get(): List { * @param fieldName the name of the field to find. * @return the [java.lang.reflect.Field] object if found, or `null` if not found. */ -fun Class<*>.findField(fieldName: String): Field { +internal fun Class<*>.findField(fieldName: String): Field { // Search in the class hierarchy var clazz: Class<*>? = this while (clazz != null) { @@ -264,6 +259,18 @@ fun Class<*>.findField(fieldName: String): Field { throw NoSuchFieldException("Class '${this.name}' does not have field '$fieldName'") } +/** + * Reads a [field] of the owner object [obj] via Unsafe, and falls back onto the reflexivity in case of failure. + */ +internal fun readFieldSafely(obj: Any?, field: Field): kotlin.Result { + // we wrap an unsafe read into `runCatching` to handle `UnsupportedOperationException`, + // which can be thrown, for instance, when attempting to read + // a field of a hidden or record class (starting from Java 15); + // in this case we fall back to read via reflection + return runCatching { readFieldViaUnsafe(obj, field) } + .recoverCatching { field.apply { isAccessible = true }.get(obj) } +} + /** * Thrown in case when `cause` exception is unexpected by Lincheck internal logic. */ diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt index 5f7036804..1fb6f9984 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt @@ -945,10 +945,10 @@ abstract class ManagedStrategy( } /** - * Tracks all objects in [objs] energetically. + * Tracks all objects in [objs] eagerly. * Required as a trick to overcome issue with leaking this in constructors, see https://github.com/JetBrains/lincheck/issues/424. */ - override fun updateSnapshotWithEnergeticTracking(objs: Array) = runInIgnoredSection { + override fun updateSnapshotWithEagerTracking(objs: Array) = runInIgnoredSection { staticMemorySnapshot.trackObjects(objs) } diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt index 8ab575fc3..27d661b04 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt @@ -12,8 +12,10 @@ package org.jetbrains.kotlinx.lincheck.strategy.managed import org.jetbrains.kotlinx.lincheck.findField import org.jetbrains.kotlinx.lincheck.isPrimitiveWrapper +import org.jetbrains.kotlinx.lincheck.readFieldSafely import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.MemoryNode.* import org.jetbrains.kotlinx.lincheck.util.* +import java.lang.Class import java.lang.reflect.Field import java.lang.reflect.Modifier import java.util.Collections @@ -46,18 +48,15 @@ class SnapshotTracker { fun trackField(obj: Any?, className: String, fieldName: String) { if (obj != null && obj !in trackedObjects) return - val clazz: Class<*> = Class.forName(className).let { - if (obj != null) it - else it.findField(fieldName).declaringClass - } + val clazz = getDeclaringClass(obj, className, fieldName) val field = clazz.findField(fieldName) - val readResult = runCatching { readFieldViaUnsafe(obj, field) } + val readResult = readFieldSafely(obj, field) if (readResult.isSuccess) { val fieldValue = readResult.getOrNull() trackSingleField(obj, clazz, field, fieldValue) { - if (shouldTrackEnergetically(fieldValue)) { - trackHierarchy(fieldValue!!) + if (shouldTrackEagerly(fieldValue)) { + trackReachableObjectSubgraph(fieldValue!!) } } } @@ -71,8 +70,8 @@ class SnapshotTracker { if (readResult.isSuccess) { val elementValue = readResult.getOrNull() trackSingleArrayCell(array, index, elementValue) { - if (shouldTrackEnergetically(elementValue)) { - trackHierarchy(elementValue!!) + if (shouldTrackEagerly(elementValue)) { + trackReachableObjectSubgraph(elementValue!!) } } } @@ -83,7 +82,7 @@ class SnapshotTracker { // see https://github.com/JetBrains/lincheck/pull/418/commits/0d708b84dd2bfd5dbfa961549dda128d91dc3a5b#diff-a684b1d7deeda94bbf907418b743ae2c0ec0a129760d3b87d00cdf5adfab56c4R146-R199 objs .filter { it != null && it in trackedObjects } - .forEach { trackHierarchy(it!!) } + .forEach { trackReachableObjectSubgraph(it!!) } } fun restoreValues() { @@ -137,7 +136,7 @@ class SnapshotTracker { } } - private fun trackHierarchy(obj: Any) { + private fun trackReachableObjectSubgraph(obj: Any) { traverseObjectGraph( obj, onArrayElement = { array, index, elementValue -> @@ -157,7 +156,7 @@ class SnapshotTracker { ) } - private fun shouldTrackEnergetically(obj: Any?): Boolean { + private fun shouldTrackEagerly(obj: Any?): Boolean { if (obj == null) return false return ( // TODO: in further development of snapshot restoring feature this check should be removed @@ -209,6 +208,13 @@ class SnapshotTracker { ) } + private fun getDeclaringClass(obj: Any?, className: String, fieldName: String): Class<*> { + return Class.forName(className).let { + if (obj != null) it + else it.findField(fieldName).declaringClass + } + } + private fun createFieldNode(obj: Any?, field: Field, value: Any?): MemoryNode { return if (obj == null) StaticFieldNode(field, value) else RegularFieldNode(field, value) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/ConstructorArgumentsSnapshotTrackerTransformer.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/ConstructorArgumentsSnapshotTrackerTransformer.kt index 88b8281c4..ec5ce6608 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/ConstructorArgumentsSnapshotTrackerTransformer.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/ConstructorArgumentsSnapshotTrackerTransformer.kt @@ -12,7 +12,6 @@ package org.jetbrains.kotlinx.lincheck.transformation.transformers import org.jetbrains.kotlinx.lincheck.transformation.* import org.objectweb.asm.Opcodes.* -import org.objectweb.asm.Type import org.objectweb.asm.Type.* import org.objectweb.asm.commons.GeneratorAdapter import sun.nio.ch.lincheck.Injections @@ -29,7 +28,7 @@ internal class ConstructorArgumentsSnapshotTrackerTransformer( /** * Searches for invocations of constructors `className::(args)` and inserts bytecode * that will extract all objects from `args` which types are subtypes of the class `className`. - * Snapshot tracker then tracks the extracted objects energetically. + * Snapshot tracker then tracks the extracted objects eagerly. * * This is a hack solution to the problem of impossibility of identifying whether * some object on stack in the constructor is a `this` reference or not. @@ -69,23 +68,11 @@ internal class ConstructorArgumentsSnapshotTrackerTransformer( code = { // STACK: args val arguments = storeArguments(desc) - val matchedLocals = arguments.filterIndexed { index, _ -> matchedArguments.contains(index) } + val matchedLocals = arguments.filterIndexed { index, _ -> matchedArguments.contains(index) }.toIntArray() // STACK: - push(matchedLocals.size) - // STACK: length - visitTypeInsn(ANEWARRAY, "java/lang/Object") + pushArray(matchedLocals) // STACK: array - matchedLocals.forEachIndexed { index, local -> - // STACK: array - visitInsn(DUP) - push(index) - loadLocal(local) - // STACK: array, array, index, obj - visitInsn(AASTORE) - // STACK: array - } - // STACK: array - invokeStatic(Injections::updateSnapshotWithEnergeticTracking) + invokeStatic(Injections::updateSnapshotWithEagerTracking) // STACK: loadLocals(arguments) // STACK: args diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/ObjectGraph.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/ObjectGraph.kt index e2313e72e..21dc9de28 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/ObjectGraph.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/ObjectGraph.kt @@ -165,12 +165,7 @@ internal inline fun traverseObjectFields( ) { obj.javaClass.allDeclaredFieldWithSuperclasses.forEach { field -> if (!traverseStaticFields && Modifier.isStatic(field.modifiers)) return@forEach - // we wrap an unsafe read into `runCatching` to handle `UnsupportedOperationException`, - // which can be thrown, for instance, when attempting to read - // a field of a hidden or record class (starting from Java 15); - // in this case we fall back to read via reflection - val result = runCatching { readFieldViaUnsafe(obj, field) } - .recoverCatching { field.apply { isAccessible = true }.get(obj) } + val result = readFieldSafely(obj, field) // do not pass non-readable fields if (result.isSuccess) { val fieldValue = result.getOrNull() diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/AtomicFUSnapshotTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/AtomicFUSnapshotTest.kt index 38f02e631..67b1bd49a 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/AtomicFUSnapshotTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/AtomicFUSnapshotTest.kt @@ -10,6 +10,9 @@ package org.jetbrains.kotlinx.lincheck_test.strategy.modelchecking.snapshot +import kotlinx.atomicfu.AtomicIntArray +import kotlinx.atomicfu.atomic +import kotlinx.atomicfu.atomicArrayOfNulls import org.jetbrains.kotlinx.lincheck.annotations.Operation import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario @@ -22,15 +25,15 @@ class AtomicFUSnapshotTest : AbstractSnapshotTest() { // TODO: atomicfu classes like AtomicInt, AtomicRef are compiled to pure field + java atomic field updater. // Because of this, methods, such as `AtomicInt::getAndIncrement()`, will not be tracked as modification of `value` field. - // Tracking of atomicfu classes should be implemented energetically, the same way as for java atomics + // Tracking of atomicfu classes should be implemented eagerly, the same way as for java atomics // in order to handle modification that do not reference `value` field directly. - // If energetic traversal does not help/not possible to reach `value` field, then such indirect methods should be handled separately, + // If eager traversal does not help/not possible to reach `value` field, then such indirect methods should be handled separately, // the same way as reflexivity, var-handles, and unsafe by tracking the creation of java afu and retrieving the name of the associated field. - private val atomicFUInt = kotlinx.atomicfu.atomic(1) - private val atomicFURef = kotlinx.atomicfu.atomic(Wrapper(1)) + private val atomicFUInt = atomic(1) + private val atomicFURef = atomic(Wrapper(1)) - private val atomicFUIntArray = kotlinx.atomicfu.AtomicIntArray(3) - private val atomicFURefArray = kotlinx.atomicfu.atomicArrayOfNulls(3) + private val atomicFUIntArray = AtomicIntArray(3) + private val atomicFURefArray = atomicArrayOfNulls(3) init { for (i in 0..atomicFUIntArray.size - 1) { From a15f64533c24265e2ec8be6d59b2b973c9a53c13 Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Mon, 2 Dec 2024 15:03:22 +0100 Subject: [PATCH 22/29] Simplify and refactor code in SnapshotTracker --- .../strategy/managed/SnapshotTracker.kt | 55 +++++++++++++------ 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt index 27d661b04..44f6c3773 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt @@ -54,10 +54,11 @@ class SnapshotTracker { if (readResult.isSuccess) { val fieldValue = readResult.getOrNull() - trackSingleField(obj, clazz, field, fieldValue) { - if (shouldTrackEagerly(fieldValue)) { - trackReachableObjectSubgraph(fieldValue!!) - } + if ( + trackSingleField(obj, clazz, field, fieldValue) && + shouldTrackEagerly(fieldValue) + ) { + trackReachableObjectSubgraph(fieldValue!!) } } } @@ -69,10 +70,11 @@ class SnapshotTracker { if (readResult.isSuccess) { val elementValue = readResult.getOrNull() - trackSingleArrayCell(array, index, elementValue) { - if (shouldTrackEagerly(elementValue)) { - trackReachableObjectSubgraph(elementValue!!) - } + if ( + trackSingleArrayCell(array, index, elementValue) && + shouldTrackEagerly(elementValue) + ) { + trackReachableObjectSubgraph(elementValue!!) } } } @@ -92,13 +94,16 @@ class SnapshotTracker { .forEach { restoreValues(it, visitedObjects) } } + /** + * @return `true` if the [fieldValue] is a trackable object, and it is added + * as a parent object for its own fields for further lazy tracking. + */ private fun trackSingleField( obj: Any?, clazz: Class<*>, field: Field, - fieldValue: Any?, - callback: (() -> Unit)? = null - ) { + fieldValue: Any? + ): Boolean { val nodesList = if (obj != null) trackedObjects[obj] else trackedObjects.getOrPut(clazz) { mutableListOf() } @@ -108,32 +113,46 @@ class SnapshotTracker { nodesList .filterIsInstance() .any { it.field.name == field.name } // field is already tracked - ) return + ) return false val childNode = createFieldNode(obj, field, fieldValue) nodesList.add(childNode) - if (isTrackableObject(childNode.initialValue)) { + val isFieldValueTrackable = isTrackableObject(childNode.initialValue) + + if (isFieldValueTrackable) { trackedObjects.putIfAbsent(childNode.initialValue, mutableListOf()) - callback?.invoke() } + + return isFieldValueTrackable } - private fun trackSingleArrayCell(array: Any, index: Int, elementValue: Any?, callback: (() -> Unit)? = null) { + /** + * @return `true` if the [elementValue] is a trackable object, and it is added + * as a parent object for its own fields for further lazy tracking. + */ + private fun trackSingleArrayCell( + array: Any, + index: Int, + elementValue: Any? + ): Boolean { val nodesList = trackedObjects[array] if ( nodesList == null || // array is not tracked nodesList.any { it is ArrayCellNode && it.index == index } // this array cell is already tracked - ) return + ) return false val childNode = createArrayCellNode(index, elementValue) nodesList.add(childNode) - if (isTrackableObject(childNode.initialValue)) { + val isElementValueTrackable = isTrackableObject(childNode.initialValue) + + if (isElementValueTrackable) { trackedObjects.putIfAbsent(childNode.initialValue, mutableListOf()) - callback?.invoke() } + + return isElementValueTrackable } private fun trackReachableObjectSubgraph(obj: Any) { From 28b153b85644440dc8c31e0efed74532b4728073 Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Mon, 2 Dec 2024 15:10:23 +0100 Subject: [PATCH 23/29] Rewrite values restoring in a non-recursion way using stack --- .../strategy/managed/SnapshotTracker.kt | 66 +++++++++++-------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt index 44f6c3773..258d2b967 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/SnapshotTracker.kt @@ -20,6 +20,7 @@ import java.lang.reflect.Field import java.lang.reflect.Modifier import java.util.Collections import java.util.IdentityHashMap +import java.util.Stack import java.util.concurrent.atomic.AtomicIntegerArray import java.util.concurrent.atomic.AtomicLongArray import java.util.concurrent.atomic.AtomicReferenceArray @@ -185,37 +186,46 @@ class SnapshotTracker { ) } - private fun restoreValues(obj: Any, visitedObjects: MutableSet) { - if (obj in visitedObjects) return - visitedObjects.add(obj) - - trackedObjects[obj]!! - .forEach { node -> - if (node is ArrayCellNode) { - val index = node.index - val initialValue = node.initialValue - - when (obj) { - // No need to add support for writing to atomicfu array elements, - // because atomicfu arrays are compiled to atomic java arrays - is AtomicReferenceArray<*> -> @Suppress("UNCHECKED_CAST") (obj as AtomicReferenceArray).set(index, initialValue) - is AtomicIntegerArray -> obj.set(index, initialValue as Int) - is AtomicLongArray -> obj.set(index, initialValue as Long) - else -> writeArrayElementViaUnsafe(obj, index, initialValue) + private fun restoreValues(root: Any, visitedObjects: MutableSet) { + val stackOfObjects: Stack = Stack() + stackOfObjects.push(root) + + while (stackOfObjects.isNotEmpty()) { + val obj = stackOfObjects.pop() + if (obj in visitedObjects) continue + visitedObjects.add(obj) + + trackedObjects[obj]!! + .forEach { node -> + if (node is ArrayCellNode) { + val index = node.index + val initialValue = node.initialValue + + when (obj) { + // No need to add support for writing to atomicfu array elements, + // because atomicfu arrays are compiled to atomic java arrays + is AtomicReferenceArray<*> -> @Suppress("UNCHECKED_CAST") (obj as AtomicReferenceArray).set( + index, + initialValue + ) + + is AtomicIntegerArray -> obj.set(index, initialValue as Int) + is AtomicLongArray -> obj.set(index, initialValue as Long) + else -> writeArrayElementViaUnsafe(obj, index, initialValue) + } + } else if (!Modifier.isFinal((node as FieldNode).field.modifiers)) { + writeFieldViaUnsafe( + if (node is StaticFieldNode) null else obj, + node.field, + node.initialValue + ) } - } - else if (!Modifier.isFinal((node as FieldNode).field.modifiers)) { - writeFieldViaUnsafe( - if (node is StaticFieldNode) null else obj, - node.field, - node.initialValue - ) - } - if (isTrackableObject(node.initialValue)) { - restoreValues(node.initialValue!!, visitedObjects) + if (isTrackableObject(node.initialValue)) { + stackOfObjects.push(node.initialValue!!) + } } - } + } } private fun isTrackableObject(value: Any?): Boolean { From af39fd75fa40c483172edf226bc474e7ccf9a10c Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Mon, 2 Dec 2024 17:15:25 +0100 Subject: [PATCH 24/29] Add fixes to the PR --- bootstrap/src/sun/nio/ch/lincheck/EventTracker.java | 1 + src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt | 2 +- .../lincheck/strategy/managed/AtomicFieldUpdaterNames.kt | 4 ++-- .../strategy/managed/ManagedCTestConfiguration.kt | 1 - .../kotlinx/lincheck/strategy/managed/ManagedOptions.kt | 9 --------- .../kotlinx/lincheck/strategy/managed/UnsafeNames.kt | 6 +++--- .../kotlinx/lincheck/strategy/managed/VarHandleNames.kt | 6 +++--- .../transformers/SharedMemoryAccessTransformer.kt | 3 ++- .../org/jetbrains/kotlinx/lincheck/util/ObjectGraph.kt | 7 +++++-- .../org/jetbrains/kotlinx/lincheck/util/UnsafeHolder.kt | 2 +- .../modelchecking/snapshot/AbstractSnapshotTest.kt | 1 - .../modelchecking/snapshot/CollectionSnapshotTest.kt | 1 - 12 files changed, 18 insertions(+), 25 deletions(-) diff --git a/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java b/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java index b46fdd9f1..f6663a1f3 100644 --- a/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java +++ b/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java @@ -33,6 +33,7 @@ public interface EventTracker { void afterNewObjectCreation(Object obj); void updateSnapshotWithEagerTracking(Object[] objs); + boolean beforeReadField(Object obj, String className, String fieldName, int codeLocation, boolean isStatic, boolean isFinal); boolean beforeReadArrayElement(Object array, int index, int codeLocation); diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt index 0a4093825..e18c13a4a 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt @@ -260,7 +260,7 @@ internal fun Class<*>.findField(fieldName: String): Field { } /** - * Reads a [field] of the owner object [obj] via Unsafe, and falls back onto the reflexivity in case of failure. + * Reads a [field] of the owner object [obj] via Unsafe, in case of failure fallbacks into reading the field via reflection. */ internal fun readFieldSafely(obj: Any?, field: Field): kotlin.Result { // we wrap an unsafe read into `runCatching` to handle `UnsupportedOperationException`, diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/AtomicFieldUpdaterNames.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/AtomicFieldUpdaterNames.kt index a1ef39392..04c2d5b76 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/AtomicFieldUpdaterNames.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/AtomicFieldUpdaterNames.kt @@ -11,7 +11,7 @@ package org.jetbrains.kotlinx.lincheck.strategy.managed import org.jetbrains.kotlinx.lincheck.util.UnsafeHolder.UNSAFE -import org.jetbrains.kotlinx.lincheck.util.findFieldNameByOffset +import org.jetbrains.kotlinx.lincheck.util.findFieldNameByOffsetViaUnsafe import java.util.concurrent.atomic.AtomicIntegerFieldUpdater import java.util.concurrent.atomic.AtomicLongFieldUpdater import java.util.concurrent.atomic.AtomicReferenceFieldUpdater @@ -37,7 +37,7 @@ internal object AtomicFieldUpdaterNames { val offsetField = updater.javaClass.getDeclaredField("offset") val offset = UNSAFE.getLong(updater, UNSAFE.objectFieldOffset(offsetField)) - return findFieldNameByOffset(targetType, offset) + return findFieldNameByOffsetViaUnsafe(targetType, offset) } catch (t: Throwable) { t.printStackTrace() } diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedCTestConfiguration.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedCTestConfiguration.kt index 7699ea0d9..788e8120d 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedCTestConfiguration.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedCTestConfiguration.kt @@ -52,7 +52,6 @@ abstract class ManagedCTestConfiguration( const val DEFAULT_CHECK_OBSTRUCTION_FREEDOM = false const val DEFAULT_HANGING_DETECTION_THRESHOLD = 101 const val LIVELOCK_EVENTS_THRESHOLD = 10001 - const val DEFAULT_RESTORE_STATIC_MEMORY = true val DEFAULT_GUARANTEES = listOf() } } diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedOptions.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedOptions.kt index 46fb55193..555b2901b 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedOptions.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedOptions.kt @@ -14,7 +14,6 @@ import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedCTestConfiguration import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedCTestConfiguration.Companion.DEFAULT_GUARANTEES import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedCTestConfiguration.Companion.DEFAULT_HANGING_DETECTION_THRESHOLD import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedCTestConfiguration.Companion.DEFAULT_INVOCATIONS -import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedCTestConfiguration.Companion.DEFAULT_RESTORE_STATIC_MEMORY import java.util.* /** @@ -24,7 +23,6 @@ abstract class ManagedOptions, CTEST : CTestConfigurat protected var invocationsPerIteration = DEFAULT_INVOCATIONS protected var checkObstructionFreedom = DEFAULT_CHECK_OBSTRUCTION_FREEDOM protected var hangingDetectionThreshold = DEFAULT_HANGING_DETECTION_THRESHOLD - protected var restoreStaticMemory = DEFAULT_RESTORE_STATIC_MEMORY protected val guarantees: MutableList = ArrayList(DEFAULT_GUARANTEES) /** @@ -52,13 +50,6 @@ abstract class ManagedOptions, CTEST : CTestConfigurat this.hangingDetectionThreshold = hangingDetectionThreshold } - /** - * Set to `true` to activate the static memory snapshot tracking and restoring algorithm. - */ - fun restoreStaticMemory(restoreStaticMemory: Boolean): OPT = applyAndCast { - this.restoreStaticMemory = restoreStaticMemory - } - /** * Add a guarantee that methods in some classes are either correct in terms of concurrent execution or irrelevant. * These guarantees can be used for optimization. For example, we can add a guarantee that all the methods diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/UnsafeNames.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/UnsafeNames.kt index da1bd9fb6..0ae61d1cc 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/UnsafeNames.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/UnsafeNames.kt @@ -12,7 +12,7 @@ package org.jetbrains.kotlinx.lincheck.strategy.managed import org.jetbrains.kotlinx.lincheck.strategy.managed.UnsafeName.* import org.jetbrains.kotlinx.lincheck.util.UnsafeHolder -import org.jetbrains.kotlinx.lincheck.util.findFieldNameByOffset +import org.jetbrains.kotlinx.lincheck.util.findFieldNameByOffsetViaUnsafe /** * Helper object to provide the field name and the owner of the Unsafe method call. @@ -29,7 +29,7 @@ internal object UnsafeNames { if (secondParameter is Long) { return if (firstParameter is Class<*>) { // The First parameter is a Class object in case of static field access. - val fieldName = findFieldNameByOffset(firstParameter, secondParameter) + val fieldName = findFieldNameByOffsetViaUnsafe(firstParameter, secondParameter) ?: return TreatAsDefaultMethod UnsafeStaticMethod(firstParameter, fieldName, parameters.drop(2)) } else if (firstParameter != null && firstParameter::class.java.isArray) { @@ -46,7 +46,7 @@ internal object UnsafeNames { ) } else if (firstParameter != null) { // Then is an instance method call. - val fieldName = findFieldNameByOffset(firstParameter::class.java, secondParameter) + val fieldName = findFieldNameByOffsetViaUnsafe(firstParameter::class.java, secondParameter) ?: return TreatAsDefaultMethod UnsafeInstanceMethod( owner = firstParameter, diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/VarHandleNames.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/VarHandleNames.kt index fd45e3bd4..16e1a81a8 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/VarHandleNames.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/VarHandleNames.kt @@ -11,7 +11,7 @@ package org.jetbrains.kotlinx.lincheck.strategy.managed import org.jetbrains.kotlinx.lincheck.strategy.managed.VarHandleMethodType.* -import org.jetbrains.kotlinx.lincheck.util.findFieldNameByOffset +import org.jetbrains.kotlinx.lincheck.util.findFieldNameByOffsetViaUnsafe import org.jetbrains.kotlinx.lincheck.util.readFieldViaUnsafe import sun.misc.Unsafe import java.lang.reflect.Field @@ -102,7 +102,7 @@ internal object VarHandleNames { override fun getMethodType(varHandle: Any, parameters: Array): VarHandleMethodType { val ownerType = readFieldViaUnsafe(varHandle, receiverTypeField, Unsafe::getObject) as Class<*> val fieldOffset = readFieldViaUnsafe(varHandle, fieldOffsetField, Unsafe::getLong) - val fieldName = findFieldNameByOffset(ownerType, fieldOffset) ?: return TreatAsDefaultMethod + val fieldName = findFieldNameByOffsetViaUnsafe(ownerType, fieldOffset) ?: return TreatAsDefaultMethod val firstParameter = parameters.firstOrNull() ?: return TreatAsDefaultMethod if (!ownerType.isInstance(firstParameter)) return TreatAsDefaultMethod @@ -124,7 +124,7 @@ internal object VarHandleNames { val ownerType = readFieldViaUnsafe(varHandle, receiverTypeField, Unsafe::getObject) as Class<*> val fieldOffset = readFieldViaUnsafe(varHandle, fieldOffsetField, Unsafe::getLong) - val fieldName = findFieldNameByOffset(ownerType, fieldOffset) ?: return TreatAsDefaultMethod + val fieldName = findFieldNameByOffsetViaUnsafe(ownerType, fieldOffset) ?: return TreatAsDefaultMethod return StaticVarHandleMethod(ownerType, fieldName, parameters.toList()) } diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SharedMemoryAccessTransformer.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SharedMemoryAccessTransformer.kt index b39f32176..4a42c0921 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SharedMemoryAccessTransformer.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SharedMemoryAccessTransformer.kt @@ -36,7 +36,8 @@ internal class SharedMemoryAccessTransformer( if ( isCoroutineInternalClass(owner) || isCoroutineStateMachineClass(owner) || - // when initializing our own fields in constructor, we do not want to track that + // when initializing our own fields in constructor, we do not want to track that, + // otherwise `VerifyError` will be thrown, see https://github.com/JetBrains/lincheck/issues/424 (methodName == "" && className == owner) ) { visitFieldInsn(opcode, owner, fieldName, desc) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/ObjectGraph.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/ObjectGraph.kt index 21dc9de28..e31286643 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/ObjectGraph.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/ObjectGraph.kt @@ -16,6 +16,9 @@ import java.lang.reflect.* import java.util.* +private typealias FieldCallback = (obj: Any, field: Field, value: Any?) -> Any? +private typealias ArrayElementCallback = (array: Any, index: Int, element: Any?) -> Any? + /** * Traverses a subgraph of objects reachable from a given root object in BFS order. * @@ -44,8 +47,8 @@ import java.util.* internal fun traverseObjectGraph( root: Any, traverseStaticFields: Boolean = false, - onField: ((obj: Any, field: Field, value: Any?) -> Any?)?, - onArrayElement: ((array: Any, index: Int, element: Any?) -> Any?)?, + onField: FieldCallback?, + onArrayElement: ArrayElementCallback?, ) { val queue = ArrayDeque() val visitedObjects = Collections.newSetFromMap(IdentityHashMap()) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/UnsafeHolder.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/UnsafeHolder.kt index c45fb9ba1..f891c0c5b 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/UnsafeHolder.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/UnsafeHolder.kt @@ -143,7 +143,7 @@ internal fun getFieldOffsetViaUnsafe(field: Field): Long { } @Suppress("DEPRECATION") -internal fun findFieldNameByOffset(targetType: Class<*>, offset: Long): String? { +internal fun findFieldNameByOffsetViaUnsafe(targetType: Class<*>, offset: Long): String? { // Extract the private offset value and find the matching field. for (field in targetType.declaredFields) { try { diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/AbstractSnapshotTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/AbstractSnapshotTest.kt index 2bdf2297a..28e87646d 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/AbstractSnapshotTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/AbstractSnapshotTest.kt @@ -40,7 +40,6 @@ abstract class AbstractSnapshotTest { .actorsBefore(0) .actorsAfter(0) .actorsPerThread(2) - .restoreStaticMemory(true) .apply { customize() } .check(this::class) } \ No newline at end of file diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/CollectionSnapshotTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/CollectionSnapshotTest.kt index eac2a72d6..669358aee 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/CollectionSnapshotTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/strategy/modelchecking/snapshot/CollectionSnapshotTest.kt @@ -32,7 +32,6 @@ abstract class CollectionSnapshotTest : AbstractSnapshotTest() { .invocationsPerIteration(1) .threads(1) .actorsPerThread(10) - .restoreStaticMemory(true) .apply { customize() } .check(this::class) From d8bf525d06d7e04c3019bf7dee1c23bdfa8241c6 Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Mon, 2 Dec 2024 18:01:11 +0100 Subject: [PATCH 25/29] Add fixes --- .../kotlinx/lincheck/strategy/Strategy.kt | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt index 8f210daf2..35ee68b9d 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt @@ -108,14 +108,15 @@ fun Strategy.runIteration(invocations: Int, verifier: Verifier): LincheckFailure if (!nextInvocation()) break val result = runInvocation() + try { failure = verify(result, verifier) + } finally { + // verifier calls `@Operation`s of the class under test which can + // modify the static memory; thus, we need to restore initial values + restoreStaticMemorySnapshot() } - finally { - if (this is ManagedStrategy) { - restoreStaticMemorySnapshot() - } - } + if (failure != null) break } @@ -123,6 +124,15 @@ fun Strategy.runIteration(invocations: Int, verifier: Verifier): LincheckFailure return failure } +/** + * Performs static snapshot restoring if the caller is a [ManagedStrategy]. + */ +private fun Strategy.restoreStaticMemorySnapshot() { + if (this is ManagedStrategy) { + restoreStaticMemorySnapshot() + } +} + /** * Verifies the results of the given invocation. * Attempts to collect the trace in case of incorrect results. From 3097c43b52aba58225f3b8db6190e68eab078019 Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Wed, 11 Dec 2024 15:46:11 +0100 Subject: [PATCH 26/29] Add PR fixes --- .../src/sun/nio/ch/lincheck/Injections.java | 6 ++-- gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 0 bytes gradle/wrapper/gradle-wrapper.properties | 15 -------- .../kotlinx/lincheck/strategy/Strategy.kt | 33 +++++++----------- .../strategy/managed/ManagedStrategy.kt | 8 ++--- ...ctorArgumentsSnapshotTrackerTransformer.kt | 10 ++++-- .../SharedMemoryAccessTransformer.kt | 3 +- 7 files changed, 27 insertions(+), 48 deletions(-) delete mode 100644 gradle/wrapper/gradle-wrapper.jar delete mode 100644 gradle/wrapper/gradle-wrapper.properties diff --git a/bootstrap/src/sun/nio/ch/lincheck/Injections.java b/bootstrap/src/sun/nio/ch/lincheck/Injections.java index f48e705bc..4bad46c1c 100644 --- a/bootstrap/src/sun/nio/ch/lincheck/Injections.java +++ b/bootstrap/src/sun/nio/ch/lincheck/Injections.java @@ -295,9 +295,11 @@ public static void afterNewObjectCreation(Object obj) { } /** - * Called from instrumented code on constructor invocation, where passed objects are subtypes of the constructor class type. + * Called from instrumented code before constructors' invocations, + * where passed objects are subtypes of the constructor class type. + * Required to update the static memory snapshot. */ - public static void updateSnapshotWithEagerTracking(Object[] objs) { + public static void updateSnapshotBeforeConstructorCall(Object[] objs) { getEventTracker().updateSnapshotWithEagerTracking(objs); } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index d64cd4917707c1f8861d8cb53dd15194d4248596..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43462 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 0bde95023..000000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,15 +0,0 @@ -# -# Lincheck -# -# Copyright (C) 2019 - 2023 JetBrains s.r.o. -# -# This Source Code Form is subject to the terms of the -# Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed -# with this file, You can obtain one at http://mozilla.org/MPL/2.0/. -# - -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt index 35ee68b9d..c8540bf00 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt @@ -105,34 +105,25 @@ fun Strategy.runIteration(invocations: Int, verifier: Verifier): LincheckFailure var failure: LincheckFailure? = null for (invocation in 0 until invocations) { - if (!nextInvocation()) - break + if (!nextInvocation()) break val result = runInvocation() - try { - failure = verify(result, verifier) - } finally { - // verifier calls `@Operation`s of the class under test which can - // modify the static memory; thus, we need to restore initial values - restoreStaticMemorySnapshot() - } - - if (failure != null) - break + failure = + try { + verify(result, verifier) + } finally { + // verifier calls `@Operation`s of the class under test which can + // modify the static memory; thus, we need to restore initial values + if (this is ManagedStrategy) { + restoreStaticMemorySnapshot() + } + } + if (failure != null) break } return failure } -/** - * Performs static snapshot restoring if the caller is a [ManagedStrategy]. - */ -private fun Strategy.restoreStaticMemorySnapshot() { - if (this is ManagedStrategy) { - restoreStaticMemorySnapshot() - } -} - /** * Verifies the results of the given invocation. * Attempts to collect the trace in case of incorrect results. diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt index 1fb6f9984..3a9ec6377 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt @@ -232,11 +232,9 @@ abstract class ManagedStrategy( override fun runInvocation(): InvocationResult { while (true) { initializeInvocation() - val result: InvocationResult - try { - result = runner.run() - } - finally { + val result: InvocationResult = try { + runner.run() + } finally { restoreStaticMemorySnapshot() } // In case the runner detects a deadlock, some threads can still manipulate the current strategy, diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/ConstructorArgumentsSnapshotTrackerTransformer.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/ConstructorArgumentsSnapshotTrackerTransformer.kt index ec5ce6608..f4d96f172 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/ConstructorArgumentsSnapshotTrackerTransformer.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/ConstructorArgumentsSnapshotTrackerTransformer.kt @@ -52,8 +52,12 @@ internal class ConstructorArgumentsSnapshotTrackerTransformer( !isArray(type) && !isPrimitive(type) && isInstanceOf(type.className.replace('.', '/'), owner) - ) index - else null + ) { + index + } + else { + null + } } .filterNotNull() .toIntArray() @@ -72,7 +76,7 @@ internal class ConstructorArgumentsSnapshotTrackerTransformer( // STACK: pushArray(matchedLocals) // STACK: array - invokeStatic(Injections::updateSnapshotWithEagerTracking) + invokeStatic(Injections::updateSnapshotBeforeConstructorCall) // STACK: loadLocals(arguments) // STACK: args diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SharedMemoryAccessTransformer.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SharedMemoryAccessTransformer.kt index 4a42c0921..67080c113 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SharedMemoryAccessTransformer.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SharedMemoryAccessTransformer.kt @@ -36,7 +36,7 @@ internal class SharedMemoryAccessTransformer( if ( isCoroutineInternalClass(owner) || isCoroutineStateMachineClass(owner) || - // when initializing our own fields in constructor, we do not want to track that, + // when initializing our own fields in constructor, we do not want to track that; // otherwise `VerifyError` will be thrown, see https://github.com/JetBrains/lincheck/issues/424 (methodName == "" && className == owner) ) { @@ -277,7 +277,6 @@ internal class SharedMemoryAccessTransformer( // STACK: value } - /* * For an array access instruction (either load or store), * tries to obtain the type of the read/written array element. From fc57dde5c1b3c7423ae7055d34ce8e03c15e702d Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Wed, 11 Dec 2024 18:53:09 +0100 Subject: [PATCH 27/29] Bring gradle folder back --- gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 15 +++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..d64cd4917707c1f8861d8cb53dd15194d4248596 GIT binary patch literal 43462 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..0bde95023 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,15 @@ +# +# Lincheck +# +# Copyright (C) 2019 - 2023 JetBrains s.r.o. +# +# This Source Code Form is subject to the terms of the +# Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed +# with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists From be5329a2199d32621a8d9f5a5774b6db1932337a Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Wed, 11 Dec 2024 19:01:53 +0100 Subject: [PATCH 28/29] Add small renaming --- bootstrap/src/sun/nio/ch/lincheck/EventTracker.java | 2 +- bootstrap/src/sun/nio/ch/lincheck/Injections.java | 2 +- .../kotlinx/lincheck/strategy/managed/ManagedStrategy.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java b/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java index f6663a1f3..e530a32e9 100644 --- a/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java +++ b/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java @@ -32,7 +32,7 @@ public interface EventTracker { void beforeNewObjectCreation(String className); void afterNewObjectCreation(Object obj); - void updateSnapshotWithEagerTracking(Object[] objs); + void updateSnapshotBeforeConstructorCall(Object[] objs); boolean beforeReadField(Object obj, String className, String fieldName, int codeLocation, boolean isStatic, boolean isFinal); diff --git a/bootstrap/src/sun/nio/ch/lincheck/Injections.java b/bootstrap/src/sun/nio/ch/lincheck/Injections.java index 4bad46c1c..77a188f69 100644 --- a/bootstrap/src/sun/nio/ch/lincheck/Injections.java +++ b/bootstrap/src/sun/nio/ch/lincheck/Injections.java @@ -300,7 +300,7 @@ public static void afterNewObjectCreation(Object obj) { * Required to update the static memory snapshot. */ public static void updateSnapshotBeforeConstructorCall(Object[] objs) { - getEventTracker().updateSnapshotWithEagerTracking(objs); + getEventTracker().updateSnapshotBeforeConstructorCall(objs); } /** diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt index 3a9ec6377..169e329fa 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt @@ -946,7 +946,7 @@ abstract class ManagedStrategy( * Tracks all objects in [objs] eagerly. * Required as a trick to overcome issue with leaking this in constructors, see https://github.com/JetBrains/lincheck/issues/424. */ - override fun updateSnapshotWithEagerTracking(objs: Array) = runInIgnoredSection { + override fun updateSnapshotBeforeConstructorCall(objs: Array) = runInIgnoredSection { staticMemorySnapshot.trackObjects(objs) } From 6ca73a9f66c5c387bbd03ccd20c9da9a0af54270 Mon Sep 17 00:00:00 2001 From: Dmitrii Art Date: Thu, 12 Dec 2024 01:56:42 +0100 Subject: [PATCH 29/29] Add minor fixes --- .../kotlinx/lincheck/strategy/Strategy.kt | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt index c8540bf00..574e55971 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt @@ -102,26 +102,23 @@ abstract class Strategy protected constructor( * @return the failure, if detected, null otherwise. */ fun Strategy.runIteration(invocations: Int, verifier: Verifier): LincheckFailure? { - var failure: LincheckFailure? = null - for (invocation in 0 until invocations) { - if (!nextInvocation()) break + if (!nextInvocation()) return null val result = runInvocation() - failure = - try { - verify(result, verifier) - } finally { - // verifier calls `@Operation`s of the class under test which can - // modify the static memory; thus, we need to restore initial values - if (this is ManagedStrategy) { - restoreStaticMemorySnapshot() - } + val failure = try { + verify(result, verifier) + } finally { + // verifier calls `@Operation`s of the class under test which can + // modify the static memory; thus, we need to restore initial values + if (this is ManagedStrategy) { + restoreStaticMemorySnapshot() } - if (failure != null) break + } + if (failure != null) return failure } - return failure + return null } /**