Skip to content

Commit 6f2b1a9

Browse files
committed
Support anonymous classes
1 parent 2e2d62a commit 6f2b1a9

File tree

14 files changed

+87
-52
lines changed

14 files changed

+87
-52
lines changed

utbot-core/src/main/kotlin/org/utbot/common/ReflectionUtil.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,4 @@ inline fun <reified R> Field.withAccessibility(block: () -> R): R {
7272
isAccessible = prevAccessibility
7373
setModifiers(this, prevModifiers)
7474
}
75-
}
75+
}

utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ object UtSettings {
151151
*
152152
* False by default, set it to true if debug visualization is needed.
153153
*/
154-
var useDebugVisualization by getBooleanProperty(false)
154+
var useDebugVisualization by getBooleanProperty(true)
155155

156156
/**
157157
* Set the value to true if you want to automatically copy the path of the

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import org.utbot.framework.plugin.api.util.method
3131
import org.utbot.framework.plugin.api.util.primitiveTypeJvmNameOrNull
3232
import org.utbot.framework.plugin.api.util.safeJField
3333
import org.utbot.framework.plugin.api.util.shortClassId
34+
import org.utbot.framework.plugin.api.util.supertypeOfAnonymousClass
3435
import org.utbot.framework.plugin.api.util.toReferenceTypeBytecodeSignature
3536
import org.utbot.framework.plugin.api.util.voidClassId
3637
import soot.ArrayType
@@ -697,8 +698,14 @@ open class ClassId @JvmOverloads constructor(
697698
*/
698699
val prettifiedName: String
699700
get() {
700-
val className = jClass.canonicalName ?: name // Explicit jClass reference to get null instead of exception
701-
return className
701+
val baseName = when {
702+
// anonymous classes have empty simpleName and their canonicalName is null,
703+
// so we create a specific name for them
704+
isAnonymous -> "Anonymous${supertypeOfAnonymousClass.prettifiedName}"
705+
// in other cases where canonical name is still null, we use ClassId.name instead
706+
else -> jClass.canonicalName ?: name // Explicit jClass reference to get null instead of exception
707+
}
708+
return baseName
702709
.substringAfterLast(".")
703710
.replace(Regex("[^a-zA-Z0-9]"), "")
704711
.let { if (this.isArray) it + "Array" else it }

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,34 @@ infix fun ClassId.isSubtypeOf(type: ClassId): Boolean {
108108

109109
infix fun ClassId.isNotSubtypeOf(type: ClassId): Boolean = !(this isSubtypeOf type)
110110

111+
/**
112+
* - Anonymous class that extends a class will have this class as its superclass and no interfaces.
113+
* - Anonymous class that implements an interface, will have the only interface
114+
* and [java.lang.Object] as its superclass.
115+
*
116+
* @return [ClassId] of a type that the given anonymous class inherits
117+
*/
118+
val ClassId.supertypeOfAnonymousClass: ClassId
119+
get() {
120+
if (this is BuiltinClassId) error("Cannot obtain info about supertypes of BuiltinClassId $canonicalName")
121+
if (!isAnonymous) error("An anonymous class expected, but got $canonicalName")
122+
123+
val clazz = jClass
124+
val superclass = clazz.superclass.id
125+
val interfaces = clazz.interfaces.map { it.id }
126+
127+
return when {
128+
// anonymous class actually inherits from Object, e.g. Object obj = new Object() { ... };
129+
superclass == objectClassId && interfaces.isEmpty() -> objectClassId
130+
// anonymous class implements some interface
131+
superclass == objectClassId -> {
132+
interfaces.singleOrNull() ?: error("Anonymous class can have no more than one interface")
133+
}
134+
// anonymous class inherits from some class other than java.lang.Object
135+
else -> superclass
136+
}
137+
}
138+
111139
val ClassId.kClass: KClass<*>
112140
get() = jClass.kotlin
113141

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,11 @@ private val isAnonymousRegex = ".*\\$\\d+$".toRegex()
199199
val SootClass.isAnonymous
200200
get() = name matches isAnonymousRegex
201201

202+
private val isLambdaRegex = ".*(\\$)lambda_.*".toRegex()
203+
204+
val SootClass.isLambda: Boolean
205+
get() = name matches isLambdaRegex
206+
202207
val Type.numDimensions get() = if (this is ArrayType) numDimensions else 0
203208

204209
/**

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3364,9 +3364,18 @@ class Traverser(
33643364
if (returnValue != null) {
33653365
queuedSymbolicStateUpdates += constructConstraintForType(returnValue, returnValue.possibleConcreteTypes).asSoftConstraint()
33663366

3367+
// We only remove anonymous classes if there are regular classes available.
3368+
// If there are no other options, then we do use anonymous classes.
33673369
workaround(REMOVE_ANONYMOUS_CLASSES) {
33683370
val sootClass = returnValue.type.sootClass
3369-
if (!environment.state.isInNestedMethod() && (sootClass.isAnonymous || sootClass.isArtificialEntity)) {
3371+
val isInNestedMethod = environment.state.isInNestedMethod()
3372+
3373+
if (!isInNestedMethod && sootClass.isArtificialEntity) {
3374+
return
3375+
}
3376+
3377+
val onlyAnonymousTypesAvailable = returnValue.typeStorage.possibleConcreteTypes.all { it.classId.isAnonymous }
3378+
if (!isInNestedMethod && sootClass.isAnonymous && !onlyAnonymousTypesAvailable) {
33703379
return
33713380
}
33723381
}

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

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.utbot.engine
22

33
import org.utbot.common.WorkaroundReason
4-
import org.utbot.common.heuristic
54
import org.utbot.common.workaround
65
import org.utbot.engine.pc.UtAddrExpression
76
import org.utbot.engine.pc.UtBoolExpression
@@ -200,19 +199,17 @@ class TypeResolver(private val typeRegistry: TypeRegistry, private val hierarchy
200199
val leastCommonSootClass = (leastCommonType as? RefType)?.sootClass
201200
val keepArtificialEntities = leastCommonSootClass?.isArtificialEntity == true
202201

203-
heuristic(WorkaroundReason.REMOVE_ANONYMOUS_CLASSES) {
204-
possibleConcreteTypes.forEach {
205-
val sootClass = (it.baseType as? RefType)?.sootClass ?: run {
206-
// All not RefType should be included in the concreteTypes, e.g., arrays
207-
concreteTypes += it
208-
return@forEach
209-
}
210-
when {
211-
sootClass.isAnonymous || sootClass.isUtMock -> unwantedTypes += it
212-
sootClass.isArtificialEntity -> if (keepArtificialEntities) concreteTypes += it else Unit
213-
workaround(WorkaroundReason.HACK) { leastCommonSootClass == OBJECT_TYPE && sootClass.isOverridden } -> Unit
214-
else -> concreteTypes += it
215-
}
202+
possibleConcreteTypes.forEach {
203+
val sootClass = (it.baseType as? RefType)?.sootClass ?: run {
204+
// All not RefType should be included in the concreteTypes, e.g., arrays
205+
concreteTypes += it
206+
return@forEach
207+
}
208+
when {
209+
sootClass.isAnonymous || sootClass.isUtMock -> unwantedTypes += it
210+
sootClass.isArtificialEntity -> if (keepArtificialEntities) concreteTypes += it else Unit
211+
workaround(WorkaroundReason.HACK) { leastCommonSootClass == OBJECT_TYPE && sootClass.isOverridden } -> Unit
212+
else -> concreteTypes += it
216213
}
217214
}
218215

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

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@ import mu.KotlinLogging
1717
import org.utbot.analytics.EngineAnalyticsContext
1818
import org.utbot.analytics.FeatureProcessor
1919
import org.utbot.analytics.Predictors
20-
import org.utbot.common.WorkaroundReason.REMOVE_ANONYMOUS_CLASSES
2120
import org.utbot.common.bracket
2221
import org.utbot.common.debug
23-
import org.utbot.common.workaround
2422
import org.utbot.engine.MockStrategy.NO_MOCKS
2523
import org.utbot.engine.pc.UtArraySelectExpression
2624
import org.utbot.engine.pc.UtBoolExpression
@@ -74,7 +72,6 @@ import org.utbot.framework.plugin.api.UtNullModel
7472
import org.utbot.framework.plugin.api.UtOverflowFailure
7573
import org.utbot.framework.plugin.api.UtResult
7674
import org.utbot.framework.plugin.api.UtSymbolicExecution
77-
import org.utbot.framework.plugin.api.onSuccess
7875
import org.utbot.framework.util.graph
7976
import org.utbot.framework.plugin.api.util.executableId
8077
import org.utbot.framework.plugin.api.util.id
@@ -462,15 +459,6 @@ class UtBotSymbolicEngine(
462459
// in case an exception occurred from the concrete execution
463460
concreteExecutionResult ?: return@forEach
464461

465-
workaround(REMOVE_ANONYMOUS_CLASSES) {
466-
concreteExecutionResult.result.onSuccess {
467-
if (it.classId.isAnonymous) {
468-
logger.debug("Anonymous class found as a concrete result, symbolic one will be returned")
469-
return@flow
470-
}
471-
}
472-
}
473-
474462
val coveredInstructions = concreteExecutionResult.coverage.coveredInstructions
475463
if (coveredInstructions.isNotEmpty()) {
476464
val count = coveredInstructionTracker.add(coveredInstructions)
@@ -575,16 +563,6 @@ class UtBotSymbolicEngine(
575563
instrumentation
576564
)
577565

578-
workaround(REMOVE_ANONYMOUS_CLASSES) {
579-
concreteExecutionResult.result.onSuccess {
580-
if (it.classId.isAnonymous) {
581-
logger.debug("Anonymous class found as a concrete result, symbolic one will be returned")
582-
emit(symbolicUtExecution)
583-
return
584-
}
585-
}
586-
}
587-
588566
val concolicUtExecution = symbolicUtExecution.copy(
589567
stateAfter = concreteExecutionResult.stateAfter,
590568
result = concreteExecutionResult.result,

utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ class AssembleModelGenerator(private val methodUnderTest: UtMethod<*>) {
106106
}
107107

108108
return IdentityHashMap<UtModel, UtModel>().apply {
109-
models.forEach { getOrPut(it) { assembleModel(it) } }
109+
models
110+
.filterNot { it.classId.isAnonymous } // we cannot create an assemble model for an anonymous class instance
111+
.forEach { getOrPut(it) { assembleModel(it) } }
110112
}
111113
}
112114

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import org.utbot.framework.plugin.api.util.intClassId
5454
import org.utbot.framework.plugin.api.util.isArray
5555
import org.utbot.framework.plugin.api.util.isPrimitiveWrapperOrString
5656
import org.utbot.framework.plugin.api.util.stringClassId
57+
import org.utbot.framework.plugin.api.util.supertypeOfAnonymousClass
5758
import org.utbot.framework.plugin.api.util.wrapperByPrimitive
5859
import java.lang.reflect.Field
5960
import java.lang.reflect.Modifier
@@ -118,7 +119,9 @@ internal class CgVariableConstructor(val context: CgContext) :
118119
val obj = if (model.isMock) {
119120
mockFrameworkManager.createMockFor(model, baseName)
120121
} else {
121-
newVar(model.classId, baseName) { testClassThisInstance[createInstance](model.classId.name) }
122+
val modelType = model.classId
123+
val variableType = if (modelType.isAnonymous) modelType.supertypeOfAnonymousClass else modelType
124+
newVar(variableType, baseName) { testClassThisInstance[createInstance](model.classId.name) }
122125
}
123126

124127
valueByModelId[model.id] = obj

0 commit comments

Comments
 (0)