Skip to content

Commit 3df3c83

Browse files
committed
[TS] Model the Array constructor
`new Array(n)` used to allocate an object of an unresolved class (8 occurrences on the maths corpus probe), so none of the array approximations applied to it and every subsequent operation on the array was mocked. Now: - `new Array` in EtsNewExpr allocates a genuine array object (EtsArrayType, length 0), so the array method approximations and the constructor handling apply to it; - the `constructor` call on an array-typed instance is approximated: the zero-argument form keeps the empty length, the single-argument form sets the length with the same size-validity fork as EtsNewArrayExpr (non-integral or negative sizes throw, as in JS); the multi-argument literal form is left to the logged mock fallback.
1 parent b68e7aa commit 3df3c83

2 files changed

Lines changed: 79 additions & 0 deletions

File tree

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

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import org.jacodb.ets.model.EtsMethodSignature
1111
import org.jacodb.ets.model.EtsUnknownType
1212
import org.jacodb.ets.utils.CONSTRUCTOR_NAME
1313
import org.usvm.UBoolExpr
14+
import org.usvm.UConcreteHeapRef
1415
import org.usvm.UExpr
1516
import org.usvm.USort
1617
import org.usvm.api.allocateConcreteRef
1718
import org.usvm.api.initializeArray
19+
import org.usvm.api.initializeArrayLength
1820
import org.usvm.api.makeSymbolicPrimitive
1921
import org.usvm.api.memcpy
2022
import org.usvm.api.typeStreamOf
@@ -120,6 +122,11 @@ internal fun TsExprResolver.tryApproximateInstanceCall(
120122
.takeIf { it !is TsUnresolvedSort }
121123
?: addressSort
122124

125+
// Handle the `Array` constructor: `new Array()` / `new Array(n)`
126+
if (expr.callee.name == CONSTRUCTOR_NAME) {
127+
return from(handleArrayConstructor(expr, instanceType))
128+
}
129+
123130
// Handle 'Array.push()' method calls
124131
if (expr.callee.name == "push") {
125132
return from(handleArrayPush(expr, instanceType, elementSort))
@@ -574,6 +581,66 @@ private fun TsExprResolver.handleArrayPop(
574581
*
575582
* https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.fill
576583
*/
584+
/**
585+
* The `Array` constructor. `new Array()` keeps the zero length set at allocation;
586+
* `new Array(n)` sets the length to `n`, forking on the validity of the size
587+
* (a non-integral or out-of-range `n` throws a RangeError in JS).
588+
* The multi-argument literal form `new Array(a, b, ...)` is not approximated.
589+
*/
590+
private fun TsExprResolver.handleArrayConstructor(
591+
expr: EtsInstanceCallExpr,
592+
arrayType: EtsArrayType,
593+
): UExpr<*>? = with(ctx) {
594+
val resolved = resolve(expr.instance)?.asExpr(addressSort) ?: return null
595+
val array = resolved as? UConcreteHeapRef ?: run {
596+
logger.warn { "Array constructor on a non-concrete instance is not approximated" }
597+
return null
598+
}
599+
600+
when (expr.args.size) {
601+
0 -> array
602+
603+
1 -> {
604+
val size = resolve(expr.args.single()) ?: return null
605+
if (size.sort != fp64Sort) {
606+
logger.warn { "Unsupported sort for the Array constructor size: ${size.sort}" }
607+
return null
608+
}
609+
val bvSize = mkFpToBvExpr(
610+
roundingMode = fpRoundingModeSortDefaultValue(),
611+
value = size.asExpr(fp64Sort),
612+
bvSize = 32,
613+
isSigned = true,
614+
)
615+
val isValidSize = mkAnd(
616+
mkEq(
617+
mkBvToFpExpr(
618+
sort = fp64Sort,
619+
roundingMode = fpRoundingModeSortDefaultValue(),
620+
value = bvSize,
621+
signed = true,
622+
),
623+
size.asExpr(fp64Sort),
624+
),
625+
mkBvSignedLessOrEqualExpr(mkBv(0), bvSize.asExpr(bv32Sort)),
626+
)
627+
scope.fork(
628+
isValidSize,
629+
blockOnFalseState = { throwException("Invalid array length: ${size.asExpr(fp64Sort)}") },
630+
) ?: return null
631+
scope.doWithState {
632+
memory.initializeArrayLength(array, arrayType, sizeSort, bvSize.asExpr(sizeSort))
633+
}
634+
array
635+
}
636+
637+
else -> {
638+
logger.warn { "The literal form of the Array constructor is not approximated: ${expr.args.size} args" }
639+
null
640+
}
641+
}
642+
}
643+
577644
private fun TsExprResolver.handleArrayFill(
578645
expr: EtsInstanceCallExpr,
579646
arrayType: EtsArrayType,

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,18 @@ class TsExprResolver(
973973
expr.type
974974
}
975975

976+
if (expr.type.typeName == "Array") {
977+
// `new Array(...)`: allocate a genuine array object so that the array
978+
// approximations apply; the length is set by the constructor call
979+
// that immediately follows the allocation.
980+
val arrayType = EtsArrayType(EtsUnknownType, dimensions = 1)
981+
return@with scope.calcOnState {
982+
val address = memory.allocConcrete(arrayType)
983+
memory.initializeArrayLength(address, arrayType, sizeSort, mkBv(0).asExpr(sizeSort))
984+
address
985+
}
986+
}
987+
976988
if (expr.type.typeName == "Boolean") {
977989
val clazz = scene.sdkClasses.filter { it.name == "Boolean" }.maxByOrNull { it.methods.size }
978990
?: error("No Boolean class found in SDK")

0 commit comments

Comments
 (0)