Skip to content

Fuzzing dates #929

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<Class<*>, ClassId> = mapOf(
java.lang.Void.TYPE to voidClassId,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -52,7 +54,7 @@ private val logger = KotlinLogging.logger {}
*/
fun collectConstantsForFuzzer(graph: ExceptionalUnitGraph): Set<FuzzedConcreteValue> {
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 }
}
Expand All @@ -67,6 +69,7 @@ fun collectConstantsForFuzzer(graph: ExceptionalUnitGraph): Set<FuzzedConcreteVa
BoundValuesForDoubleChecks,
StringConstant,
RegexByVarStringConstant,
DateFormatByVarStringConstant,
).flatMap { finder ->
try {
finder.find(graph, unit, value)
Expand Down Expand Up @@ -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<FuzzedConcreteValue> {
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<Constant>()?.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<FuzzedConcreteValue> {
if (value !is Constant || value is NullConstant) return emptyList()
Expand Down
3 changes: 3 additions & 0 deletions utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {} }

Expand Down Expand Up @@ -154,6 +155,7 @@ fun defaultModelProviders(idGenerator: IdentityPreservingIdGenerator<Int>): Mode
CollectionModelProvider(idGenerator),
ArrayModelProvider(idGenerator),
EnumModelProvider(idGenerator),
DateConstantModelProvider(idGenerator),
ConstantsModelProvider,
StringConstantModelProvider,
RegexModelProvider,
Expand All @@ -170,6 +172,7 @@ internal fun modelProviderForRecursiveCalls(idGenerator: IdentityPreservingIdGen
val nonRecursiveProviders = ModelProvider.of(
CollectionModelProvider(idGenerator),
EnumModelProvider(idGenerator),
DateConstantModelProvider(idGenerator),
StringConstantModelProvider,
CharToStringModelProvider,
ConstantsModelProvider,
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -33,4 +37,7 @@ fun Random.flipCoin(probability: Int): Boolean {

fun Long.invertBit(bitIndex: Int): Long {
return this xor (1L shl bitIndex)
}
}

fun Int.hex(): String =
toString(16)
Original file line number Diff line number Diff line change
@@ -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>): FuzzedValue {
val instantiationChain = mutableListOf<UtStatementModel>()
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 }})"
}
}
Original file line number Diff line number Diff line change
@@ -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<Int>,
) : 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<FuzzedValue> {
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<FuzzedValue> {
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<FuzzedValue> {
val constructor = dateClassId.allConstructors.first { it.parameters.isEmpty() }
return sequenceOf(assembleModel(idGenerator.createId(), constructor, emptyList()))
}

override fun generate(description: FuzzedMethodDescription): Sequence<FuzzedParameter> {
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<ClassId>,
baseMethodDescription: FuzzedMethodDescription,
modelProvider: ModelProvider,
): Sequence<List<FuzzedValue>> {
if (types.isEmpty())
return sequenceOf(listOf())
val syntheticMethodDescription = FuzzedMethodDescription(
"<synthetic method for DateModelProvider>", // 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<UtStatementModel>()
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<UtStatementModel>()
val modificationsChain = mutableListOf<UtStatementModel>()
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))
)
}
}

}
Loading