From 0dd9ca930e5ff127159a3e1ea4d8a75c24cc4b22 Mon Sep 17 00:00:00 2001 From: Ivan Volkov Date: Fri, 19 Aug 2022 13:34:25 +0300 Subject: [PATCH 1/5] Sketch for ArrayModelProvider --- .../fuzzer/providers/ArrayModelProvider.kt | 81 ++++++++++++++++--- .../fuzzer/providers/ObjectModelProvider.kt | 1 + 2 files changed, 69 insertions(+), 13 deletions(-) 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 02767a832b..47ac7e10de 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 @@ -1,34 +1,89 @@ 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.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.framework.plugin.api.util.voidClassId import org.utbot.fuzzer.FuzzedMethodDescription import org.utbot.fuzzer.FuzzedParameter -import org.utbot.fuzzer.IdGenerator +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.IdentityPreservingIdGenerator import org.utbot.fuzzer.ModelProvider import org.utbot.fuzzer.ModelProvider.Companion.yieldAllValues -import java.util.function.IntSupplier +import org.utbot.fuzzer.defaultModelProviders +import org.utbot.fuzzer.fuzz class ArrayModelProvider( - private val idGenerator: IdGenerator + private val idGenerator: IdentityPreservingIdGenerator ) : ModelProvider { + + private val defaultArraySize: Int = 3 + override fun generate(description: FuzzedMethodDescription): Sequence = sequence { description.parametersMap .asSequence() .filter { (classId, _) -> classId.isArray } .forEach { (arrayClassId, indices) -> - yieldAllValues(indices, listOf(0, 10).map { arraySize -> - UtArrayModel( - id = idGenerator.createId(), - arrayClassId, - length = arraySize, - arrayClassId.elementClassId!!.defaultValueModel(), - mutableMapOf() - ).fuzzed { - this.summary = "%var% = ${arrayClassId.elementClassId!!.simpleName}[$arraySize]" - } + val lengths = generateArrayLengths(description) + + // Fuzz small arrays with interesting elements + yieldAllValues(indices, generateArrayRecursively(arrayClassId, description, defaultArraySize)) + + // Fuzz arrays with interesting lengths and default-valued elements + yieldAllValues(indices, lengths.asSequence().map { length -> + UtArrayModel( + id = idGenerator.createId(), + arrayClassId, + length = length, + arrayClassId.elementClassId!!.defaultValueModel(), + mutableMapOf() + ).fuzzed { + this.summary = "%var% = ${arrayClassId.elementClassId!!.simpleName}[$length]" + } }) } } + + private fun generateArrayLengths(description: FuzzedMethodDescription): Set { + val syntheticArrayLengthMethodDescription = FuzzedMethodDescription( + "", + voidClassId, + listOf(intClassId), + description.concreteValues + ).apply { + packageName = description.packageName + } + + return fuzz(syntheticArrayLengthMethodDescription, ConstantsModelProvider) + .map { (it.single().model as UtPrimitiveModel).value as Int } + .filter { it in 0..10 } + .toSet() + .plus(0) + } + + private fun generateArrayRecursively(arrayClassId: ClassId, description: FuzzedMethodDescription, length: Int): Sequence { + val elementClassId = arrayClassId.elementClassId ?: error("expected ClassId for array but got ${arrayClassId.name}") + val syntheticArrayElementSetterMethodDescription = FuzzedMethodDescription( + "${arrayClassId.simpleName}OfLength$length", + voidClassId, + List(length) { elementClassId }, + description.concreteValues + ).apply { + packageName = description.packageName + } + return fuzz(syntheticArrayElementSetterMethodDescription, defaultModelProviders(idGenerator)).map { + FuzzedValue( + UtArrayModel( + idGenerator.createId(), + arrayClassId, + length, + elementClassId.defaultValueModel(), + it.withIndex().associate { it.index to it.value.model }.toMutableMap() + ) + ) + } + } } \ 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 4a45f34257..930e23c818 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 @@ -77,6 +77,7 @@ class ObjectModelProvider( nonRecursiveModelProvider ) } + .asSequence() .flatMap { (constructorId, fuzzedParameters) -> if (constructorId.parameters.isEmpty()) { sequenceOf(assembleModel(idGenerator.createId(), constructorId, emptyList())) + From 1a730a9f6dcb745cae391403dee1f86e345625c9 Mon Sep 17 00:00:00 2001 From: Ivan Volkov Date: Fri, 19 Aug 2022 17:39:50 +0300 Subject: [PATCH 2/5] Add RecursiveModelProvider abstraction --- .../main/kotlin/org/utbot/fuzzer/Fuzzer.kt | 15 +++- .../fuzzer/providers/ArrayModelProvider.kt | 88 +++++++++---------- .../fuzzer/providers/ObjectModelProvider.kt | 49 ++++------- .../providers/RecursiveModelProvider.kt | 51 +++++++++++ .../framework/plugin/api/ModelProviderTest.kt | 6 +- 5 files changed, 128 insertions(+), 81 deletions(-) create mode 100644 utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/RecursiveModelProvider.kt 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 392d9cbb4d..42c946597a 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt @@ -164,12 +164,11 @@ fun defaultModelProviders(idGenerator: IdentityPreservingIdGenerator): Mode } /** - * Creates a model provider for [ObjectModelProvider] that generates values for object constructor. + * Creates a model provider consisting of providers that do not make recursive calls inside them */ -fun objectModelProviders(idGenerator: IdentityPreservingIdGenerator): ModelProvider { +fun nonRecursiveProviders(idGenerator: IdentityPreservingIdGenerator): ModelProvider { return ModelProvider.of( CollectionModelProvider(idGenerator), - ArrayModelProvider(idGenerator), EnumModelProvider(idGenerator), StringConstantModelProvider, RegexModelProvider, @@ -180,6 +179,16 @@ fun objectModelProviders(idGenerator: IdentityPreservingIdGenerator): Model ) } +/** + * Creates a model provider consisting of providers that will make no more than [recursion] nested recursive calls. + */ +fun recursiveModelProviders(idGenerator: IdentityPreservingIdGenerator, recursion: Int): ModelProvider { + return ModelProvider.of( + ObjectModelProvider(idGenerator, recursion), + ArrayModelProvider(idGenerator, recursion) + ) +} + fun defaultModelMutators(): List = listOf( StringRandomMutator, RegexStringModelMutator, 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 47ac7e10de..b20ed81695 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 @@ -2,62 +2,56 @@ 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.framework.plugin.api.util.voidClassId import org.utbot.fuzzer.FuzzedMethodDescription import org.utbot.fuzzer.FuzzedParameter import org.utbot.fuzzer.FuzzedValue import org.utbot.fuzzer.IdentityPreservingIdGenerator -import org.utbot.fuzzer.ModelProvider import org.utbot.fuzzer.ModelProvider.Companion.yieldAllValues -import org.utbot.fuzzer.defaultModelProviders -import org.utbot.fuzzer.fuzz class ArrayModelProvider( - private val idGenerator: IdentityPreservingIdGenerator -) : ModelProvider { + idGenerator: IdentityPreservingIdGenerator, + recursion: Int = 1 +) : RecursiveModelProvider(idGenerator, recursion) { - private val defaultArraySize: Int = 3 + private val defaultArraySize = 3 + + private val limitRecursivelyFuzzed = + when(recursion) { + 1 -> Int.MAX_VALUE + else -> 3 + } override fun generate(description: FuzzedMethodDescription): Sequence = sequence { description.parametersMap .asSequence() .filter { (classId, _) -> classId.isArray } .forEach { (arrayClassId, indices) -> - val lengths = generateArrayLengths(description) // Fuzz small arrays with interesting elements yieldAllValues(indices, generateArrayRecursively(arrayClassId, description, defaultArraySize)) // Fuzz arrays with interesting lengths and default-valued elements + val lengths = generateArrayLengths(description) yieldAllValues(indices, lengths.asSequence().map { length -> - UtArrayModel( - id = idGenerator.createId(), - arrayClassId, - length = length, - arrayClassId.elementClassId!!.defaultValueModel(), - mutableMapOf() - ).fuzzed { - this.summary = "%var% = ${arrayClassId.elementClassId!!.simpleName}[$length]" - } + createFuzzedArrayModel(arrayClassId, length, null) }) } } private fun generateArrayLengths(description: FuzzedMethodDescription): Set { - val syntheticArrayLengthMethodDescription = FuzzedMethodDescription( - "", - voidClassId, - listOf(intClassId), - description.concreteValues - ).apply { - packageName = description.packageName - } + val fuzzedLengths = fuzzValuesRecursively( + types = listOf(intClassId), + baseMethodDescription = description, + modelProvider = ConstantsModelProvider, + generatedValuesName = "array length" + ) - return fuzz(syntheticArrayLengthMethodDescription, ConstantsModelProvider) + return fuzzedLengths .map { (it.single().model as UtPrimitiveModel).value as Int } .filter { it in 0..10 } .toSet() @@ -66,24 +60,28 @@ class ArrayModelProvider( private fun generateArrayRecursively(arrayClassId: ClassId, description: FuzzedMethodDescription, length: Int): Sequence { val elementClassId = arrayClassId.elementClassId ?: error("expected ClassId for array but got ${arrayClassId.name}") - val syntheticArrayElementSetterMethodDescription = FuzzedMethodDescription( - "${arrayClassId.simpleName}OfLength$length", - voidClassId, - List(length) { elementClassId }, - description.concreteValues - ).apply { - packageName = description.packageName - } - return fuzz(syntheticArrayElementSetterMethodDescription, defaultModelProviders(idGenerator)).map { - FuzzedValue( - UtArrayModel( - idGenerator.createId(), - arrayClassId, - length, - elementClassId.defaultValueModel(), - it.withIndex().associate { it.index to it.value.model }.toMutableMap() - ) - ) - } + val fuzzedArrayElements = fuzzValuesRecursively( + types = List(length) { elementClassId }, + baseMethodDescription = description, + modelProvider = generateRecursiveProvider(), + generatedValuesName = "elements of array" + ) + + return fuzzedArrayElements + .take(limitRecursivelyFuzzed) + .map { elements -> + createFuzzedArrayModel(arrayClassId, length, elements.map { it.model }) + } } + + private fun createFuzzedArrayModel(arrayClassId: ClassId, length: Int, values: List?) = + UtArrayModel( + idGenerator.createId(), + arrayClassId, + length, + arrayClassId.elementClassId!!.defaultValueModel(), + values?.withIndex()?.associate { it.index to it.value }?.toMutableMap() ?: mutableMapOf() + ).fuzzed { + this.summary = "%var% = ${arrayClassId.elementClassId!!.simpleName}[$length]" + } } \ 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 930e23c818..88c3f5ee01 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 @@ -14,18 +14,14 @@ 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.IdentityPreservingIdGenerator -import org.utbot.fuzzer.FuzzedConcreteValue 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 org.utbot.fuzzer.TooManyCombinationsException -import org.utbot.fuzzer.exceptIsInstance 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.Field @@ -39,25 +35,20 @@ private val logger by lazy { KotlinLogging.logger {} } * Creates [UtAssembleModel] for objects which have public constructors with primitives types and String as parameters. */ class ObjectModelProvider( - private val idGenerator: IdentityPreservingIdGenerator, - private val limit: Int = Int.MAX_VALUE, - private val recursion: Int = 1 -) : ModelProvider { + idGenerator: IdentityPreservingIdGenerator, + recursion: Int = 1, +) : RecursiveModelProvider(idGenerator, recursion) { - var modelProvider: ModelProvider = objectModelProviders(idGenerator) + // TODO: can we make it private val (maybe depending on recursion)? var limitValuesCreatedByFieldAccessors: Int = 100 set(value) { field = maxOf(0, value) } - 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) - } + private val limit: Int = + when(recursion) { + 1 -> Int.MAX_VALUE + else -> 1 } override fun generate(description: FuzzedMethodDescription): Sequence = sequence { @@ -74,14 +65,14 @@ class ObjectModelProvider( .associateWith { constructorId -> fuzzParameters( constructorId, - nonRecursiveModelProvider + generateRecursiveProvider() ) } .asSequence() .flatMap { (constructorId, fuzzedParameters) -> if (constructorId.parameters.isEmpty()) { sequenceOf(assembleModel(idGenerator.createId(), constructorId, emptyList())) + - generateModelsWithFieldsInitialization(constructorId, description, concreteValues) + generateModelsWithFieldsInitialization(constructorId, description) } else { fuzzedParameters.map { params -> @@ -98,20 +89,18 @@ class ObjectModelProvider( } } - private fun generateModelsWithFieldsInitialization(constructorId: ConstructorId, description: FuzzedMethodDescription, concreteValues: Collection): Sequence { + private fun generateModelsWithFieldsInitialization(constructorId: ConstructorId, description: FuzzedMethodDescription): Sequence { if (limitValuesCreatedByFieldAccessors == 0) return emptySequence() val fields = findSuitableFields(constructorId.classId, description) - val syntheticClassFieldsSetterMethodDescription = FuzzedMethodDescription( - "${constructorId.classId.simpleName}", - voidClassId, - fields.map { it.classId }, - concreteValues - ).apply { - packageName = description.packageName - } - return fuzz(syntheticClassFieldsSetterMethodDescription, nonRecursiveModelProvider) - .take(limitValuesCreatedByFieldAccessors) // limit the number of fuzzed values in this particular case + val fieldValuesSets = fuzzValuesRecursively( + types = fields.map { it.classId }, + baseMethodDescription = description, + modelProvider = generateRecursiveProvider(), + generatedValuesName = "${constructorId.classId.simpleName} fields" + ).take(limitValuesCreatedByFieldAccessors) // limit the number of fuzzed values in this particular case + + return fieldValuesSets .map { fieldValues -> val fuzzedModel = assembleModel(idGenerator.createId(), constructorId, emptyList()) val assembleModel = fuzzedModel.model as? UtAssembleModel ?: error("Expected UtAssembleModel but ${fuzzedModel.model::class.java} found") 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 new file mode 100644 index 0000000000..d2cb9f9966 --- /dev/null +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/RecursiveModelProvider.kt @@ -0,0 +1,51 @@ +package org.utbot.fuzzer.providers + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.util.voidClassId +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.IdentityPreservingIdGenerator +import org.utbot.fuzzer.ModelProvider +import org.utbot.fuzzer.fuzz +import org.utbot.fuzzer.nonRecursiveProviders +import org.utbot.fuzzer.recursiveModelProviders + +// TODO: maybe use `recursionDepth` instead of `recursion` here and in [recursiveModelProviders]? +abstract class RecursiveModelProvider( + protected val idGenerator: IdentityPreservingIdGenerator, + protected val recursion: Int +): ModelProvider { + // TODO: currently it is var due to tests, maybe we can make it private val? + var modelProviderForRecursiveCalls: ModelProvider = + if (recursion > 0) + nonRecursiveProviders(idGenerator).with(recursiveModelProviders(idGenerator, recursion - 1)) + else + nonRecursiveProviders(idGenerator) + + protected fun generateRecursiveProvider( + baseProvider: ModelProvider = modelProviderForRecursiveCalls, + fallbackProvider: ModelProvider = NullModelProvider + ): ModelProvider { + return if (recursion > 0) + baseProvider + else + baseProvider.withFallback(fallbackProvider) + } + + protected fun fuzzValuesRecursively( + types: List, + baseMethodDescription: FuzzedMethodDescription, + modelProvider: ModelProvider, + generatedValuesName: String + ): Sequence> { + val syntheticMethodDescription = FuzzedMethodDescription( + " Date: Thu, 25 Aug 2022 13:06:16 +0300 Subject: [PATCH 3/5] Moved common logic from ArrayMP and ObjectMP to RecursiveMP --- .../org/utbot/engine/UtBotSymbolicEngine.kt | 2 +- .../main/kotlin/org/utbot/fuzzer/Fuzzer.kt | 20 ++- .../kotlin/org/utbot/fuzzer/ModelProvider.kt | 12 +- .../fuzzer/providers/ArrayModelProvider.kt | 61 ++----- .../fuzzer/providers/ObjectModelProvider.kt | 160 +++++++----------- .../providers/RecursiveModelProvider.kt | 75 +++++--- .../framework/plugin/api/ModelProviderTest.kt | 2 + 7 files changed, 162 insertions(+), 170 deletions(-) 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 4eb56f7ba2..d344ed6c72 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -439,7 +439,7 @@ class UtBotSymbolicEngine( packageName = executableId.classId.packageName } fuzz(thisMethodDescription, ObjectModelProvider(defaultIdGenerator).apply { - limitValuesCreatedByFieldAccessors = 500 + totalLimit = 500 }) }.withMutations( TrieBasedFuzzerStatistics(coveredInstructionValues), methodUnderTestDescription, *defaultModelMutators().toTypedArray() 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 42c946597a..5d69c57074 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt @@ -166,7 +166,7 @@ fun defaultModelProviders(idGenerator: IdentityPreservingIdGenerator): Mode /** * Creates a model provider consisting of providers that do not make recursive calls inside them */ -fun nonRecursiveProviders(idGenerator: IdentityPreservingIdGenerator): ModelProvider { +private fun nonRecursiveProviders(idGenerator: IdentityPreservingIdGenerator): ModelProvider { return ModelProvider.of( CollectionModelProvider(idGenerator), EnumModelProvider(idGenerator), @@ -180,15 +180,25 @@ fun nonRecursiveProviders(idGenerator: IdentityPreservingIdGenerator): Mode } /** - * Creates a model provider consisting of providers that will make no more than [recursion] nested recursive calls. + * TODO: write doc here */ -fun recursiveModelProviders(idGenerator: IdentityPreservingIdGenerator, recursion: Int): ModelProvider { +private fun recursiveModelProviders(idGenerator: IdentityPreservingIdGenerator, recursionDepth: Int): ModelProvider { return ModelProvider.of( - ObjectModelProvider(idGenerator, recursion), - ArrayModelProvider(idGenerator, recursion) + ObjectModelProvider(idGenerator, recursionDepth), + ArrayModelProvider(idGenerator, recursionDepth) ) } +/** + * Creates a model provider from a list of default providers. + */ +fun defaultModelProviders(idGenerator: IdentityPreservingIdGenerator, recursionDepth: Int = 1): ModelProvider = + if (recursionDepth >= 0) + nonRecursiveProviders(idGenerator).with(recursiveModelProviders(idGenerator, recursionDepth)) + else + nonRecursiveProviders(idGenerator) + + fun defaultModelMutators(): List = listOf( StringRandomMutator, RegexStringModelMutator, diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt index 42e6eadc5b..0ac7a81aaf 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt @@ -37,7 +37,16 @@ fun interface ModelProvider { return if (this is Combined) { Combined(providers.filterNot(filter)) } else { - Combined(if (filter(this)) emptyList() else listOf(this)) + Combined(if (filter(this)) emptyList() else listOf(this)) // TODO: remove Combined from here (?) + } + } + + // TODO: add KDoc here + fun map(transform: (ModelProvider) -> ModelProvider): ModelProvider { + return if (this is Combined) { + Combined(providers.map(transform)) + } else { + transform(this) } } @@ -123,6 +132,7 @@ fun interface ModelProvider { /** * Wrapper class that delegates implementation to the [providers]. */ + // TODO: flatten Combined instances in providers (?) private class Combined(val providers: List): ModelProvider { override fun generate(description: FuzzedMethodDescription): Sequence = sequence { providers.forEach { provider -> 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 b20ed81695..6e228ef1a7 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 @@ -8,47 +8,35 @@ 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.FuzzedParameter -import org.utbot.fuzzer.FuzzedValue import org.utbot.fuzzer.IdentityPreservingIdGenerator -import org.utbot.fuzzer.ModelProvider.Companion.yieldAllValues class ArrayModelProvider( idGenerator: IdentityPreservingIdGenerator, recursion: Int = 1 ) : RecursiveModelProvider(idGenerator, recursion) { - - private val defaultArraySize = 3 - - private val limitRecursivelyFuzzed = - when(recursion) { - 1 -> Int.MAX_VALUE - else -> 3 - } - - override fun generate(description: FuzzedMethodDescription): Sequence = sequence { - description.parametersMap - .asSequence() - .filter { (classId, _) -> classId.isArray } - .forEach { (arrayClassId, indices) -> - - // Fuzz small arrays with interesting elements - yieldAllValues(indices, generateArrayRecursively(arrayClassId, description, defaultArraySize)) - - // Fuzz arrays with interesting lengths and default-valued elements - val lengths = generateArrayLengths(description) - yieldAllValues(indices, lengths.asSequence().map { length -> - createFuzzedArrayModel(arrayClassId, length, null) - }) + override fun copy(idGenerator: IdentityPreservingIdGenerator, recursionDepthLeft: Int) = + ArrayModelProvider(idGenerator, recursionDepthLeft) + + override fun generateModelConstructors( + description: FuzzedMethodDescription, + clazz: ClassId + ): List { + if (!clazz.isArray) + return listOf() + val lengths = generateArrayLengths(description).sorted() + return lengths.map { length -> + ModelConstructor(List(length) { clazz.elementClassId!! }) { values -> + createFuzzedArrayModel(clazz, length, values.map { it.model } ) } + } } private fun generateArrayLengths(description: FuzzedMethodDescription): Set { + description.concreteValues val fuzzedLengths = fuzzValuesRecursively( types = listOf(intClassId), baseMethodDescription = description, - modelProvider = ConstantsModelProvider, - generatedValuesName = "array length" + modelProvider = ConstantsModelProvider ) return fuzzedLengths @@ -56,22 +44,7 @@ class ArrayModelProvider( .filter { it in 0..10 } .toSet() .plus(0) - } - - private fun generateArrayRecursively(arrayClassId: ClassId, description: FuzzedMethodDescription, length: Int): Sequence { - val elementClassId = arrayClassId.elementClassId ?: error("expected ClassId for array but got ${arrayClassId.name}") - val fuzzedArrayElements = fuzzValuesRecursively( - types = List(length) { elementClassId }, - baseMethodDescription = description, - modelProvider = generateRecursiveProvider(), - generatedValuesName = "elements of array" - ) - - return fuzzedArrayElements - .take(limitRecursivelyFuzzed) - .map { elements -> - createFuzzedArrayModel(arrayClassId, length, elements.map { it.model }) - } + .plus(3) } private fun createFuzzedArrayModel(arrayClassId: ClassId, length: Int, values: List?) = 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 88c3f5ee01..06f4fe0d23 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 @@ -1,6 +1,5 @@ package org.utbot.fuzzer.providers -import mu.KotlinLogging import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ConstructorId import org.utbot.framework.plugin.api.FieldId @@ -16,12 +15,7 @@ import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.stringClassId import org.utbot.fuzzer.IdentityPreservingIdGenerator 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 org.utbot.fuzzer.TooManyCombinationsException -import org.utbot.fuzzer.fuzz import org.utbot.fuzzer.providers.ConstantsModelProvider.fuzzed import java.lang.reflect.Constructor import java.lang.reflect.Field @@ -29,8 +23,6 @@ import java.lang.reflect.Member import java.lang.reflect.Method import java.lang.reflect.Modifier.* -private val logger by lazy { KotlinLogging.logger {} } - /** * Creates [UtAssembleModel] for objects which have public constructors with primitives types and String as parameters. */ @@ -38,91 +30,78 @@ class ObjectModelProvider( idGenerator: IdentityPreservingIdGenerator, recursion: Int = 1, ) : RecursiveModelProvider(idGenerator, recursion) { + override fun copy(idGenerator: IdentityPreservingIdGenerator, recursionDepthLeft: Int) = + ObjectModelProvider(idGenerator, recursionDepthLeft) + + override fun generateModelConstructors( + description: FuzzedMethodDescription, + clazz: ClassId + ): List { + if (clazz == stringClassId || clazz.isPrimitiveWrapper) + return listOf() + + val constructors = collectConstructors(clazz) { javaConstructor -> + isAccessible(javaConstructor, description.packageName) + }.sortedWith( + primitiveParameterizedConstructorsFirstAndThenByParameterCount + ) - // TODO: can we make it private val (maybe depending on recursion)? - var limitValuesCreatedByFieldAccessors: Int = 100 - set(value) { - field = maxOf(0, value) - } - - private val limit: Int = - when(recursion) { - 1 -> Int.MAX_VALUE - else -> 1 - } + return buildList { - override fun generate(description: FuzzedMethodDescription): Sequence = sequence { - val fuzzedValues = with(description) { - parameters.asSequence() - .filterNot { it == stringClassId || it.isPrimitiveWrapper } - .flatMap { classId -> - collectConstructors(classId) { javaConstructor -> - isAccessible(javaConstructor, description.packageName) - }.sortedWith( - primitiveParameterizedConstructorsFirstAndThenByParameterCount - ).take(limit) - } - .associateWith { constructorId -> - fuzzParameters( - constructorId, - generateRecursiveProvider() + constructors.forEach { constructorId -> + with(constructorId) { + add( + ModelConstructor(parameters) { assembleModel(idGenerator.createId(), constructorId, it) } ) - } - .asSequence() - .flatMap { (constructorId, fuzzedParameters) -> - if (constructorId.parameters.isEmpty()) { - sequenceOf(assembleModel(idGenerator.createId(), constructorId, emptyList())) + - generateModelsWithFieldsInitialization(constructorId, description) - } - else { - fuzzedParameters.map { params -> - assembleModel(idGenerator.createId(), constructorId, params) + if (parameters.isEmpty()) { + val fields = findSuitableFields(classId, description) + if (fields.isNotEmpty()) { + add( + ModelConstructor(fields.map { it.classId }) { + generateModelsWithFieldsInitialization(this, fields, it) + } + ) } } } - } - - fuzzedValues.forEach { fuzzedValue -> - description.parametersMap[fuzzedValue.model.classId]?.forEach { index -> - yieldValue(index, fuzzedValue) } + + //add(ModelConstructor(listOf()) { UtNullModel(clazz).fuzzed {}}) } } - private fun generateModelsWithFieldsInitialization(constructorId: ConstructorId, description: FuzzedMethodDescription): Sequence { - if (limitValuesCreatedByFieldAccessors == 0) return emptySequence() - val fields = findSuitableFields(constructorId.classId, description) - - val fieldValuesSets = fuzzValuesRecursively( - types = fields.map { it.classId }, - baseMethodDescription = description, - modelProvider = generateRecursiveProvider(), - generatedValuesName = "${constructorId.classId.simpleName} fields" - ).take(limitValuesCreatedByFieldAccessors) // limit the number of fuzzed values in this particular case - - return fieldValuesSets - .map { fieldValues -> - val fuzzedModel = assembleModel(idGenerator.createId(), 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 + private fun generateModelsWithFieldsInitialization( + constructorId: ConstructorId, + fields: List, + fieldValues: List + ): FuzzedValue { + val fuzzedModel = assembleModel(idGenerator.createId(), 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) + return fuzzedModel } companion object { @@ -146,21 +125,6 @@ class ObjectModelProvider( return !hasAnyAccessModifier } - private fun FuzzedMethodDescription.fuzzParameters(constructorId: ConstructorId, vararg modelProviders: ModelProvider): Sequence> { - val fuzzedMethod = FuzzedMethodDescription( - executableId = constructorId, - concreteValues = this.concreteValues - ).apply { - this.packageName = this@fuzzParameters.packageName - } - return try { - fuzz(fuzzedMethod, *modelProviders) - } catch (t: TooManyCombinationsException) { - logger.warn(t) { "Number of combination of ${parameters.size} parameters is huge. Fuzzing is skipped for $name" } - emptySequence() - } - } - private fun assembleModel(id: Int, constructorId: ConstructorId, params: List): FuzzedValue { val instantiationChain = mutableListOf() return UtAssembleModel( @@ -221,4 +185,4 @@ class ObjectModelProvider( val setter: Method?, ) } -} +} \ 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 d2cb9f9966..45b04de606 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 @@ -3,43 +3,76 @@ package org.utbot.fuzzer.providers 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.FuzzedValue import org.utbot.fuzzer.IdentityPreservingIdGenerator import org.utbot.fuzzer.ModelProvider +import org.utbot.fuzzer.ModelProvider.Companion.yieldAllValues +import org.utbot.fuzzer.defaultModelProviders +import org.utbot.fuzzer.exceptIsInstance import org.utbot.fuzzer.fuzz -import org.utbot.fuzzer.nonRecursiveProviders -import org.utbot.fuzzer.recursiveModelProviders -// TODO: maybe use `recursionDepth` instead of `recursion` here and in [recursiveModelProviders]? +data class ModelConstructor( + val neededTypes: List, + val createModel: (List) -> FuzzedValue +) + abstract class RecursiveModelProvider( protected val idGenerator: IdentityPreservingIdGenerator, - protected val recursion: Int + protected val recursionDepthLeft: Int ): ModelProvider { - // TODO: currently it is var due to tests, maybe we can make it private val? - var modelProviderForRecursiveCalls: ModelProvider = - if (recursion > 0) - nonRecursiveProviders(idGenerator).with(recursiveModelProviders(idGenerator, recursion - 1)) - else - nonRecursiveProviders(idGenerator) - - protected fun generateRecursiveProvider( - baseProvider: ModelProvider = modelProviderForRecursiveCalls, - fallbackProvider: ModelProvider = NullModelProvider - ): ModelProvider { - return if (recursion > 0) - baseProvider + var modelProviderForRecursiveCalls: ModelProvider = defaultModelProviders(idGenerator, recursionDepthLeft - 1) + + var fallbackProvider: ModelProvider = NullModelProvider + + var totalLimit: Int = 1000 + + var branchingLimit: Int = 10 //Int.MAX_VALUE + + private fun getModelProvider(numOfBranches: Int): ModelProvider = + if (recursionDepthLeft > 0) + modelProviderForRecursiveCalls.map { + if (it is RecursiveModelProvider) + it.copy(idGenerator, recursionDepthLeft - 1).apply { + modelProviderForRecursiveCalls = this@RecursiveModelProvider.modelProviderForRecursiveCalls + fallbackProvider = this@RecursiveModelProvider.fallbackProvider + totalLimit = this@RecursiveModelProvider.totalLimit / numOfBranches + branchingLimit = this@RecursiveModelProvider.branchingLimit + } + else + it + } else - baseProvider.withFallback(fallbackProvider) - } + modelProviderForRecursiveCalls + .exceptIsInstance() + .withFallback(fallbackProvider) + + abstract fun copy(idGenerator: IdentityPreservingIdGenerator, recursionDepthLeft: Int): RecursiveModelProvider + + abstract fun generateModelConstructors(description: FuzzedMethodDescription, clazz: ClassId): List + + 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)) + } + } + }.take(totalLimit) protected fun fuzzValuesRecursively( types: List, baseMethodDescription: FuzzedMethodDescription, modelProvider: ModelProvider, - generatedValuesName: String ): Sequence> { + if (types.isEmpty()) + return sequenceOf(listOf()) val syntheticMethodDescription = FuzzedMethodDescription( - "", // TODO: maybe add something here voidClassId, types, baseMethodDescription.concreteValues 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 b7d0320f9d..d3e10654ec 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 @@ -21,6 +21,7 @@ import org.utbot.fuzzer.providers.ObjectModelProvider import org.utbot.fuzzer.providers.PrimitivesModelProvider import org.utbot.fuzzer.providers.StringConstantModelProvider import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.utbot.framework.plugin.api.samples.FieldSetterClass import org.utbot.framework.plugin.api.samples.OuterClassWithEnums @@ -412,6 +413,7 @@ class ModelProviderTest { } } + @Disabled @Test @Suppress("unused", "UNUSED_PARAMETER", "ConvertSecondaryConstructorToPrimary") fun `test complex object is constructed with the simplest inner object constructor`() { From 36c749e02fed4007f8ad679c105a370489712971 Mon Sep 17 00:00:00 2001 From: Ivan Volkov Date: Mon, 29 Aug 2022 19:04:12 +0300 Subject: [PATCH 4/5] Minor changes & documentation added --- .../main/kotlin/org/utbot/fuzzer/Fuzzer.kt | 19 ------- .../kotlin/org/utbot/fuzzer/ModelProvider.kt | 4 +- .../fuzzer/providers/ArrayModelProvider.kt | 37 ++++++------ .../fuzzer/providers/ObjectModelProvider.kt | 25 ++++---- .../providers/RecursiveModelProvider.kt | 57 +++++++++++++++---- .../framework/plugin/api/ModelProviderTest.kt | 2 - 6 files changed, 83 insertions(+), 61 deletions(-) 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 5d69c57074..8235455f06 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt @@ -12,7 +12,6 @@ import org.utbot.fuzzer.providers.EnumModelProvider import org.utbot.fuzzer.providers.ObjectModelProvider import org.utbot.fuzzer.providers.PrimitiveDefaultsModelProvider import org.utbot.fuzzer.providers.PrimitiveWrapperModelProvider -import org.utbot.fuzzer.providers.PrimitivesModelProvider import org.utbot.fuzzer.providers.RegexModelProvider import org.utbot.fuzzer.providers.StringConstantModelProvider import java.util.* @@ -145,24 +144,6 @@ fun Sequence>.withMutations(statistics: FuzzerStatistics): ModelProvider { - return ModelProvider.of( - ObjectModelProvider(idGenerator), - CollectionModelProvider(idGenerator), - ArrayModelProvider(idGenerator), - EnumModelProvider(idGenerator), - ConstantsModelProvider, - StringConstantModelProvider, - RegexModelProvider, - CharToStringModelProvider, - PrimitivesModelProvider, - PrimitiveWrapperModelProvider, - ) -} - /** * Creates a model provider consisting of providers that do not make recursive calls inside them */ diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt index 0ac7a81aaf..95fb9ec97d 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt @@ -41,7 +41,9 @@ fun interface ModelProvider { } } - // TODO: add KDoc here + /** + * Applies [transform] for current provider + */ fun map(transform: (ModelProvider) -> ModelProvider): ModelProvider { return if (this is Combined) { Combined(providers.map(transform)) 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 6e228ef1a7..394344e3a7 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 @@ -12,39 +12,44 @@ import org.utbot.fuzzer.IdentityPreservingIdGenerator class ArrayModelProvider( idGenerator: IdentityPreservingIdGenerator, - recursion: Int = 1 -) : RecursiveModelProvider(idGenerator, recursion) { - override fun copy(idGenerator: IdentityPreservingIdGenerator, recursionDepthLeft: Int) = - ArrayModelProvider(idGenerator, recursionDepthLeft) + recursionDepthLeft: Int = 1 +) : RecursiveModelProvider(idGenerator, recursionDepthLeft) { + override fun createNewInstance(parentProvider: RecursiveModelProvider, newTotalLimit: Int): 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 + } override fun generateModelConstructors( description: FuzzedMethodDescription, - clazz: ClassId + classId: ClassId ): List { - if (!clazz.isArray) + if (!classId.isArray) return listOf() - val lengths = generateArrayLengths(description).sorted() + val lengths = generateArrayLengths(description) return lengths.map { length -> - ModelConstructor(List(length) { clazz.elementClassId!! }) { values -> - createFuzzedArrayModel(clazz, length, values.map { it.model } ) + ModelConstructor(List(length) { classId.elementClassId!! }) { values -> + createFuzzedArrayModel(classId, length, values.map { it.model } ) } } } - private fun generateArrayLengths(description: FuzzedMethodDescription): Set { - description.concreteValues + private fun generateArrayLengths(description: FuzzedMethodDescription): List { val fuzzedLengths = fuzzValuesRecursively( types = listOf(intClassId), baseMethodDescription = description, modelProvider = ConstantsModelProvider ) - return fuzzedLengths + // 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 0..10 } - .toSet() - .plus(0) - .plus(3) + .filter { it in 1..10 && it != 3 } + .toSortedSet() + .toList() } private fun createFuzzedArrayModel(arrayClassId: ClassId, length: Int, values: List?) = 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 06f4fe0d23..628e680988 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,23 +24,28 @@ import java.lang.reflect.Method import java.lang.reflect.Modifier.* /** - * Creates [UtAssembleModel] for objects which have public constructors with primitives types and String as parameters. + * Creates [UtAssembleModel] for objects which have public constructors */ class ObjectModelProvider( idGenerator: IdentityPreservingIdGenerator, - recursion: Int = 1, -) : RecursiveModelProvider(idGenerator, recursion) { - override fun copy(idGenerator: IdentityPreservingIdGenerator, recursionDepthLeft: Int) = - ObjectModelProvider(idGenerator, recursionDepthLeft) + 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 generateModelConstructors( description: FuzzedMethodDescription, - clazz: ClassId + classId: ClassId ): List { - if (clazz == stringClassId || clazz.isPrimitiveWrapper) + if (classId == stringClassId || classId.isPrimitiveWrapper) return listOf() - val constructors = collectConstructors(clazz) { javaConstructor -> + val constructors = collectConstructors(classId) { javaConstructor -> isAccessible(javaConstructor, description.packageName) }.sortedWith( primitiveParameterizedConstructorsFirstAndThenByParameterCount @@ -54,7 +59,7 @@ class ObjectModelProvider( ModelConstructor(parameters) { assembleModel(idGenerator.createId(), constructorId, it) } ) if (parameters.isEmpty()) { - val fields = findSuitableFields(classId, description) + val fields = findSuitableFields(this.classId, description) if (fields.isNotEmpty()) { add( ModelConstructor(fields.map { it.classId }) { @@ -65,8 +70,6 @@ class ObjectModelProvider( } } } - - //add(ModelConstructor(listOf()) { UtNullModel(clazz).fuzzed {}}) } } 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 45b04de606..4fbf031adc 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 @@ -12,14 +12,37 @@ import org.utbot.fuzzer.defaultModelProviders import org.utbot.fuzzer.exceptIsInstance import org.utbot.fuzzer.fuzz +/** + * Auxiliary data class that stores information describing how to construct model from parts (submodels) + * + * @param neededTypes list containing type ([ClassId]) of each submodel + * @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: (List) -> FuzzedValue + val createModel: (subModels: List) -> FuzzedValue ) +/** + * Abstraction for providers that may call other providers recursively inside them. [generate] will firstly get possible + * model constructors (provided by [generateModelConstructors]) and then fuzz parameters for each of them using synthetic method + * + * @param recursionDepthLeft maximum recursion level, i.e. maximum number of nested calls produced by this provider + * + * @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 + * 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 branchingLimit maximum number of [ModelConstructor]s used by [generate] (see [generateModelConstructors]) + */ abstract class RecursiveModelProvider( - protected val idGenerator: IdentityPreservingIdGenerator, - protected val recursionDepthLeft: Int + val idGenerator: IdentityPreservingIdGenerator, + val recursionDepthLeft: Int ): ModelProvider { var modelProviderForRecursiveCalls: ModelProvider = defaultModelProviders(idGenerator, recursionDepthLeft - 1) @@ -33,12 +56,7 @@ abstract class RecursiveModelProvider( if (recursionDepthLeft > 0) modelProviderForRecursiveCalls.map { if (it is RecursiveModelProvider) - it.copy(idGenerator, recursionDepthLeft - 1).apply { - modelProviderForRecursiveCalls = this@RecursiveModelProvider.modelProviderForRecursiveCalls - fallbackProvider = this@RecursiveModelProvider.fallbackProvider - totalLimit = this@RecursiveModelProvider.totalLimit / numOfBranches - branchingLimit = this@RecursiveModelProvider.branchingLimit - } + it.createNewInstance(this, totalLimit / numOfBranches) else it } @@ -47,9 +65,24 @@ abstract class RecursiveModelProvider( .exceptIsInstance() .withFallback(fallbackProvider) - abstract fun copy(idGenerator: IdentityPreservingIdGenerator, recursionDepthLeft: Int): RecursiveModelProvider + /** + * 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 - abstract fun generateModelConstructors(description: FuzzedMethodDescription, clazz: ClassId): List + /** + * 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 fun copySettingsFrom(otherProvider: RecursiveModelProvider): RecursiveModelProvider { + modelProviderForRecursiveCalls = otherProvider.modelProviderForRecursiveCalls + fallbackProvider = otherProvider.fallbackProvider + totalLimit = otherProvider.totalLimit + branchingLimit = otherProvider.branchingLimit + return this + } final override fun generate(description: FuzzedMethodDescription): Sequence = sequence { description.parametersMap.asSequence().forEach { (classId, indices) -> @@ -72,7 +105,7 @@ abstract class RecursiveModelProvider( if (types.isEmpty()) return sequenceOf(listOf()) val syntheticMethodDescription = FuzzedMethodDescription( - "", // TODO: maybe add something here + "", //TODO: maybe add more info here voidClassId, types, baseMethodDescription.concreteValues 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 d3e10654ec..b7d0320f9d 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 @@ -21,7 +21,6 @@ import org.utbot.fuzzer.providers.ObjectModelProvider import org.utbot.fuzzer.providers.PrimitivesModelProvider import org.utbot.fuzzer.providers.StringConstantModelProvider import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.utbot.framework.plugin.api.samples.FieldSetterClass import org.utbot.framework.plugin.api.samples.OuterClassWithEnums @@ -413,7 +412,6 @@ class ModelProviderTest { } } - @Disabled @Test @Suppress("unused", "UNUSED_PARAMETER", "ConvertSecondaryConstructorToPrimary") fun `test complex object is constructed with the simplest inner object constructor`() { From d6d09f263457d66c3542065dd03e30257376e08d Mon Sep 17 00:00:00 2001 From: Ivan Volkov Date: Tue, 30 Aug 2022 14:08:27 +0300 Subject: [PATCH 5/5] Return behavior of defaultModelProviders and minor fixes: * Add flattening to Combined * Change default value for RecursiveModelProvider.branchingLimit --- .../main/kotlin/org/utbot/fuzzer/Fuzzer.kt | 40 +++++++++++-------- .../kotlin/org/utbot/fuzzer/ModelProvider.kt | 17 ++++++-- .../providers/RecursiveModelProvider.kt | 8 ++-- 3 files changed, 41 insertions(+), 24 deletions(-) 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 8235455f06..6b6d813af4 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt @@ -12,6 +12,7 @@ import org.utbot.fuzzer.providers.EnumModelProvider import org.utbot.fuzzer.providers.ObjectModelProvider import org.utbot.fuzzer.providers.PrimitiveDefaultsModelProvider import org.utbot.fuzzer.providers.PrimitiveWrapperModelProvider +import org.utbot.fuzzer.providers.PrimitivesModelProvider import org.utbot.fuzzer.providers.RegexModelProvider import org.utbot.fuzzer.providers.StringConstantModelProvider import java.util.* @@ -145,39 +146,44 @@ fun Sequence>.withMutations(statistics: FuzzerStatistics): ModelProvider { +fun defaultModelProviders(idGenerator: IdentityPreservingIdGenerator): ModelProvider { return ModelProvider.of( + ObjectModelProvider(idGenerator), CollectionModelProvider(idGenerator), + ArrayModelProvider(idGenerator), EnumModelProvider(idGenerator), + ConstantsModelProvider, StringConstantModelProvider, RegexModelProvider, CharToStringModelProvider, - ConstantsModelProvider, - PrimitiveDefaultsModelProvider, + PrimitivesModelProvider, PrimitiveWrapperModelProvider, ) } /** - * TODO: write doc here + * Creates a model provider from a list of providers that we want to use by default in [RecursiveModelProvider] */ -private fun recursiveModelProviders(idGenerator: IdentityPreservingIdGenerator, recursionDepth: Int): ModelProvider { - return ModelProvider.of( - ObjectModelProvider(idGenerator, recursionDepth), - ArrayModelProvider(idGenerator, recursionDepth) +internal fun modelProviderForRecursiveCalls(idGenerator: IdentityPreservingIdGenerator, recursionDepth: Int): ModelProvider { + val nonRecursiveProviders = ModelProvider.of( + CollectionModelProvider(idGenerator), + EnumModelProvider(idGenerator), + StringConstantModelProvider, + CharToStringModelProvider, + ConstantsModelProvider, + PrimitiveDefaultsModelProvider, + PrimitiveWrapperModelProvider, ) -} -/** - * Creates a model provider from a list of default providers. - */ -fun defaultModelProviders(idGenerator: IdentityPreservingIdGenerator, recursionDepth: Int = 1): ModelProvider = - if (recursionDepth >= 0) - nonRecursiveProviders(idGenerator).with(recursiveModelProviders(idGenerator, recursionDepth)) + return if (recursionDepth >= 0) + nonRecursiveProviders + .with(ObjectModelProvider(idGenerator, recursionDepth)) + .with(ArrayModelProvider(idGenerator, recursionDepth)) else - nonRecursiveProviders(idGenerator) + nonRecursiveProviders +} fun defaultModelMutators(): List = listOf( diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt index 95fb9ec97d..4b28dc0dcf 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt @@ -37,7 +37,7 @@ fun interface ModelProvider { return if (this is Combined) { Combined(providers.filterNot(filter)) } else { - Combined(if (filter(this)) emptyList() else listOf(this)) // TODO: remove Combined from here (?) + Combined(if (filter(this)) emptyList() else listOf(this)) } } @@ -134,8 +134,19 @@ fun interface ModelProvider { /** * Wrapper class that delegates implementation to the [providers]. */ - // TODO: flatten Combined instances in providers (?) - private class Combined(val providers: List): ModelProvider { + private class Combined(providers: List): ModelProvider { + val providers: List + + init { + // Flattening to avoid Combined inside Combined (for correct work of except, map, etc.) + this.providers = providers.flatMap { + if (it is Combined) + it.providers + else + listOf(it) + } + } + override fun generate(description: FuzzedMethodDescription): Sequence = sequence { providers.forEach { provider -> provider.generate(description).forEach { 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 4fbf031adc..e74bcf1670 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 @@ -8,9 +8,9 @@ import org.utbot.fuzzer.FuzzedValue import org.utbot.fuzzer.IdentityPreservingIdGenerator import org.utbot.fuzzer.ModelProvider import org.utbot.fuzzer.ModelProvider.Companion.yieldAllValues -import org.utbot.fuzzer.defaultModelProviders import org.utbot.fuzzer.exceptIsInstance import org.utbot.fuzzer.fuzz +import org.utbot.fuzzer.modelProviderForRecursiveCalls /** * Auxiliary data class that stores information describing how to construct model from parts (submodels) @@ -43,14 +43,14 @@ data class ModelConstructor( abstract class RecursiveModelProvider( val idGenerator: IdentityPreservingIdGenerator, val recursionDepthLeft: Int -): ModelProvider { - var modelProviderForRecursiveCalls: ModelProvider = defaultModelProviders(idGenerator, recursionDepthLeft - 1) +) : ModelProvider { + var modelProviderForRecursiveCalls: ModelProvider = modelProviderForRecursiveCalls(idGenerator, recursionDepthLeft - 1) var fallbackProvider: ModelProvider = NullModelProvider var totalLimit: Int = 1000 - var branchingLimit: Int = 10 //Int.MAX_VALUE + var branchingLimit: Int = Int.MAX_VALUE private fun getModelProvider(numOfBranches: Int): ModelProvider = if (recursionDepthLeft > 0)