Skip to content
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 @@ -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
Expand Down Expand Up @@ -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.
Expand Down
247 changes: 46 additions & 201 deletions utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
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

/**
* 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() {

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<Any>()
classId.jClass.isAssignableFrom(java.util.TreeSet::class.java) -> TreeSet<Any>()
classId.jClass.isAssignableFrom(java.util.HashMap::class.java) -> HashMap<Any, Any>()
classId.jClass.isAssignableFrom(java.util.ArrayDeque::class.java) -> ArrayDeque<Any>()
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<UtStatementModel>()
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
)
}
}
}
237 changes: 237 additions & 0 deletions utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
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<FuzzedConcreteValue> {
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(
ConstantsFromIfStatement,
ConstantsFromCast,
BoundValuesForDoubleChecks,
StringConstant,
).flatMap { finder ->
try {
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 {
ConstantsAsIs.find(graph, unit, value).asSequence()
}
}
}.toSet()
}

private interface ConstantsFinder {
fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List<FuzzedConcreteValue>
}

private object ConstantsFromIfStatement: ConstantsFinder {
override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List<FuzzedConcreteValue> {
if (value !is Constant || (unit !is JIfStmt && unit !is JAssignStmt)) return emptyList()

var useBoxes: List<Value> = 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()
}

}

private object ConstantsFromCast: ConstantsFinder {
override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List<FuzzedConcreteValue> {
if (value !is JCastExpr) return emptyList()

val next = nextDirectUnit(graph, unit)
if (next is JAssignStmt) {
val const = next.useBoxes.findFirstInstanceOf<Constant>()
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 object BoundValuesForDoubleChecks: ConstantsFinder {
override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List<FuzzedConcreteValue> {
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 StringConstant: ConstantsFinder {
override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List<FuzzedConcreteValue> {
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<Constant>()?.plainValue
if (stringConstantWasPassedAsArg != null) {
return listOf(FuzzedConcreteValue(stringClassId, stringConstantWasPassedAsArg, FuzzedOp.CH))
}
val stringConstantWasPassedAsThis = graph.getPredsOf(unit)
?.filterIsInstance<JAssignStmt>()
?.firstOrNull()
?.useBoxes
?.findFirstInstanceOf<Constant>()
?.plainValue
if (stringConstantWasPassedAsThis != null) {
return listOf(FuzzedConcreteValue(stringClassId, stringConstantWasPassedAsThis, FuzzedOp.CH))
}
}
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()
return listOf(FuzzedConcreteValue(value.type.classId, value.plainValue))

}

}

private inline fun <reified T> List<ValueBox>.findFirstInstanceOf(): T? {
return map { it.value }
.filterIsInstance<T>()
.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()
Loading