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 b244761b4a..be01e42877 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 @@ -1,6 +1,7 @@ package org.utbot.fuzzing.providers import mu.KotlinLogging +import org.utbot.common.isStatic import org.utbot.framework.UtSettings import org.utbot.framework.plugin.api.* import org.utbot.framework.plugin.api.util.* @@ -46,33 +47,31 @@ class ObjectValueProvider( description: FuzzedDescription, type: FuzzedType ) = sequence { - val classId = type.classId - val constructors = classId.allConstructors - .filter { - isAccessible(it.constructor, description.description.packageName) - } - constructors.forEach { constructorId -> - yield(createValue(classId, constructorId, description)) + findAccessibleCreators(description, type).forEach { creatorExecutableId -> + yield(createValue(type.classId, creatorExecutableId, description)) } } - private fun createValue(classId: ClassId, constructorId: ConstructorId, description: FuzzedDescription): Seed.Recursive { + private fun createValue(classId: ClassId, creatorExecutableId: ExecutableId, description: FuzzedDescription): Seed.Recursive { return Seed.Recursive( - construct = Routine.Create(constructorId.executable.genericParameterTypes.map { + construct = Routine.Create(creatorExecutableId.executable.genericParameterTypes.map { toFuzzerType(it, description.typeCache) }) { values -> val id = idGenerator.createId() UtAssembleModel( id = id, classId = classId, - modelName = "${constructorId.classId.name}${constructorId.parameters}#" + id.hex(), + modelName = "${creatorExecutableId.classId.name}.${creatorExecutableId.signature}#" + id.hex(), instantiationCall = UtExecutableCallModel( null, - constructorId, + creatorExecutableId, values.map { it.model }), modificationsChainProvider = { mutableListOf() } ).fuzzed { - summary = "%var% = ${classId.simpleName}(${constructorId.parameters.joinToString { it.simpleName }})" + summary = "%var% = ${when (creatorExecutableId) { + is ConstructorId -> classId.simpleName + is MethodId -> creatorExecutableId.simpleNameWithClass + }}(${creatorExecutableId.parameters.joinToString { it.simpleName }})" } }, modify = sequence { @@ -164,12 +163,8 @@ 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) - } + findAccessibleCreators(description, toFuzzerType(jClass, description.typeCache)).any() && + jClass.let { toFuzzerType(it, description.typeCache).isDefinitelySubtypeOf(description, type) } } catch (ignore: Throwable) { return false } @@ -191,6 +186,27 @@ class AbstractsObjectValueProvider( } } +private fun findAccessibleCreators( + description: FuzzedDescription, + neededType: FuzzedType +): Sequence = + (neededType.classId.allConstructors + (neededType.classId.jClass.methods + .filter { + it.isStatic && toFuzzerType(it.genericReturnType, description.typeCache) + .isDefinitelySubtypeOf(description, neededType) + } + .map { it.executableId }) + ).filter { isAccessible(it.executable, description.description.packageName) } + +/** + * NOTE: this function takes conservative when generics are involved. + * + * For example, `false` may be returned when [this] is `List` or `ArrayList` while [other] is `List`. + */ +// TODO should be reworked with accurate generic matching +private fun FuzzedType.isDefinitelySubtypeOf(description: FuzzedDescription, other: FuzzedType): Boolean = + classId.isSubtypeOf(other.classId) && traverseHierarchy(description.typeCache).contains(other) + internal class PublicSetterGetter( val setter: Method, val getter: Method, diff --git a/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaFuzzingTest.kt b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaFuzzingTest.kt index 360d706356..b7e60d978c 100644 --- a/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaFuzzingTest.kt +++ b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaFuzzingTest.kt @@ -24,6 +24,7 @@ import org.utbot.fuzzing.utils.Trie import java.lang.reflect.GenericArrayType import java.lang.reflect.ParameterizedType import java.lang.reflect.Type +import java.time.LocalDateTime import java.util.IdentityHashMap import java.util.concurrent.atomic.AtomicInteger import kotlin.reflect.jvm.javaMethod @@ -177,6 +178,31 @@ class JavaFuzzingTest { seenStrings.forEach { assertInstanceOf(String::class.java, it) } } + @Test + fun `fuzzer can create instances of classes without public constructors but with static factory method in their class`() { + var seenLocalDateTime = false + runBlockingWithContext { + runJavaFuzzing( + TestIdentityPreservingIdGenerator, + methodUnderTest = LocalDateTime::getMinute.javaMethod!!.executableId, + constants = emptyList(), + names = emptyList(), + ) { thisInstance, _, _ -> + val control = runCatching { + ValueConstructor() + .construct(listOfNotNull(thisInstance?.model)) + .singleOrNull()?.value + }.getOrNull()?.let { constructedThisInstance -> + assertInstanceOf(LocalDateTime::class.java, constructedThisInstance) + seenLocalDateTime = true + Control.STOP + } ?: Control.CONTINUE + BaseFeedback(Trie.emptyNode(), control) + } + } + assertTrue(seenLocalDateTime) { "No value was generated for type LocalDateTime" } + } + @Test fun `value providers override every function of fuzzing in simple case`() { val provided = MarkerValueProvider("p")