Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Moved common logic from ArrayMP and ObjectMP to RecursiveMP
  • Loading branch information
volivan239 committed Aug 29, 2022
commit 570809e431746fff197cf8da8ed07f27e153a4da
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
20 changes: 15 additions & 5 deletions utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ fun defaultModelProviders(idGenerator: IdentityPreservingIdGenerator<Int>): Mode
/**
* Creates a model provider consisting of providers that do not make recursive calls inside them
*/
fun nonRecursiveProviders(idGenerator: IdentityPreservingIdGenerator<Int>): ModelProvider {
private fun nonRecursiveProviders(idGenerator: IdentityPreservingIdGenerator<Int>): ModelProvider {
return ModelProvider.of(
CollectionModelProvider(idGenerator),
EnumModelProvider(idGenerator),
Expand All @@ -180,15 +180,25 @@ fun nonRecursiveProviders(idGenerator: IdentityPreservingIdGenerator<Int>): 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<Int>, recursion: Int): ModelProvider {
private fun recursiveModelProviders(idGenerator: IdentityPreservingIdGenerator<Int>, 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<Int>, recursionDepth: Int = 1): ModelProvider =
if (recursionDepth >= 0)
nonRecursiveProviders(idGenerator).with(recursiveModelProviders(idGenerator, recursionDepth))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Note, that the other method called 'defaultModelProviders' without parameter with recursion has another set of model providers. Therefore. I'd recommend to change name for this method to avoid ambiguous calls.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This method should have replaced the method without recursionDepth parameter with no changes in behavior when this parameter is not specified. Seems like I accidently got duplication after rebase. Deleted method without parameter now. Could you please check that it didn't change the behavior?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It changes because PrimitivesModelProvider isn't used anymore. By default I want to keep an ability to set different providers by default, because I usually want to use shallower value set for it. Right now this change is only about PrimitivesModelProvider, but in the future I'd want to add some providers that are used for generating flat parameters and aren't used for recursive.

else
nonRecursiveProviders(idGenerator)


fun defaultModelMutators(): List<ModelMutator> = listOf(
StringRandomMutator,
RegexStringModelMutator,
Expand Down
12 changes: 11 additions & 1 deletion utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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>): ModelProvider {
override fun generate(description: FuzzedMethodDescription): Sequence<FuzzedParameter> = sequence {
providers.forEach { provider ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,70 +8,43 @@ 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<Int>,
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<FuzzedParameter> = 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<Int>, recursionDepthLeft: Int) =
ArrayModelProvider(idGenerator, recursionDepthLeft)

override fun generateModelConstructors(
description: FuzzedMethodDescription,
clazz: ClassId
): List<ModelConstructor> {
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<Int> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks like this can be simplified. It is too much work to generate some values in interval from 0 up to 10

description.concreteValues
val fuzzedLengths = fuzzValuesRecursively(
types = listOf(intClassId),
baseMethodDescription = description,
modelProvider = ConstantsModelProvider,
generatedValuesName = "array length"
modelProvider = ConstantsModelProvider
)

return fuzzedLengths
.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<FuzzedValue> {
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<UtModel>?) =
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -16,113 +15,93 @@ 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
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.
*/
class ObjectModelProvider(
idGenerator: IdentityPreservingIdGenerator<Int>,
recursion: Int = 1,
) : RecursiveModelProvider(idGenerator, recursion) {
override fun copy(idGenerator: IdentityPreservingIdGenerator<Int>, recursionDepthLeft: Int) =
ObjectModelProvider(idGenerator, recursionDepthLeft)

override fun generateModelConstructors(
description: FuzzedMethodDescription,
clazz: ClassId
): List<ModelConstructor> {
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<FuzzedParameter> = 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<FuzzedValue> {
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<FieldDescription>,
fieldValues: List<FuzzedValue>
): 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 {
Expand All @@ -146,21 +125,6 @@ class ObjectModelProvider(
return !hasAnyAccessModifier
}

private fun FuzzedMethodDescription.fuzzParameters(constructorId: ConstructorId, vararg modelProviders: ModelProvider): Sequence<List<FuzzedValue>> {
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>): FuzzedValue {
val instantiationChain = mutableListOf<UtStatementModel>()
return UtAssembleModel(
Expand Down Expand Up @@ -221,4 +185,4 @@ class ObjectModelProvider(
val setter: Method?,
)
}
}
}
Loading