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 8641d5a9b4..86083c30b3 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -103,6 +103,8 @@ import kotlinx.coroutines.job import kotlinx.coroutines.yield import org.utbot.framework.plugin.api.UtExecutionSuccess import org.utbot.framework.plugin.api.UtLambdaModel +import org.utbot.framework.plugin.api.util.executable +import org.utbot.fuzzer.toFuzzerType val logger = KotlinLogging.logger {} val pathLogger = KotlinLogging.logger(logger.name + ".path") @@ -426,6 +428,7 @@ class UtBotSymbolicEngine( packageName = classUnderTest.packageName val names = graph.body.method.tags.filterIsInstance().firstOrNull()?.names parameterNameMap = { index -> names?.getOrNull(index) } + fuzzerType = { try { toFuzzerType(methodUnderTest.executable.genericParameterTypes[it]) } catch (_: Throwable) { null } } } val coveredInstructionTracker = Trie(Instruction::id) val coveredInstructionValues = linkedMapOf, List>() diff --git a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt index 32ddfd9701..656b0c1655 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt @@ -1,14 +1,17 @@ package org.utbot.fuzzer import mu.KotlinLogging +import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.classId import org.utbot.framework.plugin.api.util.booleanClassId import org.utbot.framework.plugin.api.util.byteClassId import org.utbot.framework.plugin.api.util.charClassId import org.utbot.framework.plugin.api.util.doubleClassId import org.utbot.framework.plugin.api.util.floatClassId +import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.intClassId import org.utbot.framework.plugin.api.util.longClassId +import org.utbot.framework.plugin.api.util.objectClassId import org.utbot.framework.plugin.api.util.shortClassId import org.utbot.framework.plugin.api.util.stringClassId import org.utbot.framework.util.executableId @@ -46,6 +49,11 @@ import soot.jimple.internal.JStaticInvokeExpr import soot.jimple.internal.JTableSwitchStmt import soot.jimple.internal.JVirtualInvokeExpr import soot.toolkits.graph.ExceptionalUnitGraph +import java.lang.reflect.GenericArrayType +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type +import java.lang.reflect.TypeVariable +import java.lang.reflect.WildcardType private val logger = KotlinLogging.logger {} @@ -292,3 +300,23 @@ private fun sootIfToFuzzedOp(unit: JIfStmt) = when (unit.condition) { } private fun nextDirectUnit(graph: ExceptionalUnitGraph, unit: Unit): Unit? = graph.getSuccsOf(unit).takeIf { it.size == 1 }?.first() + +fun toFuzzerType(type: Type): FuzzedType { + return when (type) { + is WildcardType -> type.upperBounds.firstOrNull()?.let(::toFuzzerType) ?: FuzzedType(objectClassId) + is TypeVariable<*> -> type.bounds.firstOrNull()?.let(::toFuzzerType) ?: FuzzedType(objectClassId) + is ParameterizedType -> FuzzedType((type.rawType as Class<*>).id, type.actualTypeArguments.map { toFuzzerType(it) }) + is GenericArrayType -> { + val genericComponentType = type.genericComponentType + val fuzzerType = toFuzzerType(genericComponentType) + val classId = if (genericComponentType !is GenericArrayType) { + ClassId("[L${fuzzerType.classId.name};", fuzzerType.classId) + } else { + ClassId("[" + fuzzerType.classId.name, fuzzerType.classId) + } + FuzzedType(classId) + } + is Class<*> -> FuzzedType(type.id) + else -> error("Unknown type: $type") + } +} \ No newline at end of file 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 e1f1317cf0..054a564e1a 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt @@ -40,6 +40,18 @@ class FuzzedMethodDescription( */ var parameterNameMap: (Int) -> String? = { null } + /** + * For every parameter returns a list with acceptable types. + * Usually it keeps upper bound. + * + * Every parameter can have several parameter types. + * For example [Map] has two type parameters, [Collection] has only one. + * + * Fuzzer doesn't care about interconnection between these types, therefore it waits + * that function already has all necessary information to bound this values. + */ + var fuzzerType: (Int) -> FuzzedType? = { null } + /** * Map class id to indices of this class in parameters list. */ diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedType.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedType.kt new file mode 100644 index 0000000000..a46080cba2 --- /dev/null +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedType.kt @@ -0,0 +1,20 @@ +package org.utbot.fuzzer + +import org.utbot.framework.plugin.api.ClassId + +/** + * Contains information about classId and generic types, that should be applied. + * + * Currently, there's some limitation for generics that are supported: + * 1. Only concrete types and collections are supported + * 2. No relative types like: `Map` + * + * Note, that this class can be replaced by API mechanism for collecting parametrized types, + * but at the moment it doesn't fully support all necessary operations. + * + * @see ClassId.typeParameters + */ +class FuzzedType( + val classId: ClassId, + val generics: List = emptyList() +) \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt index 9cfcab9cb0..7f0f699511 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt @@ -1,14 +1,19 @@ package org.utbot.fuzzer import mu.KotlinLogging +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.voidClassId import org.utbot.fuzzer.mutators.NumberRandomMutator import org.utbot.fuzzer.mutators.RegexStringModelMutator import org.utbot.fuzzer.mutators.StringRandomMutator import org.utbot.fuzzer.providers.ArrayModelProvider import org.utbot.fuzzer.providers.CharToStringModelProvider -import org.utbot.fuzzer.providers.CollectionModelProvider +import org.utbot.fuzzer.providers.CollectionWithEmptyStatesModelProvider import org.utbot.fuzzer.providers.ConstantsModelProvider import org.utbot.fuzzer.providers.EnumModelProvider +import org.utbot.fuzzer.providers.CollectionWithModificationModelProvider +import org.utbot.fuzzer.providers.NumberClassModelProvider import org.utbot.fuzzer.providers.ObjectModelProvider import org.utbot.fuzzer.providers.PrimitiveDefaultsModelProvider import org.utbot.fuzzer.providers.PrimitiveWrapperModelProvider @@ -19,6 +24,8 @@ import java.util.* import java.util.concurrent.atomic.AtomicInteger import kotlin.random.Random import org.utbot.fuzzer.providers.DateConstantModelProvider +import org.utbot.fuzzer.providers.PrimitiveRandomModelProvider +import org.utbot.fuzzer.providers.RecursiveModelProvider private val logger by lazy { KotlinLogging.logger {} } @@ -146,23 +153,34 @@ fun Sequence>.withMutations(statistics: FuzzerStatistics 4` there are 2 values in concrete value: 4 and 5. + * + * All values after filtering are cast to [Int]. + */ +fun fuzzNumbers(concreteValues: Collection, vararg defaultValues: Int, filter: (Number) -> Boolean = { true }): Sequence { + val description = FuzzedMethodDescription("helper: number fuzzing", voidClassId, listOf(intClassId), concreteValues) + val fuzzedValues = fuzz(description, ConstantsModelProvider) + .mapNotNull { ((it.single().model as? UtPrimitiveModel)?.value as? Number) } + .filter(filter) + .map { it.toInt() } + return (defaultValues.asSequence() + fuzzedValues).distinct() +} + /** * Creates a model provider from a list of default providers. */ fun defaultModelProviders(idGenerator: IdentityPreservingIdGenerator): ModelProvider { - return ModelProvider.of( - ObjectModelProvider(idGenerator), - CollectionModelProvider(idGenerator), - ArrayModelProvider(idGenerator), - EnumModelProvider(idGenerator), - DateConstantModelProvider(idGenerator), - ConstantsModelProvider, - StringConstantModelProvider, - RegexModelProvider, - CharToStringModelProvider, - PrimitivesModelProvider, - PrimitiveWrapperModelProvider, - ) + return modelProviderForRecursiveCalls(idGenerator, recursionDepth = -1) + .with(ObjectModelProvider(idGenerator)) + .with(ArrayModelProvider(idGenerator)) + .with(CollectionWithModificationModelProvider(idGenerator)) + .exceptIsInstance() + .except(PrimitiveDefaultsModelProvider) + .with(PrimitivesModelProvider) } /** @@ -170,22 +188,27 @@ fun defaultModelProviders(idGenerator: IdentityPreservingIdGenerator): Mode */ internal fun modelProviderForRecursiveCalls(idGenerator: IdentityPreservingIdGenerator, recursionDepth: Int): ModelProvider { val nonRecursiveProviders = ModelProvider.of( - CollectionModelProvider(idGenerator), + CollectionWithEmptyStatesModelProvider(idGenerator), EnumModelProvider(idGenerator), DateConstantModelProvider(idGenerator), + RegexModelProvider, StringConstantModelProvider, CharToStringModelProvider, ConstantsModelProvider, + PrimitiveRandomModelProvider(Random(0)), PrimitiveDefaultsModelProvider, PrimitiveWrapperModelProvider, + NumberClassModelProvider(idGenerator, Random(0)), ) - return if (recursionDepth >= 0) + return if (recursionDepth >= 0) { nonRecursiveProviders .with(ObjectModelProvider(idGenerator, recursionDepth)) .with(ArrayModelProvider(idGenerator, recursionDepth)) - else + .with(CollectionWithModificationModelProvider(idGenerator, recursionDepth)) + } else { nonRecursiveProviders + } } diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/objects/AssembleModelUtils.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/objects/AssembleModelUtils.kt index 19947761bf..0f05bacf8e 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/objects/AssembleModelUtils.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/objects/AssembleModelUtils.kt @@ -1,9 +1,14 @@ package org.utbot.fuzzer.objects +import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtStatementModel +import org.utbot.framework.plugin.api.util.voidClassId import org.utbot.fuzzer.FuzzedValue import org.utbot.fuzzer.ModelProvider import org.utbot.fuzzer.hex @@ -19,3 +24,86 @@ fun ModelProvider.assembleModel(id: Int, constructorId: ConstructorId, params: L summary = "%var% = ${constructorId.classId.simpleName}(${constructorId.parameters.joinToString { it.simpleName }})" } } + +fun ClassId.create( + block: AssembleModelDsl.() -> Unit +): UtAssembleModel { + return AssembleModelDsl(this).apply(block).build() +} + +class AssembleModelDsl internal constructor( + val classId: ClassId +) { + val using = KeyWord.Using + val call = KeyWord.Call + val constructor = KeyWord.Constructor(classId) + val method = KeyWord.Method(classId) + + var id: () -> Int? = { null } + var name: (Int?) -> String = { "" } + + private lateinit var initialization: () -> UtExecutableCallModel + private val modChain = mutableListOf<(UtAssembleModel) -> UtStatementModel>() + + fun params(vararg params: ClassId) = params.toList() + + fun values(vararg model: UtModel) = model.toList() + + infix fun KeyWord.Using.instance(executableId: T) = UsingDsl(executableId) + + infix fun KeyWord.Call.instance(executableId: T) = CallDsl(executableId, false) + + infix fun KeyWord.Using.static(executableId: T) = UsingDsl(executableId) + + infix fun KeyWord.Call.static(executableId: T) = CallDsl(executableId, true) + + infix fun KeyWord.Using.empty(ignored: KeyWord.Constructor) { + initialization = { UtExecutableCallModel(null, ConstructorId(classId, emptyList()), emptyList()) } + } + + infix fun ConstructorId.with(models: List) { + initialization = { UtExecutableCallModel(null, this, models) } + } + + infix fun UsingDsl.with(models: List) { + initialization = { UtExecutableCallModel(null, executableId, models) } + } + + infix fun CallDsl.with(models: List) { + modChain += { UtExecutableCallModel(it, executableId, models.toList()) } + } + + internal fun build(): UtAssembleModel { + val objectId = id() + return UtAssembleModel( + id = objectId, + classId = classId, + modelName = name(objectId), + instantiationCall = initialization() + ) { + modChain.map { it(this) } + } + } + + sealed class KeyWord { + object Using : KeyWord() + object Call : KeyWord() + class Constructor(val classId: ClassId) : KeyWord() { + operator fun invoke(vararg params: ClassId): ConstructorId { + return ConstructorId(classId, params.toList()) + } + } + class Method(val classId: ClassId) : KeyWord() { + operator fun invoke(name: String, params: List = emptyList(), returns: ClassId = voidClassId): MethodId { + return MethodId(classId, name, returns, params) + } + + operator fun invoke(classId: ClassId, name: String, params: List = emptyList(), returns: ClassId = voidClassId): MethodId { + return MethodId(classId, name, returns, params) + } + } + } + + class UsingDsl(val executableId: ExecutableId) + class CallDsl(val executableId: ExecutableId, val isStatic: Boolean) +} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ArrayModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ArrayModelProvider.kt index 394344e3a7..ecca10b591 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ArrayModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ArrayModelProvider.kt @@ -3,55 +3,36 @@ package org.utbot.fuzzer.providers import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.UtArrayModel import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.util.defaultValueModel -import org.utbot.framework.plugin.api.util.intClassId import org.utbot.framework.plugin.api.util.isArray import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.FuzzedType import org.utbot.fuzzer.IdentityPreservingIdGenerator +import org.utbot.fuzzer.fuzzNumbers class ArrayModelProvider( idGenerator: IdentityPreservingIdGenerator, recursionDepthLeft: Int = 1 ) : RecursiveModelProvider(idGenerator, recursionDepthLeft) { - override fun createNewInstance(parentProvider: RecursiveModelProvider, newTotalLimit: Int): RecursiveModelProvider = + + override fun newInstance(parentProvider: RecursiveModelProvider): RecursiveModelProvider = ArrayModelProvider(parentProvider.idGenerator, parentProvider.recursionDepthLeft - 1) - .copySettingsFrom(parentProvider) - .apply { - totalLimit = newTotalLimit - if (parentProvider is ArrayModelProvider) - branchingLimit = 1 // This improves performance but also forbids generating "jagged" arrays - } + .copySettings(parentProvider) override fun generateModelConstructors( description: FuzzedMethodDescription, - classId: ClassId - ): List { - if (!classId.isArray) - return listOf() - val lengths = generateArrayLengths(description) - return lengths.map { length -> - ModelConstructor(List(length) { classId.elementClassId!! }) { values -> + parameterIndex: Int, + classId: ClassId, + ): Sequence = sequence { + if (!classId.isArray) return@sequence + val lengths = fuzzNumbers(description.concreteValues, 0, 3) { it in 1..10 } + lengths.forEach { length -> + yield(ModelConstructor(listOf(FuzzedType(classId.elementClassId!!)), repeat = length) { values -> createFuzzedArrayModel(classId, length, values.map { it.model } ) - } + }) } } - private fun generateArrayLengths(description: FuzzedMethodDescription): List { - val fuzzedLengths = fuzzValuesRecursively( - types = listOf(intClassId), - baseMethodDescription = description, - modelProvider = ConstantsModelProvider - ) - - // Firstly we will use most "interesting" default sizes, then we will use small sizes obtained from src - return listOf(3, 0) + fuzzedLengths - .map { (it.single().model as UtPrimitiveModel).value as Int } - .filter { it in 1..10 && it != 3 } - .toSortedSet() - .toList() - } - private fun createFuzzedArrayModel(arrayClassId: ClassId, length: Int, values: List?) = UtArrayModel( idGenerator.createId(), diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionModelProvider.kt deleted file mode 100644 index 41d81ea448..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionModelProvider.kt +++ /dev/null @@ -1,104 +0,0 @@ -package org.utbot.fuzzer.providers - -import org.utbot.framework.plugin.api.ConstructorId -import org.utbot.framework.plugin.api.ExecutableId -import org.utbot.framework.plugin.api.MethodId -import org.utbot.framework.plugin.api.UtAssembleModel -import org.utbot.framework.plugin.api.UtExecutableCallModel -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtStatementModel -import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.jClass -import org.utbot.fuzzer.FuzzedMethodDescription -import org.utbot.fuzzer.FuzzedParameter -import org.utbot.fuzzer.IdGenerator -import org.utbot.fuzzer.ModelProvider -import org.utbot.fuzzer.ModelProvider.Companion.yieldAllValues -import java.util.function.IntSupplier - -/** - * Provides different collection for concrete classes. - * - * For example, ArrayList, LinkedList, Collections.singletonList can be passed to check - * if that parameter breaks anything. For example, in case method doesn't expect - * a non-modifiable collection and tries to add values. - */ -class CollectionModelProvider( - private val idGenerator: IdGenerator -) : ModelProvider { - - private val generators = mapOf( - java.util.List::class.java to ::createListModels, - java.util.Set::class.java to ::createSetModels, - java.util.Map::class.java to ::createMapModels, - java.util.Collection::class.java to ::createCollectionModels, - java.lang.Iterable::class.java to ::createCollectionModels, - java.util.Iterator::class.java to ::createIteratorModels, - ) - - override fun generate(description: FuzzedMethodDescription): Sequence = sequence { - description.parametersMap - .asSequence() - .forEach { (classId, indices) -> - generators[classId.jClass]?.let { createModels -> - yieldAllValues(indices, createModels().map { it.fuzzed() }) - } - } - } - - private fun createListModels(): List { - return listOf( - java.util.List::class.java.createdBy(java.util.ArrayList::class.java.asConstructor()), - java.util.List::class.java.createdBy(java.util.LinkedList::class.java.asConstructor()), - java.util.List::class.java.createdBy(java.util.Collections::class.java.methodCall("emptyList", java.util.List::class.java)), - java.util.List::class.java.createdBy(java.util.Collections::class.java.methodCall("synchronizedList", java.util.List::class.java, params = listOf(java.util.List::class.java)), listOf( - java.util.List::class.java.createdBy(java.util.ArrayList::class.java.asConstructor()) - )), - ) - } - - private fun createSetModels(): List { - return listOf( - java.util.Set::class.java.createdBy(java.util.HashSet::class.java.asConstructor()), - java.util.Set::class.java.createdBy(java.util.TreeSet::class.java.asConstructor()), - java.util.Set::class.java.createdBy(java.util.Collections::class.java.methodCall("emptySet", java.util.Set::class.java)) - ) - } - - private fun createMapModels(): List { - return listOf( - java.util.Map::class.java.createdBy(java.util.HashMap::class.java.asConstructor()), - java.util.Map::class.java.createdBy(java.util.TreeMap::class.java.asConstructor()), - java.util.Map::class.java.createdBy(java.util.Collections::class.java.methodCall("emptyMap", java.util.Map::class.java)), - ) - } - - private fun createCollectionModels(): List { - return listOf( - java.util.Collection::class.java.createdBy(java.util.ArrayList::class.java.asConstructor()), - java.util.Collection::class.java.createdBy(java.util.HashSet::class.java.asConstructor()), - java.util.Collection::class.java.createdBy(java.util.Collections::class.java.methodCall("emptySet", java.util.Set::class.java)), - ) - } - - private fun createIteratorModels(): List { - return listOf( - java.util.Iterator::class.java.createdBy(java.util.Collections::class.java.methodCall("emptyIterator", java.util.Iterator::class.java)), - ) - } - - private fun Class<*>.asConstructor() = ConstructorId(id, emptyList()) - - private fun Class<*>.methodCall(methodName: String, returnType: Class<*>, params: List> = emptyList()) = MethodId(id, methodName, returnType.id, params.map { it.id }) - - private fun Class<*>.createdBy(init: ExecutableId, params: List = emptyList()): UtAssembleModel { - val instantiationCall = UtExecutableCallModel(instance = null, init, params) - val genId = idGenerator.createId() - return UtAssembleModel( - genId, - id, - "${init.classId.name}${init.parameters}#" + genId.toString(16), - instantiationCall - ) - } -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionWithEmptyStatesModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionWithEmptyStatesModelProvider.kt new file mode 100644 index 0000000000..c3f18831f7 --- /dev/null +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionWithEmptyStatesModelProvider.kt @@ -0,0 +1,47 @@ +package org.utbot.fuzzer.providers + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.isSubtypeOf +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.FuzzedParameter +import org.utbot.fuzzer.IdGenerator +import org.utbot.fuzzer.ModelProvider +import org.utbot.fuzzer.ModelProvider.Companion.yieldAllValues +import org.utbot.fuzzer.objects.create + +/** + * Provides different collection for concrete classes. + * + * For example, ArrayList, LinkedList, Collections.singletonList can be passed to check + * if that parameter breaks anything. For example, in case method doesn't expect + * a non-modifiable collection and tries to add values. + */ +class CollectionWithEmptyStatesModelProvider( + private val idGenerator: IdGenerator +) : ModelProvider { + + private val generators = listOf( + Info(List::class.id, "emptyList"), + Info(Set::class.id, "emptySet"), + Info(Map::class.id, "emptyMap"), + Info(Collection::class.id, "emptyList", returnType = List::class.id), + Info(Iterable::class.id, "emptyList", returnType = List::class.id), + Info(Iterator::class.id, "emptyIterator"), + ) + + override fun generate(description: FuzzedMethodDescription): Sequence = sequence { + description.parametersMap + .asSequence() + .forEach { (classId, indices) -> + generators.find { classId == it.classId }?.let { generator -> + yieldAllValues(indices, listOf(generator.classId.create { + id = { idGenerator.createId() } + using static method(java.util.Collections::class.id, generator.methodName, returns = generator.returnType) with values() + }.fuzzed { summary = "%var% = empty collection" })) + } + } + } + + private class Info(val classId: ClassId, val methodName: String, val returnType: ClassId = classId) +} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionWithModificationModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionWithModificationModelProvider.kt new file mode 100644 index 0000000000..94ac32465d --- /dev/null +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionWithModificationModelProvider.kt @@ -0,0 +1,141 @@ +package org.utbot.fuzzer.providers + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.framework.plugin.api.util.voidClassId +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.IdentityPreservingIdGenerator +import org.utbot.fuzzer.fuzzNumbers +import org.utbot.fuzzer.objects.create + +class CollectionWithModificationModelProvider( + idGenerator: IdentityPreservingIdGenerator, + recursionDepthLeft: Int = 1, + private var defaultModificationCount: IntArray = intArrayOf(0, 1, 3) +) : RecursiveModelProvider(idGenerator, recursionDepthLeft) { + + init { + totalLimit = 100_000 + } + + // List of available implementations with modification method to insert values + // Should be listed from more specific interface to more general, + // because suitable info is searched by the list. + private val modifications = listOf( + // SETS + Info(java.util.NavigableSet::class.id, java.util.TreeSet::class.id, "add", listOf(objectClassId), booleanClassId) { + it.size == 1 && it[0].classId.isSubtypeOfWithReflection(java.lang.Comparable::class.id) + }, + Info(java.util.SortedSet::class.id, java.util.TreeSet::class.id, "add", listOf(objectClassId), booleanClassId) { + it.size == 1 && it[0].classId.isSubtypeOfWithReflection(java.lang.Comparable::class.id) + }, + Info(java.util.Set::class.id, java.util.HashSet::class.id, "add", listOf(objectClassId), booleanClassId), + // QUEUES + Info(java.util.Queue::class.id, java.util.ArrayDeque::class.id, "add", listOf(objectClassId), booleanClassId), + Info(java.util.Deque::class.id, java.util.ArrayDeque::class.id, "add", listOf(objectClassId), booleanClassId), + Info(java.util.Stack::class.id, java.util.Stack::class.id, "push", listOf(objectClassId), booleanClassId), + // LISTS + Info(java.util.List::class.id, java.util.ArrayList::class.id, "add", listOf(objectClassId), booleanClassId), + // MAPS + Info(java.util.NavigableMap::class.id, java.util.TreeMap::class.id, "put", listOf(objectClassId, objectClassId), objectClassId) { + it.size == 2 && it[0].classId.isSubtypeOfWithReflection(java.lang.Comparable::class.id) + }, + Info(java.util.SortedMap::class.id, java.util.TreeMap::class.id, "put", listOf(objectClassId, objectClassId), objectClassId) { + it.size == 2 && it[0].classId.isSubtypeOfWithReflection(java.lang.Comparable::class.id) + }, + Info(java.util.Map::class.id, java.util.HashMap::class.id, "put", listOf(objectClassId, objectClassId), objectClassId), + // ITERABLE + Info(java.util.Collection::class.id, java.util.ArrayList::class.id, "add", listOf(objectClassId), booleanClassId), + Info(java.lang.Iterable::class.id, java.util.ArrayList::class.id, "add", listOf(objectClassId), booleanClassId), + ) + private var modificationCount = 7 + + override fun newInstance(parentProvider: RecursiveModelProvider): RecursiveModelProvider { + val newInstance = CollectionWithModificationModelProvider( + parentProvider.idGenerator, parentProvider.recursionDepthLeft - 1 + ) + newInstance.copySettings(parentProvider) + if (parentProvider is CollectionWithModificationModelProvider) { + newInstance.defaultModificationCount = parentProvider.defaultModificationCount + } + return newInstance + } + + override fun generateModelConstructors( + description: FuzzedMethodDescription, + parameterIndex: Int, + classId: ClassId, + ): Sequence { + + val info: Info? = if (!classId.isAbstract) { + when { + classId.isSubtypeOfWithReflection(Collection::class.id) -> Info(classId, classId, "add", listOf(objectClassId), booleanClassId) + classId.isSubtypeOfWithReflection(Map::class.id) -> Info(classId, classId, "put", listOf(objectClassId, objectClassId), objectClassId) + else -> null + } + } else { + modifications.find { + classId == it.superClass + } + } + + val sequence = info?.let { + val genericTypes = description.fuzzerType(parameterIndex)?.generics ?: emptyList() + if (genericTypes.isNotEmpty()) { + // this check removes cases when TreeSet or TreeMap is created without comparable key + val lengths = if (info.canModify(genericTypes)) { + fuzzNumbers(description.concreteValues, *defaultModificationCount) { it in 1..modificationCount } + } else { + sequenceOf(0) + } + lengths.map { length -> + ModelConstructor(genericTypes, repeat = length) { values -> + info.assembleModel(info.concreteClass, values) + } + } + } else { + emptySequence() + } + } + return sequence ?: emptySequence() + } + + private fun Info.assembleModel(concreteClassId: ClassId, values: List): FuzzedValue { + return concreteClassId.create { + id = { idGenerator.createId() } + using empty constructor + val paramCount = params.size + values.asSequence() + .windowed(paramCount, paramCount) + .forEach { each -> + call instance method( + methodName, + params, + returnType + ) with values(*Array(paramCount) { each[it].model }) + } + }.fuzzed { + summary = "%var% = test collection" + } + } + + private class Info( + val superClass: ClassId, + val concreteClass: ClassId, + val methodName: String, + val params: List, + val returnType: ClassId = voidClassId, + val canModify: (List) -> Boolean = { true } + ) + + private fun ClassId.isSubtypeOfWithReflection(another: ClassId): Boolean { + // commented code above doesn't work this case: SomeList extends LinkedList {} and Collection +// return isSubtypeOf(another) + return another.jClass.isAssignableFrom(this.jClass) + } +} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ConstantsModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ConstantsModelProvider.kt index b9c0af4706..95ee2af8d0 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ConstantsModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ConstantsModelProvider.kt @@ -32,7 +32,7 @@ object ConstantsModelProvider : ModelProvider { } } - private fun modifyValue(value: Any, op: FuzzedContext): FuzzedValue? { + fun modifyValue(value: Any, op: FuzzedContext): FuzzedValue? { if (op !is FuzzedContext.Comparison) return null val multiplier = if (op == FuzzedContext.Comparison.LT || op == FuzzedContext.Comparison.GE) -1 else 1 return when(value) { diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/DateConstantModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/DateConstantModelProvider.kt index f425937fca..f5f403b610 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/DateConstantModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/DateConstantModelProvider.kt @@ -16,6 +16,7 @@ import org.utbot.framework.plugin.api.util.stringClassId import org.utbot.framework.plugin.api.util.voidClassId import org.utbot.fuzzer.FuzzedMethodDescription import org.utbot.fuzzer.FuzzedParameter +import org.utbot.fuzzer.FuzzedType import org.utbot.fuzzer.FuzzedValue import org.utbot.fuzzer.IdentityPreservingIdGenerator import org.utbot.fuzzer.ModelProvider @@ -56,7 +57,7 @@ class DateConstantModelProvider( constructor.parameters.all { it == intClassId || it == longClassId } }.map { constructorId -> with(constructorId) { - ModelConstructor(parameters) { assembleModel(idGenerator.createId(), constructorId, it) } + ModelConstructor(parameters.map(::FuzzedType)) { assembleModel(idGenerator.createId(), constructorId, it) } } }.sortedBy { it.neededTypes.size } @@ -64,7 +65,7 @@ class DateConstantModelProvider( constructorsFromNumbers.forEach { constructor -> yieldAll( fuzzValues( - constructor.neededTypes, + constructor.neededTypes.map(FuzzedType::classId), baseMethodDescription, defaultModelProviders(idGenerator) ).map(constructor.createModel) diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/NumberClassModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/NumberClassModelProvider.kt new file mode 100644 index 0000000000..d0a9938abb --- /dev/null +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/NumberClassModelProvider.kt @@ -0,0 +1,74 @@ +package org.utbot.fuzzer.providers + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.util.byteClassId +import org.utbot.framework.plugin.api.util.doubleClassId +import org.utbot.framework.plugin.api.util.floatClassId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.isPrimitive +import org.utbot.framework.plugin.api.util.longClassId +import org.utbot.framework.plugin.api.util.primitiveByWrapper +import org.utbot.framework.plugin.api.util.shortClassId +import org.utbot.framework.plugin.api.util.wrapperByPrimitive +import org.utbot.fuzzer.FuzzedConcreteValue +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.FuzzedParameter +import org.utbot.fuzzer.IdentityPreservingIdGenerator +import org.utbot.fuzzer.ModelProvider +import org.utbot.fuzzer.ModelProvider.Companion.yieldValue +import org.utbot.fuzzer.objects.create +import kotlin.random.Random + +/** + * Provides random implementation if current requested type is [Number]. + */ +class NumberClassModelProvider( + val idGenerator: IdentityPreservingIdGenerator, + val random: Random, +) : ModelProvider { + // byteClassId generates bad code because of type cast on method Byte.valueOf + private val types = setOf(/*byteClassId,*/ shortClassId, intClassId, longClassId, floatClassId, doubleClassId) + + override fun generate(description: FuzzedMethodDescription): Sequence = sequence { + description.parametersMap[Number::class.id]?.forEach { index -> + val fuzzedValues = description.concreteValues.filter { types.contains(it.classId) } + + (-5 until 5).map { FuzzedConcreteValue(types.random(random), it) } + fuzzedValues.forEach { fuzzedValue -> + val targetType = fuzzedValue.classId + check(targetType.isPrimitive) { "$targetType is not primitive value" } + val castedValue = castNumberIfPossible(fuzzedValue.value as Number, targetType) + val targetValues = listOfNotNull( + castedValue.let(::UtPrimitiveModel), + ConstantsModelProvider.modifyValue(castedValue, fuzzedValue.fuzzedContext)?.model + ) + // we use wrapper type to generate simple values, + // because at the moment code generator uses reflection + // if primitive types are provided + val wrapperType = wrapperByPrimitive[targetType] ?: return@sequence + targetValues.forEach { targetValue -> + yieldValue(index, wrapperType.create { + id = { idGenerator.createId() } + using static method( + classId = wrapperType, + name = "valueOf", + params = listOf(primitiveByWrapper[wrapperType]!!), + returns = wrapperType + ) with values(targetValue) + }.fuzzed { summary = "%var% = ${Number::class.simpleName}(${targetValue})" }) + } + } + } + } + + private fun castNumberIfPossible(number: Number, classId: ClassId): Number = when (classId) { + byteClassId -> number.toInt().toByte() + shortClassId -> number.toInt().toShort() + intClassId -> number.toInt() + longClassId -> number.toLong() + floatClassId -> number.toFloat() + doubleClassId -> number.toDouble() + else -> number + } +} \ No newline at end of file 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 656af81ed9..d80261818b 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 @@ -24,6 +24,7 @@ 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.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.FuzzedType import org.utbot.fuzzer.FuzzedValue import org.utbot.fuzzer.IdentityPreservingIdGenerator import org.utbot.fuzzer.objects.assembleModel @@ -35,21 +36,21 @@ class ObjectModelProvider( idGenerator: IdentityPreservingIdGenerator, recursionDepthLeft: Int = 1, ) : RecursiveModelProvider(idGenerator, recursionDepthLeft) { - override fun createNewInstance(parentProvider: RecursiveModelProvider, newTotalLimit: Int): RecursiveModelProvider = - ObjectModelProvider(parentProvider.idGenerator, parentProvider.recursionDepthLeft - 1) - .copySettingsFrom(parentProvider) - .apply { - totalLimit = newTotalLimit - branchingLimit = 1 // When called recursively, we will use only simplest constructor - } + override fun newInstance(parentProvider: RecursiveModelProvider): RecursiveModelProvider { + val newInstance = ObjectModelProvider(parentProvider.idGenerator, parentProvider.recursionDepthLeft - 1) + newInstance.copySettings(parentProvider) + newInstance.branchingLimit = 1 + return newInstance + } override fun generateModelConstructors( description: FuzzedMethodDescription, - classId: ClassId - ): List { + parameterIndex: Int, + classId: ClassId, + ): Sequence = sequence { if (unwantedConstructorsClasses.contains(classId) || classId.isPrimitiveWrapper || classId.isEnum || classId.isAbstract - ) return listOf() + ) return@sequence val constructors = collectConstructors(classId) { javaConstructor -> isAccessible(javaConstructor, description.packageName) @@ -57,23 +58,18 @@ class ObjectModelProvider( primitiveParameterizedConstructorsFirstAndThenByParameterCount ) - return buildList { - - constructors.forEach { constructorId -> - with(constructorId) { - add( - ModelConstructor(parameters) { assembleModel(idGenerator.createId(), constructorId, it) } - ) - if (parameters.isEmpty()) { - val fields = findSuitableFields(this.classId, description) - if (fields.isNotEmpty()) { - add( - ModelConstructor(fields.map { it.classId }) { - generateModelsWithFieldsInitialization(this, fields, it) - } - ) + constructors.forEach { constructorId -> + yield(ModelConstructor(constructorId.parameters.map { classId -> FuzzedType(classId) }) { + assembleModel(idGenerator.createId(), constructorId, it) + }) + if (constructorId.parameters.isEmpty()) { + val fields = findSuitableFields(constructorId.classId, description) + if (fields.isNotEmpty()) { + yield( + ModelConstructor(fields.map { FuzzedType(it.classId) }) { + generateModelsWithFieldsInitialization(constructorId, fields, it) } - } + ) } } } diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitiveRandomModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitiveRandomModelProvider.kt new file mode 100644 index 0000000000..f866742cab --- /dev/null +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitiveRandomModelProvider.kt @@ -0,0 +1,66 @@ +package org.utbot.fuzzer.providers + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.byteClassId +import org.utbot.framework.plugin.api.util.charClassId +import org.utbot.framework.plugin.api.util.doubleClassId +import org.utbot.framework.plugin.api.util.floatClassId +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.longClassId +import org.utbot.framework.plugin.api.util.primitiveByWrapper +import org.utbot.framework.plugin.api.util.shortClassId +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.FuzzedParameter +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.ModelProvider +import org.utbot.fuzzer.ModelProvider.Companion.yieldValue +import kotlin.random.Random + +/** + * Provides default values for primitive types. + */ +class PrimitiveRandomModelProvider(val random: Random, val size: Int = 5) : ModelProvider { + override fun generate(description: FuzzedMethodDescription): Sequence = sequence { + description.parametersMap.forEach { (classId, parameterIndices) -> + for (i in 1..size) { + valueOf(primitiveByWrapper[classId] ?: classId)?.let { model -> + parameterIndices.forEach { index -> + yieldValue(index, model) + } + } + } + } + } + + fun valueOf(classId: ClassId): FuzzedValue? = when (classId) { + booleanClassId -> random.nextBoolean().let { v -> UtPrimitiveModel(v).fuzzed { summary = "%var% = $v" } } + byteClassId -> random.nextInt(Byte.MIN_VALUE.toInt(), Byte.MAX_VALUE.toInt()).let { v -> + UtPrimitiveModel(v.toByte()).fuzzed { summary = "%var% = random byte" } + } + charClassId -> random.nextInt(1, 256).let { v -> + UtPrimitiveModel(v.toChar()).fuzzed { summary = "%var% = random char" } + } + shortClassId -> random.nextInt(Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt()).let { v -> + UtPrimitiveModel(v.toShort()).fuzzed { summary = "%var% = random short" } + } + intClassId -> random.nextInt().let { v -> + UtPrimitiveModel(v).fuzzed { summary = "%var% = random integer" } + } + longClassId -> random.nextLong().let { v -> + UtPrimitiveModel(v).fuzzed { summary = "%var% = random long" } + } + floatClassId -> random.nextFloat().let { v -> + UtPrimitiveModel(v).fuzzed { summary = "%var% = random float" } + } + doubleClassId -> random.nextDouble().let { v -> + UtPrimitiveModel(0.0).fuzzed { summary = "%var% = random double" } + } + stringClassId -> (1..5).map { random.nextInt('a'.code, 'z'.code).toChar() }.joinToString("").let { s -> + UtPrimitiveModel(s).fuzzed { summary = "%var% = random string" } + } + else -> null + } +} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/RecursiveModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/RecursiveModelProvider.kt index e74bcf1670..fa4eb01e13 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/RecursiveModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/RecursiveModelProvider.kt @@ -4,6 +4,7 @@ import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.util.voidClassId import org.utbot.fuzzer.FuzzedMethodDescription import org.utbot.fuzzer.FuzzedParameter +import org.utbot.fuzzer.FuzzedType import org.utbot.fuzzer.FuzzedValue import org.utbot.fuzzer.IdentityPreservingIdGenerator import org.utbot.fuzzer.ModelProvider @@ -16,12 +17,16 @@ import org.utbot.fuzzer.modelProviderForRecursiveCalls * Auxiliary data class that stores information describing how to construct model from parts (submodels) * * @param neededTypes list containing type ([ClassId]) of each submodel + * @param repeat if value greater than 1, [neededTypes] is duplicated, therefore [createModel] should accept `neededTypes.size * repeat` values * @param createModel lambda that takes subModels (they should have types listed in [neededTypes]) and generates a model using them */ data class ModelConstructor( - val neededTypes: List, - val createModel: (subModels: List) -> FuzzedValue -) + val neededTypes: List, + val repeat: Int = 1, + val createModel: (subModels: List) -> FuzzedValue, +) { + var limit: Int = Int.MAX_VALUE +} /** * Abstraction for providers that may call other providers recursively inside them. [generate] will firstly get possible @@ -31,13 +36,10 @@ data class ModelConstructor( * * @property modelProviderForRecursiveCalls providers that can be called by this provider. * Note that if [modelProviderForRecursiveCalls] has instances of [RecursiveModelProvider] then this provider will use - * their copies created by [createNewInstance] rather than themselves (this is important because we have to change some + * their copies created by [newInstance] rather than themselves (this is important because we have to change some * properties like [recursionDepthLeft], [totalLimit], etc.) - * * @property fallbackProvider provider that will be used instead [modelProviderForRecursiveCalls] after reaching maximum recursion level - * - * @property totalLimit maximum number of parameters produced by this provider - * + * @property totalLimit maximum number of values produced by this provider * @property branchingLimit maximum number of [ModelConstructor]s used by [generate] (see [generateModelConstructors]) */ abstract class RecursiveModelProvider( @@ -45,73 +47,75 @@ abstract class RecursiveModelProvider( val recursionDepthLeft: Int ) : ModelProvider { var modelProviderForRecursiveCalls: ModelProvider = modelProviderForRecursiveCalls(idGenerator, recursionDepthLeft - 1) - var fallbackProvider: ModelProvider = NullModelProvider - var totalLimit: Int = 1000 - var branchingLimit: Int = Int.MAX_VALUE - private fun getModelProvider(numOfBranches: Int): ModelProvider = - if (recursionDepthLeft > 0) - modelProviderForRecursiveCalls.map { - if (it is RecursiveModelProvider) - it.createNewInstance(this, totalLimit / numOfBranches) - else - it - } - else - modelProviderForRecursiveCalls - .exceptIsInstance() - .withFallback(fallbackProvider) - /** * Creates instance of the class on which it is called, assuming that it will be called recursively from [parentProvider] */ - protected abstract fun createNewInstance(parentProvider: RecursiveModelProvider, newTotalLimit: Int): RecursiveModelProvider + protected abstract fun newInstance(parentProvider: RecursiveModelProvider): RecursiveModelProvider /** * Creates [ModelProvider]s that will be used to generate values recursively. The order of elements in returned list is important: * only first [branchingLimit] constructors will be used, so you should place most effective providers first */ - protected abstract fun generateModelConstructors(description: FuzzedMethodDescription, classId: ClassId): List + protected abstract fun generateModelConstructors( + description: FuzzedMethodDescription, + parameterIndex: Int, + classId: ClassId, + ): Sequence - protected fun copySettingsFrom(otherProvider: RecursiveModelProvider): RecursiveModelProvider { - modelProviderForRecursiveCalls = otherProvider.modelProviderForRecursiveCalls - fallbackProvider = otherProvider.fallbackProvider - totalLimit = otherProvider.totalLimit - branchingLimit = otherProvider.branchingLimit + protected open fun copySettings(other: RecursiveModelProvider): RecursiveModelProvider { + modelProviderForRecursiveCalls = other.modelProviderForRecursiveCalls + fallbackProvider = other.fallbackProvider + totalLimit = other.totalLimit + branchingLimit = other.branchingLimit return this } final override fun generate(description: FuzzedMethodDescription): Sequence = sequence { - description.parametersMap.asSequence().forEach { (classId, indices) -> - val constructors = generateModelConstructors(description, classId).take(branchingLimit) - constructors.asSequence().forEach { constructor -> - val modelProvider = getModelProvider(constructors.size) - val valuesSets = - fuzzValuesRecursively(constructor.neededTypes, description, modelProvider) - .take(totalLimit / constructors.size) - yieldAllValues(indices, valuesSets.map(constructor.createModel)) - } + description.parameters.forEachIndexed { index, classId -> + generateModelConstructors(description, index, classId) + .take(branchingLimit) + .forEach { creator -> + yieldAllValues(listOf(index), creator.recursiveCall(description)) + } } }.take(totalLimit) - protected fun fuzzValuesRecursively( - types: List, - baseMethodDescription: FuzzedMethodDescription, - modelProvider: ModelProvider, - ): Sequence> { - if (types.isEmpty()) - return sequenceOf(listOf()) + private fun ModelConstructor.recursiveCall(baseMethodDescription: FuzzedMethodDescription): Sequence { + // when no parameters are needed just call model creator once, + // for example, if collection is empty or object has empty constructor + if (neededTypes.isEmpty() || repeat == 0) { + return sequenceOf(createModel(listOf())) + } val syntheticMethodDescription = FuzzedMethodDescription( - "", //TODO: maybe add more info here + "", voidClassId, - types, + (1..repeat).flatMap { neededTypes.map { it.classId } }, baseMethodDescription.concreteValues ).apply { packageName = baseMethodDescription.packageName + fuzzerType = { index -> + neededTypes[index % neededTypes.size] // because we can repeat neededTypes several times + } } - return fuzz(syntheticMethodDescription, modelProvider) + return fuzz(syntheticMethodDescription, nextModelProvider()) + .map { createModel(it) } + .take(limit) } + + private fun nextModelProvider(): ModelProvider = + if (recursionDepthLeft > 0) { + modelProviderForRecursiveCalls.map { + if (it is RecursiveModelProvider) { + it.newInstance(this) + } else { it } + } + } else { + modelProviderForRecursiveCalls + .exceptIsInstance() + .withFallback(fallbackProvider) + } } \ No newline at end of file diff --git a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/CollectionModelProviderTest.kt b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/CollectionModelProviderTest.kt new file mode 100644 index 0000000000..9f29d50ef0 --- /dev/null +++ b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/CollectionModelProviderTest.kt @@ -0,0 +1,271 @@ +package org.utbot.framework.plugin.api + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.intWrapperClassId +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.framework.plugin.api.util.primitiveByWrapper +import org.utbot.framework.plugin.api.util.primitiveWrappers +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.framework.plugin.api.util.withUtContext +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.exceptIsInstance +import org.utbot.fuzzer.providers.CollectionWithEmptyStatesModelProvider +import org.utbot.fuzzer.providers.CollectionWithModificationModelProvider +import java.util.* + +class CollectionModelProviderTest { + + interface MyInterface + + @Test + fun `empty collection is created for unknown interface without modifications`() { + withUtContext(UtContext(this::class.java.classLoader)) { + val result = collect( + CollectionWithModificationModelProvider(TestIdentityPreservingIdGenerator), + parameters = listOf(Collection::class.id), + ) { + fuzzerType = { FuzzedType(Collection::class.id, listOf(FuzzedType(MyInterface::class.id))) } + } + assertEquals(1, result.size) + val models = result[0]!! + assertEquals(1, models.size) { "test should generate only 1 empty model" } + val model = models[0] + assertTrue(model is UtAssembleModel) { "unexpected model type" } + assertEquals(0, (model as UtAssembleModel).modificationsChain.size) { "Model should not have any modifications" } + } + } + + @Test + fun `collection is created with modification of concrete class`() { + val modifications = intArrayOf(0, 1, 3, 5) + withUtContext(UtContext(this::class.java.classLoader)) { + val result = collect( + CollectionWithModificationModelProvider( + TestIdentityPreservingIdGenerator, + defaultModificationCount = modifications + ), + parameters = listOf(Collection::class.id), + ) { + fuzzerType = { FuzzedType(Collection::class.id, listOf(FuzzedType(objectClassId))) } + } + assertEquals(1, result.size) + val models = result[0]!! + assertEquals(modifications.size, models.size) { "test should generate only 3 model: empty, with 1 modification and 3 modification" } + modifications.forEachIndexed { index, expectedModifications -> + val model = models[index] + assertTrue(model is UtAssembleModel) { "unexpected model type" } + assertEquals(expectedModifications, (model as UtAssembleModel).modificationsChain.size) { "Model has unexpected number of modifications" } + } + } + } + + @Test + fun `collection can create simple values with concrete type`() { + val modifications = intArrayOf(1) + withUtContext(UtContext(this::class.java.classLoader)) { + val result = collect( + CollectionWithModificationModelProvider( + TestIdentityPreservingIdGenerator, + defaultModificationCount = modifications + ).apply { + totalLimit = 1 + }, + parameters = listOf(Collection::class.id), + ) { + fuzzerType = { FuzzedType(Collection::class.id, listOf(FuzzedType(intWrapperClassId))) } + } + assertEquals(1, result.size) + val models = result[0]!! + assertEquals(modifications.size, models.size) + modifications.forEachIndexed { index, expectedModifications -> + val model = models[index] + assertTrue(model is UtAssembleModel) + val modificationsChain = (model as UtAssembleModel).modificationsChain + assertEquals(expectedModifications, modificationsChain.size) + val statementModel = modificationsChain[0] + testStatementIsAsSimpleAddIntToCollection(ArrayList::class.id, statementModel) + } + } + } + + @Test + fun `collection can create recursively values with concrete type`() { + val modifications = intArrayOf(1) + withUtContext(UtContext(this::class.java.classLoader)) { + val result = collect( + CollectionWithModificationModelProvider( + TestIdentityPreservingIdGenerator, + defaultModificationCount = modifications + ).apply { + // removes empty collections from the result + modelProviderForRecursiveCalls = modelProviderForRecursiveCalls + .exceptIsInstance() + totalLimit = 1 + }, + parameters = listOf(Collection::class.id), + ) { + fuzzerType = { + FuzzedType(Collection::class.id, listOf( + FuzzedType(Set::class.id, listOf( + FuzzedType(intWrapperClassId) + )) + )) + } + } + assertEquals(1, result.size) + val models = result[0]!! + assertEquals(modifications.size, models.size) + modifications.forEachIndexed { index, expectedModifications -> + val model = models[index] + assertTrue(model is UtAssembleModel) + val modificationsChain = (model as UtAssembleModel).modificationsChain + assertEquals(expectedModifications, modificationsChain.size) + var statementModel = modificationsChain[0] + assertTrue(statementModel is UtExecutableCallModel) + statementModel as UtExecutableCallModel + assertEquals( + MethodId(ArrayList::class.id, "add", booleanClassId, listOf(objectClassId)), + statementModel.executable + ) + assertEquals(1, statementModel.params.size) + val innerType = statementModel.params[0] + assertTrue(innerType is UtAssembleModel) + innerType as UtAssembleModel + assertEquals(HashSet::class.id, innerType.classId) + assertEquals(1, innerType.modificationsChain.size) + statementModel = innerType.modificationsChain[0] + testStatementIsAsSimpleAddIntToCollection(HashSet::class.id, statementModel) + } + } + } + + private fun testStatementIsAsSimpleAddIntToCollection(collectionId: ClassId, statementModel: UtStatementModel) { + testStatementIsAsSimpleAddGenericSimpleTypeToCollection(collectionId, intWrapperClassId, statementModel) + } + + private fun testStatementIsAsSimpleAddGenericSimpleTypeToCollection(collectionId: ClassId, genericId: ClassId, statementModel: UtStatementModel) { + assertTrue(primitiveWrappers.contains(genericId)) { "This test works only with primitive wrapper types" } + assertTrue(statementModel is UtExecutableCallModel) + statementModel as UtExecutableCallModel + assertEquals( + MethodId(collectionId, "add", booleanClassId, listOf(objectClassId)), + statementModel.executable + ) + assertEquals(1, statementModel.params.size) + val classModel = statementModel.params[0] + assertTrue(classModel is UtPrimitiveModel) + classModel as UtPrimitiveModel + assertEquals(primitiveByWrapper[genericId], classModel.classId) + assertTrue(genericId.jClass.isAssignableFrom(classModel.value::class.java)) + } + + @Test + fun `map can create simple values with concrete type`() { + val modifications = intArrayOf(1) + withUtContext(UtContext(this::class.java.classLoader)) { + val result = collect( + CollectionWithModificationModelProvider( + TestIdentityPreservingIdGenerator, + defaultModificationCount = modifications + ).apply { + totalLimit = 1 + }, + parameters = listOf(Map::class.id), + ) { + fuzzerType = { FuzzedType(Map::class.id, listOf(FuzzedType(intWrapperClassId), FuzzedType(stringClassId))) } + } + assertEquals(1, result.size) + val models = result[0]!! + assertEquals(modifications.size, models.size) + modifications.forEachIndexed { index, expectedModifications -> + val model = models[index] + assertTrue(model is UtAssembleModel) + val modificationsChain = (model as UtAssembleModel).modificationsChain + assertEquals(expectedModifications, modificationsChain.size) + val statementModel = modificationsChain[0] + testStatementIsAsSimpleAddIntStringToMap(HashMap::class.id, statementModel) + } + } + } + + private fun testStatementIsAsSimpleAddIntStringToMap(collectionId: ClassId, statementModel: UtStatementModel) { + assertTrue(statementModel is UtExecutableCallModel) + statementModel as UtExecutableCallModel + assertEquals( + MethodId(collectionId, "put", objectClassId, listOf(objectClassId, objectClassId)), + statementModel.executable + ) + assertEquals(2, statementModel.params.size) + val intClassModel = statementModel.params[0] + assertTrue(intClassModel is UtPrimitiveModel) + intClassModel as UtPrimitiveModel + assertEquals(intClassId, intClassModel.classId) + assertTrue(intClassModel.value is Int) + val stringClassModel = statementModel.params[1] + assertTrue(stringClassModel is UtPrimitiveModel) + stringClassModel as UtPrimitiveModel + assertEquals(stringClassId, stringClassModel.classId) + assertTrue(stringClassModel.value is String) + } + + ///region REGRESSION TESTS + @Test + fun lists() { + testExpectedCollectionIsCreatedWithCorrectGenericType(Collection::class.id, ArrayList::class.id, intWrapperClassId) + testExpectedCollectionIsCreatedWithCorrectGenericType(List::class.id, ArrayList::class.id, intWrapperClassId) + testExpectedCollectionIsCreatedWithCorrectGenericType(Stack::class.id, Stack::class.id, intWrapperClassId) + testExpectedCollectionIsCreatedWithCorrectGenericType(java.util.Deque::class.id, java.util.ArrayDeque::class.id, intWrapperClassId) + testExpectedCollectionIsCreatedWithCorrectGenericType(Queue::class.id, java.util.ArrayDeque::class.id, intWrapperClassId) + } + + @Test + fun sets() { + testExpectedCollectionIsCreatedWithCorrectGenericType(Set::class.id, HashSet::class.id, intWrapperClassId) + testExpectedCollectionIsCreatedWithCorrectGenericType(SortedSet::class.id, TreeSet::class.id, intWrapperClassId) + testExpectedCollectionIsCreatedWithCorrectGenericType(NavigableSet::class.id, TreeSet::class.id, intWrapperClassId) + } + + class ConcreteClass : LinkedList() + + @Test + fun `concrete class is created`() { + testExpectedCollectionIsCreatedWithCorrectGenericType(ConcreteClass::class.id, ConcreteClass::class.id, intWrapperClassId) + } + + private fun testExpectedCollectionIsCreatedWithCorrectGenericType(collectionId: ClassId, expectedId: ClassId, genericId: ClassId) { + withUtContext(UtContext(this::class.java.classLoader)) { + val modifications = intArrayOf(1) + val result = collect( + CollectionWithModificationModelProvider( + TestIdentityPreservingIdGenerator, + defaultModificationCount = modifications + ).apply { + totalLimit = 1 + }, + parameters = listOf(collectionId), + ) { + fuzzerType = { FuzzedType(collectionId, listOf(FuzzedType(genericId))) } + } + assertEquals(1, result.size) + val models = result[0]!! + assertEquals(modifications.size, models.size) + modifications.forEachIndexed { index, expectedModifications -> + val model = models[index] + assertTrue(model is UtAssembleModel) + val modificationsChain = (model as UtAssembleModel).modificationsChain + assertEquals(expectedModifications, modificationsChain.size) + val statementModel = modificationsChain[0] + testStatementIsAsSimpleAddGenericSimpleTypeToCollection(expectedId, genericId, statementModel) + } + } + } + + ///end region +} \ No newline at end of file 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 909741c55d..bb96795bd0 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 @@ -32,6 +32,7 @@ import org.utbot.framework.plugin.api.util.primitiveWrappers import org.utbot.framework.plugin.api.util.voidWrapperClassId import org.utbot.fuzzer.FuzzedContext import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.IdentityPreservingIdGenerator import org.utbot.fuzzer.ReferencePreservingIntIdGenerator import org.utbot.fuzzer.ModelProvider.Companion.yieldValue import org.utbot.fuzzer.defaultModelProviders @@ -40,6 +41,7 @@ import org.utbot.fuzzer.providers.CharToStringModelProvider.fuzzed import org.utbot.fuzzer.providers.EnumModelProvider import org.utbot.fuzzer.providers.PrimitiveDefaultsModelProvider import java.util.Date +import java.util.concurrent.atomic.AtomicInteger import kotlin.random.Random class ModelProviderTest { @@ -431,19 +433,22 @@ class ModelProviderTest { parameters = listOf(Outer::class.java.id) ) assertEquals(1, result.size) - assertEquals(1, result[0]!!.size) - val outerModel = result[0]!![0] as UtAssembleModel - outerModel.instantiationCall.let { - val constructorParameters = it.params - assertEquals(1, constructorParameters.size) - val innerModel = (constructorParameters[0] as UtAssembleModel) - assertEquals(Inner::class.java.id, innerModel.classId) - val innerConstructorParameters = innerModel.instantiationCall - assertEquals(2, innerConstructorParameters.params.size) - assertTrue(innerConstructorParameters.params.all { param -> param is UtPrimitiveModel }) - assertEquals(intClassId, innerConstructorParameters.params[0].classId) - assertEquals(doubleClassId, innerConstructorParameters.params[1].classId) + var callCount = 0 + result[0]!!.filterIsInstance().forEach { outerModel -> + callCount++ + outerModel.instantiationCall.let { + val constructorParameters = it.params + assertEquals(1, constructorParameters.size) + val innerModel = (constructorParameters[0] as UtAssembleModel) + assertEquals(Inner::class.java.id, innerModel.classId) + val innerConstructorParameters = innerModel.instantiationCall + assertEquals(2, innerConstructorParameters.params.size) + assertTrue(innerConstructorParameters.params.all { param -> param is UtPrimitiveModel }) + assertEquals(intClassId, innerConstructorParameters.params[0].classId) + assertEquals(doubleClassId, innerConstructorParameters.params[1].classId) + } } + assertEquals(callCount, result[0]!!.size) } } @@ -556,22 +561,29 @@ class ModelProviderTest { } } - private fun collect( - modelProvider: ModelProvider, - name: String = "testMethod", - returnType: ClassId = voidClassId, - parameters: List, - constants: List = emptyList(), - block: FuzzedMethodDescription.() -> Unit = {} - ): Map> { - return mutableMapOf>().apply { - modelProvider.generate(FuzzedMethodDescription(name, returnType, parameters, constants).apply(block)).forEach { (i, m) -> - computeIfAbsent(i) { mutableListOf() }.add(m.model) - } - } - } - private enum class OneTwoThree { ONE, TWO, THREE } } + +internal fun collect( + modelProvider: ModelProvider, + name: String = "testMethod", + returnType: ClassId = voidClassId, + parameters: List, + constants: List = emptyList(), + block: FuzzedMethodDescription.() -> Unit = {} +): Map> { + return mutableMapOf>().apply { + modelProvider.generate(FuzzedMethodDescription(name, returnType, parameters, constants).apply(block)).forEach { (i, m) -> + computeIfAbsent(i) { mutableListOf() }.add(m.model) + } + } +} + +internal object TestIdentityPreservingIdGenerator : IdentityPreservingIdGenerator { + private val cache = mutableMapOf() + private val gen = AtomicInteger() + override fun getOrCreateIdForValue(value: Any): Int = cache.computeIfAbsent(value) { createId() } + override fun createId(): Int = gen.incrementAndGet() +} \ No newline at end of file