Skip to content

Commit 6c70471

Browse files
authored
Merge branch 'develop' into bbrockbernd/remove-result-for-custom-threads
2 parents 25280b9 + f3e6f7e commit 6c70471

File tree

11 files changed

+158
-85
lines changed

11 files changed

+158
-85
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ repositories {
3434

3535
dependencies {
3636
// Lincheck dependency
37-
testImplementation("org.jetbrains.kotlinx:lincheck:2.36")
37+
testImplementation("org.jetbrains.kotlinx:lincheck:2.37")
3838
}
3939
```
4040

docs/v.list

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
<vars>
66

7-
<var name="lincheckVersion" value="2.36" type="string"/>
7+
<var name="lincheckVersion" value="2.37" type="string"/>
88
<var name="jctoolsVersion" value="3.3.0" type="string"/>
99

1010
</vars>

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
group=org.jetbrains.kotlinx
1212
name=lincheck
13-
version=2.37-SNAPSHOT
13+
version=2.38-SNAPSHOT
1414

1515
inceptionYear=2019
1616
lastCopyrightYear=2023

src/jvm/main/org/jetbrains/kotlinx/lincheck/IdeaPlugin.kt

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import sun.nio.ch.lincheck.*
1515
import org.jetbrains.kotlinx.lincheck.runner.*
1616
import org.jetbrains.kotlinx.lincheck.strategy.*
1717
import org.jetbrains.kotlinx.lincheck.strategy.managed.*
18+
import org.jetbrains.kotlinx.lincheck.strategy.managed.ExecutionMode.GENERAL_PURPOSE_MODEL_CHECKER
1819
import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.*
1920
import org.jetbrains.kotlinx.lincheck.execution.ExecutionResult
2021
import org.jetbrains.kotlinx.lincheck.execution.ExecutionScenario
@@ -39,7 +40,7 @@ const val MINIMAL_PLUGIN_VERSION = "0.11"
3940
* @param version current Lincheck version.
4041
* @param minimalPluginVersion minimal compatible plugin version.
4142
* @param exceptions representation of the exceptions with their stacktrace occurred during the execution.
42-
* @param isGeneralPurposeModelChecking indicates if lincheck is running in the GPMC mode
43+
* @param executionMode indicates the mode in which lincheck is running current test (see [ManagedStrategy.executionMode])
4344
*/
4445
@Suppress("UNUSED_PARAMETER")
4546
fun testFailed(
@@ -48,7 +49,7 @@ fun testFailed(
4849
version: String?,
4950
minimalPluginVersion: String,
5051
exceptions: Array<String>,
51-
isGeneralPurposeModelChecking: Boolean,
52+
executionMode: String
5253
) {}
5354

5455

@@ -159,7 +160,7 @@ internal fun ManagedStrategy.runReplayIfPluginEnabled(failure: LincheckFailure)
159160
version = lincheckVersion,
160161
minimalPluginVersion = MINIMAL_PLUGIN_VERSION,
161162
exceptions = exceptionsRepresentation,
162-
isGeneralPurposeModelChecking = isGeneralPurposeModelChecking,
163+
executionMode = executionMode.id
163164
)
164165
// Replay execution while it's needed.
165166
do {
@@ -173,7 +174,12 @@ internal fun ManagedStrategy.runReplayIfPluginEnabled(failure: LincheckFailure)
173174
* (due to difficulties with passing objects like List and TracePoint, as class versions may vary)
174175
*
175176
* Each trace point is transformed into the line of the following form:
176-
* `type,iThread,callDepth,shouldBeExpanded,eventId,representation`.
177+
* `type;iThread;callDepth;shouldBeExpanded;eventId;representation;stackTraceElement;codeLocationId`.
178+
*
179+
* stackTraceElement is "className:methodName:fileName:lineNumber" or "null" string if it is not applicable
180+
* codeLocationId is strictly growing abstract id of location, and it must grow in syntactic order to
181+
* be able to order events occurred at same line in the same file. It is `-1` if it is not
182+
* applicable and stackTranceElement is "null".
177183
*
178184
* Later, when [testFailed] breakpoint is triggered debugger parses these lines back to trace points.
179185
*
@@ -203,6 +209,13 @@ private fun constructTraceForPlugin(failure: LincheckFailure, trace: Trace): Arr
203209
val event = node.event
204210
val eventId = event.eventId
205211
val representation = event.toStringImpl(withLocation = false)
212+
val (location, locationId) = if (event is CodeLocationTracePoint) {
213+
val ste = event.stackTraceElement
214+
"${ste.className}:${ste.methodName}:${ste.fileName}:${ste.lineNumber}" to event.codeLocation
215+
}
216+
else {
217+
"null" to -1
218+
}
206219
val type = when (event) {
207220
is SwitchEventTracePoint -> {
208221
when (event.reason) {
@@ -218,30 +231,33 @@ private fun constructTraceForPlugin(failure: LincheckFailure, trace: Trace): Arr
218231
}
219232

220233
if (representation.isNotEmpty()) {
221-
representations.add("$type;${node.iThread};${node.callDepth};${node.shouldBeExpanded(false)};${eventId};${representation}")
234+
representations.add("$type;${node.iThread};${node.callDepth};${node.shouldBeExpanded(false)};${eventId};${representation};${location};${locationId}")
222235
}
223236
}
224237

225238
is CallNode -> {
226239
val beforeEventId = node.call.eventId
227240
val representation = node.call.toStringImpl(withLocation = false)
241+
val ste = node.call.stackTraceElement
242+
val location = "${ste.className}:${ste.methodName}:${ste.fileName}:${ste.lineNumber}"
243+
228244
if (representation.isNotEmpty()) {
229-
representations.add("0;${node.iThread};${node.callDepth};${node.shouldBeExpanded(false)};${beforeEventId};${representation}")
245+
representations.add("0;${node.iThread};${node.callDepth};${node.shouldBeExpanded(false)};${beforeEventId};${representation};${location};${node.call.codeLocation}")
230246
}
231247
}
232248

233249
is ActorNode -> {
234250
val beforeEventId = -1
235251
val representation = node.actorRepresentation
236252
if (representation.isNotEmpty()) {
237-
representations.add("1;${node.iThread};${node.callDepth};${node.shouldBeExpanded(false)};${beforeEventId};${representation}")
253+
representations.add("1;${node.iThread};${node.callDepth};${node.shouldBeExpanded(false)};${beforeEventId};${representation};null;-1")
238254
}
239255
}
240256

241257
is ActorResultNode -> {
242258
val beforeEventId = -1
243259
val representation = node.resultRepresentation.toString()
244-
representations.add("2;${node.iThread};${node.callDepth};${node.shouldBeExpanded(false)};${beforeEventId};${representation};${node.exceptionNumberIfExceptionResult ?: -1}")
260+
representations.add("2;${node.iThread};${node.callDepth};${node.shouldBeExpanded(false)};${beforeEventId};${representation};${node.exceptionNumberIfExceptionResult ?: -1};null;-1")
245261
}
246262

247263
else -> {}
@@ -333,7 +349,7 @@ private data class ExceptionProcessingResult(
333349
* Used to collect the data about the test instance, object numbers, threads, and continuations.
334350
*/
335351
private fun visualize(strategy: ManagedStrategy) = runCatching {
336-
if (strategy.isGeneralPurposeModelChecking) return@runCatching
352+
if (strategy.executionMode == GENERAL_PURPOSE_MODEL_CHECKER) return@runCatching
337353

338354
val runner = strategy.runner as ParallelThreadsRunner
339355
val allThreads = strategy.getRegisteredThreads()
@@ -424,7 +440,7 @@ internal val eventIdStrictOrderingCheck =
424440
* We will call `ideaPluginEnabled` method only once
425441
* and so on the plugin side the callback will be called also only once.
426442
*/
427-
internal val ideaPluginEnabled = ideaPluginEnabled()
443+
internal val ideaPluginEnabled by lazy { ideaPluginEnabled() }
428444

429445
/**
430446
* Debugger replaces the result of this method to `true` if idea plugin is enabled.

src/jvm/main/org/jetbrains/kotlinx/lincheck/Utils.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import java.lang.ref.*
2323
import java.lang.reflect.*
2424
import java.lang.reflect.Method
2525
import java.util.*
26+
import java.util.concurrent.ConcurrentHashMap
2627
import kotlin.coroutines.*
2728
import kotlin.coroutines.intrinsics.*
2829

@@ -323,4 +324,7 @@ internal fun <T> Class<T>.newDefaultInstance(): T {
323324
}
324325

325326
throw IllegalStateException("No suitable constructor found for ${this.canonicalName}")
326-
}
327+
}
328+
329+
// We store the class references in a local map to avoid repeated Class.forName calls and reflection overhead
330+
val classCache = ConcurrentHashMap<String, Class<*>>()

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

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,12 @@ abstract class ManagedStrategy(
5252
private val testCfg: ManagedCTestConfiguration,
5353
) : Strategy(scenario), EventTracker {
5454

55-
val isGeneralPurposeModelChecking = testClass == GeneralPurposeModelCheckingWrapper::class.java
55+
val executionMode: ExecutionMode =
56+
when {
57+
testClass == GeneralPurposeModelCheckingWrapper::class.java -> ExecutionMode.GENERAL_PURPOSE_MODEL_CHECKER
58+
isInTraceDebuggerMode -> ExecutionMode.TRACE_DEBUGGER
59+
else -> ExecutionMode.DATA_STRUCTURES
60+
}
5661

5762
// The flag to enable IntelliJ IDEA plugin mode
5863
var inIdeaPluginReplayMode: Boolean = false
@@ -655,7 +660,7 @@ abstract class ManagedStrategy(
655660
// the managed strategy can construct a trace to reproduce this failure.
656661
// Let's then store the corresponding failing result and construct the trace.
657662
suddenInvocationResult = UnexpectedExceptionInvocationResult(
658-
exception,
663+
exception,
659664
runner.collectExecutionResults()
660665
)
661666
threadScheduler.abortAllThreads()
@@ -790,7 +795,7 @@ abstract class ManagedStrategy(
790795
iThread = iThread,
791796
actorId = currentActorId[iThread]!!,
792797
callStackTrace = callStackTrace[iThread]!!,
793-
stackTraceElement = CodeLocations.stackTrace(codeLocation)
798+
codeLocation = codeLocation
794799
)
795800
} else {
796801
null
@@ -833,7 +838,7 @@ abstract class ManagedStrategy(
833838
iThread = iThread,
834839
actorId = currentActorId[iThread]!!,
835840
callStackTrace = callStackTrace[iThread]!!,
836-
stackTraceElement = CodeLocations.stackTrace(codeLocation)
841+
codeLocation = codeLocation
837842
)
838843
traceCollector!!.passCodeLocation(tracePoint)
839844
}
@@ -846,7 +851,7 @@ abstract class ManagedStrategy(
846851
iThread = iThread,
847852
actorId = currentActorId[iThread]!!,
848853
callStackTrace = callStackTrace[iThread]!!,
849-
stackTraceElement = CodeLocations.stackTrace(codeLocation)
854+
codeLocation = codeLocation
850855
)
851856
} else {
852857
null
@@ -872,7 +877,7 @@ abstract class ManagedStrategy(
872877
iThread = currentThreadId,
873878
actorId = currentActorId[currentThreadId]!!,
874879
callStackTrace = callStackTrace[currentThreadId]!!,
875-
stackTraceElement = CodeLocations.stackTrace(codeLocation)
880+
codeLocation = codeLocation
876881
)
877882
traceCollector?.passCodeLocation(tracePoint)
878883
}
@@ -885,7 +890,7 @@ abstract class ManagedStrategy(
885890
iThread = iThread,
886891
actorId = currentActorId[iThread]!!,
887892
callStackTrace = callStackTrace[iThread]!!,
888-
stackTraceElement = CodeLocations.stackTrace(codeLocation)
893+
codeLocation = codeLocation
889894
)
890895
} else {
891896
null
@@ -918,7 +923,7 @@ abstract class ManagedStrategy(
918923
iThread = iThread,
919924
actorId = currentActorId[iThread]!!,
920925
callStackTrace = callStackTrace[iThread]!!,
921-
stackTraceElement = CodeLocations.stackTrace(codeLocation)
926+
codeLocation = codeLocation
922927
)
923928
traceCollector?.passCodeLocation(tracePoint)
924929
}
@@ -987,7 +992,7 @@ abstract class ManagedStrategy(
987992
actorId = currentActorId[iThread]!!,
988993
callStackTrace = callStackTrace[iThread]!!,
989994
fieldName = fieldName,
990-
stackTraceElement = CodeLocations.stackTrace(codeLocation)
995+
codeLocation = codeLocation
991996
)
992997
} else {
993998
null
@@ -1014,7 +1019,7 @@ abstract class ManagedStrategy(
10141019
actorId = currentActorId[iThread]!!,
10151020
callStackTrace = callStackTrace[iThread]!!,
10161021
fieldName = "${adornedStringRepresentation(array)}[$index]",
1017-
stackTraceElement = CodeLocations.stackTrace(codeLocation)
1022+
codeLocation = codeLocation
10181023
)
10191024
} else {
10201025
null
@@ -1055,7 +1060,7 @@ abstract class ManagedStrategy(
10551060
actorId = currentActorId[iThread]!!,
10561061
callStackTrace = callStackTrace[iThread]!!,
10571062
fieldName = fieldName,
1058-
stackTraceElement = CodeLocations.stackTrace(codeLocation)
1063+
codeLocation = codeLocation
10591064
).also {
10601065
it.initializeWrittenValue(adornedStringRepresentation(value))
10611066
}
@@ -1081,7 +1086,7 @@ abstract class ManagedStrategy(
10811086
actorId = currentActorId[iThread]!!,
10821087
callStackTrace = callStackTrace[iThread]!!,
10831088
fieldName = "${adornedStringRepresentation(array)}[$index]",
1084-
stackTraceElement = CodeLocations.stackTrace(codeLocation)
1089+
codeLocation = codeLocation
10851090
).also {
10861091
it.initializeWrittenValue(adornedStringRepresentation(value))
10871092
}
@@ -1153,7 +1158,7 @@ abstract class ManagedStrategy(
11531158
val invokeDynamic = ConstantDynamic(name, descriptor, trueBootstrapMethodHandle, *bootstrapMethodArguments)
11541159
return invokeDynamicCallSites[invokeDynamic]
11551160
}
1156-
1161+
11571162
override fun cacheInvokeDynamicCallSite(
11581163
name: String,
11591164
descriptor: String,
@@ -1545,7 +1550,7 @@ abstract class ManagedStrategy(
15451550
className = className,
15461551
methodName = methodName,
15471552
callStackTrace = callStackTrace,
1548-
stackTraceElement = CodeLocations.stackTrace(codeLocation),
1553+
codeLocation = codeLocation
15491554
)
15501555
// handle non-atomic methods
15511556
if (atomicMethodDescriptor == null) {
@@ -2082,6 +2087,17 @@ private val BlockingReason.obstructionFreedomViolationMessage: String get() = wh
20822087
is BlockingReason.Suspended -> OBSTRUCTION_FREEDOM_SUSPEND_VIOLATION_MESSAGE
20832088
}
20842089

2090+
/**
2091+
* @param id specifies the string literal that will be parsed on the plugin side,
2092+
* thus, it should never be changed unconsciously. The plugin will use this values
2093+
* to determine what kind of UI to show to the user.
2094+
*/
2095+
enum class ExecutionMode(val id: String) {
2096+
DATA_STRUCTURES("DATA_STRUCTURES"),
2097+
GENERAL_PURPOSE_MODEL_CHECKER("GENERAL_PURPOSE_MODEL_CHECKER"),
2098+
TRACE_DEBUGGER("TRACE_DEBUGGER")
2099+
}
2100+
20852101
private const val OBSTRUCTION_FREEDOM_SPINLOCK_VIOLATION_MESSAGE =
20862102
"The algorithm should be non-blocking, but an active lock is detected"
20872103

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
package org.jetbrains.kotlinx.lincheck.strategy.managed
1212

13+
import org.jetbrains.kotlinx.lincheck.classCache
1314
import org.jetbrains.kotlinx.lincheck.findField
1415
import org.jetbrains.kotlinx.lincheck.strategy.managed.SnapshotTracker.MemoryNode.*
1516
import org.jetbrains.kotlinx.lincheck.util.*
@@ -19,6 +20,7 @@ import java.lang.reflect.Modifier
1920
import java.util.Collections
2021
import java.util.IdentityHashMap
2122
import java.util.Stack
23+
import java.util.concurrent.ConcurrentHashMap
2224
import java.util.concurrent.atomic.AtomicIntegerArray
2325
import java.util.concurrent.atomic.AtomicLongArray
2426
import java.util.concurrent.atomic.AtomicReferenceArray
@@ -57,7 +59,7 @@ class SnapshotTracker {
5759
if (obj != null && !isTracked(obj)) return
5860
trackFieldImpl(
5961
obj = obj,
60-
clazz = getDeclaringClass(obj, Class.forName(accessClassName), fieldName),
62+
clazz = getDeclaringClass(obj, accessClassName, fieldName),
6163
fieldName = fieldName
6264
)
6365
}
@@ -251,14 +253,17 @@ class SnapshotTracker {
251253
)
252254
}
253255

254-
private fun getDeclaringClass(obj: Any?, clazz: Class<*>, fieldName: String): Class<*> {
255-
return if (obj != null) {
256+
private fun getDeclaringClass(obj: Any?, className: String, fieldName: String): Class<*> {
257+
val clazz = classCache.getOrPut(className) { Class.forName(className) }
258+
return getDeclaringClass(obj, clazz, fieldName)
259+
}
260+
261+
private fun getDeclaringClass(obj: Any?, clazz: Class<*>, fieldName: String): Class<*> =
262+
if (obj != null) {
256263
clazz
257-
}
258-
else {
264+
} else {
259265
clazz.findField(fieldName).declaringClass
260266
}
261-
}
262267

263268
private fun createFieldNode(obj: Any?, field: Field, value: Any?): MemoryNode {
264269
return if (obj == null) {

0 commit comments

Comments
 (0)