diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/ThrowingWithLambdaExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/ThrowingWithLambdaExampleTest.kt new file mode 100644 index 0000000000..fb338e5e1d --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/ThrowingWithLambdaExampleTest.kt @@ -0,0 +1,21 @@ +package org.utbot.examples.lambda + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +class ThrowingWithLambdaExampleTest : UtValueTestCaseChecker(testClass = ThrowingWithLambdaExample::class) { + @Test + fun testAnyExample() { + check( + ThrowingWithLambdaExample::anyExample, + eq(4), + { l, _, _ -> l == null }, + { l, _, r -> l.isEmpty() && r == false }, + { l, _, r -> l.isNotEmpty() && 42 in l && r == true }, + { l, _, r -> l.isNotEmpty() && 42 !in l && r == false }, + coverage = DoNotCalculate // TODO failed coverage calculation + ) + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectCounter.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectCounter.kt new file mode 100644 index 0000000000..53dd3d320a --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectCounter.kt @@ -0,0 +1,12 @@ +package org.utbot.engine + +import java.util.concurrent.atomic.AtomicInteger + +/** + * Counts new objects during execution. Used to give new addresses for objects in [Traverser] and [Resolver]. + */ +data class ObjectCounter(val initialValue: Int) { + private val internalCounter = AtomicInteger(initialValue) + + fun createNewAddr(): Int = internalCounter.getAndIncrement() +} 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 807ff3a0ca..74e34da975 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt @@ -101,6 +101,7 @@ import org.utbot.engine.types.TypeRegistry import org.utbot.engine.types.TypeResolver import org.utbot.framework.plugin.api.visible.UtStreamConsumingException import org.utbot.framework.plugin.api.UtStreamConsumingFailure +import org.utbot.framework.plugin.api.util.isStatic // hack const val MAX_LIST_SIZE = 10 @@ -130,7 +131,8 @@ class Resolver( private val typeResolver: TypeResolver, val holder: UtSolverStatusSAT, methodUnderTest: ExecutableId, - private val softMaxArraySize: Int + private val softMaxArraySize: Int, + private val objectCounter: ObjectCounter ) { private val classLoader: ClassLoader @@ -529,7 +531,28 @@ class Resolver( if (sootClass.isLambda) { return constructLambda(concreteAddr, sootClass).also { lambda -> - lambda.capturedValues += collectFieldModels(addr, actualType).values + val collectedFieldModels = collectFieldModels(addr, actualType).toMutableMap() + + if (!lambda.lambdaMethodId.isStatic) { + val thisInstanceField = FieldId(declaringClass = sootClass.id, name = "cap0") + + if (thisInstanceField !in collectedFieldModels || collectedFieldModels[thisInstanceField] is UtNullModel) { + // Non-static lambda has to have `this` instance captured as `cap0` field that cannot be null, + // so if we do not have it as field or it is null (for example, an exception was thrown before initializing lambda), + // we need to construct `this` instance by ourselves. + // Since we do not know its fields, we create an empty object of the corresponding type that will be + // constructed in codegen using reflection. + val thisInstanceClassId = sootClass.name.substringBeforeLast("\$lambda").let { + Scene.v().getSootClass(it) + }.id + val thisInstanceModel = + UtCompositeModel(objectCounter.createNewAddr(), thisInstanceClassId, isMock = false) + + collectedFieldModels[thisInstanceField] = thisInstanceModel + } + } + + lambda.capturedValues += collectedFieldModels.values } } 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 4faffa970c..257336cb38 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -267,9 +267,10 @@ class Traverser( private var queuedSymbolicStateUpdates = SymbolicStateUpdate() - private val objectCounter = AtomicInteger(TypeRegistry.objectCounterInitialValue) + internal val objectCounter = ObjectCounter(TypeRegistry.objectCounterInitialValue) + private fun findNewAddr(insideStaticInitializer: Boolean): UtAddrExpression { - val newAddr = objectCounter.getAndIncrement() + val newAddr = objectCounter.createNewAddr() // return negative address for objects created inside static initializer // to make their address space be intersected with address space of // parameters of method under symbolic execution 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 92e9ac9665..79b78e634d 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -237,7 +237,8 @@ class UtBotSymbolicEngine( typeResolver, state.solver.lastStatus as UtSolverStatusSAT, methodUnderTest, - softMaxArraySize + softMaxArraySize, + traverser.objectCounter ) val resolvedParameters = state.methodUnderTestParameters @@ -444,8 +445,16 @@ class UtBotSymbolicEngine( Predictors.testName.provide(state.path, predictedTestName, "") // resolving - val resolver = - Resolver(hierarchy, memory, typeRegistry, typeResolver, holder, methodUnderTest, softMaxArraySize) + val resolver = Resolver( + hierarchy, + memory, + typeRegistry, + typeResolver, + holder, + methodUnderTest, + softMaxArraySize, + traverser.objectCounter + ) val (modelsBefore, modelsAfter, instrumentation) = resolver.resolveModels(parameters) diff --git a/utbot-sample/src/main/java/org/utbot/examples/lambda/ThrowingWithLambdaExample.java b/utbot-sample/src/main/java/org/utbot/examples/lambda/ThrowingWithLambdaExample.java new file mode 100644 index 0000000000..acbc77ceb3 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/lambda/ThrowingWithLambdaExample.java @@ -0,0 +1,27 @@ +package org.utbot.examples.lambda; + +import org.utbot.api.mock.UtMock; + +public class ThrowingWithLambdaExample { + // This example mostly checks that we can construct non-static lambda even if it's init section was not analyzed + // (e.g., an exception was thrown before it). + boolean anyExample(int[] values, IntPredicate predicate) { + UtMock.assume(predicate != null); + + for (int value : values) { + if (predicate.test(value)) { + return true; + } + } + + return false; + } + + // To make this lambda non-static, we need to make it use `this` instance. + @SuppressWarnings({"unused", "ConstantConditions"}) + IntPredicate nonStaticIntPredicate = x -> this != null && x == 42; + + interface IntPredicate { + boolean test(int value); + } +}