Skip to content

Commit a5d1af4

Browse files
authored
Introduce utbot-light module (#2445)
1 parent f16d5ca commit a5d1af4

File tree

22 files changed

+312
-63
lines changed

22 files changed

+312
-63
lines changed

settings.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,5 @@ if (projectType == springEdition || projectType == ultimateEdition) {
8989

9090

9191

92+
include("utbot-light")
93+

utbot-analytics/src/main/kotlin/org/utbot/features/UtExpressionStructureCounter.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ class UtExpressionStructureCounter(private val input: Iterable<UtExpression>) :
145145
override fun visit(expr: UtAddNoOverflowExpression) = multipleExpressions(expr.left, expr.right)
146146

147147
override fun visit(expr: UtSubNoOverflowExpression) = multipleExpressions(expr.left, expr.right)
148+
override fun visit(expr: UtMulNoOverflowExpression)= multipleExpressions(expr.left, expr.right)
148149

149150
override fun visit(expr: UtNegExpression): NestStat {
150151
val stat = buildState(expr.variable.expr)

utbot-api/build.gradle.kts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +0,0 @@
1-
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
2-
3-
plugins {
4-
id("com.github.johnrengelman.shadow") version "7.1.2"
5-
}
6-
7-
tasks {
8-
withType<ShadowJar> {
9-
archiveClassifier.set(" ")
10-
minimize()
11-
}
12-
}

utbot-core/build.gradle.kts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
2-
31
val kotlinLoggingVersion: String by rootProject
42
val junit4Version: String by rootProject
53

@@ -12,11 +10,4 @@ dependencies {
1210
implementation(group = "net.java.dev.jna", name = "jna-platform", version = "5.5.0")
1311

1412
testImplementation(group = "junit", name = "junit", version = junit4Version)
15-
}
16-
17-
tasks {
18-
withType<ShadowJar> {
19-
archiveClassifier.set(" ")
20-
minimize()
21-
}
2213
}

utbot-framework-api/build.gradle.kts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
2-
31
val junit4Version: String by rootProject
42
val sootVersion: String by rootProject
53
val commonsLangVersion: String by rootProject
@@ -8,10 +6,6 @@ val rdVersion: String? by rootProject
86
val kryoVersion: String? by rootProject
97
val kryoSerializersVersion: String? by rootProject
108

11-
plugins {
12-
id("com.github.johnrengelman.shadow") version "7.1.2"
13-
}
14-
159
dependencies {
1610
api(project(":utbot-core"))
1711
api(project(":utbot-api"))
@@ -30,11 +24,8 @@ dependencies {
3024
testImplementation(group = "junit", name = "junit", version = junit4Version)
3125
}
3226

33-
tasks {
34-
withType<ShadowJar> {
35-
archiveClassifier.set(" ")
36-
minimize()
37-
}
27+
java {
28+
withSourcesJar()
3829
}
3930

4031
tasks {

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,13 @@ object UtSettings : AbstractSettings(logger, defaultKeyForSettingsPath, defaultS
576576
var maxArraySize by getIntProperty(1024)
577577

578578
// endregion
579+
580+
// region UTBot light related options
581+
// Changes to improve symbolic engine for light version
582+
583+
var disableUnsatChecking by getBooleanProperty(false)
584+
585+
// endregion
579586
}
580587

581588
/**
@@ -592,6 +599,11 @@ enum class PathSelectorType {
592599
*/
593600
INHERITORS_SELECTOR,
594601

602+
/**
603+
* [BFSSelector]
604+
*/
605+
BFS_SELECTOR,
606+
595607
/**
596608
* [SubpathGuidedSelector]
597609
*/

utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/Trivial.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
public class Trivial {
44
public int aMethod(int a) {
5-
return a;
5+
String s = "a";
6+
if (a > 1) {
7+
return s.length();
8+
}
9+
return s.length() + 1;
610
}
711
}

utbot-framework-test/src/test/java/org/utbot/examples/manual/UtBotJavaApiTest.java

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.utbot.examples.manual.examples.customer.C;
1919
import org.utbot.examples.manual.examples.customer.Demo9;
2020
import org.utbot.external.api.TestMethodInfo;
21+
import org.utbot.external.api.UnitTestBotLight;
2122
import org.utbot.external.api.UtBotJavaApi;
2223
import org.utbot.external.api.UtModelFactory;
2324
import org.utbot.framework.codegen.domain.ForceStaticMocking;
@@ -1306,7 +1307,16 @@ public void testFuzzingSimple() {
13061307

13071308
@NotNull
13081309
private String getClassPath(Class<?> clazz) {
1309-
return clazz.getProtectionDomain().getCodeSource().getLocation().getPath();
1310+
try {
1311+
return normalizePath(clazz.getProtectionDomain().getCodeSource().getLocation());
1312+
} catch (URISyntaxException e) {
1313+
throw new RuntimeException(e);
1314+
}
1315+
}
1316+
1317+
@NotNull
1318+
private String normalizePath(URL url) throws URISyntaxException {
1319+
return new File(url.toURI()).getPath();
13101320
}
13111321

13121322
@NotNull
@@ -1376,4 +1386,37 @@ public UtCompositeModel createArrayOfComplexArraysModel() {
13761386
classIdOfArrayOfComplexArraysClass,
13771387
Collections.singletonMap("array", arrayOfComplexArrayClasses));
13781388
}
1389+
1390+
@Test
1391+
public void testUnitTestBotLight() {
1392+
String classpath = getClassPath(Trivial.class);
1393+
String dependencyClassPath = getDependencyClassPath();
1394+
1395+
UtCompositeModel model = modelFactory.
1396+
produceCompositeModel(
1397+
classIdForType(Trivial.class)
1398+
);
1399+
1400+
EnvironmentModels environmentModels = new EnvironmentModels(
1401+
model,
1402+
Collections.singletonList(new UtPrimitiveModel(2)),
1403+
Collections.emptyMap()
1404+
);
1405+
1406+
Method methodUnderTest = PredefinedGeneratorParameters.getMethodByName(
1407+
Trivial.class,
1408+
"aMethod",
1409+
int.class
1410+
);
1411+
1412+
UnitTestBotLight.run(
1413+
(engine, state) -> System.err.println("Got a call:" + state.getStmt()),
1414+
new TestMethodInfo(
1415+
methodUnderTest,
1416+
environmentModels
1417+
),
1418+
classpath,
1419+
dependencyClassPath
1420+
);
1421+
}
13791422
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.utbot.engine
2+
3+
import org.utbot.engine.state.ExecutionState
4+
5+
/**
6+
* [UtBotSymbolicEngine] will fire an event every time it traverses new [ExecutionState].
7+
*/
8+
fun interface ExecutionStateListener {
9+
fun visit(graph: InterProceduralUnitGraph, state: ExecutionState)
10+
}

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4060,12 +4060,14 @@ class Traverser(
40604060
val memory = symbolicState.memory
40614061
val solver = symbolicState.solver
40624062

4063-
//no need to respect soft constraints in NestedMethod
4064-
val holder = solver.check(respectSoft = !environment.state.isInNestedMethod())
4063+
if (!UtSettings.disableUnsatChecking) {
4064+
//no need to respect soft constraints in NestedMethod
4065+
val holder = solver.check(respectSoft = !environment.state.isInNestedMethod())
40654066

4066-
if (holder !is UtSolverStatusSAT) {
4067-
logger.trace { "processResult<${environment.method.signature}> UNSAT" }
4068-
return
4067+
if (holder !is UtSolverStatusSAT) {
4068+
logger.trace { "processResult<${environment.method.signature}> UNSAT" }
4069+
return
4070+
}
40694071
}
40704072
val methodResult = MethodResult(symbolicResult)
40714073

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

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,14 @@ import org.utbot.instrumentation.ConcreteExecutor
4848
import org.utbot.instrumentation.instrumentation.Instrumentation
4949
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData
5050
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
51+
import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation
5152
import org.utbot.taint.*
5253
import org.utbot.taint.model.TaintConfiguration
5354
import soot.jimple.Stmt
5455
import soot.tagkit.ParamNamesTag
5556
import java.lang.reflect.Method
57+
import java.util.function.Consumer
58+
import java.util.function.Predicate
5659
import kotlin.math.min
5760
import kotlin.system.measureTimeMillis
5861

@@ -80,6 +83,9 @@ private fun pathSelector(graph: InterProceduralUnitGraph, typeRegistry: TypeRegi
8083
PathSelectorType.INHERITORS_SELECTOR -> inheritorsSelector(graph, typeRegistry) {
8184
withStepsLimit(pathSelectorStepsLimit)
8285
}
86+
PathSelectorType.BFS_SELECTOR -> bfsSelector(graph, StrategyOption.VISIT_COUNTING) {
87+
withStepsLimit(pathSelectorStepsLimit)
88+
}
8389
PathSelectorType.SUBPATH_GUIDED_SELECTOR -> subpathGuidedSelector(graph, StrategyOption.DISTANCE) {
8490
withStepsLimit(pathSelectorStepsLimit)
8591
}
@@ -141,6 +147,18 @@ class UtBotSymbolicEngine(
141147
mockerContext = applicationContext.mockerContext,
142148
)
143149

150+
private val stateListeners: MutableList<ExecutionStateListener> = mutableListOf();
151+
152+
fun addListener(listener: ExecutionStateListener): UtBotSymbolicEngine {
153+
stateListeners += listener
154+
return this
155+
}
156+
157+
fun removeListener(listener: ExecutionStateListener): UtBotSymbolicEngine {
158+
stateListeners -= listener
159+
return this
160+
}
161+
144162
fun attachMockListener(mockListener: MockListener) = mocker.mockListenerController?.attach(mockListener)
145163

146164
fun detachMockListener(mockListener: MockListener) = mocker.mockListenerController?.detach(mockListener)
@@ -216,6 +234,22 @@ class UtBotSymbolicEngine(
216234
.onStart { preTraverse() }
217235
.onCompletion { postTraverse() }
218236

237+
/**
238+
* Traverse through all states and get results.
239+
*
240+
* This method is supposed to used when calling [traverse] is not suitable,
241+
* e.g. from Java programs. It runs traversing with blocking style using callback
242+
* to provide [UtResult].
243+
*/
244+
@JvmOverloads
245+
fun traverseAll(consumer: Consumer<UtResult> = Consumer { }) {
246+
runBlocking {
247+
traverse().collect {
248+
consumer.accept(it)
249+
}
250+
}
251+
}
252+
219253
private fun traverseImpl(): Flow<UtResult> = flow {
220254

221255
require(trackableResources.isEmpty())
@@ -252,7 +286,7 @@ class UtBotSymbolicEngine(
252286
"queue size=${(pathSelector as? NonUniformRandomSearch)?.size ?: -1}"
253287
}
254288

255-
if (controller.executeConcretely || statesForConcreteExecution.isNotEmpty()) {
289+
if (UtSettings.useConcreteExecution && (controller.executeConcretely || statesForConcreteExecution.isNotEmpty())) {
256290
val state = pathSelector.pollUntilFastSAT()
257291
?: statesForConcreteExecution.pollUntilSat(processUnknownStatesDuringConcreteExecution)
258292
?: break
@@ -316,6 +350,14 @@ class UtBotSymbolicEngine(
316350
}
317351
}
318352

353+
// I am not sure this part works correctly when concrete execution is enabled.
354+
// todo test this part more accurate
355+
try {
356+
fireExecutionStateEvent(state)
357+
} catch (ce: CancellationException) {
358+
break
359+
}
360+
319361
} else {
320362
val state = pathSelector.poll()
321363

@@ -360,11 +402,27 @@ class UtBotSymbolicEngine(
360402

361403
// TODO: think about concise modifying globalGraph in Traverser and UtBotSymbolicEngine
362404
globalGraph.visitNode(state)
405+
406+
try {
407+
fireExecutionStateEvent(state)
408+
} catch (ce: CancellationException) {
409+
break
410+
}
363411
}
364412
}
365413
}
366414
}
367415

416+
private fun fireExecutionStateEvent(state: ExecutionState) {
417+
stateListeners.forEach { l ->
418+
try {
419+
l.visit(globalGraph, state)
420+
} catch (t: Throwable) {
421+
logger.error(t) { "$l failed with error" }
422+
}
423+
}
424+
}
425+
368426

369427
/**
370428
* Run fuzzing flow.
@@ -516,7 +574,11 @@ class UtBotSymbolicEngine(
516574
val solver = state.solver
517575
val parameters = state.parameters.map { it.value }
518576
val symbolicResult = requireNotNull(state.methodResult?.symbolicResult) { "The state must have symbolicResult" }
519-
val holder = requireNotNull(solver.lastStatus as? UtSolverStatusSAT) { "The state must be SAT!" }
577+
val holder = if (UtSettings.disableUnsatChecking) {
578+
(solver.lastStatus as? UtSolverStatusSAT) ?: return
579+
} else {
580+
requireNotNull(solver.lastStatus as? UtSolverStatusSAT) { "The state must be SAT!" }
581+
}
520582

521583
val predictedTestName = Predictors.testName.predict(state.path)
522584
Predictors.testName.provide(state.path, predictedTestName, "")

utbot-framework/src/main/kotlin/org/utbot/engine/pc/Simplificator.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,12 @@ open class Simplificator(
607607
applySimplification(expr.right)
608608
)
609609

610+
override fun visit(expr: UtMulNoOverflowExpression): UtExpression =
611+
UtMulNoOverflowExpression(
612+
applySimplification(expr.left),
613+
applySimplification(expr.right)
614+
)
615+
610616
// CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-negConcrete
611617
// Neg (Concrete a) ---> Concrete (-a)
612618
override fun visit(expr: UtNegExpression): UtExpression =

utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtExpression.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,31 @@ data class UtSubNoOverflowExpression(
805805
override fun hashCode() = hashCode
806806
}
807807

808+
data class UtMulNoOverflowExpression(
809+
val left: UtExpression,
810+
val right: UtExpression,
811+
) : UtBoolExpression() {
812+
override val hashCode = Objects.hash(left, right)
813+
814+
override fun <TResult> accept(visitor: UtExpressionVisitor<TResult>): TResult = visitor.visit(this)
815+
816+
override fun toString() = "(mulNoOverflow $left $right)"
817+
818+
override fun equals(other: Any?): Boolean {
819+
if (this === other) return true
820+
if (javaClass != other?.javaClass) return false
821+
822+
other as UtMulNoOverflowExpression
823+
824+
if (left != other.left) return false
825+
if (right != other.right) return false
826+
827+
return true
828+
}
829+
830+
override fun hashCode() = hashCode
831+
}
832+
808833
data class UtNegExpression(val variable: PrimitiveValue) : UtExpression(alignSort(variable.type.toSort())) {
809834
override val hashCode = variable.hashCode
810835

utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtExpressionVisitor.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,5 @@ interface UtExpressionVisitor<TResult> {
4242
// Add and Sub with overflow detection
4343
fun visit(expr: UtAddNoOverflowExpression): TResult
4444
fun visit(expr: UtSubNoOverflowExpression): TResult
45+
fun visit(expr: UtMulNoOverflowExpression): TResult
4546
}

0 commit comments

Comments
 (0)