Skip to content

Commit e7e448a

Browse files
authored
Fuzzer should change objects with its public setters and fields #289 (#439)
1 parent d438849 commit e7e448a

File tree

6 files changed

+246
-17
lines changed

6 files changed

+246
-17
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,8 @@ class UtBotSymbolicEngine(
406406

407407
val methodUnderTestDescription = FuzzedMethodDescription(executableId, collectConstantsForFuzzer(graph)).apply {
408408
compilableName = if (methodUnderTest.isMethod) executableId.name else null
409+
className = executableId.classId.simpleName
410+
packageName = executableId.classId.packageName
409411
val names = graph.body.method.tags.filterIsInstance<ParamNamesTag>().firstOrNull()?.names
410412
parameterNameMap = { index -> names?.getOrNull(index) }
411413
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ class FuzzedMethodDescription(
2525
*/
2626
var compilableName: String? = null
2727

28+
/**
29+
* Class Name
30+
*/
31+
var className: String? = null
32+
33+
/**
34+
* Package Name
35+
*/
36+
var packageName: String? = null
37+
2838
/**
2939
* Returns parameter name by its index in the signature
3040
*/

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

Lines changed: 113 additions & 14 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,7 +23,10 @@ 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
21-
import java.lang.reflect.Modifier
26+
import java.lang.reflect.Field
27+
import java.lang.reflect.Member
28+
import java.lang.reflect.Method
29+
import java.lang.reflect.Modifier.*
2230
import java.util.function.BiConsumer
2331
import java.util.function.IntSupplier
2432

@@ -28,11 +36,25 @@ import java.util.function.IntSupplier
2836
class ObjectModelProvider : ModelProvider {
2937

3038
var modelProvider: ModelProvider
39+
var limitValuesCreatedByFieldAccessors: Int = 100
40+
set(value) {
41+
field = maxOf(0, value)
42+
}
3143

3244
private val idGenerator: IntSupplier
3345
private val recursion: Int
3446
private val limit: Int
3547

48+
private val nonRecursiveModelProvider: ModelProvider
49+
get() {
50+
val modelProviderWithoutRecursion = modelProvider.exceptIsInstance<ObjectModelProvider>()
51+
return if (recursion > 0) {
52+
ObjectModelProvider(idGenerator, limit = 1, recursion - 1).with(modelProviderWithoutRecursion)
53+
} else {
54+
modelProviderWithoutRecursion.withFallback(NullModelProvider)
55+
}
56+
}
57+
3658
constructor(idGenerator: IntSupplier) : this(idGenerator, Int.MAX_VALUE)
3759

3860
constructor(idGenerator: IntSupplier, limit: Int) : this(idGenerator, limit, 1)
@@ -50,25 +72,21 @@ class ObjectModelProvider : ModelProvider {
5072
.filterNot { it == stringClassId || it.isPrimitiveWrapper }
5173
.flatMap { classId ->
5274
collectConstructors(classId) { javaConstructor ->
53-
isPublic(javaConstructor)
75+
isAccessible(javaConstructor, description.packageName)
5476
}.sortedWith(
5577
primitiveParameterizedConstructorsFirstAndThenByParameterCount
5678
).take(limit)
5779
}
58-
.associateWith { constructorId ->
59-
val modelProviderWithoutRecursion = modelProvider.exceptIsInstance<ObjectModelProvider>()
80+
.associateWith { constructorId ->
6081
fuzzParameters(
6182
constructorId,
62-
if (recursion > 0) {
63-
ObjectModelProvider(idGenerator, limit = 1, recursion - 1).with(modelProviderWithoutRecursion)
64-
} else {
65-
modelProviderWithoutRecursion.withFallback(NullModelProvider)
66-
}
83+
nonRecursiveModelProvider
6784
)
6885
}
6986
.flatMap { (constructorId, fuzzedParameters) ->
7087
if (constructorId.parameters.isEmpty()) {
71-
sequenceOf(assembleModel(idGenerator.asInt, constructorId, emptyList()))
88+
sequenceOf(assembleModel(idGenerator.asInt, constructorId, emptyList())) +
89+
generateModelsWithFieldsInitialization(constructorId, description, concreteValues)
7290
}
7391
else {
7492
fuzzedParameters.map { params ->
@@ -85,6 +103,42 @@ class ObjectModelProvider : ModelProvider {
85103
}
86104
}
87105

106+
private fun generateModelsWithFieldsInitialization(constructorId: ConstructorId, description: FuzzedMethodDescription, concreteValues: Collection<FuzzedConcreteValue>): Sequence<FuzzedValue> {
107+
if (limitValuesCreatedByFieldAccessors == 0) return emptySequence()
108+
val fields = findSuitableFields(constructorId.classId, description)
109+
val syntheticClassFieldsSetterMethodDescription = FuzzedMethodDescription(
110+
"${constructorId.classId.simpleName}<syntheticClassFieldSetter>",
111+
voidClassId,
112+
fields.map { it.classId },
113+
concreteValues
114+
)
115+
116+
return fuzz(syntheticClassFieldsSetterMethodDescription, nonRecursiveModelProvider)
117+
.take(limitValuesCreatedByFieldAccessors) // limit the number of fuzzed values in this particular case
118+
.map { fieldValues ->
119+
val fuzzedModel = assembleModel(idGenerator.asInt, constructorId, emptyList())
120+
val assembleModel = fuzzedModel.model as? UtAssembleModel ?: error("Expected UtAssembleModel but ${fuzzedModel.model::class.java} found")
121+
val modificationChain = assembleModel.modificationsChain as? MutableList ?: error("Modification chain must be mutable")
122+
fieldValues.asSequence().mapIndexedNotNull { index, value ->
123+
val field = fields[index]
124+
when {
125+
field.canBeSetDirectly -> UtDirectSetFieldModel(
126+
fuzzedModel.model,
127+
FieldId(constructorId.classId, field.name),
128+
value.model
129+
)
130+
field.setter != null -> UtExecutableCallModel(
131+
fuzzedModel.model,
132+
MethodId(constructorId.classId, field.setter.name, field.setter.returnType.id, listOf(field.classId)),
133+
listOf(value.model)
134+
)
135+
else -> null
136+
}
137+
}.forEach(modificationChain::add)
138+
fuzzedModel
139+
}
140+
}
141+
88142
companion object {
89143
private fun collectConstructors(classId: ClassId, predicate: (Constructor<*>) -> Boolean): Sequence<ConstructorId> {
90144
return classId.jClass.declaredConstructors.asSequence()
@@ -94,8 +148,16 @@ class ObjectModelProvider : ModelProvider {
94148
}
95149
}
96150

97-
private fun isPublic(javaConstructor: Constructor<*>): Boolean {
98-
return javaConstructor.modifiers and Modifier.PUBLIC != 0
151+
private fun isAccessible(member: Member, packageName: String?): Boolean {
152+
return isPublic(member.modifiers) ||
153+
(isPackagePrivate(member.modifiers) && member.declaringClass.`package`.name == packageName)
154+
}
155+
156+
private fun isPackagePrivate(modifiers: Int): Boolean {
157+
val hasAnyAccessModifier = isPrivate(modifiers)
158+
|| isProtected(modifiers)
159+
|| isProtected(modifiers)
160+
return !hasAnyAccessModifier
99161
}
100162

101163
private fun FuzzedMethodDescription.fuzzParameters(constructorId: ConstructorId, vararg modelProviders: ModelProvider): Sequence<List<FuzzedValue>> {
@@ -112,14 +174,44 @@ class ObjectModelProvider : ModelProvider {
112174
id,
113175
constructorId.classId,
114176
"${constructorId.classId.name}${constructorId.parameters}#" + id.toString(16),
115-
instantiationChain
177+
instantiationChain = instantiationChain,
178+
modificationsChain = mutableListOf()
116179
).apply {
117180
instantiationChain += UtExecutableCallModel(null, constructorId, params.map { it.model }, this)
118181
}.fuzzed {
119182
summary = "%var% = ${constructorId.classId.simpleName}(${constructorId.parameters.joinToString { it.simpleName }})"
120183
}
121184
}
122185

186+
private fun findSuitableFields(classId: ClassId, description: FuzzedMethodDescription): List<FieldDescription> {
187+
val jClass = classId.jClass
188+
return jClass.declaredFields.map { field ->
189+
FieldDescription(
190+
field.name,
191+
field.type.id,
192+
isAccessible(field, description.packageName) && !isFinal(field.modifiers) && !isStatic(field.modifiers),
193+
jClass.findPublicSetterIfHasPublicGetter(field, description)
194+
)
195+
}
196+
}
197+
198+
private fun Class<*>.findPublicSetterIfHasPublicGetter(field: Field, description: FuzzedMethodDescription): Method? {
199+
val postfixName = field.name.capitalize()
200+
val setterName = "set$postfixName"
201+
val getterName = "get$postfixName"
202+
val getter = try { getDeclaredMethod(getterName) } catch (_: NoSuchMethodException) { return null }
203+
return if (isAccessible(getter, description.packageName) && getter.returnType == field.type) {
204+
declaredMethods.find {
205+
isAccessible(it, description.packageName) &&
206+
it.name == setterName &&
207+
it.parameterCount == 1 &&
208+
it.parameterTypes[0] == field.type
209+
}
210+
} else {
211+
null
212+
}
213+
}
214+
123215
private val primitiveParameterizedConstructorsFirstAndThenByParameterCount =
124216
compareByDescending<ConstructorId> { constructorId ->
125217
constructorId.parameters.all { classId ->
@@ -128,5 +220,12 @@ class ObjectModelProvider : ModelProvider {
128220
}.thenComparingInt { constructorId ->
129221
constructorId.parameters.size
130222
}
223+
224+
private class FieldDescription(
225+
val name: String,
226+
val classId: ClassId,
227+
val canBeSetDirectly: Boolean,
228+
val setter: Method?,
229+
)
131230
}
132-
}
231+
}
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+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.utbot.framework.plugin.api.samples;
2+
3+
@SuppressWarnings("All")
4+
public class PackagePrivateFieldAndClass {
5+
6+
volatile int pkgField = 0;
7+
8+
PackagePrivateFieldAndClass() {
9+
10+
}
11+
12+
PackagePrivateFieldAndClass(int value) {
13+
pkgField = value;
14+
}
15+
16+
}

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

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ 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
27+
import org.utbot.framework.plugin.api.samples.PackagePrivateFieldAndClass
2628
import org.utbot.framework.plugin.api.util.primitiveByWrapper
2729
import org.utbot.framework.plugin.api.util.primitiveWrappers
2830
import org.utbot.framework.plugin.api.util.voidWrapperClassId
@@ -431,15 +433,87 @@ class ModelProviderTest {
431433
}
432434
}
433435

436+
@Test
437+
fun `test complex object is created with setters`() {
438+
val j = FieldSetterClass::class.java
439+
assertEquals(6, j.declaredFields.size)
440+
assertTrue(
441+
setOf(
442+
"pubStaticField",
443+
"pubFinalField",
444+
"pubField",
445+
"pubFieldWithSetter",
446+
"prvField",
447+
"prvFieldWithSetter",
448+
).containsAll(j.declaredFields.map { it.name })
449+
)
450+
assertEquals(4, j.declaredMethods.size)
451+
assertTrue(
452+
setOf(
453+
"getPubFieldWithSetter",
454+
"setPubFieldWithSetter",
455+
"getPrvFieldWithSetter",
456+
"setPrvFieldWithSetter",
457+
).containsAll(j.declaredMethods.map { it.name })
458+
)
459+
460+
withUtContext(UtContext(this::class.java.classLoader)) {
461+
val result = collect(ObjectModelProvider { 0 }.apply {
462+
modelProvider = PrimitiveDefaultsModelProvider
463+
}, parameters = listOf(FieldSetterClass::class.java.id))
464+
assertEquals(1, result.size)
465+
assertEquals(2, result[0]!!.size)
466+
assertEquals(0, (result[0]!![0] as UtAssembleModel).modificationsChain.size) { "One of models must be without any modifications" }
467+
val expectedModificationSize = 3
468+
val modificationsChain = (result[0]!![1] as UtAssembleModel).modificationsChain
469+
val actualModificationSize = modificationsChain.size
470+
assertEquals(expectedModificationSize, actualModificationSize) { "In target class there's only $expectedModificationSize fields that can be changed, but generated $actualModificationSize modifications" }
471+
472+
assertEquals("pubField", (modificationsChain[0] as UtDirectSetFieldModel).fieldId.name)
473+
assertEquals("pubFieldWithSetter", (modificationsChain[1] as UtDirectSetFieldModel).fieldId.name)
474+
assertEquals("setPrvFieldWithSetter", (modificationsChain[2] as UtExecutableCallModel).executable.name)
475+
}
476+
}
477+
478+
@Test
479+
fun `test complex object is created with setters and package private field and constructor`() {
480+
val j = PackagePrivateFieldAndClass::class.java
481+
assertEquals(1, j.declaredFields.size)
482+
assertTrue(
483+
setOf(
484+
"pkgField",
485+
).containsAll(j.declaredFields.map { it.name })
486+
)
487+
488+
withUtContext(UtContext(this::class.java.classLoader)) {
489+
val result = collect(ObjectModelProvider { 0 }.apply {
490+
modelProvider = PrimitiveDefaultsModelProvider
491+
}, parameters = listOf(PackagePrivateFieldAndClass::class.java.id)) {
492+
packageName = PackagePrivateFieldAndClass::class.java.`package`.name
493+
}
494+
assertEquals(1, result.size)
495+
assertEquals(3, result[0]!!.size)
496+
assertEquals(0, (result[0]!![0] as UtAssembleModel).modificationsChain.size) { "One of models must be without any modifications" }
497+
assertEquals(0, (result[0]!![2] as UtAssembleModel).modificationsChain.size) { "Modification by constructor doesn't change fields" }
498+
val expectedModificationSize = 1
499+
val modificationsChain = (result[0]!![1] as UtAssembleModel).modificationsChain
500+
val actualModificationSize = modificationsChain.size
501+
assertEquals(expectedModificationSize, actualModificationSize) { "In target class there's only $expectedModificationSize fields that can be changed, but generated $actualModificationSize modifications" }
502+
503+
assertEquals("pkgField", (modificationsChain[0] as UtDirectSetFieldModel).fieldId.name)
504+
}
505+
}
506+
434507
private fun collect(
435508
modelProvider: ModelProvider,
436509
name: String = "testMethod",
437510
returnType: ClassId = voidClassId,
438511
parameters: List<ClassId>,
439-
constants: List<FuzzedConcreteValue> = emptyList()
512+
constants: List<FuzzedConcreteValue> = emptyList(),
513+
block: FuzzedMethodDescription.() -> Unit = {}
440514
): Map<Int, List<UtModel>> {
441515
return mutableMapOf<Int, MutableList<UtModel>>().apply {
442-
modelProvider.generate(FuzzedMethodDescription(name, returnType, parameters, constants)) { i, m ->
516+
modelProvider.generate(FuzzedMethodDescription(name, returnType, parameters, constants).apply(block)) { i, m ->
443517
computeIfAbsent(i) { mutableListOf() }.add(m.model)
444518
}
445519
}
@@ -448,4 +522,4 @@ class ModelProviderTest {
448522
private enum class OneTwoThree {
449523
ONE, TWO, THREE
450524
}
451-
}
525+
}

0 commit comments

Comments
 (0)