From c122e77e6a7b314c2f8651df5e68a9305773bd69 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Mon, 27 Mar 2023 13:21:19 +0300 Subject: [PATCH 1/7] Create speculativeNotNull implementation for Spring --- .../org/utbot/framework}/TrustedPackages.kt | 3 +- .../org/utbot/framework/plugin/api/Api.kt | 40 +++++++++++++++++++ .../utbot/framework/plugin/api/util/IdUtil.kt | 5 +++ .../org/utbot/engine/ArrayObjectWrappers.kt | 1 + .../engine/CollectionIteratorWrappers.kt | 1 + .../org/utbot/engine/CollectionWrappers.kt | 1 + .../kotlin/org/utbot/engine/Extensions.kt | 3 -- .../main/kotlin/org/utbot/engine/Memory.kt | 1 + .../main/kotlin/org/utbot/engine/Resolver.kt | 1 + .../kotlin/org/utbot/engine/ThreadWrappers.kt | 1 + .../main/kotlin/org/utbot/engine/Traverser.kt | 27 +++++-------- .../statics/concrete/EnumConcreteUtils.kt | 1 + .../modifications/ExecutablesAnalyzer.kt | 2 +- 13 files changed, 63 insertions(+), 24 deletions(-) rename {utbot-framework/src/main/kotlin/org/utbot/engine/util/trusted => utbot-framework-api/src/main/kotlin/org/utbot/framework}/TrustedPackages.kt (87%) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/util/trusted/TrustedPackages.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/TrustedPackages.kt similarity index 87% rename from utbot-framework/src/main/kotlin/org/utbot/engine/util/trusted/TrustedPackages.kt rename to utbot-framework-api/src/main/kotlin/org/utbot/framework/TrustedPackages.kt index 78584ae2c2..b4beb13441 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/util/trusted/TrustedPackages.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/TrustedPackages.kt @@ -1,6 +1,5 @@ -package org.utbot.engine.util.trusted +package org.utbot.framework -import org.utbot.framework.TrustedLibraries import soot.SootClass /** 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 3ddb0ef6ac..a84ebb2531 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 @@ -56,10 +56,14 @@ import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract import org.utbot.common.isAbstract import org.utbot.common.isStatic +import org.utbot.framework.isFromTrustedLibrary import org.utbot.framework.plugin.api.TypeReplacementMode.* +import org.utbot.framework.plugin.api.util.allDeclaredFieldIds +import org.utbot.framework.plugin.api.util.fieldId import org.utbot.framework.plugin.api.util.isSubtypeOf import org.utbot.framework.plugin.api.util.utContext import org.utbot.framework.process.OpenModulesContainer +import soot.SootField const val SYMBOLIC_NULL_ADDR: Int = 0 @@ -1196,6 +1200,7 @@ enum class TypeReplacementMode { * @param mockFrameworkInstalled shows if we have installed framework dependencies * @param staticsMockingIsConfigured shows if we have installed static mocking tools */ +@Suppress("KDocUnresolvedReference") open class ApplicationContext( val mockFrameworkInstalled: Boolean = true, staticsMockingIsConfigured: Boolean = true, @@ -1229,6 +1234,26 @@ open class ApplicationContext( * if it is guided with some additional information. */ open fun replaceTypeIfNeeded(type: RefType): ClassId? = null + + /** + * Sets the restrictions on speculative not null + * constraints in current application context. + * + * @see docs/SpeculativeFieldNonNullability.md for more information. + */ + open fun avoidSpeculativeNotNullChecks(field: SootField): Boolean = + UtSettings.maximizeCoverageUsingReflection || !field.declaringClass.isFromTrustedLibrary() + + /** + * Checks whether accessing [field] (with a method invocation or field access) speculatively + * cannot produce [NullPointerException] (according to its finality or accessibility). + * + * @see docs/SpeculativeFieldNonNullability.md for more information. + */ + open fun speculativelyCannotProduceNullPointerException( + field: SootField, + classUnderTest: ClassId, + ): Boolean = field.isFinal || !field.isPublic } /** @@ -1265,6 +1290,21 @@ class SpringApplicationContext( } else { null } + + override fun avoidSpeculativeNotNullChecks(field: SootField): Boolean = false + + /** + * In Spring applications we can mark as speculatively not null + * fields if they are mocked and injecting into class under test so on. + * + * Fields are not mocked if their actual type is obtained from [springInjectedClasses]. + * + */ + override fun speculativelyCannotProduceNullPointerException( + field: SootField, + classUnderTest: ClassId, + ): Boolean = + field.fieldId in classUnderTest.allDeclaredFieldIds && field.declaringClass.id !in springInjectedClasses } val RefType.isAbstractType 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 b1d7d9e710..70a2055010 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 @@ -11,6 +11,8 @@ import org.utbot.framework.plugin.api.MethodId 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.id +import soot.SootField import java.lang.reflect.Constructor import java.lang.reflect.Executable import java.lang.reflect.Field @@ -451,6 +453,9 @@ val ClassId.allDeclaredFieldIds: Sequence .flatMap { it.declaredFields.asSequence() } .map { it.fieldId } +val SootField.fieldId: FieldId + get() = FieldId(declaringClass.id, name) + // FieldId utils val FieldId.safeJField: Field? get() = declaringClass.jClass.declaredFields.firstOrNull { it.name == name } 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 7d0303e6e0..2b5e16b864 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt @@ -28,6 +28,7 @@ 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.getIdOrThrow +import org.utbot.framework.plugin.api.util.fieldId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.objectArrayClassId import org.utbot.framework.plugin.api.util.objectClassId diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionIteratorWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionIteratorWrappers.kt index 42a9390624..9eae85413f 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionIteratorWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionIteratorWrappers.kt @@ -11,6 +11,7 @@ import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtExecutableCallModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.util.fieldId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.methodId import soot.SootClass 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 705a6fb105..914a2edc21 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt @@ -31,6 +31,7 @@ import org.utbot.framework.util.graph import org.utbot.framework.plugin.api.id import org.utbot.framework.plugin.api.util.booleanClassId import org.utbot.framework.plugin.api.util.constructorId +import org.utbot.framework.plugin.api.util.fieldId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.methodId import org.utbot.framework.plugin.api.util.objectClassId diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt index 86fe89fe9b..0ed8aa6c0b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt @@ -285,9 +285,6 @@ val JimpleLocal.variable: LocalVariable val Type.defaultSymValue: UtExpression get() = toSort().defaultValue -val SootField.fieldId: FieldId - get() = FieldId(declaringClass.id, name) - val SootField.isEnumConstant: Boolean get() = name in declaringClass.id.enumConstants.orEmpty().map { enum -> enum.name } 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 26100d8853..4e79e42cf7 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt @@ -41,6 +41,7 @@ import org.utbot.engine.types.STRING_TYPE import org.utbot.engine.types.SeqType import org.utbot.engine.types.TypeResolver import org.utbot.framework.plugin.api.id +import org.utbot.framework.plugin.api.util.fieldId import org.utbot.framework.plugin.api.util.isEnum import soot.ArrayType import soot.CharType 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 e7a43e0048..3aa92d575b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt @@ -104,6 +104,7 @@ import org.utbot.framework.UtSettings import org.utbot.framework.plugin.api.visible.UtStreamConsumingException import org.utbot.framework.plugin.api.UtStreamConsumingFailure import org.utbot.framework.plugin.api.util.constructor.ValueConstructor +import org.utbot.framework.plugin.api.util.fieldId import org.utbot.framework.plugin.api.util.isStatic // hack diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt index 2400462da5..bb5bd68a23 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt @@ -23,6 +23,7 @@ import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.id import org.utbot.framework.plugin.api.util.constructorId import org.utbot.framework.plugin.api.util.defaultValueModel +import org.utbot.framework.plugin.api.util.fieldId import org.utbot.framework.plugin.api.util.intClassId import org.utbot.framework.plugin.api.util.objectClassId import org.utbot.framework.plugin.api.util.stringClassId 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 6134a38cae..71be00c91b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -105,7 +105,6 @@ import org.utbot.engine.types.OBJECT_TYPE import org.utbot.engine.types.SECURITY_FIELD_SIGNATURE import org.utbot.engine.types.TypeRegistry import org.utbot.engine.types.TypeResolver -import org.utbot.engine.util.trusted.isFromTrustedLibrary import org.utbot.engine.util.statics.concrete.associateEnumSootFieldsWithConcreteValues import org.utbot.engine.util.statics.concrete.isEnumAffectingExternalStatics import org.utbot.engine.util.statics.concrete.isEnumValuesFieldName @@ -113,9 +112,9 @@ import org.utbot.engine.util.statics.concrete.makeEnumNonStaticFieldsUpdates import org.utbot.engine.util.statics.concrete.makeEnumStaticFieldsUpdates import org.utbot.engine.util.statics.concrete.makeSymbolicValuesFromEnumConcreteValues import org.utbot.framework.UtSettings -import org.utbot.framework.UtSettings.maximizeCoverageUsingReflection import org.utbot.framework.UtSettings.preferredCexOption import org.utbot.framework.UtSettings.substituteStaticsWithSymbolicVariable +import org.utbot.framework.isFromTrustedLibrary import org.utbot.framework.plugin.api.ApplicationContext import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ExecutableId @@ -128,6 +127,7 @@ import org.utbot.framework.plugin.api.classId import org.utbot.framework.plugin.api.id import org.utbot.framework.plugin.api.isAbstractType import org.utbot.framework.plugin.api.util.executable +import org.utbot.framework.plugin.api.util.fieldId import org.utbot.framework.plugin.api.util.findFieldByIdOrNull import org.utbot.framework.plugin.api.util.jField import org.utbot.framework.plugin.api.util.jClass @@ -2322,35 +2322,26 @@ class Traverser( } /** - * Marks the [createdField] as speculatively not null if the [field] is considering as - * not producing [NullPointerException]. + * Marks the [createdField] as speculatively not null if the [field] is considering + * as not producing [NullPointerException]. * - * @see [SootField.speculativelyCannotProduceNullPointerException], [markAsSpeculativelyNotNull], [isFromTrustedLibrary]. + * See more detailed documentation in [ApplicationContext] mentioned methods. */ private fun checkAndMarkLibraryFieldSpeculativelyNotNull(field: SootField, createdField: SymbolicValue) { - if (maximizeCoverageUsingReflection || !field.declaringClass.isFromTrustedLibrary()) { + if (applicationContext.avoidSpeculativeNotNullChecks(field) || + !applicationContext.speculativelyCannotProduceNullPointerException(field, methodUnderTest.classId)) { return } - if (field.speculativelyCannotProduceNullPointerException()) { - markAsSpeculativelyNotNull(createdField.addr) - } + markAsSpeculativelyNotNull(createdField.addr) } - /** - * Checks whether accessing [this] field (with a method invocation or field access) speculatively can produce - * [NullPointerException] (according to its finality or accessibility). - * - * @see docs/SpeculativeFieldNonNullability.md for more information. - */ - @Suppress("KDocUnresolvedReference") - private fun SootField.speculativelyCannotProduceNullPointerException(): Boolean = isFinal || !isPublic - private fun createArray(pName: String, type: ArrayType): ArrayValue { val addr = UtAddrExpression(mkBVConst(pName, UtIntSort)) return createArray(addr, type, useConcreteType = false) } + /** * Creates an array with given [addr] and [type]. * 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 f369526901..4838dbe940 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 @@ -11,6 +11,7 @@ import org.utbot.engine.symbolic.SymbolicStateUpdate import org.utbot.engine.symbolic.asHardConstraint import org.utbot.engine.types.TypeResolver import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.util.fieldId import org.utbot.framework.plugin.api.util.jField import soot.SootClass import soot.SootField diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/modifications/ExecutablesAnalyzer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/modifications/ExecutablesAnalyzer.kt index 0859735b39..e4c966bdad 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/modifications/ExecutablesAnalyzer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/modifications/ExecutablesAnalyzer.kt @@ -1,10 +1,10 @@ package org.utbot.framework.modifications -import org.utbot.engine.fieldId import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.id +import org.utbot.framework.plugin.api.util.fieldId import org.utbot.framework.util.executableId import soot.Scene import soot.SootMethod From dae56eb1382ef0a9e31c0933012a761c0aa6f7ca Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Mon, 27 Mar 2023 17:46:11 +0300 Subject: [PATCH 2/7] Correct lazy initialization --- .../org/utbot/framework/plugin/api/Api.kt | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) 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 a84ebb2531..8584d8466c 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 @@ -1269,13 +1269,20 @@ class SpringApplicationContext( private val beanQualifiedNames: List = emptyList(), private val shouldUseImplementors: Boolean, ): ApplicationContext(mockInstalled, staticsMockingIsConfigured) { - - private val springInjectedClasses: List by lazy { - beanQualifiedNames - .map { fqn -> utContext.classLoader.loadClass(fqn) } - .filterNot { it.isAbstract || it.isInterface || it.isLocalClass || it.isMemberClass && !it.isStatic } - .map { it.id } - } + + private val springInjectedClasses: Set + get() { + if (springInjectedClassesStorage.isEmpty()) { + springInjectedClassesStorage = beanQualifiedNames + .map { fqn -> utContext.classLoader.loadClass(fqn) } + .filterNot { it.isAbstract || it.isInterface || it.isLocalClass || it.isMemberClass && !it.isStatic } + .mapTo(mutableSetOf()) { it.id } + } + + return springInjectedClassesStorage + } + + private var springInjectedClassesStorage = mutableSetOf() override val typeReplacementMode: TypeReplacementMode get() = if (shouldUseImplementors) KnownImplementor else NoImplementors @@ -1303,8 +1310,7 @@ class SpringApplicationContext( override fun speculativelyCannotProduceNullPointerException( field: SootField, classUnderTest: ClassId, - ): Boolean = - field.fieldId in classUnderTest.allDeclaredFieldIds && field.declaringClass.id !in springInjectedClasses + ): Boolean = field.fieldId in classUnderTest.allDeclaredFieldIds && field.declaringClass.id !in springInjectedClasses } val RefType.isAbstractType From 1fba46a879bb9988c290e9e6b9bfa0bd7ea1c7a0 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Mon, 27 Mar 2023 18:50:30 +0300 Subject: [PATCH 3/7] Update documentation --- docs/SpeculativeFieldNonNullability.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/SpeculativeFieldNonNullability.md b/docs/SpeculativeFieldNonNullability.md index 3ebcce6d08..0cd70fd67f 100644 --- a/docs/SpeculativeFieldNonNullability.md +++ b/docs/SpeculativeFieldNonNullability.md @@ -16,19 +16,31 @@ is desirable, as it increases the coverage, but it has a downside. It is possibl most of generated branches would be `NPE` branches, while useful paths could be lost due to timeout. Beyond that, in many cases the `null` value of a field can't be generated using the public API -of the class. This is particularly true for final fields, especially in system classes. +of the class. + +- First of all, this is particularly true for final fields, especially in system classes. it is also often true for non-public fields from standard library and third-party libraries (even setters often do not allow `null` values). Automatically generated tests assign `null` values to fields using reflection, but these tests may be uninformative as the corresponding `NPE` branches would never occur in the real code that limits itself to the public API. +- After that, field may be declared with some annotation that shows that null value is actually impossible. +For example, in Spring applications `@InjectMocks` and `@Mock` annotations on the fields of class under test +mean that these fields always have value, so `NPE` branches for them would never occur in real code. + + ## The solution To discard irrelevant `NPE` branches, we can speculatively mark fields we as non-nullable even they -do not have an explicit `@NotNull` annotation. In particular, we can use this approach to final and non-public -fields of system classes, as they are usually correctly initialized and are not equal `null`. +do not have an explicit `@NotNull` annotation. + +- In particular, we can use this approach to final and non-public +fields of system classes, as they are usually correctly initialized and are not equal `null` +- For Spring application, we use this approach for the fields of class +under test not obtained from Spring bean definitions -At the same time, we can't always add the "not null" hard constraint for the field: it would break +At the same time, for non-Spring classes, +we can't always add the "not null" hard constraint for the field: it would break some special cases like `Optional` class, which uses the `null` value of its final field as a marker of an empty value. From 2ff452b4167029b823401171e7bfbb517c86d4f6 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Mon, 27 Mar 2023 19:04:04 +0300 Subject: [PATCH 4/7] Little fixes under review --- .../src/main/kotlin/org/utbot/framework/plugin/api/Api.kt | 7 +++++-- .../src/main/kotlin/org/utbot/engine/Traverser.kt | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) 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 8584d8466c..6a0140eeac 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 @@ -1270,10 +1270,11 @@ class SpringApplicationContext( private val shouldUseImplementors: Boolean, ): ApplicationContext(mockInstalled, staticsMockingIsConfigured) { + // Classes representing concrete types that are actually used in Spring application private val springInjectedClasses: Set get() { if (springInjectedClassesStorage.isEmpty()) { - springInjectedClassesStorage = beanQualifiedNames + springInjectedClassesStorage += beanQualifiedNames .map { fqn -> utContext.classLoader.loadClass(fqn) } .filterNot { it.isAbstract || it.isInterface || it.isLocalClass || it.isMemberClass && !it.isStatic } .mapTo(mutableSetOf()) { it.id } @@ -1282,7 +1283,9 @@ class SpringApplicationContext( return springInjectedClassesStorage } - private var springInjectedClassesStorage = mutableSetOf() + // This is a service field to model the lazy behavior of [springInjectedClasses]. + // Do not call it outside the getter. + private val springInjectedClassesStorage = mutableSetOf() override val typeReplacementMode: TypeReplacementMode get() = if (shouldUseImplementors) KnownImplementor else NoImplementors 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 71be00c91b..4e58b65b8a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -2341,7 +2341,6 @@ class Traverser( return createArray(addr, type, useConcreteType = false) } - /** * Creates an array with given [addr] and [type]. * From cc0909f94eecb61681d9fb4f54544a6176a2017a Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Tue, 28 Mar 2023 10:11:53 +0300 Subject: [PATCH 5/7] A fix for potential performance problem --- .../src/main/kotlin/org/utbot/framework/plugin/api/Api.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 6a0140eeac..7c7cff16f8 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 @@ -61,6 +61,7 @@ import org.utbot.framework.plugin.api.TypeReplacementMode.* import org.utbot.framework.plugin.api.util.allDeclaredFieldIds import org.utbot.framework.plugin.api.util.fieldId import org.utbot.framework.plugin.api.util.isSubtypeOf +import org.utbot.framework.plugin.api.util.objectClassId import org.utbot.framework.plugin.api.util.utContext import org.utbot.framework.process.OpenModulesContainer import soot.SootField @@ -1200,7 +1201,6 @@ enum class TypeReplacementMode { * @param mockFrameworkInstalled shows if we have installed framework dependencies * @param staticsMockingIsConfigured shows if we have installed static mocking tools */ -@Suppress("KDocUnresolvedReference") open class ApplicationContext( val mockFrameworkInstalled: Boolean = true, staticsMockingIsConfigured: Boolean = true, @@ -1278,6 +1278,11 @@ class SpringApplicationContext( .map { fqn -> utContext.classLoader.loadClass(fqn) } .filterNot { it.isAbstract || it.isInterface || it.isLocalClass || it.isMemberClass && !it.isStatic } .mapTo(mutableSetOf()) { it.id } + + // This is done to be sure that this storage is not empty after the first class loading iteration. + // So, even if all loaded classes were filtered out, we will not try to load them again. + // Having `Object` in a list of injected classes is harmless from the point of abstract types replacements. + springInjectedClassesStorage += objectClassId } return springInjectedClassesStorage From b4632f5e5619d8303c6e7a6ac6f06d8bc8bdf195 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Tue, 28 Mar 2023 16:16:17 +0300 Subject: [PATCH 6/7] Apply review comments --- .../kotlin/org/utbot/framework/plugin/api/Api.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) 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 7c7cff16f8..a49829e58a 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 @@ -1270,10 +1270,12 @@ class SpringApplicationContext( private val shouldUseImplementors: Boolean, ): ApplicationContext(mockInstalled, staticsMockingIsConfigured) { + private var areInjectedClassesInitialized : Boolean = false + // Classes representing concrete types that are actually used in Spring application private val springInjectedClasses: Set get() { - if (springInjectedClassesStorage.isEmpty()) { + if (!areInjectedClassesInitialized && springInjectedClassesStorage.isEmpty()) { springInjectedClassesStorage += beanQualifiedNames .map { fqn -> utContext.classLoader.loadClass(fqn) } .filterNot { it.isAbstract || it.isInterface || it.isLocalClass || it.isMemberClass && !it.isStatic } @@ -1281,8 +1283,7 @@ class SpringApplicationContext( // This is done to be sure that this storage is not empty after the first class loading iteration. // So, even if all loaded classes were filtered out, we will not try to load them again. - // Having `Object` in a list of injected classes is harmless from the point of abstract types replacements. - springInjectedClassesStorage += objectClassId + areInjectedClassesInitialized = true } return springInjectedClassesStorage @@ -1290,10 +1291,13 @@ class SpringApplicationContext( // This is a service field to model the lazy behavior of [springInjectedClasses]. // Do not call it outside the getter. + // + // Actually, we should just call [springInjectedClasses] with `by lazy`, but we had problems + // with a strange `kotlin.UNINITIALIZED_VALUE` in `speculativelyCannotProduceNullPointerException` method call. private val springInjectedClassesStorage = mutableSetOf() - override val typeReplacementMode: TypeReplacementMode - get() = if (shouldUseImplementors) KnownImplementor else NoImplementors + override val typeReplacementMode: TypeReplacementMode = + if (shouldUseImplementors) KnownImplementor else NoImplementors /** * Replaces an interface type with its implementor type From 3ff81b430ae489d5ce333b0a4473f49f85e0d64d Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Tue, 28 Mar 2023 16:26:07 +0300 Subject: [PATCH 7/7] Correct initialization condition --- .../src/main/kotlin/org/utbot/framework/plugin/api/Api.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a49829e58a..e75910713b 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 @@ -1275,7 +1275,7 @@ class SpringApplicationContext( // Classes representing concrete types that are actually used in Spring application private val springInjectedClasses: Set get() { - if (!areInjectedClassesInitialized && springInjectedClassesStorage.isEmpty()) { + if (!areInjectedClassesInitialized) { springInjectedClassesStorage += beanQualifiedNames .map { fqn -> utContext.classLoader.loadClass(fqn) } .filterNot { it.isAbstract || it.isInterface || it.isLocalClass || it.isMemberClass && !it.isStatic }