Skip to content

Use symbolic result instead of concrete if it is better #2700

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

Merged
merged 3 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import org.jacodb.api.ext.jcdbSignature
import org.usvm.instrumentation.testcase.api.UTestExecutionExceptionResult
import org.usvm.instrumentation.testcase.api.UTestExecutionFailedResult
import org.usvm.instrumentation.testcase.api.UTestExecutionInitFailedResult
import org.usvm.instrumentation.testcase.api.UTestExecutionResult
import org.usvm.instrumentation.testcase.api.UTestExecutionState
import org.usvm.instrumentation.testcase.api.UTestExecutionSuccessResult
import org.usvm.instrumentation.testcase.api.UTestExecutionTimedOutResult
Expand All @@ -20,6 +19,10 @@ import org.usvm.instrumentation.util.enclosingClass
import org.usvm.instrumentation.util.enclosingMethod
import org.utbot.common.isPublic
import org.utbot.contest.usvm.jc.JcExecution
import org.utbot.contest.usvm.jc.UTestConcreteExecutionResult
import org.utbot.contest.usvm.jc.UTestResultWrapper
import org.utbot.contest.usvm.jc.UTestSymbolicExceptionResult
import org.utbot.contest.usvm.jc.UTestSymbolicSuccessResult
import org.utbot.framework.codegen.domain.builtin.UtilMethodProvider
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.Coverage
Expand Down Expand Up @@ -65,56 +68,91 @@ class JcToUtExecutionConverter(
private var uTestProcessResult = instToModelConverter.processUTest()

fun convert(): UtExecution? {
val coverage = convertCoverage(getTrace(jcExecution.uTestExecutionResult), jcExecution.method.enclosingType.jcClass)
val coverage = convertCoverage(getTrace(jcExecution.uTestExecutionResultWrapper), jcExecution.method.enclosingType.jcClass)

val utUsvmExecution: UtUsvmExecution = when (val executionResult = jcExecution.uTestExecutionResult) {
is UTestExecutionSuccessResult -> UtUsvmExecution(
stateBefore = convertState(executionResult.initialState, EnvironmentStateKind.INITIAL, jcExecution.method),
stateAfter = convertState(executionResult.resultState, EnvironmentStateKind.FINAL, jcExecution.method),
// TODO usvm-sbft: ask why `UTestExecutionSuccessResult.result` is nullable
result = UtExecutionSuccess(executionResult.result?.let {
jcToUtModelConverter.convert(it, EnvironmentStateKind.FINAL)
} ?: UtVoidModel),
coverage = coverage,
instrumentation = uTestProcessResult.instrumentation,
)
is UTestExecutionExceptionResult -> {
val utUsvmExecution: UtUsvmExecution = when (jcExecution.uTestExecutionResultWrapper) {
is UTestSymbolicExceptionResult -> {
UtUsvmExecution(
stateBefore = convertState(executionResult.initialState, EnvironmentStateKind.INITIAL, jcExecution.method),
stateAfter = convertState(executionResult.resultState, EnvironmentStateKind.FINAL, jcExecution.method),
stateBefore = constructStateBeforeFromUTest(),
stateAfter = MissingState,
result = createExecutionFailureResult(
executionResult.cause,
jcExecution.method,
exceptionDescriptor = UTestExceptionDescriptor(
type = jcExecution.uTestExecutionResultWrapper.exceptionType,
message = "",
stackTrace = emptyList(),
raisedByUserCode = true,
),
jcTypedMethod = jcExecution.method,
),
coverage = coverage,
instrumentation = uTestProcessResult.instrumentation,
)
}

is UTestExecutionInitFailedResult -> {
logger.warn(convertException(executionResult.cause)) {
"Execution failed before method under test call on ${jcExecution.method.method}"
}
null
}
is UTestSymbolicSuccessResult -> {
val resultWrapper = jcExecution.uTestExecutionResultWrapper
resultWrapper.initStatements.forEach { instToModelConverter.processInst(it) }
instToModelConverter.processInst(resultWrapper.result)

is UTestExecutionFailedResult -> {
logger.error(convertException(executionResult.cause)) {
"Concrete execution failed on ${jcExecution.method.method}"
}
null
}
val resultUtModel = instToModelConverter.findModelByInst(resultWrapper.result)

is UTestExecutionTimedOutResult -> {
logger.warn { "Timeout on ${jcExecution.method.method}" }
UtUsvmExecution(
stateBefore = constructStateBeforeFromUTest(),
stateAfter = MissingState,
result = UtTimeoutException(TimeoutException("Concrete execution timed out")),
result = UtExecutionSuccess(resultUtModel),
coverage = coverage,
instrumentation = uTestProcessResult.instrumentation,
)
}

is UTestConcreteExecutionResult ->
when (val executionResult = jcExecution.uTestExecutionResultWrapper.uTestExecutionResult) {
is UTestExecutionSuccessResult -> UtUsvmExecution(
stateBefore = convertState(executionResult.initialState, EnvironmentStateKind.INITIAL, jcExecution.method),
stateAfter = convertState(executionResult.resultState, EnvironmentStateKind.FINAL, jcExecution.method),
// TODO usvm-sbft: ask why `UTestExecutionSuccessResult.result` is nullable
result = UtExecutionSuccess(executionResult.result?.let {
jcToUtModelConverter.convert(it, EnvironmentStateKind.FINAL)
} ?: UtVoidModel),
coverage = coverage,
instrumentation = uTestProcessResult.instrumentation,
)

is UTestExecutionExceptionResult -> {
UtUsvmExecution(
stateBefore = convertState(executionResult.initialState, EnvironmentStateKind.INITIAL, jcExecution.method),
stateAfter = convertState(executionResult.resultState, EnvironmentStateKind.FINAL, jcExecution.method),
result = createExecutionFailureResult(executionResult.cause, jcExecution.method),
coverage = coverage,
instrumentation = uTestProcessResult.instrumentation,
)
}

is UTestExecutionInitFailedResult -> {
logger.warn(convertException(executionResult.cause)) {
"Execution failed before method under test call on ${jcExecution.method.method}"
}
null
}

is UTestExecutionFailedResult -> {
logger.error(convertException(executionResult.cause)) {
"Concrete execution failed on ${jcExecution.method.method}"
}
null
}

is UTestExecutionTimedOutResult -> {
logger.warn { "Timeout on ${jcExecution.method.method}" }
UtUsvmExecution(
stateBefore = constructStateBeforeFromUTest(),
stateAfter = MissingState,
result = UtTimeoutException(TimeoutException("Concrete execution timed out")),
coverage = coverage,
instrumentation = uTestProcessResult.instrumentation,
)
}
}
} ?: return null

return utUsvmExecution
Expand Down Expand Up @@ -206,12 +244,22 @@ class JcToUtExecutionConverter(
cache = mutableMapOf()
)) as Throwable

private fun getTrace(executionResult: UTestExecutionResult): List<JcInst>? = when (executionResult) {
is UTestExecutionExceptionResult -> executionResult.trace
is UTestExecutionInitFailedResult -> executionResult.trace
is UTestExecutionSuccessResult -> executionResult.trace
is UTestExecutionFailedResult -> emptyList()
is UTestExecutionTimedOutResult -> emptyList()
/**
* Gets trace from execution result if it is present.
*
* Otherwise, (e.g. we use symbolic result if concrete fails),
* minimization will take just 'UtSettings.maxUnknownCoverageExecutionsPerMethodPerResultType' executions.
*/
private fun getTrace(executionResult: UTestResultWrapper): List<JcInst>? = when (executionResult) {
is UTestConcreteExecutionResult -> when (val res = executionResult.uTestExecutionResult) {
is UTestExecutionExceptionResult -> res.trace
is UTestExecutionInitFailedResult -> res.trace
is UTestExecutionSuccessResult -> res.trace
is UTestExecutionFailedResult -> emptyList()
is UTestExecutionTimedOutResult -> emptyList()
}
is UTestSymbolicExceptionResult -> emptyList()
is UTestSymbolicSuccessResult -> emptyList()
Comment on lines +261 to +262
Copy link
Collaborator

@IlyaMuravjov IlyaMuravjov Nov 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Executions with empty coverage are not minimized. It's somewhat ok for timeout executions, since there can only be so many of them (due to time it takes to register timeout), but symbolic results can be emitted much faster. If it's too difficult to extract any coverage info from the machine, consider merging #2627 into usvm_competitions_2024 with UtSettings.maxUnknownCoverageExecutionsPerMethodPerResultType set to something like 10.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merged a copy of that PR into competitions, added a comment here.

}

private fun convertState(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class UTestInstToUtModelConverter(
}
}

private fun processInst(uTestInst: UTestInst) {
fun processInst(uTestInst: UTestInst) {
when (uTestInst) {
is UTestExpression -> processExpr(uTestInst)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
package org.utbot.contest.usvm.jc

import org.jacodb.api.JcType
import org.jacodb.api.JcTypedMethod
import org.usvm.api.JcCoverage
import org.usvm.instrumentation.testcase.UTest
import org.usvm.instrumentation.testcase.api.UTestExecutionResult
import org.usvm.instrumentation.testcase.api.UTestExpression
import org.usvm.instrumentation.testcase.api.UTestInst

data class JcExecution(
val method: JcTypedMethod,
val uTest: UTest,
val uTestExecutionResult: UTestExecutionResult,
val uTestExecutionResultWrapper: UTestResultWrapper,
val coverage: JcCoverage
)

sealed interface UTestResultWrapper

class UTestConcreteExecutionResult(
val uTestExecutionResult: UTestExecutionResult
) : UTestResultWrapper

class UTestSymbolicExceptionResult(
val exceptionType: JcType
) : UTestResultWrapper

class UTestSymbolicSuccessResult(
val initStatements: List<UTestInst>,
val result: UTestExpression
) : UTestResultWrapper
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ import org.usvm.api.util.JcTestStateResolver
import org.usvm.instrumentation.executor.UTestConcreteExecutor
import org.usvm.instrumentation.testcase.UTest
import org.usvm.instrumentation.testcase.api.UTestAllocateMemoryCall
import org.usvm.instrumentation.testcase.api.UTestExecutionExceptionResult
import org.usvm.instrumentation.testcase.api.UTestExecutionSuccessResult
import org.usvm.instrumentation.testcase.api.UTestExpression
import org.usvm.instrumentation.testcase.api.UTestMethodCall
import org.usvm.instrumentation.testcase.api.UTestNullExpression
import org.usvm.instrumentation.testcase.api.UTestStaticMethodCall
import org.usvm.machine.JcContext
import org.usvm.machine.state.JcMethodResult
import org.usvm.machine.state.JcState
import org.usvm.machine.state.localIdx
import org.usvm.memory.ULValue
Expand Down Expand Up @@ -52,9 +55,27 @@ class JcTestExecutor(
runner.executeAsync(uTest)
}

// sometimes symbolic result is preferable that concolic: e.g. if concrete times out
val preferableResult =
if (execResult !is UTestExecutionSuccessResult && execResult !is UTestExecutionExceptionResult) {
val symbolicResult = state.methodResult
when (symbolicResult) {
is JcMethodResult.JcException -> UTestSymbolicExceptionResult(symbolicResult.type)
is JcMethodResult.Success -> {
val resultScope = MemoryScope(ctx, model, state.memory, stringConstants, method)
val resultExpr = resultScope.resolveExpr(symbolicResult.value, method.returnType)
val resultInitializer = resultScope.decoderApi.initializerInstructions()
UTestSymbolicSuccessResult(resultInitializer, resultExpr)
}
JcMethodResult.NoCall -> UTestConcreteExecutionResult(execResult)
}
} else {
UTestConcreteExecutionResult(execResult)
}

val coverage = resolveCoverage(method, state)

return JcExecution(method, uTest, execResult, coverage)
return JcExecution(method, uTest, preferableResult, coverage)
}

@Suppress("UNUSED_PARAMETER")
Expand Down