diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt index 4313ddd5fd..73ec1bbc87 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -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().firstOrNull()?.names parameterNameMap = { index -> names?.getOrNull(index) } } diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt index 86e706e804..f4b290932c 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt @@ -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 */ diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt index 52d9f0c054..a383f642e3 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt @@ -2,7 +2,10 @@ 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 @@ -10,6 +13,8 @@ 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 @@ -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 @@ -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() + 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) @@ -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() + .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 -> @@ -85,6 +103,42 @@ class ObjectModelProvider : ModelProvider { } } + private fun generateModelsWithFieldsInitialization(constructorId: ConstructorId, description: FuzzedMethodDescription, concreteValues: Collection): Sequence { + if (limitValuesCreatedByFieldAccessors == 0) return emptySequence() + val fields = findSuitableFields(constructorId.classId, description) + val syntheticClassFieldsSetterMethodDescription = FuzzedMethodDescription( + "${constructorId.classId.simpleName}", + 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 { return classId.jClass.declaredConstructors.asSequence() @@ -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> { @@ -112,7 +174,8 @@ 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 { @@ -120,6 +183,35 @@ class ObjectModelProvider : ModelProvider { } } + private fun findSuitableFields(classId: ClassId, description: FuzzedMethodDescription): List { + 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 + } + } else { + null + } + } + private val primitiveParameterizedConstructorsFirstAndThenByParameterCount = compareByDescending { constructorId -> constructorId.parameters.all { classId -> @@ -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?, + ) } -} \ No newline at end of file +} diff --git a/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/FieldSetterClass.java b/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/FieldSetterClass.java new file mode 100644 index 0000000000..1e4d344c3f --- /dev/null +++ b/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/FieldSetterClass.java @@ -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; + } +} diff --git a/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/PackagePrivateFieldAndClass.java b/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/PackagePrivateFieldAndClass.java new file mode 100644 index 0000000000..f8975924b0 --- /dev/null +++ b/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/PackagePrivateFieldAndClass.java @@ -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; + } + +} diff --git a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt index 106a4a3d62..4be6de845c 100644 --- a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt +++ b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt @@ -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 @@ -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, - constants: List = emptyList() + constants: List = emptyList(), + block: FuzzedMethodDescription.() -> Unit = {} ): Map> { return mutableMapOf>().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) } } @@ -448,4 +522,4 @@ class ModelProviderTest { private enum class OneTwoThree { ONE, TWO, THREE } -} \ No newline at end of file +}