Skip to content

Commit f612a03

Browse files
committed
Prefer symbolic executions to fuzzed execution in minimization
1 parent ca8a318 commit f612a03

File tree

4 files changed

+104
-12
lines changed

4 files changed

+104
-12
lines changed

utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,11 @@ object UtSettings : AbstractSettings(
378378
*/
379379
var disableSandbox by getBooleanProperty(false)
380380

381+
/**
382+
* Exclude fuzzer-produced executions during minimization if there is an equivalent symbolic execution
383+
*/
384+
var preferSymbolicExecutionsDuringMinimization by getBooleanProperty(true)
385+
381386
}
382387

383388
/**

utbot-framework-test/src/test/kotlin/org/utbot/framework/minimization/MinimizationGreedyEssentialTest.kt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,46 @@ class MinimizationGreedyEssentialTest {
6868
val minimizedExecutions = GreedyEssential.minimize(executions)
6969
assertEquals((1..size).toList(), minimizedExecutions.sorted())
7070
}
71+
72+
@Test
73+
fun testWithSourcePriority() {
74+
val executions = mapOf(
75+
10 to listOf(1, 2, 3, 4, 5),
76+
20 to listOf(2, 3, 4, 5, 6, 7),
77+
21 to listOf(2, 3, 4, 5, 6, 7),
78+
25 to listOf(1, 7, 2, 3, 5),
79+
30 to listOf(8, 9),
80+
50 to listOf(1, 8, 9),
81+
60 to listOf(1, 9)
82+
)
83+
84+
val sourcePriorities = mapOf(
85+
10 to 0,
86+
20 to 1,
87+
21 to 0,
88+
25 to 0,
89+
30 to 1,
90+
50 to 1,
91+
60 to 0
92+
)
93+
94+
val minimizedExecutions = GreedyEssential.minimize(executions, sourcePriorities)
95+
assertEquals(listOf(21, 50), minimizedExecutions)
96+
}
97+
98+
@Test
99+
fun testWithoutSourcePriority() {
100+
val executions = mapOf(
101+
10 to listOf(1, 2, 3, 4, 5),
102+
20 to listOf(2, 3, 4, 5, 6, 7),
103+
21 to listOf(2, 3, 4, 5, 6, 7),
104+
25 to listOf(1, 7, 2, 3, 5),
105+
30 to listOf(8, 9),
106+
50 to listOf(1, 8, 9),
107+
60 to listOf(1, 9)
108+
)
109+
110+
val minimizedExecutions = GreedyEssential.minimize(executions)
111+
assertEquals(listOf(20, 50), minimizedExecutions)
112+
}
71113
}

utbot-framework/src/main/kotlin/org/utbot/framework/minimization/GreedyEssential.kt

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,51 @@ package org.utbot.framework.minimization
22

33
import java.util.PriorityQueue
44

5-
private inline class ExecutionNumber(val number: Int)
5+
@JvmInline
6+
private value class ExecutionNumber(val number: Int)
67

7-
private inline class LineNumber(val number: Int)
8+
@JvmInline
9+
private value class LineNumber(val number: Int)
10+
11+
@JvmInline
12+
private value class SourcePriority(val number: Int)
13+
14+
private data class CoveredLinesWithSourcePriority(
15+
val sourcePriority: SourcePriority,
16+
val coveredLines: List<LineNumber>
17+
)
818

919
/**
1020
* [Greedy essential algorithm](CONFLUENCE:Test+Minimization)
1121
*/
1222
class GreedyEssential private constructor(
13-
executionToCoveredLines: Map<ExecutionNumber, List<LineNumber>>
23+
executionToCoveredLines: Map<ExecutionNumber, CoveredLinesWithSourcePriority>
1424
) {
25+
private val executionToSourcePriority: Map<ExecutionNumber, SourcePriority> =
26+
executionToCoveredLines
27+
.mapValues { it.value.sourcePriority }
28+
1529
private val executionToUsefulLines: Map<ExecutionNumber, MutableSet<LineNumber>> =
1630
executionToCoveredLines
17-
.mapValues { it.value.toMutableSet() }
31+
.mapValues { it.value.coveredLines.toMutableSet() }
1832

1933
private val lineToUnusedCoveringExecutions: Map<LineNumber, MutableSet<ExecutionNumber>> =
2034
executionToCoveredLines
21-
.flatMap { (execution, lines) -> lines.map { it to execution } }
35+
.flatMap { (execution, lines) -> lines.coveredLines.map { it to execution } }
2236
.groupBy({ it.first }) { it.second }
2337
.mapValues { it.value.toMutableSet() }
2438

2539
private val executionByPriority =
26-
PriorityQueue(compareByDescending<Pair<ExecutionNumber, Int>> { it.second }.thenBy { it.first.number })
40+
PriorityQueue(
41+
compareByDescending<Triple<ExecutionNumber, Int, SourcePriority>> { it.second }
42+
.thenBy { it.third.number }
43+
.thenBy { it.first.number }
44+
)
2745
.apply {
2846
addAll(
2947
executionToCoveredLines
3048
.keys
31-
.map { it to executionToUsefulLines[it]!!.size }
49+
.map { Triple(it, executionToUsefulLines[it]!!.size, executionToSourcePriority[it]!!) }
3250
)
3351
}
3452

@@ -69,7 +87,7 @@ class GreedyEssential private constructor(
6987
}
7088

7189
private fun executionToPriority(execution: ExecutionNumber) =
72-
execution to executionToUsefulLines[execution]!!.size
90+
Triple(execution, executionToUsefulLines[execution]!!.size, executionToSourcePriority[execution]!!)
7391

7492
private fun removeLineFromExecution(execution: ExecutionNumber, line: LineNumber) {
7593
executionByPriority.remove(executionToPriority(execution))
@@ -89,10 +107,15 @@ class GreedyEssential private constructor(
89107
*
90108
* @return retained execution ids.
91109
*/
92-
fun minimize(executions: Map<Int, List<Int>>): List<Int> {
110+
fun minimize(executions: Map<Int, List<Int>>, sourcePriorities: Map<Int, Int> = emptyMap()): List<Int> {
93111
val convertedExecutions = executions
94112
.entries
95-
.associate { (execution, lines) -> ExecutionNumber(execution) to lines.map { LineNumber(it) } }
113+
.associate { (execution, lines) ->
114+
ExecutionNumber(execution) to CoveredLinesWithSourcePriority(
115+
SourcePriority(sourcePriorities.getOrDefault(execution, 0)),
116+
lines.map { LineNumber(it) }
117+
)
118+
}
96119

97120
val prioritizer = GreedyEssential(convertedExecutions)
98121
val list = mutableListOf<ExecutionNumber>()

utbot-framework/src/main/kotlin/org/utbot/framework/minimization/Minimization.kt

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@ import org.utbot.framework.plugin.api.UtExecutableCallModel
1313
import org.utbot.framework.plugin.api.UtExecution
1414
import org.utbot.framework.plugin.api.UtExecutionFailure
1515
import org.utbot.framework.plugin.api.UtExecutionResult
16+
import org.utbot.framework.plugin.api.UtFailedExecution
1617
import org.utbot.framework.plugin.api.UtModel
1718
import org.utbot.framework.plugin.api.UtNullModel
1819
import org.utbot.framework.plugin.api.UtPrimitiveModel
1920
import org.utbot.framework.plugin.api.UtStatementModel
21+
import org.utbot.framework.plugin.api.UtSymbolicExecution
2022
import org.utbot.framework.plugin.api.UtVoidModel
23+
import org.utbot.fuzzer.UtFuzzedExecution
2124

2225

2326
/**
@@ -52,8 +55,15 @@ fun minimizeExecutions(executions: List<UtExecution>): List<UtExecution> {
5255
executions.indices.filter { executions[it].coverage?.coveredInstructions?.isEmpty() ?: true }.toSet()
5356
// ^^^ here we add executions with empty or null coverage, because it happens only if a concrete execution failed,
5457
// so we don't know the actual coverage for such executions
58+
59+
val sourcePriorities = if (UtSettings.preferSymbolicExecutionsDuringMinimization) {
60+
executions.withIndex().associate { it.index to it.value.getSourcePriorityForMinimization() }
61+
} else {
62+
emptyMap()
63+
}
64+
5565
val mapping = buildMapping(executions.filterIndexed { idx, _ -> idx !in unknownCoverageExecutions })
56-
val usedExecutionIndexes = (GreedyEssential.minimize(mapping) + unknownCoverageExecutions).toSet()
66+
val usedExecutionIndexes = (GreedyEssential.minimize(mapping, sourcePriorities) + unknownCoverageExecutions).toSet()
5767

5868
val usedMinimizedExecutions = executions.filterIndexed { idx, _ -> idx in usedExecutionIndexes }
5969

@@ -64,6 +74,19 @@ fun minimizeExecutions(executions: List<UtExecution>): List<UtExecution> {
6474
}
6575
}
6676

77+
/**
78+
* Compute the priority of the given execution during minimization. Lower values correspond to higher priority.
79+
*
80+
* If [UtSettings.preferSymbolicExecutionsDuringMinimization] is true, minimizer will use these priorities
81+
* to select symbolic executions instead of fuzzer-generated executions with the same coverage.
82+
*/
83+
private fun UtExecution.getSourcePriorityForMinimization(): Int = when (this) {
84+
is UtSymbolicExecution -> 0
85+
is UtFuzzedExecution -> 1
86+
is UtFailedExecution -> 2
87+
else -> 3
88+
}
89+
6790
/**
6891
* Groups the [executions] by their `paths` on `first` [branchInstructionsNumber] `branch` instructions.
6992
*
@@ -150,7 +173,6 @@ private fun buildMapping(executions: List<UtExecution>): Map<Int, List<Int>> {
150173
val thrownExceptions = mutableMapOf<String, Long>()
151174
val mapping = mutableMapOf<Int, List<Int>>()
152175

153-
154176
executions.forEachIndexed { idx, execution ->
155177
execution.coverage?.let { coverage ->
156178
val instructionsWithoutExtra = coverage.coveredInstructions.map { it.id }

0 commit comments

Comments
 (0)