Skip to content

Commit edc1d8d

Browse files
authored
Added constructing this instance for captured values of lambda in case of early return (#1658)
1 parent 8b8d9f3 commit edc1d8d

File tree

6 files changed

+100
-7
lines changed

6 files changed

+100
-7
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.utbot.examples.lambda
2+
3+
import org.junit.jupiter.api.Test
4+
import org.utbot.testcheckers.eq
5+
import org.utbot.testing.DoNotCalculate
6+
import org.utbot.testing.UtValueTestCaseChecker
7+
8+
class ThrowingWithLambdaExampleTest : UtValueTestCaseChecker(testClass = ThrowingWithLambdaExample::class) {
9+
@Test
10+
fun testAnyExample() {
11+
check(
12+
ThrowingWithLambdaExample::anyExample,
13+
eq(4),
14+
{ l, _, _ -> l == null },
15+
{ l, _, r -> l.isEmpty() && r == false },
16+
{ l, _, r -> l.isNotEmpty() && 42 in l && r == true },
17+
{ l, _, r -> l.isNotEmpty() && 42 !in l && r == false },
18+
coverage = DoNotCalculate // TODO failed coverage calculation
19+
)
20+
}
21+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.utbot.engine
2+
3+
import java.util.concurrent.atomic.AtomicInteger
4+
5+
/**
6+
* Counts new objects during execution. Used to give new addresses for objects in [Traverser] and [Resolver].
7+
*/
8+
data class ObjectCounter(val initialValue: Int) {
9+
private val internalCounter = AtomicInteger(initialValue)
10+
11+
fun createNewAddr(): Int = internalCounter.getAndIncrement()
12+
}

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

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ import org.utbot.engine.types.TypeRegistry
101101
import org.utbot.engine.types.TypeResolver
102102
import org.utbot.framework.plugin.api.visible.UtStreamConsumingException
103103
import org.utbot.framework.plugin.api.UtStreamConsumingFailure
104+
import org.utbot.framework.plugin.api.util.isStatic
104105

105106
// hack
106107
const val MAX_LIST_SIZE = 10
@@ -130,7 +131,8 @@ class Resolver(
130131
private val typeResolver: TypeResolver,
131132
val holder: UtSolverStatusSAT,
132133
methodUnderTest: ExecutableId,
133-
private val softMaxArraySize: Int
134+
private val softMaxArraySize: Int,
135+
private val objectCounter: ObjectCounter
134136
) {
135137

136138
private val classLoader: ClassLoader
@@ -529,7 +531,28 @@ class Resolver(
529531

530532
if (sootClass.isLambda) {
531533
return constructLambda(concreteAddr, sootClass).also { lambda ->
532-
lambda.capturedValues += collectFieldModels(addr, actualType).values
534+
val collectedFieldModels = collectFieldModels(addr, actualType).toMutableMap()
535+
536+
if (!lambda.lambdaMethodId.isStatic) {
537+
val thisInstanceField = FieldId(declaringClass = sootClass.id, name = "cap0")
538+
539+
if (thisInstanceField !in collectedFieldModels || collectedFieldModels[thisInstanceField] is UtNullModel) {
540+
// Non-static lambda has to have `this` instance captured as `cap0` field that cannot be null,
541+
// so if we do not have it as field or it is null (for example, an exception was thrown before initializing lambda),
542+
// we need to construct `this` instance by ourselves.
543+
// Since we do not know its fields, we create an empty object of the corresponding type that will be
544+
// constructed in codegen using reflection.
545+
val thisInstanceClassId = sootClass.name.substringBeforeLast("\$lambda").let {
546+
Scene.v().getSootClass(it)
547+
}.id
548+
val thisInstanceModel =
549+
UtCompositeModel(objectCounter.createNewAddr(), thisInstanceClassId, isMock = false)
550+
551+
collectedFieldModels[thisInstanceField] = thisInstanceModel
552+
}
553+
}
554+
555+
lambda.capturedValues += collectedFieldModels.values
533556
}
534557
}
535558

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,9 +267,10 @@ class Traverser(
267267

268268
private var queuedSymbolicStateUpdates = SymbolicStateUpdate()
269269

270-
private val objectCounter = AtomicInteger(TypeRegistry.objectCounterInitialValue)
270+
internal val objectCounter = ObjectCounter(TypeRegistry.objectCounterInitialValue)
271+
271272
private fun findNewAddr(insideStaticInitializer: Boolean): UtAddrExpression {
272-
val newAddr = objectCounter.getAndIncrement()
273+
val newAddr = objectCounter.createNewAddr()
273274
// return negative address for objects created inside static initializer
274275
// to make their address space be intersected with address space of
275276
// parameters of method under symbolic execution

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,8 @@ class UtBotSymbolicEngine(
237237
typeResolver,
238238
state.solver.lastStatus as UtSolverStatusSAT,
239239
methodUnderTest,
240-
softMaxArraySize
240+
softMaxArraySize,
241+
traverser.objectCounter
241242
)
242243

243244
val resolvedParameters = state.methodUnderTestParameters
@@ -444,8 +445,16 @@ class UtBotSymbolicEngine(
444445
Predictors.testName.provide(state.path, predictedTestName, "")
445446

446447
// resolving
447-
val resolver =
448-
Resolver(hierarchy, memory, typeRegistry, typeResolver, holder, methodUnderTest, softMaxArraySize)
448+
val resolver = Resolver(
449+
hierarchy,
450+
memory,
451+
typeRegistry,
452+
typeResolver,
453+
holder,
454+
methodUnderTest,
455+
softMaxArraySize,
456+
traverser.objectCounter
457+
)
449458

450459
val (modelsBefore, modelsAfter, instrumentation) = resolver.resolveModels(parameters)
451460

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.utbot.examples.lambda;
2+
3+
import org.utbot.api.mock.UtMock;
4+
5+
public class ThrowingWithLambdaExample {
6+
// This example mostly checks that we can construct non-static lambda even if it's init section was not analyzed
7+
// (e.g., an exception was thrown before it).
8+
boolean anyExample(int[] values, IntPredicate predicate) {
9+
UtMock.assume(predicate != null);
10+
11+
for (int value : values) {
12+
if (predicate.test(value)) {
13+
return true;
14+
}
15+
}
16+
17+
return false;
18+
}
19+
20+
// To make this lambda non-static, we need to make it use `this` instance.
21+
@SuppressWarnings({"unused", "ConstantConditions"})
22+
IntPredicate nonStaticIntPredicate = x -> this != null && x == 42;
23+
24+
interface IntPredicate {
25+
boolean test(int value);
26+
}
27+
}

0 commit comments

Comments
 (0)