Skip to content

Fuzzer should change objects with its public setters #439

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,8 @@ class UtBotSymbolicEngine(

val methodUnderTestDescription = FuzzedMethodDescription(executableId, collectConstantsForFuzzer(graph)).apply {
compilableName = if (methodUnderTest.isMethod) executableId.name else null
className = executableId.classId.simpleName
packageName = executableId.classId.packageName
val names = graph.body.method.tags.filterIsInstance<ParamNamesTag>().firstOrNull()?.names
parameterNameMap = { index -> names?.getOrNull(index) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ class FuzzedMethodDescription(
*/
var compilableName: String? = null

/**
* Class Name
*/
var className: String? = null

/**
* Package Name
*/
var packageName: String? = null

/**
* Returns parameter name by its index in the signature
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ package org.utbot.fuzzer.providers

import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.ConstructorId
import org.utbot.framework.plugin.api.FieldId
import org.utbot.framework.plugin.api.MethodId
import org.utbot.framework.plugin.api.UtAssembleModel
import org.utbot.framework.plugin.api.UtDirectSetFieldModel
import org.utbot.framework.plugin.api.UtExecutableCallModel
import org.utbot.framework.plugin.api.UtStatementModel
import org.utbot.framework.plugin.api.util.id
import org.utbot.framework.plugin.api.util.isPrimitive
import org.utbot.framework.plugin.api.util.isPrimitiveWrapper
import org.utbot.framework.plugin.api.util.jClass
import org.utbot.framework.plugin.api.util.stringClassId
import org.utbot.framework.plugin.api.util.voidClassId
import org.utbot.fuzzer.FuzzedConcreteValue
import org.utbot.fuzzer.FuzzedMethodDescription
import org.utbot.fuzzer.FuzzedValue
import org.utbot.fuzzer.ModelProvider
Expand All @@ -18,7 +23,10 @@ import org.utbot.fuzzer.fuzz
import org.utbot.fuzzer.objectModelProviders
import org.utbot.fuzzer.providers.ConstantsModelProvider.fuzzed
import java.lang.reflect.Constructor
import java.lang.reflect.Modifier
import java.lang.reflect.Field
import java.lang.reflect.Member
import java.lang.reflect.Method
import java.lang.reflect.Modifier.*
import java.util.function.BiConsumer
import java.util.function.IntSupplier

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

var modelProvider: ModelProvider
var limitValuesCreatedByFieldAccessors: Int = 100
set(value) {
field = maxOf(0, value)
}

private val idGenerator: IntSupplier
private val recursion: Int
private val limit: Int

private val nonRecursiveModelProvider: ModelProvider
get() {
val modelProviderWithoutRecursion = modelProvider.exceptIsInstance<ObjectModelProvider>()
return if (recursion > 0) {
ObjectModelProvider(idGenerator, limit = 1, recursion - 1).with(modelProviderWithoutRecursion)
} else {
modelProviderWithoutRecursion.withFallback(NullModelProvider)
}
}

constructor(idGenerator: IntSupplier) : this(idGenerator, Int.MAX_VALUE)

constructor(idGenerator: IntSupplier, limit: Int) : this(idGenerator, limit, 1)
Expand All @@ -50,25 +72,21 @@ class ObjectModelProvider : ModelProvider {
.filterNot { it == stringClassId || it.isPrimitiveWrapper }
.flatMap { classId ->
collectConstructors(classId) { javaConstructor ->
isPublic(javaConstructor)
isAccessible(javaConstructor, description.packageName)
}.sortedWith(
primitiveParameterizedConstructorsFirstAndThenByParameterCount
).take(limit)
}
.associateWith { constructorId ->
val modelProviderWithoutRecursion = modelProvider.exceptIsInstance<ObjectModelProvider>()
.associateWith { constructorId ->
fuzzParameters(
constructorId,
if (recursion > 0) {
ObjectModelProvider(idGenerator, limit = 1, recursion - 1).with(modelProviderWithoutRecursion)
} else {
modelProviderWithoutRecursion.withFallback(NullModelProvider)
}
nonRecursiveModelProvider
)
}
.flatMap { (constructorId, fuzzedParameters) ->
if (constructorId.parameters.isEmpty()) {
sequenceOf(assembleModel(idGenerator.asInt, constructorId, emptyList()))
sequenceOf(assembleModel(idGenerator.asInt, constructorId, emptyList())) +
generateModelsWithFieldsInitialization(constructorId, description, concreteValues)
}
else {
fuzzedParameters.map { params ->
Expand All @@ -85,6 +103,42 @@ class ObjectModelProvider : ModelProvider {
}
}

private fun generateModelsWithFieldsInitialization(constructorId: ConstructorId, description: FuzzedMethodDescription, concreteValues: Collection<FuzzedConcreteValue>): Sequence<FuzzedValue> {
if (limitValuesCreatedByFieldAccessors == 0) return emptySequence()
val fields = findSuitableFields(constructorId.classId, description)
val syntheticClassFieldsSetterMethodDescription = FuzzedMethodDescription(
"${constructorId.classId.simpleName}<syntheticClassFieldSetter>",
voidClassId,
fields.map { it.classId },
concreteValues
)

return fuzz(syntheticClassFieldsSetterMethodDescription, nonRecursiveModelProvider)
.take(limitValuesCreatedByFieldAccessors) // limit the number of fuzzed values in this particular case
.map { fieldValues ->
val fuzzedModel = assembleModel(idGenerator.asInt, constructorId, emptyList())
val assembleModel = fuzzedModel.model as? UtAssembleModel ?: error("Expected UtAssembleModel but ${fuzzedModel.model::class.java} found")
val modificationChain = assembleModel.modificationsChain as? MutableList ?: error("Modification chain must be mutable")
fieldValues.asSequence().mapIndexedNotNull { index, value ->
val field = fields[index]
when {
field.canBeSetDirectly -> UtDirectSetFieldModel(
fuzzedModel.model,
FieldId(constructorId.classId, field.name),
value.model
)
field.setter != null -> UtExecutableCallModel(
fuzzedModel.model,
MethodId(constructorId.classId, field.setter.name, field.setter.returnType.id, listOf(field.classId)),
listOf(value.model)
)
else -> null
}
}.forEach(modificationChain::add)
fuzzedModel
}
}

companion object {
private fun collectConstructors(classId: ClassId, predicate: (Constructor<*>) -> Boolean): Sequence<ConstructorId> {
return classId.jClass.declaredConstructors.asSequence()
Expand All @@ -94,8 +148,16 @@ class ObjectModelProvider : ModelProvider {
}
}

private fun isPublic(javaConstructor: Constructor<*>): Boolean {
return javaConstructor.modifiers and Modifier.PUBLIC != 0
private fun isAccessible(member: Member, packageName: String?): Boolean {
return isPublic(member.modifiers) ||
(isPackagePrivate(member.modifiers) && member.declaringClass.`package`.name == packageName)
}

private fun isPackagePrivate(modifiers: Int): Boolean {
val hasAnyAccessModifier = isPrivate(modifiers)
|| isProtected(modifiers)
|| isProtected(modifiers)
return !hasAnyAccessModifier
}

private fun FuzzedMethodDescription.fuzzParameters(constructorId: ConstructorId, vararg modelProviders: ModelProvider): Sequence<List<FuzzedValue>> {
Expand All @@ -112,14 +174,44 @@ class ObjectModelProvider : ModelProvider {
id,
constructorId.classId,
"${constructorId.classId.name}${constructorId.parameters}#" + id.toString(16),
instantiationChain
instantiationChain = instantiationChain,
modificationsChain = mutableListOf()
).apply {
instantiationChain += UtExecutableCallModel(null, constructorId, params.map { it.model }, this)
}.fuzzed {
summary = "%var% = ${constructorId.classId.simpleName}(${constructorId.parameters.joinToString { it.simpleName }})"
}
}

private fun findSuitableFields(classId: ClassId, description: FuzzedMethodDescription): List<FieldDescription> {
val jClass = classId.jClass
return jClass.declaredFields.map { field ->
FieldDescription(
field.name,
field.type.id,
isAccessible(field, description.packageName) && !isFinal(field.modifiers) && !isStatic(field.modifiers),
jClass.findPublicSetterIfHasPublicGetter(field, description)
)
}
}

private fun Class<*>.findPublicSetterIfHasPublicGetter(field: Field, description: FuzzedMethodDescription): Method? {
val postfixName = field.name.capitalize()
val setterName = "set$postfixName"
val getterName = "get$postfixName"
val getter = try { getDeclaredMethod(getterName) } catch (_: NoSuchMethodException) { return null }
return if (isAccessible(getter, description.packageName) && getter.returnType == field.type) {
declaredMethods.find {
isAccessible(it, description.packageName) &&
it.name == setterName &&
it.parameterCount == 1 &&
it.parameterTypes[0] == field.type
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly we should enlarge StatementsStorage.isSetterOrDirectAccessor method with this type check.

}
} else {
null
}
}

private val primitiveParameterizedConstructorsFirstAndThenByParameterCount =
compareByDescending<ConstructorId> { constructorId ->
constructorId.parameters.all { classId ->
Expand All @@ -128,5 +220,12 @@ class ObjectModelProvider : ModelProvider {
}.thenComparingInt { constructorId ->
constructorId.parameters.size
}

private class FieldDescription(
val name: String,
val classId: ClassId,
val canBeSetDirectly: Boolean,
val setter: Method?,
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.utbot.framework.plugin.api.samples;

@SuppressWarnings("All")
public class FieldSetterClass {

public static int pubStaticField;
public final int pubFinalField = 0;
public int pubField;
public int pubFieldWithSetter;
private int prvField;
private int prvFieldWithSetter;

public int getPubFieldWithSetter() {
return pubFieldWithSetter;
}

public void setPubFieldWithSetter(int pubFieldWithSetter) {
this.pubFieldWithSetter = pubFieldWithSetter;
}

public int getPrvFieldWithSetter() {
return prvFieldWithSetter;
}

public void setPrvFieldWithSetter(int prvFieldWithSetter) {
this.prvFieldWithSetter = prvFieldWithSetter;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.utbot.framework.plugin.api.samples;

@SuppressWarnings("All")
public class PackagePrivateFieldAndClass {

volatile int pkgField = 0;

PackagePrivateFieldAndClass() {

}

PackagePrivateFieldAndClass(int value) {
pkgField = value;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import org.utbot.fuzzer.providers.PrimitivesModelProvider
import org.utbot.fuzzer.providers.StringConstantModelProvider
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import org.utbot.framework.plugin.api.samples.FieldSetterClass
import org.utbot.framework.plugin.api.samples.PackagePrivateFieldAndClass
import org.utbot.framework.plugin.api.util.primitiveByWrapper
import org.utbot.framework.plugin.api.util.primitiveWrappers
import org.utbot.framework.plugin.api.util.voidWrapperClassId
Expand Down Expand Up @@ -431,15 +433,87 @@ class ModelProviderTest {
}
}

@Test
fun `test complex object is created with setters`() {
val j = FieldSetterClass::class.java
assertEquals(6, j.declaredFields.size)
assertTrue(
setOf(
"pubStaticField",
"pubFinalField",
"pubField",
"pubFieldWithSetter",
"prvField",
"prvFieldWithSetter",
).containsAll(j.declaredFields.map { it.name })
)
assertEquals(4, j.declaredMethods.size)
assertTrue(
setOf(
"getPubFieldWithSetter",
"setPubFieldWithSetter",
"getPrvFieldWithSetter",
"setPrvFieldWithSetter",
).containsAll(j.declaredMethods.map { it.name })
)

withUtContext(UtContext(this::class.java.classLoader)) {
val result = collect(ObjectModelProvider { 0 }.apply {
modelProvider = PrimitiveDefaultsModelProvider
}, parameters = listOf(FieldSetterClass::class.java.id))
assertEquals(1, result.size)
assertEquals(2, result[0]!!.size)
assertEquals(0, (result[0]!![0] as UtAssembleModel).modificationsChain.size) { "One of models must be without any modifications" }
val expectedModificationSize = 3
val modificationsChain = (result[0]!![1] as UtAssembleModel).modificationsChain
val actualModificationSize = modificationsChain.size
assertEquals(expectedModificationSize, actualModificationSize) { "In target class there's only $expectedModificationSize fields that can be changed, but generated $actualModificationSize modifications" }

assertEquals("pubField", (modificationsChain[0] as UtDirectSetFieldModel).fieldId.name)
assertEquals("pubFieldWithSetter", (modificationsChain[1] as UtDirectSetFieldModel).fieldId.name)
assertEquals("setPrvFieldWithSetter", (modificationsChain[2] as UtExecutableCallModel).executable.name)
}
}

@Test
fun `test complex object is created with setters and package private field and constructor`() {
val j = PackagePrivateFieldAndClass::class.java
assertEquals(1, j.declaredFields.size)
assertTrue(
setOf(
"pkgField",
).containsAll(j.declaredFields.map { it.name })
)

withUtContext(UtContext(this::class.java.classLoader)) {
val result = collect(ObjectModelProvider { 0 }.apply {
modelProvider = PrimitiveDefaultsModelProvider
}, parameters = listOf(PackagePrivateFieldAndClass::class.java.id)) {
packageName = PackagePrivateFieldAndClass::class.java.`package`.name
}
assertEquals(1, result.size)
assertEquals(3, result[0]!!.size)
assertEquals(0, (result[0]!![0] as UtAssembleModel).modificationsChain.size) { "One of models must be without any modifications" }
assertEquals(0, (result[0]!![2] as UtAssembleModel).modificationsChain.size) { "Modification by constructor doesn't change fields" }
val expectedModificationSize = 1
val modificationsChain = (result[0]!![1] as UtAssembleModel).modificationsChain
val actualModificationSize = modificationsChain.size
assertEquals(expectedModificationSize, actualModificationSize) { "In target class there's only $expectedModificationSize fields that can be changed, but generated $actualModificationSize modifications" }

assertEquals("pkgField", (modificationsChain[0] as UtDirectSetFieldModel).fieldId.name)
}
}

private fun collect(
modelProvider: ModelProvider,
name: String = "testMethod",
returnType: ClassId = voidClassId,
parameters: List<ClassId>,
constants: List<FuzzedConcreteValue> = emptyList()
constants: List<FuzzedConcreteValue> = emptyList(),
block: FuzzedMethodDescription.() -> Unit = {}
): Map<Int, List<UtModel>> {
return mutableMapOf<Int, MutableList<UtModel>>().apply {
modelProvider.generate(FuzzedMethodDescription(name, returnType, parameters, constants)) { i, m ->
modelProvider.generate(FuzzedMethodDescription(name, returnType, parameters, constants).apply(block)) { i, m ->
computeIfAbsent(i) { mutableListOf() }.add(m.model)
}
}
Expand All @@ -448,4 +522,4 @@ class ModelProviderTest {
private enum class OneTwoThree {
ONE, TWO, THREE
}
}
}