From a5e94d23de803471fd15f84fcd61ecba6aed73ee Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Wed, 14 Jun 2023 11:21:11 +0300 Subject: [PATCH 1/3] Support using identifiers generated by database as method arguments --- .../org/utbot/engine/UtBotSymbolicEngine.kt | 11 +++- .../codegen/tree/CgVariableConstructor.kt | 9 +-- .../constructors/MockValueConstructor.kt | 16 +++-- .../utbot/fuzzing/providers/Collections.kt | 2 +- .../org/utbot/fuzzing/providers/Field.kt | 65 +++++++++++++++++++ .../org/utbot/fuzzing/providers/Objects.kt | 10 +-- .../org/utbot/fuzzing/providers/Utils.kt | 14 ++++ .../org/utbot/fuzzing/spring/SavedEntity.kt | 46 +++++++++++++ .../fuzzing/spring/SpringBeanValueProvider.kt | 7 +- 9 files changed, 151 insertions(+), 29 deletions(-) create mode 100644 utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Field.kt create mode 100644 utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Utils.kt create mode 100644 utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/SavedEntity.kt 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 4191acdbfa..d8c43e5648 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -41,6 +41,8 @@ import org.utbot.framework.util.sootMethod import org.utbot.fuzzer.* import org.utbot.fuzzing.* import org.utbot.fuzzing.providers.ObjectValueProvider +import org.utbot.fuzzing.providers.FieldValueProvider +import org.utbot.fuzzing.spring.SavedEntityProvider import org.utbot.fuzzing.spring.SpringBeanValueProvider import org.utbot.fuzzing.utils.Trie import org.utbot.instrumentation.ConcreteExecutor @@ -394,6 +396,9 @@ class UtBotSymbolicEngine( entityClassId = ClassId("com.rest.order.models.Order") ) ) + val generatedValueFieldIds = listOf( + FieldId(ClassId("com.rest.order.models.Order"), "id") + ) logger.info { "Relevant repositories: $relevantRepositories" } // spring should try to generate bean values, but if it fails, then object value provider is used for it val springBeanValueProvider = SpringBeanValueProvider( @@ -405,7 +410,11 @@ class UtBotSymbolicEngine( }, relevantRepositories = relevantRepositories ).withFallback(ObjectValueProvider(defaultIdGenerator)) - provider.except { p -> p is ObjectValueProvider }.with(springBeanValueProvider) + provider + .except { p -> p is ObjectValueProvider } + .with(springBeanValueProvider) + .with(ValueProvider.of(relevantRepositories.map { SavedEntityProvider(defaultIdGenerator, it) })) + .with(ValueProvider.of(generatedValueFieldIds.map { FieldValueProvider(defaultIdGenerator, it) })) }.let(transform) runJavaFuzzing( defaultIdGenerator, diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgVariableConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgVariableConstructor.kt index 0cdc1d7cc4..ec0aa33347 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgVariableConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgVariableConstructor.kt @@ -240,18 +240,13 @@ open class CgVariableConstructor(val context: CgContext) : val executable = statementCall.statement val params = statementCall.params - val type = when (executable) { - is MethodId -> executable.returnType - is DirectFieldAccessId -> executable.fieldId.type - is ConstructorId -> executable.classId - } // Don't use redundant constructors for primitives and String - val initExpr = if (isPrimitiveWrapperOrString(type)) { + val initExpr = if (executable is ConstructorId && isPrimitiveWrapperOrString(executable.classId)) { cgLiteralForWrapper(params) } else { createCgExecutableCallFromUtExecutableCall(statementCall) } - newVar(type, model, baseName) { + newVar(model.classId, model, baseName) { initExpr }.also { valueByUtModelWrapper[model.wrap()] = it diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/MockValueConstructor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/MockValueConstructor.kt index 9afa5dddef..e736d48bba 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/MockValueConstructor.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/MockValueConstructor.kt @@ -318,15 +318,17 @@ class MockValueConstructor( /** * Constructs object with [UtAssembleModel]. */ - private fun constructFromAssembleModel(assembleModel: UtAssembleModel): Any { + private fun constructFromAssembleModel(assembleModel: UtAssembleModel): Any? { constructedObjects[assembleModel]?.let { return it } val instantiationExecutableCall = assembleModel.instantiationCall val result = updateWithStatementCallModel(instantiationExecutableCall) - checkNotNull(result) { - "Tracked instance can't be null for call ${instantiationExecutableCall.statement} in model $assembleModel" - } - constructedObjects[assembleModel] = result + // TODO figure out the why tracked instance can't be null, + // right now behaviour is incorrect as null producing models can get constructed multiple times +// checkNotNull(result) { +// "Tracked instance can't be null for call ${instantiationExecutableCall.statement} in model $assembleModel" +// } + result?.let { constructedObjects[assembleModel] = it } assembleModel.modificationsChain.forEach { statementModel -> when (statementModel) { @@ -335,7 +337,7 @@ class MockValueConstructor( } } - return constructedObjects[assembleModel] ?: error("Can't assemble model: $assembleModel") + return constructedObjects[assembleModel] // ?: error("Can't assemble model: $assembleModel") } private fun constructFromLambdaModel(lambdaModel: UtLambdaModel): Any { @@ -437,7 +439,7 @@ class MockValueConstructor( newInstance(*args.toTypedArray()) } - private fun DirectFieldAccessId.get(instance: Any?): Any { + private fun DirectFieldAccessId.get(instance: Any?): Any? { val field = fieldId.jField return field.runSandbox { field.get(instance) diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Collections.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Collections.kt index 86091e3e33..51bda79661 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Collections.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Collections.kt @@ -70,7 +70,7 @@ class EmptyCollectionValueProvider( summary = "%var% = ${executableId.classId.simpleName}#${executableId.name}" } } else { - UtNullModel(classId).fuzzed { summary = "%var% = null" } + nullFuzzedValue(classId) } }, )) diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Field.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Field.kt new file mode 100644 index 0000000000..14fb1773b5 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Field.kt @@ -0,0 +1,65 @@ +package org.utbot.fuzzing.providers + +import mu.KotlinLogging +import org.utbot.framework.plugin.api.DirectFieldAccessId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtDirectGetFieldModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.IdGenerator +import org.utbot.fuzzer.fuzzed +import org.utbot.fuzzing.FuzzedDescription +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.toFuzzerType + +class FieldValueProvider( + private val idGenerator: IdGenerator, + private val fieldId: FieldId, +) : JavaValueProvider { + companion object { + private val logger = KotlinLogging.logger {} + } + + override fun accept(type: FuzzedType): Boolean = type.classId == fieldId.type + + override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence> = sequenceOf( + Seed.Recursive( + construct = Routine.Create(listOf( + toFuzzerType(fieldId.declaringClass.jClass, description.typeCache) + )) { values -> + val thisInstanceValue = values.single() + val thisInstanceModel = when (val model = thisInstanceValue.model) { + is UtReferenceModel -> model + is UtNullModel -> return@Create nullFuzzedValue(type.classId) + else -> { + logger.warn { "This instance model can be only UtReferenceModel or UtNullModel, but $model is met" } + return@Create nullFuzzedValue(type.classId) + } + } + UtAssembleModel( + id = idGenerator.createId(), + classId = type.classId, + modelName = "${thisInstanceModel.modelName}.${fieldId.name}", + instantiationCall = UtDirectGetFieldModel( + instance = thisInstanceModel, + fieldAccess = DirectFieldAccessId( + classId = fieldId.declaringClass, + name = "", + fieldId = fieldId + ) + ) + ).fuzzed { + summary = "${thisInstanceValue.summary}.${fieldId.name}" + } + }, + modify = emptySequence(), + empty = nullRoutine(type.classId) + ) + ) +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt index 508fbb10ce..e73ff38c07 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt @@ -86,11 +86,7 @@ class ObjectValueProvider( } } }, - empty = Routine.Empty { - UtNullModel(classId).fuzzed { - summary = "%var% = null" - } - } + empty = nullRoutine(classId) ) } @@ -133,9 +129,7 @@ object NullValueProvider : ValueProvider> { if (description.scope?.getProperty(NULLABLE_PROP) == true) { - yield(Seed.Simple(UtNullModel(type.classId).fuzzed { - summary = "%var% = null" - })) + yield(Seed.Simple(nullFuzzedValue(classClassId))) } } } diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Utils.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Utils.kt new file mode 100644 index 0000000000..a6317ce518 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Utils.kt @@ -0,0 +1,14 @@ +package org.utbot.fuzzing.providers + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.fuzzed +import org.utbot.fuzzing.Routine + +fun nullRoutine(classId: ClassId): Routine.Empty = + Routine.Empty { nullFuzzedValue(classId) } + +fun nullFuzzedValue(classId: ClassId): FuzzedValue = + UtNullModel(classId).fuzzed { summary = "%var% = null" } \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/SavedEntity.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/SavedEntity.kt new file mode 100644 index 0000000000..2a41d30f68 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/SavedEntity.kt @@ -0,0 +1,46 @@ +package org.utbot.fuzzing.spring + +import org.utbot.framework.plugin.api.SpringRepositoryId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.util.SpringModelUtils +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.IdGenerator +import org.utbot.fuzzer.fuzzed +import org.utbot.fuzzing.FuzzedDescription +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.providers.nullRoutine + +class SavedEntityProvider( + private val idGenerator: IdGenerator, + private val repositoryId: SpringRepositoryId +) : JavaValueProvider { + override fun accept(type: FuzzedType): Boolean = type.classId == repositoryId.entityClassId + + override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence> = + sequenceOf( + Seed.Recursive( + construct = Routine.Create(listOf(type)) { values -> + val entityValue = values.single() + val entityModel = entityValue.model + UtAssembleModel( + id = idGenerator.createId(), + classId = type.classId, + modelName = "${repositoryId.repositoryBeanName}.save(${(entityModel as? UtReferenceModel)?.modelName ?: "..."})", + instantiationCall = SpringModelUtils.createSaveCallModel( + repositoryId = repositoryId, + id = idGenerator.createId(), + entityModel = entityModel + ) + ).fuzzed { + summary = "%var% = ${repositoryId.repositoryBeanName}.save(${entityValue.summary})" + } + }, + modify = emptySequence(), + empty = nullRoutine(type.classId) + ) + ) +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/SpringBeanValueProvider.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/SpringBeanValueProvider.kt index 8e961998dd..ac040b4e6b 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/SpringBeanValueProvider.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/SpringBeanValueProvider.kt @@ -9,6 +9,7 @@ import org.utbot.fuzzer.IdGenerator import org.utbot.fuzzer.fuzzed import org.utbot.fuzzing.* import org.utbot.fuzzing.providers.SPRING_BEAN_PROP +import org.utbot.fuzzing.providers.nullRoutine class SpringBeanValueProvider( private val idGenerator: IdGenerator, @@ -59,11 +60,7 @@ class SpringBeanValueProvider( }) } }, - empty = Routine.Empty { - UtNullModel(type.classId).fuzzed { - summary = "%var% = null" - } - } + empty = nullRoutine(type.classId) ) ) } From dfffa566d00f500c66c3b03f4dd2f3845e3314a0 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Wed, 14 Jun 2023 19:26:38 +0300 Subject: [PATCH 2/3] Properly handle `instantiationCall` of type `UtDirectGetFieldModel` evaluating into `null` --- .../constructors/MockValueConstructor.kt | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/MockValueConstructor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/MockValueConstructor.kt index e736d48bba..5b5bf226c2 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/MockValueConstructor.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/MockValueConstructor.kt @@ -318,17 +318,22 @@ class MockValueConstructor( /** * Constructs object with [UtAssembleModel]. */ - private fun constructFromAssembleModel(assembleModel: UtAssembleModel): Any? { + private fun constructFromAssembleModel(assembleModel: UtAssembleModel): Any { constructedObjects[assembleModel]?.let { return it } val instantiationExecutableCall = assembleModel.instantiationCall val result = updateWithStatementCallModel(instantiationExecutableCall) - // TODO figure out the why tracked instance can't be null, - // right now behaviour is incorrect as null producing models can get constructed multiple times -// checkNotNull(result) { -// "Tracked instance can't be null for call ${instantiationExecutableCall.statement} in model $assembleModel" -// } - result?.let { constructedObjects[assembleModel] = it } + + // Executions that get `null` in a complicated way (e.g. like this: `new Pair(null, null).getFirst()`) + // are only produced by fuzzer and are considered undesirable because fuzzer can also produce simpler + // executions that just use `null` literal. + // + // So for such executions we throw `IllegalStateException` that indicates that construction of arguments + // has failed and causes execution to terminate with `UtConcreteExecutionProcessedFailure` execution result. + checkNotNull(result) { + "Tracked instance can't be null for call ${instantiationExecutableCall.statement} in model $assembleModel" + } + constructedObjects[assembleModel] = result assembleModel.modificationsChain.forEach { statementModel -> when (statementModel) { @@ -337,7 +342,7 @@ class MockValueConstructor( } } - return constructedObjects[assembleModel] // ?: error("Can't assemble model: $assembleModel") + return constructedObjects[assembleModel] ?: error("Can't assemble model: $assembleModel") } private fun constructFromLambdaModel(lambdaModel: UtLambdaModel): Any { From 18f5fbf7dd97c3076a4ec209ff04792e88f6d625 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Wed, 14 Jun 2023 19:30:12 +0300 Subject: [PATCH 3/3] Improve check for redundant constructors for primitives and String in code generation --- .../org/utbot/framework/codegen/tree/CgVariableConstructor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgVariableConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgVariableConstructor.kt index ec0aa33347..ebb3fd3244 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgVariableConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgVariableConstructor.kt @@ -241,7 +241,7 @@ open class CgVariableConstructor(val context: CgContext) : val params = statementCall.params // Don't use redundant constructors for primitives and String - val initExpr = if (executable is ConstructorId && isPrimitiveWrapperOrString(executable.classId)) { + val initExpr = if (executable is ConstructorId && isPrimitiveWrapperOrString(model.classId)) { cgLiteralForWrapper(params) } else { createCgExecutableCallFromUtExecutableCall(statementCall)