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

New model checker algorithm #410

Draft
wants to merge 48 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
5da52ff
moved all event-structure-related files from the `eventstruct-mc` branch
eupp May 20, 2024
3b99739
WIP: fixes after `eventstruct-mc` branch transferring
eupp May 20, 2024
d5876d9
minor fixes
eupp May 23, 2024
2d6a0c1
minor fixes and refactoring
eupp May 30, 2024
4b56f1d
transfer atomics tests from eventstruct-mc branch
eupp Jun 6, 2024
2f16111
add EXPERIMENTAL_MODEL_CHECKING instrumentation mode
eupp Jun 6, 2024
0a3c64e
minor fixes
eupp Jun 6, 2024
bdc40bd
repair object allocation tracking code
eupp Jun 10, 2024
dcabb69
minor fixes
eupp Jun 10, 2024
1408231
repair EventStructureStrategy spin-loop bounding
eupp Jun 10, 2024
76f58b3
add array accesses tests
eupp Jun 17, 2024
2ca6978
fix in `MemoryLocation` and `MemoryTracker`
eupp Jun 17, 2024
47c83b2
transfer System.arraycopy instrumentation from eventstruct-mc branch
eupp Jun 18, 2024
7f9acfe
ransfer `java.lang.reflect.Array.newInstance` instrumentation from ev…
eupp Jun 18, 2024
00f3e3f
add runtime check to avoid intercepting array reads if the array elem…
eupp Jun 19, 2024
3df0107
fix spin-loop bounding in event-structure strategy (reset counters on…
eupp Jun 20, 2024
f819285
add VarHandle tests
eupp Jun 20, 2024
40b9d65
fix VarHandle handling logic
eupp Jun 20, 2024
4d14db9
fix `onArrayCopy` (wrap in ignored section)
eupp Jun 21, 2024
38303d1
minor fixes
eupp Jun 21, 2024
265ec40
minor fixes
eupp Jul 7, 2024
7885bcf
bugfixing FieldInheritedFromInterfaceTest (problems with static class…
eupp Aug 1, 2024
8271ed6
WIP: compilation errors fixes after rebase
eupp Aug 1, 2024
bd91f74
WIP: compilation errors fixes after rebase (in tests)
eupp Aug 2, 2024
2194f78
restore atomic methods result interception
eupp Aug 2, 2024
e62b3d3
restore atomic methods result interception
eupp Aug 2, 2024
efaf031
fix debug stats printing
eupp Aug 2, 2024
05a0232
fixes after rebase (fix invocation initialization logic)
eupp Aug 2, 2024
8ec1ab7
minor fixes
eupp Aug 2, 2024
347a27b
fix skipping verification of SpinLoopBoundInvocationResult
eupp Aug 2, 2024
eab0e76
minor fixes in `trackAtomicMethodMemoryAccess` method after rebase
eupp Aug 2, 2024
35c6322
fix bug with clocks (perform deep copy of `HBClock` objects)
eupp Aug 5, 2024
f60ee52
fix bug in MethodCallTransformer and repair old model checking strategy
eupp Aug 7, 2024
4cd12c7
fix event structure execution reset logic for trace collection replay
eupp Aug 7, 2024
384498a
repair ByteArrayAccessTest/BooleanArrayAccessTest
eupp Jun 17, 2024
81e5655
better fix for validation actor bug in EventStructureStrategy
eupp Aug 7, 2024
8fd399c
fix bug in Execution::isEmpty
eupp Aug 8, 2024
c3a4b13
minor infra fixes
eupp Aug 8, 2024
1c99b02
hack to get LocalObjectsTests pass (related to SPIN_BOUND constant --…
eupp Aug 14, 2024
cc55529
fix trace collection for the new-mc (do not re-create runner)
eupp Aug 8, 2024
37a9304
bugfix coroutines scheduling and blocking
eupp Aug 8, 2024
3b92001
fix duplicate coroutine resume events
eupp Aug 8, 2024
9553a80
fix a BATSHIT CRAZY BUG in the coroutines runner
eupp Aug 8, 2024
43c7db4
fix ParallelThreadsRunner::Completion::context (make it immutable)
eupp Aug 8, 2024
e17af1e
introduce system property to opt-in into experimental model checking
eupp Aug 28, 2024
06de6a6
minor
eupp Aug 28, 2024
6b30fbd
fixes after rebase
eupp Oct 1, 2024
c6b9e25
remove obsolete files
eupp Oct 2, 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
17 changes: 12 additions & 5 deletions bootstrap/src/sun/nio/ch/lincheck/EventTracker.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,30 @@ public interface EventTracker {

void beforeNewObjectCreation(String className);
void afterNewObjectCreation(Object obj);
void afterObjectInitialization(Object obj);

boolean beforeReadField(Object obj, String className, String fieldName, int codeLocation,
boolean beforeReadField(Object obj, String className, String fieldName, String typeDescriptor, int codeLocation,
boolean isStatic, boolean isFinal);
boolean beforeReadArrayElement(Object array, int index, int codeLocation);
boolean beforeReadArrayElement(Object array, int index, String typeDescriptor, int codeLocation);
void afterRead(Object value);

boolean beforeWriteField(Object obj, String className, String fieldName, Object value, int codeLocation,
Object interceptReadResult();

boolean beforeWriteField(Object obj, String className, String fieldName, String typeDescriptor, Object value, int codeLocation,
boolean isStatic, boolean isFinal);
boolean beforeWriteArrayElement(Object array, int index, Object value, int codeLocation);
boolean beforeWriteArrayElement(Object array, int index, String typeDescriptor, Object value, int codeLocation);
void afterWrite();

void afterReflectiveSetter(Object receiver, Object value);

void beforeMethodCall(Object owner, String className, String methodName, int codeLocation, int methodId, Object[] params);
void onArrayCopy(Object srcArray, int srcPos, Object dstArray, int dstPos, int length);

boolean beforeMethodCall(Object owner, String className, String methodName, int codeLocation, int methodId, Object[] params);
void onMethodCallReturn(Object result);
void onMethodCallException(Throwable t);

Object interceptMethodCallResult();

Random getThreadLocalRandom();
int randomNextInt();

Expand Down
42 changes: 32 additions & 10 deletions bootstrap/src/sun/nio/ch/lincheck/Injections.java
Original file line number Diff line number Diff line change
Expand Up @@ -186,20 +186,24 @@ public static boolean isRandom(Object any) {
*
* @return whether the trace point was created
*/
public static boolean beforeReadField(Object obj, String className, String fieldName, int codeLocation,
public static boolean beforeReadField(Object obj, String className, String fieldName, String typeDescriptor, int codeLocation,
boolean isStatic, boolean isFinal) {
if (!isStatic && obj == null) return false; // Ignore, NullPointerException will be thrown
return getEventTracker().beforeReadField(obj, className, fieldName, codeLocation, isStatic, isFinal);
return getEventTracker().beforeReadField(obj, className, fieldName, typeDescriptor, codeLocation, isStatic, isFinal);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-check if typeDescriptor is required (find its usages).

}

/**
* Called from the instrumented code before any array cell read.
*
* @return whether the trace point was created
*/
public static boolean beforeReadArray(Object array, int index, int codeLocation) {
public static boolean beforeReadArray(Object array, int index, String typeDescriptor, int codeLocation) {
if (array == null) return false; // Ignore, NullPointerException will be thrown
return getEventTracker().beforeReadArrayElement(array, index, codeLocation);
return getEventTracker().beforeReadArrayElement(array, index, typeDescriptor, codeLocation);
}

public static Object interceptReadResult() {
return getEventTracker().interceptReadResult();
}

/**
Expand All @@ -214,20 +218,20 @@ public static void afterRead(Object value) {
*
* @return whether the trace point was created
*/
public static boolean beforeWriteField(Object obj, String className, String fieldName, Object value, int codeLocation,
public static boolean beforeWriteField(Object obj, String className, String fieldName, String typeDescriptor, Object value, int codeLocation,
boolean isStatic, boolean isFinal) {
if (!isStatic && obj == null) return false; // Ignore, NullPointerException will be thrown
return getEventTracker().beforeWriteField(obj, className, fieldName, value, codeLocation, isStatic, isFinal);
return getEventTracker().beforeWriteField(obj, className, fieldName, typeDescriptor, value, codeLocation, isStatic, isFinal);
}

/**
* Called from the instrumented code before any array cell write.
*
* @return whether the trace point was created
*/
public static boolean beforeWriteArray(Object array, int index, Object value, int codeLocation) {
public static boolean beforeWriteArray(Object array, int index, String typeDescriptor, Object value, int codeLocation) {
if (array == null) return false; // Ignore, NullPointerException will be thrown
return getEventTracker().beforeWriteArrayElement(array, index, value, codeLocation);
return getEventTracker().beforeWriteArrayElement(array, index, typeDescriptor, value, codeLocation);
}

/**
Expand All @@ -250,13 +254,27 @@ public static void afterReflectiveSetter(Object receiver, Object value) {
getEventTracker().afterReflectiveSetter(receiver, value);
}

public static void onArrayCopy(Object srcArray, int srcPos, Object dstArray, int dstPos, int length) {
getEventTracker().onArrayCopy(srcArray, srcPos, dstArray, dstPos, length);
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment about System.arraycopy


/**
* Called from the instrumented code before any method call.
*
* @param owner is `null` for public static methods.
* @return true if the method result should be intercepted. TODO: revisit this API decision
*/
public static void beforeMethodCall(Object owner, String className, String methodName, int codeLocation, int methodId, Object[] params) {
getEventTracker().beforeMethodCall(owner, className, methodName, codeLocation, methodId, params);
public static boolean beforeMethodCall(Object owner, String className, String methodName, int codeLocation, int methodId, Object[] params) {
return getEventTracker().beforeMethodCall(owner, className, methodName, codeLocation, methodId, params);
}

/**
* Intercepts the result of a method call.
*
* @return The intercepted result of the method call.
*/
public static Object interceptMethodCallResult() {
return getEventTracker().interceptMethodCallResult();
}

/**
Expand Down Expand Up @@ -294,6 +312,10 @@ public static void afterNewObjectCreation(Object obj) {
getEventTracker().afterNewObjectCreation(obj);
}

public static void afterObjectInitialization(Object obj) {
getEventTracker().afterObjectInitialization(obj);
}

/**
* Called from the instrumented code to replace [java.lang.Object.hashCode] method call with some
* deterministic value.
Expand Down
15 changes: 13 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ repositories {
kotlin {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
compilerOptions {
allWarningsAsErrors = true
// allWarningsAsErrors = true
}

jvm {
Expand Down Expand Up @@ -119,7 +119,16 @@ tasks {
if (instrumentAllClasses.toBoolean()) {
systemProperty("lincheck.instrumentAllClasses", "true")
}
val extraArgs = mutableListOf<String>()
val extraArgs = mutableListOf<String>(
// flags to import Unsafe module;
// it is used in some tests to check handling of unsafe APIs by Lincheck
"--add-opens", "java.base/jdk.internal.misc=ALL-UNNAMED",
"--add-exports", "java.base/jdk.internal.util=ALL-UNNAMED",
)
val useExperimentalModelChecking: String by project
if (useExperimentalModelChecking.toBoolean()) {
extraArgs.add("-Dlincheck.useExperimentalModelChecking=true")
}
val withEventIdSequentialCheck: String by project
if (withEventIdSequentialCheck.toBoolean()) {
extraArgs.add("-Dlincheck.debug.withEventIdSequentialCheck=true")
Expand Down Expand Up @@ -157,6 +166,8 @@ tasks {
ideaActive -> 10
else -> 0
}
// temporarily ignore representation tests, because they are unsupported in the new strategy
// exclude("org/jetbrains/kotlinx/lincheck_test/representation")
}

val jvmTestIsolated = register<Test>("jvmTestIsolated") {
Expand Down
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ lastCopyrightYear=2023
jdkToolchainVersion=17
runAllTestsInSeparateJVMs=false
instrumentAllClasses=false
useExperimentalModelChecking=false
withEventIdSequentialCheck=false

kotlinVersion=1.9.21
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ internal fun createFromTestClassAnnotations(testClass: Class<*>): List<CTestConf
testClass
),
timeoutMs = DEFAULT_TIMEOUT_MS,
customScenarios = emptyList()
customScenarios = emptyList(),
experimentalModelChecking = false,
)
}
return stressConfigurations + modelCheckingConfigurations
Expand Down
21 changes: 1 addition & 20 deletions src/jvm/main/org/jetbrains/kotlinx/lincheck/ObjectTraverser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@
package org.jetbrains.kotlinx.lincheck

import org.jetbrains.kotlinx.lincheck.strategy.managed.ObjectLabelFactory.getObjectNumber
import org.jetbrains.kotlinx.lincheck.util.readFieldViaUnsafe
import sun.misc.Unsafe
import java.lang.reflect.Field
import org.jetbrains.kotlinx.lincheck.util.*
import java.lang.reflect.Modifier
import java.math.BigDecimal
import java.math.BigInteger
Expand Down Expand Up @@ -115,23 +113,6 @@ private fun enumerateObjects(obj: Any, processedObjects: MutableSet<Any>, object
}
}

private fun readField(obj: Any?, field: Field): Any? {
if (!field.type.isPrimitive) {
return readFieldViaUnsafe(obj, field, Unsafe::getObject)
}
return when (field.type) {
Boolean::class.javaPrimitiveType -> readFieldViaUnsafe(obj, field, Unsafe::getBoolean)
Byte::class.javaPrimitiveType -> readFieldViaUnsafe(obj, field, Unsafe::getByte)
Char::class.javaPrimitiveType -> readFieldViaUnsafe(obj, field, Unsafe::getChar)
Short::class.javaPrimitiveType -> readFieldViaUnsafe(obj, field, Unsafe::getShort)
Int::class.javaPrimitiveType -> readFieldViaUnsafe(obj, field, Unsafe::getInt)
Long::class.javaPrimitiveType -> readFieldViaUnsafe(obj, field, Unsafe::getLong)
Double::class.javaPrimitiveType -> readFieldViaUnsafe(obj, field, Unsafe::getDouble)
Float::class.javaPrimitiveType -> readFieldViaUnsafe(obj, field, Unsafe::getFloat)
else -> error("No more types expected")
}
}

private fun isAtomic(value: Any?): Boolean {
if (value == null) return false
return value.javaClass.canonicalName.let {
Expand Down
2 changes: 1 addition & 1 deletion src/jvm/main/org/jetbrains/kotlinx/lincheck/Reporter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class Reporter(private val logLevel: LoggingLevel) {
}
}

@JvmField val DEFAULT_LOG_LEVEL = WARN
@JvmField val DEFAULT_LOG_LEVEL = INFO
enum class LoggingLevel {
INFO, WARN
}
Expand Down
16 changes: 10 additions & 6 deletions src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ internal fun <T> CancellableContinuation<T>.cancelByLincheck(promptCancellation:
}
}

internal enum class CancellationResult { CANCELLED_BEFORE_RESUMPTION, CANCELLED_AFTER_RESUMPTION, CANCELLATION_FAILED }
enum class CancellationResult { CANCELLED_BEFORE_RESUMPTION, CANCELLED_AFTER_RESUMPTION, CANCELLATION_FAILED }

/**
* Returns `true` if the continuation was cancelled by [CancellableContinuation.cancel].
Expand Down Expand Up @@ -233,17 +233,21 @@ internal val Class<*>.allDeclaredFieldWithSuperclasses get(): List<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) {
for (field in targetType.allDeclaredFieldWithSuperclasses) {
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
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
}

Expand Down
19 changes: 18 additions & 1 deletion src/jvm/main/org/jetbrains/kotlinx/lincheck/execution/HBClock.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,32 @@ import org.jetbrains.kotlinx.lincheck.Result

data class HBClock(val clock: IntArray) {
val threads: Int get() = clock.size

val empty: Boolean get() = clock.all { it == 0 }
operator fun get(i: Int) = clock[i]

fun set(other: HBClock) {
check(clock.size == other.clock.size)
for (i in clock.indices) {
clock[i] = other.clock[i]
}
}

fun reset() {
for (i in clock.indices) {
clock[i] = 0
}
}

/**
* Checks whether the clock contains information for any thread
* excluding the one this clock is associated with.
*/
fun isEmpty(clockThreadId: Int) = clock.filterIndexed { t, _ -> t != clockThreadId }.all { it == 0 }

fun copy(): HBClock {
return HBClock(clock.copyOf())
}

override fun toString() = clock.joinToString(prefix = "[", separator = ",", postfix = "]")

override fun equals(other: Any?): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ package org.jetbrains.kotlinx.lincheck.runner

import org.jetbrains.kotlinx.lincheck.execution.*
import org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedStrategy
import org.jetbrains.kotlinx.lincheck.strategy.managed.eventstructure.consistency.Inconsistency

/**
* Represents results for invocations, see [Runner.run].
Expand Down Expand Up @@ -65,7 +66,29 @@ class ObstructionFreedomViolationInvocationResult(
val results: ExecutionResult
) : InvocationResult()

class InconsistentInvocationResult(
val inconsistency: Inconsistency
) : InvocationResult()

/**
* Invocation is aborted due to one of the threads reaching
* the bound on the number of spin-loop iterations.
*/
class SpinLoopBoundInvocationResult : InvocationResult()

/**
* Indicates that spin-cycle has been found for the first time and replay of current interleaving is required.
*/
data object SpinCycleFoundAndReplayRequired: InvocationResult()
data object SpinCycleFoundAndReplayRequired: InvocationResult()

fun InvocationResult.isAbortedInvocation(): Boolean =
when (this) {
is ManagedDeadlockInvocationResult,
is RunnerTimeoutInvocationResult,
is SpinLoopBoundInvocationResult,
is UnexpectedExceptionInvocationResult,
is ObstructionFreedomViolationInvocationResult,
is InconsistentInvocationResult
-> true
else -> false
}
Loading