From a21fcfbeca845a506597f99fc23389c0c4c2c520 Mon Sep 17 00:00:00 2001 From: Maksim Pelevin Date: Thu, 7 Sep 2023 08:55:19 +0300 Subject: [PATCH 01/10] Introduce Builder value provider --- .../src/main/kotlin/org/utbot/fuzzing/Api.kt | 35 +++- .../kotlin/org/utbot/fuzzing/Configuration.kt | 2 +- .../kotlin/org/utbot/fuzzing/Mutations.kt | 9 +- .../kotlin/org/utbot/fuzzing/JavaLanguage.kt | 21 +- .../org/utbot/fuzzing/providers/Objects.kt | 191 ++++++++++++++---- 5 files changed, 208 insertions(+), 50 deletions(-) diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Api.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Api.kt index a33cba2ea0..3b02445c94 100644 --- a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Api.kt +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Api.kt @@ -141,7 +141,8 @@ sealed interface Seed { class Recursive( val construct: Routine.Create, val modify: Sequence> = emptySequence(), - val empty: Routine.Empty + val empty: Routine.Empty, + val finally: Routine.Modify? = null, ) : Seed /** @@ -186,6 +187,13 @@ sealed class Routine(val types: List) : Iterable by types { } } + class Modify( + types: List, + val callable: (instance: R, arguments: List) -> R + ) : Routine(types) { + operator fun invoke(instance: R, arguments: List): R = callable(instance, arguments) + } + /** * Creates a collection of concrete sizes. */ @@ -541,7 +549,22 @@ private fun , FEEDBACK : Feedback< parameterIndex = -1 } ) - } + }, + finally = task.finally?.let { finally -> + fuzz( + finally.types, + fuzzing, + description, + random, + configuration, + finally, + state.copy { + recursionTreeDepth++ + iterations = -1 + parameterIndex = -1 + } + ) + } ) } catch (nsv: NoSeedValueException) { @Suppress("UNCHECKED_CAST") @@ -577,7 +600,12 @@ private fun create(result: Result): R = when(result) { else -> error("Undefined object call method ${func.builder}") } } - obj + finally?.let { func -> + when (val builder = func.builder) { + is Routine.Modify -> builder(obj, func.result.map { create(it) }) + else -> error("Undefined object call method ${func.builder}") + } + } ?: obj } is Result.Collection -> with(result) { val collection: R = when (val c = construct.builder) { @@ -659,6 +687,7 @@ sealed interface Result { class Recursive( val construct: Node, val modify: List>, + val finally: Node?, ) : Result /** diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Configuration.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Configuration.kt index a6c19926c9..9ba2ebbe5b 100644 --- a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Configuration.kt +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Configuration.kt @@ -22,7 +22,7 @@ data class Configuration( * * To stop recursion [Seed.Recursive.empty] is called to create new values. */ - var recursionTreeDepth: Int = 4, + var recursionTreeDepth: Int = 5, /** * The limit of collection size to create. diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Mutations.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Mutations.kt index 9868be39b8..90c5278d93 100644 --- a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Mutations.kt +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Mutations.kt @@ -295,7 +295,8 @@ sealed interface RecursiveMutations : Mutation { return Result.Recursive( construct = recursive.mutate(source.construct,random, configuration), - modify = source.modify + modify = source.modify, + finally = source.finally, ) } } @@ -309,7 +310,8 @@ sealed interface RecursiveMutations : Mutation { return Result.Recursive( construct = source.construct, - modify = source.modify.shuffled(random).take(random.nextInt(source.modify.size + 1)) + modify = source.modify.shuffled(random).take(random.nextInt(source.modify.size + 1)), + finally = source.finally ) } } @@ -326,7 +328,8 @@ sealed interface RecursiveMutations : Mutation) = lis FloatValueProvider, StringValueProvider, NumberValueProvider, - anyObjectValueProvider(idGenerator), - ArrayValueProvider(idGenerator), - EnumValueProvider(idGenerator), - ListSetValueProvider(idGenerator), - MapValueProvider(idGenerator), - IteratorValueProvider(idGenerator), - EmptyCollectionValueProvider(idGenerator), - DateValueProvider(idGenerator), + (ObjectValueProvider(idGenerator) + with ArrayValueProvider(idGenerator) + with EnumValueProvider(idGenerator) + with ListSetValueProvider(idGenerator) + with MapValueProvider(idGenerator) + with IteratorValueProvider(idGenerator) + with EmptyCollectionValueProvider(idGenerator) + with DateValueProvider(idGenerator)) + .withFallback( + AbstractsObjectValueProvider(idGenerator) + with BuilderObjectValueProvider(idGenerator) + ), VoidValueProvider, NullValueProvider, ) @@ -235,6 +239,7 @@ private fun toClassId(type: Type, cache: MutableMap): ClassId } is ParameterizedType -> (type.rawType as Class<*>).id is Class<*> -> type.id + is TypeVariable<*> -> type.bounds.firstOrNull()?.let { toClassId(it, cache) } ?: objectClassId else -> error("unknown type: $type") } } diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt index bb14e0ecfd..713b07bf94 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt @@ -2,13 +2,7 @@ package org.utbot.fuzzing.providers import mu.KotlinLogging import org.utbot.framework.UtSettings -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.ConstructorId -import org.utbot.framework.plugin.api.FieldId -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.id +import org.utbot.framework.plugin.api.* import org.utbot.framework.plugin.api.util.classClassId import org.utbot.framework.plugin.api.util.constructor import org.utbot.framework.plugin.api.util.dateClassId @@ -24,6 +18,7 @@ import org.utbot.framework.plugin.api.util.isStatic import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.method import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.framework.util.executableId import org.utbot.fuzzer.FuzzedType import org.utbot.fuzzer.FuzzedValue import org.utbot.fuzzer.IdGenerator @@ -40,13 +35,11 @@ import org.utbot.fuzzing.utils.hex import org.utbot.modifications.AnalysisMode import org.utbot.modifications.FieldInvolvementMode import org.utbot.modifications.UtBotFieldsModificatorsSearcher +import soot.RefType import soot.Scene import soot.SootClass -import java.lang.reflect.Field -import java.lang.reflect.Member -import java.lang.reflect.Method -import java.lang.reflect.Modifier -import java.lang.reflect.TypeVariable +import soot.SootMethod +import java.lang.reflect.* private val logger = KotlinLogging.logger {} @@ -67,8 +60,10 @@ private fun isIgnored(type: ClassId): Boolean { fun anyObjectValueProvider(idGenerator: IdentityPreservingIdGenerator) = ObjectValueProvider(idGenerator).letIf(UtSettings.fuzzingImplementationOfAbstractClasses) { ovp -> - ovp.withFallback(AbstractsObjectValueProvider(idGenerator)) - } + ovp.withFallback( + AbstractsObjectValueProvider(idGenerator).with(BuilderObjectValueProvider(idGenerator)) + ) + }.withFallback(AnyDepthNullValueProvider) class ObjectValueProvider( val idGenerator: IdGenerator, @@ -180,6 +175,114 @@ object AnyDepthNullValueProvider : JavaValueProvider { ) = sequenceOf>(Seed.Simple(nullFuzzedValue(classClassId))) } +class BuilderObjectValueProvider( + val idGenerator: IdGenerator, +) : JavaValueProvider { + + override fun accept(type: FuzzedType) = type.classId.isRefType && !isKnownTypes(type.classId) + + override fun generate(description: FuzzedDescription, type: FuzzedType) = sequence> { + val t = try { + Scene.v().getRefType(type.classId.name).sootClass + } catch (ignore: Throwable) { + logger.error(ignore) { "Soot may be not initialized" } + return@sequence + } + val implementors = if (t.isInterface) { + Scene.v().activeHierarchy.let { it.getImplementersOf(t) + it.getSubinterfacesOf(t) + t }.toSet() + } else { + Scene.v().activeHierarchy.let { it.getSubclassesOf(t) + t }.toSet() + } + Scene.v().classes.forEach { sootClass -> + val classId = sootClass.id + if (sootClass.isUseful() && isAccessible(sootClass, description.description.packageName)) { + sootClass.methods.asSequence() + .filter { isAccessible(it, description.description.packageName) } + .filter { (implementors.contains((it.returnType as? RefType)?.sootClass)) } + .forEach { method -> + val returnType = method.returnType as RefType + val returnClassId = returnType.classId + val isStaticMethod = method.isStatic + val parameters = method.parameterTypes.map { + toFuzzerType(it.classId.jClass, description.typeCache) + } as MutableList + if (isStaticMethod) { + yield(Seed.Recursive( + construct = Routine.Create(parameters) { params -> + UtAssembleModel( + idGenerator.createId(), + returnClassId, + method.toString(), + UtExecutableCallModel( + null, + method.executableId, + params.map { it.model } + ), + modificationsChainProvider = { mutableListOf() } + ).fuzzed { } + }, + empty = nullRoutine(returnClassId) + )) + } else { + val builderInstance = toFuzzerType(classId.jClass, description.typeCache) + yield(Seed.Recursive( + construct = Routine.Create(listOf(builderInstance)) { + it.first() + }, + modify = sootClass.methods.asSequence() + .filter { isAccessible(it, description.description.packageName) } + .filter { (it.returnType as? RefType)?.sootClass == sootClass } + .map { builderMethod -> + Routine.Call(builderMethod.parameterTypes.map { + toFuzzerType(it.classId.jClass, description.typeCache) + }) { instance, values -> + val utAssembleModel = instance.model as? UtAssembleModel ?: return@Call + (utAssembleModel.modificationsChain as MutableList).add(UtExecutableCallModel( + utAssembleModel, + builderMethod.executableId, + values.map { it.model } + )) + } + }, + empty = nullRoutine(returnClassId), + finally = Routine.Modify(parameters) { instance, values -> + UtAssembleModel( + idGenerator.createId(), + returnClassId, + method.toString(), + UtExecutableCallModel( + instance.model as? UtAssembleModel ?: return@Modify nullFuzzedValue(returnClassId), + method.executableId, + values.map { it.model } + ), + ).fuzzed { } + } + )) + } + } + } + } + } + +} + +private fun SootClass.isUseful(): Boolean { + if (!isConcrete) return false + val packageName = packageName + if (packageName != null) { + if (packageName.startsWith("jdk.internal") || + packageName.startsWith("org.utbot") || + packageName.startsWith("sun.") + ) + return false + } + val isAnonymousClass = name.matches(""".*\$\d+$""".toRegex()) + if (isAnonymousClass) { + return false + } + return true +} + /** * Finds and create object from implementations of abstract classes or interfaces. */ @@ -196,18 +299,10 @@ class AbstractsObjectValueProvider( logger.error(ignore) { "Soot may be not initialized" } return@sequence } + fun canCreateClass(sc: SootClass): Boolean { try { - if (!sc.isConcrete) return false - val packageName = sc.packageName - if (packageName != null) { - if (packageName.startsWith("jdk.internal") || - packageName.startsWith("org.utbot") || - packageName.startsWith("sun.")) - return false - } - val isAnonymousClass = sc.name.matches(""".*\$\d+$""".toRegex()) - if (isAnonymousClass) { + if (!sc.isUseful()) { return false } val jClass = sc.id.jClass @@ -216,7 +311,8 @@ class AbstractsObjectValueProvider( jClass.let { // This won't work in case of implementations with generics like `Impl implements A`. // Should be reworked with accurate generic matching between all classes. - toFuzzerType(it, description.typeCache).traverseHierarchy(description.typeCache).contains(type) + toFuzzerType(it, description.typeCache).traverseHierarchy(description.typeCache) + .contains(type) } } catch (ignore: Throwable) { return false @@ -229,12 +325,14 @@ class AbstractsObjectValueProvider( else -> emptyList() } implementations.shuffled(description.random).take(10).forEach { concrete -> - yield(Seed.Recursive( - construct = Routine.Create(listOf(toFuzzerType(concrete.id.jClass, description.typeCache))) { - it.first() - }, - empty = nullRoutine(type.classId) - )) + yield( + Seed.Recursive( + construct = Routine.Create(listOf(toFuzzerType(concrete.id.jClass, description.typeCache))) { + it.first() + }, + empty = nullRoutine(type.classId) + ) + ) } } } @@ -258,7 +356,11 @@ internal class MethodDescription( val method: Method ) -internal fun findAccessibleModifiableFields(description: FuzzedDescription?, classId: ClassId, packageName: String?): List { +internal fun findAccessibleModifiableFields( + description: FuzzedDescription?, + classId: ClassId, + packageName: String? +): List { return generateSequence(classId.jClass) { it.superclass }.flatMap { jClass -> jClass.declaredFields.map { field -> val setterAndGetter = jClass.findPublicSetterGetterIfHasPublicGetter(field, packageName) @@ -283,7 +385,7 @@ internal fun findMethodsToModifyWith( description: FuzzedDescription, valueClassId: ClassId, classUnderTest: ClassId, - ): List { +): List { val packageName = description.description.packageName val methodUnderTestName = description.description.name.substringAfter(description.description.className + ".") @@ -314,7 +416,11 @@ internal fun Class<*>.findPublicSetterGetterIfHasPublicGetter(field: Field, pack @Suppress("DEPRECATION") val postfixName = field.name.capitalize() val setterName = "set$postfixName" val getterName = "get$postfixName" - val getter = try { getDeclaredMethod(getterName) } catch (_: NoSuchMethodException) { return null } + val getter = try { + getDeclaredMethod(getterName) + } catch (_: NoSuchMethodException) { + return null + } return if (isAccessible(getter, packageName) && getter.returnType == field.type) { declaredMethods.find { isAccessible(it, packageName) && @@ -342,11 +448,26 @@ internal fun isAccessible(clazz: Class<*>, packageName: String?): Boolean { (packageName != null && isNotPrivateOrProtected(clazz.modifiers) && clazz.`package`?.name == packageName) } +internal fun isAccessible(sootClass: SootClass, packageName: String?): Boolean { + return Modifier.isPublic(sootClass.modifiers) || + (packageName != null && isNotPrivateOrProtected(sootClass.modifiers) && sootClass.packageName == packageName) +} + +internal fun isAccessible(sootMethod: SootMethod, packageName: String?): Boolean { + var clazz = sootMethod.declaringClass + while (clazz != null) { + if (!isAccessible(clazz, packageName)) return false + clazz = if (clazz.hasOuterClass()) clazz.outerClass else null + } + return Modifier.isPublic(sootMethod.modifiers) || + (packageName != null && isNotPrivateOrProtected(sootMethod.modifiers) && sootMethod.declaringClass.packageName == packageName) +} + private fun findModifyingMethodNames( methodUnderTestName: String, valueClassId: ClassId, classUnderTest: ClassId, - ) : Set = +): Set = UtBotFieldsModificatorsSearcher(fieldInvolvementMode = FieldInvolvementMode.ReadAndWrite) .let { searcher -> searcher.update(setOf(valueClassId, classUnderTest)) From e5be238d8088124fbc38a01c028f81c8844f51f1 Mon Sep 17 00:00:00 2001 From: Maksim Pelevin Date: Thu, 7 Sep 2023 11:48:21 +0300 Subject: [PATCH 02/10] Rename finally to transformers --- .../src/main/kotlin/org/utbot/fuzzing/Api.kt | 52 +++++++++++-------- .../kotlin/org/utbot/fuzzing/Mutations.kt | 6 +-- .../org/utbot/fuzzing/providers/Objects.kt | 19 +++---- 3 files changed, 40 insertions(+), 37 deletions(-) diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Api.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Api.kt index 3b02445c94..dc0872de19 100644 --- a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Api.kt +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Api.kt @@ -142,7 +142,7 @@ sealed interface Seed { val construct: Routine.Create, val modify: Sequence> = emptySequence(), val empty: Routine.Empty, - val finally: Routine.Modify? = null, + val transformers: Sequence> = emptySequence(), ) : Seed /** @@ -550,20 +550,26 @@ private fun , FEEDBACK : Feedback< } ) }, - finally = task.finally?.let { finally -> - fuzz( - finally.types, - fuzzing, - description, - random, - configuration, - finally, - state.copy { - recursionTreeDepth++ - iterations = -1 - parameterIndex = -1 - } - ) + transformers = task.transformers.let { transformer -> + if (transformer === emptySequence>() || transformer.none()) { + emptyList() + } else { + transformer.map { f -> + fuzz( + f.types, + fuzzing, + description, + random, + configuration, + f, + state.copy { + recursionTreeDepth++ + iterations = -1 + parameterIndex = -1 + } + ) + }.toList() + } } ) } catch (nsv: NoSeedValueException) { @@ -600,12 +606,16 @@ private fun create(result: Result): R = when(result) { else -> error("Undefined object call method ${func.builder}") } } - finally?.let { func -> - when (val builder = func.builder) { - is Routine.Modify -> builder(obj, func.result.map { create(it) }) - else -> error("Undefined object call method ${func.builder}") + transformers.let { transformers -> + var transformed = obj + transformers.forEach { transformer -> + transformed = when (val builder = transformer.builder) { + is Routine.Modify -> builder(obj, transformer.result.map { create(it) }) + else -> error("Undefined object call method ${transformer.builder}") + } } - } ?: obj + transformed + } } is Result.Collection -> with(result) { val collection: R = when (val c = construct.builder) { @@ -687,7 +697,7 @@ sealed interface Result { class Recursive( val construct: Node, val modify: List>, - val finally: Node?, + val transformers: List>, ) : Result /** diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Mutations.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Mutations.kt index 90c5278d93..edeeff60e8 100644 --- a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Mutations.kt +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Mutations.kt @@ -296,7 +296,7 @@ sealed interface RecursiveMutations : Mutation : Mutation : Mutation) = - ObjectValueProvider(idGenerator).letIf(UtSettings.fuzzingImplementationOfAbstractClasses) { ovp -> - ovp.withFallback( - AbstractsObjectValueProvider(idGenerator).with(BuilderObjectValueProvider(idGenerator)) - ) - }.withFallback(AnyDepthNullValueProvider) - class ObjectValueProvider( val idGenerator: IdGenerator, ) : JavaValueProvider { @@ -109,7 +101,7 @@ class ObjectValueProvider( when { fd.canBeSetDirectly -> { yield(Routine.Call(listOf(fd.type)) { self, values -> - val model = self.model as UtAssembleModel + val model = self.model as? UtAssembleModel ?: return@Call model.modificationsChain as MutableList += UtDirectSetFieldModel( model, FieldId(classId, fd.name), @@ -120,7 +112,7 @@ class ObjectValueProvider( fd.setter != null && fd.getter != null -> { yield(Routine.Call(listOf(fd.type)) { self, values -> - val model = self.model as UtAssembleModel + val model = self.model as? UtAssembleModel ?: return@Call model.modificationsChain as MutableList += UtExecutableCallModel( model, fd.setter.executableId, @@ -245,7 +237,7 @@ class BuilderObjectValueProvider( } }, empty = nullRoutine(returnClassId), - finally = Routine.Modify(parameters) { instance, values -> + transformers = sequenceOf(Routine.Modify(parameters) { instance, values -> UtAssembleModel( idGenerator.createId(), returnClassId, @@ -256,7 +248,7 @@ class BuilderObjectValueProvider( values.map { it.model } ), ).fuzzed { } - } + }) )) } } @@ -272,7 +264,8 @@ private fun SootClass.isUseful(): Boolean { if (packageName != null) { if (packageName.startsWith("jdk.internal") || packageName.startsWith("org.utbot") || - packageName.startsWith("sun.") + packageName.startsWith("sun.") || + packageName.startsWith("com.sun.") ) return false } From 4a45ad4e4c6741dd4bc532de70419d37aeb55680 Mon Sep 17 00:00:00 2001 From: Maksim Pelevin Date: Thu, 7 Sep 2023 17:35:09 +0300 Subject: [PATCH 03/10] Simplify code --- .../kotlin/org/utbot/fuzzing/providers/Objects.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt index 6d9b713a05..292082ca6a 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt @@ -300,14 +300,13 @@ class AbstractsObjectValueProvider( } val jClass = sc.id.jClass return isAccessible(jClass, description.description.packageName) && - jClass.declaredConstructors.any { isAccessible(it, description.description.packageName) } && - jClass.let { - // This won't work in case of implementations with generics like `Impl implements A`. - // Should be reworked with accurate generic matching between all classes. - toFuzzerType(it, description.typeCache).traverseHierarchy(description.typeCache) - .contains(type) - } + jClass.declaredConstructors.any { isAccessible(it, description.description.packageName) } + // This won't work in case of implementations with generics like `Impl implements A`. + // Should be reworked with accurate generic matching between all classes. + && toFuzzerType(jClass, description.typeCache).traverseHierarchy(description.typeCache) + .contains(type) } catch (ignore: Throwable) { + logger.warn("Cannot resolve class $sc", ignore) return false } } From 510099e62b9aa1cb98ae8669e992bfa9bfca8b56 Mon Sep 17 00:00:00 2001 From: Maksim Pelevin Date: Fri, 8 Sep 2023 09:05:29 +0300 Subject: [PATCH 04/10] Fix problem when synthetic methods are used for test generation --- .../main/kotlin/org/utbot/fuzzing/providers/Objects.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt index 292082ca6a..cf2d62f321 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt @@ -191,6 +191,12 @@ class BuilderObjectValueProvider( sootClass.methods.asSequence() .filter { isAccessible(it, description.description.packageName) } .filter { (implementors.contains((it.returnType as? RefType)?.sootClass)) } + .filter { + // Simple check whether the method is synthetic, + // because Soot has no information about this: + // https://mailman.cs.mcgill.ca/pipermail/soot-list/2012-November/004972.html + !it.name.matches(nameWithDigitsAfterSpecialCharRegex) + } .forEach { method -> val returnType = method.returnType as RefType val returnClassId = returnType.classId @@ -258,6 +264,8 @@ class BuilderObjectValueProvider( } +private val nameWithDigitsAfterSpecialCharRegex = """.*\$\d+$""".toRegex() + private fun SootClass.isUseful(): Boolean { if (!isConcrete) return false val packageName = packageName @@ -269,7 +277,7 @@ private fun SootClass.isUseful(): Boolean { ) return false } - val isAnonymousClass = name.matches(""".*\$\d+$""".toRegex()) + val isAnonymousClass = name.matches(nameWithDigitsAfterSpecialCharRegex) if (isAnonymousClass) { return false } From 84d51c6322693b3653597cc28ac2da7a88ac8370 Mon Sep 17 00:00:00 2001 From: Maksim Pelevin Date: Mon, 11 Sep 2023 08:29:59 +0300 Subject: [PATCH 05/10] Add fuzzed summary --- .../org/utbot/fuzzing/providers/Objects.kt | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt index cf2d62f321..60b6f66884 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt @@ -17,6 +17,7 @@ import org.utbot.framework.plugin.api.util.isRefType import org.utbot.framework.plugin.api.util.isStatic import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.method +import org.utbot.framework.plugin.api.util.simpleNameWithClass import org.utbot.framework.plugin.api.util.stringClassId import org.utbot.framework.util.executableId import org.utbot.fuzzer.FuzzedType @@ -217,7 +218,13 @@ class BuilderObjectValueProvider( params.map { it.model } ), modificationsChainProvider = { mutableListOf() } - ).fuzzed { } + ).fuzzed { + val creatorExecutableId = method.executableId + summary = "%var% = ${when (creatorExecutableId) { + is ConstructorId -> classId.simpleName + is MethodId -> creatorExecutableId.simpleNameWithClass + }}(${creatorExecutableId.parameters.joinToString { it.simpleName }})" + } }, empty = nullRoutine(returnClassId) )) @@ -253,7 +260,13 @@ class BuilderObjectValueProvider( method.executableId, values.map { it.model } ), - ).fuzzed { } + ).fuzzed { + val creatorExecutableId = method.executableId + summary = "%var% = ${when (creatorExecutableId) { + is ConstructorId -> classId.simpleName + is MethodId -> creatorExecutableId.simpleNameWithClass + }}(${creatorExecutableId.parameters.joinToString { it.simpleName }})" + } }) )) } From 0852a76ccc7b848c3b0f4a0c95e03f48c3620d24 Mon Sep 17 00:00:00 2001 From: Maksim Pelevin Date: Mon, 11 Sep 2023 08:36:07 +0300 Subject: [PATCH 06/10] Remove unnecessary check --- .../src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt index 60b6f66884..f665f3f202 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt @@ -102,7 +102,7 @@ class ObjectValueProvider( when { fd.canBeSetDirectly -> { yield(Routine.Call(listOf(fd.type)) { self, values -> - val model = self.model as? UtAssembleModel ?: return@Call + val model = self.model as UtAssembleModel model.modificationsChain as MutableList += UtDirectSetFieldModel( model, FieldId(classId, fd.name), @@ -113,7 +113,7 @@ class ObjectValueProvider( fd.setter != null && fd.getter != null -> { yield(Routine.Call(listOf(fd.type)) { self, values -> - val model = self.model as? UtAssembleModel ?: return@Call + val model = self.model as UtAssembleModel model.modificationsChain as MutableList += UtExecutableCallModel( model, fd.setter.executableId, From 59c99e587c436d3f8f16d5b151ca59b4bd861142 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Mon, 11 Sep 2023 10:58:12 +0300 Subject: [PATCH 07/10] Preserve `transformers` when copying `Seed.Recursive` --- .../providers/ModifyingWithMethodsProviderWrapper.kt | 1 + .../fuzzing/spring/PropertyPreservingValueProvider.kt | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/ModifyingWithMethodsProviderWrapper.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/ModifyingWithMethodsProviderWrapper.kt index fa3ab0b4cc..9e723ebf0a 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/ModifyingWithMethodsProviderWrapper.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/ModifyingWithMethodsProviderWrapper.kt @@ -47,6 +47,7 @@ class ModifyingWithMethodsProviderWrapper( } }, empty = seed.empty, + transformers = seed.transformers, ) } else seed } diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/PropertyPreservingValueProvider.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/PropertyPreservingValueProvider.kt index 986d6d2ead..e4dcb30084 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/PropertyPreservingValueProvider.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/PropertyPreservingValueProvider.kt @@ -54,7 +54,13 @@ class PropertyPreservingValueProvider(private val delegateProvider: JavaValuePro callable = modification.callable ) }, - empty = seed.empty + empty = seed.empty, + transformers = seed.transformers.map { transformer -> + Routine.Modify( + types = transformer.types.addPreservedProperties(), + callable = transformer.callable + ) + } ) is Seed.Collection -> Seed.Collection( construct = seed.construct, From 1caa8de39acf66b4a41ca039299ed0116cc10503 Mon Sep 17 00:00:00 2001 From: Maksim Pelevin Date: Mon, 11 Sep 2023 14:43:45 +0300 Subject: [PATCH 08/10] Handle CNFE when class from method parameter is not present in classloader --- .../main/kotlin/org/utbot/fuzzing/providers/Objects.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt index f665f3f202..c40d666edf 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt @@ -198,12 +198,17 @@ class BuilderObjectValueProvider( // https://mailman.cs.mcgill.ca/pipermail/soot-list/2012-November/004972.html !it.name.matches(nameWithDigitsAfterSpecialCharRegex) } - .forEach { method -> + .forEach byMethod@{ method -> val returnType = method.returnType as RefType val returnClassId = returnType.classId val isStaticMethod = method.isStatic val parameters = method.parameterTypes.map { - toFuzzerType(it.classId.jClass, description.typeCache) + try { + toFuzzerType(it.classId.jClass, description.typeCache) + } catch (e: ClassNotFoundException) { + logger.warn { "${it.classId} is not loaded by classpath" } + return@byMethod + } } as MutableList if (isStaticMethod) { yield(Seed.Recursive( From 19a929837d50aafff8218b0e28c54983dfe463a4 Mon Sep 17 00:00:00 2001 From: Maksim Pelevin Date: Mon, 11 Sep 2023 14:44:23 +0300 Subject: [PATCH 09/10] Unify hierarchy calling method --- .../src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt index c40d666edf..3cc062c1da 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt @@ -338,8 +338,8 @@ class AbstractsObjectValueProvider( } val implementations = when { - t.isInterface -> Scene.v().fastHierarchy.getAllImplementersOfInterface(t).filter(::canCreateClass) - t.isAbstract -> Scene.v().fastHierarchy.getSubclassesOf(t).filter(::canCreateClass) + t.isInterface -> Scene.v().activeHierarchy.getImplementersOf(t).filter(::canCreateClass) + t.isAbstract -> Scene.v().activeHierarchy.getSubclassesOf(t).filter(::canCreateClass) else -> emptyList() } implementations.shuffled(description.random).take(10).forEach { concrete -> From bbb102bf9ddf5839756fc35874451e3bfba311b8 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Tue, 12 Sep 2023 22:11:42 +0300 Subject: [PATCH 10/10] Use `null` for partially unresolvable classes in fuzzed Spring tests --- ...PartiallyUnresolvableClassValueProvider.kt | 39 +++++++++++++++++++ .../spring/SpringApplicationContextImpl.kt | 4 +- 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/PartiallyUnresolvableClassValueProvider.kt diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/PartiallyUnresolvableClassValueProvider.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/PartiallyUnresolvableClassValueProvider.kt new file mode 100644 index 0000000000..422ef38bb1 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/PartiallyUnresolvableClassValueProvider.kt @@ -0,0 +1,39 @@ +package org.utbot.fuzzing.spring + +import mu.KotlinLogging +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.util.allDeclaredFieldIds +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzing.FuzzedDescription +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.providers.nullFuzzedValue + +/** + * Returns [JavaValueProvider] that only uses `null` value for classes that mention (in their + * constructor, method, or field signatures) classes that are not present on the classpath. + */ +fun JavaValueProvider.useNullForPartiallyUnresolvableClasses() = + PartiallyUnresolvableClassValueProvider().withFallback(this) + +private class PartiallyUnresolvableClassValueProvider : JavaValueProvider { + companion object { + private val logger = KotlinLogging.logger {} + } + + private val classResolvabilityCache = mutableMapOf() + + override fun accept(type: FuzzedType): Boolean = !classResolvabilityCache.getOrPut(type.classId) { + runCatching { + type.classId.allConstructors.toList() + type.classId.allMethods.toList() + type.classId.allDeclaredFieldIds.toList() + }.onFailure { e -> + logger.warn { "Failed to resolve ${type.classId} dependencies, using `null` value, cause: $e" } + }.isSuccess + } + + override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence> = + sequenceOf(Seed.Simple(nullFuzzedValue(type.classId))) +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringApplicationContextImpl.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringApplicationContextImpl.kt index f17bf1086a..a9951d61f1 100644 --- a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringApplicationContextImpl.kt +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringApplicationContextImpl.kt @@ -13,6 +13,7 @@ import org.utbot.framework.context.TypeReplacer import org.utbot.framework.context.custom.CoverageFilteringConcreteExecutionContext import org.utbot.framework.context.custom.mockAllTypesWithoutSpecificValueProvider import org.utbot.framework.context.utils.transformJavaFuzzingContext +import org.utbot.framework.context.utils.transformValueProvider import org.utbot.framework.context.utils.withValueProvider import org.utbot.framework.plugin.api.BeanDefinitionData import org.utbot.framework.plugin.api.ClassId @@ -25,6 +26,7 @@ import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.utContext import org.utbot.fuzzing.spring.unit.InjectMockValueProvider +import org.utbot.fuzzing.spring.useNullForPartiallyUnresolvableClasses class SpringApplicationContextImpl( private val delegateContext: ApplicationContext, @@ -77,7 +79,7 @@ class SpringApplicationContextImpl( classpathWithoutDependencies, this ) - } + }.transformValueProvider { it.useNullForPartiallyUnresolvableClasses() } } override fun createCodeGenerator(params: CodeGeneratorParams): AbstractCodeGenerator =