Skip to content

Commit b41bb3d

Browse files
CaelmBleiddLipen
andauthored
[TS] Fixes with arrays (#333)
Co-authored-by: Konstantin Chukharev <lipen00@gmail.com>
1 parent 42e0308 commit b41bb3d

File tree

11 files changed

+79
-13
lines changed

11 files changed

+79
-13
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ package org.usvm.machine
33
data class TsOptions(
44
val interproceduralAnalysis: Boolean = true,
55
val enableVisualization: Boolean = false,
6+
val maxArraySize: Int = 1_000,
67
)

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,16 @@ fun TsContext.checkReadingInRange(
248248
blockOnFalseState = { throwException("Index out of bounds: $index, length: $length") }
249249
)
250250
}
251+
252+
fun TsContext.checkLengthBounds(
253+
scope: TsStepScope,
254+
length: UExpr<TsSizeSort>,
255+
maxLength: Int,
256+
): Unit? {
257+
// Check that length is non-negative and does not exceed `maxLength`.
258+
val condition = mkAnd(
259+
mkBvSignedGreaterOrEqualExpr(length, mkBv(0)),
260+
mkBvSignedLessOrEqualExpr(length, mkBv(maxLength))
261+
)
262+
return scope.assert(condition)
263+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ internal fun TsExprResolver.handleInstanceFieldRef(
4949

5050
// Handle reading "length" property.
5151
if (value.field.name == "length") {
52-
return readLengthProperty(scope, instanceLocal, instance)
52+
return readLengthProperty(scope, instanceLocal, instance, options.maxArraySize)
5353
}
5454

5555
// Read the field.

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ fun TsContext.readLengthProperty(
1818
scope: TsStepScope,
1919
instanceLocal: EtsLocal,
2020
instance: UHeapRef,
21-
): UExpr<*> {
21+
maxArraySize: Int,
22+
): UExpr<*>? {
2223
// Determine the array type.
2324
val arrayType: EtsArrayType = when (val type = instanceLocal.type) {
2425
is EtsArrayType -> type
@@ -33,15 +34,16 @@ fun TsContext.readLengthProperty(
3334
}
3435

3536
// Read the length of the array.
36-
return readArrayLength(scope, instance, arrayType)
37+
return readArrayLength(scope, instance, arrayType, maxArraySize)
3738
}
3839

3940
// Reads the length of the array and returns it as a fp64 expression.
4041
fun TsContext.readArrayLength(
4142
scope: TsStepScope,
4243
array: UHeapRef,
4344
arrayType: EtsArrayType,
44-
): UExpr<KFp64Sort> {
45+
maxArraySize: Int,
46+
): UExpr<KFp64Sort>? {
4547
checkNotFake(array)
4648

4749
// Read the length of the array.
@@ -50,10 +52,8 @@ fun TsContext.readArrayLength(
5052
memory.read(lengthLValue)
5153
}
5254

53-
// Ensure that the length is non-negative.
54-
scope.doWithState {
55-
pathConstraints += mkBvSignedGreaterOrEqualExpr(length, mkBv(0))
56-
}
55+
// Check that the length is within the allowed bounds.
56+
checkLengthBounds(scope, length, maxArraySize) ?: return null
5757

5858
// Convert the length to fp64.
5959
return mkBvToFpExpr(

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ import org.usvm.dataflow.ts.util.type
9090
import org.usvm.isAllocatedConcreteHeapRef
9191
import org.usvm.machine.TsConcreteMethodCallStmt
9292
import org.usvm.machine.TsContext
93+
import org.usvm.machine.TsOptions
9394
import org.usvm.machine.interpreter.PromiseState
9495
import org.usvm.machine.interpreter.TsStepScope
9596
import org.usvm.machine.interpreter.getGlobals
@@ -133,6 +134,7 @@ private const val ECMASCRIPT_BITWISE_SHIFT_MASK = 0b11111
133134
class TsExprResolver(
134135
internal val ctx: TsContext,
135136
internal val scope: TsStepScope,
137+
internal val options: TsOptions,
136138
internal val hierarchy: EtsHierarchy,
137139
) : EtsEntity.Visitor<UExpr<out USort>?> {
138140

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ import org.usvm.types.TypesResult
7575
import org.usvm.types.first
7676
import org.usvm.types.single
7777
import org.usvm.util.mkArrayIndexLValue
78+
import org.usvm.util.mkArrayLengthLValue
7879
import org.usvm.util.mkFieldLValue
7980
import org.usvm.util.mkRegisterStackLValue
8081
import org.usvm.util.resolveEtsMethods
@@ -89,7 +90,7 @@ typealias TsStepScope = StepScope<TsState, EtsType, EtsStmt, TsContext>
8990
class TsInterpreter(
9091
private val ctx: TsContext,
9192
private val graph: TsGraph,
92-
private val tsOptions: TsOptions,
93+
private val options: TsOptions,
9394
private val observer: TsInterpreterObserver? = null,
9495
) : UInterpreter<TsState>() {
9596

@@ -603,7 +604,7 @@ class TsInterpreter(
603604
}
604605
}
605606

606-
if (!tsOptions.interproceduralAnalysis && methodResult == TsMethodResult.NoCall) {
607+
if (!options.interproceduralAnalysis && methodResult == TsMethodResult.NoCall) {
607608
mockMethodCall(scope, callExpr.callee)
608609
scope.doWithState { newStmt(stmt) }
609610
return
@@ -637,7 +638,7 @@ class TsInterpreter(
637638
return
638639
}
639640

640-
if (tsOptions.interproceduralAnalysis) {
641+
if (options.interproceduralAnalysis) {
641642
val exprResolver = exprResolverWithScope(scope)
642643
exprResolver.resolve(stmt.expr) ?: return
643644
val nextStmt = stmt.nextStmt ?: return
@@ -696,6 +697,7 @@ class TsInterpreter(
696697
TsExprResolver(
697698
ctx = ctx,
698699
scope = scope,
700+
options = options,
699701
hierarchy = graph.hierarchy,
700702
)
701703

@@ -738,7 +740,13 @@ class TsInterpreter(
738740
state.pathConstraints += mkNot(mkHeapRefEq(ref, mkUndefinedValue()))
739741

740742
if (parameterType is EtsArrayType) {
741-
state.pathConstraints += state.memory.types.evalTypeEquals(ref, parameterType)
743+
state.pathConstraints += state.memory.types.evalIsSubtype(ref, parameterType)
744+
745+
val lengthLValue = mkArrayLengthLValue(ref, parameterType)
746+
val length = state.memory.read(lengthLValue).asExpr(sizeSort)
747+
state.pathConstraints += mkBvSignedGreaterOrEqualExpr(length, mkBv(0))
748+
state.pathConstraints += mkBvSignedLessOrEqualExpr(length, mkBv(options.maxArraySize))
749+
742750
return@run
743751
}
744752

usvm-ts/src/test/kotlin/org/usvm/samples/arrays/ArrayMethods.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,24 @@ class ArrayMethods : TsMethodTestRunner() {
2525
)
2626
}
2727

28+
@Test
29+
fun testArrayPushIntoNumber() {
30+
val method = getMethod("arrayPushIntoNumber")
31+
discoverProperties<TsTestValue.TsArray<*>, TsTestValue.TsArray<*>>(
32+
method = method,
33+
{ x, r -> x.values.size == r.values.size - 1 && (r.values.last() as TsTestValue.TsNumber).number == 123.0 }
34+
)
35+
}
36+
37+
@Test
38+
fun testArrayPushIntoUnknown() {
39+
val method = getMethod("arrayPushIntoUnknown")
40+
discoverProperties<TsTestValue.TsArray<*>, TsTestValue.TsArray<*>>(
41+
method = method,
42+
{ x, r -> x.values.size == r.values.size - 1 && (r.values.last() as TsTestValue.TsNumber).number == 123.0 }
43+
)
44+
}
45+
2846
@Test
2947
fun testArrayPop() {
3048
val method = getMethod("arrayPop")

usvm-ts/src/test/kotlin/org/usvm/samples/arrays/InputArrays.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.usvm.samples.arrays
22

33
import org.jacodb.ets.model.EtsScene
4+
import org.junit.jupiter.api.RepeatedTest
45
import org.junit.jupiter.api.Test
56
import org.usvm.api.TsTestValue
67
import org.usvm.util.TsMethodTestRunner
@@ -171,4 +172,13 @@ class InputArrays : TsMethodTestRunner() {
171172
},
172173
)
173174
}
175+
176+
@RepeatedTest(10)
177+
fun `test getLength`() {
178+
val method = getMethod("getLength")
179+
discoverProperties<TsTestValue.TsArray<TsTestValue>, TsTestValue.TsNumber>(
180+
method = method,
181+
{ x, r -> (r eq x.values.size) },
182+
)
183+
}
174184
}

usvm-ts/src/test/kotlin/org/usvm/util/TsTestResolver.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ open class TsTestStateResolver(
308308
"Other sorts must be resolved above, but got: $sort"
309309
}
310310

311-
val lValue = mkArrayIndexLValue(sort, concreteRef, index, type)
311+
val lValue = mkArrayIndexLValue(sort, heapRef, index, type)
312312
val value = memory.read(lValue)
313313

314314
if (value.sort is UAddressSort) {

usvm-ts/src/test/resources/samples/arrays/ArrayMethods.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ class ArrayMethods {
1212
}
1313
}
1414

15+
arrayPushIntoNumber(x: number[]) {
16+
x.push(123);
17+
return x;
18+
}
19+
20+
arrayPushIntoUnknown(x: any[]) {
21+
x.push(123);
22+
return x;
23+
}
24+
1525
arrayPop(x: boolean) {
1626
const arr = [10, 20, 30];
1727
const lastElement = arr.pop();

0 commit comments

Comments
 (0)