Skip to content

Commit 3f538a3

Browse files
committed
Fuzzer should change objects which are created with empty constructor by its public setters #289
1 parent 9a3acb6 commit 3f538a3

File tree

3 files changed

+178
-11
lines changed

3 files changed

+178
-11
lines changed

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

Lines changed: 106 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@ package org.utbot.fuzzer.providers
22

33
import org.utbot.framework.plugin.api.ClassId
44
import org.utbot.framework.plugin.api.ConstructorId
5+
import org.utbot.framework.plugin.api.FieldId
6+
import org.utbot.framework.plugin.api.MethodId
57
import org.utbot.framework.plugin.api.UtAssembleModel
8+
import org.utbot.framework.plugin.api.UtDirectSetFieldModel
69
import org.utbot.framework.plugin.api.UtExecutableCallModel
710
import org.utbot.framework.plugin.api.UtStatementModel
811
import org.utbot.framework.plugin.api.util.id
912
import org.utbot.framework.plugin.api.util.isPrimitive
1013
import org.utbot.framework.plugin.api.util.isPrimitiveWrapper
1114
import org.utbot.framework.plugin.api.util.jClass
1215
import org.utbot.framework.plugin.api.util.stringClassId
16+
import org.utbot.framework.plugin.api.util.voidClassId
17+
import org.utbot.fuzzer.FuzzedConcreteValue
1318
import org.utbot.fuzzer.FuzzedMethodDescription
1419
import org.utbot.fuzzer.FuzzedValue
1520
import org.utbot.fuzzer.ModelProvider
@@ -18,6 +23,8 @@ import org.utbot.fuzzer.fuzz
1823
import org.utbot.fuzzer.objectModelProviders
1924
import org.utbot.fuzzer.providers.ConstantsModelProvider.fuzzed
2025
import java.lang.reflect.Constructor
26+
import java.lang.reflect.Field
27+
import java.lang.reflect.Method
2128
import java.lang.reflect.Modifier
2229
import java.util.function.BiConsumer
2330
import java.util.function.IntSupplier
@@ -33,6 +40,16 @@ class ObjectModelProvider : ModelProvider {
3340
private val recursion: Int
3441
private val limit: Int
3542

43+
private val nonRecursiveModelProvider: ModelProvider
44+
get() {
45+
val modelProviderWithoutRecursion = modelProvider.exceptIsInstance<ObjectModelProvider>()
46+
return if (recursion > 0) {
47+
ObjectModelProvider(idGenerator, limit = 1, recursion - 1).with(modelProviderWithoutRecursion)
48+
} else {
49+
modelProviderWithoutRecursion.withFallback(NullModelProvider)
50+
}
51+
}
52+
3653
constructor(idGenerator: IntSupplier) : this(idGenerator, Int.MAX_VALUE)
3754

3855
constructor(idGenerator: IntSupplier, limit: Int) : this(idGenerator, limit, 1)
@@ -55,20 +72,16 @@ class ObjectModelProvider : ModelProvider {
5572
primitiveParameterizedConstructorsFirstAndThenByParameterCount
5673
).take(limit)
5774
}
58-
.associateWith { constructorId ->
59-
val modelProviderWithoutRecursion = modelProvider.exceptIsInstance<ObjectModelProvider>()
75+
.associateWith { constructorId ->
6076
fuzzParameters(
6177
constructorId,
62-
if (recursion > 0) {
63-
ObjectModelProvider(idGenerator, limit = 1, recursion - 1).with(modelProviderWithoutRecursion)
64-
} else {
65-
modelProviderWithoutRecursion.withFallback(NullModelProvider)
66-
}
78+
nonRecursiveModelProvider
6779
)
6880
}
6981
.flatMap { (constructorId, fuzzedParameters) ->
7082
if (constructorId.parameters.isEmpty()) {
71-
sequenceOf(assembleModel(idGenerator.asInt, constructorId, emptyList()))
83+
sequenceOf(assembleModel(idGenerator.asInt, constructorId, emptyList())) +
84+
generateModelsWithFieldsInitialization(constructorId, concreteValues)
7285
}
7386
else {
7487
fuzzedParameters.map { params ->
@@ -85,6 +98,40 @@ class ObjectModelProvider : ModelProvider {
8598
}
8699
}
87100

101+
private fun generateModelsWithFieldsInitialization(constructorId: ConstructorId, concreteValues: Collection<FuzzedConcreteValue>): Sequence<FuzzedValue> {
102+
val fields = findSuitableFields(constructorId.classId)
103+
val syntheticClassFieldsSetterMethodDescription = FuzzedMethodDescription(
104+
"${constructorId.classId.simpleName}<syntheticClassFieldSetter>",
105+
voidClassId,
106+
fields.map { it.classId },
107+
concreteValues
108+
)
109+
110+
return fuzz(syntheticClassFieldsSetterMethodDescription, nonRecursiveModelProvider)
111+
.map { fieldValues ->
112+
val fuzzedModel = assembleModel(idGenerator.asInt, constructorId, emptyList())
113+
val assembleModel = fuzzedModel.model as? UtAssembleModel ?: error("Expected UtAssembleModel but ${fuzzedModel.model::class.java} found")
114+
val modificationChain = assembleModel.modificationsChain as? MutableList ?: error("Modification chain must be mutable")
115+
fieldValues.asSequence().mapIndexedNotNull { index, value ->
116+
val field = fields[index]
117+
when {
118+
field.setter != null -> UtExecutableCallModel(
119+
fuzzedModel.model,
120+
MethodId(constructorId.classId, field.setter.name, field.setter.returnType.id, listOf(field.classId)),
121+
listOf(value.model)
122+
)
123+
field.canBeSetDirectly -> UtDirectSetFieldModel(
124+
fuzzedModel.model,
125+
FieldId(constructorId.classId, field.name),
126+
value.model
127+
)
128+
else -> null
129+
}
130+
}.forEach(modificationChain::add)
131+
fuzzedModel
132+
}
133+
}
134+
88135
companion object {
89136
private fun collectConstructors(classId: ClassId, predicate: (Constructor<*>) -> Boolean): Sequence<ConstructorId> {
90137
return classId.jClass.declaredConstructors.asSequence()
@@ -112,14 +159,56 @@ class ObjectModelProvider : ModelProvider {
112159
id,
113160
constructorId.classId,
114161
"${constructorId.classId.name}${constructorId.parameters}#" + id.toString(16),
115-
instantiationChain
162+
instantiationChain = instantiationChain,
163+
modificationsChain = mutableListOf()
116164
).apply {
117165
instantiationChain += UtExecutableCallModel(null, constructorId, params.map { it.model }, this)
118166
}.fuzzed {
119167
summary = "%var% = ${constructorId.classId.simpleName}(${constructorId.parameters.joinToString { it.simpleName }})"
120168
}
121169
}
122170

171+
private fun findSuitableFields(classId: ClassId): List<FieldDescription> {
172+
val jClass = classId.jClass
173+
return jClass.declaredFields.map { field ->
174+
FieldDescription(
175+
field.name,
176+
field.type.id,
177+
field.isPublic && !field.isFinal && !field.isStatic,
178+
jClass.findPublicSetterIfHasPublicGetter(field)
179+
)
180+
}
181+
}
182+
183+
private fun Class<*>.findPublicSetterIfHasPublicGetter(field: Field): Method? {
184+
val postfixName = field.name.capitalize()
185+
val setterName = "set$postfixName"
186+
val getterName = "get$postfixName"
187+
val getter = try { getDeclaredMethod(getterName) } catch (_: NoSuchMethodException) { return null }
188+
return if (getter has Modifier.PUBLIC && getter.returnType == field.type) {
189+
declaredMethods.find {
190+
it has Modifier.PUBLIC &&
191+
it.name == setterName &&
192+
it.parameterCount == 1 &&
193+
it.parameterTypes[0] == field.type
194+
}
195+
} else {
196+
null
197+
}
198+
}
199+
private val Field.isPublic
200+
get() = has(Modifier.PUBLIC)
201+
202+
private val Field.isFinal
203+
get() = has(Modifier.FINAL)
204+
205+
private val Field.isStatic
206+
get() = has(Modifier.STATIC)
207+
208+
private infix fun Field.has(modifier: Int) = (modifiers and modifier) != 0
209+
210+
private infix fun Method.has(modifier: Int) = (modifiers and modifier) != 0
211+
123212
private val primitiveParameterizedConstructorsFirstAndThenByParameterCount =
124213
compareByDescending<ConstructorId> { constructorId ->
125214
constructorId.parameters.all { classId ->
@@ -128,5 +217,12 @@ class ObjectModelProvider : ModelProvider {
128217
}.thenComparingInt { constructorId ->
129218
constructorId.parameters.size
130219
}
220+
221+
private class FieldDescription(
222+
val name: String,
223+
val classId: ClassId,
224+
val canBeSetDirectly: Boolean,
225+
val setter: Method?,
226+
)
131227
}
132-
}
228+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.utbot.framework.plugin.api.samples;
2+
3+
@SuppressWarnings("All")
4+
public class FieldSetterClass {
5+
6+
public static int pubStaticField;
7+
public final int pubFinalField = 0;
8+
public int pubField;
9+
public int pubFieldWithSetter;
10+
private int prvField;
11+
private int prvFieldWithSetter;
12+
13+
public int getPubFieldWithSetter() {
14+
return pubFieldWithSetter;
15+
}
16+
17+
public void setPubFieldWithSetter(int pubFieldWithSetter) {
18+
this.pubFieldWithSetter = pubFieldWithSetter;
19+
}
20+
21+
public int getPrvFieldWithSetter() {
22+
return prvFieldWithSetter;
23+
}
24+
25+
public void setPrvFieldWithSetter(int prvFieldWithSetter) {
26+
this.prvFieldWithSetter = prvFieldWithSetter;
27+
}
28+
}

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

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import org.utbot.fuzzer.providers.PrimitivesModelProvider
2323
import org.utbot.fuzzer.providers.StringConstantModelProvider
2424
import org.junit.jupiter.api.Assertions.*
2525
import org.junit.jupiter.api.Test
26+
import org.utbot.framework.plugin.api.samples.FieldSetterClass
2627
import org.utbot.framework.plugin.api.util.primitiveByWrapper
2728
import org.utbot.framework.plugin.api.util.primitiveWrappers
2829
import org.utbot.framework.plugin.api.util.voidWrapperClassId
@@ -431,6 +432,48 @@ class ModelProviderTest {
431432
}
432433
}
433434

435+
@Test
436+
fun `test complex object is created with setters`() {
437+
val j = FieldSetterClass::class.java
438+
assertEquals(6, j.declaredFields.size)
439+
assertTrue(
440+
setOf(
441+
"pubStaticField",
442+
"pubFinalField",
443+
"pubField",
444+
"pubFieldWithSetter",
445+
"prvField",
446+
"prvFieldWithSetter",
447+
).containsAll(j.declaredFields.map { it.name })
448+
)
449+
assertEquals(4, j.declaredMethods.size)
450+
assertTrue(
451+
setOf(
452+
"getPubFieldWithSetter",
453+
"setPubFieldWithSetter",
454+
"getPrvFieldWithSetter",
455+
"setPrvFieldWithSetter",
456+
).containsAll(j.declaredMethods.map { it.name })
457+
)
458+
459+
withUtContext(UtContext(this::class.java.classLoader)) {
460+
val result = collect(ObjectModelProvider { 0 }.apply {
461+
modelProvider = PrimitiveDefaultsModelProvider
462+
}, parameters = listOf(FieldSetterClass::class.java.id))
463+
assertEquals(1, result.size)
464+
assertEquals(2, result[0]!!.size)
465+
assertEquals(0, (result[0]!![0] as UtAssembleModel).modificationsChain.size) { "One of models must be without any modifications" }
466+
val expectedModificationSize = 3
467+
val modificationsChain = (result[0]!![1] as UtAssembleModel).modificationsChain
468+
val actualModificationSize = modificationsChain.size
469+
assertEquals(expectedModificationSize, actualModificationSize) { "In target class there's only $expectedModificationSize fields that can be changed, but generated $actualModificationSize modifications" }
470+
471+
assertEquals("pubField", (modificationsChain[0] as UtDirectSetFieldModel).fieldId.name)
472+
assertEquals("setPubFieldWithSetter", (modificationsChain[1] as UtExecutableCallModel).executable.name)
473+
assertEquals("setPrvFieldWithSetter", (modificationsChain[2] as UtExecutableCallModel).executable.name)
474+
}
475+
}
476+
434477
private fun collect(
435478
modelProvider: ModelProvider,
436479
name: String = "testMethod",
@@ -448,4 +491,4 @@ class ModelProviderTest {
448491
private enum class OneTwoThree {
449492
ONE, TWO, THREE
450493
}
451-
}
494+
}

0 commit comments

Comments
 (0)