Skip to content

Commit b65f7f8

Browse files
committed
Support anonymous classes
1 parent 058c682 commit b65f7f8

File tree

10 files changed

+77
-46
lines changed

10 files changed

+77
-46
lines changed

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
@@ -109,6 +109,34 @@ infix fun ClassId.isSubtypeOf(type: ClassId): Boolean {
109109

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

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

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

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

3384+
// We only remove anonymous classes if there are regular classes available.
3385+
// If there are no other options, then we do use anonymous classes.
33843386
workaround(REMOVE_ANONYMOUS_CLASSES) {
33853387
val sootClass = returnValue.type.sootClass
3386-
if (!environment.state.isInNestedMethod() && (sootClass.isAnonymous || sootClass.isArtificialEntity)) {
3388+
val isInNestedMethod = environment.state.isInNestedMethod()
3389+
3390+
if (!isInNestedMethod && sootClass.isArtificialEntity) {
3391+
return
3392+
}
3393+
3394+
val onlyAnonymousTypesAvailable = returnValue.typeStorage.possibleConcreteTypes.all { (it as? RefType)?.sootClass?.isAnonymous == true }
3395+
if (!isInNestedMethod && sootClass.isAnonymous && !onlyAnonymousTypesAvailable) {
33873396
return
33883397
}
33893398
}

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
@@ -107,7 +107,9 @@ class AssembleModelGenerator(private val methodUnderTest: UtMethod<*>) {
107107
}
108108

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

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

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,8 +495,8 @@ internal class CgStatementConstructorImpl(context: CgContext) :
495495
val isGetFieldUtilMethod = (expression is CgMethodCall && expression.executableId.isGetFieldUtilMethod)
496496
val shouldCastBeSafety = expression == nullLiteral() || isGetFieldUtilMethod
497497

498-
type = baseType
499498
expr = typeCast(baseType, expression, shouldCastBeSafety)
499+
type = expr.type
500500
}
501501
expression.type isNotSubtypeOf baseType && !typeAccessible -> {
502502
type = if (expression.type.isArray) objectArrayClassId else objectClassId

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import org.utbot.framework.plugin.api.util.builtinStaticMethodId
5252
import org.utbot.framework.plugin.api.util.methodId
5353
import org.utbot.framework.plugin.api.util.objectArrayClassId
5454
import org.utbot.framework.plugin.api.util.objectClassId
55+
import org.utbot.framework.plugin.api.util.supertypeOfAnonymousClass
5556

5657
internal data class EnvironmentFieldStateCache(
5758
val thisInstance: FieldStateCache,
@@ -248,15 +249,21 @@ internal fun CgContextOwner.importIfNeeded(method: MethodId) {
248249
/**
249250
* Casts [expression] to [targetType].
250251
*
252+
* If [targetType] is anonymous, then we cannot use it in type cast,
253+
* because anonymous classes cannot be accessed in the source code.
254+
* So, in this case we find the supertype of anonymous class and use it as the [targetType] instead.
255+
*
251256
* @param isSafetyCast shows if we should render "as?" instead of "as" in Kotlin
252257
*/
253258
internal fun CgContextOwner.typeCast(
254259
targetType: ClassId,
255260
expression: CgExpression,
256261
isSafetyCast: Boolean = false
257-
): CgTypeCast {
258-
if (targetType.simpleName.isEmpty()) {
259-
error("Cannot cast an expression to the anonymous type $targetType")
262+
): CgExpression {
263+
@Suppress("NAME_SHADOWING")
264+
val targetType = when {
265+
targetType.isAnonymous -> targetType.supertypeOfAnonymousClass
266+
else -> targetType
260267
}
261268
importIfNeeded(targetType)
262269
return CgTypeCast(targetType, expression, isSafetyCast)

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ClassIdUtil.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ infix fun ClassId.isAccessibleFrom(packageName: String): Boolean {
2121

2222
val isAccessibleFromPackageByModifiers = isPublic || (this.packageName == packageName && (isPackagePrivate || isProtected))
2323

24-
return isOuterClassAccessible && isAccessibleFromPackageByModifiers && !isLocal && !isSynthetic
24+
return isOuterClassAccessible && isAccessibleFromPackageByModifiers && !isLocal && !isAnonymous && !isSynthetic
2525
}

0 commit comments

Comments
 (0)