diff --git a/core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt b/core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt index 49cc31a3f..b00d341b6 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt @@ -10,7 +10,7 @@ import org.vorpal.research.kthelper.logging.log import java.nio.file.Path import kotlin.io.path.readLines -val Config.outputDirectory: Path get() = getPathValue("kex", "outputDir")!! +val Config.outputDirectory: Path get() = getPathValue("kex", "outputDir")!!.normalize() val Config.instrumentedCodeDirectory: Path get() { @@ -19,7 +19,7 @@ val Config.instrumentedCodeDirectory: Path if (!getBooleanValue("debug", "saveInstrumentedCode", false)) { deleteOnExit(instrumentedCodeDir) } - return instrumentedCodeDir + return instrumentedCodeDir.normalize() } val Config.compiledCodeDirectory: Path @@ -29,21 +29,21 @@ val Config.compiledCodeDirectory: Path if (!getBooleanValue("debug", "saveCompiledCode", false)) { deleteOnExit(compiledCodeDir) } - return compiledCodeDir + return compiledCodeDir.normalize() } val Config.testcaseDirectory: Path get() { val testcaseDirName = getPathValue("testGen", "testsDir", "tests") - return outputDirectory.resolve(testcaseDirName).toAbsolutePath() + return outputDirectory.resolve(testcaseDirName).toAbsolutePath().normalize() } val Config.runtimeDepsPath: Path? - get() = getPathValue("kex", "runtimeDepsPath") + get() = getPathValue("kex", "runtimeDepsPath")?.normalize() val Config.libPath: Path? get() = getStringValue("kex", "libPath")?.let { - runtimeDepsPath?.resolve(it) + runtimeDepsPath?.resolve(it)?.normalize() } fun getRuntime(): Container? { diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/CrashReproductionChecker.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/CrashReproductionChecker.kt index 82a8c6802..8f9f1c5d1 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/CrashReproductionChecker.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/CrashReproductionChecker.kt @@ -2,14 +2,20 @@ package org.vorpal.research.kex.asm.analysis.crash import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.isActive import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull +import kotlinx.coroutines.yield import org.vorpal.research.kex.ExecutionContext import org.vorpal.research.kex.asm.analysis.crash.precondition.ConstraintExceptionPrecondition import org.vorpal.research.kex.asm.analysis.crash.precondition.ConstraintExceptionPreconditionBuilder import org.vorpal.research.kex.asm.analysis.crash.precondition.DescriptorExceptionPreconditionBuilder import org.vorpal.research.kex.asm.analysis.crash.precondition.ExceptionPreconditionBuilder import org.vorpal.research.kex.asm.analysis.crash.precondition.ExceptionPreconditionBuilderImpl +import org.vorpal.research.kex.asm.analysis.crash.precondition.ExceptionPreconditionChannel +import org.vorpal.research.kex.asm.analysis.crash.precondition.ExceptionPreconditionProvider +import org.vorpal.research.kex.asm.analysis.crash.precondition.ExceptionPreconditionReceiver import org.vorpal.research.kex.asm.analysis.symbolic.ConditionCheckQuery import org.vorpal.research.kex.asm.analysis.symbolic.DefaultCallResolver import org.vorpal.research.kex.asm.analysis.symbolic.EmptyQuery @@ -19,6 +25,7 @@ import org.vorpal.research.kex.asm.analysis.symbolic.SymbolicPathSelector import org.vorpal.research.kex.asm.analysis.symbolic.SymbolicTraverser import org.vorpal.research.kex.asm.analysis.symbolic.UpdateAction import org.vorpal.research.kex.asm.analysis.symbolic.UpdateAndReportQuery +import org.vorpal.research.kex.asm.analysis.util.checkAsyncIncremental import org.vorpal.research.kex.asm.analysis.util.checkAsyncIncrementalAndSlice import org.vorpal.research.kex.compile.CompilationException import org.vorpal.research.kex.config.kexConfig @@ -54,14 +61,11 @@ import org.vorpal.research.kfg.nullptrClass import org.vorpal.research.kfg.runtimeException import org.vorpal.research.kthelper.assert.ktassert import org.vorpal.research.kthelper.logging.log -import kotlin.io.path.ExperimentalPathApi -import kotlin.io.path.deleteRecursively -import kotlin.time.Duration +import java.nio.file.Files +import kotlin.coroutines.coroutineContext +import kotlin.io.path.isDirectory import kotlin.time.Duration.Companion.seconds import kotlin.time.ExperimentalTime -import kotlin.time.TimedValue -import kotlin.time.measureTimedValue - operator fun ClassManager.get(frame: StackTraceElement): Method { val entryClass = this[frame.className.asmString] @@ -130,15 +134,31 @@ private fun StackTrace.targetInstructions(context: ExecutionContext): Set { + abstract val preconditions: Map +} + +data class DescriptorCrashReproductionResult( + override val preconditions: Map> +) : CrashReproductionResult>() + +data class ConstraintCrashReproductionResult( + override val preconditions: Map +) : CrashReproductionResult() + + +@Suppress("MemberVisibilityCanBePrivate") +abstract class AbstractCrashReproductionChecker( ctx: ExecutionContext, - @Suppress("MemberVisibilityCanBePrivate") - val stackTrace: StackTrace, - private val targetInstructions: Set, - private val preconditionBuilder: ExceptionPreconditionBuilder, - private val reproductionChecker: ExceptionReproductionChecker, - private val shouldStopAfterFirst: Boolean + stackTrace: StackTrace, + protected val targetInstructions: Set, + protected val preconditionProvider: ExceptionPreconditionProvider, + protected val preconditionReceiver: ExceptionPreconditionReceiver, + protected val reproductionChecker: ExceptionReproductionChecker, + protected val shouldStopAfterFirst: Boolean ) : SymbolicTraverser(ctx, ctx.cm[stackTrace.stackTraceLines.last()]) { + abstract val result: CrashReproductionResult + override val pathSelector: SymbolicPathSelector = RandomizedDistancePathSelector(ctx, rootMethod, targetInstructions, stackTrace) override val callResolver: SymbolicCallResolver = StackTraceCallResolver( @@ -146,10 +166,7 @@ class CrashReproductionChecker( ) override val invokeDynamicResolver: SymbolicInvokeDynamicResolver = DefaultCallResolver(ctx) - private val generatedTestClasses = mutableSetOf() - private val descriptors = mutableMapOf>() - private val preconditions = mutableMapOf() - private var lastPrecondition = mutableMapOf, ConstraintExceptionPrecondition>() + protected val resultsInner = mutableMapOf() init { ktassert( @@ -158,209 +175,55 @@ class CrashReproductionChecker( ) } - @Suppress("unused") - companion object { - - data class CrashReproductionResult( - val testClasses: Set, - val descriptors: Map>, - val preconditions: Map - ) { - companion object { - fun empty() = CrashReproductionResult(emptySet(), emptyMap(), emptyMap()) - } - } - - private suspend fun runChecker( - context: ExecutionContext, - stackTrace: StackTrace, - targetInstructions: Set, - preconditionBuilder: ExceptionPreconditionBuilder, - reproductionChecker: ExceptionReproductionChecker, - shouldStopAfterFirst: Boolean - ): CrashReproductionResult { - val checker = CrashReproductionChecker( - context, - stackTrace, - targetInstructions, - preconditionBuilder, - reproductionChecker, - shouldStopAfterFirst - ) - checker.analyze() - return CrashReproductionResult( - checker.generatedTestClasses, - checker.descriptors, - checker.preconditions - ) - } - - @OptIn(ExperimentalTime::class) - private suspend fun runCheckerWithTimeLimit( - timeLimit: Duration, - context: ExecutionContext, - stackTrace: StackTrace, - targetInstructions: Set, - preconditionBuilder: ExceptionPreconditionBuilder, - reproductionChecker: ExceptionReproductionChecker, - shouldStopAfterFirst: Boolean - ): TimedValue = measureTimedValue { - val checker = CrashReproductionChecker( - context, - stackTrace, - targetInstructions, - preconditionBuilder, - reproductionChecker, - shouldStopAfterFirst - ) - withTimeoutOrNull(timeLimit) { - checker.analyze() - } - CrashReproductionResult( - checker.generatedTestClasses, - checker.descriptors, - checker.preconditions - ) - } - - @ExperimentalTime - @DelicateCoroutinesApi - fun run(context: ExecutionContext, stackTrace: StackTrace): Set { - val timeLimit = kexConfig.getIntValue("crash", "timeLimit", 100) - val stopAfterFirstCrash = kexConfig.getBooleanValue("crash", "stopAfterFirstCrash", false) - - val coroutineContext = newFixedThreadPoolContextWithMDC(1, "crash-dispatcher") - return runBlocking(coroutineContext) { - withTimeoutOrNull(timeLimit.seconds) { - async { - runChecker( - context, - stackTrace, - stackTrace.targetInstructions(context), - ExceptionPreconditionBuilderImpl(context, stackTrace.targetException(context)), - ExceptionReproductionCheckerImpl(context, stackTrace), - stopAfterFirstCrash - ).testClasses - }.await() - } ?: emptySet() - } - } - - @ExperimentalTime - @DelicateCoroutinesApi - fun runWithDescriptorPreconditions(context: ExecutionContext, stackTrace: StackTrace): Set = - runIteratively(context, stackTrace) { result -> - DescriptorExceptionPreconditionBuilder( - context, - stackTrace.targetException(context), - result.descriptors.values.toSet() - ) - } - - @ExperimentalTime - @DelicateCoroutinesApi - fun runWithConstraintPreconditions(context: ExecutionContext, stackTrace: StackTrace): Set = - runIteratively(context, stackTrace) { result -> - ConstraintExceptionPreconditionBuilder( - context, - stackTrace.targetException(context), - result.preconditions.values.toSet() - ) - } - - @OptIn(ExperimentalPathApi::class) - @ExperimentalTime - @DelicateCoroutinesApi - fun runIteratively( - context: ExecutionContext, - stackTrace: StackTrace, - preconditionBuilder: (CrashReproductionResult) -> ExceptionPreconditionBuilder - ): Set { - val globalTimeLimit = kexConfig.getIntValue("crash", "globalTimeLimit", 100).seconds - val localTimeLimit = kexConfig.getIntValue("crash", "localTimeLimit")?.seconds - ?: (globalTimeLimit / stackTrace.size) - val stopAfterFirstCrash = kexConfig.getBooleanValue("crash", "stopAfterFirstCrash", false) - - val coroutineContext = newFixedThreadPoolContextWithMDC(1, "crash-dispatcher") - val firstLine = stackTrace.stackTraceLines.first() - return runBlocking(coroutineContext) { - val result = withTimeoutOrNull(globalTimeLimit) { - var (result, _) = runCheckerWithTimeLimit( - localTimeLimit, - context, - StackTrace(stackTrace.firstLine, listOf(firstLine)), - stackTrace.targetInstructions(context), - ExceptionPreconditionBuilderImpl(context, stackTrace.targetException(context)), - ExceptionReproductionCheckerImpl( - context, - StackTrace(stackTrace.firstLine, listOf(firstLine)) - ), - stopAfterFirstCrash - ) - - for ((line, prev) in stackTrace.stackTraceLines.drop(1) - .zip(stackTrace.stackTraceLines.dropLast(1))) { - if (result.testClasses.isEmpty()) break - kexConfig.testcaseDirectory.deleteRecursively() - ReflectionUtilsPrinter.invalidateAll() - - val targetInstructions = context.cm[line].body.flatten() - .filter { it.location.line == line.lineNumber } - .filterTo(mutableSetOf()) { it is CallInst && it.method.name == prev.methodName } - - val (currentResult, _) = runCheckerWithTimeLimit( - localTimeLimit, - context, - StackTrace(stackTrace.firstLine, listOf(line)), - targetInstructions, - preconditionBuilder(result), - ExceptionReproductionCheckerImpl( - context, - StackTrace(stackTrace.firstLine, listOf(line)), - ), - stopAfterFirstCrash - ) + private suspend fun checkNewPreconditions() { + if (!preconditionProvider.hasNewPreconditions) return + val newPreconditions = preconditionProvider.getNewPreconditions() + if (newPreconditions.isEmpty()) return - val reproductionChecker = ExceptionReproductionCheckerImpl( - context, - StackTrace(stackTrace.firstLine, stackTrace.stackTraceLines.takeWhile { it != line } + line) - ) - val filteredTestCases = currentResult.testClasses.filterTo(mutableSetOf()) { - reproductionChecker.isReproduced(it) - } - result = currentResult.copy( - testClasses = filteredTestCases, - descriptors = currentResult.descriptors.filterKeys { it in filteredTestCases }, - preconditions = currentResult.preconditions.filterKeys { it in filteredTestCases } + for ((key, preconditions) in newPreconditions) { + val (checkedInst, checkedState) = key + checkReachabilityIncremental( + checkedState, + ConditionCheckQuery( + preconditions.map { precondition -> + UpdateAndReportQuery( + precondition, + { state -> state }, + { state, parameters -> + throwExceptionAndReport( + state, + parameters, + checkedInst, + generate(preconditionProvider.targetException.symbolicClass) + ) + } ) } - result - } ?: return@runBlocking emptySet() + ) + ) + } + } - val reproductionChecker = ExceptionReproductionCheckerImpl(context, stackTrace) - val resultingTestClasses = result.testClasses.filterTo(mutableSetOf()) { - reproductionChecker.isReproduced(it) - } - if (resultingTestClasses.isEmpty()) { - kexConfig.testcaseDirectory.deleteRecursively() - ReflectionUtilsPrinter.invalidateAll() - } - resultingTestClasses - } + override suspend fun processMethod(method: Method) { + super.processMethod(method) + while (coroutineContext.isActive) { + checkNewPreconditions() + yield() } } - override suspend fun traverseInstruction(inst: Instruction) { - if (shouldStopAfterFirst && generatedTestClasses.isNotEmpty()) { + final override suspend fun traverseInstruction(inst: Instruction) { + if (shouldStopAfterFirst && resultsInner.isNotEmpty()) { return } + checkNewPreconditions() if (inst in targetInstructions) { val traverserState = currentState ?: return checkReachabilityIncremental( traverserState, ConditionCheckQuery( - preconditionBuilder.build(inst, traverserState).map { precondition -> + preconditionProvider.getPreconditions(inst, traverserState).map { precondition -> UpdateAndReportQuery( precondition, { state -> state }, @@ -369,7 +232,7 @@ class CrashReproductionChecker( state, parameters, inst, - generate(preconditionBuilder.targetException.symbolicClass) + generate(preconditionProvider.targetException.symbolicClass) ) } ) @@ -382,7 +245,7 @@ class CrashReproductionChecker( } - override suspend fun traverseCallInst(inst: CallInst) = acquireState { traverserState -> + final override suspend fun traverseCallInst(inst: CallInst) = acquireState { traverserState -> val callee = when { inst.isStatic -> staticRef(inst.method.klass) else -> traverserState.mkTerm(inst.callee) @@ -426,7 +289,102 @@ class CrashReproductionChecker( checkReachabilityIncremental(traverserState, nullQuery) } - override suspend fun checkIncremental( + final override suspend fun checkIncremental( + method: Method, + state: SymbolicState, + queries: List + ): List?> = checkAndBuildPrecondition(method, state, queries) + + final override fun report(inst: Instruction, parameters: Parameters, testPostfix: String): Boolean = + reportAndProducePrecondition(inst, parameters, testPostfix) + + abstract suspend fun checkAndBuildPrecondition( + method: Method, + state: SymbolicState, + queries: List + ): List?> + + abstract fun reportAndProducePrecondition( + inst: Instruction, + parameters: Parameters, + testPostfix: String + ): Boolean +} + +class DescriptorCrashReproductionChecker( + ctx: ExecutionContext, + stackTrace: StackTrace, + targetInstructions: Set, + preconditionProvider: ExceptionPreconditionProvider>, + preconditionReceiver: ExceptionPreconditionReceiver>, + reproductionChecker: ExceptionReproductionChecker, + shouldStopAfterFirst: Boolean +) : AbstractCrashReproductionChecker>( + ctx, + stackTrace, + targetInstructions, + preconditionProvider, + preconditionReceiver, + reproductionChecker, + shouldStopAfterFirst +) { + override val result: CrashReproductionResult> + get() = DescriptorCrashReproductionResult(resultsInner.toMap()) + + override suspend fun checkAndBuildPrecondition( + method: Method, + state: SymbolicState, + queries: List + ): List?> = method.checkAsyncIncremental(ctx, state, queries) + + override fun reportAndProducePrecondition( + inst: Instruction, + parameters: Parameters, + testPostfix: String + ): Boolean { + if (inst !in targetInstructions) return false + val testName = rootMethod.klassName + testPostfix + testIndex.getAndIncrement() + val generator = UnsafeGenerator(ctx, rootMethod, testName) + generator.generate(parameters) + val testFile = generator.emit() + try { + compilerHelper.compileFile(testFile) + if (!reproductionChecker.isReproduced(generator.testKlassName)) { + return false + } + resultsInner[generator.testKlassName] = parameters + preconditionReceiver.addPrecondition(parameters) + return true + } catch (e: CompilationException) { + log.error("Failed to compile test file $testFile") + return false + } + } +} + +class ConstraintCrashReproductionChecker( + ctx: ExecutionContext, + stackTrace: StackTrace, + targetInstructions: Set, + preconditionProvider: ExceptionPreconditionProvider, + preconditionReceiver: ExceptionPreconditionReceiver, + reproductionChecker: ExceptionReproductionChecker, + shouldStopAfterFirst: Boolean +) : AbstractCrashReproductionChecker( + ctx, + stackTrace, + targetInstructions, + preconditionProvider, + preconditionReceiver, + reproductionChecker, + shouldStopAfterFirst +) { + override val result: CrashReproductionResult + get() = ConstraintCrashReproductionResult(resultsInner.toMap()) + + private var lastPrecondition = mutableMapOf, ConstraintExceptionPrecondition>() + + override suspend fun checkAndBuildPrecondition( method: Method, state: SymbolicState, queries: List @@ -436,7 +394,11 @@ class CrashReproductionChecker( return result.map { it?.first } } - override fun report(inst: Instruction, parameters: Parameters, testPostfix: String): Boolean { + override fun reportAndProducePrecondition( + inst: Instruction, + parameters: Parameters, + testPostfix: String + ): Boolean { if (inst !in targetInstructions) return false val testName = rootMethod.klassName + testPostfix + testIndex.getAndIncrement() val generator = UnsafeGenerator(ctx, rootMethod, testName) @@ -447,9 +409,8 @@ class CrashReproductionChecker( if (!reproductionChecker.isReproduced(generator.testKlassName)) { return false } - generatedTestClasses += generator.testKlassName - descriptors[generator.testKlassName] = parameters - preconditions[generator.testKlassName] = lastPrecondition[parameters]!! + resultsInner[generator.testKlassName] = lastPrecondition[parameters]!! + preconditionReceiver.addPrecondition(lastPrecondition[parameters]!!) return true } catch (e: CompilationException) { log.error("Failed to compile test file $testFile") @@ -457,3 +418,98 @@ class CrashReproductionChecker( } } } + +object CrashReproductionChecker { + + @ExperimentalTime + @DelicateCoroutinesApi + fun runWithDescriptorPreconditions(context: ExecutionContext, stackTrace: StackTrace): Set = + runIteratively( + context, + stackTrace, + { DescriptorExceptionPreconditionBuilder(context, stackTrace.targetException(context), emptySet()) }, + ::DescriptorCrashReproductionChecker + ) + + @ExperimentalTime + @DelicateCoroutinesApi + fun runWithConstraintPreconditions(context: ExecutionContext, stackTrace: StackTrace): Set = + runIteratively( + context, + stackTrace, + { ConstraintExceptionPreconditionBuilder(context, stackTrace.targetException(context), emptySet()) }, + ::ConstraintCrashReproductionChecker + ) + + @ExperimentalTime + @DelicateCoroutinesApi + fun runIteratively( + context: ExecutionContext, + stackTrace: StackTrace, + preconditionBuilder: () -> ExceptionPreconditionBuilder, + crashReproductionBuilder: ( + ExecutionContext, StackTrace, targetInstructions: Set, ExceptionPreconditionProvider, + ExceptionPreconditionReceiver, ExceptionReproductionChecker, Boolean + ) -> AbstractCrashReproductionChecker + ): Set { + val timeLimit = kexConfig.getIntValue("crash", "timeLimit", 100).seconds + val stopAfterFirstCrash = kexConfig.getBooleanValue("crash", "stopAfterFirstCrash", false) + val executors = kexConfig.getIntValue("symbolic", "numberOfExecutors", 8) + + val actualNumberOfExecutors = maxOf(1, minOf(executors, stackTrace.stackTraceLines.size)) + val coroutineContext = newFixedThreadPoolContextWithMDC(actualNumberOfExecutors, "crash-dispatcher") + return runBlocking(coroutineContext) { + lateinit var resultGetter: () -> CrashReproductionResult + withTimeoutOrNull(timeLimit) { + var index = 0 + var producerChannel = ExceptionPreconditionChannel( + "${index++}", + ExceptionPreconditionBuilderImpl(context, stackTrace.targetException(context)) + ) + var receiverChannel = ExceptionPreconditionChannel("${index++}", preconditionBuilder()) + var targetInstructions = stackTrace.targetInstructions(context) + + stackTrace.stackTraceLines.zip(stackTrace.stackTraceLines.drop(1) + null).map { (line, next) -> + val oneLineStackTrace = StackTrace(stackTrace.firstLine, listOf(line)) + val checker = crashReproductionBuilder( + context, + oneLineStackTrace, + targetInstructions, + producerChannel, + receiverChannel, + ExceptionReproductionCheckerImpl(context, oneLineStackTrace), + stopAfterFirstCrash + ) + resultGetter = { checker.result } + producerChannel = receiverChannel + receiverChannel = ExceptionPreconditionChannel("${index++}", preconditionBuilder()) + next?.let { + targetInstructions = context.cm[next].body.flatten() + .filter { it.location.line == next.lineNumber } + .filterTo(mutableSetOf()) { it is CallInst && it.method.name == line.methodName } + } + async { checker.analyze() } + }.awaitAll() + } + val reproductionChecker = ExceptionReproductionCheckerImpl(context, stackTrace) + val filteredTestCases = resultGetter().preconditions.keys.filterTo(mutableSetOf()) { + reproductionChecker.isReproduced(it) + } + val testCasePaths = filteredTestCases.mapTo(mutableSetOf()) { + kexConfig.testcaseDirectory.resolve("${it.asmString}.java").toAbsolutePath().normalize() + } + kexConfig.testcaseDirectory + .takeIf { Files.exists(it) } + ?.let { testCaseDir -> + Files.walk(testCaseDir) + .filter { !it.isDirectory() } + .filter { it !in testCasePaths } + .filter { !it.endsWith("${ReflectionUtilsPrinter.REFLECTION_UTILS_CLASS}.java") } + .forEach { + Files.deleteIfExists(it) + } + } + filteredTestCases + } + } +} diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/ExceptionPreconditionBuilder.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/ExceptionPreconditionBuilder.kt index a582301ec..a8f620a18 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/ExceptionPreconditionBuilder.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/ExceptionPreconditionBuilder.kt @@ -5,7 +5,61 @@ import org.vorpal.research.kex.trace.symbolic.PersistentSymbolicState import org.vorpal.research.kfg.ir.Class import org.vorpal.research.kfg.ir.value.instruction.Instruction -interface ExceptionPreconditionBuilder { +interface ExceptionPreconditionBuilder { val targetException: Class + + /** + * @return `true` if the precondition is successfully added + */ + fun addPrecondition(precondition: T): Boolean fun build(location: Instruction, state: TraverserState): Set } + +interface ExceptionPreconditionProvider { + val targetException: Class + val hasNewPreconditions: Boolean + fun getNewPreconditions(): Map, Set> + fun getPreconditions(location: Instruction, state: TraverserState): Set +} + +interface ExceptionPreconditionReceiver { + fun addPrecondition(precondition: T) +} + +class ExceptionPreconditionChannel( + val name: String, + val builder: ExceptionPreconditionBuilder +) : ExceptionPreconditionProvider, ExceptionPreconditionReceiver { + private val mappings = mutableMapOf, MutableSet>() + + override val targetException: Class + get() = builder.targetException + + override var hasNewPreconditions = false + private set + + private val lock = Any() + + override fun addPrecondition(precondition: T): Unit = synchronized(lock) { + hasNewPreconditions = hasNewPreconditions || builder.addPrecondition(precondition) + } + + override fun getNewPreconditions(): Map, Set> = + synchronized(lock) { + when { + hasNewPreconditions -> mappings + .mapValues { (key, _) -> getPreconditions(key.first, key.second) } + .filterValues { it.isNotEmpty() } + .also { hasNewPreconditions = false } + else -> emptyMap() + } + } + + override fun getPreconditions(location: Instruction, state: TraverserState): Set = + synchronized(lock) { + val preconditions = builder.build(location, state) + val uncheckedPreconditions = preconditions - mappings.getOrPut(location to state, ::mutableSetOf) + mappings[location to state]!!.addAll(preconditions) + return uncheckedPreconditions + } +} diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/ExceptionPreconditionManager.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/ExceptionPreconditionManager.kt index 4bf537be4..6361e5b73 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/ExceptionPreconditionManager.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/ExceptionPreconditionManager.kt @@ -25,12 +25,12 @@ import org.vorpal.research.kfg.stringClass import org.vorpal.research.kfg.stringIndexOOB -class ExceptionPreconditionManager( +class ExceptionPreconditionManager( val ctx: ExecutionContext ) : TermBuilder { private val cm get() = ctx.cm private val tf get() = ctx.types - private val conditions = mutableMapOf>() + private val conditions = mutableMapOf>>() init { @@ -39,10 +39,12 @@ class ExceptionPreconditionManager( val stringClass = cm.stringClass conditions.getOrPut(charSequenceClass.getMethod("charAt", tf.charType, tf.intType)) { - val contracts = mutableMapOf() - contracts[cm.stringIndexOOB] = object : ExceptionPreconditionBuilder { + val contracts = mutableMapOf>() + contracts[cm.stringIndexOOB] = object : ExceptionPreconditionBuilder { override val targetException get() = cm.stringIndexOOB + override fun addPrecondition(precondition: T): Boolean = false + override fun build(location: Instruction, state: TraverserState): Set { location as CallInst val lengthMethod = charSequenceClass.getMethod("length", tf.intType) @@ -74,10 +76,12 @@ class ExceptionPreconditionManager( } conditions.getOrPut(stringClass.getMethod("charAt", tf.charType, tf.intType)) { - val contracts = mutableMapOf() - contracts[cm.stringIndexOOB] = object : ExceptionPreconditionBuilder { + val contracts = mutableMapOf>() + contracts[cm.stringIndexOOB] = object : ExceptionPreconditionBuilder { override val targetException get() = cm.stringIndexOOB + override fun addPrecondition(precondition: T): Boolean = false + override fun build(location: Instruction, state: TraverserState): Set { location as CallInst val lengthMethod = charSequenceClass.getMethod("length", tf.intType) @@ -109,10 +113,12 @@ class ExceptionPreconditionManager( } conditions.getOrPut(stringClass.getMethod("substring", stringClass.asType, tf.intType)) { - val contracts = mutableMapOf() - contracts[cm.stringIndexOOB] = object : ExceptionPreconditionBuilder { + val contracts = mutableMapOf>() + contracts[cm.stringIndexOOB] = object : ExceptionPreconditionBuilder { override val targetException get() = cm.stringIndexOOB + override fun addPrecondition(precondition: T): Boolean = false + override fun build(location: Instruction, state: TraverserState): Set { location as CallInst val lengthMethod = stringClass.getMethod("length", tf.intType) @@ -144,10 +150,12 @@ class ExceptionPreconditionManager( } conditions.getOrPut(stringClass.getMethod("substring", stringClass.asType, tf.intType, tf.intType)) { - val contracts = mutableMapOf() - contracts[cm.stringIndexOOB] = object : ExceptionPreconditionBuilder { + val contracts = mutableMapOf>() + contracts[cm.stringIndexOOB] = object : ExceptionPreconditionBuilder { override val targetException get() = cm.stringIndexOOB + override fun addPrecondition(precondition: T): Boolean = false + override fun build(location: Instruction, state: TraverserState): Set { location as CallInst val lengthMethod = stringClass.getMethod("length", tf.intType) @@ -198,10 +206,12 @@ class ExceptionPreconditionManager( } conditions.getOrPut(integerClass.getMethod("decode", integerClass.asType, stringClass.asType)) { - val contracts = mutableMapOf() - contracts[cm.numberFormatClass] = object : ExceptionPreconditionBuilder { + val contracts = mutableMapOf>() + contracts[cm.numberFormatClass] = object : ExceptionPreconditionBuilder { override val targetException get() = cm.numberFormatClass + override fun addPrecondition(precondition: T): Boolean = false + override fun build(location: Instruction, state: TraverserState): Set { location as CallInst // val startsWithMethod = stringClass.getMethod("startsWith", tf.boolType, stringClass.asType) @@ -246,10 +256,12 @@ class ExceptionPreconditionManager( stringClass.asType ) ) { - val contracts = mutableMapOf() - contracts[cm.illegalArgumentClass] = object : ExceptionPreconditionBuilder { + val contracts = mutableMapOf>() + contracts[cm.illegalArgumentClass] = object : ExceptionPreconditionBuilder { override val targetException get() = cm.illegalArgumentClass + override fun addPrecondition(precondition: T): Boolean = false + override fun build(location: Instruction, state: TraverserState): Set { location as CallInst val lengthMethod = stringClass.getMethod("length", tf.intType) @@ -285,10 +297,12 @@ class ExceptionPreconditionManager( val charClass = cm.charWrapper conditions.getOrPut(charClass.getMethod("codePointAt", tf.intType, charSequenceClass.asType, tf.intType)) { - val contracts = mutableMapOf() - contracts[cm.stringIndexOOB] = object : ExceptionPreconditionBuilder { + val contracts = mutableMapOf>() + contracts[cm.stringIndexOOB] = object : ExceptionPreconditionBuilder { override val targetException get() = cm.stringIndexOOB + override fun addPrecondition(precondition: T): Boolean = false + override fun build(location: Instruction, state: TraverserState): Set { location as CallInst val lengthMethod = charSequenceClass.getMethod("length", tf.intType) @@ -321,7 +335,7 @@ class ExceptionPreconditionManager( } } - fun resolve(callInst: CallInst, exception: Class): ExceptionPreconditionBuilder? = + fun resolve(callInst: CallInst, exception: Class): ExceptionPreconditionBuilder? = conditions.getOrDefault(callInst.method, emptyMap())[exception] } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/builders.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/builders.kt index 3c71c70d8..6a88fea41 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/builders.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/builders.kt @@ -39,12 +39,15 @@ import org.vorpal.research.kfg.nullptrClass import org.vorpal.research.kthelper.assert.unreachable import org.vorpal.research.kthelper.logging.log -class ExceptionPreconditionBuilderImpl( +class ExceptionPreconditionBuilderImpl( val ctx: ExecutionContext, override val targetException: Class, -) : ExceptionPreconditionBuilder { +) : ExceptionPreconditionBuilder { val cm get() = ctx.cm - private val preconditionManager = ExceptionPreconditionManager(ctx) + private val preconditionManager = ExceptionPreconditionManager(ctx) + + override fun addPrecondition(precondition: T) = false + override fun build(location: Instruction, state: TraverserState): Set = when (targetException) { cm.nullptrClass -> persistentSymbolicState( @@ -173,8 +176,14 @@ class ExceptionPreconditionBuilderImpl( class DescriptorExceptionPreconditionBuilder( val ctx: ExecutionContext, override val targetException: Class, - private val parameterSet: Set>, -) : ExceptionPreconditionBuilder { + parameterSet: Set>, +) : ExceptionPreconditionBuilder> { + private val parameterSet = parameterSet.toMutableSet() + + override fun addPrecondition(precondition: Parameters): Boolean { + return parameterSet.add(precondition) + } + override fun build(location: Instruction, state: TraverserState): Set { val callInst = (location as? CallInst) ?: unreachable { log.error("Descriptor precondition is not valid for non-call instructions") } @@ -344,8 +353,14 @@ data class ConstraintExceptionPrecondition( class ConstraintExceptionPreconditionBuilder( val ctx: ExecutionContext, override val targetException: Class, - private val parameterSet: Set, -) : ExceptionPreconditionBuilder { + parameterSet: Set, +) : ExceptionPreconditionBuilder { + private val parameterSet = parameterSet.toMutableSet() + + override fun addPrecondition(precondition: ConstraintExceptionPrecondition): Boolean { + return parameterSet.add(precondition) + } + override fun build(location: Instruction, state: TraverserState): Set { val callInst = (location as? CallInst) ?: unreachable { log.error("Descriptor precondition is not valid for non-call instructions") } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/launcher/CrashReproductionLauncher.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/launcher/CrashReproductionLauncher.kt index 91ec6905c..56c75f497 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/launcher/CrashReproductionLauncher.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/launcher/CrashReproductionLauncher.kt @@ -98,6 +98,9 @@ class CrashReproductionLauncher( executePipeline(context.cm, Package.defaultPackage) { +ClassInstantiationDetector(context, context.accessLevel) } - CrashReproductionChecker.runWithDescriptorPreconditions(context, stackTrace) + val testCases = CrashReproductionChecker.runWithDescriptorPreconditions(context, stackTrace) + if (testCases.isNotEmpty()) { + log.info("Reproducing test cases:\n${testCases.joinToString("\n")}") + } } } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ReflectionUtilsPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ReflectionUtilsPrinter.kt index fb205d677..45353a88a 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ReflectionUtilsPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ReflectionUtilsPrinter.kt @@ -10,7 +10,7 @@ class ReflectionUtilsPrinter( val packageName: String ) { private val builder = JavaBuilder(packageName) - val klass = builder.run { klass(packageName, "ReflectionUtils") } + val klass = builder.run { klass(packageName, REFLECTION_UTILS_CLASS) } val newInstance: JavaBuilder.JavaFunction val newArray: JavaBuilder.JavaFunction val newObjectArray: JavaBuilder.JavaFunction @@ -25,6 +25,7 @@ class ReflectionUtilsPrinter( val callMethod: JavaBuilder.JavaFunction companion object { + const val REFLECTION_UTILS_CLASS = "ReflectionUtils" private val reflectionUtilsInstances = mutableMapOf, ReflectionUtilsPrinter>() fun reflectionUtils(packageName: String): ReflectionUtilsPrinter { val testDirectory = kexConfig.testcaseDirectory @@ -41,6 +42,7 @@ class ReflectionUtilsPrinter( } } + @Suppress("unused") fun invalidateAll() { reflectionUtilsInstances.clear() } diff --git a/kex-test.ini b/kex-test.ini index f5ac0e872..e9844daf3 100644 --- a/kex-test.ini +++ b/kex-test.ini @@ -68,8 +68,8 @@ numberOfExecutors = 1 searchStrategy = cgs [crash] -globalTimeLimit = 600 -localTimeLimit = 100 +timeLimit = 100 +numberOfExecutors = 3 stopAfterFirstCrash = false [annotations] diff --git a/kex.ini b/kex.ini index 0565f8e36..d6d92c950 100644 --- a/kex.ini +++ b/kex.ini @@ -80,8 +80,8 @@ numberOfExecutors = 8 searchStrategy = cgs [crash] -globalTimeLimit = 600 -localTimeLimit = 600 +timeLimit = 100 +numberOfExecutors = 8 stopAfterFirstCrash = false [random-runner] @@ -108,7 +108,7 @@ maxDerollCount = 1 [smt] engine = ksmt -timeout = 100 +timeout = 10 defaultAllocationSize = 512 psInlining = true