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..dc0872de19 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 transformers: Sequence> = emptySequence(), ) : 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,28 @@ private fun , FEEDBACK : Feedback< 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) { @Suppress("UNCHECKED_CAST") @@ -577,7 +606,16 @@ private fun create(result: Result): R = when(result) { else -> error("Undefined object call method ${func.builder}") } } - obj + 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}") + } + } + transformed + } } is Result.Collection -> with(result) { val collection: R = when (val c = construct.builder) { @@ -659,6 +697,7 @@ sealed interface Result { class Recursive( val construct: Node, val modify: List>, + val transformers: List>, ) : 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..edeeff60e8 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, + transformers = source.transformers, ) } } @@ -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)), + transformers = source.transformers ) } } @@ -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/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/providers/Objects.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt index bb14e0ecfd..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 @@ -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 @@ -23,11 +17,12 @@ 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 import org.utbot.fuzzer.FuzzedValue import org.utbot.fuzzer.IdGenerator -import org.utbot.fuzzer.IdentityPreservingIdGenerator import org.utbot.fuzzer.fuzzed import org.utbot.fuzzing.FuzzedDescription import org.utbot.fuzzing.JavaValueProvider @@ -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 {} @@ -65,11 +58,6 @@ private fun isIgnored(type: ClassId): Boolean { || (type.isInner && !type.isStatic) } -fun anyObjectValueProvider(idGenerator: IdentityPreservingIdGenerator) = - ObjectValueProvider(idGenerator).letIf(UtSettings.fuzzingImplementationOfAbstractClasses) { ovp -> - ovp.withFallback(AbstractsObjectValueProvider(idGenerator)) - } - class ObjectValueProvider( val idGenerator: IdGenerator, ) : JavaValueProvider { @@ -180,6 +168,140 @@ 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)) } + .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 byMethod@{ method -> + val returnType = method.returnType as RefType + val returnClassId = returnType.classId + val isStaticMethod = method.isStatic + val parameters = method.parameterTypes.map { + 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( + construct = Routine.Create(parameters) { params -> + UtAssembleModel( + idGenerator.createId(), + returnClassId, + method.toString(), + UtExecutableCallModel( + null, + method.executableId, + params.map { it.model } + ), + modificationsChainProvider = { mutableListOf() } + ).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) + )) + } 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), + transformers = sequenceOf(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 { + val creatorExecutableId = method.executableId + summary = "%var% = ${when (creatorExecutableId) { + is ConstructorId -> classId.simpleName + is MethodId -> creatorExecutableId.simpleNameWithClass + }}(${creatorExecutableId.parameters.joinToString { it.simpleName }})" + } + }) + )) + } + } + } + } + } + +} + +private val nameWithDigitsAfterSpecialCharRegex = """.*\$\d+$""".toRegex() + +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.") || + packageName.startsWith("com.sun.") + ) + return false + } + val isAnonymousClass = name.matches(nameWithDigitsAfterSpecialCharRegex) + if (isAnonymousClass) { + return false + } + return true +} + /** * Finds and create object from implementations of abstract classes or interfaces. */ @@ -196,45 +318,39 @@ 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 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 } } 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 -> - 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 +374,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 +403,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 +434,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 +466,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)) 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-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, 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 9bddff2a52..80e18cbf96 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 @@ -14,6 +14,7 @@ import org.utbot.framework.context.custom.CoverageFilteringConcreteExecutionCont import org.utbot.framework.context.custom.RerunningConcreteExecutionContext 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 @@ -26,6 +27,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, @@ -81,7 +83,7 @@ class SpringApplicationContextImpl( springApplicationContext = this ) ) - } + }.transformValueProvider { it.useNullForPartiallyUnresolvableClasses() } } override fun createCodeGenerator(params: CodeGeneratorParams): AbstractCodeGenerator =