Skip to content

Commit 297ca02

Browse files
committed
unfinished changes 22.07 (filtering of anonymous classes and lambdas)
1 parent 2f8b870 commit 297ca02

File tree

15 files changed

+177
-23
lines changed

15 files changed

+177
-23
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/Extensions.kt

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

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

203208
/**

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,8 @@ class Resolver(
510510
}
511511

512512
val sootClass = actualType.sootClass
513+
// TODO: We now allow anonymous classes and lambdas to be used if that is the only option.
514+
// In case sootClass represents a lambda, we will not be able to load its class here, so some workaround is required.
513515
val clazz = classLoader.loadClass(sootClass.name)
514516

515517
if (clazz.isEnum) {

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+
// TODO: do not remove anonymous classes for return value when it is the only option
3385+
// see: returnValue.typeStorage.possibleConcreteTypes
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.classId.isAnonymous }
3395+
if (!isInNestedMethod && sootClass.isAnonymous && !onlyAnonymousTypesAvailable) {
33873396
return
33883397
}
33893398
}

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,9 @@ class TypeResolver(private val typeRegistry: TypeRegistry, private val hierarchy
200200
val leastCommonSootClass = (leastCommonType as? RefType)?.sootClass
201201
val keepArtificialEntities = leastCommonSootClass?.isArtificialEntity == true
202202

203+
// TODO: remove this workaround reason, because now it is not a workaround.
204+
// Now we support lambdas, but only when they are the only option.
205+
// Remove this workaround everywhere in the code.
203206
heuristic(WorkaroundReason.REMOVE_ANONYMOUS_CLASSES) {
204207
possibleConcreteTypes.forEach {
205208
val sootClass = (it.baseType as? RefType)?.sootClass ?: run {
@@ -209,7 +212,13 @@ class TypeResolver(private val typeRegistry: TypeRegistry, private val hierarchy
209212
}
210213
when {
211214
sootClass.isAnonymous || sootClass.isUtMock -> unwantedTypes += it
212-
sootClass.isArtificialEntity -> if (keepArtificialEntities) concreteTypes += it else Unit
215+
sootClass.isArtificialEntity -> {
216+
if (sootClass.isLambda) {
217+
unwantedTypes += it
218+
} else if (keepArtificialEntities) {
219+
concreteTypes += it
220+
}
221+
}
213222
workaround(WorkaroundReason.HACK) { leastCommonSootClass == OBJECT_TYPE && sootClass.isOverridden } -> Unit
214223
else -> concreteTypes += it
215224
}

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -470,14 +470,15 @@ class UtBotSymbolicEngine(
470470
// in case an exception occurred from the concrete execution
471471
concreteExecutionResult ?: return@forEach
472472

473-
workaround(REMOVE_ANONYMOUS_CLASSES) {
474-
concreteExecutionResult.result.onSuccess {
475-
if (it.classId.isAnonymous) {
476-
logger.debug("Anonymous class found as a concrete result, symbolic one will be returned")
477-
return@flow
478-
}
479-
}
480-
}
473+
// do not remove concrete results of anonymous classes, because now we are able to generate code for them
474+
// workaround(REMOVE_ANONYMOUS_CLASSES) {
475+
// concreteExecutionResult.result.onSuccess {
476+
// if (it.classId.isAnonymous) {
477+
// logger.debug("Anonymous class found as a concrete result, symbolic one will be returned")
478+
// return@flow
479+
// }
480+
// }
481+
// }
481482

482483
val coveredInstructions = concreteExecutionResult.coverage.coveredInstructions
483484
if (coveredInstructions.isNotEmpty()) {

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

Lines changed: 4 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

@@ -228,6 +230,7 @@ class AssembleModelGenerator(private val methodUnderTest: UtMethod<*>) {
228230
}
229231

230232
try {
233+
// TODO: we can't use simpleName for anonymous classes, because it's empty
231234
val modelName = nextModelName(compositeModel.classId.jClass.simpleName.decapitalize())
232235

233236
val instantiationChain = mutableListOf<UtStatementModel>()

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: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,16 +248,26 @@ internal fun CgContextOwner.importIfNeeded(method: MethodId) {
248248
/**
249249
* Casts [expression] to [targetType].
250250
*
251+
* If [targetType] is anonymous, then we cannot use it in type cast,
252+
* because anonymous classes cannot be accessed in the source code.
253+
* So, in this case we find the supertype of anonymous class and use it as the [targetType] instead.
254+
*
251255
* @param isSafetyCast shows if we should render "as?" instead of "as" in Kotlin
252256
*/
253257
internal fun CgContextOwner.typeCast(
254258
targetType: ClassId,
255259
expression: CgExpression,
256260
isSafetyCast: Boolean = false
257-
): CgTypeCast {
258-
if (targetType.simpleName.isEmpty()) {
259-
error("Cannot cast an expression to the anonymous type $targetType")
261+
//): CgTypeCast {
262+
): CgExpression {
263+
@Suppress("NAME_SHADOWING")
264+
val targetType = when {
265+
targetType.isAnonymous -> targetType.supertypeOfAnonymousClass
266+
else -> targetType
260267
}
268+
// if (targetType.simpleName.isEmpty()) {
269+
// error("Cannot cast an expression to the anonymous type $targetType")
270+
// }
261271
importIfNeeded(targetType)
262272
return CgTypeCast(targetType, expression, isSafetyCast)
263273
}

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,31 @@ class CgTestMethodBuilder : CgMethodBuilder<CgTestMethod> {
8484

8585
fun buildTestMethod(init: CgTestMethodBuilder.() -> Unit) = CgTestMethodBuilder().apply(init).build()
8686

87+
class CgCustomMethodBuilder : CgMethodBuilder<CgCustomMethod> {
88+
override lateinit var name: String
89+
override lateinit var returnType: ClassId
90+
override lateinit var parameters: List<CgParameterDeclaration>
91+
override lateinit var statements: List<CgStatement>
92+
override val exceptions: MutableSet<ClassId> = mutableSetOf()
93+
override val annotations: MutableList<CgAnnotation> = mutableListOf()
94+
override var documentation: CgDocumentationComment = CgDocumentationComment(emptyList())
95+
var isStatic: Boolean = false
96+
97+
override fun build() = CgCustomMethod(
98+
name,
99+
returnType,
100+
parameters,
101+
statements,
102+
exceptions,
103+
annotations,
104+
isStatic,
105+
documentation
106+
)
107+
}
108+
109+
@Suppress("unused")
110+
fun buildCustomMethod(init: CgCustomMethodBuilder.() -> Unit) = CgCustomMethodBuilder().apply(init).build()
111+
87112
class CgErrorTestMethodBuilder : CgMethodBuilder<CgErrorTestMethod> {
88113
override lateinit var name: String
89114
override val returnType: ClassId = voidClassId

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

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ interface CgElement {
4040
is CgExecutableUnderTestCluster -> visit(element)
4141
is CgUtilMethod -> visit(element)
4242
is CgTestMethod -> visit(element)
43+
is CgCustomMethod -> visit(element)
4344
is CgErrorTestMethod -> visit(element)
4445
is CgParameterizedTestDataProviderMethod -> visit(element)
4546
is CgCommentedAnnotation -> visit(element)
@@ -211,6 +212,30 @@ class CgTestMethod(
211212
override val requiredFields: List<CgParameterDeclaration> = emptyList(),
212213
) : CgMethod(false)
213214

215+
/**
216+
* This class represents any custom method that we may want to create in the test class.
217+
* It may be some auxilliary method that by some reason has to be inside the test class.
218+
*
219+
* Note that before there was a class CgUtilMethod (now removed) that represented util methods,
220+
* but now all util methods have been moved into a separate library
221+
* in order to make the test class cleaner.
222+
*
223+
* So, use [CgCustomMethod] only if this method absolutely has to be in the test class.
224+
* If it doesn't, consider adding your method to util methods library instead.
225+
* This library sources can be found in the module `utbot-codegen-utils`.
226+
*/
227+
class CgCustomMethod(
228+
override val name: String,
229+
override val returnType: ClassId,
230+
override val parameters: List<CgParameterDeclaration>,
231+
override val statements: List<CgStatement>,
232+
override val exceptions: Set<ClassId>,
233+
override val annotations: List<CgAnnotation>,
234+
isStatic: Boolean,
235+
override val documentation: CgDocumentationComment = CgDocumentationComment(emptyList()),
236+
override val requiredFields: List<CgParameterDeclaration> = emptyList(),
237+
) : CgMethod(isStatic)
238+
214239
class CgErrorTestMethod(
215240
override val name: String,
216241
override val statements: List<CgStatement>,
@@ -562,10 +587,32 @@ class CgThisInstance(override val type: ClassId) : CgValue
562587

563588
// Variables
564589

590+
/**
591+
* @property name name of the variable
592+
* @property compileTimeType type a variable was declared with in the code.
593+
* For example, `List<Integer>` in `List<Integer> l = new ArrayList<>();`.
594+
* @property runtimeType actual type of an object stored in the variable.
595+
* For example, `ArrayList<Integer>` in `List<Integer> l = new ArrayList<>();`.
596+
*/
565597
open class CgVariable(
566598
val name: String,
567-
override val type: ClassId,
599+
val compileTimeType: ClassId,
600+
val runtimeType: ClassId
568601
) : CgValue {
602+
603+
/**
604+
* If [compileTimeType] and [runtimeType] are the same, a variable may be declared with this constructor.
605+
*/
606+
constructor(name: String, type: ClassId) : this(name, type, type)
607+
608+
/**
609+
* Property [type] inherited from [CgExpression] is delegated to [compileTimeType].
610+
* That's because when we access a variable in the code we can only work with it
611+
* through its compileTimeType interface, not knowing about its concrete implementation (runtime type).
612+
*/
613+
override val type: ClassId
614+
get() = compileTimeType
615+
569616
override fun equals(other: Any?): Boolean {
570617
if (this === other) return true
571618
if (javaClass != other?.javaClass) return false
@@ -590,11 +637,10 @@ open class CgVariable(
590637
}
591638

592639
/**
593-
* A variable with explicit not null annotation if this is required in language.
640+
* If expression is a variable, then this is a variable
641+
* with explicit not null annotation if this is required in language.
594642
*
595-
* Note:
596-
* - in Java it is an equivalent of [CgVariable]
597-
* - in Kotlin the difference is in addition of "!!" to the name
643+
* In Kotlin the difference is in addition of "!!" to the expression
598644
*/
599645
class CgNotNullAssertion(val expression: CgExpression) : CgValue {
600646
override val type: ClassId

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
@@ -15,7 +15,7 @@ import org.utbot.framework.plugin.api.util.isArray
1515
*/
1616
infix fun ClassId.isAccessibleFrom(packageName: String): Boolean {
1717

18-
if (this.isLocal || this.isSynthetic) {
18+
if (this.isLocal || this.isAnonymous || this.isSynthetic) {
1919
return false
2020
}
2121

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import org.utbot.framework.codegen.model.tree.CgComment
1616
import org.utbot.framework.codegen.model.tree.CgCommentedAnnotation
1717
import org.utbot.framework.codegen.model.tree.CgComparison
1818
import org.utbot.framework.codegen.model.tree.CgContinueStatement
19+
import org.utbot.framework.codegen.model.tree.CgCustomMethod
1920
import org.utbot.framework.codegen.model.tree.CgDeclaration
2021
import org.utbot.framework.codegen.model.tree.CgDecrement
2122
import org.utbot.framework.codegen.model.tree.CgDoWhileLoop
@@ -219,6 +220,12 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer:
219220
visit(element as CgMethod)
220221
}
221222

223+
override fun visit(element: CgCustomMethod) {
224+
renderMethodDocumentation(element)
225+
// for (annotation in element.annotations)
226+
// TODO
227+
}
228+
222229
override fun visit(element: CgErrorTestMethod) {
223230
renderMethodDocumentation(element)
224231
renderMethodSignature(element)

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import org.utbot.framework.codegen.model.tree.CgCommentedAnnotation
1515
import org.utbot.framework.codegen.model.tree.CgComparison
1616
import org.utbot.framework.codegen.model.tree.CgConstructorCall
1717
import org.utbot.framework.codegen.model.tree.CgContinueStatement
18+
import org.utbot.framework.codegen.model.tree.CgCustomMethod
1819
import org.utbot.framework.codegen.model.tree.CgDeclaration
1920
import org.utbot.framework.codegen.model.tree.CgDecrement
2021
import org.utbot.framework.codegen.model.tree.CgDoWhileLoop
@@ -102,6 +103,7 @@ interface CgVisitor<R> {
102103
// Methods
103104
fun visit(element: CgMethod): R
104105
fun visit(element: CgTestMethod): R
106+
fun visit(element: CgCustomMethod): R
105107
fun visit(element: CgErrorTestMethod): R
106108
fun visit(element: CgParameterizedTestDataProviderMethod): R
107109

0 commit comments

Comments
 (0)