diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt index 7551e0770f..b7ac0130b8 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt @@ -213,6 +213,8 @@ val atomicIntegerGetAndIncrement = MethodId(atomicIntegerClassId, "getAndIncreme val iterableClassId = java.lang.Iterable::class.id val mapClassId = java.util.Map::class.id +val dateClassId = java.util.Date::class.id + @Suppress("RemoveRedundantQualifierName") val primitiveToId: Map, ClassId> = mapOf( java.lang.Void.TYPE to voidClassId, 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 7f82ce2cdd..32ddfd9701 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt @@ -1,5 +1,6 @@ package org.utbot.fuzzer +import mu.KotlinLogging import org.utbot.framework.plugin.api.classId import org.utbot.framework.plugin.api.util.booleanClassId import org.utbot.framework.plugin.api.util.byteClassId @@ -10,7 +11,6 @@ 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 org.utbot.framework.util.executableId import soot.BooleanType import soot.ByteType @@ -36,10 +36,12 @@ import soot.jimple.internal.JEqExpr import soot.jimple.internal.JGeExpr import soot.jimple.internal.JGtExpr import soot.jimple.internal.JIfStmt +import soot.jimple.internal.JInvokeStmt import soot.jimple.internal.JLeExpr import soot.jimple.internal.JLookupSwitchStmt import soot.jimple.internal.JLtExpr import soot.jimple.internal.JNeExpr +import soot.jimple.internal.JSpecialInvokeExpr import soot.jimple.internal.JStaticInvokeExpr import soot.jimple.internal.JTableSwitchStmt import soot.jimple.internal.JVirtualInvokeExpr @@ -52,7 +54,7 @@ private val logger = KotlinLogging.logger {} */ fun collectConstantsForFuzzer(graph: ExceptionalUnitGraph): Set { return graph.body.units.reversed().asSequence() - .filter { it is JIfStmt || it is JAssignStmt || it is AbstractSwitchStmt} + .filter { it is JIfStmt || it is JAssignStmt || it is AbstractSwitchStmt || it is JInvokeStmt } .flatMap { unit -> unit.useBoxes.map { unit to it.value } } @@ -67,6 +69,7 @@ fun collectConstantsForFuzzer(graph: ExceptionalUnitGraph): Set try { finder.find(graph, unit, value) @@ -242,6 +245,24 @@ private object RegexByVarStringConstant: ConstantsFinder { } } +/** + * Finds strings that are used inside DateFormat's constructors. + * + * Due to compiler optimizations it should work when a string is assigned to a variable or static final field. + */ +private object DateFormatByVarStringConstant: ConstantsFinder { + override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { + if (unit !is JInvokeStmt || value !is JSpecialInvokeExpr) return emptyList() + if (value.method.isConstructor && value.method.declaringClass.name == "java.text.SimpleDateFormat") { + val stringConstantWasPassedAsArg = unit.useBoxes.findFirstInstanceOf()?.plainValue + if (stringConstantWasPassedAsArg != null && stringConstantWasPassedAsArg is String) { + return listOf(FuzzedConcreteValue(stringClassId, stringConstantWasPassedAsArg, FuzzedContext.Call(value.method.executableId))) + } + } + return emptyList() + } +} + private object ConstantsAsIs: ConstantsFinder { override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { if (value !is Constant || value is NullConstant) return emptyList() 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 6b6d813af4..9cfcab9cb0 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt @@ -18,6 +18,7 @@ import org.utbot.fuzzer.providers.StringConstantModelProvider import java.util.* import java.util.concurrent.atomic.AtomicInteger import kotlin.random.Random +import org.utbot.fuzzer.providers.DateConstantModelProvider private val logger by lazy { KotlinLogging.logger {} } @@ -154,6 +155,7 @@ fun defaultModelProviders(idGenerator: IdentityPreservingIdGenerator): Mode CollectionModelProvider(idGenerator), ArrayModelProvider(idGenerator), EnumModelProvider(idGenerator), + DateConstantModelProvider(idGenerator), ConstantsModelProvider, StringConstantModelProvider, RegexModelProvider, @@ -170,6 +172,7 @@ internal fun modelProviderForRecursiveCalls(idGenerator: IdentityPreservingIdGen val nonRecursiveProviders = ModelProvider.of( CollectionModelProvider(idGenerator), EnumModelProvider(idGenerator), + DateConstantModelProvider(idGenerator), StringConstantModelProvider, CharToStringModelProvider, ConstantsModelProvider, diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/RandomExtensions.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/RandomExtensions.kt index 61e022bb4d..c9eb21f9b5 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/RandomExtensions.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/RandomExtensions.kt @@ -1,6 +1,10 @@ package org.utbot.fuzzer import kotlin.random.Random +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtStatementModel /** * Chooses a random value using frequencies. @@ -33,4 +37,7 @@ fun Random.flipCoin(probability: Int): Boolean { fun Long.invertBit(bitIndex: Int): Long { return this xor (1L shl bitIndex) -} \ No newline at end of file +} + +fun Int.hex(): String = + toString(16) diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/objects/AssembleModelUtils.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/objects/AssembleModelUtils.kt new file mode 100644 index 0000000000..d030b97f90 --- /dev/null +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/objects/AssembleModelUtils.kt @@ -0,0 +1,25 @@ +package org.utbot.fuzzer.objects + +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtStatementModel +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.ModelProvider +import org.utbot.fuzzer.hex + + +fun ModelProvider.assembleModel(id: Int, constructorId: ConstructorId, params: List): FuzzedValue { + val instantiationChain = mutableListOf() + return UtAssembleModel( + id, + constructorId.classId, + "${constructorId.classId.name}${constructorId.parameters}#" + id.hex(), + instantiationChain = instantiationChain, + modificationsChain = mutableListOf() + ).apply { + instantiationChain += UtExecutableCallModel(null, constructorId, params.map { it.model }, this) + }.fuzzed { + summary = "%var% = ${constructorId.classId.simpleName}(${constructorId.parameters.joinToString { it.simpleName }})" + } +} diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/DateConstantModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/DateConstantModelProvider.kt new file mode 100644 index 0000000000..6fab59750b --- /dev/null +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/DateConstantModelProvider.kt @@ -0,0 +1,180 @@ +package org.utbot.fuzzer.providers + +import java.text.SimpleDateFormat +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.UtStatementModel +import org.utbot.framework.plugin.api.util.dateClassId +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.longClassId +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.framework.plugin.api.util.voidClassId +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.FuzzedParameter +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.IdentityPreservingIdGenerator +import org.utbot.fuzzer.ModelProvider +import org.utbot.fuzzer.ModelProvider.Companion.yieldAllValues +import org.utbot.fuzzer.defaultModelProviders +import org.utbot.fuzzer.fuzz +import org.utbot.fuzzer.hex +import org.utbot.fuzzer.objects.assembleModel + +class DateConstantModelProvider( + private val idGenerator: IdentityPreservingIdGenerator, +) : ModelProvider { + + var totalLimit: Int = 20 + + companion object { + private const val defaultDateFormat = "dd-MM-yyyy HH:mm:ss:ms" + } + + private fun String.isDate(format: String): Boolean { + val formatter = SimpleDateFormat(format).apply { + isLenient = false + } + return runCatching { formatter.parse(trim()) }.isSuccess + } + + private fun String.isDateFormat(): Boolean { + return none { it.isDigit() } && // fixes concrete date values + runCatching { SimpleDateFormat(this) }.isSuccess + } + + private fun generateFromNumbers( + baseMethodDescription: FuzzedMethodDescription, + ): Sequence { + val constructorsFromNumbers = dateClassId.allConstructors + .filter { constructor -> + constructor.parameters.isNotEmpty() && + constructor.parameters.all { it == intClassId || it == longClassId } + }.map { constructorId -> + with(constructorId) { + ModelConstructor(parameters) { assembleModel(idGenerator.createId(), constructorId, it) } + } + }.sortedBy { it.neededTypes.size } + + return sequence { + constructorsFromNumbers.forEach { constructor -> + yieldAll( + fuzzValues( + constructor.neededTypes, + baseMethodDescription, + defaultModelProviders(idGenerator) + ).map(constructor.createModel) + ) + } + } + } + + private fun generateFromDates( + baseMethodDescription: FuzzedMethodDescription, + ): Sequence { + val strings = baseMethodDescription.concreteValues + .asSequence() + .filter { it.classId == stringClassId } + .map { it.value as String } + .distinct() + val formats = strings.filter { it.isDateFormat() } + defaultDateFormat + val formatToDates = formats.associateWith { format -> strings.filter { it.isDate(format) } } + + return sequence { + formatToDates.forEach { (format, dates) -> + dates.forEach { date -> + yield(assembleDateFromString(idGenerator.createId(), format, date)) + } + } + } + } + + private fun generateNowDate(): Sequence { + val constructor = dateClassId.allConstructors.first { it.parameters.isEmpty() } + return sequenceOf(assembleModel(idGenerator.createId(), constructor, emptyList())) + } + + override fun generate(description: FuzzedMethodDescription): Sequence { + val parameters = description.parametersMap[dateClassId] + if (parameters.isNullOrEmpty()) { + return emptySequence() + } + + return sequence { + yieldAllValues( + parameters, + generateNowDate() + generateFromDates(description) + + generateFromNumbers(description).take(totalLimit) + ) + }.take(totalLimit) + } + + private fun fuzzValues( + types: List, + baseMethodDescription: FuzzedMethodDescription, + modelProvider: ModelProvider, + ): Sequence> { + if (types.isEmpty()) + return sequenceOf(listOf()) + val syntheticMethodDescription = FuzzedMethodDescription( + "", // TODO: maybe add more info here + voidClassId, + types, + baseMethodDescription.concreteValues + ).apply { + packageName = baseMethodDescription.packageName + } + return fuzz(syntheticMethodDescription, modelProvider) + } + + private fun assembleDateFromString(id: Int, formatString: String, dateString: String): FuzzedValue { + val simpleDateFormatModel = assembleSimpleDateFormat(idGenerator.createId(), formatString) + val dateFormatParse = simpleDateFormatModel.classId.jClass + .getMethod("parse", String::class.java).executableId + val instantiationChain = mutableListOf() + return UtAssembleModel( + id, + dateClassId, + "$dateFormatParse#" + id.hex(), + instantiationChain + ).apply { + instantiationChain += simpleDateFormatModel.allStatementsChain + instantiationChain += UtExecutableCallModel( + simpleDateFormatModel, dateFormatParse, listOf(UtPrimitiveModel(dateString)), returnValue = this + ) + }.fuzzed { + summary = "%var% = $dateFormatParse($stringClassId)" + } + } + + private fun assembleSimpleDateFormat(id: Int, formatString: String): UtAssembleModel { + val simpleDateFormatId = SimpleDateFormat::class.java.id + val formatStringConstructor = simpleDateFormatId.allConstructors.first { + it.parameters.singleOrNull() == stringClassId + } + val formatSetLenient = SimpleDateFormat::setLenient.executableId + val formatModel = UtPrimitiveModel(formatString) + + val instantiationChain = mutableListOf() + val modificationsChain = mutableListOf() + return UtAssembleModel( + id, + simpleDateFormatId, + "$simpleDateFormatId[$stringClassId]#" + id.hex(), + instantiationChain, + modificationsChain + ).apply { + instantiationChain += UtExecutableCallModel( + instance = null, formatStringConstructor, listOf(formatModel), returnValue = this + ) + modificationsChain += UtExecutableCallModel( + instance = this, formatSetLenient, listOf(UtPrimitiveModel(false)) + ) + } + } + +} diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt index 1bfb616316..656af81ed9 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt @@ -1,5 +1,14 @@ package org.utbot.fuzzer.providers +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.isFinal +import java.lang.reflect.Modifier.isPrivate +import java.lang.reflect.Modifier.isProtected +import java.lang.reflect.Modifier.isPublic +import java.lang.reflect.Modifier.isStatic import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ConstructorId import org.utbot.framework.plugin.api.FieldId @@ -7,22 +16,17 @@ import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtDirectSetFieldModel import org.utbot.framework.plugin.api.UtExecutableCallModel -import org.utbot.framework.plugin.api.UtStatementModel +import org.utbot.framework.plugin.api.util.dateClassId import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.isEnum import org.utbot.framework.plugin.api.util.isPrimitive import org.utbot.framework.plugin.api.util.isPrimitiveWrapper import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.stringClassId -import org.utbot.fuzzer.IdentityPreservingIdGenerator import org.utbot.fuzzer.FuzzedMethodDescription import org.utbot.fuzzer.FuzzedValue -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.* -import org.utbot.framework.plugin.api.util.isEnum +import org.utbot.fuzzer.IdentityPreservingIdGenerator +import org.utbot.fuzzer.objects.assembleModel /** * Creates [UtAssembleModel] for objects which have public constructors @@ -43,8 +47,9 @@ class ObjectModelProvider( description: FuzzedMethodDescription, classId: ClassId ): List { - if (classId == stringClassId || classId.isPrimitiveWrapper || classId.isEnum || classId.isAbstract) - return listOf() + if (unwantedConstructorsClasses.contains(classId) || + classId.isPrimitiveWrapper || classId.isEnum || classId.isAbstract + ) return listOf() val constructors = collectConstructors(classId) { javaConstructor -> isAccessible(javaConstructor, description.packageName) @@ -109,6 +114,11 @@ class ObjectModelProvider( } companion object { + + private val unwantedConstructorsClasses = listOf( + stringClassId, dateClassId + ) + private fun collectConstructors(classId: ClassId, predicate: (Constructor<*>) -> Boolean): Sequence { return classId.jClass.declaredConstructors.asSequence() .filter(predicate) @@ -129,21 +139,6 @@ class ObjectModelProvider( return !hasAnyAccessModifier } - private fun assembleModel(id: Int, constructorId: ConstructorId, params: List): FuzzedValue { - val instantiationChain = mutableListOf() - return UtAssembleModel( - id, - constructorId.classId, - "${constructorId.classId.name}${constructorId.parameters}#" + id.toString(16), - instantiationChain = instantiationChain, - modificationsChain = mutableListOf() - ).apply { - instantiationChain += UtExecutableCallModel(null, constructorId, params.map { it.model }, this) - }.fuzzed { - summary = "%var% = ${constructorId.classId.simpleName}(${constructorId.parameters.joinToString { it.simpleName }})" - } - } - private fun findSuitableFields(classId: ClassId, description: FuzzedMethodDescription): List { val jClass = classId.jClass return jClass.declaredFields.map { field ->