Skip to content

Commit 9f1463a

Browse files
committed
WIP: support creation of models for lambdas and declaring variables for them in code generation
1 parent 5e32c19 commit 9f1463a

File tree

18 files changed

+354
-51
lines changed

18 files changed

+354
-51
lines changed

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,6 @@ data class UtAssembleModel(
530530
}
531531
}
532532

533-
// TODO: should lambda be a reference model?
534533
/**
535534
* Model for lambdas.
536535
*
@@ -540,14 +539,16 @@ data class UtAssembleModel(
540539
* Depending on the captured variables, this method will be either static or non-static.
541540
*
542541
* Since lambdas are not classes we cannot use a class loader to get info about them as we can do for other models.
543-
* Hence the necessity for this specific lambda model that will be processed differently: instead of working
542+
* Hence, the necessity for this specific lambda model that will be processed differently: instead of working
544543
* with a class we will be working with the synthetic method that represents our lambda.
545544
*/
546545
// TODO: what about support for Kotlin lambdas (they are not exactly the same as Java's due to functional types)
547546
class UtLambdaModel(
548547
override val id: Int?,
549-
override val classId: ClassId,
550-
) : UtReferenceModel(id, classId)
548+
val samType: ClassId,
549+
val declaringClass: ClassId,
550+
val lambdaName: String,
551+
) : UtReferenceModel(id, samType)
551552

552553
/**
553554
* Model for a step to obtain [UtAssembleModel].

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import kotlin.math.min
7272
import kotlinx.collections.immutable.persistentListOf
7373
import kotlinx.collections.immutable.persistentSetOf
7474
import org.utbot.framework.plugin.api.SYMBOLIC_NULL_ADDR
75+
import org.utbot.framework.plugin.api.UtLambdaModel
7576
import org.utbot.framework.plugin.api.UtSandboxFailure
7677
import soot.ArrayType
7778
import soot.BooleanType
@@ -512,6 +513,11 @@ class Resolver(
512513
val sootClass = actualType.sootClass
513514
// TODO: We now allow anonymous classes and lambdas to be used if that is the only option.
514515
// In case sootClass represents a lambda, we will not be able to load its class here, so some workaround is required.
516+
517+
if (sootClass.isLambda) {
518+
return constructLambda(concreteAddr, sootClass)
519+
}
520+
515521
val clazz = classLoader.loadClass(sootClass.name)
516522

517523
if (clazz.isEnum) {
@@ -633,6 +639,34 @@ class Resolver(
633639
return constructedType.classId.jClass
634640
}
635641

642+
private fun constructLambda(addr: Address, sootClass: SootClass): UtLambdaModel {
643+
val samType = sootClass.interfaces.singleOrNull()?.id
644+
?: error("Lambda must implement single interface, but ${sootClass.interfaces.size} found for ${sootClass.name}")
645+
646+
val declaringClass = classLoader.loadClass(sootClass.name.substringBeforeLast("\$lambda"))
647+
648+
// TODO: add comments describing the format of Soot class name for lambdas, because here we transform it into the actual synthetic method name
649+
val lambdaName = sootClass.name
650+
.let { name ->
651+
val start = name.lastIndexOf("\$lambda") + 1
652+
val end = name.lastIndexOf("__")
653+
name.substring(start, end)
654+
}
655+
.let {
656+
val builder = StringBuilder(it)
657+
builder[it.indexOfFirst { c -> c == '_' }] = '$'
658+
builder[it.indexOfLast { c -> c == '_' }] = '$'
659+
builder.toString()
660+
}
661+
662+
return UtLambdaModel(
663+
id = addr,
664+
samType = samType,
665+
declaringClass = declaringClass.id,
666+
lambdaName = lambdaName
667+
)
668+
}
669+
636670
private fun constructEnum(addr: Address, type: RefType, clazz: Class<*>): UtEnumConstantModel {
637671
val descriptor = MemoryChunkDescriptor(ENUM_ORDINAL, type, IntType.v())
638672
val array = findArray(descriptor, state)

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

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3382,24 +3382,23 @@ class Traverser(
33823382
queuedSymbolicStateUpdates += constructConstraintForType(returnValue, returnValue.possibleConcreteTypes).asSoftConstraint()
33833383

33843384
// TODO: do not remove anonymous classes for return value when it is the only option
3385-
// see: returnValue.typeStorage.possibleConcreteTypes
3386-
workaround(REMOVE_ANONYMOUS_CLASSES) {
3387-
val sootClass = returnValue.type.sootClass
3388-
val isInNestedMethod = environment.state.isInNestedMethod()
3389-
3390-
if (!isInNestedMethod && sootClass.isArtificialEntity) {
3391-
return
3392-
}
3393-
3394-
val onlyAnonymousTypesAndLambdasAvailable = returnValue.typeStorage.possibleConcreteTypes.all {
3395-
// only ref types can be anonymous or lambdas
3396-
val type = (it as? RefType)?.sootClass ?: return@all false
3397-
type.isLambda || type.isAnonymous
3398-
}
3385+
// workaround(REMOVE_ANONYMOUS_CLASSES) {
3386+
// val sootClass = returnValue.type.sootClass
3387+
// val isInNestedMethod = environment.state.isInNestedMethod()
3388+
//
3389+
// if (!isInNestedMethod && sootClass.isArtificialEntity) {
3390+
// return
3391+
// }
3392+
//
3393+
// val onlyAnonymousTypesAndLambdasAvailable = returnValue.typeStorage.possibleConcreteTypes.all {
3394+
// // only ref types can be anonymous or lambdas
3395+
// val type = (it as? RefType)?.sootClass ?: return@all false
3396+
// type.isLambda || type.isAnonymous
3397+
// }
33993398
// if (!isInNestedMethod && sootClass.isAnonymous && !onlyAnonymousTypesAndLambdasAvailable) {
34003399
// return
34013400
// }
3402-
}
3401+
// }
34033402
}
34043403

34053404
//fill arrays with default 0/null and other stuff

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import org.utbot.framework.plugin.api.UtExecution
2525
import org.utbot.framework.plugin.api.UtExecutionFailure
2626
import org.utbot.framework.plugin.api.UtExecutionResult
2727
import org.utbot.framework.plugin.api.UtExecutionSuccess
28+
import org.utbot.framework.plugin.api.UtLambdaModel
2829
import org.utbot.framework.plugin.api.UtMockValue
2930
import org.utbot.framework.plugin.api.UtModel
3031
import org.utbot.framework.plugin.api.UtNullModel
@@ -189,6 +190,7 @@ class ValueConstructor {
189190
is UtArrayModel -> UtConcreteValue(constructArray(model))
190191
is UtAssembleModel -> UtConcreteValue(constructFromAssembleModel(model))
191192
is UtVoidModel -> UtConcreteValue(Unit)
193+
is UtLambdaModel -> TODO("lambda model")
192194
}
193195
}
194196

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import org.utbot.framework.plugin.api.UtCompositeModel
2424
import org.utbot.framework.plugin.api.UtDirectSetFieldModel
2525
import org.utbot.framework.plugin.api.UtEnumConstantModel
2626
import org.utbot.framework.plugin.api.UtExecutableCallModel
27+
import org.utbot.framework.plugin.api.UtLambdaModel
2728
import org.utbot.framework.plugin.api.UtMethod
2829
import org.utbot.framework.plugin.api.UtModel
2930
import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation
@@ -189,6 +190,7 @@ class AssembleModelGenerator(private val methodUnderTest: UtMethod<*>) {
189190
is UtArrayModel -> assembleArrayModel(utModel)
190191
is UtCompositeModel -> assembleCompositeModel(utModel)
191192
is UtAssembleModel -> assembleAssembleModel(utModel)
193+
is UtLambdaModel -> throw AssembleException("lambda model") //TODO("lambda model")
192194
}
193195
} catch (e: AssembleException) {
194196
utModel

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package org.utbot.framework.codegen.model.constructor.builtin
22

33
import org.utbot.framework.codegen.MockitoStaticMocking
4+
import org.utbot.framework.codegen.model.constructor.util.arrayTypeOf
45
import org.utbot.framework.codegen.model.constructor.util.utilMethodId
56
import org.utbot.framework.codegen.model.tree.CgClassId
67
import org.utbot.framework.plugin.api.BuiltinClassId
78
import org.utbot.framework.plugin.api.ClassId
89
import org.utbot.framework.plugin.api.MethodId
910
import org.utbot.framework.plugin.api.util.booleanClassId
11+
import org.utbot.framework.plugin.api.util.classClassId
1012
import org.utbot.framework.plugin.api.util.id
1113
import org.utbot.framework.plugin.api.util.intClassId
1214
import org.utbot.framework.plugin.api.util.jClass
@@ -36,7 +38,10 @@ internal val ClassId.possibleUtilMethodIds: Set<MethodId>
3638
streamsDeepEqualsMethodId,
3739
mapsDeepEqualsMethodId,
3840
hasCustomEqualsMethodId,
39-
getArrayLengthMethodId
41+
getArrayLengthMethodId,
42+
buildStaticLambdaMethodId,
43+
buildLambdaMethodId,
44+
getSingleAbstractMethodMethodId
4045
)
4146

4247
internal val ClassId.getUnsafeInstanceMethodId: MethodId
@@ -146,6 +151,38 @@ internal val ClassId.getArrayLengthMethodId: MethodId
146151
arguments = arrayOf(objectClassId)
147152
)
148153

154+
internal val ClassId.buildStaticLambdaMethodId: MethodId
155+
get() = utilMethodId(
156+
name = "buildStaticLambda",
157+
returnType = objectClassId,
158+
arguments = arrayOf(
159+
classClassId,
160+
classClassId,
161+
stringClassId,
162+
arrayTypeOf(capturedArgumentClassId)
163+
)
164+
)
165+
166+
internal val ClassId.buildLambdaMethodId: MethodId
167+
get() = utilMethodId(
168+
name = "buildLambda",
169+
returnType = objectClassId,
170+
arguments = arrayOf(
171+
classClassId,
172+
classClassId,
173+
stringClassId,
174+
objectClassId,
175+
arrayTypeOf(capturedArgumentClassId)
176+
)
177+
)
178+
179+
internal val ClassId.getSingleAbstractMethodMethodId: MethodId
180+
get() = utilMethodId(
181+
name = "getSingleAbstractMethod",
182+
returnType = java.lang.reflect.Method::class.id,
183+
arguments = arrayOf(classClassId)
184+
)
185+
149186
/**
150187
* [MethodId] for [AutoCloseable.close].
151188
*/
@@ -181,3 +218,14 @@ internal val ClassId.closeMethodIdOrNull: MethodId?
181218
this is BuiltinClassId -> null
182219
else -> (jClass as? AutoCloseable)?.let { closeMethodId }
183220
}
221+
222+
// TODO: when util class branch is merged into main,
223+
// we should move this method into UtilMethodProvider,
224+
// so that class CapturedArgument is declared either in util class or in test class,
225+
// but for now it can only be declared in the test class
226+
val ClassId.capturedArgumentClassId: BuiltinClassId
227+
get() = BuiltinClassId(
228+
name = "${this.name}\$CapturedArgument",
229+
canonicalName = "${this.name}.CapturedArgument",
230+
simpleName = "CapturedArgument"
231+
)

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ import kotlinx.collections.immutable.persistentSetOf
4343
import org.utbot.framework.codegen.model.constructor.CgMethodTestSet
4444
import org.utbot.framework.codegen.model.constructor.TestClassContext
4545
import org.utbot.framework.codegen.model.constructor.TestClassModel
46+
import org.utbot.framework.codegen.model.constructor.builtin.buildLambdaMethodId
47+
import org.utbot.framework.codegen.model.constructor.builtin.buildStaticLambdaMethodId
48+
import org.utbot.framework.codegen.model.constructor.builtin.getSingleAbstractMethodMethodId
4649
import org.utbot.framework.codegen.model.constructor.builtin.streamsDeepEqualsMethodId
4750
import org.utbot.framework.codegen.model.tree.CgParameterKind
4851
import org.utbot.framework.plugin.api.BuiltinClassId
@@ -382,6 +385,15 @@ internal interface CgContextOwner {
382385

383386
val getArrayLength: MethodId
384387
get() = outerMostTestClass.getArrayLengthMethodId
388+
389+
val buildStaticLambda: MethodId
390+
get() = outerMostTestClass.buildStaticLambdaMethodId
391+
392+
val buildLambda: MethodId
393+
get() = outerMostTestClass.buildLambdaMethodId
394+
395+
val getSingleAbstractMethod: MethodId
396+
get() = outerMostTestClass.getSingleAbstractMethodMethodId
385397
}
386398

387399
/**

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import org.utbot.framework.codegen.TestNg
66
import org.utbot.framework.codegen.model.constructor.builtin.any
77
import org.utbot.framework.codegen.model.constructor.builtin.anyOfClass
88
import org.utbot.framework.codegen.model.constructor.builtin.arraysDeepEqualsMethodId
9+
import org.utbot.framework.codegen.model.constructor.builtin.buildLambdaMethodId
10+
import org.utbot.framework.codegen.model.constructor.builtin.buildStaticLambdaMethodId
911
import org.utbot.framework.codegen.model.constructor.builtin.createArrayMethodId
1012
import org.utbot.framework.codegen.model.constructor.builtin.createInstanceMethodId
1113
import org.utbot.framework.codegen.model.constructor.builtin.deepEqualsMethodId
@@ -15,6 +17,7 @@ import org.utbot.framework.codegen.model.constructor.builtin.getDeclaredConstruc
1517
import org.utbot.framework.codegen.model.constructor.builtin.getDeclaredMethod
1618
import org.utbot.framework.codegen.model.constructor.builtin.getEnumConstantByNameMethodId
1719
import org.utbot.framework.codegen.model.constructor.builtin.getFieldValueMethodId
20+
import org.utbot.framework.codegen.model.constructor.builtin.getSingleAbstractMethodMethodId
1821
import org.utbot.framework.codegen.model.constructor.builtin.getStaticFieldValueMethodId
1922
import org.utbot.framework.codegen.model.constructor.builtin.getTargetException
2023
import org.utbot.framework.codegen.model.constructor.builtin.getUnsafeInstanceMethodId
@@ -176,8 +179,11 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA
176179
streamsDeepEqualsMethodId,
177180
mapsDeepEqualsMethodId,
178181
hasCustomEqualsMethodId,
179-
getArrayLengthMethodId -> emptySet()
180-
else -> error("Unknown util method $this")
182+
getArrayLengthMethodId,
183+
getSingleAbstractMethodMethodId -> emptySet()
184+
buildStaticLambdaMethodId -> setOf(Throwable::class.id)
185+
buildLambdaMethodId -> setOf(Throwable::class.id)
186+
else -> error("Unknown util method ${this@findExceptionTypes}")
181187
}
182188
}
183189
}

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

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ import org.utbot.framework.plugin.api.UtExecution
101101
import org.utbot.framework.plugin.api.UtExecutionFailure
102102
import org.utbot.framework.plugin.api.UtExecutionSuccess
103103
import org.utbot.framework.plugin.api.UtExplicitlyThrownException
104+
import org.utbot.framework.plugin.api.UtLambdaModel
104105
import org.utbot.framework.plugin.api.UtModel
105106
import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation
106107
import org.utbot.framework.plugin.api.UtNullModel
@@ -274,17 +275,6 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
274275

275276
private fun <E> Map<FieldId, E>.accessibleFields(): Map<FieldId, E> = filterKeys { !it.isInaccessible }
276277

277-
/**
278-
* @return expression for [java.lang.Class] of the given [classId]
279-
*/
280-
// TODO: move this method somewhere, because now it duplicates the identical method from MockFrameworkManager
281-
private fun getClassOf(classId: ClassId): CgExpression =
282-
if (classId isAccessibleFrom testClassPackageName) {
283-
CgGetJavaClass(classId)
284-
} else {
285-
newVar(classCgClassId) { Class::class.id[forName](classId.name) }
286-
}
287-
288278
/**
289279
* Generates result assertions for unit tests.
290280
*/
@@ -706,6 +696,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
706696
)
707697
}
708698
}
699+
is UtLambdaModel -> Unit // TODO: check if we actually do not have to do anything with the lambdas here
709700
is UtVoidModel -> {
710701
// Unit result is considered in generateResultAssertions method
711702
error("Unexpected UtVoidModel in deep equals")

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ internal class CgTestClassConstructor(val context: CgContext) :
222222
createInstance -> listOf(getUnsafeInstance)
223223
deepEquals -> listOf(arraysDeepEquals, iterablesDeepEquals, streamsDeepEquals, mapsDeepEquals, hasCustomEquals)
224224
arraysDeepEquals, iterablesDeepEquals, streamsDeepEquals, mapsDeepEquals -> listOf(deepEquals)
225+
buildLambda, buildStaticLambda -> listOf(getSingleAbstractMethod) // TODO: check this dependency when the lambdas support is complete, maybe something will change
225226
else -> emptyList()
226227
}
227228

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,21 @@ import org.utbot.framework.plugin.api.UtCompositeModel
4141
import org.utbot.framework.plugin.api.UtDirectSetFieldModel
4242
import org.utbot.framework.plugin.api.UtEnumConstantModel
4343
import org.utbot.framework.plugin.api.UtExecutableCallModel
44+
import org.utbot.framework.plugin.api.UtLambdaModel
4445
import org.utbot.framework.plugin.api.UtModel
4546
import org.utbot.framework.plugin.api.UtNullModel
4647
import org.utbot.framework.plugin.api.UtPrimitiveModel
4748
import org.utbot.framework.plugin.api.UtReferenceModel
4849
import org.utbot.framework.plugin.api.UtVoidModel
4950
import org.utbot.framework.plugin.api.util.defaultValueModel
51+
import org.utbot.framework.plugin.api.util.executableId
5052
import org.utbot.framework.plugin.api.util.jField
5153
import org.utbot.framework.plugin.api.util.findFieldByIdOrNull
5254
import org.utbot.framework.plugin.api.util.id
5355
import org.utbot.framework.plugin.api.util.intClassId
5456
import org.utbot.framework.plugin.api.util.isArray
5557
import org.utbot.framework.plugin.api.util.isPrimitiveWrapperOrString
58+
import org.utbot.framework.plugin.api.util.jClass
5659
import org.utbot.framework.plugin.api.util.stringClassId
5760
import org.utbot.framework.plugin.api.util.wrapperByPrimitive
5861
import java.lang.reflect.Field
@@ -103,6 +106,7 @@ internal class CgVariableConstructor(val context: CgContext) :
103106
is UtArrayModel -> constructArray(model, baseName)
104107
is UtEnumConstantModel -> constructEnumConstant(model, baseName)
105108
is UtClassRefModel -> constructClassRef(model, baseName)
109+
is UtLambdaModel -> constructLambda(model, baseName)
106110
}
107111
} else valueByModel.getOrPut(model) {
108112
when (model) {
@@ -114,6 +118,29 @@ internal class CgVariableConstructor(val context: CgContext) :
114118
}
115119
}
116120

121+
private fun constructLambda(model: UtLambdaModel, baseName: String): CgVariable {
122+
val lambdaDeclaringClass = model.declaringClass
123+
val lambdaMethodId = lambdaDeclaringClass.jClass
124+
.declaredMethods
125+
.singleOrNull { it.name == model.lambdaName }
126+
?.executableId
127+
// synthetic lambda methods should not have overloads, so we always expect there to be only one method with the given name
128+
?: error("More than one method with name ${model.lambdaName} found in class: ${lambdaDeclaringClass.canonicalName}")
129+
130+
// TODO: Support captured variables. At the moment only lambdas without them are supported.
131+
return newVar(model.samType, baseName) {
132+
if (lambdaMethodId.isStatic) {
133+
testClassThisInstance[buildStaticLambda](
134+
getClassOf(model.samType),
135+
getClassOf(model.declaringClass),
136+
model.lambdaName
137+
)
138+
} else {
139+
TODO("non-static lambdas")
140+
}
141+
}
142+
}
143+
117144
private fun constructComposite(model: UtCompositeModel, baseName: String): CgVariable {
118145
val obj = if (model.isMock) {
119146
mockFrameworkManager.createMockFor(model, baseName)

0 commit comments

Comments
 (0)