Skip to content

Commit f5462e6

Browse files
authored
TS interpreter observer (#268)
1 parent 16bd6c3 commit f5462e6

File tree

11 files changed

+343
-14
lines changed

11 files changed

+343
-14
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.usvm.api.checkers
2+
3+
import org.jacodb.ets.model.EtsIfStmt
4+
import org.jacodb.ets.model.EtsMethod
5+
import org.jacodb.ets.model.EtsStmt
6+
import org.usvm.machine.TsInterpreterObserver
7+
import org.usvm.machine.expr.TsSimpleValueResolver
8+
import org.usvm.machine.interpreter.TsStepScope
9+
import org.usvm.machine.state.TsState
10+
import org.usvm.statistics.UMachineObserver
11+
import kotlin.collections.filter
12+
import kotlin.collections.isNotEmpty
13+
14+
data class UncoveredIfSuccessors(val ifStmt: EtsIfStmt, val successors: Set<EtsStmt>)
15+
16+
class UnreachableCodeDetector : TsInterpreterObserver, UMachineObserver<TsState> {
17+
private val uncoveredSuccessorsOfVisitedIfStmts = hashMapOf<EtsMethod, MutableMap<EtsIfStmt, MutableSet<EtsStmt>>>()
18+
lateinit var result: Map<EtsMethod, List<UncoveredIfSuccessors>>
19+
20+
override fun onIfStatement(simpleValueResolver: TsSimpleValueResolver, stmt: EtsIfStmt, scope: TsStepScope) {
21+
val ifStmts = uncoveredSuccessorsOfVisitedIfStmts.getOrPut(stmt.method) { mutableMapOf() }
22+
// We've already added its successors in the map
23+
if (stmt in ifStmts) return
24+
25+
val successors = stmt.method.cfg.successors(stmt)
26+
ifStmts[stmt] = successors.toMutableSet()
27+
}
28+
29+
override fun onState(parent: TsState, forks: Sequence<TsState>) {
30+
val previousStatement = parent.pathNode.parent?.statement
31+
if (previousStatement !is EtsIfStmt) return
32+
33+
val method = parent.currentStatement.method
34+
val remainingUncoveredIfSuccessors = uncoveredSuccessorsOfVisitedIfStmts.getValue(method)
35+
val remainingSuccessorsForCurrentIf = remainingUncoveredIfSuccessors[previousStatement]
36+
?: return // No uncovered successors for this if statement
37+
38+
remainingSuccessorsForCurrentIf -= parent.currentStatement
39+
forks.forEach { remainingSuccessorsForCurrentIf -= it.currentStatement }
40+
}
41+
42+
override fun onMachineStopped() {
43+
result = uncoveredSuccessorsOfVisitedIfStmts
44+
.mapNotNull { (method, uncoveredIfSuccessors) ->
45+
uncoveredIfSuccessors
46+
.filter { it.value.isNotEmpty() }
47+
.takeIf { it.isNotEmpty() }
48+
?.let { ifSucc -> method to ifSucc.map { UncoveredIfSuccessors(it.key, it.value) } }
49+
}.toMap()
50+
}
51+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package org.usvm.machine
2+
3+
import org.jacodb.ets.model.EtsAssignStmt
4+
import org.jacodb.ets.model.EtsCallExpr
5+
import org.jacodb.ets.model.EtsIfStmt
6+
import org.jacodb.ets.model.EtsReturnStmt
7+
import org.jacodb.ets.model.EtsThrowStmt
8+
import org.usvm.UBoolExpr
9+
import org.usvm.machine.expr.TsSimpleValueResolver
10+
import org.usvm.machine.interpreter.TsStepScope
11+
import org.usvm.statistics.UInterpreterObserver
12+
13+
@Suppress("unused")
14+
interface TsInterpreterObserver : UInterpreterObserver {
15+
fun onAssignStatement(
16+
simpleValueResolver: TsSimpleValueResolver,
17+
stmt: EtsAssignStmt,
18+
scope: TsStepScope,
19+
) {
20+
// default empty implementation
21+
}
22+
23+
// TODO on entry point
24+
25+
fun onCallWithUnresolvedArguments(
26+
simpleValueResolver: TsSimpleValueResolver,
27+
stmt: EtsCallExpr,
28+
scope: TsStepScope,
29+
) {
30+
// default empty implementation
31+
}
32+
33+
// TODO onCallWithResolvedArguments
34+
35+
fun onIfStatement(
36+
simpleValueResolver: TsSimpleValueResolver,
37+
stmt: EtsIfStmt,
38+
scope: TsStepScope,
39+
) {
40+
// default empty implementation
41+
}
42+
43+
fun onIfStatementWithResolvedCondition(
44+
simpleValueResolver: TsSimpleValueResolver,
45+
stmt: EtsIfStmt,
46+
condition: UBoolExpr,
47+
scope: TsStepScope,
48+
) {
49+
// default empty implementation
50+
}
51+
52+
fun onReturnStatement(
53+
simpleValueResolver: TsSimpleValueResolver,
54+
stmt: EtsReturnStmt,
55+
scope: TsStepScope,
56+
) {
57+
// default empty implementation
58+
}
59+
60+
fun onThrowStatement(
61+
simpleValueResolver: TsSimpleValueResolver,
62+
stmt: EtsThrowStmt,
63+
scope: TsStepScope,
64+
) {
65+
// default empty implementation
66+
}
67+
}

usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,15 @@ import kotlin.time.Duration.Companion.seconds
2828
class TsMachine(
2929
private val project: EtsScene,
3030
private val options: UMachineOptions,
31+
private val tsOptions: TsOptions,
32+
private val machineObserver: UMachineObserver<TsState>? = null,
33+
observer: TsInterpreterObserver? = null,
3134
) : UMachine<TsState>() {
3235
private val typeSystem = TsTypeSystem(typeOperationsTimeout = 1.seconds, project)
3336
private val components = TsComponents(typeSystem, options)
3437
private val ctx = TsContext(project, components)
3538
private val applicationGraph = TsApplicationGraph(project)
36-
private val interpreter = TsInterpreter(ctx, applicationGraph)
39+
private val interpreter = TsInterpreter(ctx, applicationGraph, tsOptions, observer)
3740
private val cfgStatistics = CfgStatisticsImpl(applicationGraph)
3841

3942
fun analyze(
@@ -98,6 +101,7 @@ class TsMachine(
98101

99102
observers.add(timeStatistics)
100103
observers.add(stepsStatistics)
104+
machineObserver?.let { observers.add(it) }
101105

102106
run(
103107
interpreter,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package org.usvm.machine
2+
3+
data class TsOptions(
4+
val interproceduralAnalysis: Boolean = true,
5+
)

usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,7 @@ class TsExprResolver(
696696
scope.doWithState {
697697
// TODO: Handle static initializer result
698698
val result = methodResult
699-
if (result is TsMethodResult.Success && result.method == initializer) {
699+
if (result is TsMethodResult.Success && result.methodSignature() == initializer.signature) {
700700
methodResult = TsMethodResult.NoCall
701701
}
702702
}

usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import org.jacodb.ets.model.EtsIfStmt
99
import org.jacodb.ets.model.EtsInstanceFieldRef
1010
import org.jacodb.ets.model.EtsLocal
1111
import org.jacodb.ets.model.EtsMethod
12+
import org.jacodb.ets.model.EtsMethodSignature
13+
import org.jacodb.ets.utils.callExpr
1214
import org.jacodb.ets.model.EtsNopStmt
1315
import org.jacodb.ets.model.EtsNullType
1416
import org.jacodb.ets.model.EtsParameterRef
@@ -22,13 +24,19 @@ import org.jacodb.ets.model.EtsValue
2224
import org.jacodb.ets.utils.getDeclaredLocals
2325
import org.usvm.StepResult
2426
import org.usvm.StepScope
27+
import org.usvm.UAddressSort
2528
import org.usvm.UInterpreter
29+
import org.usvm.api.makeSymbolicPrimitive
30+
import org.usvm.api.makeSymbolicRefUntyped
2631
import org.usvm.api.targets.TsTarget
2732
import org.usvm.collections.immutable.internal.MutabilityOwnership
2833
import org.usvm.forkblacklists.UForkBlackList
2934
import org.usvm.machine.TsApplicationGraph
3035
import org.usvm.machine.TsContext
36+
import org.usvm.machine.TsInterpreterObserver
37+
import org.usvm.machine.TsOptions
3138
import org.usvm.machine.expr.TsExprResolver
39+
import org.usvm.machine.expr.TsUnresolvedSort
3240
import org.usvm.machine.expr.mkTruthyExpr
3341
import org.usvm.machine.state.TsMethodResult
3442
import org.usvm.machine.state.TsState
@@ -37,6 +45,7 @@ import org.usvm.machine.state.localsCount
3745
import org.usvm.machine.state.newStmt
3846
import org.usvm.machine.state.parametersWithThisCount
3947
import org.usvm.machine.state.returnValue
48+
import org.usvm.machine.types.mkFakeValue
4049
import org.usvm.sizeSort
4150
import org.usvm.targets.UTargetsSet
4251
import org.usvm.util.mkArrayIndexLValue
@@ -52,6 +61,8 @@ typealias TsStepScope = StepScope<TsState, EtsType, EtsStmt, TsContext>
5261
class TsInterpreter(
5362
private val ctx: TsContext,
5463
private val applicationGraph: TsApplicationGraph,
64+
private val tsOptions: TsOptions,
65+
private val observer: TsInterpreterObserver? = null,
5566
) : UInterpreter<TsState>() {
5667

5768
private val forkBlackList: UForkBlackList<TsState, EtsStmt> = UForkBlackList.createDefault()
@@ -93,6 +104,10 @@ class TsInterpreter(
93104

94105
private fun visitIfStmt(scope: TsStepScope, stmt: EtsIfStmt) {
95106
val exprResolver = exprResolverWithScope(scope)
107+
108+
val simpleValueResolver = exprResolver.simpleValueResolver
109+
observer?.onIfStatement(simpleValueResolver, stmt, scope)
110+
96111
val expr = exprResolver.resolve(stmt.condition) ?: return
97112

98113
val boolExpr = if (expr.sort == ctx.boolSort) {
@@ -101,6 +116,8 @@ class TsInterpreter(
101116
ctx.mkTruthyExpr(expr, scope)
102117
}
103118

119+
observer?.onIfStatementWithResolvedCondition(simpleValueResolver, stmt, boolExpr, scope)
120+
104121
val (posStmt, negStmt) = applicationGraph.successors(stmt).take(2).toList()
105122

106123
scope.forkWithBlackList(
@@ -115,6 +132,8 @@ class TsInterpreter(
115132
private fun visitReturnStmt(scope: TsStepScope, stmt: EtsReturnStmt) {
116133
val exprResolver = exprResolverWithScope(scope)
117134

135+
observer?.onReturnStatement(exprResolver.simpleValueResolver, stmt, scope)
136+
118137
val valueToReturn = stmt.returnValue
119138
?.let { exprResolver.resolve(it) ?: return }
120139
?: ctx.mkUndefinedValue()
@@ -126,6 +145,28 @@ class TsInterpreter(
126145

127146
private fun visitAssignStmt(scope: TsStepScope, stmt: EtsAssignStmt) = with(ctx) {
128147
val exprResolver = exprResolverWithScope(scope)
148+
149+
stmt.callExpr?.let {
150+
val methodResult = scope.calcOnState { methodResult }
151+
152+
when (methodResult) {
153+
is TsMethodResult.NoCall -> observer?.onCallWithUnresolvedArguments(
154+
exprResolver.simpleValueResolver,
155+
it,
156+
scope
157+
)
158+
159+
is TsMethodResult.Success -> observer?.onAssignStatement(exprResolver.simpleValueResolver, stmt, scope)
160+
is TsMethodResult.TsException -> error("Exceptions must be processed earlier")
161+
}
162+
163+
if (!tsOptions.interproceduralAnalysis && methodResult == TsMethodResult.NoCall) {
164+
mockMethodCall(scope, it.callee)
165+
return
166+
}
167+
} ?: observer?.onAssignStatement(exprResolver.simpleValueResolver, stmt, scope)
168+
169+
129170
val expr = exprResolver.resolve(stmt.rhv) ?: return
130171

131172
check(expr.sort != unresolvedSort) {
@@ -222,16 +263,33 @@ class TsInterpreter(
222263

223264
private fun visitCallStmt(scope: TsStepScope, stmt: EtsCallStmt) {
224265
val exprResolver = exprResolverWithScope(scope)
225-
exprResolver.resolve(stmt.expr) ?: return
226266

227267
scope.doWithState {
228-
val nextStmt = stmt.nextStmt ?: return@doWithState
229-
newStmt(nextStmt)
268+
if (methodResult is TsMethodResult.Success) {
269+
newStmt(applicationGraph.successors(stmt).single())
270+
methodResult = TsMethodResult.NoCall
271+
}
230272
}
273+
274+
if (tsOptions.interproceduralAnalysis) {
275+
exprResolver.resolve(stmt.expr) ?: return
276+
277+
scope.doWithState {
278+
val nextStmt = stmt.nextStmt ?: return@doWithState
279+
newStmt(nextStmt)
280+
}
281+
282+
return
283+
}
284+
285+
// intraprocedural analysis
286+
mockMethodCall(scope, stmt.method.signature)
231287
}
232288

233289
private fun visitThrowStmt(scope: TsStepScope, stmt: EtsThrowStmt) {
234290
// TODO do not forget to pop the sorts call stack in the state
291+
val exprResolver = exprResolverWithScope(scope)
292+
observer?.onThrowStatement(exprResolver.simpleValueResolver, stmt, scope)
235293
TODO()
236294
}
237295

@@ -307,6 +365,28 @@ class TsInterpreter(
307365
return state
308366
}
309367

368+
private fun mockMethodCall(scope: TsStepScope, method: EtsMethodSignature) {
369+
scope.doWithState {
370+
with(ctx) {
371+
val resultSort = typeToSort(method.returnType)
372+
val resultValue = when (resultSort) {
373+
is UAddressSort -> makeSymbolicRefUntyped()
374+
is TsUnresolvedSort -> {
375+
mkFakeValue(
376+
scope,
377+
makeSymbolicPrimitive(ctx.boolSort),
378+
makeSymbolicPrimitive(ctx.fp64Sort),
379+
makeSymbolicRefUntyped()
380+
)
381+
}
382+
383+
else -> makeSymbolicPrimitive(resultSort)
384+
}
385+
methodResult = TsMethodResult.Success.MockedCall(method, resultValue)
386+
}
387+
}
388+
}
389+
310390
// TODO: expand with interpreter implementation
311391
private val EtsStmt.nextStmt: EtsStmt?
312392
get() = applicationGraph.successors(this).firstOrNull()

usvm-ts/src/main/kotlin/org/usvm/machine/state/TsMethodResult.kt

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.usvm.machine.state
22

33
import org.jacodb.ets.model.EtsMethod
4+
import org.jacodb.ets.model.EtsMethodSignature
45
import org.jacodb.ets.model.EtsType
56
import org.usvm.UExpr
67
import org.usvm.UHeapRef
@@ -15,13 +16,26 @@ sealed interface TsMethodResult {
1516
*/
1617
object NoCall : TsMethodResult
1718

18-
/**
19-
* A [method] successfully returned a [value].
20-
*/
21-
class Success(
22-
val method: EtsMethod,
23-
val value: UExpr<out USort>,
24-
) : TsMethodResult
19+
sealed class Success(val value: UExpr<out USort>) : TsMethodResult {
20+
abstract fun methodSignature(): EtsMethodSignature
21+
22+
/**
23+
* A [method] successfully returned a [value].
24+
*/
25+
class RegularCall(
26+
val method: EtsMethod,
27+
value: UExpr<out USort>,
28+
) : Success(value) {
29+
override fun methodSignature(): EtsMethodSignature = method.signature
30+
}
31+
32+
class MockedCall(
33+
val methodSignature: EtsMethodSignature,
34+
value: UExpr<out USort>,
35+
) : Success(value) {
36+
override fun methodSignature(): EtsMethodSignature = methodSignature
37+
}
38+
}
2539

2640
/**
2741
* A method threw an exception with [type] type.

usvm-ts/src/main/kotlin/org/usvm/machine/state/TsStateUtils.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ fun TsState.returnValue(valueToReturn: UExpr<out USort>) {
2020
popLocalToSortStack()
2121
}
2222

23-
methodResult = TsMethodResult.Success(returnFromMethod, valueToReturn)
23+
methodResult = TsMethodResult.Success.RegularCall(returnFromMethod, valueToReturn)
2424

2525
if (returnSite != null) {
2626
newStmt(returnSite)

0 commit comments

Comments
 (0)