Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static memory snapshot collecting and restoring #418

Merged
merged 29 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
8b73ca2
Add static memory snapshot collecting and restoring
dmitrii-artuhov Oct 17, 2024
ca7d02a
Add the class which implements the logic for static memory snapshot
dmitrii-artuhov Oct 17, 2024
75126a6
Add tests and fixes to snapshot algorithm
dmitrii-artuhov Oct 21, 2024
3e6ec07
Remove comments
dmitrii-artuhov Oct 21, 2024
760b076
Make snapshot algorithm lazy
dmitrii-artuhov Oct 30, 2024
7edbd56
Fix bugs
dmitrii-artuhov Oct 30, 2024
feefb3d
Add energetic traverse for non-transformed classes (such as 'AtomicIn…
dmitrii-artuhov Nov 1, 2024
128050a
Refactor code
dmitrii-artuhov Nov 1, 2024
c9f7ee2
Add energetic snapshot recording for non-transformed classes
dmitrii-artuhov Nov 7, 2024
b7a488d
Enable snapshot restoring for all tests, fix representation tests
dmitrii-artuhov Nov 8, 2024
b733f25
Add minor refactoring
dmitrii-artuhov Nov 8, 2024
c187f06
Add tests for java atomics and atomicfu classes for snapshot restoring
dmitrii-artuhov Nov 12, 2024
5ef2024
Add collections tests
dmitrii-artuhov Nov 14, 2024
4c08faa
Add tracking for fields of objects the same type as constructor owner
dmitrii-artuhov Nov 15, 2024
eb9a9a2
Optimize tracking of constructors' arguments
dmitrii-artuhov Nov 26, 2024
0803b6d
Remove println's and comments
dmitrii-artuhov Nov 26, 2024
1b6bbef
Fix todo with subclass check in SnapshotTrackerTransformer
dmitrii-artuhov Nov 27, 2024
86d5bd2
Add VarHandles to excludes in heirarchy tracking
dmitrii-artuhov Nov 27, 2024
8eac5dc
Clean up code (remove comments and useless annotations)
dmitrii-artuhov Nov 27, 2024
709a6d3
Add some PR fixes
dmitrii-artuhov Dec 2, 2024
7f5e6ea
Add more fixes to the PR
dmitrii-artuhov Dec 2, 2024
a15f645
Simplify and refactor code in SnapshotTracker
dmitrii-artuhov Dec 2, 2024
28b153b
Rewrite values restoring in a non-recursion way using stack
dmitrii-artuhov Dec 2, 2024
af39fd7
Add fixes to the PR
dmitrii-artuhov Dec 2, 2024
d8bf525
Add fixes
dmitrii-artuhov Dec 2, 2024
3097c43
Add PR fixes
dmitrii-artuhov Dec 11, 2024
fc57dde
Bring gradle folder back
dmitrii-artuhov Dec 11, 2024
be5329a
Add small renaming
dmitrii-artuhov Dec 11, 2024
6ca73a9
Add minor fixes
dmitrii-artuhov Dec 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bootstrap/src/sun/nio/ch/lincheck/EventTracker.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public interface EventTracker {
void beforeNewObjectCreation(String className);
void afterNewObjectCreation(Object obj);

void updateSnapshotWithEagerTracking(Object[] objs);
eupp marked this conversation as resolved.
Show resolved Hide resolved
boolean beforeReadField(Object obj, String className, String fieldName, int codeLocation,
boolean isStatic, boolean isFinal);
boolean beforeReadArrayElement(Object array, int index, int codeLocation);
Expand Down
7 changes: 7 additions & 0 deletions bootstrap/src/sun/nio/ch/lincheck/Injections.java
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,13 @@ public static void afterNewObjectCreation(Object obj) {
getEventTracker().afterNewObjectCreation(obj);
}

/**
* Called from instrumented code on constructor invocation, where passed objects are subtypes of the constructor class type.
*/
public static void updateSnapshotWithEagerTracking(Object[] objs) {
ndkoval marked this conversation as resolved.
Show resolved Hide resolved
getEventTracker().updateSnapshotWithEagerTracking(objs);
}

/**
* Called from the instrumented code to replace [java.lang.Object.hashCode] method call with some
* deterministic value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ 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 org.jetbrains.kotlinx.lincheck.util.readFieldViaUnsafe
import java.math.BigDecimal
import java.math.BigInteger
import kotlin.coroutines.Continuation
Expand Down
69 changes: 55 additions & 14 deletions src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.util.readFieldViaUnsafe
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.*
Expand Down Expand Up @@ -213,21 +212,63 @@ 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<Field> {
val fields: MutableList<Field> = ArrayList<Field>()
var currentClass: Class<*>? = this
while (currentClass != null) {
val declaredFields: Array<Field> = currentClass.declaredFields
fields.addAll(declaredFields)
currentClass = currentClass.superclass
}
return fields
}

@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) {
/**
* 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.
*/
internal fun Class<*>.findField(fieldName: String): Field {
// Search in the class hierarchy
var clazz: Class<*>? = this
while (clazz != null) {
// Check class itself
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 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
}
return null // Field not found

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.
eupp marked this conversation as resolved.
Show resolved Hide resolved
*/
internal fun readFieldSafely(obj: Any?, field: Field): kotlin.Result<Any?> {
// 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) }
}

/**
Expand Down
19 changes: 15 additions & 4 deletions src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/Strategy.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -101,15 +102,25 @@ abstract class Strategy protected constructor(
* @return the failure, if detected, null otherwise.
*/
fun Strategy.runIteration(invocations: Int, verifier: Verifier): LincheckFailure? {
var failure: LincheckFailure? = null
ndkoval marked this conversation as resolved.
Show resolved Hide resolved

for (invocation in 0 until invocations) {
if (!nextInvocation())
return null
break
ndkoval marked this conversation as resolved.
Show resolved Hide resolved
val result = runInvocation()
val failure = verify(result, verifier)
try {
ndkoval marked this conversation as resolved.
Show resolved Hide resolved
failure = verify(result, verifier)
}
finally {
if (this is ManagedStrategy) {
restoreStaticMemorySnapshot()
}
}
if (failure != null)
return failure
break
}
return null

return failure
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,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 = true
eupp marked this conversation as resolved.
Show resolved Hide resolved
val DEFAULT_GUARANTEES = listOf<ManagedStrategyGuarantee>()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*

/**
Expand All @@ -23,6 +24,7 @@ abstract class ManagedOptions<OPT : Options<OPT, CTEST>, 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<ManagedStrategyGuarantee> = ArrayList(DEFAULT_GUARANTEES)

/**
Expand Down Expand Up @@ -50,6 +52,13 @@ abstract class ManagedOptions<OPT : Options<OPT, CTEST>, 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
}
eupp marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -216,13 +219,26 @@ abstract class ManagedStrategy(
parkingTracker.reset()
}

/**
* Restores recorded values of all memory reachable from static state.
*/
fun restoreStaticMemorySnapshot() {
staticMemorySnapshot.restoreValues()
}

/**
* Runs the current invocation.
*/
override fun runInvocation(): InvocationResult {
while (true) {
initializeInvocation()
val result = runner.run()
val result: InvocationResult
try {
ndkoval marked this conversation as resolved.
Show resolved Hide resolved
result = runner.run()
}
finally {
ndkoval marked this conversation as resolved.
Show resolved Hide resolved
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.
Expand Down Expand Up @@ -742,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) {
Expand Down Expand Up @@ -778,6 +795,7 @@ abstract class ManagedStrategy(

/** Returns <code>true</code> 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
}
Expand Down Expand Up @@ -813,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
Expand Down Expand Up @@ -842,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
Expand Down Expand Up @@ -909,6 +929,29 @@ 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.
*/
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.
*/
fun updateSnapshotOnArrayElementAccess(array: Any, index: Int) = runInIgnoredSection {
ndkoval marked this conversation as resolved.
Show resolved Hide resolved
staticMemorySnapshot.trackArrayCell(array, index)
}

/**
* 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<Any?>) = runInIgnoredSection {
staticMemorySnapshot.trackObjects(objs)
}

private fun methodGuaranteeType(owner: Any?, className: String, methodName: String): ManagedGuaranteeType? = runInIgnoredSection {
userDefinedGuarantees?.forEach { guarantee ->
val ownerName = owner?.javaClass?.canonicalName ?: className
Expand Down
Loading