From 0f48597a6f7040d5c5468227bccf85310ccb1475 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Fri, 15 Jul 2022 02:01:33 +0300 Subject: [PATCH 01/10] Support anonymous classes --- .../org/utbot/framework/assemble/AssembleModelGeneratorTests.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/framework/assemble/AssembleModelGeneratorTests.kt b/utbot-framework-test/src/test/kotlin/org/utbot/framework/assemble/AssembleModelGeneratorTests.kt index 0108faad44..5ff4c14c2d 100644 --- a/utbot-framework-test/src/test/kotlin/org/utbot/framework/assemble/AssembleModelGeneratorTests.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/framework/assemble/AssembleModelGeneratorTests.kt @@ -60,6 +60,7 @@ import org.utbot.framework.util.modelIdCounter import kotlin.reflect.full.functions import org.utbot.examples.assemble.* import org.utbot.framework.codegen.model.constructor.util.arrayTypeOf +import org.utbot.framework.util.SootUtils /** * Test classes must be located in the same folder as [AssembleTestUtils] class. From ded6d11a00ccaffc026d042abb4b8b3b9a1d13f1 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Fri, 15 Jul 2022 02:01:33 +0300 Subject: [PATCH 02/10] Support lambda expressions --- .../kotlin/org/utbot/framework/UtSettings.kt | 10 +- .../org/utbot/framework/plugin/api/Api.kt | 44 +- .../utbot/framework/plugin/api/util/IdUtil.kt | 23 + .../lambda/CustomPredicateExampleTest.kt | 0 .../kotlin/org/utbot/engine/Extensions.kt | 4 + .../kotlin/org/utbot/engine/MockStrategy.kt | 22 +- .../main/kotlin/org/utbot/engine/Resolver.kt | 43 ++ .../main/kotlin/org/utbot/engine/Traverser.kt | 19 +- .../kotlin/org/utbot/engine/TypeResolver.kt | 35 +- .../org/utbot/engine/ValueConstructor.kt | 31 + .../util/lambda/LambdaConstructionUtils.kt | 172 ++++++ .../assemble/AssembleModelGenerator.kt | 5 +- .../constructor/builtin/UtilMethodBuiltins.kt | 95 ++- .../model/constructor/context/CgContext.kt | 29 +- .../tree/CgCallableAccessManager.kt | 46 +- .../constructor/tree/CgMethodConstructor.kt | 17 +- .../tree/CgTestClassConstructor.kt | 58 +- .../constructor/tree/CgVariableConstructor.kt | 40 ++ .../constructor/tree/MockFrameworkManager.kt | 10 - .../util/CgStatementConstructor.kt | 14 + .../constructor/util/ConstructorUtils.kt | 7 +- .../framework/codegen/model/tree/CgElement.kt | 54 +- .../model/visitor/CgAbstractRenderer.kt | 7 + .../codegen/model/visitor/CgKotlinRenderer.kt | 2 +- .../codegen/model/visitor/CgVisitor.kt | 2 + .../codegen/model/visitor/UtilMethods.kt | 566 ++++++++++++++++++ .../concrete/MockValueConstructor.kt | 31 + .../fields/ExecutionStateAnalyzer.kt | 5 + .../framework/minimization/Minimization.kt | 2 + .../utbot/framework/util/UtModelVisitor.kt | 3 + 30 files changed, 1287 insertions(+), 109 deletions(-) create mode 100644 utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/CustomPredicateExampleTest.kt create mode 100644 utbot-framework/src/main/kotlin/org/utbot/engine/util/lambda/LambdaConstructionUtils.kt diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt index af653ab4bb..1d638d8f57 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt @@ -73,7 +73,7 @@ object UtSettings : AbstractSettings( * Timeout for symbolic execution * */ - var utBotGenerationTimeoutInMillis by getLongProperty(60000L) + var utBotGenerationTimeoutInMillis by getLongProperty(600000L) /** * Random seed in path selector. @@ -113,7 +113,7 @@ object UtSettings : AbstractSettings( * * False by default, set it to true if debug visualization is needed. */ - var useDebugVisualization by getBooleanProperty(false) + var useDebugVisualization by getBooleanProperty(true) /** * Set the value to true if you want to automatically copy the path of the @@ -136,7 +136,7 @@ object UtSettings : AbstractSettings( * @see * UtBot Expression Optimizations */ - var useExpressionSimplification by getBooleanProperty(true) + var useExpressionSimplification by getBooleanProperty(false) /* * Activate or deactivate tests on comments && names/displayNames @@ -188,7 +188,7 @@ object UtSettings : AbstractSettings( * * True by default. */ - var useConcreteExecution by getBooleanProperty(true) + var useConcreteExecution by getBooleanProperty(false) /** * Enable check of full coverage for methods with code generations tests. @@ -300,7 +300,7 @@ object UtSettings : AbstractSettings( * * False by default. */ - var enableUnsatCoreCalculationForHardConstraints by getBooleanProperty(false) + var enableUnsatCoreCalculationForHardConstraints by getBooleanProperty(true) /** * Enable it to process states with unknown solver status diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt index 11c0cf26cb..99dc82efe3 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt @@ -512,6 +512,35 @@ data class UtAssembleModel( } } +/** + * Model for lambdas. + * + * Lambdas in Java represent the implementation of a single abstract method (SAM) of a functional interface. + * They can be used to create an instance of said functional interface, but **they are not classes**. + * In Java lambdas are compiled into synthetic methods of a class they are declared in. + * Depending on the captured variables, this method will be either static or non-static. + * + * Since lambdas are not classes we cannot use a class loader to get info about them as we can do for other models. + * Hence, the necessity for this specific lambda model that will be processed differently: instead of working + * with a class we will be working with the synthetic method that represents our lambda. + */ +// TODO: what about support for Kotlin lambdas (they are not exactly the same as Java's due to functional types) +class UtLambdaModel( + override val id: Int?, + val samType: ClassId, + val declaringClass: ClassId, + val lambdaName: String, + val capturedValues: MutableList = mutableListOf(), +) : UtReferenceModel(id, samType) { + + val lambdaMethodId: MethodId + get() = declaringClass.jClass + .declaredMethods + .singleOrNull { it.name == lambdaName } + ?.executableId // synthetic lambda methods should not have overloads, so we always expect there to be only one method with the given name + ?: error("More than one method with name $lambdaName found in class: ${declaringClass.canonicalName}") +} + /** * Model for a step to obtain [UtAssembleModel]. */ @@ -1010,12 +1039,12 @@ open class MethodId( get() = method.modifiers } -class ConstructorId( +open class ConstructorId( override val classId: ClassId, override val parameters: List ) : ExecutableId() { - override val name: String = "" - override val returnType: ClassId = voidClassId + final override val name: String = "" + final override val returnType: ClassId = voidClassId override val modifiers: Int get() = constructor.modifiers @@ -1040,6 +1069,15 @@ class BuiltinMethodId( (if (isPrivate) Modifier.PRIVATE else 0) } +class BuiltinConstructorId( + classId: ClassId, + parameters: List, + // by default, we assume that the builtin constructor is public + override val isPublic: Boolean = true, + override val isProtected: Boolean = false, + override val isPrivate: Boolean = false +) : ConstructorId(classId, parameters) + open class TypeParameters(val parameters: List = emptyList()) class WildcardTypeParameter : TypeParameters(emptyList()) diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt index 45e52fad96..5f10a167f8 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt @@ -1,6 +1,7 @@ package org.utbot.framework.plugin.api.util import org.utbot.framework.plugin.api.BuiltinClassId +import org.utbot.framework.plugin.api.BuiltinConstructorId import org.utbot.framework.plugin.api.BuiltinMethodId import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ConstructorId @@ -52,6 +53,24 @@ val ClassId.denotableType: ClassId } } +private val isLambdaRegex = ".*(\\$)lambda_.*".toRegex() + +val ClassId.isLambda: Boolean + get() = name matches isLambdaRegex + +val ClassId.isFunctionalInterface: Boolean + get() { + // we cannot access jClass of a builtin type, so we have to return false + if (this is BuiltinClassId) return false + // we cannot access jClass for lambdas, but we know that it is not a functional interface anyway + if (this.isLambda) return false + + val clazz = this.jClass + if (!clazz.isInterface) return false + + val abstractMethods = clazz.methods.filter { java.lang.reflect.Modifier.isAbstract(it.modifiers) } + return abstractMethods.size == 1 + } @Suppress("unused") val ClassId.enclosingClass: ClassId? @@ -526,6 +545,10 @@ fun builtinMethodId(classId: BuiltinClassId, name: String, returnType: ClassId, return BuiltinMethodId(classId, name, returnType, arguments.toList()) } +fun builtinConstructorId(classId: BuiltinClassId, vararg arguments: ClassId): BuiltinConstructorId { + return BuiltinConstructorId(classId, arguments.toList()) +} + fun builtinStaticMethodId(classId: ClassId, name: String, returnType: ClassId, vararg arguments: ClassId): BuiltinMethodId { return BuiltinMethodId(classId, name, returnType, arguments.toList(), isStatic = true) } \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/CustomPredicateExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/CustomPredicateExampleTest.kt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt index ade64eb978..785c62860b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt @@ -35,6 +35,7 @@ import org.utbot.framework.UtSettings.checkNpeInNestedMethods import org.utbot.framework.UtSettings.checkNpeInNestedNotPrivateMethods import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.id +import org.utbot.framework.plugin.api.util.isLambda import soot.ArrayType import soot.PrimType import soot.RefLikeType @@ -192,6 +193,9 @@ private val isAnonymousRegex = ".*\\$\\d+$".toRegex() val SootClass.isAnonymous get() = name matches isAnonymousRegex +val SootClass.isLambda: Boolean + get() = this.id.isLambda + val Type.numDimensions get() = if (this is ArrayType) numDimensions else 0 /** diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/MockStrategy.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/MockStrategy.kt index 6f2bee6b0c..71b39877ad 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/MockStrategy.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/MockStrategy.kt @@ -1,6 +1,7 @@ package org.utbot.engine import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.util.isFunctionalInterface /** * Mock strategies. @@ -17,15 +18,24 @@ enum class MockStrategy { }, OTHER_PACKAGES { - override fun eligibleToMock(classToMock: ClassId, classUnderTest: ClassId): Boolean = - classToMock != classUnderTest && classToMock.packageName.let { - it != classUnderTest.packageName && !isSystemPackage(it) - } + override fun eligibleToMock(classToMock: ClassId, classUnderTest: ClassId): Boolean { + if (classToMock == classUnderTest) return false + if (classToMock.packageName == classUnderTest.packageName) return false + + // we always mock functional interfaces + if (classToMock.isFunctionalInterface) return true + + return !isSystemPackage(classToMock.packageName) + } }, OTHER_CLASSES { - override fun eligibleToMock(classToMock: ClassId, classUnderTest: ClassId): Boolean = - classToMock != classUnderTest && !isSystemPackage(classToMock.packageName) + override fun eligibleToMock(classToMock: ClassId, classUnderTest: ClassId): Boolean { + if (classToMock == classUnderTest) return false + // we always mock functional interfaces + if (classToMock.isFunctionalInterface) return true + return !isSystemPackage(classToMock.packageName) + } }; /** diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt index 01687fee75..4f26228952 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt @@ -47,6 +47,7 @@ import org.utbot.framework.plugin.api.UtExecutionSuccess import org.utbot.framework.plugin.api.UtExplicitlyThrownException import org.utbot.framework.plugin.api.UtImplicitlyThrownException import org.utbot.framework.plugin.api.UtInstrumentation +import org.utbot.framework.plugin.api.UtLambdaModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation import org.utbot.framework.plugin.api.UtNullModel @@ -509,6 +510,13 @@ class Resolver( } val sootClass = actualType.sootClass + + if (sootClass.isLambda) { + return constructLambda(concreteAddr, sootClass).also { lambda -> + lambda.capturedValues += collectFieldModels(addr, actualType).values + } + } + val clazz = classLoader.loadClass(sootClass.name) if (clazz.isEnum) { @@ -630,6 +638,41 @@ class Resolver( return constructedType.classId.jClass } + private fun constructLambda(addr: Address, sootClass: SootClass): UtLambdaModel { + val samType = sootClass.interfaces.singleOrNull()?.id + ?: error("Lambda must implement single interface, but ${sootClass.interfaces.size} found for ${sootClass.name}") + + val declaringClass = classLoader.loadClass(sootClass.name.substringBeforeLast("\$lambda")) + + // Java compiles lambdas into synthetic methods with specific names. + // However, Soot represents lambdas as classes. + // Names of these classes are the modified names of these synthetic methods. + // Specifically, Soot replaces some `$` signs by `_`, adds two underscores and some number + // to the end of the synthetic method name to form the name of a SootClass for lambda. + // For example, given a synthetic method `lambda$foo$1` (lambda declared in method `foo` of class `org.utbot.MyClass`), + // Soot will treat this lambda as a class named `org.utbot.MyClass$lambda_foo_1__5` (the last number is probably arbitrary, it's not important). + // Here we obtain the synthetic method name of lambda from the name of its SootClass. + val lambdaName = sootClass.name + .let { name -> + val start = name.lastIndexOf("\$lambda") + 1 + val end = name.lastIndexOf("__") + name.substring(start, end) + } + .let { + val builder = StringBuilder(it) + builder[it.indexOfFirst { c -> c == '_' }] = '$' + builder[it.indexOfLast { c -> c == '_' }] = '$' + builder.toString() + } + + return UtLambdaModel( + id = addr, + samType = samType, + declaringClass = declaringClass.id, + lambdaName = lambdaName + ) + } + private fun constructEnum(addr: Address, type: RefType, clazz: Class<*>): UtEnumConstantModel { val descriptor = MemoryChunkDescriptor(ENUM_ORDINAL, type, IntType.v()) val array = findArray(descriptor, state) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt index 12107cecd8..4825f2196f 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -9,7 +9,6 @@ import kotlinx.collections.immutable.toPersistentSet import org.utbot.common.WorkaroundReason.HACK import org.utbot.framework.UtSettings.ignoreStaticsFromTrustedLibraries import org.utbot.common.WorkaroundReason.IGNORE_STATICS_FROM_TRUSTED_LIBRARIES -import org.utbot.common.WorkaroundReason.REMOVE_ANONYMOUS_CLASSES import org.utbot.common.unreachableBranch import org.utbot.common.withAccessibility import org.utbot.common.workaround @@ -1836,7 +1835,7 @@ class Traverser( return fromMemory } val addr = fromMemory?.addr ?: findNewAddr() - val created = createObject(addr, classType, useConcreteType = false, mockInfoGenerator) + val created = createObject(addr, classType, useConcreteType = true, mockInfoGenerator) queuedSymbolicStateUpdates += MemoryUpdate(staticInstanceStorage = persistentHashMapOf(classType.id to created)) return created } @@ -3408,22 +3407,6 @@ class Traverser( val returnValue = (symbolicResult as? SymbolicSuccess)?.value as? ObjectValue if (returnValue != null) { queuedSymbolicStateUpdates += constructConstraintForType(returnValue, returnValue.possibleConcreteTypes).asSoftConstraint() - - // We only remove anonymous classes if there are regular classes available. - // If there are no other options, then we do use anonymous classes. - workaround(REMOVE_ANONYMOUS_CLASSES) { - val sootClass = returnValue.type.sootClass - val isInNestedMethod = environment.state.isInNestedMethod() - - if (!isInNestedMethod && sootClass.isArtificialEntity) { - return - } - - val onlyAnonymousTypesAvailable = returnValue.typeStorage.possibleConcreteTypes.all { (it as? RefType)?.sootClass?.isAnonymous == true } - if (!isInNestedMethod && sootClass.isAnonymous && !onlyAnonymousTypesAvailable) { - return - } - } } //fill arrays with default 0/null and other stuff diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt index 6b889a3969..70ed86a371 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt @@ -112,7 +112,7 @@ class TypeResolver(private val typeRegistry: TypeRegistry, private val hierarchy if (numDimensions == 0) baseType else baseType.makeArrayType(numDimensions) } - return TypeStorage(type, concretePossibleTypes).filterInappropriateClassesForCodeGeneration() + return TypeStorage(type, concretePossibleTypes)//.filterInappropriateClassesForCodeGeneration() } private fun isInappropriateOrArrayOfMocksOrLocals(numDimensions: Int, baseType: Type?): Boolean { @@ -182,7 +182,7 @@ class TypeResolver(private val typeRegistry: TypeRegistry, private val hierarchy else -> error("Unexpected type $type") } - return TypeStorage(type, possibleTypes).filterInappropriateClassesForCodeGeneration() + return TypeStorage(type, possibleTypes)//.filterInappropriateClassesForCodeGeneration() } /** @@ -197,17 +197,26 @@ class TypeResolver(private val typeRegistry: TypeRegistry, private val hierarchy val leastCommonSootClass = (leastCommonType as? RefType)?.sootClass val keepArtificialEntities = leastCommonSootClass?.isArtificialEntity == true - possibleConcreteTypes.forEach { - val sootClass = (it.baseType as? RefType)?.sootClass ?: run { - // All not RefType should be included in the concreteTypes, e.g., arrays - concreteTypes += it - return@forEach - } - when { - sootClass.isUtMock -> unwantedTypes += it - sootClass.isArtificialEntity -> if (keepArtificialEntities) concreteTypes += it else Unit - workaround(WorkaroundReason.HACK) { leastCommonSootClass == OBJECT_TYPE && sootClass.isOverridden } -> Unit - else -> concreteTypes += it + heuristic(WorkaroundReason.REMOVE_ANONYMOUS_CLASSES) { + possibleConcreteTypes.forEach { + val sootClass = (it.baseType as? RefType)?.sootClass ?: run { + // All not RefType should be included in the concreteTypes, e.g., arrays + concreteTypes += it + return@forEach + } + when { + sootClass.isUtMock -> unwantedTypes += it + sootClass.isArtificialEntity -> { +// if (sootClass.isLambda) { +// unwantedTypes += it +// } else + if (keepArtificialEntities) { + concreteTypes += it + } + } + workaround(WorkaroundReason.HACK) { leastCommonSootClass == OBJECT_TYPE && sootClass.isOverridden } -> Unit + else -> concreteTypes += it + } } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ValueConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ValueConstructor.kt index 445a5145f4..2f7f081cbd 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ValueConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ValueConstructor.kt @@ -2,6 +2,9 @@ package org.utbot.engine import org.utbot.common.invokeCatching import org.utbot.framework.plugin.api.ClassId +import org.utbot.engine.util.lambda.CapturedArgument +import org.utbot.engine.util.lambda.constructLambda +import org.utbot.engine.util.lambda.constructStaticLambda import org.utbot.framework.plugin.api.ConstructorId import org.utbot.framework.plugin.api.EnvironmentModels import org.utbot.framework.plugin.api.FieldId @@ -24,6 +27,7 @@ import org.utbot.framework.plugin.api.UtExecution import org.utbot.framework.plugin.api.UtExecutionFailure import org.utbot.framework.plugin.api.UtExecutionResult import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.framework.plugin.api.UtLambdaModel import org.utbot.framework.plugin.api.UtMockValue import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNullModel @@ -188,6 +192,7 @@ class ValueConstructor { is UtCompositeModel -> UtConcreteValue(constructObject(model), model.classId.jClass) is UtArrayModel -> UtConcreteValue(constructArray(model)) is UtAssembleModel -> UtConcreteValue(constructFromAssembleModel(model)) + is UtLambdaModel -> UtConcreteValue(constructFromLambdaModel(model)) is UtVoidModel -> UtConcreteValue(Unit) } } @@ -335,6 +340,32 @@ class ValueConstructor { ?: error("Can't assemble model: $assembleModel") } + private fun constructFromLambdaModel(lambdaModel: UtLambdaModel): Any { + // A class representing a functional interface. + val samType: Class<*> = lambdaModel.samType.jClass + // A class where the lambda is declared. + val declaringClass: Class<*> = lambdaModel.declaringClass.jClass + // A name of the synthetic method that represents a lambda. + val lambdaName = lambdaModel.lambdaName + + return if (lambdaModel.lambdaMethodId.isStatic) { + val capturedArguments = lambdaModel.capturedValues + .map { model -> CapturedArgument(type = model.classId.jClass, value = value(model)) } + .toTypedArray() + constructStaticLambda(samType, declaringClass, lambdaName, *capturedArguments) + } else { + val capturedReceiverModel = lambdaModel.capturedValues.firstOrNull() + ?: error("Non-static lambda must capture `this` instance, so there must be at least one captured value") + + // Values that the given lambda has captured. + val capturedReceiver = value(capturedReceiverModel) ?: error("Captured receiver of lambda must not be null") + val capturedArguments = lambdaModel.capturedValues.subList(1, lambdaModel.capturedValues.size) + .map { model -> CapturedArgument(type = model.classId.jClass, value = value(model)) } + .toTypedArray() + constructLambda(samType, declaringClass, lambdaName, capturedReceiver, *capturedArguments) + } + } + /** * Updates instance state with [UtExecutableCallModel] invocation. */ diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/util/lambda/LambdaConstructionUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/util/lambda/LambdaConstructionUtils.kt new file mode 100644 index 0000000000..237ab9f48b --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/util/lambda/LambdaConstructionUtils.kt @@ -0,0 +1,172 @@ +package org.utbot.engine.util.lambda + +import java.lang.invoke.LambdaMetafactory +import java.lang.invoke.MethodHandles +import java.lang.invoke.MethodType +import java.lang.reflect.Method +import java.lang.reflect.Modifier + +/** + * This class represents the `type` and `value` of a value captured by lambda. + * Captured values are represented as arguments of a synthetic method that lambda is compiled into, + * hence the name of the class. + */ +data class CapturedArgument(val type: Class<*>, val value: Any?) + +/** + * @param clazz a class to create lookup instance for. + * @return [MethodHandles.Lookup] instance for the given [clazz]. + * It can be used, for example, to search methods of this [clazz], even the `private` ones. + */ +private fun getLookupIn(clazz: Class<*>): MethodHandles.Lookup { + val lookup = MethodHandles.lookup().`in`(clazz) + + // Allow lookup to access all members of declaringClass, including the private ones. + // For example, it is useful to access private synthetic methods representing lambdas. + val allowedModes = MethodHandles.Lookup::class.java.getDeclaredField("allowedModes") + allowedModes.isAccessible = true + allowedModes.setInt( + lookup, + Modifier.PUBLIC or Modifier.PROTECTED or Modifier.PRIVATE or Modifier.STATIC + ) + + return lookup +} + +/** + * @param lambdaMethod [Method] that represents a synthetic method for lambda. + * @param capturedArgumentTypes types of values captured by lambda. + * @return [MethodType] that represents the value of argument `instantiatedMethodType` + * of method [LambdaMetafactory.metafactory]. + */ +private fun getInstantiatedMethodType( + lambdaMethod: Method, + capturedArgumentTypes: Array> +): MethodType { + // Types of arguments of synthetic method (representing lambda) without the types of captured values. + val instantiatedMethodParamTypes = lambdaMethod.parameterTypes + .drop(capturedArgumentTypes.size) + .toTypedArray() + + return MethodType.methodType(lambdaMethod.returnType, instantiatedMethodParamTypes) +} + +/** + * @param declaringClass class where a lambda is declared. + * @param lambdaName name of synthetic method that represents a lambda. + * @return [Method] instance for the synthetic method that represent a lambda. + */ +private fun getLambdaMethod(declaringClass: Class<*>, lambdaName: String): Method { + return declaringClass.declaredMethods.firstOrNull { it.name == lambdaName } + ?: throw IllegalArgumentException("No lambda method named ${'$'}lambdaName was found in class: ${'$'}{declaringClass.canonicalName}") +} + +/** + * @param clazz functional interface + * @return a [Method] for the single abstract method of the given functional interface `clazz`. + */ +private fun getSingleAbstractMethod(clazz: Class<*>): Method { + val abstractMethods = clazz.methods.filter { Modifier.isAbstract(it.modifiers) } + require(abstractMethods.isNotEmpty()) { "No abstract methods found in class: " + clazz.canonicalName } + require(abstractMethods.size <= 1) { "More than one abstract method found in class: " + clazz.canonicalName } + return abstractMethods[0] +} + +/** + * @return an [Any] that represents an instance of the given functional interface `samType` + * and implements its single abstract method with the behavior of the given lambda. + */ +internal fun constructStaticLambda( + samType: Class<*>, + declaringClass: Class<*>, + lambdaName: String, + vararg capturedArguments: CapturedArgument +): Any { + // Create lookup for class where the lambda is declared in. + val caller = getLookupIn(declaringClass) + + // Obtain the single abstract method of a functional interface whose instance we are building. + // For example, for `java.util.function.Predicate` it will be method `test`. + val singleAbstractMethod = getSingleAbstractMethod(samType) + val invokedName = singleAbstractMethod.name + // Method type of single abstract method of the target functional interface. + val samMethodType = MethodType.methodType(singleAbstractMethod.returnType, singleAbstractMethod.parameterTypes) + + val lambdaMethod = getLambdaMethod(declaringClass, lambdaName) + lambdaMethod.isAccessible = true + val lambdaMethodType = MethodType.methodType(lambdaMethod.returnType, lambdaMethod.parameterTypes) + val lambdaMethodHandle = caller.findStatic(declaringClass, lambdaName, lambdaMethodType) + + val capturedArgumentTypes = capturedArguments.map { it.type }.toTypedArray() + val invokedType = MethodType.methodType(samType, capturedArgumentTypes) + val instantiatedMethodType = getInstantiatedMethodType(lambdaMethod, capturedArgumentTypes) + + // Create a CallSite for the given lambda. + val site = LambdaMetafactory.metafactory( + caller, + invokedName, + invokedType, + samMethodType, + lambdaMethodHandle, + instantiatedMethodType + ) + val capturedValues = capturedArguments.map { it.value }.toTypedArray() + + // Get MethodHandle and pass captured values to it to obtain an object + // that represents the target functional interface instance. + val handle = site.target + return handle.invokeWithArguments(*capturedValues) +} + +/** + * @return an [Any] that represents an instance of the given functional interface `samType` + * and implements its single abstract method with the behavior of the given lambda. + */ +internal fun constructLambda( + samType: Class<*>, + declaringClass: Class<*>, + lambdaName: String, + capturedReceiver: Any, + vararg capturedArguments: CapturedArgument +): Any { + // Create lookup for class where the lambda is declared in. + val caller = getLookupIn(declaringClass) + + // Obtain the single abstract method of a functional interface whose instance we are building. + // For example, for `java.util.function.Predicate` it will be method `test`. + val singleAbstractMethod = getSingleAbstractMethod(samType) + val invokedName = singleAbstractMethod.name + // Method type of single abstract method of the target functional interface. + val samMethodType = MethodType.methodType(singleAbstractMethod.returnType, singleAbstractMethod.parameterTypes) + + val lambdaMethod = getLambdaMethod(declaringClass, lambdaName) + lambdaMethod.isAccessible = true + val lambdaMethodType = MethodType.methodType(lambdaMethod.returnType, lambdaMethod.parameterTypes) + val lambdaMethodHandle = caller.findVirtual(declaringClass, lambdaName, lambdaMethodType) + + val capturedArgumentTypes = capturedArguments.map { it.type }.toTypedArray() + val invokedType = MethodType.methodType(samType, capturedReceiver.javaClass, *capturedArgumentTypes) + val instantiatedMethodType = getInstantiatedMethodType(lambdaMethod, capturedArgumentTypes) + + // Create a CallSite for the given lambda. + val site = LambdaMetafactory.metafactory( + caller, + invokedName, + invokedType, + samMethodType, + lambdaMethodHandle, + instantiatedMethodType + ) + val capturedValues = mutableListOf() + .apply { + add(capturedReceiver) + val capturedArgumentValues = capturedArguments.map { it.value } + addAll(capturedArgumentValues) + }.toTypedArray() + + + // Get MethodHandle and pass captured values to it to obtain an object + // that represents the target functional interface instance. + val handle = site.target + return handle.invokeWithArguments(*capturedValues) +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt index 473a2aa1b8..01c22509ef 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt @@ -24,6 +24,7 @@ import org.utbot.framework.plugin.api.UtCompositeModel import org.utbot.framework.plugin.api.UtDirectSetFieldModel import org.utbot.framework.plugin.api.UtEnumConstantModel import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtLambdaModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation import org.utbot.framework.plugin.api.UtNullModel @@ -183,7 +184,8 @@ class AssembleModelGenerator(private val methodPackageName: String) { is UtPrimitiveModel, is UtClassRefModel, is UtVoidModel, - is UtEnumConstantModel -> utModel + is UtEnumConstantModel, + is UtLambdaModel -> utModel is UtArrayModel -> assembleArrayModel(utModel) is UtCompositeModel -> assembleCompositeModel(utModel) is UtAssembleModel -> assembleAssembleModel(utModel) @@ -231,6 +233,7 @@ class AssembleModelGenerator(private val methodPackageName: String) { } try { + // TODO: we can't use simpleName for anonymous classes, because it's empty val modelName = nextModelName(compositeModel.classId.jClass.simpleName.decapitalize()) val instantiationChain = mutableListOf() diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt index ea3665583c..e7d0110e7a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt @@ -1,20 +1,28 @@ package org.utbot.framework.codegen.model.constructor.builtin import org.utbot.framework.codegen.MockitoStaticMocking +import org.utbot.framework.codegen.model.constructor.util.arrayTypeOf import org.utbot.framework.codegen.model.constructor.util.utilMethodId import org.utbot.framework.codegen.model.tree.CgClassId import org.utbot.framework.codegen.model.visitor.utilMethodTextById import org.utbot.framework.plugin.api.BuiltinClassId +import org.utbot.framework.plugin.api.BuiltinConstructorId import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.builtinConstructorId +import org.utbot.framework.plugin.api.util.classClassId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.intClassId import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.objectArrayClassId import org.utbot.framework.plugin.api.util.objectClassId import org.utbot.framework.plugin.api.util.stringClassId import org.utbot.framework.plugin.api.util.voidClassId import sun.misc.Unsafe +import java.lang.invoke.MethodHandles +import java.lang.invoke.MethodType +import java.lang.reflect.Method /** * Set of ids of all possible util methods for a given class. @@ -39,7 +47,15 @@ internal abstract class UtilMethodProvider(val utilClassId: ClassId) { streamsDeepEqualsMethodId, mapsDeepEqualsMethodId, hasCustomEqualsMethodId, - getArrayLengthMethodId + getArrayLengthMethodId, + buildStaticLambdaMethodId, + buildLambdaMethodId, + getLookupInMethodId, + getLambdaCapturedArgumentTypesMethodId, + getLambdaCapturedArgumentValuesMethodId, + getInstantiatedMethodTypeMethodId, + getLambdaMethodMethodId, + getSingleAbstractMethodMethodId ) val getUnsafeInstanceMethodId: MethodId @@ -148,6 +164,83 @@ internal abstract class UtilMethodProvider(val utilClassId: ClassId) { returnType = intClassId, arguments = arrayOf(objectClassId) ) + + val buildStaticLambdaMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "buildStaticLambda", + returnType = objectClassId, + arguments = arrayOf( + classClassId, + classClassId, + stringClassId, + arrayTypeOf(capturedArgumentClassId) + ) + ) + + val buildLambdaMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "buildLambda", + returnType = objectClassId, + arguments = arrayOf( + classClassId, + classClassId, + stringClassId, + objectClassId, + arrayTypeOf(capturedArgumentClassId) + ) + ) + + val getLookupInMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "getLookupIn", + returnType = MethodHandles.Lookup::class.id, + arguments = arrayOf(classClassId) + ) + + val getLambdaCapturedArgumentTypesMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "getLambdaCapturedArgumentTypes", + returnType = arrayTypeOf(classClassId), + arguments = arrayOf(arrayTypeOf(capturedArgumentClassId)) + ) + + val getLambdaCapturedArgumentValuesMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "getLambdaCapturedArgumentValues", + returnType = objectArrayClassId, + arguments = arrayOf(arrayTypeOf(capturedArgumentClassId)) + ) + + val getInstantiatedMethodTypeMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "getInstantiatedMethodType", + returnType = MethodType::class.id, + arguments = arrayOf(Method::class.id, arrayTypeOf(classClassId)) + ) + + val getLambdaMethodMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "getLambdaMethod", + returnType = Method::class.id, + arguments = arrayOf(classClassId, stringClassId) + ) + + val getSingleAbstractMethodMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "getSingleAbstractMethod", + returnType = java.lang.reflect.Method::class.id, + arguments = arrayOf(classClassId) + ) + + val capturedArgumentClassId: BuiltinClassId + get() = BuiltinClassId( + name = "${utilClassId.name}\$CapturedArgument", + canonicalName = "${utilClassId.name}.CapturedArgument", + simpleName = "CapturedArgument" + ) + + val capturedArgumentConstructorId: BuiltinConstructorId + get() = builtinConstructorId(capturedArgumentClassId, classClassId, objectClassId) } /** diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt index 62ea4e64f7..12f399c48a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt @@ -343,7 +343,10 @@ internal interface CgContextOwner { val testClassThisInstance: CgThisInstance - // util methods of current test class + // util methods and auxiliary classes of current test class + + val capturedArgumentClass: ClassId + get() = utilMethodProvider.capturedArgumentClassId val getUnsafeInstance: MethodId get() = utilMethodProvider.getUnsafeInstanceMethodId @@ -389,6 +392,30 @@ internal interface CgContextOwner { val getArrayLength: MethodId get() = utilMethodProvider.getArrayLengthMethodId + + val buildStaticLambda: MethodId + get() = utilMethodProvider.buildStaticLambdaMethodId + + val buildLambda: MethodId + get() = utilMethodProvider.buildLambdaMethodId + + val getLookupIn: MethodId + get() = utilMethodProvider.getLookupInMethodId + + val getSingleAbstractMethod: MethodId + get() = utilMethodProvider.getSingleAbstractMethodMethodId + + val getLambdaCapturedArgumentTypes: MethodId + get() = utilMethodProvider.getLambdaCapturedArgumentTypesMethodId + + val getLambdaCapturedArgumentValues: MethodId + get() = utilMethodProvider.getLambdaCapturedArgumentValuesMethodId + + val getInstantiatedMethodType: MethodId + get() = utilMethodProvider.getInstantiatedMethodTypeMethodId + + val getLambdaMethod: MethodId + get() = utilMethodProvider.getLambdaMethodMethodId } /** diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt index 91dd88d46f..ada6563223 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt @@ -35,6 +35,7 @@ import org.utbot.framework.codegen.model.util.at import org.utbot.framework.codegen.model.util.isAccessibleFrom import org.utbot.framework.codegen.model.util.nullLiteral import org.utbot.framework.codegen.model.util.resolve +import org.utbot.framework.plugin.api.BuiltinConstructorId import org.utbot.framework.plugin.api.BuiltinClassId import org.utbot.framework.plugin.api.BuiltinMethodId import org.utbot.framework.plugin.api.ClassId @@ -140,7 +141,7 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA //Builtin methods does not have jClass, so [methodId.method] will crash on it, //so we need to collect required exceptions manually from source codes if (methodId is BuiltinMethodId) { - findExceptionTypesOf(methodId) + methodId.findExceptionTypes() .forEach { addExceptionIfNeeded(it) } return } @@ -164,6 +165,15 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA private fun newConstructorCall(constructorId: ConstructorId) { importIfNeeded(constructorId.classId) + + // Builtin constructors do not have jClass, so [constructorId.exceptions] will crash on it, + // so we need to collect required exceptions manually from source codes (see BuiltinConstructorId.findExceptionTypes()). + + if (constructorId is BuiltinConstructorId) { + constructorId.findExceptionTypes().forEach { addExceptionIfNeeded(it) } + return + } + for (exception in constructorId.exceptions) { addExceptionIfNeeded(exception) } @@ -556,33 +566,51 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA return argumentsArrayVariable } + private fun BuiltinConstructorId.findExceptionTypes(): Set { + // At the moment we do not have builtin ids for constructors that throw exceptions, + // so we have this trivial when-expression. But if we ever add ids for such constructors, + // then we **must** specify their exceptions here, so that we take them into account when generating code. + @Suppress("UNUSED_EXPRESSION") + return when (this) { + else -> emptySet() + } + } + //WARN: if you make changes in the following sets of exceptions, //don't forget to change them in hardcoded [UtilMethods] as well - private fun findExceptionTypesOf(methodId: MethodId): Set { + private fun BuiltinMethodId.findExceptionTypes(): Set { // TODO: at the moment we treat BuiltinMethodIds that are not util method ids // as if they have no exceptions. This should be fixed by storing exception types in BuiltinMethodId // or allowing us to access actual java.lang.Class for classes from mockito and other libraries // (this could be possibly solved by using user project's class loaders in UtContext) - if (methodId !in utilMethodProvider.utilMethodIds) return emptySet() + if (!isUtil(this)) return emptySet() with(utilMethodProvider) { - return when (methodId) { - getEnumConstantByNameMethodId -> setOf(java.lang.IllegalAccessException::class.id) + return when (this@findExceptionTypes) { + getEnumConstantByNameMethodId -> setOf(IllegalAccessException::class.id) getStaticFieldValueMethodId, setStaticFieldMethodId -> setOf(java.lang.IllegalAccessException::class.id, java.lang.NoSuchFieldException::class.id) getFieldValueMethodId, setFieldMethodId -> setOf(java.lang.ClassNotFoundException::class.id, java.lang.IllegalAccessException::class.id, java.lang.NoSuchFieldException::class.id) createInstanceMethodId -> setOf(Exception::class.id) - getUnsafeInstanceMethodId -> setOf(java.lang.ClassNotFoundException::class.id, java.lang.NoSuchFieldException::class.id, java.lang.IllegalAccessException::class.id) - createArrayMethodId -> setOf(java.lang.ClassNotFoundException::class.id) + getUnsafeInstanceMethodId -> setOf(ClassNotFoundException::class.id, NoSuchFieldException::class.id, IllegalAccessException::class.id) + createArrayMethodId -> setOf(ClassNotFoundException::class.id) deepEqualsMethodId, arraysDeepEqualsMethodId, iterablesDeepEqualsMethodId, streamsDeepEqualsMethodId, mapsDeepEqualsMethodId, hasCustomEqualsMethodId, - getArrayLengthMethodId -> emptySet() - else -> error("Unknown util method $this") + getArrayLengthMethodId, + getLambdaCapturedArgumentTypesMethodId, + getLambdaCapturedArgumentValuesMethodId, + getInstantiatedMethodTypeMethodId, + getLambdaMethodMethodId, + getSingleAbstractMethodMethodId -> emptySet() + buildStaticLambdaMethodId, + buildLambdaMethodId -> setOf(Throwable::class.id) + getLookupInMethodId ->setOf(IllegalAccessException::class.id, NoSuchFieldException::class.id) + else -> error("Unknown util method ${this@findExceptionTypes}") } } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt index e7b7a880b3..94ee954c3b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt @@ -98,6 +98,7 @@ import org.utbot.framework.plugin.api.UtExecution import org.utbot.framework.plugin.api.UtExecutionFailure import org.utbot.framework.plugin.api.UtExecutionSuccess import org.utbot.framework.plugin.api.UtExplicitlyThrownException +import org.utbot.framework.plugin.api.UtLambdaModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation import org.utbot.framework.plugin.api.UtNullModel @@ -274,17 +275,6 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c private fun Map.accessibleFields(): Map = filterKeys { !it.isInaccessibleViaReflection } - /** - * @return expression for [java.lang.Class] of the given [classId] - */ - // TODO: move this method somewhere, because now it duplicates the identical method from MockFrameworkManager - private fun getClassOf(classId: ClassId): CgExpression = - if (classId isAccessibleFrom testClassPackageName) { - CgGetJavaClass(classId) - } else { - newVar(classCgClassId) { Class::class.id[forName](classId.name) } - } - /** * Generates result assertions for unit tests. */ @@ -705,6 +695,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c ) } } + is UtLambdaModel -> Unit is UtVoidModel -> { // Unit result is considered in generateResultAssertions method error("Unexpected UtVoidModel in deep equals") @@ -969,6 +960,8 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c } } + is UtLambdaModel -> Unit // TODO: lambda model + is UtNullModel, is UtPrimitiveModel, is UtArrayModel, @@ -1008,6 +1001,8 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c } } + is UtLambdaModel -> Unit // TODO: lambda model + is UtNullModel, is UtPrimitiveModel, is UtArrayModel, diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt index f78ec2942e..6a5197c0d2 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt @@ -29,6 +29,9 @@ import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.UtMethodTestSet import org.utbot.framework.codegen.model.constructor.TestClassModel +import org.utbot.framework.codegen.model.tree.CgAuxiliaryClass +import org.utbot.framework.codegen.model.tree.CgUtilEntity +import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.util.description import org.utbot.framework.plugin.api.util.humanReadableName import org.utbot.framework.plugin.api.util.kClass @@ -98,11 +101,11 @@ internal class CgTestClassConstructor(val context: CgContext) : } if (currentTestClass == outerMostTestClass) { - val utilMethods = collectUtilMethods() - // If utilMethodProvider is TestClassUtilMethodProvider, then util methods should be declared - // in the test class. Otherwise, util methods will be located elsewhere (e.g. another class or library). - if (utilMethodProvider is TestClassUtilMethodProvider && utilMethods.isNotEmpty()) { - staticDeclarationRegions += CgStaticsRegion("Util methods", utilMethods) + val utilEntities = collectUtilEntities() + // If utilMethodProvider is TestClassUtilMethodProvider, then util entities should be declared + // in the test class. Otherwise, util entities will be located elsewhere (e.g. another class). + if (utilMethodProvider is TestClassUtilMethodProvider && utilEntities.isNotEmpty()) { + staticDeclarationRegions += CgStaticsRegion("Util methods", utilEntities) } } } @@ -201,20 +204,24 @@ internal class CgTestClassConstructor(val context: CgContext) : } /** - * This method collects a list of util methods needed by the class. - * By the end of the test method generation [requiredUtilMethods] may not contain all the methods needed. + * This method collects a list of util entities (methods and classes) needed by the class. + * By the end of the test method generation [requiredUtilMethods] may not contain all the needed. * That's because some util methods may not be directly used in tests, but they may be used from other util methods. - * We define such method dependencies in [MethodId.dependencies]. + * We define such method dependencies in [MethodId.methodDependencies]. * - * Once all dependencies are collected, they are also added to [requiredUtilMethods]. + * Once all dependencies are collected, required methods are added back to [requiredUtilMethods], + * because during the work of this method they are being removed from this list, so we have to put them back in. * - * @return a list of [CgUtilMethod] representing required util methods (including dependencies). + * Also, some util methods may use some classes that also need to be generated. + * That is why we collect information about required classes using [MethodId.classDependencies]. + * + * @return a list of [CgUtilEntity] representing required util methods and classes (including their own dependencies). */ - private fun collectUtilMethods(): List { + private fun collectUtilEntities(): List { val utilMethods = mutableListOf() - // some util methods depend on the others - // using this loop we make sure that all the - // util methods dependencies are taken into account + // Some util methods depend on other util methods or some auxiliary classes. + // Using this loop we make sure that all the util method dependencies are taken into account. + val requiredClasses = mutableSetOf() while (requiredUtilMethods.isNotEmpty()) { val method = requiredUtilMethods.first() requiredUtilMethods.remove(method) @@ -225,24 +232,41 @@ internal class CgTestClassConstructor(val context: CgContext) : importUtilMethodDependencies(method) } existingMethodNames += method.name - requiredUtilMethods += method.dependencies() + requiredUtilMethods += method.methodDependencies() + requiredClasses += method.classDependencies() } } // Collect all util methods back into requiredUtilMethods. // Now there will also be util methods that weren't present in requiredUtilMethods at first, // but were needed for the present util methods to work. requiredUtilMethods += utilMethods.map { method -> method.id } - return utilMethods + + val auxiliaryClasses = requiredClasses.map { CgAuxiliaryClass(it) } + + return utilMethods + auxiliaryClasses } /** * If @receiver is an util method, then returns a list of util method ids that @receiver depends on * Otherwise, an empty list is returned */ - private fun MethodId.dependencies(): List = when (this) { + private fun MethodId.methodDependencies(): List = when (this) { createInstance -> listOf(getUnsafeInstance) deepEquals -> listOf(arraysDeepEquals, iterablesDeepEquals, streamsDeepEquals, mapsDeepEquals, hasCustomEquals) arraysDeepEquals, iterablesDeepEquals, streamsDeepEquals, mapsDeepEquals -> listOf(deepEquals) + buildLambda, buildStaticLambda -> listOf( + getLookupIn, getSingleAbstractMethod, getLambdaMethod, + getLambdaCapturedArgumentTypes, getInstantiatedMethodType, getLambdaCapturedArgumentValues + ) + else -> emptyList() + } + + /** + * If @receiver is an util method, then returns a list of auxiliary class ids that @receiver depends on. + * Otherwise, an empty list is returned. + */ + private fun MethodId.classDependencies(): List = when (this) { + buildLambda, buildStaticLambda -> listOf(capturedArgumentClass) else -> emptyList() } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt index 8d216f98f6..ab0d5ff4a5 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt @@ -20,6 +20,7 @@ import org.utbot.framework.codegen.model.tree.CgExpression import org.utbot.framework.codegen.model.tree.CgFieldAccess import org.utbot.framework.codegen.model.tree.CgGetJavaClass import org.utbot.framework.codegen.model.tree.CgLiteral +import org.utbot.framework.codegen.model.tree.CgMethodCall import org.utbot.framework.codegen.model.tree.CgStaticFieldAccess import org.utbot.framework.codegen.model.tree.CgValue import org.utbot.framework.codegen.model.tree.CgVariable @@ -41,6 +42,7 @@ import org.utbot.framework.plugin.api.UtCompositeModel import org.utbot.framework.plugin.api.UtDirectSetFieldModel import org.utbot.framework.plugin.api.UtEnumConstantModel import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtLambdaModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtPrimitiveModel @@ -105,6 +107,7 @@ internal class CgVariableConstructor(val context: CgContext) : is UtArrayModel -> constructArray(model, baseName) is UtEnumConstantModel -> constructEnumConstant(model, baseName) is UtClassRefModel -> constructClassRef(model, baseName) + is UtLambdaModel -> constructLambda(model, baseName) } } else valueByModel.getOrPut(model) { when (model) { @@ -116,6 +119,43 @@ internal class CgVariableConstructor(val context: CgContext) : } } + private fun constructLambda(model: UtLambdaModel, baseName: String): CgVariable { + val lambdaMethodId = model.lambdaMethodId + val capturedValues = model.capturedValues + return newVar(model.samType, baseName) { + if (lambdaMethodId.isStatic) { + constructStaticLambda(model, capturedValues) + } else { + constructLambda(model, capturedValues) + } + } + } + + private fun constructStaticLambda(model: UtLambdaModel, capturedValues: List): CgMethodCall { + val capturedArguments = capturedValues.map { utilMethodProvider.capturedArgumentConstructorId(getClassOf(it.classId), getOrCreateVariable(it)) } + return testClassThisInstance[buildStaticLambda]( + getClassOf(model.samType), + getClassOf(model.declaringClass), + model.lambdaName, + *capturedArguments.toTypedArray() + ) + } + + private fun constructLambda(model: UtLambdaModel, capturedValues: List): CgMethodCall { + require(capturedValues.isNotEmpty()) { "Non-static lambda must capture `this` instance, so there must be at least one captured value" } + val capturedThisInstance = getOrCreateVariable(capturedValues.first()) + val capturedArguments = capturedValues + .subList(1, capturedValues.size) + .map { utilMethodProvider.capturedArgumentConstructorId(getClassOf(it.classId), getOrCreateVariable(it)) } + return testClassThisInstance[buildLambda]( + getClassOf(model.samType), + getClassOf(model.declaringClass), + model.lambdaName, + capturedThisInstance, + *capturedArguments.toTypedArray() + ) + } + private fun constructComposite(model: UtCompositeModel, baseName: String): CgVariable { val obj = if (model.isMock) { mockFrameworkManager.createMockFor(model, baseName) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/MockFrameworkManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/MockFrameworkManager.kt index d22b2f07a1..25251a35fa 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/MockFrameworkManager.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/MockFrameworkManager.kt @@ -114,16 +114,6 @@ internal abstract class CgVariableConstructorComponent(val context: CgContext) : else -> if (withExplicitClass) anyOfClass else any } - /** - * @return expression for [java.lang.Class] of the given [classId] - */ - protected fun getClassOf(classId: ClassId): CgExpression = - if (classId isAccessibleFrom testClassPackageName) { - CgGetJavaClass(classId) - } else { - newVar(classCgClassId) { Class::class.id[forName](classId.name) } - } - private fun matchByClass(id: ClassId): CgMethodCall = argumentMatchersClassId[anyOfClass](getClassOf(id)) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt index fcc21ff275..23ad88520f 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt @@ -124,6 +124,8 @@ interface CgStatementConstructor { fun doWhileLoop(condition: CgExpression, statements: () -> Unit) fun forEachLoop(init: CgForEachLoopBuilder.() -> Unit) + fun getClassOf(classId: ClassId): CgExpression + /** * Create a variable of type [java.lang.reflect.Field] by the given [FieldId]. */ @@ -311,6 +313,18 @@ internal class CgStatementConstructorImpl(context: CgContext) : currentBlock += buildCgForEachLoop(init) } + /** + * @return expression for [java.lang.Class] of the given [classId] + */ + override fun getClassOf(classId: ClassId): CgExpression { + return if (classId isAccessibleFrom testClassPackageName) { + CgGetJavaClass(classId) + } else { + newVar(classCgClassId) { Class::class.id[forName](classId.name) } + } + } + + override fun createFieldVariable(fieldId: FieldId): CgVariable { val declaringClass = newVar(classClassId) { classClassId[forName](fieldId.declaringClass.name) } val name = fieldId.name + "Field" diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt index 31f77c23e3..a8736cb4fc 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt @@ -319,6 +319,7 @@ fun arrayTypeOf(elementType: ClassId, isNullable: Boolean = false): ClassId { name = arrayIdName, canonicalName = "${elementType.canonicalName}[]", simpleName = "${elementType.simpleName}[]", + elementClassId = elementType, isNullable = isNullable ) else -> ClassId( @@ -329,12 +330,6 @@ fun arrayTypeOf(elementType: ClassId, isNullable: Boolean = false): ClassId { } } -@Suppress("unused") -internal fun CgContextOwner.getJavaClass(classId: ClassId): CgGetClass { - importIfNeeded(classId) - return CgGetJavaClass(classId) -} - internal fun Class<*>.overridesEquals(): Boolean = when { // Object does not override equals diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt index ec8abe977a..70c3a867d6 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt @@ -8,6 +8,7 @@ import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport import org.utbot.framework.codegen.model.util.CgExceptionHandler import org.utbot.framework.codegen.model.visitor.CgRendererContext import org.utbot.framework.codegen.model.visitor.CgVisitor +import org.utbot.framework.codegen.model.visitor.auxiliaryClassTextById import org.utbot.framework.codegen.model.visitor.utilMethodTextById import org.utbot.framework.plugin.api.BuiltinClassId import org.utbot.framework.plugin.api.ClassId @@ -45,6 +46,7 @@ interface CgElement { is CgSimpleRegion<*> -> visit(element) is CgTestMethodCluster -> visit(element) is CgExecutableUnderTestCluster -> visit(element) + is CgAuxiliaryClass -> visit(element) is CgUtilMethod -> visit(element) is CgTestMethod -> visit(element) is CgErrorTestMethod -> visit(element) @@ -243,6 +245,23 @@ data class CgExecutableUnderTestCluster( override val content: List> ) : CgRegion>() +sealed class CgUtilEntity : CgElement { + internal abstract fun getText(rendererContext: CgRendererContext): String +} + +data class CgAuxiliaryClass(val id: ClassId) : CgUtilEntity() { + override fun getText(rendererContext: CgRendererContext): String { + // we should not throw an exception on failure here, + // because this function is used during rendering and + // exceptions can crash rendering, so we use an empty string if the text is not found + return with(rendererContext) { + rendererContext.utilMethodProvider + .auxiliaryClassTextById(id, codegenLanguage) + .getOrDefault("") + } + } +} + /** * This class does not inherit from [CgMethod], because it only needs an [id], * and it does not need to have info about all the other properties of [CgMethod]. @@ -251,8 +270,8 @@ data class CgExecutableUnderTestCluster( * * @property id identifier of the util method. */ -data class CgUtilMethod(val id: MethodId) : CgElement { - internal fun getText(rendererContext: CgRendererContext): String { +data class CgUtilMethod(val id: MethodId) : CgUtilEntity() { + override fun getText(rendererContext: CgRendererContext): String { // we should not throw an exception on failure here, // because this function is used during rendering and // exceptions can crash rendering, so we use an empty string if the text is not found @@ -649,10 +668,32 @@ class CgThisInstance(override val type: ClassId) : CgValue // Variables +/** + * @property name name of the variable + * @property compileTimeType type a variable was declared with in the code. + * For example, `List` in `List l = new ArrayList<>();`. + * @property runtimeType actual type of an object stored in the variable. + * For example, `ArrayList` in `List l = new ArrayList<>();`. + */ open class CgVariable( val name: String, - override val type: ClassId, + val compileTimeType: ClassId, + val runtimeType: ClassId ) : CgValue { + + /** + * If [compileTimeType] and [runtimeType] are the same, a variable may be declared with this constructor. + */ + constructor(name: String, type: ClassId) : this(name, type, type) + + /** + * Property [type] inherited from [CgExpression] is delegated to [compileTimeType]. + * That's because when we access a variable in the code we can only work with it + * through its compileTimeType interface, not knowing about its concrete implementation (runtime type). + */ + override val type: ClassId + get() = compileTimeType + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -677,11 +718,10 @@ open class CgVariable( } /** - * A variable with explicit not null annotation if this is required in language. + * If expression is a variable, then this is a variable + * with explicit not null annotation if this is required in language. * - * Note: - * - in Java it is an equivalent of [CgVariable] - * - in Kotlin the difference is in addition of "!!" to the name + * In Kotlin the difference is in addition of "!!" to the expression */ class CgNotNullAssertion(val expression: CgExpression) : CgValue { override val type: ClassId diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt index 38d5633020..7c40574c8a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt @@ -15,6 +15,7 @@ import org.utbot.framework.codegen.model.tree.CgAbstractFieldAccess import org.utbot.framework.codegen.model.tree.CgAbstractMultilineComment import org.utbot.framework.codegen.model.tree.CgArrayElementAccess import org.utbot.framework.codegen.model.tree.CgAssignment +import org.utbot.framework.codegen.model.tree.CgAuxiliaryClass import org.utbot.framework.codegen.model.tree.CgBreakStatement import org.utbot.framework.codegen.model.tree.CgComment import org.utbot.framework.codegen.model.tree.CgCommentedAnnotation @@ -227,6 +228,12 @@ internal abstract class CgAbstractRenderer( println(regionEnd) } + override fun visit(element: CgAuxiliaryClass) { + val auxiliaryClassText = element.getText(context) + auxiliaryClassText.split("\n") + .forEach { line -> println(line) } + } + override fun visit(element: CgUtilMethod) { val utilMethodText = element.getText(context) utilMethodText.split("\n") diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt index 1a12c7d92a..367910f8f4 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt @@ -541,7 +541,7 @@ internal class CgKotlinRenderer(context: CgRendererContext, printer: CgPrinter = else -> { // we cannot access kClass for BuiltinClassId // we cannot use simple name here because this class can be not imported - if (id is BuiltinClassId) id.name else id.kClass.id.asString() + if (id is BuiltinClassId) id.canonicalName else id.kClass.id.asString() } } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt index 839c611dc4..1b89761575 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt @@ -12,6 +12,7 @@ import org.utbot.framework.codegen.model.tree.CgArrayAnnotationArgument import org.utbot.framework.codegen.model.tree.CgArrayElementAccess import org.utbot.framework.codegen.model.tree.CgArrayInitializer import org.utbot.framework.codegen.model.tree.CgAssignment +import org.utbot.framework.codegen.model.tree.CgAuxiliaryClass import org.utbot.framework.codegen.model.tree.CgBreakStatement import org.utbot.framework.codegen.model.tree.CgComment import org.utbot.framework.codegen.model.tree.CgCommentedAnnotation @@ -110,6 +111,7 @@ interface CgVisitor { fun visit(element: CgTestMethodCluster): R fun visit(element: CgExecutableUnderTestCluster): R + fun visit(element: CgAuxiliaryClass): R fun visit(element: CgUtilMethod): R // Methods diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt index c9dae09d08..895170454a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt @@ -11,9 +11,17 @@ import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.MockFramework import org.utbot.framework.plugin.api.util.fieldClassId import org.utbot.framework.plugin.api.util.id +import java.lang.invoke.CallSite +import java.lang.invoke.LambdaMetafactory +import java.lang.invoke.MethodHandle +import java.lang.invoke.MethodHandles +import java.lang.invoke.MethodType +import java.lang.reflect.Field +import java.lang.reflect.Method import java.lang.reflect.Modifier import java.util.Arrays import java.util.Objects +import java.util.stream.Collectors private enum class Visibility(val text: String) { PRIVATE("private"), @@ -55,11 +63,32 @@ internal fun UtilMethodProvider.utilMethodTextById( mapsDeepEqualsMethodId -> mapsDeepEquals(visibility, codegenLanguage) hasCustomEqualsMethodId -> hasCustomEquals(visibility, codegenLanguage) getArrayLengthMethodId -> getArrayLength(visibility, codegenLanguage) + buildStaticLambdaMethodId -> buildStaticLambda(visibility, codegenLanguage) + buildLambdaMethodId -> buildLambda(visibility, codegenLanguage) + // the following methods are used only by other util methods, so they can always be private + getLookupInMethodId -> getLookupIn(codegenLanguage) + getLambdaCapturedArgumentTypesMethodId -> getLambdaCapturedArgumentTypes(codegenLanguage) + getLambdaCapturedArgumentValuesMethodId -> getLambdaCapturedArgumentValues(codegenLanguage) + getInstantiatedMethodTypeMethodId -> getInstantiatedMethodType(codegenLanguage) + getLambdaMethodMethodId -> getLambdaMethod(codegenLanguage) + getSingleAbstractMethodMethodId -> getSingleAbstractMethod(codegenLanguage) else -> error("Unknown util method for class $this: $id") } } } +internal fun UtilMethodProvider.auxiliaryClassTextById( + id: ClassId, + codegenLanguage: CodegenLanguage +): Result = runCatching { + with(this) { + when (id) { + capturedArgumentClassId -> capturedArgumentClass(codegenLanguage) + else -> error("Unknown auxiliary class: $id") + } + } +} + private fun getEnumConstantByName(visibility: Visibility, language: CodegenLanguage): String = when (language) { CodegenLanguage.JAVA -> { @@ -799,6 +828,502 @@ private fun getArrayLength(visibility: Visibility, language: CodegenLanguage) = """.trimIndent() } +private fun buildStaticLambda(visibility: Visibility, language: CodegenLanguage) = + when (language) { + CodegenLanguage.JAVA -> + """ + /** + * @param samType a class representing a functional interface. + * @param declaringClass a class where the lambda is declared. + * @param lambdaName a name of the synthetic method that represents a lambda. + * @param capturedArguments a vararg containing {@link CapturedArgument} instances representing + * values that the given lambda has captured. + * @return an {@link Object} that represents an instance of the given functional interface {@code samType} + * and implements its single abstract method with the behavior of the given lambda. + */ + ${visibility by language}static Object buildStaticLambda( + Class samType, + Class declaringClass, + String lambdaName, + CapturedArgument... capturedArguments + ) throws Throwable { + // Create lookup for class where the lambda is declared in. + java.lang.invoke.MethodHandles.Lookup caller = getLookupIn(declaringClass); + + // Obtain the single abstract method of a functional interface whose instance we are building. + // For example, for `java.util.function.Predicate` it will be method `test`. + java.lang.reflect.Method singleAbstractMethod = getSingleAbstractMethod(samType); + String invokedName = singleAbstractMethod.getName(); + // Method type of single abstract method of the target functional interface. + java.lang.invoke.MethodType samMethodType = java.lang.invoke.MethodType.methodType(singleAbstractMethod.getReturnType(), singleAbstractMethod.getParameterTypes()); + + java.lang.reflect.Method lambdaMethod = getLambdaMethod(declaringClass, lambdaName); + lambdaMethod.setAccessible(true); + java.lang.invoke.MethodType lambdaMethodType = java.lang.invoke.MethodType.methodType(lambdaMethod.getReturnType(), lambdaMethod.getParameterTypes()); + java.lang.invoke.MethodHandle lambdaMethodHandle = caller.findStatic(declaringClass, lambdaName, lambdaMethodType); + + Class[] capturedArgumentTypes = getLambdaCapturedArgumentTypes(capturedArguments); + java.lang.invoke.MethodType invokedType = java.lang.invoke.MethodType.methodType(samType, capturedArgumentTypes); + java.lang.invoke.MethodType instantiatedMethodType = getInstantiatedMethodType(lambdaMethod, capturedArgumentTypes); + + // Create a CallSite for the given lambda. + java.lang.invoke.CallSite site = java.lang.invoke.LambdaMetafactory.metafactory( + caller, + invokedName, + invokedType, + samMethodType, + lambdaMethodHandle, + instantiatedMethodType); + + Object[] capturedValues = getLambdaCapturedArgumentValues(capturedArguments); + + // Get MethodHandle and pass captured values to it to obtain an object + // that represents the target functional interface instance. + java.lang.invoke.MethodHandle handle = site.getTarget(); + return handle.invokeWithArguments(capturedValues); + } + """.trimIndent() + CodegenLanguage.KOTLIN -> + """ + /** + * @param samType a class representing a functional interface. + * @param declaringClass a class where the lambda is declared. + * @param lambdaName a name of the synthetic method that represents a lambda. + * @param capturedArguments a vararg containing [CapturedArgument] instances representing + * values that the given lambda has captured. + * @return an [Any] that represents an instance of the given functional interface `samType` + * and implements its single abstract method with the behavior of the given lambda. + */ + ${visibility by language}fun buildStaticLambda( + samType: Class<*>, + declaringClass: Class<*>, + lambdaName: String, + vararg capturedArguments: CapturedArgument + ): Any { + // Create lookup for class where the lambda is declared in. + val caller = getLookupIn(declaringClass) + + // Obtain the single abstract method of a functional interface whose instance we are building. + // For example, for `java.util.function.Predicate` it will be method `test`. + val singleAbstractMethod = getSingleAbstractMethod(samType) + val invokedName = singleAbstractMethod.name + // Method type of single abstract method of the target functional interface. + val samMethodType = java.lang.invoke.MethodType.methodType(singleAbstractMethod.returnType, singleAbstractMethod.parameterTypes) + + val lambdaMethod = getLambdaMethod(declaringClass, lambdaName) + lambdaMethod.isAccessible = true + val lambdaMethodType = java.lang.invoke.MethodType.methodType(lambdaMethod.returnType, lambdaMethod.parameterTypes) + val lambdaMethodHandle = caller.findStatic(declaringClass, lambdaName, lambdaMethodType) + + val capturedArgumentTypes = getLambdaCapturedArgumentTypes(*capturedArguments) + val invokedType = java.lang.invoke.MethodType.methodType(samType, capturedArgumentTypes) + val instantiatedMethodType = getInstantiatedMethodType(lambdaMethod, capturedArgumentTypes) + + // Create a CallSite for the given lambda. + val site = java.lang.invoke.LambdaMetafactory.metafactory( + caller, + invokedName, + invokedType, + samMethodType, + lambdaMethodHandle, + instantiatedMethodType + ) + val capturedValues = getLambdaCapturedArgumentValues(*capturedArguments) + + // Get MethodHandle and pass captured values to it to obtain an object + // that represents the target functional interface instance. + val handle = site.target + return handle.invokeWithArguments(*capturedValues) + } + """.trimIndent() + } + +private fun buildLambda(visibility: Visibility, language: CodegenLanguage) = + when (language) { + CodegenLanguage.JAVA -> + """ + /** + * @param samType a class representing a functional interface. + * @param declaringClass a class where the lambda is declared. + * @param lambdaName a name of the synthetic method that represents a lambda. + * @param capturedReceiver an object of {@code declaringClass} that is captured by the lambda. + * When the synthetic lambda method is not static, it means that the lambda captures an instance + * of the class it is declared in. + * @param capturedArguments a vararg containing {@link CapturedArgument} instances representing + * values that the given lambda has captured. + * @return an {@link Object} that represents an instance of the given functional interface {@code samType} + * and implements its single abstract method with the behavior of the given lambda. + */ + ${visibility by language}static Object buildLambda( + Class samType, + Class declaringClass, + String lambdaName, + Object capturedReceiver, + CapturedArgument... capturedArguments + ) throws Throwable { + // Create lookup for class where the lambda is declared in. + java.lang.invoke.MethodHandles.Lookup caller = getLookupIn(declaringClass); + + // Obtain the single abstract method of a functional interface whose instance we are building. + // For example, for `java.util.function.Predicate` it will be method `test`. + java.lang.reflect.Method singleAbstractMethod = getSingleAbstractMethod(samType); + String invokedName = singleAbstractMethod.getName(); + // Method type of single abstract method of the target functional interface. + java.lang.invoke.MethodType samMethodType = java.lang.invoke.MethodType.methodType(singleAbstractMethod.getReturnType(), singleAbstractMethod.getParameterTypes()); + + java.lang.reflect.Method lambdaMethod = getLambdaMethod(declaringClass, lambdaName); + lambdaMethod.setAccessible(true); + java.lang.invoke.MethodType lambdaMethodType = java.lang.invoke.MethodType.methodType(lambdaMethod.getReturnType(), lambdaMethod.getParameterTypes()); + java.lang.invoke.MethodHandle lambdaMethodHandle = caller.findVirtual(declaringClass, lambdaName, lambdaMethodType); + + Class[] capturedArgumentTypes = getLambdaCapturedArgumentTypes(capturedArguments); + java.lang.invoke.MethodType invokedType = java.lang.invoke.MethodType.methodType(samType, capturedReceiver.getClass(), capturedArgumentTypes); + java.lang.invoke.MethodType instantiatedMethodType = getInstantiatedMethodType(lambdaMethod, capturedArgumentTypes); + + // Create a CallSite for the given lambda. + java.lang.invoke.CallSite site = java.lang.invoke.LambdaMetafactory.metafactory( + caller, + invokedName, + invokedType, + samMethodType, + lambdaMethodHandle, + instantiatedMethodType); + + Object[] capturedArgumentValues = getLambdaCapturedArgumentValues(capturedArguments); + + // This array will contain the value of captured receiver + // (`this` instance of class where the lambda is declared) + // and the values of captured arguments. + Object[] capturedValues = new Object[capturedArguments.length + 1]; + + // Setting the captured receiver value. + capturedValues[0] = capturedReceiver; + + // Setting the captured argument values. + System.arraycopy(capturedArgumentValues, 0, capturedValues, 1, capturedArgumentValues.length); + + // Get MethodHandle and pass captured values to it to obtain an object + // that represents the target functional interface instance. + java.lang.invoke.MethodHandle handle = site.getTarget(); + return handle.invokeWithArguments(capturedValues); + } + """.trimIndent() + CodegenLanguage.KOTLIN -> + """ + /** + * @param samType a class representing a functional interface. + * @param declaringClass a class where the lambda is declared. + * @param lambdaName a name of the synthetic method that represents a lambda. + * @param capturedReceiver an object of `declaringClass` that is captured by the lambda. + * When the synthetic lambda method is not static, it means that the lambda captures an instance + * of the class it is declared in. + * @param capturedArguments a vararg containing [CapturedArgument] instances representing + * values that the given lambda has captured. + * @return an [Any] that represents an instance of the given functional interface `samType` + * and implements its single abstract method with the behavior of the given lambda. + */ + ${visibility by language}fun buildLambda( + samType: Class<*>, + declaringClass: Class<*>, + lambdaName: String, + capturedReceiver: Any, + vararg capturedArguments: CapturedArgument + ): Any { + // Create lookup for class where the lambda is declared in. + val caller = getLookupIn(declaringClass) + + // Obtain the single abstract method of a functional interface whose instance we are building. + // For example, for `java.util.function.Predicate` it will be method `test`. + val singleAbstractMethod = getSingleAbstractMethod(samType) + val invokedName = singleAbstractMethod.name + // Method type of single abstract method of the target functional interface. + val samMethodType = java.lang.invoke.MethodType.methodType(singleAbstractMethod.returnType, singleAbstractMethod.parameterTypes) + + val lambdaMethod = getLambdaMethod(declaringClass, lambdaName) + lambdaMethod.isAccessible = true + val lambdaMethodType = java.lang.invoke.MethodType.methodType(lambdaMethod.returnType, lambdaMethod.parameterTypes) + val lambdaMethodHandle = caller.findVirtual(declaringClass, lambdaName, lambdaMethodType) + + val capturedArgumentTypes = getLambdaCapturedArgumentTypes(*capturedArguments) + val invokedType = java.lang.invoke.MethodType.methodType(samType, capturedReceiver.javaClass, *capturedArgumentTypes) + val instantiatedMethodType = getInstantiatedMethodType(lambdaMethod, capturedArgumentTypes) + + // Create a CallSite for the given lambda. + val site = java.lang.invoke.LambdaMetafactory.metafactory( + caller, + invokedName, + invokedType, + samMethodType, + lambdaMethodHandle, + instantiatedMethodType + ) + val capturedValues = mutableListOf() + .apply { + add(capturedReceiver) + addAll(getLambdaCapturedArgumentValues(*capturedArguments)) + }.toTypedArray() + + + // Get MethodHandle and pass captured values to it to obtain an object + // that represents the target functional interface instance. + val handle = site.target + return handle.invokeWithArguments(*capturedValues) + } + """.trimIndent() + } + +private fun getLookupIn(language: CodegenLanguage) = + when (language) { + CodegenLanguage.JAVA -> + """ + /** + * @param clazz a class to create lookup instance for. + * @return {@link java.lang.invoke.MethodHandles.Lookup} instance for the given {@code clazz}. + * It can be used, for example, to search methods of this {@code clazz}, even the {@code private} ones. + */ + private static java.lang.invoke.MethodHandles.Lookup getLookupIn(Class clazz) throws IllegalAccessException, NoSuchFieldException { + java.lang.invoke.MethodHandles.Lookup lookup = java.lang.invoke.MethodHandles.lookup().in(clazz); + + // Allow lookup to access all members of declaringClass, including the private ones. + // For example, it is useful to access private synthetic methods representing lambdas. + java.lang.reflect.Field allowedModes = java.lang.invoke.MethodHandles.Lookup.class.getDeclaredField("allowedModes"); + allowedModes.setAccessible(true); + allowedModes.setInt(lookup, java.lang.reflect.Modifier.PUBLIC | java.lang.reflect.Modifier.PROTECTED | java.lang.reflect.Modifier.PRIVATE | java.lang.reflect.Modifier.STATIC); + + return lookup; + } + """.trimIndent() + CodegenLanguage.KOTLIN -> + """ + /** + * @param clazz a class to create lookup instance for. + * @return [java.lang.invoke.MethodHandles.Lookup] instance for the given [clazz]. + * It can be used, for example, to search methods of this [clazz], even the `private` ones. + */ + private fun getLookupIn(clazz: Class<*>): java.lang.invoke.MethodHandles.Lookup { + val lookup = java.lang.invoke.MethodHandles.lookup().`in`(clazz) + + // Allow lookup to access all members of declaringClass, including the private ones. + // For example, it is useful to access private synthetic methods representing lambdas. + val allowedModes = java.lang.invoke.MethodHandles.Lookup::class.java.getDeclaredField("allowedModes") + allowedModes.isAccessible = true + allowedModes.setInt(lookup, java.lang.reflect.Modifier.PUBLIC or java.lang.reflect.Modifier.PROTECTED or java.lang.reflect.Modifier.PRIVATE or java.lang.reflect.Modifier.STATIC) + + return lookup + } + """.trimIndent() + } + +private fun getLambdaCapturedArgumentTypes(language: CodegenLanguage) = + when (language) { + CodegenLanguage.JAVA -> + """ + /** + * @param capturedArguments values captured by some lambda. Note that this argument does not contain + * the possibly captured instance of the class where the lambda is declared. + * It contains all the other captured values. They are represented as arguments of the synthetic method + * that the lambda is compiled into. Hence, the name of the argument. + * @return types of the given {@code capturedArguments}. + * These types are required to build {@code invokedType}, which represents + * the target functional interface with info about captured values' types. + * See {@link java.lang.invoke.LambdaMetafactory#metafactory} method documentation for more details on what {@code invokedType} is. + */ + private static Class[] getLambdaCapturedArgumentTypes(CapturedArgument... capturedArguments) { + Class[] capturedArgumentTypes = new Class[capturedArguments.length]; + for (int i = 0; i < capturedArguments.length; i++) { + capturedArgumentTypes[i] = capturedArguments[i].type; + } + return capturedArgumentTypes; + } + """.trimIndent() + CodegenLanguage.KOTLIN -> + """ + /** + * @param capturedArguments values captured by some lambda. Note that this argument does not contain + * the possibly captured instance of the class where the lambda is declared. + * It contains all the other captured values. They are represented as arguments of the synthetic method + * that the lambda is compiled into. Hence, the name of the argument. + * @return types of the given `capturedArguments`. + * These types are required to build `invokedType`, which represents + * the target functional interface with info about captured values' types. + * See [java.lang.invoke.LambdaMetafactory.metafactory] method documentation for more details on what `invokedType` is. + */ + private fun getLambdaCapturedArgumentTypes(vararg capturedArguments: CapturedArgument): Array> { + return capturedArguments + .map { it.type } + .toTypedArray() + } + """.trimIndent() + } + +private fun getLambdaCapturedArgumentValues(language: CodegenLanguage) = + when (language) { + CodegenLanguage.JAVA -> + """ + /** + * Obtain captured values to be used as captured arguments in the lambda call. + */ + private static Object[] getLambdaCapturedArgumentValues(CapturedArgument... capturedArguments) { + return java.util.Arrays.stream(capturedArguments) + .map(argument -> argument.value) + .toArray(); + } + """.trimIndent() + CodegenLanguage.KOTLIN -> + """ + /** + * Obtain captured values to be used as captured arguments in the lambda call. + */ + private fun getLambdaCapturedArgumentValues(vararg capturedArguments: CapturedArgument): Array { + return capturedArguments + .map { it.value } + .toTypedArray() + } + """.trimIndent() + } + +private fun getInstantiatedMethodType(language: CodegenLanguage) = + when (language) { + CodegenLanguage.JAVA -> + """ + /** + * @param lambdaMethod {@link java.lang.reflect.Method} that represents a synthetic method for lambda. + * @param capturedArgumentTypes types of values captured by lambda. + * @return {@link java.lang.invoke.MethodType} that represents the value of argument {@code instantiatedMethodType} + * of method {@link java.lang.invoke.LambdaMetafactory#metafactory}. + */ + private static java.lang.invoke.MethodType getInstantiatedMethodType(java.lang.reflect.Method lambdaMethod, Class[] capturedArgumentTypes) { + // Types of arguments of synthetic method (representing lambda) without the types of captured values. + java.util.List> instantiatedMethodParamTypeList = java.util.Arrays.stream(lambdaMethod.getParameterTypes()) + .skip(capturedArgumentTypes.length) + .collect(java.util.stream.Collectors.toList()); + + // The same types, but stored in an array. + Class[] instantiatedMethodParamTypes = new Class[instantiatedMethodParamTypeList.size()]; + for (int i = 0; i < instantiatedMethodParamTypeList.size(); i++) { + instantiatedMethodParamTypes[i] = instantiatedMethodParamTypeList.get(i); + } + + return java.lang.invoke.MethodType.methodType(lambdaMethod.getReturnType(), instantiatedMethodParamTypes); + } + """.trimIndent() + CodegenLanguage.KOTLIN -> + """ + /** + * @param lambdaMethod [java.lang.reflect.Method] that represents a synthetic method for lambda. + * @param capturedArgumentTypes types of values captured by lambda. + * @return [java.lang.invoke.MethodType] that represents the value of argument `instantiatedMethodType` + * of method [java.lang.invoke.LambdaMetafactory.metafactory]. + */ + private fun getInstantiatedMethodType( + lambdaMethod: java.lang.reflect.Method, + capturedArgumentTypes: Array> + ): java.lang.invoke.MethodType { + // Types of arguments of synthetic method (representing lambda) without the types of captured values. + val instantiatedMethodParamTypes = lambdaMethod.parameterTypes + .drop(capturedArgumentTypes.size) + .toTypedArray() + + return java.lang.invoke.MethodType.methodType(lambdaMethod.returnType, instantiatedMethodParamTypes) + } + """.trimIndent() + } + +private fun getLambdaMethod(language: CodegenLanguage) = + when (language) { + CodegenLanguage.JAVA -> + """ + /** + * @param declaringClass class where a lambda is declared. + * @param lambdaName name of synthetic method that represents a lambda. + * @return {@link java.lang.reflect.Method} instance for the synthetic method that represent a lambda. + */ + private static java.lang.reflect.Method getLambdaMethod(Class declaringClass, String lambdaName) { + return java.util.Arrays.stream(declaringClass.getDeclaredMethods()) + .filter(method -> method.getName().equals(lambdaName)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No lambda method named " + lambdaName + " was found in class: " + declaringClass.getCanonicalName())); + } + """.trimIndent() + CodegenLanguage.KOTLIN -> + """ + /** + * @param declaringClass class where a lambda is declared. + * @param lambdaName name of synthetic method that represents a lambda. + * @return [java.lang.reflect.Method] instance for the synthetic method that represent a lambda. + */ + private fun getLambdaMethod(declaringClass: Class<*>, lambdaName: String): java.lang.reflect.Method { + return declaringClass.declaredMethods.firstOrNull { it.name == lambdaName } + ?: throw IllegalArgumentException("No lambda method named ${'$'}lambdaName was found in class: ${'$'}{declaringClass.canonicalName}") + } + """.trimIndent() + } + +private fun getSingleAbstractMethod(language: CodegenLanguage) = + when (language) { + CodegenLanguage.JAVA -> + """ + private static java.lang.reflect.Method getSingleAbstractMethod(Class clazz) { + java.util.List abstractMethods = java.util.Arrays.stream(clazz.getMethods()) + .filter(method -> java.lang.reflect.Modifier.isAbstract(method.getModifiers())) + .collect(java.util.stream.Collectors.toList()); + + if (abstractMethods.isEmpty()) { + throw new IllegalArgumentException("No abstract methods found in class: " + clazz.getCanonicalName()); + } + if (abstractMethods.size() > 1) { + throw new IllegalArgumentException("More than one abstract method found in class: " + clazz.getCanonicalName()); + } + + return abstractMethods.get(0); + } + """.trimIndent() + CodegenLanguage.KOTLIN -> + """ + /** + * @param clazz functional interface + * @return a [java.lang.reflect.Method] for the single abstract method of the given functional interface `clazz`. + */ + private fun getSingleAbstractMethod(clazz: Class<*>): java.lang.reflect.Method { + val abstractMethods = clazz.methods.filter { Modifier.isAbstract(it.modifiers) } + require(abstractMethods.isNotEmpty()) { "No abstract methods found in class: " + clazz.canonicalName } + require(abstractMethods.size <= 1) { "More than one abstract method found in class: " + clazz.canonicalName } + return abstractMethods[0] + } + """.trimIndent() + } + +private fun capturedArgumentClass(language: CodegenLanguage) = + when (language) { + CodegenLanguage.JAVA -> + """ + /** + * This class represents the {@code type} and {@code value} of a value captured by lambda. + * Captured values are represented as arguments of a synthetic method that lambda is compiled into, + * hence the name of the class. + */ + static class CapturedArgument { + private Class type; + private Object value; + + public CapturedArgument(Class type, Object value) { + this.type = type; + this.value = value; + } + } + """.trimIndent() + CodegenLanguage.KOTLIN -> { + """ + /** + * This class represents the `type` and `value` of a value captured by lambda. + * Captured values are represented as arguments of a synthetic method that lambda is compiled into, + * hence the name of the class. + */ + data class CapturedArgument(val type: Class<*>, val value: Any?) + """.trimIndent() + } + } + internal fun CgContextOwner.importUtilMethodDependencies(id: MethodId) { // if util methods come from a separate UtUtils class and not from the test class, // then we don't need to import any other methods, hence we return from method @@ -856,6 +1381,47 @@ private fun TestClassUtilMethodProvider.regularImportsByUtilMethod( } hasCustomEqualsMethodId -> emptyList() getArrayLengthMethodId -> listOf(java.lang.reflect.Array::class.id) + buildStaticLambdaMethodId -> when (codegenLanguage) { + CodegenLanguage.JAVA -> listOf( + MethodHandles::class.id, Method::class.id, MethodType::class.id, + MethodHandle::class.id, CallSite::class.id, LambdaMetafactory::class.id + ) + CodegenLanguage.KOTLIN -> listOf(MethodType::class.id, LambdaMetafactory::class.id) + } + buildLambdaMethodId -> when (codegenLanguage) { + CodegenLanguage.JAVA -> listOf( + MethodHandles::class.id, Method::class.id, MethodType::class.id, + MethodHandle::class.id, CallSite::class.id, LambdaMetafactory::class.id + ) + CodegenLanguage.KOTLIN -> listOf(MethodType::class.id, LambdaMetafactory::class.id) + } + getLookupInMethodId -> when (codegenLanguage) { + CodegenLanguage.JAVA -> listOf(MethodHandles::class.id, Field::class.id, Modifier::class.id) + CodegenLanguage.KOTLIN -> listOf(MethodHandles::class.id, Modifier::class.id) + } + getLambdaCapturedArgumentTypesMethodId -> when (codegenLanguage) { + CodegenLanguage.JAVA -> listOf(LambdaMetafactory::class.id) + CodegenLanguage.KOTLIN -> listOf(LambdaMetafactory::class.id) + } + getLambdaCapturedArgumentValuesMethodId -> when (codegenLanguage) { + CodegenLanguage.JAVA -> listOf(Arrays::class.id) + CodegenLanguage.KOTLIN -> emptyList() + } + getInstantiatedMethodTypeMethodId -> when (codegenLanguage) { + CodegenLanguage.JAVA -> listOf( + Method::class.id, MethodType::class.id, LambdaMetafactory::class.id, + java.util.List::class.id, Arrays::class.id, Collectors::class.id + ) + CodegenLanguage.KOTLIN -> listOf(Method::class.id, MethodType::class.id, LambdaMetafactory::class.id) + } + getLambdaMethodMethodId -> when (codegenLanguage) { + CodegenLanguage.JAVA -> listOf(Method::class.id, Arrays::class.id) + CodegenLanguage.KOTLIN -> listOf(Method::class.id) + } + getSingleAbstractMethodMethodId -> listOf( + Method::class.id, java.util.List::class.id, Arrays::class.id, + Modifier::class.id, Collectors::class.id + ) else -> error("Unknown util method for class $this: $id") } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockValueConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockValueConstructor.kt index 8bf5b4fc12..25aba74c7f 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockValueConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockValueConstructor.kt @@ -44,6 +44,10 @@ import kotlin.reflect.KClass import org.mockito.Mockito import org.mockito.stubbing.Answer import org.objectweb.asm.Type +import org.utbot.engine.util.lambda.CapturedArgument +import org.utbot.engine.util.lambda.constructLambda +import org.utbot.engine.util.lambda.constructStaticLambda +import org.utbot.framework.plugin.api.UtLambdaModel import org.utbot.instrumentation.process.runSandbox /** @@ -126,6 +130,7 @@ class MockValueConstructor( is UtCompositeModel -> UtConcreteValue(constructObject(model), model.classId.jClass) is UtArrayModel -> UtConcreteValue(constructArray(model)) is UtAssembleModel -> UtConcreteValue(constructFromAssembleModel(model), model.classId.jClass) + is UtLambdaModel -> UtConcreteValue(constructFromLambdaModel(model)) is UtVoidModel -> UtConcreteValue(Unit) } } @@ -363,6 +368,32 @@ class MockValueConstructor( return resultsCache[assembleModel] ?: error("Can't assemble model: $assembleModel") } + private fun constructFromLambdaModel(lambdaModel: UtLambdaModel): Any { + // A class representing a functional interface. + val samType: Class<*> = lambdaModel.samType.jClass + // A class where the lambda is declared. + val declaringClass: Class<*> = lambdaModel.declaringClass.jClass + // A name of the synthetic method that represents a lambda. + val lambdaName = lambdaModel.lambdaName + + return if (lambdaModel.lambdaMethodId.isStatic) { + val capturedArguments = lambdaModel.capturedValues + .map { model -> CapturedArgument(type = model.classId.jClass, value = value(model)) } + .toTypedArray() + constructStaticLambda(samType, declaringClass, lambdaName, *capturedArguments) + } else { + val capturedReceiverModel = lambdaModel.capturedValues.firstOrNull() + ?: error("Non-static lambda must capture `this` instance, so there must be at least one captured value") + + // Values that the given lambda has captured. + val capturedReceiver = value(capturedReceiverModel) ?: error("Captured receiver of lambda must not be null") + val capturedArguments = lambdaModel.capturedValues.subList(1, lambdaModel.capturedValues.size) + .map { model -> CapturedArgument(type = model.classId.jClass, value = value(model)) } + .toTypedArray() + constructLambda(samType, declaringClass, lambdaName, capturedReceiver, *capturedArguments) + } + } + /** * Updates instance state with [UtExecutableCallModel] invocation. */ diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/fields/ExecutionStateAnalyzer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/fields/ExecutionStateAnalyzer.kt index cf2b3c1c32..196ee58532 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/fields/ExecutionStateAnalyzer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/fields/ExecutionStateAnalyzer.kt @@ -12,6 +12,7 @@ import org.utbot.framework.plugin.api.UtClassRefModel import org.utbot.framework.plugin.api.UtCompositeModel import org.utbot.framework.plugin.api.UtEnumConstantModel import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtLambdaModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtPrimitiveModel @@ -232,6 +233,10 @@ private class FieldStateVisitor : UtModelVisitor() { } } + override fun visit(element: UtLambdaModel, data: FieldData) { + recordFieldState(data, element) + } + private fun recordFieldState(data: FieldData, model: UtModel) { val fields = data.fields val path = data.path diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/minimization/Minimization.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/minimization/Minimization.kt index 380ab74d2c..c6b336098a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/minimization/Minimization.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/minimization/Minimization.kt @@ -13,6 +13,7 @@ import org.utbot.framework.plugin.api.UtExecutableCallModel import org.utbot.framework.plugin.api.UtExecution import org.utbot.framework.plugin.api.UtExecutionFailure import org.utbot.framework.plugin.api.UtExecutionResult +import org.utbot.framework.plugin.api.UtLambdaModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtPrimitiveModel @@ -223,6 +224,7 @@ private fun UtModel.calculateSize(used: MutableSet = mutableSetOf()): I is UtClassRefModel, is UtEnumConstantModel, is UtArrayModel -> 1 is UtAssembleModel -> 1 + allStatementsChain.sumOf { it.calculateSize(used) } is UtCompositeModel -> 1 + fields.values.sumOf { it.calculateSize(used) } + is UtLambdaModel -> 1 + capturedValues.sumOf { it.calculateSize(used) } } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtModelVisitor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtModelVisitor.kt index fd043ad3b2..093992d1b7 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtModelVisitor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtModelVisitor.kt @@ -5,6 +5,7 @@ import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtClassRefModel import org.utbot.framework.plugin.api.UtCompositeModel import org.utbot.framework.plugin.api.UtEnumConstantModel +import org.utbot.framework.plugin.api.UtLambdaModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtPrimitiveModel @@ -33,6 +34,7 @@ abstract class UtModelVisitor { is UtArrayModel -> visit(element, data) is UtAssembleModel -> visit(element, data) is UtCompositeModel -> visit(element, data) + is UtLambdaModel -> visit(element, data) } } @@ -41,6 +43,7 @@ abstract class UtModelVisitor { protected abstract fun visit(element: UtArrayModel, data: D) protected abstract fun visit(element: UtAssembleModel, data: D) protected abstract fun visit(element: UtCompositeModel, data: D) + protected abstract fun visit(element: UtLambdaModel, data: D) /** * Returns true when we can traverse the given model. From 2d2792dae36f5ccc52e6c38b8bf3b3b1d369d2ab Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Fri, 2 Sep 2022 13:00:33 +0300 Subject: [PATCH 03/10] Add tests on lambdas --- .../lambda/CustomPredicateExampleTest.kt | 69 +++++++++++++++++++ .../lambda/CustomPredicateExample.java | 65 +++++++++++++++++ .../PredicateCapturedLocalVariable.java | 18 +++++ .../PredicateCapturedNonStaticField.java | 18 +++++ .../lambda/PredicateCapturedParameter.java | 18 +++++ .../lambda/PredicateCapturedStaticField.java | 18 +++++ .../lambda/PredicateNoCapturedValues.java | 18 +++++ 7 files changed, 224 insertions(+) create mode 100644 utbot-sample/src/main/java/org/utbot/examples/lambda/CustomPredicateExample.java create mode 100644 utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedLocalVariable.java create mode 100644 utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedNonStaticField.java create mode 100644 utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedParameter.java create mode 100644 utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedStaticField.java create mode 100644 utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateNoCapturedValues.java diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/CustomPredicateExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/CustomPredicateExampleTest.kt index e69de29bb2..b4eb08a30f 100644 --- a/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/CustomPredicateExampleTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/CustomPredicateExampleTest.kt @@ -0,0 +1,69 @@ +package org.utbot.examples.lambda + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.tests.infrastructure.DoNotCalculate +import org.utbot.tests.infrastructure.UtValueTestCaseChecker +import org.utbot.tests.infrastructure.isException + +class CustomPredicateExampleTest : UtValueTestCaseChecker(testClass = CustomPredicateExample::class) { + @Test + fun testNoCapturedValuesPredicateCheck() { + checkWithException( + CustomPredicateExample::noCapturedValuesPredicateCheck, + eq(3), + { predicate, x, r -> !predicate.test(x) && r.getOrNull() == false }, + { predicate, x, r -> predicate.test(x) && r.getOrNull() == true }, + { predicate, _, r -> predicate == null && r.isException() }, + coverage = DoNotCalculate + ) + } + + @Test + fun testCapturedLocalVariablePredicateCheck() { + checkWithException( + CustomPredicateExample::capturedLocalVariablePredicateCheck, + eq(3), + { predicate, x, r -> !predicate.test(x) && r.getOrNull() == false }, + { predicate, x, r -> predicate.test(x) && r.getOrNull() == true }, + { predicate, _, r -> predicate == null && r.isException() }, + coverage = DoNotCalculate + ) + } + + @Test + fun testCapturedParameterPredicateCheck() { + checkWithException( + CustomPredicateExample::capturedParameterPredicateCheck, + eq(3), + { predicate, x, r -> !predicate.test(x) && r.getOrNull() == false }, + { predicate, x, r -> predicate.test(x) && r.getOrNull() == true }, + { predicate, _, r -> predicate == null && r.isException() }, + coverage = DoNotCalculate + ) + } + + @Test + fun testCapturedStaticFieldPredicateCheck() { + checkWithException( + CustomPredicateExample::capturedStaticFieldPredicateCheck, + eq(3), + { predicate, x, r -> !predicate.test(x) && r.getOrNull() == false }, + { predicate, x, r -> predicate.test(x) && r.getOrNull() == true }, + { predicate, _, r -> predicate == null && r.isException() }, + coverage = DoNotCalculate + ) + } + + @Test + fun testCapturedNonStaticFieldPredicateCheck() { + checkWithException( + CustomPredicateExample::capturedNonStaticFieldPredicateCheck, + eq(3), + { predicate, x, r -> !predicate.test(x) && r.getOrNull() == false }, + { predicate, x, r -> predicate.test(x) && r.getOrNull() == true }, + { predicate, _, r -> predicate == null && r.isException() }, + coverage = DoNotCalculate + ) + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/lambda/CustomPredicateExample.java b/utbot-sample/src/main/java/org/utbot/examples/lambda/CustomPredicateExample.java new file mode 100644 index 0000000000..bb1d417cd6 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/lambda/CustomPredicateExample.java @@ -0,0 +1,65 @@ +package org.utbot.examples.lambda; + +public class CustomPredicateExample { + static int someStaticField = 5; + int someNonStaticField = 10; + + public boolean noCapturedValuesPredicateCheck(PredicateNoCapturedValues predicate, int x) { + //noinspection RedundantIfStatement + if (predicate.test(x)) { + return true; + } else { + return false; + } + } + + public boolean capturedLocalVariablePredicateCheck(PredicateCapturedLocalVariable predicate, int x) { + //noinspection RedundantIfStatement + if (predicate.test(x)) { + return true; + } else { + return false; + } + } + + public boolean capturedParameterPredicateCheck(PredicateCapturedParameter predicate, int x) { + //noinspection RedundantIfStatement + if (predicate.test(x)) { + return true; + } else { + return false; + } + } + + public boolean capturedStaticFieldPredicateCheck(PredicateCapturedStaticField predicate, int x) { + //noinspection RedundantIfStatement + if (predicate.test(x)) { + return true; + } else { + return false; + } + } + + public boolean capturedNonStaticFieldPredicateCheck(PredicateCapturedNonStaticField predicate, int x) { + //noinspection RedundantIfStatement + if (predicate.test(x)) { + return true; + } else { + return false; + } + } + + // this method contains implementation of functional interface 'CustomPredicate' + void someLambdas(int someParameter) { + PredicateNoCapturedValues predicate1 = (x) -> x == 5; + + int localVariable = 10; + PredicateCapturedLocalVariable predicate2 = (x) -> x + localVariable == 5; + + PredicateCapturedParameter predicate3 = (x) -> x + someParameter == 5; + + PredicateCapturedStaticField predicate4 = (x) -> x + someStaticField == 5; + + PredicateCapturedNonStaticField predicate5 = (x) -> x + someNonStaticField == 5; + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedLocalVariable.java b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedLocalVariable.java new file mode 100644 index 0000000000..04277c9647 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedLocalVariable.java @@ -0,0 +1,18 @@ +package org.utbot.examples.lambda; + +/** + * This functional interface is implemented via one lambda in {@link CustomPredicateExample#someLambdas}. + * + * DO NOT implement it anymore, because test on {@link CustomPredicateExample#capturedLocalVariablePredicateCheck} + * relies on the fact that there is only one implementation and that implementation is lambda. + * In addition, in this case we want the implementing lambda to capture some local variable. + * + * It is important because we want to test how we generate tests when the only available implementation is lambda, + * and we want to check different cases: with or without captured values. Note that lambdas may capture + * local variables, method parameters, static and non-static fields. That is why we have multiple functional interfaces + * in this package: one for each case. + */ +@FunctionalInterface +public interface PredicateCapturedLocalVariable { + boolean test(T value); +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedNonStaticField.java b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedNonStaticField.java new file mode 100644 index 0000000000..16a352f7bc --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedNonStaticField.java @@ -0,0 +1,18 @@ +package org.utbot.examples.lambda; + +/** + * This functional interface is implemented via one lambda in {@link CustomPredicateExample#someLambdas}. + * + * DO NOT implement it anymore, because test on {@link CustomPredicateExample#capturedNonStaticFieldPredicateCheck} + * relies on the fact that there is only one implementation and that implementation is lambda. + * In addition, in this case we want the implementing lambda to capture some non-static field of a class. + * + * It is important because we want to test how we generate tests when the only available implementation is lambda, + * and we want to check different cases: with or without captured values. Note that lambdas may capture + * local variables, method parameters, static and non-static fields. That is why we have multiple functional interfaces + * in this package: one for each case. + */ +@FunctionalInterface +public interface PredicateCapturedNonStaticField { + boolean test(T value); +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedParameter.java b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedParameter.java new file mode 100644 index 0000000000..c0f38c7d62 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedParameter.java @@ -0,0 +1,18 @@ +package org.utbot.examples.lambda; + +/** + * This functional interface is implemented via one lambda in {@link CustomPredicateExample#someLambdas}. + * + * DO NOT implement it anymore, because test on {@link CustomPredicateExample#capturedParameterPredicateCheck} + * relies on the fact that there is only one implementation and that implementation is lambda. + * In addition, in this case we want the implementing lambda to capture some method parameter. + * + * It is important because we want to test how we generate tests when the only available implementation is lambda, + * and we want to check different cases: with or without captured values. Note that lambdas may capture + * local variables, method parameters, static and non-static fields. That is why we have multiple functional interfaces + * in this package: one for each case. + */ +@FunctionalInterface +public interface PredicateCapturedParameter { + boolean test(T value); +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedStaticField.java b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedStaticField.java new file mode 100644 index 0000000000..9dfaaaffe8 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedStaticField.java @@ -0,0 +1,18 @@ +package org.utbot.examples.lambda; + +/** + * This functional interface is implemented via one lambda in {@link CustomPredicateExample#someLambdas}. + * + * DO NOT implement it anymore, because test on {@link CustomPredicateExample#capturedStaticFieldPredicateCheck} + * relies on the fact that there is only one implementation and that implementation is lambda. + * In addition, in this case we want the implementing lambda to capture some static field of some class. + * + * It is important because we want to test how we generate tests when the only available implementation is lambda, + * and we want to check different cases: with or without captured values. Note that lambdas may capture + * local variables, method parameters, static and non-static fields. That is why we have multiple functional interfaces + * in this package: one for each case. + */ +@FunctionalInterface +public interface PredicateCapturedStaticField { + boolean test(T value); +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateNoCapturedValues.java b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateNoCapturedValues.java new file mode 100644 index 0000000000..57d3974e29 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateNoCapturedValues.java @@ -0,0 +1,18 @@ +package org.utbot.examples.lambda; + +/** + * This functional interface is implemented via one lambda in {@link CustomPredicateExample#someLambdas}. + * + * DO NOT implement it anymore, because test on {@link CustomPredicateExample#noCapturedValuesPredicateCheck} + * relies on the fact that there is only one implementation and that implementation is lambda. + * In addition, in this case we want the implementing lambda to not capture any values. + * + * It is important because we want to test how we generate tests when the only available implementation is lambda, + * and we want to check different cases: with or without captured values. Note that lambdas may capture + * local variables, method parameters, static and non-static fields. That is why we have multiple functional interfaces + * in this package: one for each case. + */ +@FunctionalInterface +public interface PredicateNoCapturedValues { + boolean test(T value); +} From 10ec634db56df9cca5cd7ad47603d8d2474e06fd Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Sat, 3 Sep 2022 23:58:00 +0300 Subject: [PATCH 04/10] Fix review comments --- .../kotlin/org/utbot/framework/UtSettings.kt | 10 +-- .../org/utbot/framework/plugin/api/Api.kt | 17 ++++- .../utbot/framework/plugin/api/util/IdUtil.kt | 19 ----- .../kotlin/org/utbot/engine/Extensions.kt | 5 +- .../kotlin/org/utbot/engine/MockStrategy.kt | 22 ++---- .../main/kotlin/org/utbot/engine/Resolver.kt | 4 +- .../main/kotlin/org/utbot/engine/Traverser.kt | 2 +- .../kotlin/org/utbot/engine/TypeResolver.kt | 37 +++++----- .../util/lambda/LambdaConstructionUtils.kt | 69 ++++++++++++------- .../assemble/AssembleModelGenerator.kt | 1 - .../constructor/tree/CgMethodConstructor.kt | 10 +-- .../constructor/tree/CgVariableConstructor.kt | 12 ++-- .../framework/codegen/model/tree/CgElement.kt | 51 +++++--------- .../codegen/model/visitor/UtilMethods.kt | 14 ++-- 14 files changed, 128 insertions(+), 145 deletions(-) diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt index 1d638d8f57..af653ab4bb 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt @@ -73,7 +73,7 @@ object UtSettings : AbstractSettings( * Timeout for symbolic execution * */ - var utBotGenerationTimeoutInMillis by getLongProperty(600000L) + var utBotGenerationTimeoutInMillis by getLongProperty(60000L) /** * Random seed in path selector. @@ -113,7 +113,7 @@ object UtSettings : AbstractSettings( * * False by default, set it to true if debug visualization is needed. */ - var useDebugVisualization by getBooleanProperty(true) + var useDebugVisualization by getBooleanProperty(false) /** * Set the value to true if you want to automatically copy the path of the @@ -136,7 +136,7 @@ object UtSettings : AbstractSettings( * @see * UtBot Expression Optimizations */ - var useExpressionSimplification by getBooleanProperty(false) + var useExpressionSimplification by getBooleanProperty(true) /* * Activate or deactivate tests on comments && names/displayNames @@ -188,7 +188,7 @@ object UtSettings : AbstractSettings( * * True by default. */ - var useConcreteExecution by getBooleanProperty(false) + var useConcreteExecution by getBooleanProperty(true) /** * Enable check of full coverage for methods with code generations tests. @@ -300,7 +300,7 @@ object UtSettings : AbstractSettings( * * False by default. */ - var enableUnsatCoreCalculationForHardConstraints by getBooleanProperty(true) + var enableUnsatCoreCalculationForHardConstraints by getBooleanProperty(false) /** * Enable it to process states with unknown solver status diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt index 99dc82efe3..d8366d4200 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt @@ -521,10 +521,21 @@ data class UtAssembleModel( * Depending on the captured variables, this method will be either static or non-static. * * Since lambdas are not classes we cannot use a class loader to get info about them as we can do for other models. - * Hence, the necessity for this specific lambda model that will be processed differently: instead of working - * with a class we will be working with the synthetic method that represents our lambda. + * Hence, the necessity for this specific lambda model that will be processed differently: + * instead of working with a class we will be working with the synthetic method that represents our lambda. + * + * @property id see documentation on [UtReferenceModel.id] + * @property samType the type of functional interface that this lambda will be used for (e.g. [java.util.function.Predicate]). + * `sam` means single abstract method. See https://kotlinlang.org/docs/fun-interfaces.html for more details about it in Kotlin. + * In Java it means the same. + * @property declaringClass a class where the lambda is located. + * We need this class, because the synthetic method the lambda is compiled into will be located in it. + * @property lambdaName the name of synthetic method the lambda is compiled into. + * We need it to find this method in the [declaringClass] + * @property capturedValues models of values captured by lambda. + * Lambdas can capture local variables, method arguments, static and non-static fields. */ -// TODO: what about support for Kotlin lambdas (they are not exactly the same as Java's due to functional types) +// TODO: what about support for Kotlin lambdas and function types? See https://github.com/UnitTestBot/UTBotJava/issues/852 class UtLambdaModel( override val id: Int?, val samType: ClassId, diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt index 5f10a167f8..4a83a2f2b9 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt @@ -53,25 +53,6 @@ val ClassId.denotableType: ClassId } } -private val isLambdaRegex = ".*(\\$)lambda_.*".toRegex() - -val ClassId.isLambda: Boolean - get() = name matches isLambdaRegex - -val ClassId.isFunctionalInterface: Boolean - get() { - // we cannot access jClass of a builtin type, so we have to return false - if (this is BuiltinClassId) return false - // we cannot access jClass for lambdas, but we know that it is not a functional interface anyway - if (this.isLambda) return false - - val clazz = this.jClass - if (!clazz.isInterface) return false - - val abstractMethods = clazz.methods.filter { java.lang.reflect.Modifier.isAbstract(it.modifiers) } - return abstractMethods.size == 1 - } - @Suppress("unused") val ClassId.enclosingClass: ClassId? get() = jClass.enclosingClass?.id diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt index 785c62860b..1de5fa185a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt @@ -35,7 +35,6 @@ import org.utbot.framework.UtSettings.checkNpeInNestedMethods import org.utbot.framework.UtSettings.checkNpeInNestedNotPrivateMethods import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.id -import org.utbot.framework.plugin.api.util.isLambda import soot.ArrayType import soot.PrimType import soot.RefLikeType @@ -193,8 +192,10 @@ private val isAnonymousRegex = ".*\\$\\d+$".toRegex() val SootClass.isAnonymous get() = name matches isAnonymousRegex +private val isLambdaRegex = ".*(\\$)lambda_.*".toRegex() + val SootClass.isLambda: Boolean - get() = this.id.isLambda + get() = this.isArtificialEntity && this.name matches isLambdaRegex val Type.numDimensions get() = if (this is ArrayType) numDimensions else 0 diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/MockStrategy.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/MockStrategy.kt index 71b39877ad..019a824e63 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/MockStrategy.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/MockStrategy.kt @@ -1,7 +1,6 @@ package org.utbot.engine import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.util.isFunctionalInterface /** * Mock strategies. @@ -18,24 +17,15 @@ enum class MockStrategy { }, OTHER_PACKAGES { - override fun eligibleToMock(classToMock: ClassId, classUnderTest: ClassId): Boolean { - if (classToMock == classUnderTest) return false - if (classToMock.packageName == classUnderTest.packageName) return false - - // we always mock functional interfaces - if (classToMock.isFunctionalInterface) return true - - return !isSystemPackage(classToMock.packageName) - } + override fun eligibleToMock(classToMock: ClassId, classUnderTest: ClassId): Boolean = + classToMock != classUnderTest && classToMock.packageName.let { + it != classUnderTest.packageName && !isSystemPackage(it) + } }, OTHER_CLASSES { - override fun eligibleToMock(classToMock: ClassId, classUnderTest: ClassId): Boolean { - if (classToMock == classUnderTest) return false - // we always mock functional interfaces - if (classToMock.isFunctionalInterface) return true - return !isSystemPackage(classToMock.packageName) - } + override fun eligibleToMock(classToMock: ClassId, classUnderTest: ClassId): Boolean = + classToMock != classUnderTest && !isSystemPackage(classToMock.packageName) }; /** diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt index 4f26228952..f42f50d63f 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt @@ -642,7 +642,7 @@ class Resolver( val samType = sootClass.interfaces.singleOrNull()?.id ?: error("Lambda must implement single interface, but ${sootClass.interfaces.size} found for ${sootClass.name}") - val declaringClass = classLoader.loadClass(sootClass.name.substringBeforeLast("\$lambda")) + val declaringClass = classLoader.loadClass(sootClass.name.substringBefore("\$lambda")) // Java compiles lambdas into synthetic methods with specific names. // However, Soot represents lambdas as classes. @@ -654,7 +654,7 @@ class Resolver( // Here we obtain the synthetic method name of lambda from the name of its SootClass. val lambdaName = sootClass.name .let { name -> - val start = name.lastIndexOf("\$lambda") + 1 + val start = name.indexOf("\$lambda") + 1 val end = name.lastIndexOf("__") name.substring(start, end) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt index 4825f2196f..d5139836bd 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -1835,7 +1835,7 @@ class Traverser( return fromMemory } val addr = fromMemory?.addr ?: findNewAddr() - val created = createObject(addr, classType, useConcreteType = true, mockInfoGenerator) + val created = createObject(addr, classType, useConcreteType = false, mockInfoGenerator) queuedSymbolicStateUpdates += MemoryUpdate(staticInstanceStorage = persistentHashMapOf(classType.id to created)) return created } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt index 70ed86a371..64f950a83b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt @@ -112,7 +112,7 @@ class TypeResolver(private val typeRegistry: TypeRegistry, private val hierarchy if (numDimensions == 0) baseType else baseType.makeArrayType(numDimensions) } - return TypeStorage(type, concretePossibleTypes)//.filterInappropriateClassesForCodeGeneration() + return TypeStorage(type, concretePossibleTypes).filterInappropriateClassesForCodeGeneration() } private fun isInappropriateOrArrayOfMocksOrLocals(numDimensions: Int, baseType: Type?): Boolean { @@ -182,7 +182,7 @@ class TypeResolver(private val typeRegistry: TypeRegistry, private val hierarchy else -> error("Unexpected type $type") } - return TypeStorage(type, possibleTypes)//.filterInappropriateClassesForCodeGeneration() + return TypeStorage(type, possibleTypes).filterInappropriateClassesForCodeGeneration() } /** @@ -197,26 +197,23 @@ class TypeResolver(private val typeRegistry: TypeRegistry, private val hierarchy val leastCommonSootClass = (leastCommonType as? RefType)?.sootClass val keepArtificialEntities = leastCommonSootClass?.isArtificialEntity == true - heuristic(WorkaroundReason.REMOVE_ANONYMOUS_CLASSES) { - possibleConcreteTypes.forEach { - val sootClass = (it.baseType as? RefType)?.sootClass ?: run { - // All not RefType should be included in the concreteTypes, e.g., arrays - concreteTypes += it - return@forEach - } - when { - sootClass.isUtMock -> unwantedTypes += it - sootClass.isArtificialEntity -> { -// if (sootClass.isLambda) { -// unwantedTypes += it -// } else - if (keepArtificialEntities) { - concreteTypes += it - } + possibleConcreteTypes.forEach { + val sootClass = (it.baseType as? RefType)?.sootClass ?: run { + // All not RefType should be included in the concreteTypes, e.g., arrays + concreteTypes += it + return@forEach + } + when { + sootClass.isUtMock -> unwantedTypes += it + sootClass.isArtificialEntity -> { + if (sootClass.isLambda) { + unwantedTypes += it + } else if (keepArtificialEntities) { + concreteTypes += it } - workaround(WorkaroundReason.HACK) { leastCommonSootClass == OBJECT_TYPE && sootClass.isOverridden } -> Unit - else -> concreteTypes += it } + workaround(WorkaroundReason.HACK) { leastCommonSootClass == OBJECT_TYPE && sootClass.isOverridden } -> Unit + else -> concreteTypes += it } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/util/lambda/LambdaConstructionUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/util/lambda/LambdaConstructionUtils.kt index 237ab9f48b..ab15ff65fc 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/util/lambda/LambdaConstructionUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/util/lambda/LambdaConstructionUtils.kt @@ -58,7 +58,46 @@ private fun getInstantiatedMethodType( */ private fun getLambdaMethod(declaringClass: Class<*>, lambdaName: String): Method { return declaringClass.declaredMethods.firstOrNull { it.name == lambdaName } - ?: throw IllegalArgumentException("No lambda method named ${'$'}lambdaName was found in class: ${'$'}{declaringClass.canonicalName}") + ?: throw IllegalArgumentException("No lambda method named $lambdaName was found in class: ${declaringClass.canonicalName}") +} + +/** + * This class contains some info that is needed by both [constructLambda] and [constructStaticLambda]. + * We obtain this info in [prepareLambdaInfo] to avoid duplicated code in [constructLambda] and [constructStaticLambda]. + */ +private data class LambdaMetafactoryInfo( + val caller: MethodHandles.Lookup, + val invokedName: String, + val samMethodType: MethodType, + val lambdaMethod: Method, + val lambdaMethodType: MethodType +) + +/** + * Obtain and prepare [LambdaMetafactoryInfo] that is needed by [constructLambda] and [constructStaticLambda]. + */ +private fun prepareLambdaInfo( + samType: Class<*>, + declaringClass: Class<*>, + lambdaName: String, +): LambdaMetafactoryInfo { + // Create lookup for class where the lambda is declared in. + val caller = getLookupIn(declaringClass) + + // Obtain the single abstract method of a functional interface whose instance we are building. + // For example, for `java.util.function.Predicate` it will be method `test`. + val singleAbstractMethod = getSingleAbstractMethod(samType) + + val invokedName = singleAbstractMethod.name + + // Method type of single abstract method of the target functional interface. + val samMethodType = MethodType.methodType(singleAbstractMethod.returnType, singleAbstractMethod.parameterTypes) + + val lambdaMethod = getLambdaMethod(declaringClass, lambdaName) + lambdaMethod.isAccessible = true + val lambdaMethodType = MethodType.methodType(lambdaMethod.returnType, lambdaMethod.parameterTypes) + + return LambdaMetafactoryInfo(caller, invokedName, samMethodType, lambdaMethod, lambdaMethodType) } /** @@ -82,19 +121,9 @@ internal fun constructStaticLambda( lambdaName: String, vararg capturedArguments: CapturedArgument ): Any { - // Create lookup for class where the lambda is declared in. - val caller = getLookupIn(declaringClass) + val (caller, invokedName, samMethodType, lambdaMethod, lambdaMethodType) = + prepareLambdaInfo(samType, declaringClass, lambdaName) - // Obtain the single abstract method of a functional interface whose instance we are building. - // For example, for `java.util.function.Predicate` it will be method `test`. - val singleAbstractMethod = getSingleAbstractMethod(samType) - val invokedName = singleAbstractMethod.name - // Method type of single abstract method of the target functional interface. - val samMethodType = MethodType.methodType(singleAbstractMethod.returnType, singleAbstractMethod.parameterTypes) - - val lambdaMethod = getLambdaMethod(declaringClass, lambdaName) - lambdaMethod.isAccessible = true - val lambdaMethodType = MethodType.methodType(lambdaMethod.returnType, lambdaMethod.parameterTypes) val lambdaMethodHandle = caller.findStatic(declaringClass, lambdaName, lambdaMethodType) val capturedArgumentTypes = capturedArguments.map { it.type }.toTypedArray() @@ -129,19 +158,9 @@ internal fun constructLambda( capturedReceiver: Any, vararg capturedArguments: CapturedArgument ): Any { - // Create lookup for class where the lambda is declared in. - val caller = getLookupIn(declaringClass) - - // Obtain the single abstract method of a functional interface whose instance we are building. - // For example, for `java.util.function.Predicate` it will be method `test`. - val singleAbstractMethod = getSingleAbstractMethod(samType) - val invokedName = singleAbstractMethod.name - // Method type of single abstract method of the target functional interface. - val samMethodType = MethodType.methodType(singleAbstractMethod.returnType, singleAbstractMethod.parameterTypes) + val (caller, invokedName, samMethodType, lambdaMethod, lambdaMethodType) = + prepareLambdaInfo(samType, declaringClass, lambdaName) - val lambdaMethod = getLambdaMethod(declaringClass, lambdaName) - lambdaMethod.isAccessible = true - val lambdaMethodType = MethodType.methodType(lambdaMethod.returnType, lambdaMethod.parameterTypes) val lambdaMethodHandle = caller.findVirtual(declaringClass, lambdaName, lambdaMethodType) val capturedArgumentTypes = capturedArguments.map { it.type }.toTypedArray() diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt index 01c22509ef..3ea84673ec 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt @@ -233,7 +233,6 @@ class AssembleModelGenerator(private val methodPackageName: String) { } try { - // TODO: we can't use simpleName for anonymous classes, because it's empty val modelName = nextModelName(compositeModel.classId.jClass.simpleName.decapitalize()) val instantiationChain = mutableListOf() diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt index 94ee954c3b..3be50621e5 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt @@ -695,7 +695,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c ) } } - is UtLambdaModel -> Unit + is UtLambdaModel -> Unit // we do not check equality of lambdas is UtVoidModel -> { // Unit result is considered in generateResultAssertions method error("Unexpected UtVoidModel in deep equals") @@ -960,8 +960,8 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c } } - is UtLambdaModel -> Unit // TODO: lambda model - + // Lambdas do not have fields. They have captured values, but we do not consider them here. + is UtLambdaModel, is UtNullModel, is UtPrimitiveModel, is UtArrayModel, @@ -1001,8 +1001,8 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c } } - is UtLambdaModel -> Unit // TODO: lambda model - + // Lambdas do not have fields. They have captured values, but we do not consider them here. + is UtLambdaModel, is UtNullModel, is UtPrimitiveModel, is UtArrayModel, diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt index ab0d5ff4a5..01922eb3b1 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt @@ -132,8 +132,10 @@ internal class CgVariableConstructor(val context: CgContext) : } private fun constructStaticLambda(model: UtLambdaModel, capturedValues: List): CgMethodCall { - val capturedArguments = capturedValues.map { utilMethodProvider.capturedArgumentConstructorId(getClassOf(it.classId), getOrCreateVariable(it)) } - return testClassThisInstance[buildStaticLambda]( + val capturedArguments = capturedValues.map { + utilMethodProvider.capturedArgumentConstructorId(getClassOf(it.classId), getOrCreateVariable(it)) + } + return utilsClassId[buildStaticLambda]( getClassOf(model.samType), getClassOf(model.declaringClass), model.lambdaName, @@ -142,12 +144,14 @@ internal class CgVariableConstructor(val context: CgContext) : } private fun constructLambda(model: UtLambdaModel, capturedValues: List): CgMethodCall { - require(capturedValues.isNotEmpty()) { "Non-static lambda must capture `this` instance, so there must be at least one captured value" } + require(capturedValues.isNotEmpty()) { + "Non-static lambda must capture `this` instance, so there must be at least one captured value" + } val capturedThisInstance = getOrCreateVariable(capturedValues.first()) val capturedArguments = capturedValues .subList(1, capturedValues.size) .map { utilMethodProvider.capturedArgumentConstructorId(getClassOf(it.classId), getOrCreateVariable(it)) } - return testClassThisInstance[buildLambda]( + return utilsClassId[buildLambda]( getClassOf(model.samType), getClassOf(model.declaringClass), model.lambdaName, diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt index 70c3a867d6..31aca4d64a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt @@ -245,20 +245,27 @@ data class CgExecutableUnderTestCluster( override val content: List> ) : CgRegion>() +/** + * Util entity is either an instance of [CgAuxiliaryClass] or [CgUtilMethod]. + * Util methods are the helper methods that we use in our generated tests, + * and auxiliary classes are the classes that util methods use. + */ sealed class CgUtilEntity : CgElement { internal abstract fun getText(rendererContext: CgRendererContext): String } +/** + * This class represents classes that are required by our util methods. + * For example, class `CapturedArgument` that is used by util methods that construct lambda values. + * + * **Note** that we call such classes **auxiliary** instead of **util** in order to avoid confusion + * with class `org.utbot.runtime.utils.UtUtils`, which is generally called an **util class**. + * `UtUtils` is a class that contains all our util methods and **auxiliary classes**. + */ data class CgAuxiliaryClass(val id: ClassId) : CgUtilEntity() { override fun getText(rendererContext: CgRendererContext): String { - // we should not throw an exception on failure here, - // because this function is used during rendering and - // exceptions can crash rendering, so we use an empty string if the text is not found - return with(rendererContext) { - rendererContext.utilMethodProvider - .auxiliaryClassTextById(id, codegenLanguage) - .getOrDefault("") - } + return rendererContext.utilMethodProvider + .auxiliaryClassTextById(id, rendererContext.codegenLanguage) } } @@ -272,13 +279,9 @@ data class CgAuxiliaryClass(val id: ClassId) : CgUtilEntity() { */ data class CgUtilMethod(val id: MethodId) : CgUtilEntity() { override fun getText(rendererContext: CgRendererContext): String { - // we should not throw an exception on failure here, - // because this function is used during rendering and - // exceptions can crash rendering, so we use an empty string if the text is not found return with(rendererContext) { rendererContext.utilMethodProvider .utilMethodTextById(id, mockFrameworkUsed, mockFramework, codegenLanguage) - .getOrDefault("") } } } @@ -668,32 +671,10 @@ class CgThisInstance(override val type: ClassId) : CgValue // Variables -/** - * @property name name of the variable - * @property compileTimeType type a variable was declared with in the code. - * For example, `List` in `List l = new ArrayList<>();`. - * @property runtimeType actual type of an object stored in the variable. - * For example, `ArrayList` in `List l = new ArrayList<>();`. - */ open class CgVariable( val name: String, - val compileTimeType: ClassId, - val runtimeType: ClassId + override val type: ClassId, ) : CgValue { - - /** - * If [compileTimeType] and [runtimeType] are the same, a variable may be declared with this constructor. - */ - constructor(name: String, type: ClassId) : this(name, type, type) - - /** - * Property [type] inherited from [CgExpression] is delegated to [compileTimeType]. - * That's because when we access a variable in the code we can only work with it - * through its compileTimeType interface, not knowing about its concrete implementation (runtime type). - */ - override val type: ClassId - get() = compileTimeType - override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt index 895170454a..1a559b5a67 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt @@ -38,15 +38,17 @@ private enum class Visibility(val text: String) { } } +// TODO: This method may throw an exception that will crash rendering. +// TODO: Add isolation on rendering: https://github.com/UnitTestBot/UTBotJava/issues/853 internal fun UtilMethodProvider.utilMethodTextById( id: MethodId, mockFrameworkUsed: Boolean, mockFramework: MockFramework, codegenLanguage: CodegenLanguage -): Result = runCatching { +): String { // If util methods are declared in the test class, then they are private. Otherwise, they are public. val visibility = if (this is TestClassUtilMethodProvider) Visibility.PRIVATE else Visibility.PUBLIC - with(this) { + return with(this) { when (id) { getUnsafeInstanceMethodId -> getUnsafeInstance(visibility, codegenLanguage) createInstanceMethodId -> createInstance(visibility, codegenLanguage) @@ -77,17 +79,15 @@ internal fun UtilMethodProvider.utilMethodTextById( } } -internal fun UtilMethodProvider.auxiliaryClassTextById( - id: ClassId, - codegenLanguage: CodegenLanguage -): Result = runCatching { +// TODO: This method may throw an exception that will crash rendering. +// TODO: Add isolation on rendering: https://github.com/UnitTestBot/UTBotJava/issues/853 +internal fun UtilMethodProvider.auxiliaryClassTextById(id: ClassId, codegenLanguage: CodegenLanguage): String = with(this) { when (id) { capturedArgumentClassId -> capturedArgumentClass(codegenLanguage) else -> error("Unknown auxiliary class: $id") } } -} private fun getEnumConstantByName(visibility: Visibility, language: CodegenLanguage): String = when (language) { From 6bcbdd0b67a3729818678378f148df9a15b47e9b Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Mon, 5 Sep 2022 15:37:50 +0300 Subject: [PATCH 05/10] Some fixes --- .../kotlin/org/utbot/framework/plugin/api/Api.kt | 14 +++++++++----- .../kotlin/org/utbot/engine/UtBotSymbolicEngine.kt | 3 --- .../kotlin/org/utbot/engine/ValueConstructor.kt | 1 + .../engine/util/lambda/LambdaConstructionUtils.kt | 4 ++-- .../constructor/tree/CgUtilClassConstructor.kt | 2 ++ .../constructor/tree/CgVariableConstructor.kt | 3 +-- .../constructor/util/CgStatementConstructor.kt | 2 +- .../framework/codegen/model/visitor/UtilMethods.kt | 2 +- .../framework/concrete/MockValueConstructor.kt | 8 ++++++-- .../utbot/framework/concrete/UtModelConstructor.kt | 11 +++++++++-- 10 files changed, 32 insertions(+), 18 deletions(-) diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt index d8366d4200..bba8843bf9 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt @@ -11,7 +11,6 @@ package org.utbot.framework.plugin.api import org.utbot.common.isDefaultValue import org.utbot.common.withToStringThreadLocalReentrancyGuard import org.utbot.framework.UtSettings -import org.utbot.framework.plugin.api.MockFramework.MOCKITO import org.utbot.framework.plugin.api.impl.FieldIdReflectionStrategy import org.utbot.framework.plugin.api.impl.FieldIdSootStrategy import org.utbot.framework.plugin.api.util.booleanClassId @@ -1084,10 +1083,15 @@ class BuiltinConstructorId( classId: ClassId, parameters: List, // by default, we assume that the builtin constructor is public - override val isPublic: Boolean = true, - override val isProtected: Boolean = false, - override val isPrivate: Boolean = false -) : ConstructorId(classId, parameters) + isPublic: Boolean = true, + isProtected: Boolean = false, + isPrivate: Boolean = false +) : ConstructorId(classId, parameters) { + override val modifiers: Int = + (if (isPublic) Modifier.PUBLIC else 0) or + (if (isProtected) Modifier.PROTECTED else 0) or + (if (isPrivate) Modifier.PRIVATE else 0) +} open class TypeParameters(val parameters: List = emptyList()) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt index eda8fb3f4e..0a154100f8 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -59,16 +59,13 @@ import org.utbot.framework.plugin.api.UtOverflowFailure import org.utbot.framework.plugin.api.UtResult import org.utbot.framework.plugin.api.UtSymbolicExecution import org.utbot.framework.util.graph -import org.utbot.framework.plugin.api.util.executableId import org.utbot.framework.plugin.api.util.isStatic -import org.utbot.framework.plugin.api.onSuccess import org.utbot.framework.plugin.api.util.description import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.isConstructor import org.utbot.framework.plugin.api.util.isEnum import org.utbot.framework.plugin.api.util.utContext import org.utbot.framework.plugin.api.util.voidClassId -import org.utbot.framework.util.graph import org.utbot.framework.util.sootMethod import org.utbot.fuzzer.FallbackModelProvider import org.utbot.fuzzer.FuzzedMethodDescription diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ValueConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ValueConstructor.kt index 2f7f081cbd..bcef64e327 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ValueConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ValueConstructor.kt @@ -39,6 +39,7 @@ import org.utbot.framework.plugin.api.UtValueExecutionState import org.utbot.framework.plugin.api.UtVoidModel import org.utbot.framework.plugin.api.isMockModel import org.utbot.framework.plugin.api.util.constructor +import org.utbot.framework.plugin.api.util.isStatic import org.utbot.framework.plugin.api.util.jField import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.method diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/util/lambda/LambdaConstructionUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/util/lambda/LambdaConstructionUtils.kt index ab15ff65fc..1f26451bb7 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/util/lambda/LambdaConstructionUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/util/lambda/LambdaConstructionUtils.kt @@ -155,7 +155,7 @@ internal fun constructLambda( samType: Class<*>, declaringClass: Class<*>, lambdaName: String, - capturedReceiver: Any, + capturedReceiver: Any?, vararg capturedArguments: CapturedArgument ): Any { val (caller, invokedName, samMethodType, lambdaMethod, lambdaMethodType) = @@ -164,7 +164,7 @@ internal fun constructLambda( val lambdaMethodHandle = caller.findVirtual(declaringClass, lambdaName, lambdaMethodType) val capturedArgumentTypes = capturedArguments.map { it.type }.toTypedArray() - val invokedType = MethodType.methodType(samType, capturedReceiver.javaClass, *capturedArgumentTypes) + val invokedType = MethodType.methodType(samType, declaringClass, *capturedArgumentTypes) val instantiatedMethodType = getInstantiatedMethodType(lambdaMethod, capturedArgumentTypes) // Create a CallSite for the given lambda. diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt index e81af665a4..fa0d27fed6 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt @@ -3,6 +3,7 @@ package org.utbot.framework.codegen.model.constructor.tree import org.utbot.framework.codegen.model.CodeGenerator import org.utbot.framework.codegen.model.UtilClassKind import org.utbot.framework.codegen.model.constructor.builtin.utUtilsClassId +import org.utbot.framework.codegen.model.tree.CgAuxiliaryClass import org.utbot.framework.codegen.model.tree.CgRegularClassFile import org.utbot.framework.codegen.model.tree.CgUtilMethod import org.utbot.framework.codegen.model.tree.buildRegularClass @@ -25,6 +26,7 @@ internal object CgUtilClassConstructor { content += utilClassKind.utilClassVersionComment content += utilClassKind.utilClassKindComment content += utilMethodProvider.utilMethodIds.map { CgUtilMethod(it) } + content += CgAuxiliaryClass(utilMethodProvider.capturedArgumentClassId) } } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt index 01922eb3b1..85d7c9893b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt @@ -56,11 +56,10 @@ import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.intClassId import org.utbot.framework.plugin.api.util.isArray import org.utbot.framework.plugin.api.util.isPrimitiveWrapperOrString +import org.utbot.framework.plugin.api.util.isStatic import org.utbot.framework.plugin.api.util.stringClassId import org.utbot.framework.plugin.api.util.supertypeOfAnonymousClass import org.utbot.framework.plugin.api.util.wrapperByPrimitive -import java.lang.reflect.Field -import java.lang.reflect.Modifier /** * Constructs CgValue or CgVariable given a UtModel diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt index 23ad88520f..53f7742726 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt @@ -320,7 +320,7 @@ internal class CgStatementConstructorImpl(context: CgContext) : return if (classId isAccessibleFrom testClassPackageName) { CgGetJavaClass(classId) } else { - newVar(classCgClassId) { Class::class.id[forName](classId.name) } + newVar(classCgClassId) { classClassId[forName](classId.name) } } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt index 1a559b5a67..9876151b57 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt @@ -1302,7 +1302,7 @@ private fun capturedArgumentClass(language: CodegenLanguage) = * Captured values are represented as arguments of a synthetic method that lambda is compiled into, * hence the name of the class. */ - static class CapturedArgument { + public static class CapturedArgument { private Class type; private Object value; diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockValueConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockValueConstructor.kt index 25aba74c7f..4768cbf68b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockValueConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockValueConstructor.kt @@ -48,6 +48,7 @@ import org.utbot.engine.util.lambda.CapturedArgument import org.utbot.engine.util.lambda.constructLambda import org.utbot.engine.util.lambda.constructStaticLambda import org.utbot.framework.plugin.api.UtLambdaModel +import org.utbot.framework.plugin.api.util.isStatic import org.utbot.instrumentation.process.runSandbox /** @@ -369,6 +370,7 @@ class MockValueConstructor( } private fun constructFromLambdaModel(lambdaModel: UtLambdaModel): Any { + constructedObjects[lambdaModel]?.let { return it } // A class representing a functional interface. val samType: Class<*> = lambdaModel.samType.jClass // A class where the lambda is declared. @@ -376,7 +378,7 @@ class MockValueConstructor( // A name of the synthetic method that represents a lambda. val lambdaName = lambdaModel.lambdaName - return if (lambdaModel.lambdaMethodId.isStatic) { + val lambda = if (lambdaModel.lambdaMethodId.isStatic) { val capturedArguments = lambdaModel.capturedValues .map { model -> CapturedArgument(type = model.classId.jClass, value = value(model)) } .toTypedArray() @@ -386,12 +388,14 @@ class MockValueConstructor( ?: error("Non-static lambda must capture `this` instance, so there must be at least one captured value") // Values that the given lambda has captured. - val capturedReceiver = value(capturedReceiverModel) ?: error("Captured receiver of lambda must not be null") + val capturedReceiver = value(capturedReceiverModel) val capturedArguments = lambdaModel.capturedValues.subList(1, lambdaModel.capturedValues.size) .map { model -> CapturedArgument(type = model.classId.jClass, value = value(model)) } .toTypedArray() constructLambda(samType, declaringClass, lambdaName, capturedReceiver, *capturedArguments) } + constructedObjects[lambdaModel] = lambda + return lambda } /** diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtModelConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtModelConstructor.kt index 2efdbfac02..a2194dedfe 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtModelConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtModelConstructor.kt @@ -9,6 +9,7 @@ import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtClassRefModel import org.utbot.framework.plugin.api.UtCompositeModel import org.utbot.framework.plugin.api.UtEnumConstantModel +import org.utbot.framework.plugin.api.UtLambdaModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtPrimitiveModel @@ -82,8 +83,13 @@ class UtModelConstructor( * * Handles cache on stateBefore values. */ - override fun construct(value: Any?, classId: ClassId): UtModel = - when (value) { + override fun construct(value: Any?, classId: ClassId): UtModel { + objectToModelCache[value]?.let { model -> + if (model is UtLambdaModel) { + return model + } + } + return when (value) { null -> UtNullModel(classId) is Unit -> UtVoidModel is Byte, @@ -107,6 +113,7 @@ class UtModelConstructor( is Class<*> -> constructFromClass(value) else -> constructFromAny(value) } + } // Q: Is there a way to get rid of duplicated code? From 45b988d32a738f6411a604e84ae6857ff4c39384 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Thu, 15 Sep 2022 22:53:55 +0300 Subject: [PATCH 06/10] Fix subtype check --- .../kotlin/org/utbot/framework/plugin/api/Api.kt | 12 +++++++++++- .../org/utbot/framework/plugin/api/util/IdUtil.kt | 10 ++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt index bba8843bf9..43d2804ebb 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt @@ -794,6 +794,12 @@ open class ClassId @JvmOverloads constructor( open val outerClass: Class<*>? get() = jClass.enclosingClass + open val superclass: Class<*>? + get() = jClass.superclass + + open val interfaces: Array> + get() = jClass.interfaces + /** * For member classes returns a name including * enclosing classes' simple names e.g. `A.B`. @@ -846,7 +852,7 @@ class BuiltinClassId( elementClassId: ClassId? = null, override val canonicalName: String, override val simpleName: String, - // by default we assume that the class is not a member class + // by default, we assume that the class is not a member class override val simpleNameWithEnclosings: String = simpleName, override val isNullable: Boolean = false, override val isPublic: Boolean = true, @@ -864,6 +870,10 @@ class BuiltinClassId( override val allMethods: Sequence = emptySequence(), override val allConstructors: Sequence = emptySequence(), override val outerClass: Class<*>? = null, + // by default, we assume that the class does not have a superclass (other than Object) + override val superclass: Class<*>? = java.lang.Object::class.java, + // by default, we assume that the class does not implement any interfaces + override val interfaces: Array> = emptyArray(), override val packageName: String = when (val index = canonicalName.lastIndexOf('.')) { -1, 0 -> "" diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt index 4a83a2f2b9..4addbd3f0d 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt @@ -118,17 +118,19 @@ infix fun ClassId.isSubtypeOf(type: ClassId): Boolean { if (left == right) { return true } - val leftClass = this.jClass + val leftClass = this val interfaces = sequence { var types = listOf(leftClass) while (types.isNotEmpty()) { yieldAll(types) - types = types.map { it.interfaces }.flatMap { it.toList() } + types = types + .flatMap { it.interfaces.toList() } + .map { it.id } } } - val superclasses = generateSequence(leftClass) { it.superclass } + val superclasses = generateSequence(leftClass) { it.superclass?.id } val superTypes = interfaces + superclasses - return right in superTypes.map { it.id } + return right in superTypes } infix fun ClassId.isNotSubtypeOf(type: ClassId): Boolean = !(this isSubtypeOf type) From 95357a1974a223dd53cb4a22f911a35b53749773 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Thu, 15 Sep 2022 22:54:46 +0300 Subject: [PATCH 07/10] Minor fix --- .../org/utbot/framework/assemble/AssembleModelGeneratorTests.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/framework/assemble/AssembleModelGeneratorTests.kt b/utbot-framework-test/src/test/kotlin/org/utbot/framework/assemble/AssembleModelGeneratorTests.kt index 5ff4c14c2d..0108faad44 100644 --- a/utbot-framework-test/src/test/kotlin/org/utbot/framework/assemble/AssembleModelGeneratorTests.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/framework/assemble/AssembleModelGeneratorTests.kt @@ -60,7 +60,6 @@ import org.utbot.framework.util.modelIdCounter import kotlin.reflect.full.functions import org.utbot.examples.assemble.* import org.utbot.framework.codegen.model.constructor.util.arrayTypeOf -import org.utbot.framework.util.SootUtils /** * Test classes must be located in the same folder as [AssembleTestUtils] class. From 97ba5228c92419048eae1440a1fd2e6d298fb4a0 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Fri, 16 Sep 2022 00:10:57 +0300 Subject: [PATCH 08/10] Minor fix --- .../org/utbot/framework/codegen/model/visitor/UtilMethods.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt index 9876151b57..346b7463a1 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt @@ -1285,7 +1285,7 @@ private fun getSingleAbstractMethod(language: CodegenLanguage) = * @return a [java.lang.reflect.Method] for the single abstract method of the given functional interface `clazz`. */ private fun getSingleAbstractMethod(clazz: Class<*>): java.lang.reflect.Method { - val abstractMethods = clazz.methods.filter { Modifier.isAbstract(it.modifiers) } + val abstractMethods = clazz.methods.filter { java.lang.reflect.Modifier.isAbstract(it.modifiers) } require(abstractMethods.isNotEmpty()) { "No abstract methods found in class: " + clazz.canonicalName } require(abstractMethods.size <= 1) { "More than one abstract method found in class: " + clazz.canonicalName } return abstractMethods[0] From 848f660633d549bdf819eedcdbaad271e1ba7c66 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Sun, 18 Sep 2022 16:08:52 +0300 Subject: [PATCH 09/10] Fix Kotlin nested class code generation --- .../codegen/model/visitor/CgKotlinRenderer.kt | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt index 367910f8f4..09459b5f8b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt @@ -13,6 +13,7 @@ import org.utbot.framework.codegen.model.tree.CgAnonymousFunction import org.utbot.framework.codegen.model.tree.CgArrayAnnotationArgument import org.utbot.framework.codegen.model.tree.CgArrayElementAccess import org.utbot.framework.codegen.model.tree.CgArrayInitializer +import org.utbot.framework.codegen.model.tree.CgAuxiliaryClass import org.utbot.framework.codegen.model.tree.CgComparison import org.utbot.framework.codegen.model.tree.CgConstructorCall import org.utbot.framework.codegen.model.tree.CgDeclaration @@ -32,6 +33,7 @@ import org.utbot.framework.codegen.model.tree.CgNotNullAssertion import org.utbot.framework.codegen.model.tree.CgParameterDeclaration import org.utbot.framework.codegen.model.tree.CgParameterizedTestDataProviderMethod import org.utbot.framework.codegen.model.tree.CgRegularClass +import org.utbot.framework.codegen.model.tree.CgSimpleRegion import org.utbot.framework.codegen.model.tree.CgSpread import org.utbot.framework.codegen.model.tree.CgStaticsRegion import org.utbot.framework.codegen.model.tree.CgSwitchCase @@ -120,8 +122,32 @@ internal class CgKotlinRenderer(context: CgRendererContext, printer: CgPrinter = } // render static declaration regions inside a companion object println() + + // In Kotlin, we put static declarations in a companion object of the class, + // but that **does not** apply to nested classes. + // They must be located in the class itself, not its companion object. + // That is why here we extract all the auxiliary classes from static regions + // to form a separate region specifically for them. + // See the docs on CgAuxiliaryClass for details on what they represent. + val auxiliaryClassesRegion = element.staticDeclarationRegions + .flatMap { it.content } + .filterIsInstance() + .let { classes -> CgSimpleRegion("Util classes", classes) } + + if (auxiliaryClassesRegion.content.isNotEmpty()) { + auxiliaryClassesRegion.accept(this) + println() + } + + // Here we update the static regions by removing all the auxiliary classes from them. + // The remaining content of regions will be rendered inside a companion object. + val updatedStaticRegions = element.staticDeclarationRegions.map { region -> + val updatedContent = region.content.filterNot { it is CgAuxiliaryClass } + CgStaticsRegion(region.header, updatedContent) + } + renderCompanionObject { - for ((i, staticsRegion) in element.staticDeclarationRegions.withIndex()) { + for ((i, staticsRegion) in updatedStaticRegions.withIndex()) { if (i != 0) println() staticsRegion.accept(this) From 105615c82f1a04905a899b54769185f6337853b6 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Sun, 18 Sep 2022 16:15:23 +0300 Subject: [PATCH 10/10] Do not run compilation on Kotlin tests on lambdas due to generics issues --- .../examples/lambda/CustomPredicateExampleTest.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/CustomPredicateExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/CustomPredicateExampleTest.kt index b4eb08a30f..2327358339 100644 --- a/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/CustomPredicateExampleTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/CustomPredicateExampleTest.kt @@ -1,12 +1,24 @@ package org.utbot.examples.lambda import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.testcheckers.eq +import org.utbot.tests.infrastructure.CodeGeneration import org.utbot.tests.infrastructure.DoNotCalculate import org.utbot.tests.infrastructure.UtValueTestCaseChecker import org.utbot.tests.infrastructure.isException -class CustomPredicateExampleTest : UtValueTestCaseChecker(testClass = CustomPredicateExample::class) { +class CustomPredicateExampleTest : UtValueTestCaseChecker( + testClass = CustomPredicateExample::class, + languagePipelines = listOf( + CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), + // TODO: https://github.com/UnitTestBot/UTBotJava/issues/88 (generics in Kotlin) + // At the moment, when we create an instance of a functional interface via lambda (through reflection), + // we need to do a type cast (e.g. `obj as Predicate`), but since generics are not supported yet, + // we use a raw type (e.g. `Predicate`) instead (which is not allowed in Kotlin). + CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) + ) +) { @Test fun testNoCapturedValuesPredicateCheck() { checkWithException(