Skip to content

Commit 976d420

Browse files
ArsenHDDamtev
authored andcommitted
Support anonymous classes
1 parent 42077da commit 976d420

File tree

10 files changed

+82
-58
lines changed

10 files changed

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

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

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

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

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

3421+
// We only remove anonymous classes if there are regular classes available.
3422+
// If there are no other options, then we do use anonymous classes.
34213423
workaround(REMOVE_ANONYMOUS_CLASSES) {
34223424
val sootClass = returnValue.type.sootClass
3423-
if (!environment.state.isInNestedMethod() && (sootClass.isAnonymous || sootClass.isArtificialEntity)) {
3425+
val isInNestedMethod = environment.state.isInNestedMethod()
3426+
3427+
if (!isInNestedMethod && sootClass.isArtificialEntity) {
3428+
return
3429+
}
3430+
3431+
val onlyAnonymousTypesAvailable = returnValue.typeStorage.possibleConcreteTypes.all { (it as? RefType)?.sootClass?.isAnonymous == true }
3432+
if (!isInNestedMethod && sootClass.isAnonymous && !onlyAnonymousTypesAvailable) {
34243433
return
34253434
}
34263435
}

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
@@ -473,15 +470,6 @@ class UtBotSymbolicEngine(
473470
// in case an exception occurred from the concrete execution
474471
concreteExecutionResult ?: return@forEach
475472

476-
workaround(REMOVE_ANONYMOUS_CLASSES) {
477-
concreteExecutionResult.result.onSuccess {
478-
if (it.classId.isAnonymous) {
479-
logger.debug("Anonymous class found as a concrete result, symbolic one will be returned")
480-
return@flow
481-
}
482-
}
483-
}
484-
485473
val coveredInstructions = concreteExecutionResult.coverage.coveredInstructions
486474
if (coveredInstructions.isNotEmpty()) {
487475
val coverageKey = coveredInstructionTracker.add(coveredInstructions)
@@ -591,16 +579,6 @@ class UtBotSymbolicEngine(
591579
instrumentation
592580
)
593581

594-
workaround(REMOVE_ANONYMOUS_CLASSES) {
595-
concreteExecutionResult.result.onSuccess {
596-
if (it.classId.isAnonymous) {
597-
logger.debug("Anonymous class found as a concrete result, symbolic one will be returned")
598-
emit(symbolicUtExecution)
599-
return
600-
}
601-
}
602-
}
603-
604582
val concolicUtExecution = symbolicUtExecution.copy(
605583
stateAfter = concreteExecutionResult.stateAfter,
606584
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) { utilsClassId[createInstance](model.classId.name) }
122+
val modelType = model.classId
123+
val variableType = if (modelType.isAnonymous) modelType.supertypeOfAnonymousClass else modelType
124+
newVar(variableType, baseName) { utilsClassId[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,
@@ -255,15 +256,21 @@ internal fun CgContextOwner.importIfNeeded(method: MethodId) {
255256
/**
256257
* Casts [expression] to [targetType].
257258
*
259+
* If [targetType] is anonymous, then we cannot use it in type cast,
260+
* because anonymous classes cannot be accessed in the source code.
261+
* So, in this case we find the supertype of anonymous class and use it as the [targetType] instead.
262+
*
258263
* @param isSafetyCast shows if we should render "as?" instead of "as" in Kotlin
259264
*/
260265
internal fun CgContextOwner.typeCast(
261266
targetType: ClassId,
262267
expression: CgExpression,
263268
isSafetyCast: Boolean = false
264-
): CgTypeCast {
265-
if (targetType.simpleName.isEmpty()) {
266-
error("Cannot cast an expression to the anonymous type $targetType")
269+
): CgExpression {
270+
@Suppress("NAME_SHADOWING")
271+
val targetType = when {
272+
targetType.isAnonymous -> targetType.supertypeOfAnonymousClass
273+
else -> targetType
267274
}
268275
importIfNeeded(targetType)
269276
return CgTypeCast(targetType, expression, isSafetyCast)

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

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package org.utbot.framework.codegen.model.util
22

33
import org.utbot.framework.plugin.api.ClassId
44
import org.utbot.framework.plugin.api.util.id
5-
import org.utbot.framework.plugin.api.util.isArray
65

76
/**
87
* For now we will count class accessible if it is:
@@ -14,19 +13,13 @@ import org.utbot.framework.plugin.api.util.isArray
1413
* @param packageName name of the package we check accessibility from
1514
*/
1615
infix fun ClassId.isAccessibleFrom(packageName: String): Boolean {
17-
18-
if (this.isLocal || this.isSynthetic) {
19-
return false
16+
val isOuterClassAccessible = if (isNested) {
17+
outerClass!!.id.isAccessibleFrom(packageName)
18+
} else {
19+
true
2020
}
2121

22-
val outerClassId = outerClass?.id
23-
if (outerClassId != null && !outerClassId.isAccessibleFrom(packageName)) {
24-
return false
25-
}
22+
val isAccessibleFromPackageByModifiers = isPublic || (this.packageName == packageName && (isPackagePrivate || isProtected))
2623

27-
return if (this.isArray) {
28-
elementClassId!!.isAccessibleFrom(packageName)
29-
} else {
30-
isPublic || (this.packageName == packageName && (isPackagePrivate || isProtected))
31-
}
24+
return isOuterClassAccessible && isAccessibleFromPackageByModifiers && !isLocal && !isAnonymous && !isSynthetic
3225
}

0 commit comments

Comments
 (0)