From fb11fdbd7b805becbc76eed8a4f4a722578f3e03 Mon Sep 17 00:00:00 2001 From: Dmitrii Timofeev Date: Tue, 28 Jun 2022 01:04:05 +0300 Subject: [PATCH 1/3] Make UtEnumConstModel and UtClassRefModel reference models #414 Historically `UtEnumConstModel` and `UtClassRefModel` have been processed not as other reference models, but in a special way, more like to primitive types. This approach leads to several problems, especially to class cast errors when processing generic collections with enums or class references as elements. This commit makes `UtEnumConstModel` and `UtClassRefModel` subtypes of `UtReferenceModel`. * Concrete executor is modified to respect the identity of static fields to avoid rewriting enum values and `Class` instances. * Special processing for enums is implemented. When a new enum value is created, or an `Object` is being cast to the enum type, static values for the enum class are initialized, and the set of hard constraint is added to require that the new instance has the same address and ordinal as any one of enum constants to implement reference equality for enums. * Corresponding changes in fuzzer model providers have been implemented. * UtModelVisitor has been updated to reflect the new inheritance hierarchy. --- utbot-framework-api/build.gradle | 6 + .../org/utbot/framework/plugin/api/Api.kt | 37 +++++- .../org/utbot/engine/ArrayObjectWrappers.kt | 5 +- .../org/utbot/engine/CollectionWrappers.kt | 4 +- .../main/kotlin/org/utbot/engine/Memory.kt | 18 ++- .../main/kotlin/org/utbot/engine/Resolver.kt | 22 ++-- .../main/kotlin/org/utbot/engine/Traverser.kt | 78 +++++++++--- .../statics/concrete/EnumConcreteUtils.kt | 4 +- .../org/utbot/external/api/UtModelFactory.kt | 6 +- .../constructor/tree/CgVariableConstructor.kt | 4 +- .../concrete/MockValueConstructor.kt | 11 +- .../concrete/UtExecutionInstrumentation.kt | 11 +- .../framework/concrete/UtModelConstructor.kt | 4 +- .../fields/ExecutionStateAnalyzer.kt | 10 +- .../utbot/framework/util/UtModelVisitor.kt | 6 +- .../examples/manual/UtBotJavaApiTest.java | 8 +- .../examples/enums/ComplexEnumExamplesTest.kt | 109 +++++++++++++++++ .../main/kotlin/org/utbot/fuzzer/Fuzzer.kt | 6 +- .../fuzzer/providers/EnumModelProvider.kt | 19 ++- .../framework/plugin/api/ModelProviderTest.kt | 7 +- .../examples/enums/ComplexEnumExamples.java | 113 ++++++++++++++++++ .../java/org/utbot/examples/enums/State.java | 41 +++++++ 22 files changed, 458 insertions(+), 71 deletions(-) create mode 100644 utbot-framework/src/test/kotlin/org/utbot/examples/enums/ComplexEnumExamplesTest.kt create mode 100644 utbot-sample/src/main/java/org/utbot/examples/enums/ComplexEnumExamples.java create mode 100644 utbot-sample/src/main/java/org/utbot/examples/enums/State.java diff --git a/utbot-framework-api/build.gradle b/utbot-framework-api/build.gradle index d922375eef..9a71cda4a1 100644 --- a/utbot-framework-api/build.gradle +++ b/utbot-framework-api/build.gradle @@ -14,6 +14,12 @@ dependencies { implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlin_logging_version } +compileKotlin { + kotlinOptions { + freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" + } +} + shadowJar { configurations = [project.configurations.compileClasspath] archiveClassifier.set('') diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt index 04317cd909..9b719c8d6e 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt @@ -51,6 +51,8 @@ import soot.jimple.JimpleBody import soot.jimple.Stmt import java.io.File import java.lang.reflect.Modifier +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract import kotlin.jvm.internal.CallableReference import kotlin.reflect.KCallable import kotlin.reflect.KClass @@ -59,6 +61,8 @@ import kotlin.reflect.full.instanceParameter import kotlin.reflect.jvm.javaConstructor import kotlin.reflect.jvm.javaType +const val SYMBOLIC_NULL_ADDR: Int = 0 + data class UtMethod( val callable: KCallable, val clazz: KClass<*> @@ -267,6 +271,27 @@ fun UtModel.hasDefaultValue() = */ fun UtModel.isMockModel() = this is UtCompositeModel && isMock +/** + * Get model id (symbolic null value for UtNullModel) + * or null if model has no id (e.g., a primitive model) or the id is null. + */ +fun UtModel.idOrNull(): Int? = when (this) { + is UtNullModel -> SYMBOLIC_NULL_ADDR + is UtReferenceModel -> id + else -> null +} + +/** + * Returns the model id if it is available, or throws an [IllegalStateException]. + */ +@OptIn(ExperimentalContracts::class) +fun UtModel?.getIdOrThrow(): Int { + contract { + returns() implies (this@getIdOrThrow != null) + } + return this?.idOrNull() ?: throw IllegalStateException("Model id must not be null: $this") +} + /** * Model for nulls. */ @@ -308,20 +333,24 @@ object UtVoidModel : UtModel(voidClassId) * Model for enum constant */ data class UtEnumConstantModel( + override val id: Int?, override val classId: ClassId, val value: Enum<*> -) : UtModel(classId) { - override fun toString(): String = "$value" +) : UtReferenceModel(id, classId) { + // Model id is included for debugging purposes + override fun toString(): String = "$value@$id" } /** * Model for class reference */ data class UtClassRefModel( + override val id: Int?, override val classId: ClassId, val value: Class<*> -) : UtModel(classId) { - override fun toString(): String = "$value" +) : UtReferenceModel(id, classId) { + // Model id is included for debugging purposes + override fun toString(): String = "$value@$id" } /** diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt index c5f3cde9e9..6671f25ac2 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt @@ -24,7 +24,8 @@ import org.utbot.framework.plugin.api.UtCompositeModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.getIdOrThrow +import org.utbot.framework.plugin.api.idOrNull import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.objectArrayClassId import org.utbot.framework.plugin.api.util.objectClassId @@ -398,7 +399,7 @@ class AssociativeArrayWrapper : WrapperInterface { UtNullModel(objectClassId), stores = (0 until sizeValue).associateTo(mutableMapOf()) { i -> val model = touchedValues.stores[i] - val addr = if (model is UtNullModel) 0 else (model as UtReferenceModel).id!! + val addr = model.getIdOrThrow() addr to resolver.resolveModel( ObjectValue( TypeStorage(OBJECT_TYPE), diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt index e5c6558558..2e2039dddc 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt @@ -22,9 +22,9 @@ 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.UtReferenceModel import org.utbot.framework.plugin.api.UtStatementModel import org.utbot.framework.plugin.api.classId +import org.utbot.framework.plugin.api.getIdOrThrow import org.utbot.framework.util.graph import org.utbot.framework.plugin.api.id import org.utbot.framework.plugin.api.util.booleanClassId @@ -373,7 +373,7 @@ private fun constructKeysAndValues(keysModel: UtModel, valuesModel: UtModel, siz keysModel is UtArrayModel && valuesModel is UtArrayModel -> { List(size) { keysModel.stores[it].let { model -> - val addr = if (model is UtNullModel) 0 else (model as UtReferenceModel).id + val addr = model.getIdOrThrow() // as we do not support generics for now, valuesModel.classId.elementClassId is unknown, // but it can be known with generics support val defaultValue = UtNullModel(valuesModel.classId.elementClassId ?: objectClassId) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt index 549d02f16b..6f06ac74a7 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt @@ -56,6 +56,7 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentMap +import org.utbot.framework.plugin.api.classId import soot.ArrayType import soot.BooleanType import soot.ByteType @@ -147,7 +148,8 @@ data class Memory( // TODO: split purely symbolic memory and information about s private val speculativelyNotNullAddresses: UtArrayExpressionBase = UtConstArrayExpression( UtFalse, UtArraySort(UtAddrSort, UtBoolSort) - ) + ), + private val symbolicEnumValues: PersistentList = persistentListOf() ) { val chunkIds: Set get() = initial.keys @@ -297,7 +299,8 @@ data class Memory( // TODO: split purely symbolic memory and information about s visitedValues = updVisitedValues, touchedAddresses = updTouchedAddresses, instanceFieldReadOperations = instanceFieldReadOperations.addAll(update.instanceFieldReads), - speculativelyNotNullAddresses = updSpeculativelyNotNullAddresses + speculativelyNotNullAddresses = updSpeculativelyNotNullAddresses, + symbolicEnumValues = symbolicEnumValues.addAll(update.symbolicEnumValues) ) } @@ -307,7 +310,6 @@ data class Memory( // TODO: split purely symbolic memory and information about s */ fun memoryForNestedMethod(): Memory = this.copy(updates = MemoryUpdate()) - /** * Returns copy of queued [updates] which consists only of updates of static fields. * This is necessary for substituting unbounded symbolic variables into the static fields. @@ -350,6 +352,9 @@ data class Memory( // TODO: split purely symbolic memory and information about s fun findStaticInstanceOrNull(id: ClassId): ObjectValue? = staticInstanceStorage[id] fun findTypeForArrayOrNull(addr: UtAddrExpression): ArrayType? = addrToArrayType[addr] + + fun getSymbolicEnumValues(classId: ClassId): List = + symbolicEnumValues.filter { it.type.classId == classId } } /** @@ -967,7 +972,8 @@ data class MemoryUpdate( val touchedAddresses: PersistentList = persistentListOf(), val classIdToClearStatics: ClassId? = null, val instanceFieldReads: PersistentSet = persistentHashSetOf(), - val speculativelyNotNullAddresses: PersistentList = persistentListOf() + val speculativelyNotNullAddresses: PersistentList = persistentListOf(), + val symbolicEnumValues: PersistentList = persistentListOf() ) { operator fun plus(other: MemoryUpdate) = this.copy( @@ -986,7 +992,11 @@ data class MemoryUpdate( classIdToClearStatics = other.classIdToClearStatics, instanceFieldReads = instanceFieldReads.addAll(other.instanceFieldReads), speculativelyNotNullAddresses = speculativelyNotNullAddresses.addAll(other.speculativelyNotNullAddresses), + symbolicEnumValues = symbolicEnumValues.addAll(other.symbolicEnumValues), ) + + fun getSymbolicEnumValues(classId: ClassId): List = + symbolicEnumValues.filter { it.type.classId == classId } } // array - Java Array diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt index 93a1eba650..8729faba80 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt @@ -71,6 +71,7 @@ import kotlin.math.max import kotlin.math.min import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentSetOf +import org.utbot.framework.plugin.api.SYMBOLIC_NULL_ADDR import soot.ArrayType import soot.BooleanType import soot.ByteType @@ -325,7 +326,7 @@ class Resolver( val mockInfoEnriched = mockInfos.getValue(concreteAddr) val mockInfo = mockInfoEnriched.mockInfo - if (concreteAddr == NULL_ADDR) { + if (concreteAddr == SYMBOLIC_NULL_ADDR) { return UtNullModel(mockInfo.classId) } @@ -437,7 +438,7 @@ class Resolver( private fun resolveObject(objectValue: ObjectValue): UtModel { val concreteAddr = holder.concreteAddr(objectValue.addr) - if (concreteAddr == NULL_ADDR) { + if (concreteAddr == SYMBOLIC_NULL_ADDR) { return UtNullModel(objectValue.type.sootClass.id) } @@ -498,7 +499,7 @@ class Resolver( actualType: RefType, ): UtModel { val concreteAddr = holder.concreteAddr(addr) - if (concreteAddr == NULL_ADDR) { + if (concreteAddr == SYMBOLIC_NULL_ADDR) { return UtNullModel(defaultType.sootClass.id) } @@ -615,7 +616,7 @@ class Resolver( val modeledNumDimensions = holder.eval(numDimensionsArray.select(addrExpression)).intValue() val classRef = classRefByName(modeledType, modeledNumDimensions) - val model = UtClassRefModel(CLASS_REF_CLASS_ID, classRef) + val model = UtClassRefModel(addr, CLASS_REF_CLASS_ID, classRef) addConstructedModel(addr, model) return model @@ -640,7 +641,7 @@ class Resolver( clazz.enumConstants.indices.random() } val value = clazz.enumConstants[index] as Enum<*> - val model = UtEnumConstantModel(clazz.id, value) + val model = UtEnumConstantModel(addr, clazz.id, value) addConstructedModel(addr, model) return model @@ -795,7 +796,7 @@ class Resolver( */ private fun constructArrayModel(instance: ArrayValue): UtModel { val concreteAddr = holder.concreteAddr(instance.addr) - if (concreteAddr == NULL_ADDR) { + if (concreteAddr == SYMBOLIC_NULL_ADDR) { return UtNullModel(instance.type.id) } @@ -829,7 +830,7 @@ class Resolver( concreteAddr: Address, details: ArrayExtractionDetails, ): UtModel { - if (concreteAddr == NULL_ADDR) { + if (concreteAddr == SYMBOLIC_NULL_ADDR) { return UtNullModel(actualType.id) } @@ -903,7 +904,7 @@ class Resolver( elementType: ArrayType, details: ArrayExtractionDetails ): UtModel { - if (addr == NULL_ADDR) { + if (addr == SYMBOLIC_NULL_ADDR) { return UtNullModel(elementType.id) } @@ -927,7 +928,7 @@ class Resolver( * Uses [constructTypeOrNull] to evaluate possible element type. */ private fun arrayOfObjectsElementModel(concreteAddr: Address, defaultType: RefType): UtModel { - if (concreteAddr == NULL_ADDR) { + if (concreteAddr == SYMBOLIC_NULL_ADDR) { return UtNullModel(defaultType.id) } @@ -976,8 +977,7 @@ private data class ArrayExtractionDetails( val oneDimensionalArray: UtArrayExpressionBase ) -private const val NULL_ADDR = 0 -internal val nullObjectAddr = UtAddrExpression(mkInt(NULL_ADDR)) +internal val nullObjectAddr = UtAddrExpression(mkInt(SYMBOLIC_NULL_ADDR)) fun SymbolicValue.isNullObject() = diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt index 74c8d7b624..3712db126b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -497,7 +497,7 @@ class Traverser( val declaringClass = field.declaringClass val updates = if (declaringClass.isEnum) { - makeConcreteUpdatesForEnums(fieldId, declaringClass, stmt) + makeConcreteUpdatesForEnumsWithStmt(fieldId, declaringClass, stmt) } else { makeConcreteUpdatesForNonEnumStaticField(field, fieldId, declaringClass, stmt) } @@ -518,13 +518,10 @@ class Traverser( return true } - @Suppress("UnnecessaryVariable") - private fun makeConcreteUpdatesForEnums( - fieldId: FieldId, - declaringClass: SootClass, - stmt: Stmt - ): SymbolicStateUpdate { - val type = declaringClass.type + private fun makeConcreteUpdatesForEnum( + type: RefType, + fieldId: FieldId? = null + ): Pair { val jClass = type.id.jClass // symbolic value for enum class itself @@ -545,7 +542,7 @@ class Traverser( val (staticFieldUpdates, curFieldSymbolicValueForLocalVariable) = makeEnumStaticFieldsUpdates( staticFields, - declaringClass, + type.sootClass, enumConstantSymbolicResultsByName, enumConstantSymbolicValues, enumClassValue, @@ -564,14 +561,25 @@ class Traverser( val initializedStaticFieldsMemoryUpdate = MemoryUpdate( initializedStaticFields = staticFields.associate { it.first.fieldId to it.second.single() }.toPersistentMap(), - meaningfulStaticFields = meaningfulStaticFields.map { it.first.fieldId }.toPersistentSet() + meaningfulStaticFields = meaningfulStaticFields.map { it.first.fieldId }.toPersistentSet(), + symbolicEnumValues = enumConstantSymbolicValues.toPersistentList() ) - val allUpdates = staticFieldUpdates + - nonStaticFieldsUpdates + - initializedStaticFieldsMemoryUpdate + - createConcreteLocalValueUpdate(stmt, curFieldSymbolicValueForLocalVariable) + return Pair( + staticFieldUpdates + nonStaticFieldsUpdates + initializedStaticFieldsMemoryUpdate, + curFieldSymbolicValueForLocalVariable + ) + } + @Suppress("UnnecessaryVariable") + private fun makeConcreteUpdatesForEnumsWithStmt( + fieldId: FieldId, + declaringClass: SootClass, + stmt: Stmt + ): SymbolicStateUpdate { + val (enumUpdates, curFieldSymbolicValueForLocalVariable) = + makeConcreteUpdatesForEnum(declaringClass.type, fieldId) + val allUpdates = enumUpdates + createConcreteLocalValueUpdate(stmt, curFieldSymbolicValueForLocalVariable) return allUpdates } @@ -1381,6 +1389,39 @@ class Traverser( queuedSymbolicStateUpdates = queuedSymbolicStateUpdates.copy(memoryUpdates = MemoryUpdate()) } + /** + * Return a symbolic value of the ordinal corresponding to the enum value with the given address. + */ + private fun findEnumOrdinal(type: RefType, addr: UtAddrExpression): PrimitiveValue { + val array = memory.findArray(MemoryChunkDescriptor(ENUM_ORDINAL, type, IntType.v())) + return array.select(addr).toIntValue() + } + + /** + * Initialize enum class: create symbolic values for static enum values and generate constraints + * that restrict the new instance to match one of enum values. + */ + private fun initEnum(type: RefType, addr: UtAddrExpression, ordinal: PrimitiveValue) { + val classId = type.id + var predefinedEnumValues = memory.getSymbolicEnumValues(classId) + if (predefinedEnumValues.isEmpty()) { + val (enumValuesUpdate, _) = makeConcreteUpdatesForEnum(type) + queuedSymbolicStateUpdates += enumValuesUpdate + predefinedEnumValues = enumValuesUpdate.memoryUpdates.getSymbolicEnumValues(classId) + } + + val enumValueConstraints = mkOr( + listOf(addrEq(addr, nullObjectAddr)) + predefinedEnumValues.map { + mkAnd( + addrEq(addr, it.addr), + mkEq(ordinal, findEnumOrdinal(it.type, it.addr)) + ) + } + ) + + queuedSymbolicStateUpdates += enumValueConstraints.asHardConstraint() + } + private fun arrayInstanceOf(value: ArrayValue, checkType: Type): PrimitiveValue { val notNullConstraint = mkNot(addrEq(value.addr, nullObjectAddr)) @@ -1538,6 +1579,11 @@ class Traverser( queuedSymbolicStateUpdates += typeConstraint.asHardConstraint() queuedSymbolicStateUpdates += typeRegistry.zeroDimensionConstraint(objectValue.addr).asHardConstraint() + // If we are casting to an enum class, we should initialize enum values and add value equality constraints + if (typeAfterCast.sootClass?.isEnum == true) { + initEnum(typeAfterCast, castedObject.addr, findEnumOrdinal(typeAfterCast, castedObject.addr)) + } + // TODO add memory constraints JIRA:1523 return castedObject } @@ -1986,13 +2032,13 @@ class Traverser( queuedSymbolicStateUpdates += typeRegistry.typeConstraint(addr, typeStorage).all().asHardConstraint() - val array = memory.findArray(MemoryChunkDescriptor(ENUM_ORDINAL, type, IntType.v())) - val ordinal = array.select(addr).toIntValue() + val ordinal = findEnumOrdinal(type, addr) val enumSize = classLoader.loadClass(type.sootClass.name).enumConstants.size queuedSymbolicStateUpdates += mkOr(Ge(ordinal, 0), addrEq(addr, nullObjectAddr)).asHardConstraint() queuedSymbolicStateUpdates += mkOr(Lt(ordinal, enumSize), addrEq(addr, nullObjectAddr)).asHardConstraint() + initEnum(type, addr, ordinal) touchAddress(addr) return ObjectValue(typeStorage, addr) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/util/statics/concrete/EnumConcreteUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/util/statics/concrete/EnumConcreteUtils.kt index 96329b4c01..f4ca0a88be 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/util/statics/concrete/EnumConcreteUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/util/statics/concrete/EnumConcreteUtils.kt @@ -68,7 +68,7 @@ fun Traverser.makeEnumStaticFieldsUpdates( enumConstantSymbolicResultsByName: Map, enumConstantSymbolicValues: List, enumClassValue: ObjectValue, - fieldId: FieldId + fieldId: FieldId? ): Pair { var staticFieldsUpdates = SymbolicStateUpdate() var symbolicValueForLocal: SymbolicValue? = null @@ -101,7 +101,7 @@ fun Traverser.makeEnumStaticFieldsUpdates( } // save value to associate it with local if required - if (sootStaticField.name == fieldId.name) { + if (fieldId != null && sootStaticField.name == fieldId.name) { symbolicValueForLocal = fieldSymbolicValue } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/external/api/UtModelFactory.kt b/utbot-framework/src/main/kotlin/org/utbot/external/api/UtModelFactory.kt index 2f0c10ce54..35f2756e94 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/external/api/UtModelFactory.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/external/api/UtModelFactory.kt @@ -67,8 +67,10 @@ class UtModelFactory( elements.toMutableMap() ) - fun produceClassRefModel(clazz: Class): UtModel = UtClassRefModel( - classIdForType(clazz), clazz + fun produceClassRefModel(clazz: Class<*>) = UtClassRefModel( + modelIdCounter.incrementAndGet(), + classIdForType(clazz), + clazz ) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt index 0f13a1a958..baa62c3890 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt @@ -101,13 +101,13 @@ internal class CgVariableConstructor(val context: CgContext) : is UtCompositeModel -> constructComposite(model, baseName) is UtAssembleModel -> constructAssemble(model, baseName) is UtArrayModel -> constructArray(model, baseName) + is UtEnumConstantModel -> constructEnumConstant(model, baseName) + is UtClassRefModel -> constructClassRef(model, baseName) } } else valueByModel.getOrPut(model) { when (model) { is UtNullModel -> nullLiteral() is UtPrimitiveModel -> CgLiteral(model.classId, model.value) - is UtEnumConstantModel -> constructEnumConstant(model, baseName) - is UtClassRefModel -> constructClassRef(model, baseName) is UtReferenceModel -> error("Unexpected UtReferenceModel: ${model::class}") is UtVoidModel -> error("Unexpected UtVoidModel: ${model::class}") } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockValueConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockValueConstructor.kt index 170f9c743b..eca3609bd9 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockValueConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockValueConstructor.kt @@ -121,7 +121,7 @@ class MockValueConstructor( when (model) { is UtNullModel -> UtConcreteValue(null, model.classId.jClass) is UtPrimitiveModel -> UtConcreteValue(model.value, model.classId.jClass) - is UtEnumConstantModel -> UtConcreteValue(model.value) + is UtEnumConstantModel -> UtConcreteValue(constructEnum(model)) is UtClassRefModel -> UtConcreteValue(model.value) is UtCompositeModel -> UtConcreteValue(constructObject(model), model.classId.jClass) is UtArrayModel -> UtConcreteValue(constructArray(model)) @@ -130,6 +130,15 @@ class MockValueConstructor( } } + /** + * Constructs an Enum<*> instance by model, uses reference-equality cache. + */ + private fun constructEnum(model: UtEnumConstantModel): Any { + constructedObjects[model]?.let { return it } + constructedObjects[model] = model.value + return model.value + } + /** * Constructs object by model, uses reference-equality cache. * diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtExecutionInstrumentation.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtExecutionInstrumentation.kt index 6a7ca07ec2..09ac24c414 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtExecutionInstrumentation.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtExecutionInstrumentation.kt @@ -178,7 +178,16 @@ object UtExecutionInstrumentation : Instrumentation { val stateAfterParametersWithThis = params.map { construct(it.value, it.clazz.id) } val stateAfterStatics = (staticFields.keys/* + traceHandler.computePutStatics()*/) .associateWith { fieldId -> - fieldId.jField.run { construct(withAccessibility { get(null) }, fieldId.type) } + fieldId.jField.run { + val computedValue = withAccessibility { get(null) } + val knownModel = stateBefore.statics[fieldId] + val knownValue = staticFields[fieldId] + if (knownModel != null && knownValue != null && knownValue == computedValue) { + knownModel + } else { + construct(computedValue, fieldId.type) + } + } } val (stateAfterThis, stateAfterParameters) = if (stateBefore.thisInstance == null) { null to stateAfterParametersWithThis diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtModelConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtModelConstructor.kt index 7a8c4ff2f4..2d18f00654 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtModelConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtModelConstructor.kt @@ -218,14 +218,14 @@ internal class UtModelConstructor( private fun constructFromEnum(enum: Enum<*>): UtModel = constructedObjects.getOrElse(enum) { - val utModel = UtEnumConstantModel(enum::class.java.id, enum) + val utModel = UtEnumConstantModel(handleId(enum), enum::class.java.id, enum) constructedObjects[enum] = utModel utModel } private fun constructFromClass(clazz: Class<*>): UtModel = constructedObjects.getOrElse(clazz) { - val utModel = UtClassRefModel(clazz::class.java.id, clazz) + val utModel = UtClassRefModel(handleId(clazz), clazz::class.java.id, clazz) System.err.println("ClassRef: $clazz \t\tClassloader: ${clazz.classLoader}") constructedObjects[clazz] = utModel utModel diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/fields/ExecutionStateAnalyzer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/fields/ExecutionStateAnalyzer.kt index 64d6424f7b..d5ea151257 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/fields/ExecutionStateAnalyzer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/fields/ExecutionStateAnalyzer.kt @@ -160,23 +160,23 @@ private class FieldStateVisitor : UtModelVisitor() { ) } - override fun visit(element: UtClassRefModel, data: FieldData) { + override fun visit(element: UtNullModel, data: FieldData) { recordFieldState(data, element) } - override fun visit(element: UtEnumConstantModel, data: FieldData) { + override fun visit(element: UtPrimitiveModel, data: FieldData) { recordFieldState(data, element) } - override fun visit(element: UtNullModel, data: FieldData) { + override fun visit(element: UtVoidModel, data: FieldData) { recordFieldState(data, element) } - override fun visit(element: UtPrimitiveModel, data: FieldData) { + override fun visit(element: UtClassRefModel, data: FieldData) { recordFieldState(data, element) } - override fun visit(element: UtVoidModel, data: FieldData) { + override fun visit(element: UtEnumConstantModel, data: FieldData) { recordFieldState(data, element) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtModelVisitor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtModelVisitor.kt index b6d91a8ede..fd043ad3b2 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtModelVisitor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtModelVisitor.kt @@ -21,8 +21,6 @@ abstract class UtModelVisitor { abstract fun visit(element: UtModel, data: D) - abstract fun visit(element: UtClassRefModel, data: D) - abstract fun visit(element: UtEnumConstantModel, data: D) abstract fun visit(element: UtNullModel, data: D) abstract fun visit(element: UtPrimitiveModel, data: D) abstract fun visit(element: UtVoidModel, data: D) @@ -30,12 +28,16 @@ abstract class UtModelVisitor { open fun visit(element: UtReferenceModel, data: D) { if (!canTraverseReferenceModel(element)) return when (element) { + is UtClassRefModel -> visit(element, data) + is UtEnumConstantModel -> visit(element, data) is UtArrayModel -> visit(element, data) is UtAssembleModel -> visit(element, data) is UtCompositeModel -> visit(element, data) } } + abstract fun visit(element: UtClassRefModel, data: D) + abstract fun visit(element: UtEnumConstantModel, data: D) protected abstract fun visit(element: UtArrayModel, data: D) protected abstract fun visit(element: UtAssembleModel, data: D) protected abstract fun visit(element: UtCompositeModel, data: D) diff --git a/utbot-framework/src/test/java/org/utbot/examples/manual/UtBotJavaApiTest.java b/utbot-framework/src/test/java/org/utbot/examples/manual/UtBotJavaApiTest.java index d21fac1a7a..b55375531d 100644 --- a/utbot-framework/src/test/java/org/utbot/examples/manual/UtBotJavaApiTest.java +++ b/utbot-framework/src/test/java/org/utbot/examples/manual/UtBotJavaApiTest.java @@ -204,9 +204,7 @@ public void testCustomPackage() { fields ); - UtClassRefModel classRefModel = new UtClassRefModel( - classIdForType(Class.class), Class.class - ); + UtClassRefModel classRefModel = modelFactory.produceClassRefModel(Class.class); EnvironmentModels initialState = new EnvironmentModels( classUnderTestModel, @@ -389,9 +387,7 @@ public void testClassRef() { fields ); - UtClassRefModel classRefModel = new UtClassRefModel( - classIdForType(Class.class), Class.class - ); + UtClassRefModel classRefModel = modelFactory.produceClassRefModel(Class.class); EnvironmentModels initialState = new EnvironmentModels( classUnderTestModel, diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/enums/ComplexEnumExamplesTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/enums/ComplexEnumExamplesTest.kt new file mode 100644 index 0000000000..d67c6d83d0 --- /dev/null +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/enums/ComplexEnumExamplesTest.kt @@ -0,0 +1,109 @@ +package org.utbot.examples.enums + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.examples.UtValueTestCaseChecker +import org.utbot.examples.enums.ComplexEnumExamples.Color +import org.utbot.examples.enums.ComplexEnumExamples.Color.BLUE +import org.utbot.examples.enums.ComplexEnumExamples.Color.GREEN +import org.utbot.examples.enums.ComplexEnumExamples.Color.RED +import org.utbot.examples.eq +import org.utbot.examples.ignoreExecutionsNumber +import org.utbot.framework.codegen.CodeGeneration +import org.utbot.framework.plugin.api.CodegenLanguage + +class ComplexEnumExamplesTest : UtValueTestCaseChecker( + testClass = ComplexEnumExamples::class, + testCodeGeneration = true, + languagePipelines = listOf( + CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), + CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) + ) +) { + @Test + fun testEnumToEnumMapCountValues() { + check( + ComplexEnumExamples::enumToEnumMapCountValues, + ignoreExecutionsNumber, + { m, r -> m.isEmpty() && r == 0 }, + { m, r -> m.isNotEmpty() && !m.values.contains(RED) && r == 0 }, + { m, r -> m.isNotEmpty() && m.values.contains(RED) && m.values.count { it == RED } == r } + ) + } + + @Test + fun testEnumToEnumMapCountKeys() { + check( + ComplexEnumExamples::enumToEnumMapCountKeys, + ignoreExecutionsNumber, + { m, r -> m.isEmpty() && r == 0 }, + { m, r -> m.isNotEmpty() && !m.keys.contains(GREEN) && !m.keys.contains(BLUE) && r == 0 }, + { m, r -> m.isNotEmpty() && m.keys.intersect(setOf(BLUE, GREEN)).isNotEmpty() && m.keys.count { it == BLUE || it == GREEN } == r } + ) + } + + @Test + fun testEnumToEnumMapCountMatches() { + check( + ComplexEnumExamples::enumToEnumMapCountMatches, + ignoreExecutionsNumber, + { m, r -> m.isEmpty() && r == 0 }, + { m, r -> m.entries.count { it.key == it.value } == r } + ) + } + + @Test + fun testCountEqualColors() { + check( + ComplexEnumExamples::countEqualColors, + ignoreExecutionsNumber, + { a, b, c, r -> a == b && a == c && r == 3 }, + { a, b, c, r -> setOf(a, b, c).size == 2 && r == 2 }, + { a, b, c, r -> a != b && b != c && a != c && r == 1 } + ) + } + + @Test + fun testCountNullColors() { + check( + ComplexEnumExamples::countNullColors, + eq(3), + { a, b, r -> a == null && b == null && r == 2 }, + { a, b, r -> (a == null) != (b == null) && r == 1 }, + { a, b, r -> a != null && b != null && r == 0 }, + ) + } + + @Test + @Disabled("TODO: nested anonymous classes are not supported: https://github.com/UnitTestBot/UTBotJava/issues/617") + fun testFindState() { + check( + ComplexEnumExamples::findState, + ignoreExecutionsNumber, + { c, r -> c in setOf(0, 127, 255) && r != null && r.code == c } + ) + } + + @Test + fun testCountValuesInArray() { + fun Color.isCorrectlyCounted(inputs: Array, counts: Map): Boolean = + inputs.count { it == this } == (counts[this] ?: 0) + + check( + ComplexEnumExamples::countValuesInArray, + ignoreExecutionsNumber, + { cs, r -> cs.isEmpty() && r != null && r.isEmpty() }, + { cs, r -> cs.toList().isEmpty() && r != null && r.isEmpty() }, + { cs, r -> cs.toList().isNotEmpty() && r != null && Color.values().all { it.isCorrectlyCounted(cs, r) } } + ) + } + + @Test + fun testCountRedInArray() { + check( + ComplexEnumExamples::countRedInArray, + eq(3), + { colors, result -> colors.count { it == RED } == result } + ) + } +} diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt index 33f0179095..3b2575091f 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt @@ -47,7 +47,7 @@ fun defaultModelProviders(idGenerator: IntSupplier = SimpleIdGenerator()): Model ObjectModelProvider(idGenerator), CollectionModelProvider(idGenerator), ArrayModelProvider(idGenerator), - EnumModelProvider, + EnumModelProvider(idGenerator), ConstantsModelProvider, StringConstantModelProvider, CharToStringModelProvider, @@ -63,7 +63,7 @@ fun objectModelProviders(idGenerator: IntSupplier = SimpleIdGenerator()): ModelP return ModelProvider.of( CollectionModelProvider(idGenerator), ArrayModelProvider(idGenerator), - EnumModelProvider, + EnumModelProvider(idGenerator), StringConstantModelProvider, CharToStringModelProvider, ConstantsModelProvider, @@ -72,7 +72,7 @@ fun objectModelProviders(idGenerator: IntSupplier = SimpleIdGenerator()): ModelP ) } -private class SimpleIdGenerator : IntSupplier { +class SimpleIdGenerator : IntSupplier { private val id = AtomicInteger() override fun getAsInt() = id.incrementAndGet() } \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/EnumModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/EnumModelProvider.kt index 42540ee9b5..ccafd656d1 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/EnumModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/EnumModelProvider.kt @@ -8,16 +8,29 @@ import org.utbot.fuzzer.FuzzedMethodDescription import org.utbot.fuzzer.FuzzedParameter import org.utbot.fuzzer.ModelProvider import org.utbot.fuzzer.ModelProvider.Companion.yieldAllValues +import java.util.function.IntSupplier + +class EnumModelProvider : ModelProvider { + private val idGenerator: IntSupplier + private val limit: Int + private val idCache: MutableMap, Int> = mutableMapOf() + + constructor(idGenerator: IntSupplier) : this(idGenerator, Int.MAX_VALUE) + + constructor(idGenerator: IntSupplier, limit: Int) { + this.idGenerator = idGenerator + this.limit = limit + } -object EnumModelProvider : ModelProvider { override fun generate(description: FuzzedMethodDescription): Sequence = sequence { description.parametersMap .asSequence() .filter { (classId, _) -> classId.jClass.isEnum } .forEach { (classId, indices) -> yieldAllValues(indices, classId.jClass.enumConstants.filterIsInstance>().map { - UtEnumConstantModel(classId, it).fuzzed { summary = "%var% = ${it.name}" } + val id = idCache[it] ?: idGenerator.asInt + UtEnumConstantModel(id, classId, it).fuzzed { summary = "%var% = ${it.name}" } }) } } -} \ No newline at end of file +} diff --git a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt index 0216bf6942..c444d1aff5 100644 --- a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt +++ b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt @@ -29,9 +29,10 @@ import org.utbot.framework.plugin.api.util.primitiveByWrapper import org.utbot.framework.plugin.api.util.primitiveWrappers import org.utbot.framework.plugin.api.util.voidWrapperClassId import org.utbot.fuzzer.ModelProvider.Companion.yieldValue +import org.utbot.fuzzer.SimpleIdGenerator import org.utbot.fuzzer.defaultModelProviders +import org.utbot.fuzzer.providers.CharToStringModelProvider.fuzzed import org.utbot.fuzzer.providers.EnumModelProvider -import org.utbot.fuzzer.providers.EnumModelProvider.fuzzed import org.utbot.fuzzer.providers.PrimitiveDefaultsModelProvider import java.util.Date @@ -287,11 +288,11 @@ class ModelProviderTest { @Test fun `test enum model provider`() { withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect(EnumModelProvider, parameters = listOf(OneTwoThree::class.java.id)) + val result = collect(EnumModelProvider(SimpleIdGenerator()), parameters = listOf(OneTwoThree::class.java.id)) assertEquals(1, result.size) assertEquals(3, result[0]!!.size) OneTwoThree.values().forEachIndexed { index: Int, value -> - assertEquals(UtEnumConstantModel(OneTwoThree::class.java.id, value), result[0]!![index]) + assertEquals(UtEnumConstantModel(index + 1, OneTwoThree::class.java.id, value), result[0]!![index]) } } } diff --git a/utbot-sample/src/main/java/org/utbot/examples/enums/ComplexEnumExamples.java b/utbot-sample/src/main/java/org/utbot/examples/enums/ComplexEnumExamples.java new file mode 100644 index 0000000000..42ecf5d810 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/enums/ComplexEnumExamples.java @@ -0,0 +1,113 @@ +package org.utbot.examples.enums; + +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class ComplexEnumExamples { + + public enum Color { + RED, + GREEN, + BLUE + } + + public int countEqualColors(@NotNull Color a, @NotNull Color b, @NotNull Color c) { + int equalToA = 1; + if (b == a) { + equalToA++; + } + + if (c == a) { + equalToA++; + } + + int equalToB = 1; + if (a == b) { + equalToB++; + } + + if (c == b) { + equalToB++; + } + + if (equalToA > equalToB) { + return equalToA; + } else { + return equalToB; + } + } + + public int countNullColors(Color a, Color b) { + int nullCount = 0; + if (a == null) { + nullCount++; + } + + if (b == null) { + nullCount++; + } + + return nullCount; + } + + public int enumToEnumMapCountValues(@NotNull Map map) { + int count = 0; + for (Color color: map.values()) { + if (color == Color.RED) { + count++; + } + } + return count; + } + + public int enumToEnumMapCountKeys(@NotNull Map map) { + int count = 0; + for (Color key: map.keySet()) { + if (key == Color.GREEN || Color.BLUE.equals(key)) { + count++; + } else { + // Do nothing + } + } + return count; + } + + public int enumToEnumMapCountMatches(@NotNull Map map) { + int count = 0; + for (Map.Entry entry: map.entrySet()) { + if (entry.getKey() == entry.getValue() && entry.getKey() != null) { + count++; + } + } + return count; + } + + public State findState(int code) { + return State.findStateByCode(code); + } + + public Map countValuesInArray(Color @NotNull [] colors) { + HashMap counters = new HashMap<>(); + for (Color c : colors) { + if (c != null) { + Integer value = counters.getOrDefault(c, 0); + counters.put(c, value + 1); + } + } + return counters; + } + + public int countRedInArray(@NotNull Color @NotNull [] colors) { + int count = 0; + for (Color c : colors) { + if (c == Color.RED) { + count++; + } + } + return count; + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/enums/State.java b/utbot-sample/src/main/java/org/utbot/examples/enums/State.java new file mode 100644 index 0000000000..c8d0af1e3f --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/enums/State.java @@ -0,0 +1,41 @@ +package org.utbot.examples.enums; + +public enum State { + OPEN(255) { + @Override + public String toString() { + return ""; + } + }, + CLOSED(127) { + @Override + public String toString() { + return ""; + } + }, + UNKNOWN(0) { + @Override + public String toString() { + return ""; + } + }; + + private final int code; + + State(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public static State findStateByCode(int code) { + for (State state: values()) { + if (state.getCode() == code) { + return state; + } + } + return UNKNOWN; + } +} From 0b7cbc195a2b1c941ff4a8047970ab2ff35858b4 Mon Sep 17 00:00:00 2001 From: Dmitrii Timofeev Date: Wed, 3 Aug 2022 23:15:10 +0300 Subject: [PATCH 2/3] Fuzzer refactoring: id generator interface and caching id generator A custom id generator interface hierarchy is used instead of `IntSupplier`: * `IdGenerator` that allows to create fresh identifiers, * `IdentityPreservingIdGenerator`: `IdGenerator` that can return the same id for the same object. A default implementation of `IdentityPreservingIdGenerator` is used in fuzzer. It uses reference equality for object comparison, that allows to create distinct models of equal object (in `equals` sense), and to always assign the same id to the same enum value. --- .../utbot/framework/plugin/api/util/IdUtil.kt | 8 ++ .../org/utbot/engine/UtBotSymbolicEngine.kt | 12 +-- .../org/utbot/fuzzer/FallbackModelProvider.kt | 58 +++++++----- .../main/kotlin/org/utbot/fuzzer/Fuzzer.kt | 88 +++++++++++++++++-- .../fuzzer/providers/ArrayModelProvider.kt | 5 +- .../providers/CollectionModelProvider.kt | 5 +- .../fuzzer/providers/EnumModelProvider.kt | 19 +--- .../fuzzer/providers/ObjectModelProvider.kt | 31 +++---- .../framework/plugin/api/IdGeneratorTest.kt | 83 +++++++++++++++++ .../framework/plugin/api/ModelProviderTest.kt | 43 ++++++--- 10 files changed, 260 insertions(+), 92 deletions(-) create mode 100644 utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/IdGeneratorTest.kt diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt index 29e4cc8995..a233887008 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt @@ -117,6 +117,9 @@ val ClassId.isFloatType: Boolean val ClassId.isDoubleType: Boolean get() = this == doubleClassId || this == doubleWrapperClassId +val ClassId.isClassType: Boolean + get() = this == classClassId + val voidClassId = ClassId("void") val booleanClassId = ClassId("boolean") val byteClassId = ClassId("byte") @@ -138,6 +141,8 @@ val longWrapperClassId = java.lang.Long::class.id val floatWrapperClassId = java.lang.Float::class.id val doubleWrapperClassId = java.lang.Double::class.id +val classClassId = java.lang.Class::class.id + // We consider void wrapper as primitive wrapper // because voidClassId is considered primitive here val primitiveWrappers = setOf( @@ -285,6 +290,9 @@ val ClassId.isMap: Boolean val ClassId.isIterableOrMap: Boolean get() = isIterable || isMap +val ClassId.isEnum: Boolean + get() = jClass.isEnum + fun ClassId.findFieldByIdOrNull(fieldId: FieldId): Field? { if (isNotSubtypeOf(fieldId.declaringClass)) { return null diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt index 54c553b3f1..8762330a3b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -82,6 +82,7 @@ import org.utbot.framework.plugin.api.util.utContext import org.utbot.framework.plugin.api.util.description import org.utbot.framework.util.jimpleBody import org.utbot.framework.plugin.api.util.voidClassId +import org.utbot.fuzzer.ReferencePreservingIntIdGenerator import org.utbot.fuzzer.FallbackModelProvider import org.utbot.fuzzer.FuzzedMethodDescription import org.utbot.fuzzer.FuzzedValue @@ -114,8 +115,7 @@ class EngineController { //for debugging purpose only private var stateSelectedCount = 0 -//all id values of synthetic default models must be greater that for real ones -private var nextDefaultModelId = 1500_000_000 +private val defaultIdGenerator = ReferencePreservingIntIdGenerator() private fun pathSelector(graph: InterProceduralUnitGraph, typeRegistry: TypeRegistry) = when (pathSelectorType) { @@ -391,7 +391,7 @@ class UtBotSymbolicEngine( return@flow } - val fallbackModelProvider = FallbackModelProvider { nextDefaultModelId++ } + val fallbackModelProvider = FallbackModelProvider(defaultIdGenerator) val constantValues = collectConstantsForFuzzer(graph) val thisInstance = when { @@ -405,7 +405,7 @@ class UtBotSymbolicEngine( null } else -> { - ObjectModelProvider { nextDefaultModelId++ }.withFallback(fallbackModelProvider).generate( + ObjectModelProvider(ReferencePreservingIntIdGenerator()).withFallback(fallbackModelProvider).generate( FuzzedMethodDescription("thisInstance", voidClassId, listOf(methodUnderTest.clazz.id), constantValues) ).take(10).shuffled(Random(0)).map { it.value.model }.first().apply { if (this is UtNullModel) { // it will definitely fail because of NPE, @@ -427,14 +427,14 @@ class UtBotSymbolicEngine( var attempts = UtSettings.fuzzingMaxAttempts val hasMethodUnderTestParametersToFuzz = executableId.parameters.isNotEmpty() val fuzzedValues = if (hasMethodUnderTestParametersToFuzz) { - fuzz(methodUnderTestDescription, modelProvider(defaultModelProviders { nextDefaultModelId++ })) + fuzz(methodUnderTestDescription, modelProvider(defaultModelProviders(defaultIdGenerator))) } else { // in case a method with no parameters is passed fuzzing tries to fuzz this instance with different constructors, setters and field mutators val thisMethodDescription = FuzzedMethodDescription("thisInstance", voidClassId, listOf(methodUnderTest.clazz.id), constantValues).apply { className = executableId.classId.simpleName packageName = executableId.classId.packageName } - fuzz(thisMethodDescription, ObjectModelProvider { nextDefaultModelId++ }.apply { + fuzz(thisMethodDescription, ObjectModelProvider(defaultIdGenerator).apply { limitValuesCreatedByFieldAccessors = 500 }) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FallbackModelProvider.kt b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FallbackModelProvider.kt index 89f9c21aaf..83623de5b8 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FallbackModelProvider.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FallbackModelProvider.kt @@ -14,6 +14,8 @@ 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.isClassType +import org.utbot.framework.plugin.api.util.isEnum import org.utbot.framework.plugin.api.util.isIterable import org.utbot.framework.plugin.api.util.isPrimitive import org.utbot.framework.plugin.api.util.jClass @@ -31,7 +33,7 @@ import kotlin.reflect.KClass * Used as a fallback implementation until other providers cover every type. */ open class FallbackModelProvider( - private val idGenerator: IntSupplier + private val idGenerator: IdGenerator ): AbstractModelProvider() { override fun toModel(classId: ClassId): UtModel { @@ -46,11 +48,11 @@ open class FallbackModelProvider( it.parameters.isEmpty() && it.isPublic } return when { - classId.isPrimitive -> + classId.isPrimitive || classId.isEnum || classId.isClassType -> classId.defaultValueModel() classId.isArray -> UtArrayModel( - id = idGenerator.asInt, + id = idGenerator.createId(), classId, length = 0, classId.elementClassId!!.defaultValueModel(), @@ -81,26 +83,36 @@ open class FallbackModelProvider( 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() - 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 - ) + return when { + kclass.isAbstract -> { + // sealed class is abstract by itself + UtNullModel(kclass.java.id) + + } + kclass.java.isEnum || kclass == java.lang.Class::class -> { + // No sensible fallback solution for these classes except returning default `null` value + UtNullModel(kclass.java.id) + } + defaultConstructor != null -> { + val chain = mutableListOf() + val model = UtAssembleModel( + id = idGenerator.createId(), + kclass.id, + kclass.id.toString(), + chain + ) + chain.add( + UtExecutableCallModel(model, defaultConstructor.executableId, listOf(), model) + ) + model + } + else -> { + UtCompositeModel( + id = idGenerator.createId(), + kclass.id, + isMock = false + ) + } } } } diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt index 3b2575091f..c0dde7d1c5 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt @@ -12,12 +12,89 @@ import org.utbot.fuzzer.providers.PrimitiveDefaultsModelProvider import org.utbot.fuzzer.providers.EnumModelProvider import org.utbot.fuzzer.providers.PrimitiveWrapperModelProvider import java.lang.IllegalArgumentException +import java.util.IdentityHashMap import java.util.concurrent.atomic.AtomicInteger -import java.util.function.IntSupplier import kotlin.random.Random private val logger by lazy { KotlinLogging.logger {} } +/** + * Identifier generator interface for fuzzer model providers. + * + * Provides fresh identifiers for generated models. + * + * Warning: specific generators are not guaranteed to be thread-safe. + * + * @param Id the identifier type (e.g., [Int] for [UtReferenceModel] providers) + */ +interface IdGenerator { + /** + * Create a fresh identifier. Each subsequent call should return a different value. + * + * The method is not guaranteed to be thread-safe, unless an implementation makes such a guarantee. + */ + fun createId(): Id +} + +/** + * Identity preserving identifier generator interface. + * + * It allows to optionally save identifiers assigned to specific objects and later get the same identifiers + * for these objects instead of fresh identifiers. This feature is necessary, for example, to implement reference + * equality for enum models. + * + * Warning: specific generators are not guaranteed to be thread-safe. + * + * @param Id the identifier type (e.g., [Int] for [UtReferenceModel] providers) + */ +interface IdentityPreservingIdGenerator : IdGenerator { + /** + * Return an identifier for a specified non-null object. If an identifier has already been assigned + * to an object, subsequent calls should return the same identifier for this object. + * + * Note: the interface does not specify whether reference equality or regular `equals`/`compareTo` equality + * will be used to compare objects. Each implementation may provide these guarantees by itself. + * + * The method is not guaranteed to be thread-safe, unless an implementation makes such a guarantee. + */ + fun getOrCreateIdForValue(value: Any): Id +} + +/** + * An identity preserving id generator for fuzzer value providers that returns identifiers of type [Int]. + * + * When identity-preserving identifier is requested, objects are compared by reference. + * The generator is not thread-safe. + * + * @param lowerBound an integer value so that any generated identifier is strictly greater than it. + * + * Warning: when generating [UtReferenceModel] identifiers, no identifier should be equal to zero, + * as this value is reserved for [UtNullModel]. To guarantee it, [lowerBound] should never be negative. + * Avoid using custom lower bounds (maybe except fuzzer unit tests), use the predefined default value instead. + */ +class ReferencePreservingIntIdGenerator(lowerBound: Int = DEFAULT_LOWER_BOUND) : IdentityPreservingIdGenerator { + private val lastId: AtomicInteger = AtomicInteger(lowerBound) + private val cache: IdentityHashMap = IdentityHashMap() + + override fun getOrCreateIdForValue(value: Any): Int { + return cache.getOrPut(value) { createId() } + } + + override fun createId(): Int { + return lastId.incrementAndGet() + } + + companion object { + /** + * The default lower bound (all generated integer identifiers will be greater than it). + * + * It is defined as a large value because all synthetic [UtModel] instances + * must have greater identifiers than the real models. + */ + const val DEFAULT_LOWER_BOUND: Int = 1500_000_000 + } +} + fun fuzz(description: FuzzedMethodDescription, vararg modelProviders: ModelProvider): Sequence> { if (modelProviders.isEmpty()) { throw IllegalArgumentException("At least one model provider is required") @@ -42,7 +119,7 @@ fun fuzz(description: FuzzedMethodDescription, vararg modelProviders: ModelProvi /** * Creates a model provider from a list of default providers. */ -fun defaultModelProviders(idGenerator: IntSupplier = SimpleIdGenerator()): ModelProvider { +fun defaultModelProviders(idGenerator: IdentityPreservingIdGenerator): ModelProvider { return ModelProvider.of( ObjectModelProvider(idGenerator), CollectionModelProvider(idGenerator), @@ -59,7 +136,7 @@ fun defaultModelProviders(idGenerator: IntSupplier = SimpleIdGenerator()): Model /** * Creates a model provider for [ObjectModelProvider] that generates values for object constructor. */ -fun objectModelProviders(idGenerator: IntSupplier = SimpleIdGenerator()): ModelProvider { +fun objectModelProviders(idGenerator: IdentityPreservingIdGenerator): ModelProvider { return ModelProvider.of( CollectionModelProvider(idGenerator), ArrayModelProvider(idGenerator), @@ -71,8 +148,3 @@ fun objectModelProviders(idGenerator: IntSupplier = SimpleIdGenerator()): ModelP PrimitiveWrapperModelProvider, ) } - -class SimpleIdGenerator : IntSupplier { - private val id = AtomicInteger() - override fun getAsInt() = id.incrementAndGet() -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ArrayModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ArrayModelProvider.kt index 622853d505..02767a832b 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ArrayModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ArrayModelProvider.kt @@ -5,12 +5,13 @@ import org.utbot.framework.plugin.api.util.defaultValueModel import org.utbot.framework.plugin.api.util.isArray import org.utbot.fuzzer.FuzzedMethodDescription import org.utbot.fuzzer.FuzzedParameter +import org.utbot.fuzzer.IdGenerator import org.utbot.fuzzer.ModelProvider import org.utbot.fuzzer.ModelProvider.Companion.yieldAllValues import java.util.function.IntSupplier class ArrayModelProvider( - private val idGenerator: IntSupplier + private val idGenerator: IdGenerator ) : ModelProvider { override fun generate(description: FuzzedMethodDescription): Sequence = sequence { description.parametersMap @@ -19,7 +20,7 @@ class ArrayModelProvider( .forEach { (arrayClassId, indices) -> yieldAllValues(indices, listOf(0, 10).map { arraySize -> UtArrayModel( - id = idGenerator.asInt, + id = idGenerator.createId(), arrayClassId, length = arraySize, arrayClassId.elementClassId!!.defaultValueModel(), diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionModelProvider.kt index 828822687a..8d71c0bca5 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionModelProvider.kt @@ -11,6 +11,7 @@ import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.jClass import org.utbot.fuzzer.FuzzedMethodDescription import org.utbot.fuzzer.FuzzedParameter +import org.utbot.fuzzer.IdGenerator import org.utbot.fuzzer.ModelProvider import org.utbot.fuzzer.ModelProvider.Companion.yieldAllValues import java.util.function.IntSupplier @@ -23,7 +24,7 @@ import java.util.function.IntSupplier * a non-modifiable collection and tries to add values. */ class CollectionModelProvider( - private val idGenerator: IntSupplier + private val idGenerator: IdGenerator ) : ModelProvider { private val generators = mapOf( @@ -92,7 +93,7 @@ class CollectionModelProvider( private fun Class<*>.createdBy(init: ExecutableId, params: List = emptyList()): UtAssembleModel { val instantiationChain = mutableListOf() - val genId = idGenerator.asInt + val genId = idGenerator.createId() return UtAssembleModel( genId, id, diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/EnumModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/EnumModelProvider.kt index ccafd656d1..57aa0dcf13 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/EnumModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/EnumModelProvider.kt @@ -1,34 +1,21 @@ package org.utbot.fuzzer.providers import org.utbot.framework.plugin.api.UtEnumConstantModel -import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.isSubtypeOf import org.utbot.framework.plugin.api.util.jClass +import org.utbot.fuzzer.IdentityPreservingIdGenerator import org.utbot.fuzzer.FuzzedMethodDescription import org.utbot.fuzzer.FuzzedParameter import org.utbot.fuzzer.ModelProvider import org.utbot.fuzzer.ModelProvider.Companion.yieldAllValues -import java.util.function.IntSupplier - -class EnumModelProvider : ModelProvider { - private val idGenerator: IntSupplier - private val limit: Int - private val idCache: MutableMap, Int> = mutableMapOf() - - constructor(idGenerator: IntSupplier) : this(idGenerator, Int.MAX_VALUE) - - constructor(idGenerator: IntSupplier, limit: Int) { - this.idGenerator = idGenerator - this.limit = limit - } +class EnumModelProvider(private val idGenerator: IdentityPreservingIdGenerator) : ModelProvider { override fun generate(description: FuzzedMethodDescription): Sequence = sequence { description.parametersMap .asSequence() .filter { (classId, _) -> classId.jClass.isEnum } .forEach { (classId, indices) -> yieldAllValues(indices, classId.jClass.enumConstants.filterIsInstance>().map { - val id = idCache[it] ?: idGenerator.asInt + val id = idGenerator.getOrCreateIdForValue(it) UtEnumConstantModel(id, classId, it).fuzzed { summary = "%var% = ${it.name}" } }) } diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt index 7dadefe0cb..4a45f34257 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt @@ -15,6 +15,7 @@ import org.utbot.framework.plugin.api.util.isPrimitiveWrapper import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.stringClassId import org.utbot.framework.plugin.api.util.voidClassId +import org.utbot.fuzzer.IdentityPreservingIdGenerator import org.utbot.fuzzer.FuzzedConcreteValue import org.utbot.fuzzer.FuzzedMethodDescription import org.utbot.fuzzer.FuzzedParameter @@ -31,25 +32,24 @@ import java.lang.reflect.Field import java.lang.reflect.Member import java.lang.reflect.Method import java.lang.reflect.Modifier.* -import java.util.function.IntSupplier private val logger by lazy { KotlinLogging.logger {} } /** * Creates [UtAssembleModel] for objects which have public constructors with primitives types and String as parameters. */ -class ObjectModelProvider : ModelProvider { +class ObjectModelProvider( + private val idGenerator: IdentityPreservingIdGenerator, + private val limit: Int = Int.MAX_VALUE, + private val recursion: Int = 1 +) : ModelProvider { - var modelProvider: ModelProvider + var modelProvider: ModelProvider = objectModelProviders(idGenerator) var limitValuesCreatedByFieldAccessors: Int = 100 set(value) { field = maxOf(0, value) } - private val idGenerator: IntSupplier - private val recursion: Int - private val limit: Int - private val nonRecursiveModelProvider: ModelProvider get() { val modelProviderWithoutRecursion = modelProvider.exceptIsInstance() @@ -60,17 +60,6 @@ class ObjectModelProvider : ModelProvider { } } - constructor(idGenerator: IntSupplier) : this(idGenerator, Int.MAX_VALUE) - - constructor(idGenerator: IntSupplier, limit: Int) : this(idGenerator, limit, 1) - - private constructor(idGenerator: IntSupplier, limit: Int, recursion: Int) { - this.idGenerator = idGenerator - this.recursion = recursion - this.limit = limit - this.modelProvider = objectModelProviders(idGenerator) - } - override fun generate(description: FuzzedMethodDescription): Sequence = sequence { val fuzzedValues = with(description) { parameters.asSequence() @@ -90,12 +79,12 @@ class ObjectModelProvider : ModelProvider { } .flatMap { (constructorId, fuzzedParameters) -> if (constructorId.parameters.isEmpty()) { - sequenceOf(assembleModel(idGenerator.asInt, constructorId, emptyList())) + + sequenceOf(assembleModel(idGenerator.createId(), constructorId, emptyList())) + generateModelsWithFieldsInitialization(constructorId, description, concreteValues) } else { fuzzedParameters.map { params -> - assembleModel(idGenerator.asInt, constructorId, params) + assembleModel(idGenerator.createId(), constructorId, params) } } } @@ -123,7 +112,7 @@ class ObjectModelProvider : ModelProvider { return fuzz(syntheticClassFieldsSetterMethodDescription, nonRecursiveModelProvider) .take(limitValuesCreatedByFieldAccessors) // limit the number of fuzzed values in this particular case .map { fieldValues -> - val fuzzedModel = assembleModel(idGenerator.asInt, constructorId, emptyList()) + val fuzzedModel = assembleModel(idGenerator.createId(), constructorId, emptyList()) val assembleModel = fuzzedModel.model as? UtAssembleModel ?: error("Expected UtAssembleModel but ${fuzzedModel.model::class.java} found") val modificationChain = assembleModel.modificationsChain as? MutableList ?: error("Modification chain must be mutable") fieldValues.asSequence().mapIndexedNotNull { index, value -> diff --git a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/IdGeneratorTest.kt b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/IdGeneratorTest.kt new file mode 100644 index 0000000000..fbad13f551 --- /dev/null +++ b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/IdGeneratorTest.kt @@ -0,0 +1,83 @@ +package org.utbot.framework.plugin.api + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.utbot.fuzzer.ReferencePreservingIntIdGenerator + +class IdGeneratorTest { + private enum class Size { S, M, L, XL } + private enum class Letter { K, L, M, N } + + @Test + fun `default id generator returns sequential values`() { + val idGenerator = ReferencePreservingIntIdGenerator() + val a = idGenerator.createId() + val b = idGenerator.createId() + val c = idGenerator.createId() + assertTrue(a == ReferencePreservingIntIdGenerator.DEFAULT_LOWER_BOUND + 1) + assertTrue(b == a + 1) + assertTrue(c == b + 1) + } + + @Test + fun `caching generator returns different ids for different values`() { + val idGenerator = ReferencePreservingIntIdGenerator() + val a = idGenerator.getOrCreateIdForValue("a") + val b = idGenerator.getOrCreateIdForValue("b") + assertNotEquals(a, b) + } + + @Test + fun `caching generator returns same ids for same values`() { + val idGenerator = ReferencePreservingIntIdGenerator() + val a = idGenerator.getOrCreateIdForValue("a") + idGenerator.getOrCreateIdForValue("b") + val c = idGenerator.getOrCreateIdForValue("a") + assertEquals(a, c) + } + + @Test + fun `caching generator returns consistent ids for enum values`() { + val idGenerator = ReferencePreservingIntIdGenerator(0) + val sizeIds = Size.values().map { it to idGenerator.getOrCreateIdForValue(it) }.toMap() + + assertEquals(idGenerator.getOrCreateIdForValue(Size.S), sizeIds[Size.S]) + assertEquals(idGenerator.getOrCreateIdForValue(Size.M), sizeIds[Size.M]) + assertEquals(idGenerator.getOrCreateIdForValue(Size.L), sizeIds[Size.L]) + assertEquals(idGenerator.getOrCreateIdForValue(Size.XL), sizeIds[Size.XL]) + + idGenerator.getOrCreateIdForValue(Letter.N) + idGenerator.getOrCreateIdForValue(Letter.M) + idGenerator.getOrCreateIdForValue(Letter.L) + idGenerator.getOrCreateIdForValue(Letter.K) + + assertEquals(1, idGenerator.getOrCreateIdForValue(Size.S)) + assertEquals(2, idGenerator.getOrCreateIdForValue(Size.M)) + assertEquals(3, idGenerator.getOrCreateIdForValue(Size.L)) + assertEquals(4, idGenerator.getOrCreateIdForValue(Size.XL)) + + assertEquals(8, idGenerator.getOrCreateIdForValue(Letter.K)) + assertEquals(7, idGenerator.getOrCreateIdForValue(Letter.L)) + assertEquals(6, idGenerator.getOrCreateIdForValue(Letter.M)) + assertEquals(5, idGenerator.getOrCreateIdForValue(Letter.N)) + } + + @Test + fun `caching generator respects reference equality`() { + val idGenerator = ReferencePreservingIntIdGenerator() + + val objA = listOf(1, 2, 3) + val objB = listOf(1, 2, 3) + val objC = objA + + val idA = idGenerator.getOrCreateIdForValue(objA) + val idB = idGenerator.getOrCreateIdForValue(objB) + val idC = idGenerator.getOrCreateIdForValue(objC) + + assertNotEquals(idA, idB) + assertEquals(idA, idC) + } + +} diff --git a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt index c444d1aff5..772d65123e 100644 --- a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt +++ b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt @@ -28,8 +28,8 @@ import org.utbot.framework.plugin.api.samples.PackagePrivateFieldAndClass import org.utbot.framework.plugin.api.util.primitiveByWrapper import org.utbot.framework.plugin.api.util.primitiveWrappers import org.utbot.framework.plugin.api.util.voidWrapperClassId +import org.utbot.fuzzer.ReferencePreservingIntIdGenerator import org.utbot.fuzzer.ModelProvider.Companion.yieldValue -import org.utbot.fuzzer.SimpleIdGenerator import org.utbot.fuzzer.defaultModelProviders import org.utbot.fuzzer.providers.CharToStringModelProvider.fuzzed import org.utbot.fuzzer.providers.EnumModelProvider @@ -191,7 +191,7 @@ class ModelProviderTest { val classId = A::class.java.id val models = collect( - ObjectModelProvider { 0 }.apply { + ObjectModelProvider(ReferencePreservingIntIdGenerator(0)).apply { modelProvider = ModelProvider.of(PrimitiveDefaultsModelProvider) }, parameters = listOf(classId) @@ -219,7 +219,7 @@ class ModelProviderTest { val classId = A::class.java.id val models = collect( - ObjectModelProvider { 0 }, + ObjectModelProvider(ReferencePreservingIntIdGenerator(0)), parameters = listOf(classId) ) @@ -239,7 +239,7 @@ class ModelProviderTest { val classId = A::class.java.id val models = collect( - ObjectModelProvider { 0 }, + ObjectModelProvider(ReferencePreservingIntIdGenerator(0)), parameters = listOf(classId) ) @@ -277,7 +277,7 @@ class ModelProviderTest { fun `test collection model can produce basic values with assembled model`() { withUtContext(UtContext(this::class.java.classLoader)) { val result = collect( - defaultModelProviders { 0 }, + defaultModelProviders(ReferencePreservingIntIdGenerator(0)), parameters = listOf(java.util.List::class.java.id) ) @@ -288,7 +288,7 @@ class ModelProviderTest { @Test fun `test enum model provider`() { withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect(EnumModelProvider(SimpleIdGenerator()), parameters = listOf(OneTwoThree::class.java.id)) + val result = collect(EnumModelProvider(ReferencePreservingIntIdGenerator(0)), parameters = listOf(OneTwoThree::class.java.id)) assertEquals(1, result.size) assertEquals(3, result[0]!!.size) OneTwoThree.values().forEachIndexed { index: Int, value -> @@ -300,7 +300,10 @@ class ModelProviderTest { @Test fun `test string value generates only primitive models`() { withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect(defaultModelProviders { 0 }, parameters = listOf(stringClassId)) + val result = collect( + defaultModelProviders(ReferencePreservingIntIdGenerator(0)), + parameters = listOf(stringClassId) + ) assertEquals(1, result.size) result[0]!!.forEach { assertInstanceOf(UtPrimitiveModel::class.java, it) @@ -313,7 +316,10 @@ class ModelProviderTest { fun `test wrapper primitives generate only primitive models`() { withUtContext(UtContext(this::class.java.classLoader)) { primitiveWrappers.asSequence().filterNot { it == voidWrapperClassId }.forEach { classId -> - val result = collect(defaultModelProviders { 0 }, parameters = listOf(classId)) + val result = collect( + defaultModelProviders(ReferencePreservingIntIdGenerator(0)), + parameters = listOf(classId) + ) assertEquals(1, result.size) result[0]!!.forEach { assertInstanceOf(UtPrimitiveModel::class.java, it) @@ -328,7 +334,7 @@ class ModelProviderTest { fun `test at least one string is created if characters exist as constants`() { withUtContext(UtContext(this::class.java.classLoader)) { val result = collect( - defaultModelProviders { 0 }, + defaultModelProviders(ReferencePreservingIntIdGenerator(0)), parameters = listOf(stringClassId), constants = listOf( FuzzedConcreteValue(charClassId, 'a'), @@ -351,7 +357,10 @@ class ModelProviderTest { } withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect(ObjectModelProvider { 0 }, parameters = listOf(A::class.java.id)) + val result = collect( + ObjectModelProvider(ReferencePreservingIntIdGenerator(0)), + parameters = listOf(A::class.java.id) + ) assertEquals(1, result.size) assertEquals(1, result[0]!!.size) assertInstanceOf(UtAssembleModel::class.java, result[0]!![0]) @@ -379,7 +388,10 @@ class ModelProviderTest { } withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect(ObjectModelProvider { 0 }, parameters = listOf(MyA::class.java.id)) + val result = collect( + ObjectModelProvider(ReferencePreservingIntIdGenerator(0)), + parameters = listOf(MyA::class.java.id) + ) assertEquals(1, result.size) assertEquals(1, result[0]!!.size) val outerModel = result[0]!![0] as UtAssembleModel @@ -418,7 +430,10 @@ class ModelProviderTest { } withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect(ObjectModelProvider { 0 }, parameters = listOf(Outer::class.java.id)) + val result = collect( + ObjectModelProvider(ReferencePreservingIntIdGenerator(0)), + parameters = listOf(Outer::class.java.id) + ) assertEquals(1, result.size) assertEquals(1, result[0]!!.size) val outerModel = result[0]!![0] as UtAssembleModel @@ -462,7 +477,7 @@ class ModelProviderTest { ) withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect(ObjectModelProvider { 0 }.apply { + val result = collect(ObjectModelProvider(ReferencePreservingIntIdGenerator(0)).apply { modelProvider = PrimitiveDefaultsModelProvider }, parameters = listOf(FieldSetterClass::class.java.id)) assertEquals(1, result.size) @@ -490,7 +505,7 @@ class ModelProviderTest { ) withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect(ObjectModelProvider { 0 }.apply { + val result = collect(ObjectModelProvider(ReferencePreservingIntIdGenerator(0)).apply { modelProvider = PrimitiveDefaultsModelProvider }, parameters = listOf(PackagePrivateFieldAndClass::class.java.id)) { packageName = PackagePrivateFieldAndClass::class.java.`package`.name From b11b184a93087606318ec6b4935a3fc402f34e85 Mon Sep 17 00:00:00 2001 From: Dmitrii Timofeev Date: Thu, 4 Aug 2022 11:37:23 +0300 Subject: [PATCH 3/3] A unit test to check that enum ids in recursive models are consistent --- .../api/samples/InnerClassWithEnums.java | 29 ++++++++++++++ .../api/samples/OuterClassWithEnums.java | 37 +++++++++++++++++ .../plugin/api/samples/SampleEnum.java | 6 +++ .../framework/plugin/api/ModelProviderTest.kt | 40 +++++++++++++++++++ 4 files changed, 112 insertions(+) create mode 100644 utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/InnerClassWithEnums.java create mode 100644 utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/OuterClassWithEnums.java create mode 100644 utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/SampleEnum.java diff --git a/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/InnerClassWithEnums.java b/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/InnerClassWithEnums.java new file mode 100644 index 0000000000..3f3dcd8be0 --- /dev/null +++ b/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/InnerClassWithEnums.java @@ -0,0 +1,29 @@ +package org.utbot.framework.plugin.api.samples; + +import org.jetbrains.annotations.NotNull; + +public class InnerClassWithEnums { + private SampleEnum a; + private SampleEnum b; + + public InnerClassWithEnums(SampleEnum a, SampleEnum b) { + this.a = a; + this.b = b; + } + + public SampleEnum getA() { + return a; + } + + public void setA(SampleEnum a) { + this.a = a; + } + + public SampleEnum getB() { + return b; + } + + public void setB(SampleEnum b) { + this.b = b; + } +} diff --git a/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/OuterClassWithEnums.java b/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/OuterClassWithEnums.java new file mode 100644 index 0000000000..61354b2c52 --- /dev/null +++ b/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/OuterClassWithEnums.java @@ -0,0 +1,37 @@ +package org.utbot.framework.plugin.api.samples; + +public class OuterClassWithEnums { + private SampleEnum value; + private final InnerClassWithEnums left; + private final InnerClassWithEnums right; + + public OuterClassWithEnums(SampleEnum value, InnerClassWithEnums left, InnerClassWithEnums right) { + this.value = value; + this.left = left; + this.right = right; + } + + public void setValue(SampleEnum value) { + this.value = value; + } + + public SampleEnum getA() { + if (value == SampleEnum.LEFT && left != null) { + return left.getA(); + } else if (value == SampleEnum.RIGHT && right != null) { + return right.getA(); + } else { + return null; + } + } + + public SampleEnum getB() { + if (value == SampleEnum.LEFT && left != null) { + return left.getB(); + } else if (value == SampleEnum.RIGHT && right != null) { + return right.getB(); + } else { + return null; + } + } +} diff --git a/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/SampleEnum.java b/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/SampleEnum.java new file mode 100644 index 0000000000..1571254a70 --- /dev/null +++ b/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/SampleEnum.java @@ -0,0 +1,6 @@ +package org.utbot.framework.plugin.api.samples; + +public enum SampleEnum { + LEFT, + RIGHT +} diff --git a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt index 772d65123e..782ce6ee38 100644 --- a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt +++ b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt @@ -24,7 +24,9 @@ import org.utbot.fuzzer.providers.StringConstantModelProvider import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test import org.utbot.framework.plugin.api.samples.FieldSetterClass +import org.utbot.framework.plugin.api.samples.OuterClassWithEnums import org.utbot.framework.plugin.api.samples.PackagePrivateFieldAndClass +import org.utbot.framework.plugin.api.samples.SampleEnum import org.utbot.framework.plugin.api.util.primitiveByWrapper import org.utbot.framework.plugin.api.util.primitiveWrappers import org.utbot.framework.plugin.api.util.voidWrapperClassId @@ -523,6 +525,44 @@ class ModelProviderTest { } } + @Test + fun `test that enum models in a recursive object model are consistent`() { + withUtContext(UtContext(this::class.java.classLoader)) { + val idGenerator = ReferencePreservingIntIdGenerator() + val expectedIds = SampleEnum.values().associateWith { idGenerator.getOrCreateIdForValue(it) } + + val result = collect( + ObjectModelProvider(idGenerator), + parameters = listOf(OuterClassWithEnums::class.java.id) + ) + + assertEquals(1, result.size) + val models = result[0] ?: fail("Inconsistent result") + + for (model in models) { + val outerModel = (model as? UtAssembleModel) + ?.finalInstantiationModel as? UtExecutableCallModel + ?: fail("No final instantiation model found for the outer class") + for (param in outerModel.params) { + when (param) { + is UtEnumConstantModel -> { + assertEquals(expectedIds[param.value], param.id) + } + is UtAssembleModel -> { + for (enumParam in (param.finalInstantiationModel as UtExecutableCallModel).params) { + enumParam as UtEnumConstantModel + assertEquals(expectedIds[enumParam.value], enumParam.id) + } + } + else -> { + fail("Unexpected parameter model: $param") + } + } + } + } + } + } + private fun collect( modelProvider: ModelProvider, name: String = "testMethod",