Skip to content

Commit 5c82da0

Browse files
committed
Fuzz objects for interfaces and abstract classes
1 parent 46ce930 commit 5c82da0

File tree

5 files changed

+59
-84
lines changed

5 files changed

+59
-84
lines changed

utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,9 @@ object UtSettings : AbstractSettings(logger, defaultKeyForSettingsPath, defaultS
237237
var fuzzingTimeoutInMillis: Long by getLongProperty(3_000L, 0, Long.MAX_VALUE)
238238

239239
/**
240-
* Fuzzer will create objects when they cannot be created by other providers (e.g. private classes)
240+
* Find implementations of interfaces and abstract classes to fuzz.
241241
*/
242-
var fuzzObjectWhenTheyCannotBeCreatedClean: Boolean by getBooleanProperty(false)
242+
var fuzzingImplementationOfAbstractClasses: Boolean by getBooleanProperty(true)
243243

244244
/**
245245
* Generate tests that treat possible overflows in arithmetic operations as errors

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

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
package org.utbot.fuzzer
22

33
import mu.KotlinLogging
4-
import org.utbot.framework.UtSettings
54
import org.utbot.framework.plugin.api.classId
65
import org.utbot.framework.plugin.api.util.*
76
import org.utbot.framework.util.executableId
8-
import org.utbot.fuzzing.providers.CreateObjectAnywayValueProvider
97
import soot.BooleanType
108
import soot.ByteType
119
import soot.CharType
@@ -48,7 +46,6 @@ fun collectConstantsForFuzzer(graph: ExceptionalUnitGraph): Set<FuzzedConcreteVa
4846
StringConstant,
4947
RegexByVarStringConstant,
5048
DateFormatByVarStringConstant,
51-
AbstractMethodIsCalled,
5249
).flatMap { finder ->
5350
try {
5451
finder.find(graph, unit, value)
@@ -249,16 +246,6 @@ private object DateFormatByVarStringConstant: ConstantsFinder {
249246
}
250247
}
251248

252-
private object AbstractMethodIsCalled: ConstantsFinder {
253-
override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List<FuzzedConcreteValue> {
254-
if (UtSettings.fuzzObjectWhenTheyCannotBeCreatedClean && value is JInterfaceInvokeExpr) {
255-
// todo do a better way to add information about virtual method calls to providers
256-
return listOf(FuzzedConcreteValue(value.method.javaClass.id, CreateObjectAnywayValueProvider::class, FuzzedContext.Call(value.method.executableId)))
257-
}
258-
return emptyList()
259-
}
260-
}
261-
262249
private object ConstantsAsIs: ConstantsFinder {
263250
override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List<FuzzedConcreteValue> {
264251
if (value !is Constant || value is NullConstant) return emptyList()

utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Api.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,14 @@ private object EmptyFeedback : Feedback<Nothing, Nothing> {
289289
class NoSeedValueException internal constructor(
290290
// this type cannot be generalized because Java forbids types for [Throwable].
291291
val type: Any?
292-
) : Exception("No seed candidates generated for type: $type")
292+
) : Exception() {
293+
override fun fillInStackTrace(): Throwable {
294+
return this
295+
}
296+
297+
override val message: String
298+
get() = "No seed candidates generated for type: $type"
299+
}
293300

294301
suspend fun <T, R, D : Description<T>, F : Feedback<T, R>> Fuzzing<T, R, D, F>.fuzz(
295302
description: D,

utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/JavaLanguage.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ fun defaultValueProviders(idGenerator: IdentityPreservingIdGenerator<Int>) = lis
4343
FloatValueProvider,
4444
StringValueProvider,
4545
NumberValueProvider,
46-
ObjectValueProvider(idGenerator).letIf(UtSettings.fuzzObjectWhenTheyCannotBeCreatedClean) { ovp ->
47-
ovp.withFallback(CreateObjectAnywayValueProvider(idGenerator, useMock = false))
46+
ObjectValueProvider(idGenerator).letIf(UtSettings.fuzzingImplementationOfAbstractClasses) { ovp ->
47+
ovp.withFallback(AbstractsObjectValueProvider(idGenerator))
4848
},
4949
ArrayValueProvider(idGenerator),
5050
EnumValueProvider(idGenerator),

utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt

Lines changed: 47 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
11
package org.utbot.fuzzing.providers
22

3-
import org.utbot.common.isAbstract
43
import org.utbot.framework.plugin.api.*
54
import org.utbot.framework.plugin.api.util.*
65
import org.utbot.fuzzer.*
76
import org.utbot.fuzzing.*
87
import org.utbot.fuzzing.utils.hex
8+
import soot.Scene
9+
import soot.SootClass
910
import java.lang.reflect.Field
1011
import java.lang.reflect.Member
1112
import java.lang.reflect.Method
1213
import java.lang.reflect.Modifier
1314

14-
private fun isIgnored(type: ClassId): Boolean {
15+
private fun isKnownTypes(type: ClassId): Boolean {
1516
return type == stringClassId
1617
|| type == dateClassId
1718
|| type == NumberValueProvider.classId
1819
|| type.isCollectionOrMap
1920
|| type.isPrimitiveWrapper
2021
|| type.isEnum
22+
}
23+
24+
private fun isIgnored(type: ClassId): Boolean {
25+
return isKnownTypes(type)
2126
|| type.isAbstract
2227
|| (type.isInner && !type.isStatic)
2328
}
@@ -33,10 +38,8 @@ class ObjectValueProvider(
3338
type: FuzzedType
3439
) = sequence {
3540
val classId = type.classId
36-
val constructors = findTypesOfNonRecursiveConstructor(type, description.description.packageName)
37-
.takeIf { it.isNotEmpty() }
38-
?.asSequence()
39-
?: classId.allConstructors.filter {
41+
val constructors = classId.allConstructors
42+
.filter {
4043
isAccessible(it.constructor, description.description.packageName)
4144
}
4245
constructors.forEach { constructorId ->
@@ -92,14 +95,6 @@ class ObjectValueProvider(
9295
empty = nullRoutine(classId)
9396
)
9497
}
95-
96-
private fun findTypesOfNonRecursiveConstructor(type: FuzzedType, packageName: String?): List<ConstructorId> {
97-
return type.classId.allConstructors
98-
.filter { isAccessible(it.constructor, packageName) }
99-
.filter { c ->
100-
c.parameters.all { it.isPrimitive || it == stringClassId || it.isArray }
101-
}.toList()
102-
}
10398
}
10499

105100
@Suppress("unused")
@@ -128,66 +123,52 @@ object NullValueProvider : ValueProvider<FuzzedType, FuzzedValue, FuzzedDescript
128123
}
129124
}
130125

131-
class CreateObjectAnywayValueProvider(
126+
/**
127+
* Finds and create object from implementations of abstract classes or interfaces.
128+
*/
129+
class AbstractsObjectValueProvider(
132130
val idGenerator: IdGenerator<Int>,
133-
val useMock: Boolean = false,
134131
) : ValueProvider<FuzzedType, FuzzedValue, FuzzedDescription> {
135132

136-
override fun accept(type: FuzzedType) = type.classId.isRefType && !isIgnored(type.classId)
133+
override fun accept(type: FuzzedType) = type.classId.isRefType && !isKnownTypes(type.classId)
137134

138135
override fun generate(description: FuzzedDescription, type: FuzzedType) = sequence<Seed<FuzzedType, FuzzedValue>> {
139-
val methodCalls = description.constants.filter {
140-
it.value == CreateObjectAnywayValueProvider::class
141-
}.mapNotNull {
142-
it.fuzzedContext as? FuzzedContext.Call
143-
}.map {
144-
it.method
145-
}.toSet()
146-
147-
yield(Seed.Recursive(
148-
construct = Routine.Create(emptyList()) {
149-
UtCompositeModel(idGenerator.createId(), type.classId, useMock).fuzzed {
150-
summary = "Unsafe object"
136+
val t = Scene.v().getRefType(type.classId.canonicalName).sootClass
137+
fun canCreateClass(sc: SootClass): Boolean {
138+
try {
139+
if (!sc.isConcrete) return false
140+
val packageName = sc.packageName
141+
if (packageName != null) {
142+
if (packageName.startsWith("jdk.internal") ||
143+
packageName.startsWith("org.utbot") ||
144+
packageName.startsWith("sun."))
145+
return false
151146
}
152-
},
153-
modify = sequence {
154-
// generate all fields
155-
generateSequence(type.classId.jClass) {
156-
it.superclass
157-
}.flatMap { javaClass ->
158-
javaClass.declaredFields.toList()
159-
}.forEach { field ->
160-
yield(Routine.Call(listOf(toFuzzerType(field.type, description.typeCache))) { instance, args ->
161-
(instance.model as UtCompositeModel).fields[field.fieldId] = args.first().model
162-
})
163-
}
164-
165-
generateSequence(listOf(type.classId.jClass)) { classList ->
166-
classList.flatMap { listOf(it.superclass) + it.interfaces }.filterNotNull().takeIf { it.isNotEmpty() }
167-
}.flatten().filter {
168-
isAccessible(it, description.description.packageName)
169-
}.flatMap { javaClass ->
170-
javaClass.declaredMethods.filter {
171-
javaClass.isInterface || it.isAbstract
172-
}.filter {
173-
isAccessible(it, description.description.packageName)
174-
}
175-
// todo filter by methods seen in code
176-
}.forEach { method ->
177-
val executableId = method.executableId
178-
if (methodCalls.contains(executableId)) {
179-
yield(Routine.Call(listOf(toFuzzerType(method.returnType, description.typeCache))) { instance, args ->
180-
(instance.model as UtCompositeModel).mocks[executableId] = args.map(FuzzedValue::model)
181-
})
182-
}
183-
}
184-
},
185-
empty = Routine.Empty {
186-
UtCompositeModel(idGenerator.createId(), type.classId, useMock).fuzzed {
187-
summary = "Unsafe object"
147+
val isAnonymousClass = sc.name.matches(""".*\$\d+$""".toRegex())
148+
if (isAnonymousClass) {
149+
return false
188150
}
151+
val jClass = sc.id.jClass
152+
return isAccessible(jClass, description.description.packageName) &&
153+
jClass.declaredConstructors.any { isAccessible(it, description.description.packageName) }
154+
} catch (ignore: Throwable) {
155+
return false
189156
}
190-
))
157+
}
158+
159+
val implementations = when {
160+
t.isInterface -> Scene.v().fastHierarchy.getAllImplementersOfInterface(t).filter(::canCreateClass)
161+
t.isAbstract -> Scene.v().fastHierarchy.getSubclassesOf(t).filter(::canCreateClass)
162+
else -> emptyList()
163+
}
164+
implementations.shuffled(description.random).take(10).forEach { concrete ->
165+
yield(Seed.Recursive(
166+
construct = Routine.Create(listOf(toFuzzerType(concrete.id.jClass, description.typeCache))) {
167+
it.first()
168+
},
169+
empty = nullRoutine(type.classId)
170+
))
171+
}
191172
}
192173
}
193174

0 commit comments

Comments
 (0)