From 74888676cb9e7c68e366d74f347c4de179263f20 Mon Sep 17 00:00:00 2001 From: Maxim Pelevin Date: Thu, 26 May 2022 17:20:22 +0300 Subject: [PATCH 1/2] Fuzzer improvements: * Randomize fuzzer inputs * Limit fuzzing attempts * Mutate primitive values according to comparison operations * Mutate string constants * Move fuzz-relative code out of UtBotSymbolicEngine.kt * Test added --- .../kotlin/org/utbot/framework/UtSettings.kt | 7 +- .../org/utbot/engine/UtBotSymbolicEngine.kt | 247 ++++------------- .../org/utbot/fuzzer/FuzzerFunctions.kt | 224 +++++++++++++++ .../org/utbot/fuzzer/SimpleModelProvider.kt | 101 +++++++ .../org/utbot/fuzzer/CartesianProduct.kt | 25 +- .../utbot/fuzzer/ConstantsModelProvider.kt | 25 -- .../utbot/fuzzer/FuzzedMethodDescription.kt | 22 +- .../main/kotlin/org/utbot/fuzzer/Fuzzer.kt | 10 +- .../kotlin/org/utbot/fuzzer/ModelProvider.kt | 41 ++- .../AbstractModelProvider.kt} | 16 +- .../providers/ConstantsModelProvider.kt | 47 ++++ .../{ => providers}/NullModelProvider.kt | 4 +- .../{ => providers}/ObjectModelProvider.kt | 17 +- .../PrimitivesModelProvider.kt | 34 +-- .../providers/StringConstantModelProvider.kt | 48 ++++ .../framework/plugin/api/ModelProviderTest.kt | 259 ++++++++++++++++++ 16 files changed, 845 insertions(+), 282 deletions(-) create mode 100644 utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt create mode 100644 utbot-framework/src/main/kotlin/org/utbot/fuzzer/SimpleModelProvider.kt delete mode 100644 utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ConstantsModelProvider.kt rename utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/{DefaultModelProvider.kt => providers/AbstractModelProvider.kt} (55%) create mode 100644 utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ConstantsModelProvider.kt rename utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/{ => providers}/NullModelProvider.kt (85%) rename utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/{ => providers}/ObjectModelProvider.kt (87%) rename utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/{ => providers}/PrimitivesModelProvider.kt (88%) create mode 100644 utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/StringConstantModelProvider.kt create mode 100644 utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt index 38ed305156..39da879aa6 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt @@ -2,10 +2,8 @@ package org.utbot.framework import org.utbot.common.PathUtil.toPath import java.io.FileInputStream -import java.io.FileNotFoundException import java.io.IOException import java.util.Properties -import kotlin.io.path.exists import kotlin.properties.PropertyDelegateProvider import kotlin.reflect.KProperty import mu.KotlinLogging @@ -239,6 +237,11 @@ object UtSettings { */ var useFuzzing: Boolean by getBooleanProperty(false) + /** + * Set the total attempts to improve coverage by fuzzer. + */ + var fuzzingMaxAttemps: Int by getIntProperty(Int.MAX_VALUE) + /** * Generate tests that treat possible overflows in arithmetic operations as errors * that throw Arithmetic Exception. 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 bfb5f5de90..a9c8a2ddd8 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -1,5 +1,22 @@ package org.utbot.engine +import kotlinx.collections.immutable.persistentHashMapOf +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentSetOf +import kotlinx.collections.immutable.toPersistentList +import kotlinx.collections.immutable.toPersistentMap +import kotlinx.collections.immutable.toPersistentSet +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Job +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.isActive +import kotlinx.coroutines.yield +import mu.KotlinLogging import org.utbot.analytics.Predictors import org.utbot.common.WorkaroundReason.HACK import org.utbot.common.WorkaroundReason.REMOVE_ANONYMOUS_CLASSES @@ -13,6 +30,7 @@ import org.utbot.engine.MockStrategy.NO_MOCKS import org.utbot.engine.overrides.UtArrayMock import org.utbot.engine.overrides.UtLogicMock import org.utbot.engine.overrides.UtOverrideMock +import org.utbot.engine.pc.NotBoolExpression import org.utbot.engine.pc.UtAddNoOverflowExpression import org.utbot.engine.pc.UtAddrExpression import org.utbot.engine.pc.UtAndBoolExpression @@ -38,7 +56,6 @@ import org.utbot.engine.pc.UtIteExpression import org.utbot.engine.pc.UtLongSort import org.utbot.engine.pc.UtMkTermArrayExpression import org.utbot.engine.pc.UtNegExpression -import org.utbot.engine.pc.NotBoolExpression import org.utbot.engine.pc.UtOrBoolExpression import org.utbot.engine.pc.UtPrimitiveSort import org.utbot.engine.pc.UtShortSort @@ -92,94 +109,47 @@ import org.utbot.framework.UtSettings.useDebugVisualization import org.utbot.framework.concrete.UtConcreteExecutionData import org.utbot.framework.concrete.UtConcreteExecutionResult import org.utbot.framework.concrete.UtExecutionInstrumentation -import org.utbot.framework.concrete.UtModelConstructor import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ConcreteExecutionFailureException import org.utbot.framework.plugin.api.EnvironmentModels import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.Instruction import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.MissingState import org.utbot.framework.plugin.api.Step -import org.utbot.framework.plugin.api.UtArrayModel -import org.utbot.framework.plugin.api.UtAssembleModel -import org.utbot.framework.plugin.api.UtCompositeModel import org.utbot.framework.plugin.api.UtConcreteExecutionFailure import org.utbot.framework.plugin.api.UtError -import org.utbot.framework.plugin.api.UtExecutableCallModel import org.utbot.framework.plugin.api.UtExecution import org.utbot.framework.plugin.api.UtInstrumentation import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtOverflowFailure import org.utbot.framework.plugin.api.UtResult -import org.utbot.framework.plugin.api.UtStatementModel import org.utbot.framework.plugin.api.classId import org.utbot.framework.plugin.api.graph import org.utbot.framework.plugin.api.id import org.utbot.framework.plugin.api.onSuccess -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.defaultValueModel import org.utbot.framework.plugin.api.util.executableId -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.isArray -import org.utbot.framework.plugin.api.util.isIterable -import org.utbot.framework.plugin.api.util.isPrimitive import org.utbot.framework.plugin.api.util.jClass -import org.utbot.framework.plugin.api.util.kClass -import org.utbot.framework.plugin.api.util.shortClassId import org.utbot.framework.plugin.api.util.signature import org.utbot.framework.plugin.api.util.utContext import org.utbot.framework.util.description import org.utbot.framework.util.executableId -import org.utbot.fuzzer.FuzzedConcreteValue import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.ModelProvider +import org.utbot.fuzzer.SimpleModelProvider +import org.utbot.fuzzer.collectConstantsForFuzzer import org.utbot.fuzzer.defaultModelProviders import org.utbot.fuzzer.fuzz import org.utbot.instrumentation.ConcreteExecutor -import java.lang.reflect.Method -import java.lang.reflect.ParameterizedType -import java.util.BitSet -import java.util.IdentityHashMap -import java.util.TreeSet -import kotlin.collections.plus -import kotlin.collections.plusAssign -import kotlin.math.max -import kotlin.math.min -import kotlin.reflect.KClass -import kotlin.reflect.full.instanceParameter -import kotlin.reflect.full.valueParameters -import kotlin.reflect.jvm.javaType -import kotlinx.collections.immutable.persistentHashMapOf -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.persistentSetOf -import kotlinx.collections.immutable.toPersistentList -import kotlinx.collections.immutable.toPersistentMap -import kotlinx.collections.immutable.toPersistentSet -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Job -import kotlinx.coroutines.currentCoroutineContext -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.FlowCollector -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.onCompletion -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.isActive -import kotlinx.coroutines.yield -import mu.KotlinLogging import soot.ArrayType -import soot.Body import soot.BooleanType import soot.ByteType import soot.CharType import soot.DoubleType import soot.FloatType import soot.IntType -import soot.Local import soot.LongType import soot.PrimType import soot.RefLikeType @@ -262,6 +232,15 @@ import sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl import sun.reflect.generics.reflectiveObjects.TypeVariableImpl import sun.reflect.generics.reflectiveObjects.WildcardTypeImpl +import java.lang.reflect.Method +import java.lang.reflect.ParameterizedType +import kotlin.collections.plus +import kotlin.collections.plusAssign +import kotlin.math.max +import kotlin.math.min +import kotlin.reflect.full.instanceParameter +import kotlin.reflect.full.valueParameters +import kotlin.reflect.jvm.javaType private val logger = KotlinLogging.logger {} val pathLogger = KotlinLogging.logger(logger.name + ".path") @@ -534,7 +513,7 @@ class UtBotSymbolicEngine( //Simple fuzzing - fun fuzzing() = flow { + fun fuzzing(modelProvider: ModelProvider = defaultModelProviders { nextDefaultModelId++ }) = flow { if (!UtSettings.useFuzzing) return@flow @@ -552,6 +531,8 @@ class UtBotSymbolicEngine( return@flow } + val simpleModelProvider = SimpleModelProvider { nextDefaultModelId++ } + val thisInstance = when { methodUnderTest.isStatic -> null methodUnderTest.isConstructor -> if ( @@ -563,7 +544,7 @@ class UtBotSymbolicEngine( null } else -> { - createSimpleModelByKClass(methodUnderTest.clazz).apply { + simpleModelProvider.toModel(methodUnderTest.clazz).apply { if (this is UtNullModel) { // it will definitely fail because of NPE, return@flow } @@ -571,9 +552,11 @@ class UtBotSymbolicEngine( } } - val methodUnderTestDescription = FuzzedMethodDescription(executableId, collectConstantsForFuzzer(graph.body)) - val modelProvider = defaultModelProviders { nextDefaultModelId++ }.withFallback { createModelByClassId(it) } - fuzz(methodUnderTestDescription, modelProvider).forEachIndexed { index, parameters -> + val methodUnderTestDescription = FuzzedMethodDescription(executableId, collectConstantsForFuzzer(graph)) + val modelProviderWithFallback = modelProvider.withFallback(simpleModelProvider::toModel) + val coveredInstructionTracker = mutableSetOf() + var attempts = UtSettings.fuzzingMaxAttemps + fuzz(methodUnderTestDescription, modelProviderWithFallback).forEachIndexed { index, parameters -> val initialEnvironmentModels = EnvironmentModels(thisInstance, parameters, mapOf()) try { @@ -589,6 +572,12 @@ class UtBotSymbolicEngine( } } + if (!coveredInstructionTracker.addAll(concreteExecutionResult.coverage.coveredInstructions)) { + if (--attempts < 0) { + return@flow + } + } + emit( UtExecution( stateBefore = initialEnvironmentModels, @@ -611,86 +600,6 @@ class UtBotSymbolicEngine( } } - /** - * Finds constant values in method body. - * - * todo move to utbot-fuzzer module [module should have access to some API to traverse through control-flow graph] - */ - private fun collectConstantsForFuzzer(body: Body): MutableList { - val constants = mutableListOf() - - body.units.forEach { unit -> - unit.useBoxes.asSequence().map { it.value }.filter { it is Constant || it is JCastExpr }.forEach { value -> - try { - var constant: FuzzedConcreteValue? = null - if (unit is JIfStmt && value is Constant) { - /* - * It is acceptable to check different types in if statement like ```2 == 2L```. - * - * Because of that fuzzer tries to find out the correct type between local and constant. - * Constant should be converted into type of local var in such way that if-statement can be true. - */ - val exactValue = value.javaClass.getField("value")[value] - val local = unit.conditionBox.value.useBoxes.map { it.value } - .filterIsInstance() - .takeIf { it.size == 1 } - ?.first() - if (local?.type != value.type && value.type is IntType) { - // Soot loads any integer type as an Int, - // therefore we try to guess target type using second value - // in the if statement - constant = when (local?.type) { - is CharType -> FuzzedConcreteValue(charClassId, (exactValue as Int).toChar()) - is BooleanType -> FuzzedConcreteValue(booleanClassId, (exactValue == 1)) - is ByteType -> FuzzedConcreteValue(byteClassId, (exactValue as Int).toByte()) - is ShortType -> FuzzedConcreteValue(shortClassId, (exactValue as Int).toShort()) - else -> null - } - } - } - /* - * The if-statement with 8-byte types like long and double doesn't use simple comparison, - * but cast and `cmp` method instead. This block tries to recognize this situation - * and cast wider type to more narrow one. - */ - if (value is JCastExpr) { - val next = graph.getSuccsOf(unit).takeIf { it.size == 1 }?.first() - if (next is JAssignStmt) { - val const = next.useBoxes.map { it.value } - .filterIsInstance() - .takeIf { it.size == 1 } - ?.first() - if (const != null) { - val exactValue = const.javaClass.getField("value")[const] as Number - constant = when (value.op.type) { - is ByteType -> FuzzedConcreteValue(byteClassId, exactValue.toByte()) - is ShortType -> FuzzedConcreteValue(shortClassId, exactValue.toShort()) - is IntType -> FuzzedConcreteValue(intClassId, exactValue.toInt()) - is FloatType -> FuzzedConcreteValue(floatClassId, exactValue.toFloat()) - else -> null - } - } - } - } - /* - * Load constant as is. - */ - if (constant == null && value is Constant) { - val exactValue = value.javaClass.getField("value")[value] - constant = FuzzedConcreteValue(value.type.classId, exactValue) - } - - if (constant != null) { - constants += constant - } - } catch (e: Exception) { - logger.warn(e) { "Cannot process constant value of type '${value.type}}'" } - } - } - } - return constants - } - private suspend fun FlowCollector.emitFailedConcreteExecutionResult( stateBefore: EnvironmentModels, e: ConcreteExecutionFailureException @@ -708,70 +617,6 @@ class UtBotSymbolicEngine( } - private fun createModelByClassId(classId: ClassId): UtModel { - val modelConstructor = UtModelConstructor(IdentityHashMap()) - val defaultConstructor = classId.jClass.constructors.firstOrNull { - it.parameters.isEmpty() && it.isPublic - } - return when { - classId.isPrimitive -> - classId.defaultValueModel() - classId.isArray -> - UtArrayModel( - id = nextDefaultModelId++, - classId, - length = 0, - classId.elementClassId!!.defaultValueModel(), - mutableMapOf() - ) - classId.isIterable -> { - val defaultInstance = when { - defaultConstructor != null -> defaultConstructor.newInstance() - classId.jClass.isAssignableFrom(java.util.ArrayList::class.java) -> ArrayList() - classId.jClass.isAssignableFrom(java.util.TreeSet::class.java) -> TreeSet() - classId.jClass.isAssignableFrom(java.util.HashMap::class.java) -> HashMap() - classId.jClass.isAssignableFrom(java.util.ArrayDeque::class.java) -> ArrayDeque() - classId.jClass.isAssignableFrom(java.util.BitSet::class.java) -> BitSet() - else -> null - } - if (defaultInstance != null) - modelConstructor.construct(defaultInstance, classId) - else - createSimpleModelByKClass(classId.kClass) - } - else -> - createSimpleModelByKClass(classId.kClass) - } - } - - private fun createSimpleModelByKClass(kclass: KClass<*>): UtModel { - val defaultConstructor = kclass.java.constructors.firstOrNull { - it.parameters.isEmpty() && it.isPublic // check constructor is public - } - return if (kclass.isAbstract) { // sealed class is abstract by itself - UtNullModel(kclass.java.id) - } else if (defaultConstructor != null) { - val chain = mutableListOf() - val model = UtAssembleModel( - id = nextDefaultModelId++, - kclass.id, - kclass.id.toString(), - chain - ) - chain.add( - UtExecutableCallModel(model, defaultConstructor.executableId, listOf(), model) - ) - model - } else { - UtCompositeModel( - id = nextDefaultModelId++, - kclass.id, - isMock = false - ) - } - } - - private suspend fun FlowCollector.traverseStmt(current: Stmt) { if (doPreparatoryWorkIfRequired(current)) return diff --git a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt new file mode 100644 index 0000000000..51737a2c09 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt @@ -0,0 +1,224 @@ +package org.utbot.fuzzer + +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.intClassId +import org.utbot.framework.plugin.api.util.longClassId +import org.utbot.framework.plugin.api.util.shortClassId +import org.utbot.framework.plugin.api.util.stringClassId +import mu.KotlinLogging +import soot.BooleanType +import soot.ByteType +import soot.CharType +import soot.DoubleType +import soot.FloatType +import soot.IntType +import soot.LongType +import soot.ShortType +import soot.Unit +import soot.Value +import soot.ValueBox +import soot.jimple.Constant +import soot.jimple.InvokeExpr +import soot.jimple.NullConstant +import soot.jimple.internal.ImmediateBox +import soot.jimple.internal.JAssignStmt +import soot.jimple.internal.JCastExpr +import soot.jimple.internal.JEqExpr +import soot.jimple.internal.JGeExpr +import soot.jimple.internal.JGtExpr +import soot.jimple.internal.JIfStmt +import soot.jimple.internal.JLeExpr +import soot.jimple.internal.JLtExpr +import soot.jimple.internal.JNeExpr +import soot.jimple.internal.JVirtualInvokeExpr +import soot.toolkits.graph.ExceptionalUnitGraph + +private val logger = KotlinLogging.logger {} + +/** + * Finds constant values in method body. + */ +fun collectConstantsForFuzzer(graph: ExceptionalUnitGraph): Set { + return graph.body.units.reversed().asSequence() + .filter { it is JIfStmt || it is JAssignStmt } + .flatMap { unit -> + unit.useBoxes.map { unit to it.value } + } + .filter { (_, value) -> + value is Constant || value is JCastExpr || value is InvokeExpr + } + .flatMap { (unit, value) -> + sequenceOf( + ::getConstantsFromIfStatement, + ::getConstantsFromCast, + ::getBoundValuesForDoubleChecks, + ::getStringConstant, + ).flatMap { getConstants -> + try { + getConstants(graph, unit, value) + } catch (e: Exception) { + logger.warn(e) { "Cannot process constant value of type '${value.type}}'" } + emptyList() + } + }.let { result -> + if (result.any()) result else { + getConstantsAsIs(graph, unit, value).asSequence() + } + } + }.toSet() +} + +private fun getConstantsFromIfStatement(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { + if (value !is Constant || (unit !is JIfStmt && unit !is JAssignStmt)) return emptyList() + + var useBoxes: List = emptyList() + var ifStatement: JIfStmt? = null + // simple if statement + if (unit is JIfStmt) { + useBoxes = unit.conditionBox.value.useBoxes.mapNotNull { (it as? ImmediateBox)?.value } + ifStatement = unit + } + // statement with double and long that consists of 2 units: + // 1. compare (result = local compare constant) + // 2. if result + if (unit is JAssignStmt) { + useBoxes = unit.rightOp.useBoxes.mapNotNull { (it as? ImmediateBox)?.value } + ifStatement = nextDirectUnit(graph, unit) as? JIfStmt + } + + /* + * It is acceptable to check different types in if statement like ```2 == 2L```. + * + * Because of that fuzzer tries to find out the correct type between local and constant. + * Constant should be converted into type of local var in such way that if-statement can be true. + */ + val valueIndex = useBoxes.indexOf(value) + if (useBoxes.size == 2 && valueIndex >= 0 && ifStatement != null) { + val exactValue = value.plainValue + val local = useBoxes[(valueIndex + 1) % 2] + var op = sootIfToFuzzedOp(ifStatement) + if (valueIndex == 0) { + op = reverse(op) + } + // Soot loads any integer type as an Int, + // therefore we try to guess target type using second value + // in the if statement + return listOfNotNull( + when (local.type) { + is CharType -> FuzzedConcreteValue(charClassId, (exactValue as Int).toChar(), op) + is BooleanType -> FuzzedConcreteValue(booleanClassId, (exactValue == 1), op) + is ByteType -> FuzzedConcreteValue(byteClassId, (exactValue as Int).toByte(), op) + is ShortType -> FuzzedConcreteValue(shortClassId, (exactValue as Int).toShort(), op) + is IntType -> FuzzedConcreteValue(intClassId, exactValue, op) + is LongType -> FuzzedConcreteValue(longClassId, exactValue, op) + is FloatType -> FuzzedConcreteValue(floatClassId, exactValue, op) + is DoubleType -> FuzzedConcreteValue(doubleClassId, exactValue, op) + else -> null + } + ) + } + return emptyList() +} + +/* + * The if-statement with 8-byte types like long and double doesn't use simple comparison, + * but cast and `cmp` method instead. This block tries to recognize this situation + * and cast wider type to more narrow one. + */ +private fun getConstantsFromCast(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { + if (value !is JCastExpr) return emptyList() + + val next = nextDirectUnit(graph, unit) + if (next is JAssignStmt) { + val const = next.useBoxes.findFirstInstanceOf() + if (const != null) { + val op = (nextDirectUnit(graph, next) as? JIfStmt)?.let(::sootIfToFuzzedOp) ?: FuzzedOp.NONE + val exactValue = const.plainValue as Number + return listOfNotNull( + when (value.op.type) { + is ByteType -> FuzzedConcreteValue(byteClassId, exactValue.toByte(), op) + is ShortType -> FuzzedConcreteValue(shortClassId, exactValue.toShort(), op) + is IntType -> FuzzedConcreteValue(intClassId, exactValue.toInt(), op) + is FloatType -> FuzzedConcreteValue(floatClassId, exactValue.toFloat(), op) + else -> null + } + ) + } + } + return emptyList() +} + +@Suppress("UNUSED_PARAMETER") +private fun getBoundValuesForDoubleChecks(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { + if (value !is InvokeExpr) return emptyList() + if (value.method.declaringClass.name != "java.lang.Double") return emptyList() + return when (value.method.name) { + "isNaN", "isInfinite", "isFinite" -> listOf( + FuzzedConcreteValue(doubleClassId, Double.POSITIVE_INFINITY), + FuzzedConcreteValue(doubleClassId, Double.NaN), + FuzzedConcreteValue(doubleClassId, 0.0), + ) + else -> emptyList() + } +} + +private fun getStringConstant(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { + if (unit !is JAssignStmt || value !is JVirtualInvokeExpr) return emptyList() + // if string constant is called from String class let's pass it as modification + if (value.method.declaringClass.name == "java.lang.String") { + val stringConstantWasPassedAsArg = unit.useBoxes.findFirstInstanceOf()?.plainValue + if (stringConstantWasPassedAsArg != null) { + return listOf(FuzzedConcreteValue(stringClassId, stringConstantWasPassedAsArg, FuzzedOp.CH)) + } + val stringConstantWasPassedAsThis = graph.getPredsOf(unit) + ?.filterIsInstance() + ?.firstOrNull() + ?.useBoxes + ?.findFirstInstanceOf() + ?.plainValue + if (stringConstantWasPassedAsThis != null) { + return listOf(FuzzedConcreteValue(stringClassId, stringConstantWasPassedAsThis, FuzzedOp.CH)) + } + } + return emptyList() +} + +@Suppress("UNUSED_PARAMETER") +private fun getConstantsAsIs(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { + if (value !is Constant || value is NullConstant) return emptyList() + return listOf(FuzzedConcreteValue(value.type.classId, value.plainValue)) +} + +private inline fun List.findFirstInstanceOf(): T? { + return map { it.value } + .filterIsInstance() + .firstOrNull() +} + +private val Constant.plainValue + get() = javaClass.getField("value")[this] + +private fun sootIfToFuzzedOp(unit: JIfStmt) = when (unit.condition) { + is JEqExpr -> FuzzedOp.NE + is JNeExpr -> FuzzedOp.EQ + is JGtExpr -> FuzzedOp.LE + is JGeExpr -> FuzzedOp.LT + is JLtExpr -> FuzzedOp.GE + is JLeExpr -> FuzzedOp.GT + else -> FuzzedOp.NONE +} + +private fun reverse(op: FuzzedOp) = when(op) { + FuzzedOp.GT -> FuzzedOp.LT + FuzzedOp.LT -> FuzzedOp.GT + FuzzedOp.LE -> FuzzedOp.GE + FuzzedOp.GE -> FuzzedOp.LE + else -> op +} + +private fun nextDirectUnit(graph: ExceptionalUnitGraph, unit: Unit): Unit? = graph.getSuccsOf(unit).takeIf { it.size == 1 }?.first() \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/SimpleModelProvider.kt b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/SimpleModelProvider.kt new file mode 100644 index 0000000000..175bf4de81 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/SimpleModelProvider.kt @@ -0,0 +1,101 @@ +package org.utbot.fuzzer + +import org.utbot.engine.isPublic +import org.utbot.framework.concrete.UtModelConstructor +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtStatementModel +import org.utbot.framework.plugin.api.util.defaultValueModel +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.isArray +import org.utbot.framework.plugin.api.util.isIterable +import org.utbot.framework.plugin.api.util.isPrimitive +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.kClass +import org.utbot.fuzzer.providers.AbstractModelProvider +import java.util.* +import java.util.function.IntSupplier +import kotlin.collections.ArrayDeque +import kotlin.collections.ArrayList +import kotlin.collections.HashMap +import kotlin.reflect.KClass + +open class SimpleModelProvider( + private val idGenerator: IntSupplier +): AbstractModelProvider() { + + override fun toModel(classId: ClassId): UtModel { + return createModelByClassId(classId) + } + + fun toModel(klazz: KClass<*>): UtModel = createSimpleModelByKClass(klazz) + + private fun createModelByClassId(classId: ClassId): UtModel { + val modelConstructor = UtModelConstructor(IdentityHashMap()) + val defaultConstructor = classId.jClass.constructors.firstOrNull { + it.parameters.isEmpty() && it.isPublic + } + return when { + classId.isPrimitive -> + classId.defaultValueModel() + classId.isArray -> + UtArrayModel( + id = idGenerator.asInt, + classId, + length = 0, + classId.elementClassId!!.defaultValueModel(), + mutableMapOf() + ) + classId.isIterable -> { + val defaultInstance = when { + defaultConstructor != null -> defaultConstructor.newInstance() + classId.jClass.isAssignableFrom(java.util.ArrayList::class.java) -> ArrayList() + classId.jClass.isAssignableFrom(java.util.TreeSet::class.java) -> TreeSet() + classId.jClass.isAssignableFrom(java.util.HashMap::class.java) -> HashMap() + classId.jClass.isAssignableFrom(java.util.ArrayDeque::class.java) -> ArrayDeque() + classId.jClass.isAssignableFrom(java.util.BitSet::class.java) -> BitSet() + else -> null + } + if (defaultInstance != null) + modelConstructor.construct(defaultInstance, classId) + else + createSimpleModelByKClass(classId.kClass) + } + else -> + createSimpleModelByKClass(classId.kClass) + } + } + + private fun createSimpleModelByKClass(kclass: KClass<*>): UtModel { + val defaultConstructor = kclass.java.constructors.firstOrNull { + it.parameters.isEmpty() && it.isPublic // check constructor is public + } + return if (kclass.isAbstract) { // sealed class is abstract by itself + UtNullModel(kclass.java.id) + } else if (defaultConstructor != null) { + val chain = mutableListOf() + val model = UtAssembleModel( + id = idGenerator.asInt, + kclass.id, + kclass.id.toString(), + chain + ) + chain.add( + UtExecutableCallModel(model, defaultConstructor.executableId, listOf(), model) + ) + model + } else { + UtCompositeModel( + id = idGenerator.asInt, + kclass.id, + isMock = false + ) + } + } +} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/CartesianProduct.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/CartesianProduct.kt index 83f1edbb23..28f16cef7b 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/CartesianProduct.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/CartesianProduct.kt @@ -1,18 +1,29 @@ package org.utbot.fuzzer +import kotlin.random.Random + /** * Creates iterable for all values of cartesian product of `lists`. */ -class CartesianProduct(private val lists: List>): Iterable> { +class CartesianProduct( + private val lists: List>, + private val random: Random? = null +): Iterable> { fun asSequence(): Sequence> = iterator().asSequence() override fun iterator(): Iterator> { - return Combinations(*lists.map { it.size }.toIntArray()) - .asSequence() - .map { combination -> - combination.mapIndexedTo(mutableListOf()) { element, value -> lists[element][value] } - } - .iterator() + val combinations = Combinations(*lists.map { it.size }.toIntArray()) + val sequence = if (random != null) { + // todo create lazy random algo for this because this method can cause OOME even we take only one value + val permutation = IntArray(combinations.size) { it } + permutation.shuffle(random) + permutation.asSequence().map(combinations::get) + } else { + combinations.asSequence() + } + return sequence.map { combination -> + combination.mapIndexedTo(mutableListOf()) { element, value -> lists[element][value] } + }.iterator() } } \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ConstantsModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ConstantsModelProvider.kt deleted file mode 100644 index 5b0106a748..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ConstantsModelProvider.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.utbot.fuzzer - -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.util.isPrimitive -import org.utbot.framework.plugin.api.util.stringClassId -import java.util.function.BiConsumer - -/** - * Traverses through method constants and creates appropriate models for them. - */ -object ConstantsModelProvider : ModelProvider { - - override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { - description.concreteValues - .asSequence() - .filter { (classId, _) -> classId.isPrimitive || classId == stringClassId } - .forEach { (_, value) -> - val model = UtPrimitiveModel(value) - description.parametersMap.getOrElse(model.classId) { emptyList() }.forEach { index -> - consumer.accept(index, model) - } - } - } -} \ 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 c7f106b4f6..70535c1e06 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt @@ -17,7 +17,7 @@ class FuzzedMethodDescription( val name: String, val returnType: ClassId, val parameters: List, - val concreteValues: List = emptyList() + val concreteValues: Collection = emptyList() ) { /** @@ -31,7 +31,7 @@ class FuzzedMethodDescription( result } - constructor(executableId: ExecutableId, concreteValues: List = emptyList()) : this( + constructor(executableId: ExecutableId, concreteValues: Collection = emptyList()) : this( executableId.name, executableId.returnType, executableId.parameters, @@ -44,5 +44,19 @@ class FuzzedMethodDescription( */ data class FuzzedConcreteValue( val classId: ClassId, - val value: Any -) \ No newline at end of file + val value: Any, + val relativeOp: FuzzedOp = FuzzedOp.NONE, +) +enum class FuzzedOp { + NONE, + EQ, + NE, + GT, + GE, + LT, + LE, + CH, // changed or called + ; + + fun isComparisonOp() = this == EQ || this == NE || this == GT || this == GE || this == LT || this == LE +} \ 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 8878725bd4..75a57d160d 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt @@ -3,9 +3,14 @@ package org.utbot.fuzzer import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.util.defaultValueModel +import org.utbot.fuzzer.providers.ConstantsModelProvider +import org.utbot.fuzzer.providers.ObjectModelProvider +import org.utbot.fuzzer.providers.PrimitivesModelProvider +import org.utbot.fuzzer.providers.StringConstantModelProvider import mu.KotlinLogging import java.util.concurrent.atomic.AtomicInteger import java.util.function.ToIntFunction +import kotlin.random.Random private val logger = KotlinLogging.logger {} @@ -23,13 +28,14 @@ fun fuzz(description: FuzzedMethodDescription, vararg modelProviders: ModelProvi models.add(classId.defaultValueModel()) } } - return CartesianProduct(values).asSequence() + return CartesianProduct(values, Random(0L)).asSequence() } fun defaultModelProviders(idGenerator: ToIntFunction = SimpleIdGenerator()): ModelProvider { return ObjectModelProvider(idGenerator) - .with(PrimitivesModelProvider) .with(ConstantsModelProvider) + .with(StringConstantModelProvider) + .with(PrimitivesModelProvider) } private class SimpleIdGenerator : ToIntFunction { 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 d7dec7d870..f5b6a62640 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt @@ -19,10 +19,26 @@ fun interface ModelProvider { * * This model provider is called before `anotherModelProvider`. */ - fun with(anotherModelProvider: ModelProvider) : ModelProvider { - return ModelProvider { description, consumer -> - this@ModelProvider.generate(description, consumer) - anotherModelProvider.generate(description, consumer) + fun with(anotherModelProvider: ModelProvider): ModelProvider { + fun toList(m: ModelProvider) = if (m is Combined) m.providers else listOf(m) + return Combined(toList(this) + toList(anotherModelProvider)) + } + + /** + * Removes `anotherModelProvider` from current one. + */ + fun except(anotherModelProvider: ModelProvider): ModelProvider { + return except { it == anotherModelProvider } + } + + /** + * Removes `anotherModelProvider` from current one. + */ + fun except(filter: (ModelProvider) -> Boolean): ModelProvider { + return if (this is Combined) { + Combined(providers.filterNot(filter)) + } else { + Combined(if (filter(this)) emptyList() else listOf(this)) } } @@ -54,4 +70,21 @@ fun interface ModelProvider { } } + companion object { + @JvmStatic + fun of(vararg providers: ModelProvider): ModelProvider { + return Combined(providers.toList()) + } + } + + /** + * Wrapper class that delegates implementation to the [providers]. + */ + private class Combined(val providers: List): ModelProvider { + override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { + providers.forEach { provider -> + provider.generate(description, consumer) + } + } + } } \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/DefaultModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/AbstractModelProvider.kt similarity index 55% rename from utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/DefaultModelProvider.kt rename to utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/AbstractModelProvider.kt index 625d1dfacb..7203060696 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/DefaultModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/AbstractModelProvider.kt @@ -1,26 +1,24 @@ -package org.utbot.fuzzer +package org.utbot.fuzzer.providers import org.utbot.framework.plugin.api.* +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.ModelProvider import java.util.function.BiConsumer -import java.util.function.Function /** * Simple model implementation. - * - * @param classToModel creates a list of [UtModel] for a particular class. */ @Suppress("unused") -class DefaultModelProvider( - private val classToModel: Function> -) : ModelProvider { +abstract class AbstractModelProvider: ModelProvider { override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { description.parametersMap.forEach { (classId, indices) -> - val defaultModels = classToModel.apply(classId) - defaultModels.forEach { defaultModel -> + toModel(classId)?.let { defaultModel -> indices.forEach { index -> consumer.accept(index, defaultModel) } } } } + + abstract fun toModel(classId: ClassId): UtModel? } \ 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 new file mode 100644 index 0000000000..1f4d1af4ae --- /dev/null +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ConstantsModelProvider.kt @@ -0,0 +1,47 @@ +package org.utbot.fuzzer.providers + +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.util.isPrimitive +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.FuzzedOp +import org.utbot.fuzzer.ModelProvider +import java.util.function.BiConsumer + +/** + * Traverses through method constants and creates appropriate models for them. + */ +object ConstantsModelProvider : ModelProvider { + + override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { + description.concreteValues + .asSequence() + .filter { (classId, _) -> classId.isPrimitive } + .forEach { (_, value, op) -> + sequenceOf(value, modifyValue(value, op)) + .filterNotNull() + .map(::UtPrimitiveModel) + .forEach { model -> + description.parametersMap.getOrElse(model.classId) { emptyList() }.forEach { index -> + consumer.accept(index, model) + } + } + } + } + + private fun modifyValue(value: Any, op: FuzzedOp): Any? { + if (!op.isComparisonOp()) return null + val multiplier = if (op == FuzzedOp.LT || op == FuzzedOp.GE) -1 else 1 + return when(value) { + is Boolean -> value.not() + is Byte -> value + multiplier.toByte() + is Char -> (value.toInt() + multiplier).toChar() + is Short -> value + multiplier.toShort() + is Int -> value + multiplier + is Long -> value + multiplier.toLong() + is Float -> value + multiplier.toDouble() + is Double -> value + multiplier.toDouble() + else -> null + } + } +} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/NullModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/NullModelProvider.kt similarity index 85% rename from utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/NullModelProvider.kt rename to utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/NullModelProvider.kt index 18d0135ad6..d2a1691ca1 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/NullModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/NullModelProvider.kt @@ -1,8 +1,10 @@ -package org.utbot.fuzzer +package org.utbot.fuzzer.providers import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.util.isRefType +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.ModelProvider import java.util.function.BiConsumer /** diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ObjectModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt similarity index 87% rename from utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ObjectModelProvider.kt rename to utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt index 5952091a25..04b361f28f 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ObjectModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt @@ -1,4 +1,4 @@ -package org.utbot.fuzzer +package org.utbot.fuzzer.providers import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ConstructorId @@ -8,6 +8,9 @@ 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.ModelProvider +import org.utbot.fuzzer.fuzz import java.lang.reflect.Constructor import java.lang.reflect.Modifier import java.lang.reflect.Parameter @@ -21,16 +24,18 @@ class ObjectModelProvider( private val idGenerator: ToIntFunction ) : ModelProvider { + var modelProvider: ModelProvider = ModelProvider.of(ConstantsModelProvider, StringConstantModelProvider, PrimitivesModelProvider) + override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { val assembleModels = with(description) { parameters.asSequence() .flatMap { classId -> collectConstructors(classId) { javaConstructor -> - isPublic(javaConstructor) && javaConstructor.parameters.all(::isPrimitiveOrString) + isPublic(javaConstructor) && javaConstructor.parameters.all(Companion::isPrimitiveOrString) } } .associateWith { - fuzzParameters(it) + fuzzParameters(it, modelProvider) } .flatMap { (constructorId, fuzzedParameters) -> fuzzedParameters.map { params -> @@ -64,15 +69,11 @@ class ObjectModelProvider( return parameterType.isPrimitive || String::class.java == parameterType } - private fun FuzzedMethodDescription.fuzzParameters(constructorId: ConstructorId): Sequence> { + private fun FuzzedMethodDescription.fuzzParameters(constructorId: ConstructorId, vararg modelProviders: ModelProvider): Sequence> { val fuzzedMethod = FuzzedMethodDescription( executableId = constructorId, concreteValues = this.concreteValues ) - val modelProviders = arrayOf( - PrimitivesModelProvider, - ConstantsModelProvider - ) return fuzz(fuzzedMethod, *modelProviders) } diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/PrimitivesModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitivesModelProvider.kt similarity index 88% rename from utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/PrimitivesModelProvider.kt rename to utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitivesModelProvider.kt index 5098b7ff83..e5c57ea27a 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/PrimitivesModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitivesModelProvider.kt @@ -1,8 +1,10 @@ -package org.utbot.fuzzer +package org.utbot.fuzzer.providers import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.util.* +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.ModelProvider import java.util.function.BiConsumer /** @@ -13,25 +15,24 @@ object PrimitivesModelProvider : ModelProvider { description.parametersMap.forEach { (classId, parameterIndices) -> val primitives = when (classId) { byteClassId -> listOf( + UtPrimitiveModel(0.toByte()), UtPrimitiveModel(1.toByte()), UtPrimitiveModel((-1).toByte()), - UtPrimitiveModel(Byte.MAX_VALUE), UtPrimitiveModel(Byte.MIN_VALUE), - UtPrimitiveModel(0.toByte()), + UtPrimitiveModel(Byte.MAX_VALUE), ) booleanClassId -> listOf(UtPrimitiveModel(false), UtPrimitiveModel(true)) charClassId -> listOf( UtPrimitiveModel('\\'), - UtPrimitiveModel('\t'), - UtPrimitiveModel('\n'), - UtPrimitiveModel('\u0000'), + UtPrimitiveModel(Char.MIN_VALUE), + UtPrimitiveModel(Char.MAX_VALUE), ) shortClassId -> listOf( + UtPrimitiveModel(0.toShort()), UtPrimitiveModel(1.toShort()), UtPrimitiveModel((-1).toShort()), UtPrimitiveModel(Short.MIN_VALUE), UtPrimitiveModel(Short.MAX_VALUE), - UtPrimitiveModel(0.toShort()), ) intClassId -> listOf( UtPrimitiveModel(1), @@ -41,39 +42,34 @@ object PrimitivesModelProvider : ModelProvider { UtPrimitiveModel(0), ) longClassId -> listOf( + UtPrimitiveModel(0L), UtPrimitiveModel(1L), UtPrimitiveModel(-1L), UtPrimitiveModel(Long.MIN_VALUE), UtPrimitiveModel(Long.MAX_VALUE), - UtPrimitiveModel(0L), ) floatClassId -> listOf( + UtPrimitiveModel(0.0f), UtPrimitiveModel(1.1f), UtPrimitiveModel(-1.1f), - UtPrimitiveModel(Float.POSITIVE_INFINITY), - UtPrimitiveModel(Float.NEGATIVE_INFINITY), UtPrimitiveModel(Float.MIN_VALUE), UtPrimitiveModel(Float.MAX_VALUE), + UtPrimitiveModel(Float.NEGATIVE_INFINITY), + UtPrimitiveModel(Float.POSITIVE_INFINITY), UtPrimitiveModel(Float.NaN), - UtPrimitiveModel(0.0f), ) doubleClassId -> listOf( + UtPrimitiveModel(0.0), UtPrimitiveModel(1.1), UtPrimitiveModel(-1.1), - UtPrimitiveModel(Double.POSITIVE_INFINITY), - UtPrimitiveModel(Double.NEGATIVE_INFINITY), UtPrimitiveModel(Double.MIN_VALUE), UtPrimitiveModel(Double.MAX_VALUE), + UtPrimitiveModel(Double.NEGATIVE_INFINITY), + UtPrimitiveModel(Double.POSITIVE_INFINITY), UtPrimitiveModel(Double.NaN), - UtPrimitiveModel(0.0), ) stringClassId -> listOf( UtPrimitiveModel(""), - UtPrimitiveModel(" "), - UtPrimitiveModel("nonemptystring"), - UtPrimitiveModel("multiline\n\rstring"), - UtPrimitiveModel("1"), - UtPrimitiveModel("False"), ) else -> listOf() } diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/StringConstantModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/StringConstantModelProvider.kt new file mode 100644 index 0000000000..74cfad6494 --- /dev/null +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/StringConstantModelProvider.kt @@ -0,0 +1,48 @@ +package org.utbot.fuzzer.providers + +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.FuzzedOp +import org.utbot.fuzzer.ModelProvider +import java.util.function.BiConsumer +import kotlin.random.Random + +object StringConstantModelProvider : ModelProvider { + + override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { + val random = Random(72923L) + description.concreteValues + .asSequence() + .filter { (classId, _) -> classId == stringClassId } + .forEach { (_, value, op) -> + listOf(value, mutate(random, value as? String, op)) + .asSequence() + .filterNotNull() + .map { UtPrimitiveModel(it) }.forEach { model -> + description.parametersMap.getOrElse(model.classId) { emptyList() }.forEach { index -> + consumer.accept(index, model) + } + } + } + } + + private fun mutate(random: Random, value: String?, op: FuzzedOp): String? { + if (value == null || value.isEmpty() || op != FuzzedOp.CH) return null + val indexOfMutation = random.nextInt(value.length) + return value.replaceRange(indexOfMutation, indexOfMutation + 1, SingleCharacterSequence(value[indexOfMutation] - random.nextInt(1, 128))) + } + + private class SingleCharacterSequence(private val character: Char) : CharSequence { + override val length: Int + get() = 1 + + override fun get(index: Int): Char = character + + override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { + throw UnsupportedOperationException() + } + + } +} \ 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 new file mode 100644 index 0000000000..f129643eb8 --- /dev/null +++ b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt @@ -0,0 +1,259 @@ +package org.utbot.framework.plugin.api + +import org.utbot.framework.plugin.api.util.UtContext +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.shortClassId +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.framework.plugin.api.util.voidClassId +import org.utbot.framework.plugin.api.util.withUtContext +import org.utbot.fuzzer.FuzzedConcreteValue +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.FuzzedOp +import org.utbot.fuzzer.ModelProvider +import org.utbot.fuzzer.providers.ConstantsModelProvider +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.Test +import java.util.Date + +class ModelProviderTest { + + @Test + fun `test generate primitive models for boolean`() { + val models = collect(PrimitivesModelProvider, + parameters = listOf(booleanClassId) + ) + + assertEquals(1, models.size) + assertEquals(2, models[0]!!.size) + assertTrue(models[0]!!.contains(UtPrimitiveModel(true))) + assertTrue(models[0]!!.contains(UtPrimitiveModel(false))) + } + + @Test + fun `test all known primitive types are generate at least one value`() { + val primitiveTypes = listOf( + byteClassId, + booleanClassId, + charClassId, + shortClassId, + intClassId, + longClassId, + floatClassId, + doubleClassId, + stringClassId, + ) + val models = collect(PrimitivesModelProvider, + parameters = primitiveTypes + ) + + assertEquals(primitiveTypes.size, models.size) + primitiveTypes.indices.forEach { + assertTrue(models[it]!!.isNotEmpty()) + } + } + + @Test + fun `test that empty constants don't generate any models`() { + val models = collect(ConstantsModelProvider, + parameters = listOf(intClassId), + constants = emptyList() + ) + + assertEquals(0, models.size) + } + + @Test + fun `test that one constant generate corresponding value`() { + val models = collect(ConstantsModelProvider, + parameters = listOf(intClassId), + constants = listOf( + FuzzedConcreteValue(intClassId, 123) + ) + ) + + assertEquals(1, models.size) + assertEquals(1, models[0]!!.size) + assertEquals(UtPrimitiveModel(123), models[0]!![0]) + assertEquals(intClassId, models[0]!![0].classId) + } + + @Test + fun `test that constants are mutated if comparison operation is set`() { + val models = collect(ConstantsModelProvider, + parameters = listOf(intClassId), + constants = listOf( + FuzzedConcreteValue(intClassId, 10, FuzzedOp.EQ), + FuzzedConcreteValue(intClassId, 20, FuzzedOp.NE), + FuzzedConcreteValue(intClassId, 30, FuzzedOp.LT), + FuzzedConcreteValue(intClassId, 40, FuzzedOp.LE), + FuzzedConcreteValue(intClassId, 50, FuzzedOp.GT), + FuzzedConcreteValue(intClassId, 60, FuzzedOp.GE), + ) + ) + + assertEquals(1, models.size) + val expectedValues = listOf(10, 11, 20, 21, 29, 30, 40, 41, 50, 51, 59, 60) + assertEquals(expectedValues.size, models[0]!!.size) + expectedValues.forEach { + assertTrue(models[0]!!.contains(UtPrimitiveModel(it))) + } + } + + @Test + fun `test constant empty string generates only corresponding model`() { + val models = collect(StringConstantModelProvider, + parameters = listOf(stringClassId), + constants = listOf( + FuzzedConcreteValue(stringClassId, "", FuzzedOp.CH), + ) + ) + + assertEquals(1, models.size) + assertEquals(1, models[0]!!.size) + assertEquals(UtPrimitiveModel(""), models[0]!![0]) + } + + @Test + fun `test non-empty string is not mutated if operation is not set`() { + val models = collect(StringConstantModelProvider, + parameters = listOf(stringClassId), + constants = listOf( + FuzzedConcreteValue(stringClassId, "nonemptystring", FuzzedOp.NONE), + ) + ) + + assertEquals(1, models.size) + assertEquals(1, models[0]!!.size) + assertEquals(UtPrimitiveModel("nonemptystring"), models[0]!![0]) + } + + @Test + fun `test non-empty string is mutated if modification operation is set`() { + val models = collect(StringConstantModelProvider, + parameters = listOf(stringClassId), + constants = listOf( + FuzzedConcreteValue(stringClassId, "nonemptystring", FuzzedOp.CH), + ) + ) + + assertEquals(1, models.size) + assertEquals(2, models[0]!!.size) + listOf("nonemptystring", "nonemptystr`ng").forEach { + assertTrue( models[0]!!.contains(UtPrimitiveModel(it))) { "Failed to find string $it in list ${models[0]}" } + } + } + + @Test + fun `test mutation creates the same values between different runs`() { + repeat(10) { + val models = collect(StringConstantModelProvider, + parameters = listOf(stringClassId), + constants = listOf( + FuzzedConcreteValue(stringClassId, "anotherstring", FuzzedOp.CH), + ) + ) + listOf("anotherstring", "anotherskring").forEach { + assertTrue( models[0]!!.contains(UtPrimitiveModel(it))) { "Failed to find string $it in list ${models[0]}" } + } + } + } + + @Test + @Suppress("unused", "UNUSED_PARAMETER", "RemoveEmptySecondaryConstructorBody") + fun `test default object model creation for simple constructors`() { + withUtContext(UtContext(this::class.java.classLoader)) { + class A { + constructor(a: Int) {} + constructor(a: Int, b: String) {} + constructor(a: Int, b: String, c: Boolean) + } + + val classId = A::class.java.id + val models = collect( + ObjectModelProvider { 0 }.apply { + modelProvider = ModelProvider.of(ConstantsModelProvider, StringConstantModelProvider) + }, + parameters = listOf(classId) + ) + + assertEquals(1, models.size) + assertEquals(3, models[0]!!.size) + assertTrue(models[0]!!.all { it is UtAssembleModel && it.classId == classId }) + + models[0]!!.filterIsInstance().forEachIndexed { index, model -> + assertEquals(1, model.instantiationChain.size) + val stm = model.instantiationChain[0] + assertTrue(stm is UtExecutableCallModel) + stm as UtExecutableCallModel + val paramCountInConstructorAsTheyListed = index + 1 + assertEquals(paramCountInConstructorAsTheyListed, stm.params.size) + } + } + } + + @Test + fun `test no object model is created for empty constructor`() { + withUtContext(UtContext(this::class.java.classLoader)) { + class A + + val classId = A::class.java.id + val models = collect( + ObjectModelProvider { 0 }, + parameters = listOf(classId) + ) + + assertEquals(0, models.size) + } + } + + @Test + @Suppress("unused", "UNUSED_PARAMETER") + fun `test that constructors with not primitive parameters are ignored`() { + withUtContext(UtContext(this::class.java.classLoader)) { + class A { + constructor(a: Int, b: Int) + constructor(a: Int, b: Date) + } + + val classId = A::class.java.id + val models = collect( + ObjectModelProvider { 0 }, + parameters = listOf(classId) + ) + + assertEquals(1, models.size) + assertTrue(models[0]!!.isNotEmpty()) + val chain = (models[0]!![0] as UtAssembleModel).instantiationChain + assertEquals(1, chain.size) + assertTrue(chain[0] is UtExecutableCallModel) + (chain[0] as UtExecutableCallModel).params.forEach { + assertEquals(intClassId, it.classId) + } + } + } + + private fun collect( + modelProvider: ModelProvider, + name: String = "testMethod", + returnType: ClassId = voidClassId, + parameters: List, + constants: List = emptyList() + ): Map> { + return mutableMapOf>().apply { + modelProvider.generate(FuzzedMethodDescription(name, returnType, parameters, constants)) { i, m -> + computeIfAbsent(i) { mutableListOf() }.add(m) + } + } + } + +} \ No newline at end of file From 9b07c0e635e4f0f5729840475b50488d7b98e50c Mon Sep 17 00:00:00 2001 From: Maxim Pelevin Date: Tue, 31 May 2022 11:05:59 +0300 Subject: [PATCH 2/2] Fuzzer improvements: * Rename SimpleModelProvider with more narrow name * Use interface implementation instead of function with concrete list of parameter * Fix typos --- .../org/utbot/engine/UtBotSymbolicEngine.kt | 8 +- ...elProvider.kt => FallbackModelProvider.kt} | 7 +- .../org/utbot/fuzzer/FuzzerFunctions.kt | 225 +++++++++--------- .../org/utbot/fuzzer/CartesianProduct.kt | 2 +- 4 files changed, 130 insertions(+), 112 deletions(-) rename utbot-framework/src/main/kotlin/org/utbot/fuzzer/{SimpleModelProvider.kt => FallbackModelProvider.kt} (95%) 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 a9c8a2ddd8..18bc138d74 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -138,7 +138,7 @@ import org.utbot.framework.util.description import org.utbot.framework.util.executableId import org.utbot.fuzzer.FuzzedMethodDescription import org.utbot.fuzzer.ModelProvider -import org.utbot.fuzzer.SimpleModelProvider +import org.utbot.fuzzer.FallbackModelProvider import org.utbot.fuzzer.collectConstantsForFuzzer import org.utbot.fuzzer.defaultModelProviders import org.utbot.fuzzer.fuzz @@ -531,7 +531,7 @@ class UtBotSymbolicEngine( return@flow } - val simpleModelProvider = SimpleModelProvider { nextDefaultModelId++ } + val fallbackModelProvider = FallbackModelProvider { nextDefaultModelId++ } val thisInstance = when { methodUnderTest.isStatic -> null @@ -544,7 +544,7 @@ class UtBotSymbolicEngine( null } else -> { - simpleModelProvider.toModel(methodUnderTest.clazz).apply { + fallbackModelProvider.toModel(methodUnderTest.clazz).apply { if (this is UtNullModel) { // it will definitely fail because of NPE, return@flow } @@ -553,7 +553,7 @@ class UtBotSymbolicEngine( } val methodUnderTestDescription = FuzzedMethodDescription(executableId, collectConstantsForFuzzer(graph)) - val modelProviderWithFallback = modelProvider.withFallback(simpleModelProvider::toModel) + val modelProviderWithFallback = modelProvider.withFallback(fallbackModelProvider::toModel) val coveredInstructionTracker = mutableSetOf() var attempts = UtSettings.fuzzingMaxAttemps fuzz(methodUnderTestDescription, modelProviderWithFallback).forEachIndexed { index, parameters -> diff --git a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/SimpleModelProvider.kt b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FallbackModelProvider.kt similarity index 95% rename from utbot-framework/src/main/kotlin/org/utbot/fuzzer/SimpleModelProvider.kt rename to utbot-framework/src/main/kotlin/org/utbot/fuzzer/FallbackModelProvider.kt index 175bf4de81..a7302603fd 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/SimpleModelProvider.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FallbackModelProvider.kt @@ -26,7 +26,12 @@ import kotlin.collections.ArrayList import kotlin.collections.HashMap import kotlin.reflect.KClass -open class SimpleModelProvider( +/** + * Provides some simple default models of any class. + * + * Used as a fallback implementation until other providers cover every type. + */ +open class FallbackModelProvider( private val idGenerator: IntSupplier ): AbstractModelProvider() { 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 51737a2c09..b4db7b094c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt @@ -54,144 +54,157 @@ fun collectConstantsForFuzzer(graph: ExceptionalUnitGraph): Set sequenceOf( - ::getConstantsFromIfStatement, - ::getConstantsFromCast, - ::getBoundValuesForDoubleChecks, - ::getStringConstant, - ).flatMap { getConstants -> + ConstantsFromIfStatement, + ConstantsFromCast, + BoundValuesForDoubleChecks, + StringConstant, + ).flatMap { finder -> try { - getConstants(graph, unit, value) + finder.find(graph, unit, value) } catch (e: Exception) { logger.warn(e) { "Cannot process constant value of type '${value.type}}'" } emptyList() } }.let { result -> if (result.any()) result else { - getConstantsAsIs(graph, unit, value).asSequence() + ConstantsAsIs.find(graph, unit, value).asSequence() } } }.toSet() } -private fun getConstantsFromIfStatement(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { - if (value !is Constant || (unit !is JIfStmt && unit !is JAssignStmt)) return emptyList() +private interface ConstantsFinder { + fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List +} - var useBoxes: List = emptyList() - var ifStatement: JIfStmt? = null - // simple if statement - if (unit is JIfStmt) { - useBoxes = unit.conditionBox.value.useBoxes.mapNotNull { (it as? ImmediateBox)?.value } - ifStatement = unit - } - // statement with double and long that consists of 2 units: - // 1. compare (result = local compare constant) - // 2. if result - if (unit is JAssignStmt) { - useBoxes = unit.rightOp.useBoxes.mapNotNull { (it as? ImmediateBox)?.value } - ifStatement = nextDirectUnit(graph, unit) as? JIfStmt - } +private object ConstantsFromIfStatement: ConstantsFinder { + override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { + if (value !is Constant || (unit !is JIfStmt && unit !is JAssignStmt)) return emptyList() - /* - * It is acceptable to check different types in if statement like ```2 == 2L```. - * - * Because of that fuzzer tries to find out the correct type between local and constant. - * Constant should be converted into type of local var in such way that if-statement can be true. - */ - val valueIndex = useBoxes.indexOf(value) - if (useBoxes.size == 2 && valueIndex >= 0 && ifStatement != null) { - val exactValue = value.plainValue - val local = useBoxes[(valueIndex + 1) % 2] - var op = sootIfToFuzzedOp(ifStatement) - if (valueIndex == 0) { - op = reverse(op) + var useBoxes: List = emptyList() + var ifStatement: JIfStmt? = null + // simple if statement + if (unit is JIfStmt) { + useBoxes = unit.conditionBox.value.useBoxes.mapNotNull { (it as? ImmediateBox)?.value } + ifStatement = unit + } + // statement with double and long that consists of 2 units: + // 1. compare (result = local compare constant) + // 2. if result + if (unit is JAssignStmt) { + useBoxes = unit.rightOp.useBoxes.mapNotNull { (it as? ImmediateBox)?.value } + ifStatement = nextDirectUnit(graph, unit) as? JIfStmt } - // Soot loads any integer type as an Int, - // therefore we try to guess target type using second value - // in the if statement - return listOfNotNull( - when (local.type) { - is CharType -> FuzzedConcreteValue(charClassId, (exactValue as Int).toChar(), op) - is BooleanType -> FuzzedConcreteValue(booleanClassId, (exactValue == 1), op) - is ByteType -> FuzzedConcreteValue(byteClassId, (exactValue as Int).toByte(), op) - is ShortType -> FuzzedConcreteValue(shortClassId, (exactValue as Int).toShort(), op) - is IntType -> FuzzedConcreteValue(intClassId, exactValue, op) - is LongType -> FuzzedConcreteValue(longClassId, exactValue, op) - is FloatType -> FuzzedConcreteValue(floatClassId, exactValue, op) - is DoubleType -> FuzzedConcreteValue(doubleClassId, exactValue, op) - else -> null - } - ) - } - return emptyList() -} -/* - * The if-statement with 8-byte types like long and double doesn't use simple comparison, - * but cast and `cmp` method instead. This block tries to recognize this situation - * and cast wider type to more narrow one. - */ -private fun getConstantsFromCast(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { - if (value !is JCastExpr) return emptyList() - - val next = nextDirectUnit(graph, unit) - if (next is JAssignStmt) { - val const = next.useBoxes.findFirstInstanceOf() - if (const != null) { - val op = (nextDirectUnit(graph, next) as? JIfStmt)?.let(::sootIfToFuzzedOp) ?: FuzzedOp.NONE - val exactValue = const.plainValue as Number + /* + * It is acceptable to check different types in if statement like ```2 == 2L```. + * + * Because of that fuzzer tries to find out the correct type between local and constant. + * Constant should be converted into type of local var in such way that if-statement can be true. + */ + val valueIndex = useBoxes.indexOf(value) + if (useBoxes.size == 2 && valueIndex >= 0 && ifStatement != null) { + val exactValue = value.plainValue + val local = useBoxes[(valueIndex + 1) % 2] + var op = sootIfToFuzzedOp(ifStatement) + if (valueIndex == 0) { + op = reverse(op) + } + // Soot loads any integer type as an Int, + // therefore we try to guess target type using second value + // in the if statement return listOfNotNull( - when (value.op.type) { - is ByteType -> FuzzedConcreteValue(byteClassId, exactValue.toByte(), op) - is ShortType -> FuzzedConcreteValue(shortClassId, exactValue.toShort(), op) - is IntType -> FuzzedConcreteValue(intClassId, exactValue.toInt(), op) - is FloatType -> FuzzedConcreteValue(floatClassId, exactValue.toFloat(), op) + when (local.type) { + is CharType -> FuzzedConcreteValue(charClassId, (exactValue as Int).toChar(), op) + is BooleanType -> FuzzedConcreteValue(booleanClassId, (exactValue == 1), op) + is ByteType -> FuzzedConcreteValue(byteClassId, (exactValue as Int).toByte(), op) + is ShortType -> FuzzedConcreteValue(shortClassId, (exactValue as Int).toShort(), op) + is IntType -> FuzzedConcreteValue(intClassId, exactValue, op) + is LongType -> FuzzedConcreteValue(longClassId, exactValue, op) + is FloatType -> FuzzedConcreteValue(floatClassId, exactValue, op) + is DoubleType -> FuzzedConcreteValue(doubleClassId, exactValue, op) else -> null } ) } + return emptyList() } - return emptyList() + } -@Suppress("UNUSED_PARAMETER") -private fun getBoundValuesForDoubleChecks(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { - if (value !is InvokeExpr) return emptyList() - if (value.method.declaringClass.name != "java.lang.Double") return emptyList() - return when (value.method.name) { - "isNaN", "isInfinite", "isFinite" -> listOf( - FuzzedConcreteValue(doubleClassId, Double.POSITIVE_INFINITY), - FuzzedConcreteValue(doubleClassId, Double.NaN), - FuzzedConcreteValue(doubleClassId, 0.0), - ) - else -> emptyList() +private object ConstantsFromCast: ConstantsFinder { + override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { + if (value !is JCastExpr) return emptyList() + + val next = nextDirectUnit(graph, unit) + if (next is JAssignStmt) { + val const = next.useBoxes.findFirstInstanceOf() + if (const != null) { + val op = (nextDirectUnit(graph, next) as? JIfStmt)?.let(::sootIfToFuzzedOp) ?: FuzzedOp.NONE + val exactValue = const.plainValue as Number + return listOfNotNull( + when (value.op.type) { + is ByteType -> FuzzedConcreteValue(byteClassId, exactValue.toByte(), op) + is ShortType -> FuzzedConcreteValue(shortClassId, exactValue.toShort(), op) + is IntType -> FuzzedConcreteValue(intClassId, exactValue.toInt(), op) + is FloatType -> FuzzedConcreteValue(floatClassId, exactValue.toFloat(), op) + else -> null + } + ) + } + } + return emptyList() } + } -private fun getStringConstant(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { - if (unit !is JAssignStmt || value !is JVirtualInvokeExpr) return emptyList() - // if string constant is called from String class let's pass it as modification - if (value.method.declaringClass.name == "java.lang.String") { - val stringConstantWasPassedAsArg = unit.useBoxes.findFirstInstanceOf()?.plainValue - if (stringConstantWasPassedAsArg != null) { - return listOf(FuzzedConcreteValue(stringClassId, stringConstantWasPassedAsArg, FuzzedOp.CH)) +private object BoundValuesForDoubleChecks: ConstantsFinder { + override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { + if (value !is InvokeExpr) return emptyList() + if (value.method.declaringClass.name != "java.lang.Double") return emptyList() + return when (value.method.name) { + "isNaN", "isInfinite", "isFinite" -> listOf( + FuzzedConcreteValue(doubleClassId, Double.POSITIVE_INFINITY), + FuzzedConcreteValue(doubleClassId, Double.NaN), + FuzzedConcreteValue(doubleClassId, 0.0), + ) + else -> emptyList() } - val stringConstantWasPassedAsThis = graph.getPredsOf(unit) - ?.filterIsInstance() - ?.firstOrNull() - ?.useBoxes - ?.findFirstInstanceOf() - ?.plainValue - if (stringConstantWasPassedAsThis != null) { - return listOf(FuzzedConcreteValue(stringClassId, stringConstantWasPassedAsThis, FuzzedOp.CH)) + } + +} + +private object StringConstant: ConstantsFinder { + override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { + if (unit !is JAssignStmt || value !is JVirtualInvokeExpr) return emptyList() + // if string constant is called from String class let's pass it as modification + if (value.method.declaringClass.name == "java.lang.String") { + val stringConstantWasPassedAsArg = unit.useBoxes.findFirstInstanceOf()?.plainValue + if (stringConstantWasPassedAsArg != null) { + return listOf(FuzzedConcreteValue(stringClassId, stringConstantWasPassedAsArg, FuzzedOp.CH)) + } + val stringConstantWasPassedAsThis = graph.getPredsOf(unit) + ?.filterIsInstance() + ?.firstOrNull() + ?.useBoxes + ?.findFirstInstanceOf() + ?.plainValue + if (stringConstantWasPassedAsThis != null) { + return listOf(FuzzedConcreteValue(stringClassId, stringConstantWasPassedAsThis, FuzzedOp.CH)) + } } + return emptyList() } - return emptyList() + } -@Suppress("UNUSED_PARAMETER") -private fun getConstantsAsIs(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { - if (value !is Constant || value is NullConstant) return emptyList() - return listOf(FuzzedConcreteValue(value.type.classId, value.plainValue)) +private object ConstantsAsIs: ConstantsFinder { + override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { + if (value !is Constant || value is NullConstant) return emptyList() + return listOf(FuzzedConcreteValue(value.type.classId, value.plainValue)) + + } + } private inline fun List.findFirstInstanceOf(): T? { diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/CartesianProduct.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/CartesianProduct.kt index 28f16cef7b..734ae8a34b 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/CartesianProduct.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/CartesianProduct.kt @@ -15,7 +15,7 @@ class CartesianProduct( override fun iterator(): Iterator> { val combinations = Combinations(*lists.map { it.size }.toIntArray()) val sequence = if (random != null) { - // todo create lazy random algo for this because this method can cause OOME even we take only one value + // todo create lazy random algo for this because this method can cause OOME even if we take only one value val permutation = IntArray(combinations.size) { it } permutation.shuffle(random) permutation.asSequence().map(combinations::get)