Skip to content

Commit 9f65a6e

Browse files
authored
Improve calls in TS machine (#273)
1 parent 4a98ec5 commit 9f65a6e

File tree

16 files changed

+984
-231
lines changed

16 files changed

+984
-231
lines changed

buildSrc/src/main/kotlin/usvm.kotlin-conventions.gradle.kts

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ tasks.named<Test>("test") {
4848
// Use JUnit Platform for unit tests.
4949
useJUnitPlatform()
5050

51-
maxHeapSize = "1G"
51+
maxHeapSize = "4G"
5252

5353
testLogging {
5454
events("passed")

usvm-ts/src/main/kotlin/org/usvm/api/checkers/UnreachableCodeDetector.kt

-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import org.usvm.machine.expr.TsSimpleValueResolver
88
import org.usvm.machine.interpreter.TsStepScope
99
import org.usvm.machine.state.TsState
1010
import org.usvm.statistics.UMachineObserver
11-
import kotlin.collections.filter
12-
import kotlin.collections.isNotEmpty
1311

1412
data class UncoveredIfSuccessors(val ifStmt: EtsIfStmt, val successors: Set<EtsStmt>)
1513

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

+56-45
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.usvm.machine
33
import io.ksmt.sort.KFp64Sort
44
import io.ksmt.utils.asExpr
55
import org.jacodb.ets.model.EtsAnyType
6+
import org.jacodb.ets.model.EtsArrayType
67
import org.jacodb.ets.model.EtsBooleanType
78
import org.jacodb.ets.model.EtsNullType
89
import org.jacodb.ets.model.EtsNumberType
@@ -28,7 +29,7 @@ import org.usvm.machine.expr.TsUndefinedSort
2829
import org.usvm.machine.expr.TsUnresolvedSort
2930
import org.usvm.machine.interpreter.TsStepScope
3031
import org.usvm.machine.types.FakeType
31-
import org.usvm.types.UTypeStream
32+
import org.usvm.memory.UReadOnlyMemory
3233
import org.usvm.types.single
3334
import org.usvm.util.mkFieldLValue
3435
import kotlin.contracts.ExperimentalContracts
@@ -48,42 +49,51 @@ class TsContext(
4849
* In TS we treat undefined value as a null reference in other objects.
4950
* For real null represented in the language we create another reference.
5051
*/
51-
private val undefinedValue: UExpr<UAddressSort> = mkNullRef()
52-
fun mkUndefinedValue(): UExpr<UAddressSort> = undefinedValue
52+
private val undefinedValue: UHeapRef = mkNullRef()
53+
fun mkUndefinedValue(): UHeapRef = undefinedValue
5354

5455
private val nullValue: UConcreteHeapRef = mkConcreteHeapRef(addressCounter.freshStaticAddress())
5556
fun mkTsNullValue(): UConcreteHeapRef = nullValue
5657

5758
fun typeToSort(type: EtsType): USort = when (type) {
5859
is EtsBooleanType -> boolSort
5960
is EtsNumberType -> fp64Sort
60-
is EtsRefType -> addressSort
6161
is EtsNullType -> addressSort
6262
is EtsUndefinedType -> addressSort
63-
is EtsUnknownType -> unresolvedSort
6463
is EtsUnionType -> unresolvedSort
64+
is EtsRefType -> addressSort
6565
is EtsAnyType -> unresolvedSort
66-
else -> TODO("Support all JacoDB types, encountered $type")
66+
is EtsUnknownType -> unresolvedSort
67+
else -> TODO("${type::class.simpleName} is not yet supported: $type")
6768
}
6869

69-
fun UHeapRef.getTypeStream(scope: TsStepScope): UTypeStream<EtsType> =
70-
scope.calcOnState {
71-
memory.typeStreamOf(this@getTypeStream)
72-
}
70+
// TODO: for now, ALL descriptors for array are UNKNOWN
71+
// in order to make ALL reading/writing, including '.length' access consistent
72+
// and possible in cases when the array type is not known.
73+
// For example, when we access '.length' of some array, we do not care about its type,
74+
// but we HAVE TO use some type consistent with the type used when this array was created.
75+
// Note: Using UnknownType everywhere does not lead to any errors yet,
76+
// since we do not rely on array types in any way.
77+
fun arrayDescriptorOf(type: EtsArrayType): EtsType = EtsUnknownType
78+
79+
fun UConcreteHeapRef.getFakeType(memory: UReadOnlyMemory<*>): FakeType {
80+
check(isFakeObject())
81+
return memory.typeStreamOf(this).single() as FakeType
82+
}
7383

7484
fun UConcreteHeapRef.getFakeType(scope: TsStepScope): FakeType =
75-
getTypeStream(scope).single() as FakeType
85+
scope.calcOnState { getFakeType(memory) }
7686

7787
@OptIn(ExperimentalContracts::class)
78-
fun UExpr<out USort>.isFakeObject(): Boolean {
88+
fun UExpr<*>.isFakeObject(): Boolean {
7989
contract {
8090
returns(true) implies (this@isFakeObject is UConcreteHeapRef)
8191
}
8292

8393
return sort == addressSort && this is UConcreteHeapRef && address > MAGIC_OFFSET
8494
}
8595

86-
fun UExpr<out USort>.toFakeObject(scope: TsStepScope): UConcreteHeapRef {
96+
fun UExpr<*>.toFakeObject(scope: TsStepScope): UConcreteHeapRef {
8797
if (isFakeObject()) {
8898
return this
8999
}
@@ -94,20 +104,20 @@ class TsContext(
94104
when (sort) {
95105
boolSort -> {
96106
val lvalue = getIntermediateBoolLValue(ref.address)
97-
memory.write(lvalue, this@toFakeObject.asExpr(boolSort), guard = trueExpr)
98-
memory.types.allocate(ref.address, FakeType.fromBool(this@TsContext))
107+
memory.write(lvalue, asExpr(boolSort), guard = trueExpr)
108+
memory.types.allocate(ref.address, FakeType.mkBool(this@TsContext))
99109
}
100110

101111
fp64Sort -> {
102112
val lValue = getIntermediateFpLValue(ref.address)
103-
memory.write(lValue, this@toFakeObject.asExpr(fp64Sort), guard = trueExpr)
104-
memory.types.allocate(ref.address, FakeType.fromFp(this@TsContext))
113+
memory.write(lValue, asExpr(fp64Sort), guard = trueExpr)
114+
memory.types.allocate(ref.address, FakeType.mkFp(this@TsContext))
105115
}
106116

107117
addressSort -> {
108118
val lValue = getIntermediateRefLValue(ref.address)
109-
memory.write(lValue, this@toFakeObject.asExpr(addressSort), guard = trueExpr)
110-
memory.types.allocate(ref.address, FakeType.fromRef(this@TsContext))
119+
memory.write(lValue, asExpr(addressSort), guard = trueExpr)
120+
memory.types.allocate(ref.address, FakeType.mkRef(this@TsContext))
111121
}
112122

113123
else -> TODO("Not yet supported")
@@ -117,29 +127,15 @@ class TsContext(
117127
return ref
118128
}
119129

120-
fun UExpr<out USort>.extractSingleValueFromFakeObjectOrNull(scope: TsStepScope): UExpr<out USort>? {
130+
fun UExpr<*>.extractSingleValueFromFakeObjectOrNull(scope: TsStepScope): UExpr<*>? {
121131
if (!isFakeObject()) return null
122132

123133
val type = getFakeType(scope)
124-
return scope.calcOnState {
125-
when {
126-
type.boolTypeExpr.isTrue -> {
127-
val lValue = getIntermediateBoolLValue(address)
128-
memory.read(lValue).asExpr(boolSort)
129-
}
130-
131-
type.fpTypeExpr.isTrue -> {
132-
val lValue = getIntermediateFpLValue(address)
133-
memory.read(lValue).asExpr(fp64Sort)
134-
}
135-
136-
type.refTypeExpr.isTrue -> {
137-
val lValue = getIntermediateRefLValue(address)
138-
memory.read(lValue).asExpr(addressSort)
139-
}
140-
141-
else -> null
142-
}
134+
return when {
135+
type.boolTypeExpr.isTrue -> extractBool(scope)
136+
type.fpTypeExpr.isTrue -> extractFp(scope)
137+
type.refTypeExpr.isTrue -> extractRef(scope)
138+
else -> null
143139
}
144140
}
145141

@@ -163,19 +159,34 @@ class TsContext(
163159
return mkFieldLValue(addressSort, mkConcreteHeapRef(addr), IntermediateLValueField.REF)
164160
}
165161

166-
fun UConcreteHeapRef.extractBool(scope: TsStepScope): UBoolExpr {
162+
fun UConcreteHeapRef.extractBool(memory: UReadOnlyMemory<*>): UBoolExpr {
163+
check(isFakeObject())
167164
val lValue = getIntermediateBoolLValue(address)
168-
return scope.calcOnState { memory.read(lValue) }
165+
return memory.read(lValue)
169166
}
170167

171-
fun UConcreteHeapRef.extractFp(scope: TsStepScope): UExpr<KFp64Sort> {
168+
fun UConcreteHeapRef.extractFp(memory: UReadOnlyMemory<*>): UExpr<KFp64Sort> {
169+
check(isFakeObject())
172170
val lValue = getIntermediateFpLValue(address)
173-
return scope.calcOnState { memory.read(lValue) }
171+
return memory.read(lValue)
174172
}
175173

176-
fun UConcreteHeapRef.extractRef(scope: TsStepScope): UHeapRef {
174+
fun UConcreteHeapRef.extractRef(memory: UReadOnlyMemory<*>): UHeapRef {
175+
check(isFakeObject())
177176
val lValue = getIntermediateRefLValue(address)
178-
return scope.calcOnState { memory.read(lValue) }
177+
return memory.read(lValue)
178+
}
179+
180+
fun UConcreteHeapRef.extractBool(scope: TsStepScope): UBoolExpr {
181+
return scope.calcOnState { extractBool(memory) }
182+
}
183+
184+
fun UConcreteHeapRef.extractFp(scope: TsStepScope): UExpr<KFp64Sort> {
185+
return scope.calcOnState { extractFp(memory) }
186+
}
187+
188+
fun UConcreteHeapRef.extractRef(scope: TsStepScope): UHeapRef {
189+
return scope.calcOnState { extractRef(memory) }
179190
}
180191
}
181192

usvm-ts/src/main/kotlin/org/usvm/machine/TsApplicationGraph.kt renamed to usvm-ts/src/main/kotlin/org/usvm/machine/TsGraph.kt

+16-9
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,36 @@ import org.usvm.dataflow.ts.graph.EtsApplicationGraph
77
import org.usvm.dataflow.ts.graph.EtsApplicationGraphImpl
88
import org.usvm.statistics.ApplicationGraph
99

10-
class TsApplicationGraph(scene: EtsScene) : ApplicationGraph<EtsMethod, EtsStmt> {
11-
private val applicationGraph: EtsApplicationGraph = EtsApplicationGraphImpl(scene)
10+
class TsGraph(scene: EtsScene) : ApplicationGraph<EtsMethod, EtsStmt> {
11+
private val etsGraph: EtsApplicationGraph = EtsApplicationGraphImpl(scene)
12+
13+
val cp: EtsScene
14+
get() = etsGraph.cp
1215

1316
override fun predecessors(node: EtsStmt): Sequence<EtsStmt> =
14-
applicationGraph.predecessors(node)
17+
etsGraph.predecessors(node)
1518

1619
override fun successors(node: EtsStmt): Sequence<EtsStmt> =
17-
applicationGraph.successors(node)
20+
if (node is TsMethodCall) {
21+
etsGraph.successors(node.returnSite)
22+
} else {
23+
etsGraph.successors(node)
24+
}
1825

1926
override fun callees(node: EtsStmt): Sequence<EtsMethod> =
20-
applicationGraph.callees(node)
27+
etsGraph.callees(node)
2128

2229
override fun callers(method: EtsMethod): Sequence<EtsStmt> =
23-
applicationGraph.callers(method)
30+
etsGraph.callers(method)
2431

2532
override fun entryPoints(method: EtsMethod): Sequence<EtsStmt> =
26-
applicationGraph.entryPoints(method)
33+
etsGraph.entryPoints(method)
2734

2835
override fun exitPoints(method: EtsMethod): Sequence<EtsStmt> =
29-
applicationGraph.exitPoints(method)
36+
etsGraph.exitPoints(method)
3037

3138
override fun methodOf(node: EtsStmt): EtsMethod =
32-
applicationGraph.methodOf(node)
39+
etsGraph.methodOf(node)
3340

3441
override fun statementsOf(method: EtsMethod): Sequence<EtsStmt> =
3542
method.cfg.stmts.asSequence()

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

+12-12
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ class TsMachine(
3535
private val typeSystem = TsTypeSystem(typeOperationsTimeout = 1.seconds, project)
3636
private val components = TsComponents(typeSystem, options)
3737
private val ctx = TsContext(project, components)
38-
private val applicationGraph = TsApplicationGraph(project)
39-
private val interpreter = TsInterpreter(ctx, applicationGraph, tsOptions, observer)
40-
private val cfgStatistics = CfgStatisticsImpl(applicationGraph)
38+
private val graph = TsGraph(project)
39+
private val interpreter = TsInterpreter(ctx, graph, tsOptions, observer)
40+
private val cfgStatistics = CfgStatisticsImpl(graph)
4141

4242
fun analyze(
4343
methods: List<EtsMethod>,
@@ -53,8 +53,8 @@ class TsMachine(
5353
}
5454

5555
val coverageStatistics = CoverageStatistics<EtsMethod, EtsStmt, TsState>(
56-
methodsToTrackCoverage,
57-
applicationGraph
56+
methods = methodsToTrackCoverage,
57+
applicationGraph = graph,
5858
)
5959

6060
val callGraphStatistics: PlainCallGraphStatistics<EtsMethod> =
@@ -66,13 +66,13 @@ class TsMachine(
6666
val timeStatistics = TimeStatistics<EtsMethod, TsState>()
6767

6868
val pathSelector = createPathSelector(
69-
initialStates,
70-
options,
71-
applicationGraph,
72-
timeStatistics,
73-
{ coverageStatistics },
74-
{ cfgStatistics },
75-
{ callGraphStatistics },
69+
initialStates = initialStates,
70+
options = options,
71+
applicationGraph = graph,
72+
timeStatistics = timeStatistics,
73+
coverageStatisticsFactory = { coverageStatistics },
74+
cfgStatisticsFactory = { cfgStatistics },
75+
callGraphStatisticsFactory = { callGraphStatistics },
7676
)
7777

7878
val statesCollector =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package org.usvm.machine
2+
3+
import org.jacodb.ets.model.EtsMethod
4+
import org.jacodb.ets.model.EtsMethodSignature
5+
import org.jacodb.ets.model.EtsStmt
6+
import org.jacodb.ets.model.EtsStmtLocation
7+
import org.usvm.UExpr
8+
9+
sealed interface TsMethodCall : EtsStmt {
10+
val instance: UExpr<*>?
11+
val args: List<UExpr<*>>
12+
val returnSite: EtsStmt
13+
14+
override val location: EtsStmtLocation
15+
get() = returnSite.location
16+
17+
override fun <R> accept(visitor: EtsStmt.Visitor<R>): R {
18+
error("Auxiliary instruction")
19+
}
20+
}
21+
22+
class TsVirtualMethodCallStmt(
23+
val callee: EtsMethodSignature,
24+
override val instance: UExpr<*>?,
25+
override val args: List<UExpr<*>>,
26+
override val returnSite: EtsStmt,
27+
) : TsMethodCall {
28+
override fun toString(): String {
29+
return "virtual ${callee.enclosingClass.name}::${callee.name}"
30+
}
31+
32+
fun toConcrete(callee: EtsMethod): TsConcreteMethodCallStmt {
33+
return TsConcreteMethodCallStmt(callee, instance, args, returnSite)
34+
}
35+
}
36+
37+
// Note: `args` are resolved, but not yet truncated (if more than necessary),
38+
// and not wrapped in array (if calling a vararg method)
39+
class TsConcreteMethodCallStmt(
40+
val callee: EtsMethod,
41+
override val instance: UExpr<*>?,
42+
override val args: List<UExpr<*>>,
43+
override val returnSite: EtsStmt,
44+
) : TsMethodCall {
45+
override fun toString(): String {
46+
return "concrete ${callee.signature.enclosingClass.name}::${callee.name}"
47+
}
48+
}

0 commit comments

Comments
 (0)