diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/runner/ParallelThreadsRunner.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/runner/ParallelThreadsRunner.kt index 1c89ef4bb..bb379a315 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/runner/ParallelThreadsRunner.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/runner/ParallelThreadsRunner.kt @@ -46,7 +46,7 @@ internal open class ParallelThreadsRunner( private val useClocks: UseClocks // specifies whether `HBClock`-s should always be used or with some probability ) : Runner(strategy, testClass, validationFunction, stateRepresentationFunction) { private val testName = testClass.simpleName - private val executor = FixedActiveThreadsExecutor(testName, scenario.nThreads) // should be closed in `close()` + internal val executor = FixedActiveThreadsExecutor(testName, scenario.nThreads) // should be closed in `close()` private val spinners = SpinnerGroup(executor.threads.size) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt index e452e196d..72db81a86 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt @@ -18,7 +18,7 @@ import org.jetbrains.kotlinx.lincheck.runner.* import org.jetbrains.kotlinx.lincheck.runner.ExecutionPart.* import org.jetbrains.kotlinx.lincheck.strategy.* import org.jetbrains.kotlinx.lincheck.transformation.* -import org.jetbrains.kotlinx.lincheck.transformation.LincheckClassFileTransformer.ensureClassIsTransformed +import org.jetbrains.kotlinx.lincheck.transformation.LincheckClassFileTransformer.ensureClassAndAllSuperClassesAreTransformed import org.jetbrains.kotlinx.lincheck.util.* import org.jetbrains.kotlinx.lincheck.verifier.* import sun.misc.* @@ -216,7 +216,12 @@ abstract class ManagedStrategy( // In case the runner detects a deadlock, some threads can still be in an active state, // simultaneously adding events to the TraceCollector, which leads to an inconsistent trace. // Therefore, if the runner detects deadlock, we don't even try to collect trace. - if (loggedResults is RunnerTimeoutInvocationResult) return null + if (loggedResults is RunnerTimeoutInvocationResult) { + StringBuilder() + .also { it.appendTrace(DeadlockOrLivelockFailure(scenario, emptyMap(), Trace(traceCollector!!.trace)), null, Trace(traceCollector!!.trace), emptyMap()) } + .also { println(it.toString()) } + return null + } val sameResultTypes = loggedResults.javaClass == failingResult.javaClass val sameResults = loggedResults !is CompletedInvocationResult || failingResult !is CompletedInvocationResult || loggedResults.results == failingResult.results check(sameResultTypes && sameResults) { @@ -428,6 +433,19 @@ abstract class ManagedStrategy( spinners[iThread].spinWaitUntil { // Finish forcibly if an error occurred and we already have an `InvocationResult`. if (suddenInvocationResult != null) throw ForcibleExecutionFinishError + val t = (runner as ParallelThreadsRunner).executor.threads[currentThread] + if (t.state == Thread.State.BLOCKED || t.state == Thread.State.WAITING) { + println(t.threadId) + t.stackTrace.forEach { + println(it) + } + synchronized(this) { + if (t.threadId == currentThread) { + // We need a deterministic wait to decide + switchCurrentThread(currentThread, SwitchReason.STRATEGY_SWITCH, true) + } + } + } currentThread == iThread } } @@ -640,7 +658,7 @@ abstract class ManagedStrategy( override fun beforeReadFieldStatic(className: String, fieldName: String, codeLocation: Int) = runInIgnoredSection { // We need to ensure all the classes related to the reading object are instrumented. // The following call checks all the static fields. - ensureClassIsTransformed(className.canonicalClassName) + ensureClassAndAllSuperClassesAreTransformed(className.canonicalClassName) // TODO: remove the isFinal check if (FinalFields.isFinalField(className, fieldName)) return@runInIgnoredSection @@ -786,7 +804,7 @@ abstract class ManagedStrategy( } override fun beforeNewObjectCreation(className: String) = runInIgnoredSection { - ensureClassIsTransformed(className) + ensureClassAndAllSuperClassesAreTransformed(className) } override fun afterNewObjectCreation(obj: Any) { @@ -855,7 +873,7 @@ abstract class ManagedStrategy( null -> { if (owner == null) { runInIgnoredSection { - ensureClassIsTransformed(className.canonicalClassName) + ensureClassAndAllSuperClassesAreTransformed(className.canonicalClassName) } } if (collectTrace) { diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckClassVisitor.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckClassVisitor.kt index abffd3701..a77bf66e5 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckClassVisitor.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckClassVisitor.kt @@ -873,85 +873,118 @@ internal class LincheckClassVisitor( }, code = { val argumentTypes = getArgumentTypes(desc) - - if (argumentTypes.size == 3) { - // we are in a single value overload - val nextType = argumentTypes.last() - val nextValueLocal = newLocal(OBJECT_TYPE) - - val currentType = argumentTypes[argumentTypes.lastIndex - 1] - val currenValueLocal = newLocal(OBJECT_TYPE) - - val receiverLocal = newLocal(OBJECT_TYPE) - // STACK: nextValue, currentValue, receiver - box(nextType) - // STACK: boxedNextValue, currentValue, receiver - storeLocal(nextValueLocal) - // STACK: currentValue, receiver - box(currentType) - // STACK: boxedCurrentValue, receiver - storeLocal(currenValueLocal) - // STACK: receiver - storeLocal(receiverLocal) - // STACK: - loadLocal(receiverLocal) - // STACK: receiver - loadLocal(currenValueLocal) - // STACK: boxedCurrentValue, receiver - unbox(currentType) - // STACK: currentValue, receiver - loadLocal(nextValueLocal) - // STACK: boxedNextValue, currentValue, receiver - unbox(nextType) - // STACK: nextValue, currentValue, receiver - visitMethodInsn(opcode, owner, name, desc, itf) - loadLocal(receiverLocal) - // STACK: receiver - loadLocal(nextValueLocal) - // STACK: boxedNextValue, receiver - invokeStatic(Injections::onWriteToObjectFieldOrArrayCell) - } else { - // we are in an array version overload (with index) *.compareAndSet(index, currentValue, nextValue) - val nextType = argumentTypes.last() - val nextValueLocal = newLocal(OBJECT_TYPE) - - val currentType = argumentTypes[argumentTypes.lastIndex - 1] - val currenValueLocal = newLocal(OBJECT_TYPE) - - val indexLocal = newLocal(INT_TYPE) - val receiverLocal = newLocal(OBJECT_TYPE) - // STACK: nextValue, currentValue, index, receiver - box(nextType) - // STACK: boxedNextValue, currentValue, index, receiver - storeLocal(nextValueLocal) - // STACK: currentValue, index, receiver - box(currentType) - // STACK: boxedCurrentValue, index, receiver - storeLocal(currenValueLocal) - // STACK: index, receiver - storeLocal(indexLocal) - // STACK: receiver - storeLocal(receiverLocal) - // STACK: - loadLocal(receiverLocal) - // STACK: receiver - loadLocal(indexLocal) - // STACK: index, receiver - loadLocal(currenValueLocal) - // STACK: boxedCurrentValue, index, receiver - unbox(currentType) - // STACK: currentValue, index, receiver - loadLocal(nextValueLocal) - // STACK: boxedNextValue, currentValue, index, receiver - unbox(nextType) - // STACK: nextValue, currentValue, index, receiver - visitMethodInsn(opcode, owner, name, desc, itf) - // STACK: cas-result - loadLocal(receiverLocal) - // STACK: receiver, cas-result - loadLocal(nextValueLocal) - // STACK: boxedNextValue, receiver, cas-result - invokeStatic(Injections::onWriteToObjectFieldOrArrayCell) + when (argumentTypes.size) { + 2 -> { // Method .compareAndSet(value, nextValue) - used for static VarHandles + // We are in a single value overload + val nextType = argumentTypes.last() + val nextValueLocal = newLocal(OBJECT_TYPE) + + val currentType = argumentTypes[argumentTypes.lastIndex - 1] + val currenValueLocal = newLocal(OBJECT_TYPE) + + // STACK: nextValue, currentValue + box(nextType) + // STACK: boxedNextValue, currentValue + storeLocal(nextValueLocal) + // STACK: currentValue + box(currentType) + // STACK: boxedCurrentValue + storeLocal(currenValueLocal) + // STACK: + loadLocal(currenValueLocal) + // STACK: boxedCurrentValue + unbox(currentType) + // STACK: currentValue + loadLocal(nextValueLocal) + // STACK: boxedNextValue, currentValue + unbox(nextType) + // STACK: nextValue, currentValue + visitMethodInsn(opcode, owner, name, desc, itf) + // STACK: cas-result + loadLocal(nextValueLocal) + // STACK: cas-result, boxedNextValue + invokeStatic(Injections::onWriteToObjectFieldOrArrayCell) + } + 3 -> { + // We are in a single value overload + val nextType = argumentTypes.last() + val nextValueLocal = newLocal(OBJECT_TYPE) + + val currentType = argumentTypes[argumentTypes.lastIndex - 1] + val currenValueLocal = newLocal(OBJECT_TYPE) + + val receiverLocal = newLocal(OBJECT_TYPE) + // STACK: nextValue, currentValue, receiver + box(nextType) + // STACK: boxedNextValue, currentValue, receiver + storeLocal(nextValueLocal) + // STACK: currentValue, receiver + box(currentType) + // STACK: boxedCurrentValue, receiver + storeLocal(currenValueLocal) + // STACK: receiver + storeLocal(receiverLocal) + // STACK: + loadLocal(receiverLocal) + // STACK: receiver + loadLocal(currenValueLocal) + // STACK: boxedCurrentValue, receiver + unbox(currentType) + // STACK: currentValue, receiver + loadLocal(nextValueLocal) + // STACK: boxedNextValue, currentValue, receiver + unbox(nextType) + // STACK: nextValue, currentValue, receiver + visitMethodInsn(opcode, owner, name, desc, itf) + loadLocal(receiverLocal) + // STACK: receiver + loadLocal(nextValueLocal) + // STACK: boxedNextValue, receiver + invokeStatic(Injections::onWriteToObjectFieldOrArrayCell) + } + else -> { + // we are in an array version overload (with index) *.compareAndSet(index, currentValue, nextValue) + val nextType = argumentTypes.last() + val nextValueLocal = newLocal(OBJECT_TYPE) + + val currentType = argumentTypes[argumentTypes.lastIndex - 1] + val currenValueLocal = newLocal(OBJECT_TYPE) + + val indexLocal = newLocal(INT_TYPE) + val receiverLocal = newLocal(OBJECT_TYPE) + // STACK: nextValue, currentValue, index, receiver + box(nextType) + // STACK: boxedNextValue, currentValue, index, receiver + storeLocal(nextValueLocal) + // STACK: currentValue, index, receiver + box(currentType) + // STACK: boxedCurrentValue, index, receiver + storeLocal(currenValueLocal) + // STACK: index, receiver + storeLocal(indexLocal) + // STACK: receiver + storeLocal(receiverLocal) + // STACK: + loadLocal(receiverLocal) + // STACK: receiver + loadLocal(indexLocal) + // STACK: index, receiver + loadLocal(currenValueLocal) + // STACK: boxedCurrentValue, index, receiver + unbox(currentType) + // STACK: currentValue, index, receiver + loadLocal(nextValueLocal) + // STACK: boxedNextValue, currentValue, index, receiver + unbox(nextType) + // STACK: nextValue, currentValue, index, receiver + visitMethodInsn(opcode, owner, name, desc, itf) + // STACK: cas-result + loadLocal(receiverLocal) + // STACK: receiver, cas-result + loadLocal(nextValueLocal) + // STACK: boxedNextValue, receiver, cas-result + invokeStatic(Injections::onWriteToObjectFieldOrArrayCell) + } } } ) @@ -972,62 +1005,84 @@ internal class LincheckClassVisitor( code = { val argumentTypes = getArgumentTypes(desc) // we are in a single value overload - if (argumentTypes.size == 2) { - // STACK: value, receiver - val argumentType = argumentTypes.last() - val valueLocal = newLocal(OBJECT_TYPE) - val receiverLocal = newLocal(OBJECT_TYPE) - - // STACK: value, receiver - box(argumentType) - storeLocal(valueLocal) - // STACK: boxedValue, receiver - storeLocal(receiverLocal) - // STACK: - loadLocal(receiverLocal) - // STACK: receiver - loadLocal(valueLocal) - // STACK: boxedValue, receiver - unbox(argumentType) - // STACK: value, receiver - visitMethodInsn(opcode, owner, name, desc, itf) - // STACK: - loadLocal(receiverLocal) - // STACK: receiver - loadLocal(valueLocal) - // STACK: boxedValue, receiver - invokeStatic(Injections::onWriteToObjectFieldOrArrayCell) - } else { - // we are in an array version overload (with index) varHandle.set(value, index) - val argumentType = argumentTypes.last() - val valueLocal = newLocal(OBJECT_TYPE) - val indexLocal = newLocal(INT_TYPE) - val receiverLocal = newLocal(OBJECT_TYPE) - - // STACK: value, index, receiver - box(argumentType) - // STACK: boxedValue, index, receiver - storeLocal(valueLocal) - // STACK: index, receiver - storeLocal(indexLocal) - // STACK: receiver - storeLocal(receiverLocal) - // STACK: - loadLocal(receiverLocal) - // STACK: receiver - loadLocal(indexLocal) - // STACK: index, receiver - loadLocal(valueLocal) - // STACK: boxedValue, index, receiver - unbox(argumentType) - // STACK: value, index, receiver - visitMethodInsn(opcode, owner, name, desc, itf) - // STACK: - loadLocal(receiverLocal) - // STACK: receiver - loadLocal(valueLocal) - // STACK: boxedValue, receiver - invokeStatic(Injections::onWriteToObjectFieldOrArrayCell) + when (argumentTypes.size) { + 1 -> { + // STACK: value + val argumentType = argumentTypes.last() + val valueLocal = newLocal(OBJECT_TYPE) + // STACK: value + box(argumentType) + // STACK: boxedValue + storeLocal(valueLocal) + // STACK: + loadLocal(valueLocal) + // STACK: boxedValue + unbox(argumentType) + // STACK: value + visitMethodInsn(opcode, owner, name, desc, itf) + // STACK: + loadLocal(valueLocal) + // STACK: boxedValue + invokeStatic(Injections::onWriteToObjectFieldOrArrayCell) + } + 2 -> { + // STACK: value, receiver + val argumentType = argumentTypes.last() + val valueLocal = newLocal(OBJECT_TYPE) + val receiverLocal = newLocal(OBJECT_TYPE) + + // STACK: value, receiver + box(argumentType) + storeLocal(valueLocal) + // STACK: boxedValue, receiver + storeLocal(receiverLocal) + // STACK: + loadLocal(receiverLocal) + // STACK: receiver + loadLocal(valueLocal) + // STACK: boxedValue, receiver + unbox(argumentType) + // STACK: value, receiver + visitMethodInsn(opcode, owner, name, desc, itf) + // STACK: + loadLocal(receiverLocal) + // STACK: receiver + loadLocal(valueLocal) + // STACK: boxedValue, receiver + invokeStatic(Injections::onWriteToObjectFieldOrArrayCell) + } + else -> { + // we are in an array version overload (with index) varHandle.set(value, index) + val argumentType = argumentTypes.last() + val valueLocal = newLocal(OBJECT_TYPE) + val indexLocal = newLocal(INT_TYPE) + val receiverLocal = newLocal(OBJECT_TYPE) + + // STACK: value, index, receiver + box(argumentType) + // STACK: boxedValue, index, receiver + storeLocal(valueLocal) + // STACK: index, receiver + storeLocal(indexLocal) + // STACK: receiver + storeLocal(receiverLocal) + // STACK: + loadLocal(receiverLocal) + // STACK: receiver + loadLocal(indexLocal) + // STACK: index, receiver + loadLocal(valueLocal) + // STACK: boxedValue, index, receiver + unbox(argumentType) + // STACK: value, index, receiver + visitMethodInsn(opcode, owner, name, desc, itf) + // STACK: + loadLocal(receiverLocal) + // STACK: receiver + loadLocal(valueLocal) + // STACK: boxedValue, receiver + invokeStatic(Injections::onWriteToObjectFieldOrArrayCell) + } } } ) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckJavaAgent.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckJavaAgent.kt index f87ca164c..162f5912c 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckJavaAgent.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckJavaAgent.kt @@ -15,6 +15,7 @@ import org.jetbrains.kotlinx.lincheck.* import org.jetbrains.kotlinx.lincheck.UnsafeHolder.UNSAFE import org.objectweb.asm.* import java.lang.instrument.* +import java.lang.invoke.MethodHandles import java.lang.module.* import java.lang.reflect.* import java.lang.reflect.Modifier.* @@ -50,18 +51,23 @@ object LincheckClassFileTransformer : ClassFileTransformer { private val instrumentedClasses = HashSet() - fun ensureClassIsTransformed(className: String) { + fun ensureClassAndAllSuperClassesAreTransformed(className: String) { if (className in instrumentedClasses) return // already instrumented - ensureClassIsTransformed(Class.forName(className), newSetFromMap(IdentityHashMap())) + ensureClassAndAllSuperClassesAreTransformed(Class.forName(className), newSetFromMap(IdentityHashMap())) } - fun ensureClassIsTransformed(clazz: Class<*>) { + fun ensureClassAndAllSuperClassesAreTransformed(clazz: Class<*>) { if (clazz.name in instrumentedClasses) return // already instrumented - ensureClassIsTransformed(clazz, newSetFromMap(IdentityHashMap())) + ensureClassAndAllSuperClassesAreTransformed(clazz, newSetFromMap(IdentityHashMap())) } - fun ensureObjectIsTransformed(testInstance: Any) = + var flag = false + + fun ensureObjectIsTransformed(testInstance: Any) { + if (flag) return + flag = true ensureObjectIsTransformedImpl(testInstance, newSetFromMap(IdentityHashMap())) + } private fun ensureObjectIsTransformedImpl(obj: Any, processedObjects: MutableSet) { if (processedObjects.contains(obj)) return @@ -69,7 +75,7 @@ object LincheckClassFileTransformer : ClassFileTransformer { var clazz: Class<*> = obj.javaClass - ensureClassIsTransformed(clazz) + ensureClassAndAllSuperClassesAreTransformed(clazz) while (true) { clazz.declaredFields @@ -81,7 +87,7 @@ object LincheckClassFileTransformer : ClassFileTransformer { } } - private fun ensureClassIsTransformed(clazz: Class<*>, processedObjects: MutableSet) { + private fun ensureClassAndAllSuperClassesAreTransformed(clazz: Class<*>, processedObjects: MutableSet) { if (instrumentation.isModifiableClass(clazz) && shouldTransform(clazz.name, transformationMode)) { instrumentedClasses += clazz.name println("Retransform! $clazz") @@ -97,7 +103,7 @@ object LincheckClassFileTransformer : ClassFileTransformer { .forEach { ensureObjectIsTransformedImpl(it, processedObjects) } clazz.superclass?.let { if (it.name in instrumentedClasses) return // already instrumented - ensureClassIsTransformed(it, processedObjects) + ensureClassAndAllSuperClassesAreTransformed(it, processedObjects) } } @@ -236,22 +242,6 @@ internal object TransformationInjectionsInitializer { val bootstrapJarFile = JarFile(ClassLoader.getSystemResource("bootstrap.jar").file) instrumentation.appendToBootstrapClassLoaderSearch(bootstrapJarFile) -// -// ModuleFinder.ofSystem().find("java.base").get().open().use { reader -> -// reader.list() -// // Filter classes -// .filter { it.endsWith(".class") && it != "module-info.class" } -// .map { it.removeSuffix(".class").replace("/", ".") } -// // Trampoline must not be defined by the bootstrap classloader -// .filter { it != "sun.reflect.misc.Trampoline" } -// .forEach { -// try { -// Class.forName(it) -// } catch (t: Throwable) { -// throw IllegalStateException("Cannot initialize class $it", t) -// } -// } -// } initialized = true }