Skip to content

Commit a2c969c

Browse files
committed
Extract TypeReplacer and NonNullSpeculator, replace shouldUseImplementors with beanDefinitions.isNotEmpty()
1 parent 8d9930f commit a2c969c

File tree

14 files changed

+240
-198
lines changed

14 files changed

+240
-198
lines changed

utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ import org.utbot.framework.UtSettings.preferredCexOption
117117
import org.utbot.framework.UtSettings.substituteStaticsWithSymbolicVariable
118118
import org.utbot.framework.isFromTrustedLibrary
119119
import org.utbot.framework.context.ApplicationContext
120+
import org.utbot.framework.context.NonNullSpeculator
121+
import org.utbot.framework.context.TypeReplacer
120122
import org.utbot.framework.plugin.api.ClassId
121123
import org.utbot.framework.plugin.api.ExecutableId
122124
import org.utbot.framework.plugin.api.FieldId
@@ -239,7 +241,8 @@ class Traverser(
239241
internal val typeResolver: TypeResolver,
240242
private val globalGraph: InterProceduralUnitGraph,
241243
private val mocker: Mocker,
242-
private val applicationContext: ApplicationContext,
244+
private val typeReplacer: TypeReplacer,
245+
private val nonNullSpeculator: NonNullSpeculator,
243246
private val taintContext: TaintContext,
244247
) : UtContextInitializer() {
245248

@@ -1393,8 +1396,8 @@ class Traverser(
13931396
// However, if we have the restriction on implementor type (it may be obtained
13941397
// from Spring bean definitions, for example), we can just create a symbolic object
13951398
// with hard constraint on the mentioned type.
1396-
val replacedClassId = when (applicationContext.typeReplacementMode) {
1397-
KnownImplementor -> applicationContext.replaceTypeIfNeeded(type)
1399+
val replacedClassId = when (typeReplacer.typeReplacementMode) {
1400+
KnownImplementor -> typeReplacer.replaceTypeIfNeeded(type)
13981401
AnyImplementor,
13991402
NoImplementors -> null
14001403
}
@@ -1512,7 +1515,7 @@ class Traverser(
15121515
return createMockedObject(addr, type, mockInfoGenerator, nullEqualityConstraint)
15131516
}
15141517

1515-
val concreteImplementation: Concrete? = when (applicationContext.typeReplacementMode) {
1518+
val concreteImplementation: Concrete? = when (typeReplacer.typeReplacementMode) {
15161519
AnyImplementor -> findConcreteImplementation(addr, type, typeHardConstraint)
15171520

15181521
// If our type is not abstract, both in `KnownImplementors` and `NoImplementors` mode,
@@ -2336,7 +2339,7 @@ class Traverser(
23362339
* See more detailed documentation in [ApplicationContext] mentioned methods.
23372340
*/
23382341
private fun checkAndMarkLibraryFieldSpeculativelyNotNull(field: SootField, createdField: SymbolicValue) {
2339-
if (applicationContext.speculativelyCannotProduceNullPointerException(field, methodUnderTest.classId))
2342+
if (nonNullSpeculator.speculativelyCannotProduceNullPointerException(field, methodUnderTest.classId))
23402343
markAsSpeculativelyNotNull(createdField.addr)
23412344
}
23422345

utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ class UtBotSymbolicEngine(
179179
typeResolver,
180180
globalGraph,
181181
mocker,
182-
applicationContext,
182+
applicationContext.typeReplacer,
183+
applicationContext.nonNullSpeculator,
183184
taintContext,
184185
)
185186

utbot-framework/src/main/kotlin/org/utbot/framework/context/ApplicationContext.kt

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,12 @@
11
package org.utbot.framework.context
22

3-
import org.utbot.framework.plugin.api.ClassId
43
import org.utbot.framework.plugin.api.CodeGenerationContext
5-
import org.utbot.framework.plugin.api.TypeReplacementMode
64
import org.utbot.framework.plugin.api.UtError
7-
import soot.RefType
8-
import soot.SootField
95

106
interface ApplicationContext : CodeGenerationContext {
117
val mockerContext: MockerContext
12-
13-
/**
14-
* Shows if there are any restrictions on type implementors.
15-
*/
16-
val typeReplacementMode: TypeReplacementMode
17-
18-
/**
19-
* Finds a type to replace the original abstract type
20-
* if it is guided with some additional information.
21-
*/
22-
fun replaceTypeIfNeeded(type: RefType): ClassId?
23-
24-
/**
25-
* Checks whether accessing [field] (with a method invocation or field access) speculatively
26-
* cannot produce [NullPointerException] (according to its finality or accessibility).
27-
*
28-
* @see docs/SpeculativeFieldNonNullability.md for more information.
29-
*/
30-
fun speculativelyCannotProduceNullPointerException(
31-
field: SootField,
32-
classUnderTest: ClassId,
33-
): Boolean
8+
val typeReplacer: TypeReplacer
9+
val nonNullSpeculator: NonNullSpeculator
3410

3511
fun preventsFurtherTestGeneration(): Boolean
3612

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.utbot.framework.context
2+
3+
import org.utbot.framework.plugin.api.ClassId
4+
import soot.SootField
5+
6+
interface NonNullSpeculator {
7+
/**
8+
* Checks whether accessing [field] (with a method invocation or field access) speculatively
9+
* cannot produce [NullPointerException] (according to its finality or accessibility).
10+
*
11+
* @see docs/SpeculativeFieldNonNullability.md for more information.
12+
*/
13+
fun speculativelyCannotProduceNullPointerException(
14+
field: SootField,
15+
classUnderTest: ClassId,
16+
): Boolean
17+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.utbot.framework.context
2+
3+
import org.utbot.framework.plugin.api.ClassId
4+
import org.utbot.framework.plugin.api.TypeReplacementMode
5+
import soot.RefType
6+
7+
interface TypeReplacer {
8+
/**
9+
* Shows if there are any restrictions on type implementors.
10+
*/
11+
val typeReplacementMode: TypeReplacementMode
12+
13+
/**
14+
* Finds a type to replace the original abstract type
15+
* if it is guided with some additional information.
16+
*/
17+
fun replaceTypeIfNeeded(type: RefType): ClassId?
18+
}

utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleApplicationContext.kt

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,19 @@
11
package org.utbot.framework.context.simple
22

3-
import org.utbot.framework.UtSettings
43
import org.utbot.framework.context.ApplicationContext
54
import org.utbot.framework.context.MockerContext
6-
import org.utbot.framework.isFromTrustedLibrary
7-
import org.utbot.framework.plugin.api.ClassId
8-
import org.utbot.framework.plugin.api.TypeReplacementMode
5+
import org.utbot.framework.context.NonNullSpeculator
6+
import org.utbot.framework.context.TypeReplacer
97
import org.utbot.framework.plugin.api.UtError
10-
import soot.RefType
11-
import soot.SootField
128

139
/**
1410
* A context to use when no specific data is required.
1511
*/
1612
class SimpleApplicationContext(
17-
override val mockerContext: MockerContext
13+
override val mockerContext: MockerContext,
14+
override val typeReplacer: TypeReplacer = SimpleTypeReplacer(),
15+
override val nonNullSpeculator: NonNullSpeculator = SimpleNonNullSpeculator()
1816
) : ApplicationContext {
19-
override val typeReplacementMode: TypeReplacementMode = TypeReplacementMode.AnyImplementor
20-
21-
override fun replaceTypeIfNeeded(type: RefType): ClassId? = null
22-
23-
override fun speculativelyCannotProduceNullPointerException(
24-
field: SootField,
25-
classUnderTest: ClassId,
26-
): Boolean =
27-
!UtSettings.maximizeCoverageUsingReflection &&
28-
field.declaringClass.isFromTrustedLibrary() &&
29-
(field.isFinal || !field.isPublic)
3017

3118
override fun preventsFurtherTestGeneration(): Boolean = false
3219

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.utbot.framework.context.simple
2+
3+
import org.utbot.framework.UtSettings
4+
import org.utbot.framework.context.NonNullSpeculator
5+
import org.utbot.framework.isFromTrustedLibrary
6+
import org.utbot.framework.plugin.api.ClassId
7+
import soot.SootField
8+
9+
class SimpleNonNullSpeculator : NonNullSpeculator {
10+
override fun speculativelyCannotProduceNullPointerException(
11+
field: SootField,
12+
classUnderTest: ClassId,
13+
): Boolean =
14+
!UtSettings.maximizeCoverageUsingReflection &&
15+
field.declaringClass.isFromTrustedLibrary() &&
16+
(field.isFinal || !field.isPublic)
17+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.utbot.framework.context.simple
2+
3+
import org.utbot.framework.context.TypeReplacer
4+
import org.utbot.framework.plugin.api.ClassId
5+
import org.utbot.framework.plugin.api.TypeReplacementMode
6+
import soot.RefType
7+
8+
class SimpleTypeReplacer : TypeReplacer {
9+
override val typeReplacementMode: TypeReplacementMode = TypeReplacementMode.AnyImplementor
10+
11+
override fun replaceTypeIfNeeded(type: RefType): ClassId? = null
12+
}
Lines changed: 8 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -1,156 +1,24 @@
11
package org.utbot.framework.context.spring
22

3-
import mu.KotlinLogging
4-
import org.utbot.common.isAbstract
5-
import org.utbot.common.isStatic
63
import org.utbot.framework.context.ApplicationContext
74
import org.utbot.framework.plugin.api.BeanDefinitionData
85
import org.utbot.framework.plugin.api.ClassId
96
import org.utbot.framework.plugin.api.SpringCodeGenerationContext
107
import org.utbot.framework.plugin.api.SpringContextLoadingResult
11-
import org.utbot.framework.plugin.api.SpringSettings
12-
import org.utbot.framework.plugin.api.SpringTestType
13-
import org.utbot.framework.plugin.api.TypeReplacementMode
14-
import org.utbot.framework.plugin.api.UtError
15-
import org.utbot.framework.plugin.api.classId
16-
import org.utbot.framework.plugin.api.id
17-
import org.utbot.framework.plugin.api.isAbstractType
18-
import org.utbot.framework.plugin.api.util.allDeclaredFieldIds
19-
import org.utbot.framework.plugin.api.util.allSuperTypes
20-
import org.utbot.framework.plugin.api.util.fieldId
21-
import org.utbot.framework.plugin.api.util.id
22-
import org.utbot.framework.plugin.api.util.isSubtypeOf
23-
import org.utbot.framework.plugin.api.util.jClass
24-
import org.utbot.framework.plugin.api.util.utContext
25-
import soot.RefType
26-
import soot.SootField
278

289
/**
2910
* Data we get from Spring application context
3011
* to manage engine and code generator behaviour.
31-
*
32-
* @param beanDefinitions describes bean definitions (bean name, type, some optional additional data)
33-
* @param shouldUseImplementors describes it we want to replace interfaces with injected types or not
3412
*/
35-
// TODO move this class to utbot-framework so we can use it as abstract factory
36-
// to get rid of numerous `when`s and polymorphically create things like:
37-
// - Instrumentation<UtConcreteExecutionResult>
38-
// - FuzzedType (to get rid of thisInstanceFuzzedTypeWrapper)
39-
// - JavaValueProvider
40-
// - CgVariableConstructor
41-
// - CodeGeneratorResult (generateForSpringClass)
42-
// Right now this refactoring is blocked because some interfaces need to get extracted and moved to utbot-framework-api
43-
// As an alternative we can just move ApplicationContext itself to utbot-framework
44-
class SpringApplicationContext(
45-
private val delegateContext: ApplicationContext,
46-
val beanDefinitions: List<BeanDefinitionData> = emptyList(),
47-
private val shouldUseImplementors: Boolean,
48-
override val springTestType: SpringTestType,
49-
override val springSettings: SpringSettings,
50-
): ApplicationContext by delegateContext, SpringCodeGenerationContext {
51-
52-
override var springContextLoadingResult: SpringContextLoadingResult? = null
53-
54-
companion object {
55-
private val logger = KotlinLogging.logger {}
56-
}
57-
58-
private var areInjectedClassesInitialized : Boolean = false
59-
private var areAllInjectedTypesInitialized: Boolean = false
60-
61-
// Classes representing concrete types that are actually used in Spring application
62-
private val springInjectedClasses: Set<ClassId>
63-
get() {
64-
if (!areInjectedClassesInitialized) {
65-
for (beanTypeName in beanDefinitions.map { it.beanTypeName }) {
66-
try {
67-
val beanClass = utContext.classLoader.loadClass(beanTypeName)
68-
if (!beanClass.isAbstract && !beanClass.isInterface &&
69-
!beanClass.isLocalClass && (!beanClass.isMemberClass || beanClass.isStatic)) {
70-
springInjectedClassesStorage += beanClass.id
71-
}
72-
} catch (e: Throwable) {
73-
// For some Spring beans (e.g. with anonymous classes)
74-
// it is possible to have problems with classes loading.
75-
when (e) {
76-
is ClassNotFoundException, is NoClassDefFoundError, is IllegalAccessError ->
77-
logger.warn { "Failed to load bean class for $beanTypeName (${e.message})" }
78-
79-
else -> throw e
80-
}
81-
}
82-
}
83-
84-
// This is done to be sure that this storage is not empty after the first class loading iteration.
85-
// So, even if all loaded classes were filtered out, we will not try to load them again.
86-
areInjectedClassesInitialized = true
87-
}
88-
89-
return springInjectedClassesStorage
90-
}
91-
92-
private val allInjectedTypes: Set<ClassId>
93-
get() {
94-
if (!areAllInjectedTypesInitialized) {
95-
allInjectedTypesStorage = springInjectedClasses.flatMap { it.allSuperTypes() }.toSet()
96-
areAllInjectedTypesInitialized = true
97-
}
98-
99-
return allInjectedTypesStorage
100-
}
101-
102-
// imitates `by lazy` (we can't use actual `by lazy` because communication via RD breaks it)
103-
private var allInjectedTypesStorage: Set<ClassId> = emptySet()
104-
105-
// This is a service field to model the lazy behavior of [springInjectedClasses].
106-
// Do not call it outside the getter.
107-
//
108-
// Actually, we should just call [springInjectedClasses] with `by lazy`, but we had problems
109-
// with a strange `kotlin.UNINITIALIZED_VALUE` in `speculativelyCannotProduceNullPointerException` method call.
110-
private val springInjectedClassesStorage = mutableSetOf<ClassId>()
111-
112-
override val typeReplacementMode: TypeReplacementMode =
113-
if (shouldUseImplementors) TypeReplacementMode.KnownImplementor else TypeReplacementMode.NoImplementors
114-
13+
// TODO #2358
14+
interface SpringApplicationContext : ApplicationContext, SpringCodeGenerationContext {
11515
/**
116-
* Replaces an interface type with its implementor type
117-
* if there is the unique implementor in bean definitions.
16+
* Describes bean definitions (bean name, type, some optional additional data)
11817
*/
119-
override fun replaceTypeIfNeeded(type: RefType): ClassId? =
120-
if (type.isAbstractType) {
121-
springInjectedClasses.singleOrNull { it.isSubtypeOf(type.id) }
122-
} else {
123-
null
124-
}
125-
126-
/**
127-
* In Spring applications we can mark as speculatively not null
128-
* fields if they are mocked and injecting into class under test so on.
129-
*
130-
* Fields are not mocked if their actual type is obtained from [springInjectedClasses].
131-
*
132-
*/
133-
override fun speculativelyCannotProduceNullPointerException(
134-
field: SootField,
135-
classUnderTest: ClassId,
136-
): Boolean = field.fieldId in classUnderTest.allDeclaredFieldIds && field.type.classId !in allInjectedTypes
137-
138-
override fun preventsFurtherTestGeneration(): Boolean =
139-
delegateContext.preventsFurtherTestGeneration() || springContextLoadingResult?.contextLoaded == false
140-
141-
override fun getErrors(): List<UtError> =
142-
springContextLoadingResult?.exceptions?.map { exception ->
143-
UtError(
144-
"Failed to load Spring application context",
145-
exception
146-
)
147-
}.orEmpty() + delegateContext.getErrors()
18+
val beanDefinitions: List<BeanDefinitionData>
19+
val springInjectedClasses: Set<ClassId>
20+
val allInjectedTypes: Set<ClassId>
14821

149-
fun getBeansAssignableTo(classId: ClassId): List<BeanDefinitionData> = beanDefinitions.filter { beanDef ->
150-
// some bean classes may fail to load
151-
runCatching {
152-
val beanClass = ClassId(beanDef.beanTypeName).jClass
153-
classId.jClass.isAssignableFrom(beanClass)
154-
}.getOrElse { false }
155-
}
22+
override var springContextLoadingResult: SpringContextLoadingResult?
23+
fun getBeansAssignableTo(classId: ClassId): List<BeanDefinitionData>
15624
}

0 commit comments

Comments
 (0)