Skip to content

Commit 16ea1b7

Browse files
committed
Add RecursiveModelProvider abstraction
1 parent 33086d6 commit 16ea1b7

File tree

5 files changed

+121
-80
lines changed

5 files changed

+121
-80
lines changed

utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt

+12-3
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,11 @@ fun defaultModelProviders(idGenerator: IdentityPreservingIdGenerator<Int>): Mode
134134
}
135135

136136
/**
137-
* Creates a model provider for [ObjectModelProvider] that generates values for object constructor.
137+
* Creates a model provider consisting of providers that do not make recursive calls inside them
138138
*/
139-
fun objectModelProviders(idGenerator: IdentityPreservingIdGenerator<Int>): ModelProvider {
139+
fun nonRecursiveProviders(idGenerator: IdentityPreservingIdGenerator<Int>): ModelProvider {
140140
return ModelProvider.of(
141141
CollectionModelProvider(idGenerator),
142-
ArrayModelProvider(idGenerator),
143142
EnumModelProvider(idGenerator),
144143
StringConstantModelProvider,
145144
CharToStringModelProvider,
@@ -148,3 +147,13 @@ fun objectModelProviders(idGenerator: IdentityPreservingIdGenerator<Int>): Model
148147
PrimitiveWrapperModelProvider,
149148
)
150149
}
150+
151+
/**
152+
* Creates a model provider consisting of providers that will make no more than [recursion] nested recursive calls.
153+
*/
154+
fun recursiveModelProviders(idGenerator: IdentityPreservingIdGenerator<Int>, recursion: Int): ModelProvider {
155+
return ModelProvider.of(
156+
ObjectModelProvider(idGenerator, recursion),
157+
ArrayModelProvider(idGenerator, recursion)
158+
)
159+
}

utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ArrayModelProvider.kt

+36-44
Original file line numberDiff line numberDiff line change
@@ -2,62 +2,54 @@ package org.utbot.fuzzer.providers
22

33
import org.utbot.framework.plugin.api.ClassId
44
import org.utbot.framework.plugin.api.UtArrayModel
5+
import org.utbot.framework.plugin.api.UtModel
56
import org.utbot.framework.plugin.api.UtPrimitiveModel
67
import org.utbot.framework.plugin.api.util.defaultValueModel
78
import org.utbot.framework.plugin.api.util.intClassId
89
import org.utbot.framework.plugin.api.util.isArray
9-
import org.utbot.framework.plugin.api.util.voidClassId
1010
import org.utbot.fuzzer.FuzzedMethodDescription
1111
import org.utbot.fuzzer.FuzzedParameter
1212
import org.utbot.fuzzer.FuzzedValue
1313
import org.utbot.fuzzer.IdentityPreservingIdGenerator
14-
import org.utbot.fuzzer.ModelProvider
1514
import org.utbot.fuzzer.ModelProvider.Companion.yieldAllValues
16-
import org.utbot.fuzzer.defaultModelProviders
17-
import org.utbot.fuzzer.fuzz
1815

1916
class ArrayModelProvider(
20-
private val idGenerator: IdentityPreservingIdGenerator<Int>
21-
) : ModelProvider {
17+
idGenerator: IdentityPreservingIdGenerator<Int>,
18+
recursion: Int = 1
19+
) : RecursiveModelProvider(idGenerator, recursion) {
2220

23-
private val defaultArraySize: Int = 3
21+
private val defaultArraySize: Int
22+
get() = when(recursion) {
23+
1 -> 3
24+
else -> 1
25+
}
2426

2527
override fun generate(description: FuzzedMethodDescription): Sequence<FuzzedParameter> = sequence {
2628
description.parametersMap
2729
.asSequence()
2830
.filter { (classId, _) -> classId.isArray }
2931
.forEach { (arrayClassId, indices) ->
30-
val lengths = generateArrayLengths(description)
3132

3233
// Fuzz small arrays with interesting elements
3334
yieldAllValues(indices, generateArrayRecursively(arrayClassId, description, defaultArraySize))
3435

3536
// Fuzz arrays with interesting lengths and default-valued elements
37+
val lengths = generateArrayLengths(description)
3638
yieldAllValues(indices, lengths.asSequence().map { length ->
37-
UtArrayModel(
38-
id = idGenerator.createId(),
39-
arrayClassId,
40-
length = length,
41-
arrayClassId.elementClassId!!.defaultValueModel(),
42-
mutableMapOf()
43-
).fuzzed {
44-
this.summary = "%var% = ${arrayClassId.elementClassId!!.simpleName}[$length]"
45-
}
39+
createFuzzedArrayModel(arrayClassId, length, null)
4640
})
4741
}
4842
}
4943

5044
private fun generateArrayLengths(description: FuzzedMethodDescription): Set<Int> {
51-
val syntheticArrayLengthMethodDescription = FuzzedMethodDescription(
52-
"<syntheticArrayLength>",
53-
voidClassId,
54-
listOf(intClassId),
55-
description.concreteValues
56-
).apply {
57-
packageName = description.packageName
58-
}
45+
val fuzzedLengths = fuzzValuesRecursively(
46+
types = listOf(intClassId),
47+
baseMethodDescription = description,
48+
modelProvider = ConstantsModelProvider,
49+
generatedValuesName = "array length"
50+
)
5951

60-
return fuzz(syntheticArrayLengthMethodDescription, ConstantsModelProvider)
52+
return fuzzedLengths
6153
.map { (it.single().model as UtPrimitiveModel).value as Int }
6254
.filter { it in 0..10 }
6355
.toSet()
@@ -66,24 +58,24 @@ class ArrayModelProvider(
6658

6759
private fun generateArrayRecursively(arrayClassId: ClassId, description: FuzzedMethodDescription, length: Int): Sequence<FuzzedValue> {
6860
val elementClassId = arrayClassId.elementClassId ?: error("expected ClassId for array but got ${arrayClassId.name}")
69-
val syntheticArrayElementSetterMethodDescription = FuzzedMethodDescription(
70-
"${arrayClassId.simpleName}OfLength$length<syntheticArrayElementSetter>",
71-
voidClassId,
72-
List(length) { elementClassId },
73-
description.concreteValues
74-
).apply {
75-
packageName = description.packageName
76-
}
77-
return fuzz(syntheticArrayElementSetterMethodDescription, defaultModelProviders(idGenerator)).map {
78-
FuzzedValue(
79-
UtArrayModel(
80-
idGenerator.createId(),
81-
arrayClassId,
82-
length,
83-
elementClassId.defaultValueModel(),
84-
it.withIndex().associate { it.index to it.value.model }.toMutableMap()
85-
)
86-
)
61+
return fuzzValuesRecursively(
62+
types = List(length) { elementClassId },
63+
baseMethodDescription = description,
64+
modelProvider = generateRecursiveProvider(),
65+
generatedValuesName = "elements of array"
66+
).map { elements ->
67+
createFuzzedArrayModel(arrayClassId, length, elements.map { it.model })
8768
}
8869
}
70+
71+
private fun createFuzzedArrayModel(arrayClassId: ClassId, length: Int, values: List<UtModel>?) =
72+
UtArrayModel(
73+
idGenerator.createId(),
74+
arrayClassId,
75+
length,
76+
arrayClassId.elementClassId!!.defaultValueModel(),
77+
values?.withIndex()?.associate { it.index to it.value }?.toMutableMap() ?: mutableMapOf()
78+
).fuzzed {
79+
this.summary = "%var% = ${arrayClassId.elementClassId!!.simpleName}[$length]"
80+
}
8981
}

utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt

+19-30
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,14 @@ import org.utbot.framework.plugin.api.util.isPrimitive
1414
import org.utbot.framework.plugin.api.util.isPrimitiveWrapper
1515
import org.utbot.framework.plugin.api.util.jClass
1616
import org.utbot.framework.plugin.api.util.stringClassId
17-
import org.utbot.framework.plugin.api.util.voidClassId
1817
import org.utbot.fuzzer.IdentityPreservingIdGenerator
19-
import org.utbot.fuzzer.FuzzedConcreteValue
2018
import org.utbot.fuzzer.FuzzedMethodDescription
2119
import org.utbot.fuzzer.FuzzedParameter
2220
import org.utbot.fuzzer.FuzzedValue
2321
import org.utbot.fuzzer.ModelProvider
2422
import org.utbot.fuzzer.ModelProvider.Companion.yieldValue
2523
import org.utbot.fuzzer.TooManyCombinationsException
26-
import org.utbot.fuzzer.exceptIsInstance
2724
import org.utbot.fuzzer.fuzz
28-
import org.utbot.fuzzer.objectModelProviders
2925
import org.utbot.fuzzer.providers.ConstantsModelProvider.fuzzed
3026
import java.lang.reflect.Constructor
3127
import java.lang.reflect.Field
@@ -39,25 +35,20 @@ private val logger by lazy { KotlinLogging.logger {} }
3935
* Creates [UtAssembleModel] for objects which have public constructors with primitives types and String as parameters.
4036
*/
4137
class ObjectModelProvider(
42-
private val idGenerator: IdentityPreservingIdGenerator<Int>,
43-
private val limit: Int = Int.MAX_VALUE,
44-
private val recursion: Int = 1
45-
) : ModelProvider {
38+
idGenerator: IdentityPreservingIdGenerator<Int>,
39+
recursion: Int = 1,
40+
) : RecursiveModelProvider(idGenerator, recursion) {
4641

47-
var modelProvider: ModelProvider = objectModelProviders(idGenerator)
42+
// TODO: can we make it private val (maybe depending on recursion)?
4843
var limitValuesCreatedByFieldAccessors: Int = 100
4944
set(value) {
5045
field = maxOf(0, value)
5146
}
5247

53-
private val nonRecursiveModelProvider: ModelProvider
54-
get() {
55-
val modelProviderWithoutRecursion = modelProvider.exceptIsInstance<ObjectModelProvider>()
56-
return if (recursion > 0) {
57-
ObjectModelProvider(idGenerator, limit = 1, recursion - 1).with(modelProviderWithoutRecursion)
58-
} else {
59-
modelProviderWithoutRecursion.withFallback(NullModelProvider)
60-
}
48+
private val limit: Int =
49+
when(recursion) {
50+
1 -> Int.MAX_VALUE
51+
else -> 1
6152
}
6253

6354
override fun generate(description: FuzzedMethodDescription): Sequence<FuzzedParameter> = sequence {
@@ -74,14 +65,14 @@ class ObjectModelProvider(
7465
.associateWith { constructorId ->
7566
fuzzParameters(
7667
constructorId,
77-
nonRecursiveModelProvider
68+
generateRecursiveProvider()
7869
)
7970
}
8071
.asSequence()
8172
.flatMap { (constructorId, fuzzedParameters) ->
8273
if (constructorId.parameters.isEmpty()) {
8374
sequenceOf(assembleModel(idGenerator.createId(), constructorId, emptyList())) +
84-
generateModelsWithFieldsInitialization(constructorId, description, concreteValues)
75+
generateModelsWithFieldsInitialization(constructorId, description)
8576
}
8677
else {
8778
fuzzedParameters.map { params ->
@@ -98,20 +89,18 @@ class ObjectModelProvider(
9889
}
9990
}
10091

101-
private fun generateModelsWithFieldsInitialization(constructorId: ConstructorId, description: FuzzedMethodDescription, concreteValues: Collection<FuzzedConcreteValue>): Sequence<FuzzedValue> {
92+
private fun generateModelsWithFieldsInitialization(constructorId: ConstructorId, description: FuzzedMethodDescription): Sequence<FuzzedValue> {
10293
if (limitValuesCreatedByFieldAccessors == 0) return emptySequence()
10394
val fields = findSuitableFields(constructorId.classId, description)
104-
val syntheticClassFieldsSetterMethodDescription = FuzzedMethodDescription(
105-
"${constructorId.classId.simpleName}<syntheticClassFieldSetter>",
106-
voidClassId,
107-
fields.map { it.classId },
108-
concreteValues
109-
).apply {
110-
packageName = description.packageName
111-
}
11295

113-
return fuzz(syntheticClassFieldsSetterMethodDescription, nonRecursiveModelProvider)
114-
.take(limitValuesCreatedByFieldAccessors) // limit the number of fuzzed values in this particular case
96+
val fieldValuesSets = fuzzValuesRecursively(
97+
types = fields.map { it.classId },
98+
baseMethodDescription = description,
99+
modelProvider = generateRecursiveProvider(),
100+
generatedValuesName = "${constructorId.classId.simpleName} fields"
101+
).take(limitValuesCreatedByFieldAccessors) // limit the number of fuzzed values in this particular case
102+
103+
return fieldValuesSets
115104
.map { fieldValues ->
116105
val fuzzedModel = assembleModel(idGenerator.createId(), constructorId, emptyList())
117106
val assembleModel = fuzzedModel.model as? UtAssembleModel ?: error("Expected UtAssembleModel but ${fuzzedModel.model::class.java} found")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.utbot.fuzzer.providers
2+
3+
import org.utbot.framework.plugin.api.ClassId
4+
import org.utbot.framework.plugin.api.util.voidClassId
5+
import org.utbot.fuzzer.FuzzedMethodDescription
6+
import org.utbot.fuzzer.FuzzedValue
7+
import org.utbot.fuzzer.IdentityPreservingIdGenerator
8+
import org.utbot.fuzzer.ModelProvider
9+
import org.utbot.fuzzer.fuzz
10+
import org.utbot.fuzzer.nonRecursiveProviders
11+
import org.utbot.fuzzer.recursiveModelProviders
12+
13+
// TODO: maybe use `recursionDepth` instead of `recursion` here and in [recursiveModelProviders]?
14+
abstract class RecursiveModelProvider(
15+
protected val idGenerator: IdentityPreservingIdGenerator<Int>,
16+
protected val recursion: Int
17+
): ModelProvider {
18+
// TODO: currently it is var due to tests, maybe we can make it private val?
19+
var modelProviderForRecursiveCalls: ModelProvider =
20+
if (recursion > 0)
21+
nonRecursiveProviders(idGenerator).with(recursiveModelProviders(idGenerator, recursion - 1))
22+
else
23+
nonRecursiveProviders(idGenerator)
24+
25+
protected fun generateRecursiveProvider(
26+
baseProvider: ModelProvider = modelProviderForRecursiveCalls,
27+
fallbackProvider: ModelProvider = NullModelProvider
28+
): ModelProvider {
29+
return if (recursion > 0)
30+
baseProvider
31+
else
32+
baseProvider.withFallback(fallbackProvider)
33+
}
34+
35+
protected fun fuzzValuesRecursively(
36+
types: List<ClassId>,
37+
baseMethodDescription: FuzzedMethodDescription,
38+
modelProvider: ModelProvider,
39+
generatedValuesName: String
40+
): Sequence<List<FuzzedValue>> {
41+
val syntheticMethodDescription = FuzzedMethodDescription(
42+
"<synthetic method for fuzzing $generatedValuesName",
43+
voidClassId,
44+
types,
45+
baseMethodDescription.concreteValues
46+
).apply {
47+
packageName = baseMethodDescription.packageName
48+
}
49+
return fuzz(syntheticMethodDescription, modelProvider)
50+
}
51+
}

utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ class ModelProviderTest {
194194
val classId = A::class.java.id
195195
val models = collect(
196196
ObjectModelProvider(ReferencePreservingIntIdGenerator(0)).apply {
197-
modelProvider = ModelProvider.of(PrimitiveDefaultsModelProvider)
197+
modelProviderForRecursiveCalls = ModelProvider.of(PrimitiveDefaultsModelProvider)
198198
},
199199
parameters = listOf(classId)
200200
)
@@ -480,7 +480,7 @@ class ModelProviderTest {
480480

481481
withUtContext(UtContext(this::class.java.classLoader)) {
482482
val result = collect(ObjectModelProvider(ReferencePreservingIntIdGenerator(0)).apply {
483-
modelProvider = PrimitiveDefaultsModelProvider
483+
modelProviderForRecursiveCalls = PrimitiveDefaultsModelProvider
484484
}, parameters = listOf(FieldSetterClass::class.java.id))
485485
assertEquals(1, result.size)
486486
assertEquals(2, result[0]!!.size)
@@ -508,7 +508,7 @@ class ModelProviderTest {
508508

509509
withUtContext(UtContext(this::class.java.classLoader)) {
510510
val result = collect(ObjectModelProvider(ReferencePreservingIntIdGenerator(0)).apply {
511-
modelProvider = PrimitiveDefaultsModelProvider
511+
modelProviderForRecursiveCalls = PrimitiveDefaultsModelProvider
512512
}, parameters = listOf(PackagePrivateFieldAndClass::class.java.id)) {
513513
packageName = PackagePrivateFieldAndClass::class.java.`package`.name
514514
}

0 commit comments

Comments
 (0)