Skip to content

Commit eb1bb8a

Browse files
authored
Fuzzing dates (#929)
1 parent a57281d commit eb1bb8a

File tree

7 files changed

+262
-29
lines changed

7 files changed

+262
-29
lines changed

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ val atomicIntegerGetAndIncrement = MethodId(atomicIntegerClassId, "getAndIncreme
213213
val iterableClassId = java.lang.Iterable::class.id
214214
val mapClassId = java.util.Map::class.id
215215

216+
val dateClassId = java.util.Date::class.id
217+
216218
@Suppress("RemoveRedundantQualifierName")
217219
val primitiveToId: Map<Class<*>, ClassId> = mapOf(
218220
java.lang.Void.TYPE to voidClassId,

utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.utbot.fuzzer
22

3+
import mu.KotlinLogging
34
import org.utbot.framework.plugin.api.classId
45
import org.utbot.framework.plugin.api.util.booleanClassId
56
import org.utbot.framework.plugin.api.util.byteClassId
@@ -10,7 +11,6 @@ import org.utbot.framework.plugin.api.util.intClassId
1011
import org.utbot.framework.plugin.api.util.longClassId
1112
import org.utbot.framework.plugin.api.util.shortClassId
1213
import org.utbot.framework.plugin.api.util.stringClassId
13-
import mu.KotlinLogging
1414
import org.utbot.framework.util.executableId
1515
import soot.BooleanType
1616
import soot.ByteType
@@ -36,10 +36,12 @@ import soot.jimple.internal.JEqExpr
3636
import soot.jimple.internal.JGeExpr
3737
import soot.jimple.internal.JGtExpr
3838
import soot.jimple.internal.JIfStmt
39+
import soot.jimple.internal.JInvokeStmt
3940
import soot.jimple.internal.JLeExpr
4041
import soot.jimple.internal.JLookupSwitchStmt
4142
import soot.jimple.internal.JLtExpr
4243
import soot.jimple.internal.JNeExpr
44+
import soot.jimple.internal.JSpecialInvokeExpr
4345
import soot.jimple.internal.JStaticInvokeExpr
4446
import soot.jimple.internal.JTableSwitchStmt
4547
import soot.jimple.internal.JVirtualInvokeExpr
@@ -52,7 +54,7 @@ private val logger = KotlinLogging.logger {}
5254
*/
5355
fun collectConstantsForFuzzer(graph: ExceptionalUnitGraph): Set<FuzzedConcreteValue> {
5456
return graph.body.units.reversed().asSequence()
55-
.filter { it is JIfStmt || it is JAssignStmt || it is AbstractSwitchStmt}
57+
.filter { it is JIfStmt || it is JAssignStmt || it is AbstractSwitchStmt || it is JInvokeStmt }
5658
.flatMap { unit ->
5759
unit.useBoxes.map { unit to it.value }
5860
}
@@ -67,6 +69,7 @@ fun collectConstantsForFuzzer(graph: ExceptionalUnitGraph): Set<FuzzedConcreteVa
6769
BoundValuesForDoubleChecks,
6870
StringConstant,
6971
RegexByVarStringConstant,
72+
DateFormatByVarStringConstant,
7073
).flatMap { finder ->
7174
try {
7275
finder.find(graph, unit, value)
@@ -242,6 +245,24 @@ private object RegexByVarStringConstant: ConstantsFinder {
242245
}
243246
}
244247

248+
/**
249+
* Finds strings that are used inside DateFormat's constructors.
250+
*
251+
* Due to compiler optimizations it should work when a string is assigned to a variable or static final field.
252+
*/
253+
private object DateFormatByVarStringConstant: ConstantsFinder {
254+
override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List<FuzzedConcreteValue> {
255+
if (unit !is JInvokeStmt || value !is JSpecialInvokeExpr) return emptyList()
256+
if (value.method.isConstructor && value.method.declaringClass.name == "java.text.SimpleDateFormat") {
257+
val stringConstantWasPassedAsArg = unit.useBoxes.findFirstInstanceOf<Constant>()?.plainValue
258+
if (stringConstantWasPassedAsArg != null && stringConstantWasPassedAsArg is String) {
259+
return listOf(FuzzedConcreteValue(stringClassId, stringConstantWasPassedAsArg, FuzzedContext.Call(value.method.executableId)))
260+
}
261+
}
262+
return emptyList()
263+
}
264+
}
265+
245266
private object ConstantsAsIs: ConstantsFinder {
246267
override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List<FuzzedConcreteValue> {
247268
if (value !is Constant || value is NullConstant) return emptyList()

utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import org.utbot.fuzzer.providers.StringConstantModelProvider
1818
import java.util.*
1919
import java.util.concurrent.atomic.AtomicInteger
2020
import kotlin.random.Random
21+
import org.utbot.fuzzer.providers.DateConstantModelProvider
2122

2223
private val logger by lazy { KotlinLogging.logger {} }
2324

@@ -154,6 +155,7 @@ fun defaultModelProviders(idGenerator: IdentityPreservingIdGenerator<Int>): Mode
154155
CollectionModelProvider(idGenerator),
155156
ArrayModelProvider(idGenerator),
156157
EnumModelProvider(idGenerator),
158+
DateConstantModelProvider(idGenerator),
157159
ConstantsModelProvider,
158160
StringConstantModelProvider,
159161
RegexModelProvider,
@@ -170,6 +172,7 @@ internal fun modelProviderForRecursiveCalls(idGenerator: IdentityPreservingIdGen
170172
val nonRecursiveProviders = ModelProvider.of(
171173
CollectionModelProvider(idGenerator),
172174
EnumModelProvider(idGenerator),
175+
DateConstantModelProvider(idGenerator),
173176
StringConstantModelProvider,
174177
CharToStringModelProvider,
175178
ConstantsModelProvider,

utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/RandomExtensions.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package org.utbot.fuzzer
22

33
import kotlin.random.Random
4+
import org.utbot.framework.plugin.api.ConstructorId
5+
import org.utbot.framework.plugin.api.UtAssembleModel
6+
import org.utbot.framework.plugin.api.UtExecutableCallModel
7+
import org.utbot.framework.plugin.api.UtStatementModel
48

59
/**
610
* Chooses a random value using frequencies.
@@ -33,4 +37,7 @@ fun Random.flipCoin(probability: Int): Boolean {
3337

3438
fun Long.invertBit(bitIndex: Int): Long {
3539
return this xor (1L shl bitIndex)
36-
}
40+
}
41+
42+
fun Int.hex(): String =
43+
toString(16)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.utbot.fuzzer.objects
2+
3+
import org.utbot.framework.plugin.api.ConstructorId
4+
import org.utbot.framework.plugin.api.UtAssembleModel
5+
import org.utbot.framework.plugin.api.UtExecutableCallModel
6+
import org.utbot.framework.plugin.api.UtStatementModel
7+
import org.utbot.fuzzer.FuzzedValue
8+
import org.utbot.fuzzer.ModelProvider
9+
import org.utbot.fuzzer.hex
10+
11+
12+
fun ModelProvider.assembleModel(id: Int, constructorId: ConstructorId, params: List<FuzzedValue>): FuzzedValue {
13+
val instantiationChain = mutableListOf<UtStatementModel>()
14+
return UtAssembleModel(
15+
id,
16+
constructorId.classId,
17+
"${constructorId.classId.name}${constructorId.parameters}#" + id.hex(),
18+
instantiationChain = instantiationChain,
19+
modificationsChain = mutableListOf()
20+
).apply {
21+
instantiationChain += UtExecutableCallModel(null, constructorId, params.map { it.model }, this)
22+
}.fuzzed {
23+
summary = "%var% = ${constructorId.classId.simpleName}(${constructorId.parameters.joinToString { it.simpleName }})"
24+
}
25+
}
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package org.utbot.fuzzer.providers
2+
3+
import java.text.SimpleDateFormat
4+
import org.utbot.framework.plugin.api.ClassId
5+
import org.utbot.framework.plugin.api.UtAssembleModel
6+
import org.utbot.framework.plugin.api.UtExecutableCallModel
7+
import org.utbot.framework.plugin.api.UtPrimitiveModel
8+
import org.utbot.framework.plugin.api.UtStatementModel
9+
import org.utbot.framework.plugin.api.util.dateClassId
10+
import org.utbot.framework.plugin.api.util.executableId
11+
import org.utbot.framework.plugin.api.util.id
12+
import org.utbot.framework.plugin.api.util.intClassId
13+
import org.utbot.framework.plugin.api.util.jClass
14+
import org.utbot.framework.plugin.api.util.longClassId
15+
import org.utbot.framework.plugin.api.util.stringClassId
16+
import org.utbot.framework.plugin.api.util.voidClassId
17+
import org.utbot.fuzzer.FuzzedMethodDescription
18+
import org.utbot.fuzzer.FuzzedParameter
19+
import org.utbot.fuzzer.FuzzedValue
20+
import org.utbot.fuzzer.IdentityPreservingIdGenerator
21+
import org.utbot.fuzzer.ModelProvider
22+
import org.utbot.fuzzer.ModelProvider.Companion.yieldAllValues
23+
import org.utbot.fuzzer.defaultModelProviders
24+
import org.utbot.fuzzer.fuzz
25+
import org.utbot.fuzzer.hex
26+
import org.utbot.fuzzer.objects.assembleModel
27+
28+
class DateConstantModelProvider(
29+
private val idGenerator: IdentityPreservingIdGenerator<Int>,
30+
) : ModelProvider {
31+
32+
var totalLimit: Int = 20
33+
34+
companion object {
35+
private const val defaultDateFormat = "dd-MM-yyyy HH:mm:ss:ms"
36+
}
37+
38+
private fun String.isDate(format: String): Boolean {
39+
val formatter = SimpleDateFormat(format).apply {
40+
isLenient = false
41+
}
42+
return runCatching { formatter.parse(trim()) }.isSuccess
43+
}
44+
45+
private fun String.isDateFormat(): Boolean {
46+
return none { it.isDigit() } && // fixes concrete date values
47+
runCatching { SimpleDateFormat(this) }.isSuccess
48+
}
49+
50+
private fun generateFromNumbers(
51+
baseMethodDescription: FuzzedMethodDescription,
52+
): Sequence<FuzzedValue> {
53+
val constructorsFromNumbers = dateClassId.allConstructors
54+
.filter { constructor ->
55+
constructor.parameters.isNotEmpty() &&
56+
constructor.parameters.all { it == intClassId || it == longClassId }
57+
}.map { constructorId ->
58+
with(constructorId) {
59+
ModelConstructor(parameters) { assembleModel(idGenerator.createId(), constructorId, it) }
60+
}
61+
}.sortedBy { it.neededTypes.size }
62+
63+
return sequence {
64+
constructorsFromNumbers.forEach { constructor ->
65+
yieldAll(
66+
fuzzValues(
67+
constructor.neededTypes,
68+
baseMethodDescription,
69+
defaultModelProviders(idGenerator)
70+
).map(constructor.createModel)
71+
)
72+
}
73+
}
74+
}
75+
76+
private fun generateFromDates(
77+
baseMethodDescription: FuzzedMethodDescription,
78+
): Sequence<FuzzedValue> {
79+
val strings = baseMethodDescription.concreteValues
80+
.asSequence()
81+
.filter { it.classId == stringClassId }
82+
.map { it.value as String }
83+
.distinct()
84+
val formats = strings.filter { it.isDateFormat() } + defaultDateFormat
85+
val formatToDates = formats.associateWith { format -> strings.filter { it.isDate(format) } }
86+
87+
return sequence {
88+
formatToDates.forEach { (format, dates) ->
89+
dates.forEach { date ->
90+
yield(assembleDateFromString(idGenerator.createId(), format, date))
91+
}
92+
}
93+
}
94+
}
95+
96+
private fun generateNowDate(): Sequence<FuzzedValue> {
97+
val constructor = dateClassId.allConstructors.first { it.parameters.isEmpty() }
98+
return sequenceOf(assembleModel(idGenerator.createId(), constructor, emptyList()))
99+
}
100+
101+
override fun generate(description: FuzzedMethodDescription): Sequence<FuzzedParameter> {
102+
val parameters = description.parametersMap[dateClassId]
103+
if (parameters.isNullOrEmpty()) {
104+
return emptySequence()
105+
}
106+
107+
return sequence {
108+
yieldAllValues(
109+
parameters,
110+
generateNowDate() + generateFromDates(description) +
111+
generateFromNumbers(description).take(totalLimit)
112+
)
113+
}.take(totalLimit)
114+
}
115+
116+
private fun fuzzValues(
117+
types: List<ClassId>,
118+
baseMethodDescription: FuzzedMethodDescription,
119+
modelProvider: ModelProvider,
120+
): Sequence<List<FuzzedValue>> {
121+
if (types.isEmpty())
122+
return sequenceOf(listOf())
123+
val syntheticMethodDescription = FuzzedMethodDescription(
124+
"<synthetic method for DateModelProvider>", // TODO: maybe add more info here
125+
voidClassId,
126+
types,
127+
baseMethodDescription.concreteValues
128+
).apply {
129+
packageName = baseMethodDescription.packageName
130+
}
131+
return fuzz(syntheticMethodDescription, modelProvider)
132+
}
133+
134+
private fun assembleDateFromString(id: Int, formatString: String, dateString: String): FuzzedValue {
135+
val simpleDateFormatModel = assembleSimpleDateFormat(idGenerator.createId(), formatString)
136+
val dateFormatParse = simpleDateFormatModel.classId.jClass
137+
.getMethod("parse", String::class.java).executableId
138+
val instantiationChain = mutableListOf<UtStatementModel>()
139+
return UtAssembleModel(
140+
id,
141+
dateClassId,
142+
"$dateFormatParse#" + id.hex(),
143+
instantiationChain
144+
).apply {
145+
instantiationChain += simpleDateFormatModel.allStatementsChain
146+
instantiationChain += UtExecutableCallModel(
147+
simpleDateFormatModel, dateFormatParse, listOf(UtPrimitiveModel(dateString)), returnValue = this
148+
)
149+
}.fuzzed {
150+
summary = "%var% = $dateFormatParse($stringClassId)"
151+
}
152+
}
153+
154+
private fun assembleSimpleDateFormat(id: Int, formatString: String): UtAssembleModel {
155+
val simpleDateFormatId = SimpleDateFormat::class.java.id
156+
val formatStringConstructor = simpleDateFormatId.allConstructors.first {
157+
it.parameters.singleOrNull() == stringClassId
158+
}
159+
val formatSetLenient = SimpleDateFormat::setLenient.executableId
160+
val formatModel = UtPrimitiveModel(formatString)
161+
162+
val instantiationChain = mutableListOf<UtStatementModel>()
163+
val modificationsChain = mutableListOf<UtStatementModel>()
164+
return UtAssembleModel(
165+
id,
166+
simpleDateFormatId,
167+
"$simpleDateFormatId[$stringClassId]#" + id.hex(),
168+
instantiationChain,
169+
modificationsChain
170+
).apply {
171+
instantiationChain += UtExecutableCallModel(
172+
instance = null, formatStringConstructor, listOf(formatModel), returnValue = this
173+
)
174+
modificationsChain += UtExecutableCallModel(
175+
instance = this, formatSetLenient, listOf(UtPrimitiveModel(false))
176+
)
177+
}
178+
}
179+
180+
}

0 commit comments

Comments
 (0)