Skip to content

Commit 6e6c378

Browse files
authored
Improve UTestValueDescriptor to UtModel conversion and state change asserts generation (#2691)
* Fix compilation after USVM update (jvm-instrumentation-persistent-descriptors) * Make `JcToUtModelConverter` construct `UtCompositeModel.origin` and assign same id to `stateBefore` and `stateAfter` models that describe same object * Adapt `ExecutionStateAnalyzer` to deal with absence of `refId` in USVM descriptors for classes, enums, and throwables * Adapt `ExecutionStateAnalyzer` to deal with `UtAssembleModel`s with `origin` * Replace `UtAssembleModel`s that only use reflection with `UtCompositeModel`s, improve `constModel` for `UtArrayModel`
1 parent 9bb144a commit 6e6c378

File tree

6 files changed

+141
-85
lines changed

6 files changed

+141
-85
lines changed

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelDeepMapper.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import org.utbot.framework.plugin.api.UtNullModel
1212
import org.utbot.framework.plugin.api.UtPrimitiveModel
1313
import org.utbot.framework.plugin.api.UtReferenceModel
1414
import org.utbot.framework.plugin.api.UtVoidModel
15+
import java.util.IdentityHashMap
1516

1617
/**
1718
* Performs deep mapping of [UtModel]s.
@@ -30,7 +31,7 @@ class UtModelDeepMapper private constructor(
3031
* Values are models that have been deeply mapped by this [UtModelDeepMapper].
3132
* Models are only associated with models of the same type (i.e. the cache type is actually `MutableMap<T, T>`)
3233
*/
33-
private val cache = mutableMapOf<UtModel, UtModel>()
34+
private val cache = IdentityHashMap<UtModel, UtModel>()
3435

3536
private val allInputtedModels get() = cache.keys
3637
private val allOutputtedModels get() = cache.values

utbot-framework/src/main/kotlin/org/utbot/framework/fields/ExecutionStateAnalyzer.kt

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import org.utbot.framework.plugin.api.UtPrimitiveModel
2121
import org.utbot.framework.plugin.api.UtReferenceModel
2222
import org.utbot.framework.plugin.api.UtSymbolicExecution
2323
import org.utbot.framework.plugin.api.UtVoidModel
24+
import org.utbot.framework.plugin.api.util.id
25+
import org.utbot.framework.plugin.api.util.isSubtypeOf
2426
import org.utbot.framework.util.UtModelVisitor
2527
import org.utbot.framework.util.hasThisInstance
2628
import org.utbot.fuzzer.UtFuzzedExecution
@@ -104,31 +106,23 @@ class ExecutionStateAnalyzer(val execution: UtExecution) {
104106
initialPath: FieldPath = FieldPath()
105107
): FieldStatesInfo {
106108
var modelBefore = before
109+
var modelAfter = after
107110

108111
if (before::class != after::class) {
109-
if (before is UtModelWithCompositeOrigin && after is UtModelWithCompositeOrigin && before.origin != null) {
110-
modelBefore = before.origin ?: unreachableBranch("We have already checked the origin for a null value")
111-
} else {
112-
doNotRun {
113-
// it is ok because we might have modelBefore with some absent fields (i.e. statics), but
114-
// modelAfter (constructed by concrete executor) will consist all these fields,
115-
// therefore, AssembleModelGenerator won't be able to transform the given composite model
116-
117-
val reason = if (before is UtModelWithCompositeOrigin && after is UtCompositeModel) {
118-
"ModelBefore is an UtModelWithOrigin and ModelAfter " +
119-
"is a CompositeModel, but modelBefore doesn't have an origin model."
120-
} else {
121-
"The model before and the model after have different types: " +
122-
"model before is ${before::class}, but model after is ${after::class}."
123-
}
112+
if (before is UtModelWithCompositeOrigin)
113+
modelBefore = before.origin ?: before
114+
if (after is UtModelWithCompositeOrigin)
115+
modelAfter = after.origin ?: after
116+
}
124117

125-
error("Cannot analyze fields modification. $reason")
126-
}
118+
if (modelBefore::class != modelAfter::class) {
119+
doNotRun {
120+
error("Cannot analyze model fields modification, before: [$before], after: [$after]")
121+
}
127122

128-
// remove it when we will fix assemble models in the resolver JIRA:1464
129-
workaround(WorkaroundReason.IGNORE_MODEL_TYPES_INEQUALITY) {
130-
return FieldStatesInfo(fieldsBefore = emptyMap(), fieldsAfter = emptyMap())
131-
}
123+
// remove it when we will fix assemble models in the resolver JIRA:1464
124+
workaround(WorkaroundReason.IGNORE_MODEL_TYPES_INEQUALITY) {
125+
return FieldStatesInfo(fieldsBefore = emptyMap(), fieldsAfter = emptyMap())
132126
}
133127
}
134128

@@ -139,7 +133,7 @@ class ExecutionStateAnalyzer(val execution: UtExecution) {
139133
modelBefore.accept(FieldStateVisitor(), dataBefore)
140134

141135
val dataAfter = FieldData(FieldsVisitorMode.AFTER, fieldsAfter, initialPath, previousFields = fieldsBefore)
142-
after.accept(FieldStateVisitor(), dataAfter)
136+
modelAfter.accept(FieldStateVisitor(), dataAfter)
143137

144138
return FieldStatesInfo(fieldsBefore, fieldsAfter)
145139
}
@@ -260,6 +254,13 @@ private class FieldStateVisitor : UtModelVisitor<FieldData>() {
260254
// sometimes we don't have initial state of the field, e.g. if it is static and we didn't `touch` it
261255
// during the analysis, but the concrete executor included it in the modelAfter
262256
val initial = previousFields[path] ?: return false
257+
258+
// TODO usvm-sbft: In USVM descriptors for classes, enums, and throwables don't implement `UTestRefDescriptor`
259+
// and don't have `refId`, which causes `UtReferenceModel.id` to diverge in `stateBefore` and `stateAfter`
260+
if (initial is UtClassRefModel) return initial.value != ((model as? UtClassRefModel)?.value)
261+
if (initial is UtEnumConstantModel) return initial.value != ((model as? UtEnumConstantModel)?.value)
262+
if (initial.classId isSubtypeOf java.lang.Throwable::class.id) return initial.classId != model.classId
263+
263264
return initial != model
264265
}
265266
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package org.utbot.contest.usvm
2+
3+
enum class EnvironmentStateKind {
4+
INITIAL, FINAL
5+
}

utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/JcToUtExecutionConverter.kt

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import org.utbot.framework.plugin.api.Coverage
2525
import org.utbot.framework.plugin.api.EnvironmentModels
2626
import org.utbot.framework.plugin.api.ExecutableId
2727
import org.utbot.framework.plugin.api.Instruction
28+
import org.utbot.framework.plugin.api.UtArrayModel
2829
import org.utbot.framework.plugin.api.UtAssembleModel
2930
import org.utbot.framework.plugin.api.UtExecutableCallModel
3031
import org.utbot.framework.plugin.api.UtExecution
@@ -34,12 +35,14 @@ import org.utbot.framework.plugin.api.UtExplicitlyThrownException
3435
import org.utbot.framework.plugin.api.UtImplicitlyThrownException
3536
import org.utbot.framework.plugin.api.UtInstrumentation
3637
import org.utbot.framework.plugin.api.UtPrimitiveModel
38+
import org.utbot.framework.plugin.api.UtStatementCallModel
3739
import org.utbot.framework.plugin.api.UtVoidModel
3840
import org.utbot.framework.plugin.api.mapper.UtModelDeepMapper
3941
import org.utbot.framework.plugin.api.util.executableId
4042
import org.utbot.framework.plugin.api.util.jClass
4143
import org.utbot.framework.plugin.api.util.utContext
4244
import org.utbot.fuzzer.IdGenerator
45+
import java.util.IdentityHashMap
4346

4447
private val logger = KotlinLogging.logger {}
4548

@@ -68,17 +71,19 @@ class JcToUtExecutionConverter(
6871

6972
val utUsvmExecution: UtUsvmExecution = when (val executionResult = jcExecution.uTestExecutionResult) {
7073
is UTestExecutionSuccessResult -> UtUsvmExecution(
71-
stateBefore = convertState(executionResult.initialState, jcExecution.method, jcToUtModelConverter),
72-
stateAfter = convertState(executionResult.resultState, jcExecution.method, jcToUtModelConverter),
74+
stateBefore = convertState(executionResult.initialState, EnvironmentStateKind.INITIAL, jcExecution.method),
75+
stateAfter = convertState(executionResult.resultState, EnvironmentStateKind.FINAL, jcExecution.method),
7376
// TODO usvm-sbft: ask why `UTestExecutionSuccessResult.result` is nullable
74-
result = UtExecutionSuccess(executionResult.result?.let { jcToUtModelConverter.convert(it) } ?: UtVoidModel),
77+
result = UtExecutionSuccess(executionResult.result?.let {
78+
jcToUtModelConverter.convert(it, EnvironmentStateKind.FINAL)
79+
} ?: UtVoidModel),
7580
coverage = coverage,
7681
instrumentation = instrumentation,
7782
)
7883
is UTestExecutionExceptionResult -> {
7984
UtUsvmExecution(
80-
stateBefore = convertState(executionResult.initialState, jcExecution.method, jcToUtModelConverter),
81-
stateAfter = convertState(executionResult.resultState, jcExecution.method, jcToUtModelConverter),
85+
stateBefore = convertState(executionResult.initialState, EnvironmentStateKind.INITIAL, jcExecution.method),
86+
stateAfter = convertState(executionResult.resultState, EnvironmentStateKind.FINAL, jcExecution.method),
8287
result = createExecutionFailureResult(
8388
executionResult.cause,
8489
jcExecution.method,
@@ -108,7 +113,10 @@ class JcToUtExecutionConverter(
108113
}
109114
} ?: return null
110115

111-
return utUsvmExecution.mapModels(constructAssemblingMapper())
116+
return utUsvmExecution
117+
.mapModels(constructAssemblingMapper())
118+
.mapModels(constructAssembleToCompositeModelMapper())
119+
.mapModels(constructConstArrayModelMapper())
112120
}
113121

114122
private fun constructAssemblingMapper(): UtModelDeepMapper = UtModelDeepMapper { model ->
@@ -145,6 +153,31 @@ class JcToUtExecutionConverter(
145153
} ?: model
146154
}
147155

156+
private fun constructConstArrayModelMapper(): UtModelDeepMapper = UtModelDeepMapper { model ->
157+
if (model is UtArrayModel) {
158+
val storeGroups = model.stores.entries.groupByTo(IdentityHashMap()) { it.value }
159+
val mostCommonStore = storeGroups.maxBy { it.value.size }
160+
if (mostCommonStore.value.size > 1) {
161+
model.constModel = mostCommonStore.key
162+
mostCommonStore.value.forEach { (index, _) -> model.stores.remove(index) }
163+
}
164+
}
165+
model
166+
}
167+
168+
private fun constructAssembleToCompositeModelMapper(): UtModelDeepMapper = UtModelDeepMapper { model ->
169+
if (model is UtAssembleModel
170+
&& utilMethodProvider.createInstanceMethodId == model.instantiationCall.statement
171+
&& model.modificationsChain.all {
172+
utilMethodProvider.setFieldMethodId == (it as? UtStatementCallModel)?.statement
173+
}
174+
) {
175+
model.origin ?: model
176+
} else {
177+
model
178+
}
179+
}
180+
148181
private fun convertException(exceptionDescriptor: UTestExceptionDescriptor): Throwable =
149182
toValueConverter.buildObjectFromDescriptor(exceptionDescriptor.dropStaticFields(
150183
cache = mutableMapOf()
@@ -160,18 +193,20 @@ class JcToUtExecutionConverter(
160193

161194
private fun convertState(
162195
state: UTestExecutionState,
196+
stateKind: EnvironmentStateKind,
163197
method: JcTypedMethod,
164-
modelConverter: JcToUtModelConverter,
165-
): EnvironmentModels {
198+
): EnvironmentModels {
166199
val thisInstance =
167200
if (method.isStatic) null
168201
else if (method.method.isConstructor) null
169-
else modelConverter.convert(state.instanceDescriptor ?: error("Unexpected null instanceDescriptor"))
170-
val parameters = state.argsDescriptors.map { modelConverter.convert(it ?: error("Unexpected null argDescriptor")) }
202+
else jcToUtModelConverter.convert(state.instanceDescriptor ?: error("Unexpected null instanceDescriptor"), stateKind)
203+
val parameters = state.argsDescriptors.map {
204+
jcToUtModelConverter.convert(it ?: error("Unexpected null argDescriptor"), stateKind)
205+
}
171206
val statics = state.statics
172207
.entries
173208
.associate { (jcField, uTestDescr) ->
174-
jcField.fieldId to modelConverter.convert(uTestDescr)
209+
jcField.fieldId to jcToUtModelConverter.convert(uTestDescr, stateKind)
175210
}
176211
val executableId: ExecutableId = method.method.toExecutableId(jcClasspath)
177212
return EnvironmentModels(thisInstance, parameters, statics, executableId)

0 commit comments

Comments
 (0)