in jvm-agent/src/main/org/jetbrains/lincheck/jvm/agent/LincheckJavaAgent.kt [204:268]
fun install(instrumentationMode: InstrumentationMode) {
this.instrumentationMode = instrumentationMode
setInstrumentationStrategy()
// The bytecode injections must be loaded with the bootstrap class loader,
// as the `java.base` module is loaded with it. To achieve that, we pack the
// classes related to the bytecode injections in a separate JAR (see the
// "bootstrap" project module), and add it to the bootstrap classpath.
if (!isBootstrapJarAddedToClasspath) { // don't do this twice.
appendBootstrapJarToClassLoaderSearch()
isBootstrapJarAddedToClasspath = true
}
// Add the Lincheck bytecode transformer to this JVM instance,
// allowing already loaded classes re-transformation.
instrumentation.addTransformer(LincheckClassFileTransformer, true)
// The transformation logic depends on the testing strategy.
when {
// In the stress testing mode, we use an additional optimization.
// In this mode, Lincheck needs to track only the coroutine suspensions.
// As an optimization, we remember the set of loaded classes that actually
// have suspension points, so later we can re-transform only those classes.
instrumentationMode == STRESS -> {
// If `INSTRUMENT_ALL_CLASSES` is explicitly set ---
// disable the optimization and re-transform all classes
if (INSTRUMENT_ALL_CLASSES) {
retransformClasses(getLoadedClassesToInstrument())
return
}
// Perform optimized re-transformation.
check(instrumentedClasses.isEmpty())
val classes = getLoadedClassesToInstrument().filter {
val canonicalClassName = it.name
// new classes that were loaded after the latest STRESS mode re-transformation
!transformedClassesCache.containsKey(canonicalClassName) ||
// old classes that were already loaded before and have coroutine method calls inside
canonicalClassName in coroutineCallingClasses
}
retransformClasses(classes)
instrumentedClasses.addAll(classes.map { it.name })
}
// In EAGER mode, we re-transform all the classes that were already loaded before the agent installation.
// New classes will be transformed automatically.
instrumentationStrategy == InstrumentationStrategy.EAGER -> {
retransformClasses(getLoadedClassesToInstrument())
}
// In a lazy transformation mode, Lincheck processes classes lazily, only when they are used.
instrumentationStrategy == InstrumentationStrategy.LAZY -> {
// Clear the set of instrumented classes in case something get wrong during `uninstall`.
// For instance, it is possible that Lincheck detects a deadlock, `uninstall` is called,
// but one of the "deadlocked" thread calls `ensureClassHierarchyIsTransformed` after that,
// adding a new class to `instrumentedClasses`.
// TODO: distinguish different runs by associating `instrumentedClasses` with `EventTracker`.
instrumentedClasses.clear()
// Transform some predefined classes eagerly on start,
// because often it's the only place when we can do it
val eagerlyTransformedClasses = getLoadedClassesToInstrument()
.filter { isEagerlyInstrumentedClass(it.name) }
.toTypedArray()
retransformClasses(eagerlyTransformedClasses.asList())
instrumentedClasses.addAll(eagerlyTransformedClasses.map { it.name })
}
}
}