Skip to content

Commit 472cf99

Browse files
authored
Non-deterministic detection (#2246)
1 parent dd1506e commit 472cf99

File tree

14 files changed

+594
-88
lines changed

14 files changed

+594
-88
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,12 @@ class UtSymbolicExecution(
185185
append(")")
186186
}
187187

188-
fun copy(stateAfter: EnvironmentModels, result: UtExecutionResult, coverage: Coverage): UtResult {
188+
fun copy(
189+
stateAfter: EnvironmentModels,
190+
result: UtExecutionResult,
191+
coverage: Coverage,
192+
instrumentation: List<UtInstrumentation> = this.instrumentation,
193+
): UtResult {
189194
return UtSymbolicExecution(
190195
stateBefore,
191196
stateAfter,
@@ -841,6 +846,7 @@ open class ClassId @JvmOverloads constructor(
841846
*/
842847
open val allMethods: Sequence<MethodId>
843848
get() = generateSequence(jClass) { it.superclass }
849+
.flatMap { it.interfaces.toMutableList() + it }
844850
.mapNotNull { it.declaredMethods }
845851
.flatMap { it.toList() }
846852
.map { it.executableId }

utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ class UtBotSymbolicEngine(
262262
stateBefore,
263263
concreteExecutionResult.stateAfter,
264264
concreteExecutionResult.result,
265-
instrumentation,
265+
concreteExecutionResult.newInstrumentation ?: instrumentation,
266266
mutableListOf(),
267267
listOf(),
268268
concreteExecutionResult.coverage
@@ -425,7 +425,8 @@ class UtBotSymbolicEngine(
425425
result = concreteExecutionResult.result,
426426
coverage = concreteExecutionResult.coverage,
427427
fuzzingValues = values,
428-
fuzzedMethodDescription = descr.description
428+
fuzzedMethodDescription = descr.description,
429+
instrumentation = concreteExecutionResult.newInstrumentation ?: emptyList()
429430
)
430431
)
431432

@@ -552,7 +553,8 @@ class UtBotSymbolicEngine(
552553
val concolicUtExecution = symbolicUtExecution.copy(
553554
stateAfter = concreteExecutionResult.stateAfter,
554555
result = concreteExecutionResult.result,
555-
coverage = concreteExecutionResult.coverage
556+
coverage = concreteExecutionResult.coverage,
557+
instrumentation = concreteExecutionResult.newInstrumentation ?: instrumentation
556558
)
557559

558560
emit(concolicUtExecution)

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMethodConstructor.kt

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ import org.utbot.framework.plugin.api.util.stringClassId
149149
import org.utbot.framework.plugin.api.util.voidClassId
150150
import org.utbot.framework.plugin.api.util.wrapIfPrimitive
151151
import org.utbot.framework.util.isUnit
152+
import org.utbot.fuzzer.UtFuzzedExecution
152153
import org.utbot.summary.SummarySentenceConstants.TAB
153154
import java.lang.reflect.InvocationTargetException
154155
import java.lang.reflect.ParameterizedType
@@ -184,29 +185,30 @@ open class CgMethodConstructor(val context: CgContext) : CgContextOwner by conte
184185
private var containsStreamConsumingFailureForParametrizedTests: Boolean = false
185186

186187
protected fun setupInstrumentation() {
187-
if (currentExecution is UtSymbolicExecution) {
188-
val execution = currentExecution as UtSymbolicExecution
189-
val instrumentation = execution.instrumentation
190-
if (instrumentation.isEmpty()) return
191-
192-
if (generateWarningsForStaticMocking && forceStaticMocking == ForceStaticMocking.DO_NOT_FORCE) {
193-
// warn user about possible flaky tests
194-
multilineComment(forceStaticMocking.warningMessage)
195-
return
196-
}
188+
val instrumentation = when (val execution = currentExecution) {
189+
is UtSymbolicExecution -> execution.instrumentation
190+
is UtFuzzedExecution -> execution.instrumentation
191+
else -> return
192+
}
193+
if (instrumentation.isEmpty()) return
197194

198-
instrumentation
199-
.filterIsInstance<UtNewInstanceInstrumentation>()
200-
.forEach { mockFrameworkManager.mockNewInstance(it) }
201-
instrumentation
202-
.filterIsInstance<UtStaticMethodInstrumentation>()
203-
.groupBy { it.methodId.classId }
204-
.forEach { (classId, methodMocks) -> mockFrameworkManager.mockStaticMethodsOfClass(classId, methodMocks) }
205-
206-
if (generateWarningsForStaticMocking && forceStaticMocking == ForceStaticMocking.FORCE) {
207-
// warn user about forced using static mocks
208-
multilineComment(forceStaticMocking.warningMessage)
209-
}
195+
if (generateWarningsForStaticMocking && forceStaticMocking == ForceStaticMocking.DO_NOT_FORCE) {
196+
// warn user about possible flaky tests
197+
multilineComment(forceStaticMocking.warningMessage)
198+
return
199+
}
200+
201+
instrumentation
202+
.filterIsInstance<UtNewInstanceInstrumentation>()
203+
.forEach { mockFrameworkManager.mockNewInstance(it) }
204+
instrumentation
205+
.filterIsInstance<UtStaticMethodInstrumentation>()
206+
.groupBy { it.methodId.classId }
207+
.forEach { (classId, methodMocks) -> mockFrameworkManager.mockStaticMethodsOfClass(classId, methodMocks) }
208+
209+
if (generateWarningsForStaticMocking && forceStaticMocking == ForceStaticMocking.FORCE) {
210+
// warn user about forced using static mocks
211+
multilineComment(forceStaticMocking.warningMessage)
210212
}
211213
}
212214

utbot-framework/src/main/kotlin/org/utbot/framework/util/UtConcreteExecutionResultUtils.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ private fun UtConcreteExecutionResult.updateWithAssembleModels(
2424
return UtConcreteExecutionResult(
2525
resolvedStateAfter,
2626
resolvedResult,
27-
coverage
27+
coverage,
28+
newInstrumentation
2829
)
2930
}
3031

utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentation.kt

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
package org.utbot.instrumentation.instrumentation.execution
22

3-
import java.security.ProtectionDomain
4-
import java.util.IdentityHashMap
5-
import kotlin.reflect.jvm.javaMethod
63
import org.utbot.framework.UtSettings
74
import org.utbot.framework.plugin.api.*
8-
import org.utbot.instrumentation.instrumentation.execution.constructors.ConstructOnlyUserClassesOrCachedObjectsStrategy
9-
import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructor
10-
import org.utbot.instrumentation.instrumentation.execution.mock.InstrumentationContext
11-
import org.utbot.instrumentation.instrumentation.execution.phases.PhasesController
12-
import org.utbot.instrumentation.instrumentation.execution.phases.start
135
import org.utbot.framework.plugin.api.util.singleExecutableId
146
import org.utbot.instrumentation.instrumentation.ArgumentList
157
import org.utbot.instrumentation.instrumentation.Instrumentation
168
import org.utbot.instrumentation.instrumentation.InvokeInstrumentation
179
import org.utbot.instrumentation.instrumentation.et.TraceHandler
10+
import org.utbot.instrumentation.instrumentation.execution.constructors.ConstructOnlyUserClassesOrCachedObjectsStrategy
11+
import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructor
12+
import org.utbot.instrumentation.instrumentation.execution.mock.InstrumentationContext
13+
import org.utbot.instrumentation.instrumentation.execution.ndd.NonDeterministicClassVisitor
14+
import org.utbot.instrumentation.instrumentation.execution.ndd.NonDeterministicDetector
15+
import org.utbot.instrumentation.instrumentation.execution.phases.PhasesController
16+
import org.utbot.instrumentation.instrumentation.execution.phases.start
1817
import org.utbot.instrumentation.instrumentation.instrumenter.Instrumenter
1918
import org.utbot.instrumentation.instrumentation.mock.MockClassVisitor
19+
import java.security.ProtectionDomain
20+
import java.util.*
21+
import kotlin.reflect.jvm.javaMethod
2022

2123
/**
2224
* Consists of the data needed to execute the method concretely. Also includes method arguments stored in models.
@@ -35,7 +37,8 @@ data class UtConcreteExecutionData(
3537
class UtConcreteExecutionResult(
3638
val stateAfter: EnvironmentModels,
3739
val result: UtExecutionResult,
38-
val coverage: Coverage
40+
val coverage: Coverage,
41+
val newInstrumentation: List<UtInstrumentation>? = null,
3942
) {
4043
override fun toString(): String = buildString {
4144
appendLine("UtConcreteExecutionResult(")
@@ -51,6 +54,7 @@ object UtExecutionInstrumentation : Instrumentation<UtConcreteExecutionResult> {
5154
private val instrumentationContext = InstrumentationContext()
5255

5356
private val traceHandler = TraceHandler()
57+
private val ndDetector = NonDeterministicDetector()
5458
private val pathsToUserClasses = mutableSetOf<String>()
5559

5660
override fun init(pathsToUserClasses: Set<String>) {
@@ -101,6 +105,7 @@ object UtExecutionInstrumentation : Instrumentation<UtConcreteExecutionResult> {
101105
postprocessingPhase.setStaticFields(preparationPhase.start {
102106
val result = setStaticFields(statics)
103107
resetTrace()
108+
resetND()
104109
result
105110
})
106111

@@ -110,12 +115,12 @@ object UtExecutionInstrumentation : Instrumentation<UtConcreteExecutionResult> {
110115
}
111116

112117
// statistics collection
113-
val coverage = executePhaseInTimeout(statisticsCollectionPhase) {
114-
getCoverage(clazz)
118+
val (coverage, ndResults) = executePhaseInTimeout(statisticsCollectionPhase) {
119+
getCoverage(clazz) to getNonDeterministicResults()
115120
}
116121

117122
// model construction
118-
val (executionResult, stateAfter) = executePhaseInTimeout(modelConstructionPhase) {
123+
val (executionResult, stateAfter, newInstrumentation) = executePhaseInTimeout(modelConstructionPhase) {
119124
configureConstructor {
120125
this.cache = cache
121126
strategy = ConstructOnlyUserClassesOrCachedObjectsStrategy(
@@ -124,6 +129,10 @@ object UtExecutionInstrumentation : Instrumentation<UtConcreteExecutionResult> {
124129
)
125130
}
126131

132+
val ndStatics = constructStaticInstrumentation(ndResults.statics)
133+
val ndNews = constructNewInstrumentation(ndResults.news, ndResults.calls)
134+
val newInstrumentation = mergeInstrumentations(instrumentations, ndStatics, ndNews)
135+
127136
val executionResult = convertToExecutionResult(concreteResult, returnClassId)
128137

129138
val stateAfterParametersWithThis = constructParameters(params)
@@ -135,13 +144,14 @@ object UtExecutionInstrumentation : Instrumentation<UtConcreteExecutionResult> {
135144
}
136145
val stateAfter = EnvironmentModels(stateAfterThis, stateAfterParameters, stateAfterStatics)
137146

138-
executionResult to stateAfter
147+
Triple(executionResult, stateAfter, newInstrumentation)
139148
}
140149

141150
UtConcreteExecutionResult(
142151
stateAfter,
143152
executionResult,
144-
coverage
153+
coverage,
154+
newInstrumentation
145155
)
146156
} finally {
147157
postprocessingPhase.start {
@@ -175,6 +185,10 @@ object UtExecutionInstrumentation : Instrumentation<UtConcreteExecutionResult> {
175185
traceHandler.registerClass(className)
176186
instrumenter.visitInstructions(traceHandler.computeInstructionVisitor(className))
177187

188+
instrumenter.visitClass { writer ->
189+
NonDeterministicClassVisitor(writer, ndDetector)
190+
}
191+
178192
val mockClassVisitor = instrumenter.visitClass { writer ->
179193
MockClassVisitor(
180194
writer,

utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtModelConstructor.kt

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,13 @@
11
package org.utbot.instrumentation.instrumentation.execution.constructors
22

3-
import java.lang.reflect.Modifier
4-
import java.util.IdentityHashMap
5-
import java.util.stream.BaseStream
63
import org.utbot.common.asPathToFile
74
import org.utbot.common.withAccessibility
8-
import org.utbot.framework.plugin.api.ClassId
9-
import org.utbot.framework.plugin.api.FieldId
10-
import org.utbot.framework.plugin.api.UtArrayModel
11-
import org.utbot.framework.plugin.api.UtAssembleModel
12-
import org.utbot.framework.plugin.api.UtClassRefModel
13-
import org.utbot.framework.plugin.api.UtCompositeModel
14-
import org.utbot.framework.plugin.api.UtEnumConstantModel
15-
import org.utbot.framework.plugin.api.UtLambdaModel
16-
import org.utbot.framework.plugin.api.UtModel
17-
import org.utbot.framework.plugin.api.UtNullModel
18-
import org.utbot.framework.plugin.api.UtPrimitiveModel
19-
import org.utbot.framework.plugin.api.UtReferenceModel
20-
import org.utbot.framework.plugin.api.UtVoidModel
21-
import org.utbot.framework.plugin.api.isMockModel
22-
import org.utbot.framework.plugin.api.util.booleanClassId
23-
import org.utbot.framework.plugin.api.util.byteClassId
24-
import org.utbot.framework.plugin.api.util.charClassId
25-
import org.utbot.framework.plugin.api.util.doubleClassId
26-
import org.utbot.framework.plugin.api.util.fieldId
27-
import org.utbot.framework.plugin.api.util.floatClassId
28-
import org.utbot.framework.plugin.api.util.id
29-
import org.utbot.framework.plugin.api.util.intClassId
30-
import org.utbot.framework.plugin.api.util.isInaccessibleViaReflection
31-
import org.utbot.framework.plugin.api.util.isPrimitive
32-
import org.utbot.framework.plugin.api.util.jClass
33-
import org.utbot.framework.plugin.api.util.longClassId
34-
import org.utbot.framework.plugin.api.util.objectClassId
35-
import org.utbot.framework.plugin.api.util.shortClassId
36-
import org.utbot.framework.plugin.api.util.utContext
5+
import org.utbot.framework.plugin.api.*
6+
import org.utbot.framework.plugin.api.util.*
377
import org.utbot.framework.plugin.api.visible.UtStreamConsumingException
8+
import java.lang.reflect.Modifier
9+
import java.util.*
10+
import java.util.stream.BaseStream
3811

3912
/**
4013
* Represents common interface for model constructors.
@@ -120,6 +93,7 @@ class UtModelConstructor(
12093
is Float,
12194
is Double,
12295
is Boolean -> if (classId.isPrimitive) UtPrimitiveModel(value) else constructFromAny(value)
96+
12397
is ByteArray -> constructFromByteArray(value)
12498
is ShortArray -> constructFromShortArray(value)
12599
is CharArray -> constructFromCharArray(value)
@@ -136,6 +110,20 @@ class UtModelConstructor(
136110
}
137111
}
138112

113+
fun constructMock(instance: Any, classId: ClassId, mocks: Map<MethodId, List<Any?>>): UtModel =
114+
constructedObjects.getOrElse(instance) {
115+
val utModel = UtCompositeModel(
116+
handleId(instance),
117+
classId,
118+
isMock = true,
119+
mocks = mocks.mapValuesTo(mutableMapOf()) { (method, values) ->
120+
values.map { construct(it, method.returnType) }
121+
}
122+
)
123+
constructedObjects[instance] = utModel
124+
utModel
125+
}
126+
139127
// Q: Is there a way to get rid of duplicated code?
140128

141129
private fun constructFromDoubleArray(array: DoubleArray): UtModel =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package org.utbot.instrumentation.instrumentation.execution.ndd
2+
3+
import org.objectweb.asm.MethodVisitor
4+
import org.objectweb.asm.Opcodes
5+
import org.objectweb.asm.Type
6+
7+
class NonDeterministicBytecodeInserter {
8+
private val internalName = Type.getInternalName(NonDeterministicResultStorage::class.java)
9+
10+
private fun String.getUnifiedParamsTypes(): List<String> {
11+
val list = mutableListOf<String>()
12+
var readObject = false
13+
for (c in this) {
14+
if (c == '(') {
15+
continue
16+
}
17+
if (c == ')') {
18+
break
19+
}
20+
if (readObject) {
21+
if (c == ';') {
22+
readObject = false
23+
list.add("Ljava/lang/Object;")
24+
}
25+
} else if (c == 'L') {
26+
readObject = true
27+
} else {
28+
list.add(c.toString())
29+
}
30+
}
31+
32+
return list
33+
}
34+
35+
private fun String.unifyTypeDescriptor(): String =
36+
if (startsWith('L')) {
37+
"Ljava/lang/Object;"
38+
} else {
39+
this
40+
}
41+
42+
private fun String.getReturnType(): String =
43+
substringAfter(')')
44+
45+
private fun getStoreDescriptor(descriptor: String): String = buildString {
46+
append('(')
47+
append(descriptor.getReturnType().unifyTypeDescriptor())
48+
append("Ljava/lang/String;)V")
49+
}
50+
51+
private fun MethodVisitor.invoke(name: String, descriptor: String) {
52+
visitMethodInsn(Opcodes.INVOKESTATIC, internalName, name, descriptor, false)
53+
}
54+
55+
fun insertAfterNDMethod(mv: MethodVisitor, owner: String, name: String, descriptor: String, isStatic: Boolean) {
56+
mv.visitInsn(Opcodes.DUP)
57+
mv.visitLdcInsn(NonDeterministicResultStorage.makeSignature(owner, name, descriptor))
58+
mv.invoke(if (isStatic) "storeStatic" else "storeCall", getStoreDescriptor(descriptor))
59+
}
60+
61+
fun insertBeforeNDMethod(mv: MethodVisitor, descriptor: String, isStatic: Boolean) {
62+
if (isStatic) {
63+
return
64+
}
65+
66+
val params = descriptor.getUnifiedParamsTypes()
67+
68+
params.asReversed().forEach {
69+
mv.invoke("putParameter${it[0]}", "($it)V")
70+
}
71+
72+
mv.visitInsn(Opcodes.DUP)
73+
mv.invoke("saveInstance", "(Ljava/lang/Object;)V")
74+
75+
params.forEach {
76+
mv.invoke("peakParameter${it[0]}", "()$it")
77+
}
78+
}
79+
80+
fun insertAfterNDInstanceConstructor(mv: MethodVisitor, callSite: String) {
81+
mv.visitInsn(Opcodes.DUP)
82+
mv.visitLdcInsn(callSite)
83+
mv.invoke("registerInstance", "(Ljava/lang/Object;Ljava/lang/String;)V")
84+
}
85+
}

0 commit comments

Comments
 (0)