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

Refactor byte-code transformation #285

Merged
merged 56 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
a921c2d
New transformation implemented as a part of javaagents
Mar 4, 2024
411ca48
Merge branch 'develop' into transformation
Mar 4, 2024
f92784c
Test logging enabled to find ConcurrentModification bug
Mar 5, 2024
4562867
Test logging enabled to find ConcurrentModification bug
Mar 5, 2024
649d4cb
Some more logging...
Mar 5, 2024
b305dfc
Fix collectTrace
ndkoval Mar 5, 2024
6904cd5
Avoid undesired effects by previous runner in the trace collection.
Mar 6, 2024
5bfc47c
Java 21 random transformation bug fixed
Mar 7, 2024
95fb495
Coroutines bug fixed
Mar 14, 2024
b81de56
Output fixes
Mar 14, 2024
8f35d2c
Redundant final field checks removed
Mar 14, 2024
a62648f
Review fixes
Mar 14, 2024
11ab86f
Merge branch 'develop' into transformation
Mar 14, 2024
e48d2d8
Merge with develop
Mar 14, 2024
488c6ba
Review fixes
Mar 14, 2024
6c1a8fb
Review fixes
Mar 14, 2024
50aa14d
Review fixes
Mar 14, 2024
a31db20
Review fixes
Mar 14, 2024
61e3f04
ConcurrentModification bug fix attempt 2
Mar 15, 2024
1246649
Output fix
Mar 15, 2024
88258a5
Output fix
Mar 15, 2024
9516e34
Review fixes
Mar 15, 2024
ffd8ef8
Documentation added
Mar 15, 2024
66315ad
Review fixes
Mar 15, 2024
8526733
Review fixes
Mar 16, 2024
bfc0960
Review fixes
Mar 16, 2024
4c88880
Review fixes
Mar 17, 2024
fdec41d
Documentation added
Mar 18, 2024
4618ec5
Refactoring
Mar 18, 2024
0f5db96
Refactoring
Mar 18, 2024
6167b77
Non-determenism fix attempt
Mar 18, 2024
7d70e51
Review
ndkoval Mar 18, 2024
aa4fbe0
Fix Random support
ndkoval Mar 18, 2024
ffc2583
Review fixes
Mar 18, 2024
399520a
Merge remote-tracking branch 'origin/transformation' into transformation
Mar 18, 2024
8c8b4c5
Fix OperationsInAbstractClassTest
ndkoval Mar 18, 2024
2ab9fd0
Merge remote-tracking branch 'origin/transformation' into transformation
Mar 19, 2024
c40f9f1
Review fixes
Mar 19, 2024
a11de54
Review fixes
Mar 19, 2024
8a0a5f8
Review + refactoring
ndkoval Mar 19, 2024
771af8e
Review + refactoring
ndkoval Mar 19, 2024
076f7bd
Review + refactoring
ndkoval Mar 19, 2024
83937b1
Review + refactoring
ndkoval Mar 19, 2024
2c9da61
Review + refactoring
ndkoval Mar 19, 2024
c51ab93
Review + refactoring
ndkoval Mar 19, 2024
98e9385
Documentation
ndkoval Mar 19, 2024
0e1b891
Attempt to fix the representation of atomic constructs
ndkoval Mar 19, 2024
bda1166
Attempt to fix the representation of atomic constructs
ndkoval Mar 19, 2024
63663de
Small refactoring
ndkoval Mar 19, 2024
0fb7ba8
Refactoring
ndkoval Mar 19, 2024
66a5039
Small renaming
ndkoval Mar 19, 2024
d1fa745
Review fixes
Mar 19, 2024
6d3fb40
Review fixes
Mar 19, 2024
03b4506
Review fixes
Mar 19, 2024
f96995f
Do not run JdkUnsafeTraceRepresentationTest on java <13
ndkoval Mar 19, 2024
a4b099d
Merge branch 'transformation' of github.com:JetBrains/lincheck into t…
ndkoval Mar 19, 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
50 changes: 50 additions & 0 deletions src/jvm/main/org/jetbrains/kotlinx/lincheck/EventTracker.kt
Original file line number Diff line number Diff line change
@@ -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

import java.util.*

/**
* Methods of this interface are called from the instrumented tested code during model-checking.
* See [Injections] for the documentation.
*/
internal interface EventTracker {
fun lock(monitor: Any, codeLocation: Int)
fun unlock(monitor: Any, codeLocation: Int)

fun park(codeLocation: Int)
fun unpark(thread: Thread, codeLocation: Int)

fun wait(monitor: Any, codeLocation: Int, withTimeout: Boolean)
fun notify(monitor: Any, codeLocation: Int, notifyAll: Boolean)

fun beforeReadField(obj: Any, className: String, fieldName: String, codeLocation: Int)
fun beforeReadFieldStatic(className: String, fieldName: String, codeLocation: Int)
fun beforeReadArrayElement(array: Any, index: Int, codeLocation: Int)
fun afterRead(value: Any?)

fun beforeWriteField(obj: Any, className: String, fieldName: String, value: Any?, codeLocation: Int)
fun beforeWriteFieldStatic(className: String, fieldName: String, value: Any?, codeLocation: Int)
fun beforeWriteArrayElement(array: Any, index: Int, value: Any?, codeLocation: Int)
fun afterWrite()

fun beforeMethodCall(owner: Any?, className: String, methodName: String, codeLocation: Int, params: Array<Any?>)
fun beforeAtomicMethodCall(ownerName: String, methodName: String, codeLocation: Int, params: Array<Any?>)
fun beforeAtomicUpdaterMethodCall(owner: Any, methodName: String, codeLocation: Int, params: Array<Any?>)
fun onMethodCallFinishedSuccessfully(result: Any?)
fun onMethodCallThrewException(t: Throwable)

fun getThreadLocalRandom(): Random
fun randomNextInt(): Int

fun onNewObjectCreation(obj: Any)
fun addDependency(receiver: Any, value: Any?)
avpotapov00 marked this conversation as resolved.
Show resolved Hide resolved
}
313 changes: 313 additions & 0 deletions src/jvm/main/org/jetbrains/kotlinx/lincheck/Injections.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
/*
* 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

import java.util.*

/**
* Methods of this object are called from the instrumented code.
*/
internal object Injections {
@JvmStatic
var lastSuspendedCancellableContinuationDuringVerification: Any? = null

@JvmStatic
fun storeCancellableContinuation(cont: Any) {
val t = Thread.currentThread()
if (t is TestThread) {
t.suspendedContinuation = cont
} else {
lastSuspendedCancellableContinuationDuringVerification = cont
}
}

@JvmStatic
fun enterIgnoredSection(): Boolean {
ndkoval marked this conversation as resolved.
Show resolved Hide resolved
val t = Thread.currentThread()
if (t !is TestThread || t.inIgnoredSection) return false
t.inIgnoredSection = true
return true
}

@JvmStatic
fun leaveIgnoredSection() {
ndkoval marked this conversation as resolved.
Show resolved Hide resolved
val t = Thread.currentThread() as TestThread
check(t.inIgnoredSection)
t.inIgnoredSection = false
}

@JvmStatic
fun inTestingCode(): Boolean {
ndkoval marked this conversation as resolved.
Show resolved Hide resolved
val t = Thread.currentThread()
if (t !is TestThread) return false
return t.inTestingCode && !t.inIgnoredSection
}

/**
* Called from instrumented code before the lock operation
ndkoval marked this conversation as resolved.
Show resolved Hide resolved
*/
@JvmStatic
fun lock(monitor: Any, codeLocation: Int) {
eventTracker.lock(monitor, codeLocation)
}

/**
* Called from instrumented code before the unlock operation
ndkoval marked this conversation as resolved.
Show resolved Hide resolved
*/
@JvmStatic
fun unlock(monitor: Any, codeLocation: Int) {
eventTracker.unlock(monitor, codeLocation)
}

/**
* Called from the instrumented code before the park operation
ndkoval marked this conversation as resolved.
Show resolved Hide resolved
*/
@JvmStatic
fun park(codeLocation: Int) {
eventTracker.park(codeLocation)
}

/**
* Called from the instrumented code before the unpark operation
ndkoval marked this conversation as resolved.
Show resolved Hide resolved
*/
@JvmStatic
fun unpark(thread: Thread, codeLocation: Int) {
eventTracker.unpark(thread, codeLocation)
}

/**
* Called from the instrumented code before the wait operation
ndkoval marked this conversation as resolved.
Show resolved Hide resolved
*/
@JvmStatic
fun wait(monitor: Any, codeLocation: Int) {
eventTracker.wait(monitor, codeLocation, withTimeout = false)
}


/**
* Called from the instrumented code before the wait operation
ndkoval marked this conversation as resolved.
Show resolved Hide resolved
*/
@JvmStatic
fun waitWithTimeout(monitor: Any, codeLocation: Int) {
eventTracker.wait(monitor, codeLocation, withTimeout = true)
}

/**
* Called from the instrumented code before the notify operation
ndkoval marked this conversation as resolved.
Show resolved Hide resolved
*/
@JvmStatic
fun notify(monitor: Any, codeLocation: Int) {
eventTracker.notify(monitor, codeLocation, notifyAll = false)
}

/**
* Called from the instrumented code before the notifyAll operation
ndkoval marked this conversation as resolved.
Show resolved Hide resolved
*/
@JvmStatic
fun notifyAll(monitor: Any, codeLocation: Int) {
eventTracker.notify(monitor, codeLocation, notifyAll = true)
}

/**
* Called from the instrumented code to replace random.nextInt() call with a deterministic value
*/
@JvmStatic
fun nextInt(): Int {
return eventTracker.randomNextInt()
}

/**
* Called from the instrumented code to get a deterministic random instance
*/
@JvmStatic
fun deterministicRandom(): Random {
return eventTracker.getThreadLocalRandom()
}

/**
* Called from the instrumented code to examine if this value is Random
ndkoval marked this conversation as resolved.
Show resolved Hide resolved
*/
@JvmStatic
fun isRandom(any: Any?): Boolean {
return any is Random
}

/**
* Called from the instrumented code before any field read
ndkoval marked this conversation as resolved.
Show resolved Hide resolved
*/
@JvmStatic
fun beforeReadField(obj: Any?, className: String, fieldName: String, codeLocation: Int) {
if (obj == null) return // Ignore, NullPointerException will be thrown
eventTracker.beforeReadField(obj, className, fieldName, codeLocation)
}

/**
* Called from the instrumented code before any static field read
*/
@JvmStatic
fun beforeReadFieldStatic(className: String, fieldName: String, codeLocation: Int) {
eventTracker.beforeReadFieldStatic(className, fieldName, codeLocation)
}

/**
* Called from the instrumented code before any array cell read
*/
@JvmStatic
fun beforeReadArray(array: Any?, index: Int, codeLocation: Int) {
if (array == null) return // Ignore, NullPointerException will be thrown
eventTracker.beforeReadArrayElement(array, index, codeLocation)
}

/**
* Called from the instrumented code after any field read
avpotapov00 marked this conversation as resolved.
Show resolved Hide resolved
avpotapov00 marked this conversation as resolved.
Show resolved Hide resolved
*/
@JvmStatic
fun afterRead(value: Any?) {
eventTracker.afterRead(value)
}

/**
* Called from the instrumented code before any field write
*/
@JvmStatic
fun beforeWriteField(obj: Any?, className: String, fieldName: String, value: Any?, codeLocation: Int) {
if (obj == null) return // Ignore, NullPointerException will be thrown
eventTracker.beforeWriteField(obj, className, fieldName, value, codeLocation)
}

/**
* Called from the instrumented code before any static field write
*/
@JvmStatic
fun beforeWriteFieldStatic(className: String, fieldName: String, value: Any?, codeLocation: Int) {
eventTracker.beforeWriteFieldStatic(className, fieldName, value, codeLocation)
}

/**
* Called from the instrumented code before any array cell write
*/
@JvmStatic
fun beforeWriteArray(array: Any?, index: Int, value: Any?, codeLocation: Int) {
if (array == null) return // Ignore, NullPointerException will be thrown
eventTracker.beforeWriteArrayElement(array, index, value, codeLocation)
}

/**
* Called from the instrumented code before any write operation
*/
@JvmStatic
fun afterWrite() {
eventTracker.afterWrite()
}

/**
* Called from the instrumented code before any method call
*
* @param owner is `null` for static methods
*/
@JvmStatic
fun beforeMethodCall(owner: Any?, className: String, methodName: String, codeLocation: Int, params: Array<Any?>) {
eventTracker.beforeMethodCall(owner, className, methodName, codeLocation, params)
}


/**
* Called from the instrumented code before any atomic method call
*/
@JvmStatic
fun beforeAtomicMethodCall(ownerName: String, methodName: String, codeLocation: Int, params: Array<Any?>) {
eventTracker.beforeAtomicMethodCall(ownerName, methodName, codeLocation, params)
}

/**
* Called from the instrumented code before any AtomicFieldUpdater method call
*/
@JvmStatic
fun beforeAtomicUpdaterMethodCall(owner: Any?, methodName: String, codeLocation: Int, params: Array<Any?>) {
avpotapov00 marked this conversation as resolved.
Show resolved Hide resolved
eventTracker.beforeAtomicUpdaterMethodCall(owner!!, methodName, codeLocation, params)
}

/**
* Called from the instrumented code after any method successful call, i.e., without any exception
*/
@JvmStatic
fun onMethodCallFinishedSuccessfully(result: Any?) {
eventTracker.onMethodCallFinishedSuccessfully(result)
}

/**
* Called from the instrumented code after any method that returns void successful call, i.e., without any exception
*/
@JvmStatic
fun onMethodCallVoidFinishedSuccessfully() {
eventTracker.onMethodCallFinishedSuccessfully(VOID_RESULT)
}

/**
* Called from the instrumented code after any method call threw an exception
*/
@JvmStatic
fun onMethodCallThrewException(t: Throwable) {
eventTracker.onMethodCallThrewException(t)
}

/**
* Called from the instrumented code after any object is created
*/
@JvmStatic
fun onNewObjectCreation(obj: Any) {
eventTracker.onNewObjectCreation(obj)
}

@JvmStatic
private val eventTracker: EventTracker
get() = (Thread.currentThread() as TestThread).eventTracker!! // should be non-null

@JvmStatic
val VOID_RESULT = Any()

/**
* Called from the instrumented code after value assigned to any receiver field.
* Required to track local objects.
* @see org.jetbrains.kotlinx.lincheck.strategy.managed.LocalObjectManager
avpotapov00 marked this conversation as resolved.
Show resolved Hide resolved
*/
@JvmStatic
fun addDependency(receiver: Any, value: Any?) {
ndkoval marked this conversation as resolved.
Show resolved Hide resolved
eventTracker.addDependency(receiver, value)
}

// == LISTENING METHODS ==

/**
* Called from the instrumented code to replace [java.lang.Object.hashCode] method call with some
* deterministic value.
*/
@JvmStatic
internal fun hashCodeDeterministic(obj: Any): Int {
val hashCode = obj.hashCode()
return if (hashCode == System.identityHashCode(obj)) {
ndkoval marked this conversation as resolved.
Show resolved Hide resolved
identityHashCodeDeterministic(obj)
} else {
hashCode
}
}

/**
* Called from the instrumented code to replace [java.lang.System.identityHashCode] method call with some
* deterministic value.
*/
@JvmStatic
internal fun identityHashCodeDeterministic(obj: Any?): Int {
if (obj == null) return 0
// TODO: easier to support when `javaagent` is merged
return 0
}
}
3 changes: 1 addition & 2 deletions src/jvm/main/org/jetbrains/kotlinx/lincheck/Reporter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ package org.jetbrains.kotlinx.lincheck

import org.jetbrains.kotlinx.lincheck.LoggingLevel.*
import org.jetbrains.kotlinx.lincheck.execution.*
import org.jetbrains.kotlinx.lincheck.runner.*
import org.jetbrains.kotlinx.lincheck.strategy.*
import org.jetbrains.kotlinx.lincheck.strategy.managed.*
import java.io.*
Expand Down Expand Up @@ -573,7 +572,7 @@ private fun StringBuilder.appendDeadlockWithDumpFailure(failure: DeadlockWithDum
failure.threadDump?.let { threadDump ->
// Sort threads to produce same output for the same results
for ((t, stackTrace) in threadDump.entries.sortedBy { it.key.id }) {
val threadNumber = if (t is FixedActiveThreadsExecutor.TestThread) t.iThread.toString() else "?"
val threadNumber = (t as? TestThread)?.name ?: "?"
appendLine("Thread-$threadNumber:")
stackTrace.map {
StackTraceElement(
Expand Down
Loading