@@ -5,6 +5,7 @@ import io.ksmt.utils.asExpr
55import mu.KotlinLogging
66import org.jacodb.ets.model.EtsAddExpr
77import org.jacodb.ets.model.EtsAndExpr
8+ import org.jacodb.ets.model.EtsAnyType
89import org.jacodb.ets.model.EtsArrayAccess
910import org.jacodb.ets.model.EtsArrayType
1011import org.jacodb.ets.model.EtsAwaitExpr
@@ -14,6 +15,7 @@ import org.jacodb.ets.model.EtsBitNotExpr
1415import org.jacodb.ets.model.EtsBitOrExpr
1516import org.jacodb.ets.model.EtsBitXorExpr
1617import org.jacodb.ets.model.EtsBooleanConstant
18+ import org.jacodb.ets.model.EtsBooleanType
1719import org.jacodb.ets.model.EtsCastExpr
1820import org.jacodb.ets.model.EtsConstant
1921import org.jacodb.ets.model.EtsDeleteExpr
@@ -44,6 +46,7 @@ import org.jacodb.ets.model.EtsNotExpr
4446import org.jacodb.ets.model.EtsNullConstant
4547import org.jacodb.ets.model.EtsNullishCoalescingExpr
4648import org.jacodb.ets.model.EtsNumberConstant
49+ import org.jacodb.ets.model.EtsNumberType
4750import org.jacodb.ets.model.EtsOrExpr
4851import org.jacodb.ets.model.EtsParameterRef
4952import org.jacodb.ets.model.EtsPostDecExpr
@@ -77,15 +80,16 @@ import org.jacodb.ets.utils.getDeclaredLocals
7780import org.usvm.UAddressSort
7881import org.usvm.UBoolExpr
7982import org.usvm.UBoolSort
83+ import org.usvm.UConcreteHeapRef
8084import org.usvm.UExpr
8185import org.usvm.UHeapRef
8286import org.usvm.USort
8387import org.usvm.api.allocateArray
8488import org.usvm.dataflow.ts.infer.tryGetKnownType
8589import org.usvm.dataflow.ts.util.type
86- import org.usvm.isTrue
8790import org.usvm.machine.TsConcreteMethodCallStmt
8891import org.usvm.machine.TsContext
92+ import org.usvm.machine.TsSizeSort
8993import org.usvm.machine.TsVirtualMethodCallStmt
9094import org.usvm.machine.interpreter.TsStepScope
9195import org.usvm.machine.interpreter.isInitialized
@@ -590,6 +594,9 @@ class TsExprResolver(
590594
591595 override fun visit (value : EtsArrayAccess ): UExpr <out USort >? = with (ctx) {
592596 val array = resolve(value.array)?.asExpr(addressSort) ? : return null
597+
598+ checkUndefinedOrNullPropertyRead(array) ? : return null
599+
593600 val index = resolve(value.index)?.asExpr(fp64Sort) ? : return null
594601 val bvIndex = mkFpToBvExpr(
595602 roundingMode = fpRoundingModeSortDefaultValue(),
@@ -598,20 +605,73 @@ class TsExprResolver(
598605 isSigned = true ,
599606 ).asExpr(sizeSort)
600607
601- val lValue = mkArrayIndexLValue(
602- sort = addressSort,
603- ref = array,
604- index = bvIndex,
605- type = value.array.type as EtsArrayType
606- )
607- val expr = scope.calcOnState { memory.read(lValue) }
608+ val arrayType = value.array.type as ? EtsArrayType
609+ ? : error(" Expected EtsArrayType, but got ${value.array.type} " )
610+ val sort = typeToSort(arrayType.elementType)
611+
612+ val lengthLValue = mkArrayLengthLValue(array, arrayType)
613+ val length = scope.calcOnState { memory.read(lengthLValue) }
614+
615+ checkNegativeIndexRead(bvIndex) ? : return null
616+ checkReadingInRange(bvIndex, length) ? : return null
617+
618+ val expr = if (sort is TsUnresolvedSort ) {
619+ // Concrete arrays with the unresolved sort should consist of fake objects only.
620+ if (array is UConcreteHeapRef ) {
621+ // Read a fake object from the array.
622+ val lValue = mkArrayIndexLValue(
623+ sort = addressSort,
624+ ref = array,
625+ index = bvIndex,
626+ type = arrayType
627+ )
628+
629+ scope.calcOnState { memory.read(lValue) }
630+ } else {
631+ // If the array is not concrete, we need to allocate a fake object
632+ val boolArrayType = EtsArrayType (EtsBooleanType , dimensions = 1 )
633+ val boolLValue = mkArrayIndexLValue(boolSort, array, bvIndex, boolArrayType)
634+
635+ val numberArrayType = EtsArrayType (EtsNumberType , dimensions = 1 )
636+ val fpLValue = mkArrayIndexLValue(fp64Sort, array, bvIndex, numberArrayType)
637+
638+ val unknownArrayType = EtsArrayType (EtsUnknownType , dimensions = 1 )
639+ val refLValue = mkArrayIndexLValue(addressSort, array, bvIndex, unknownArrayType)
640+
641+ scope.calcOnState {
642+ val boolValue = memory.read(boolLValue)
643+ val fpValue = memory.read(fpLValue)
644+ val refValue = memory.read(refLValue)
645+
646+ // Read an object from the memory at first,
647+ // we don't need to recreate it if it is already a fake object.
648+ val fakeObj = if (refValue.isFakeObject()) {
649+ refValue
650+ } else {
651+ mkFakeValue(scope, boolValue, fpValue, refValue).also {
652+ lValuesToAllocatedFakeObjects + = refLValue to it
653+ }
654+ }
655+
656+ memory.write(refLValue, fakeObj.asExpr(addressSort), guard = trueExpr)
608657
609- check(expr.isFakeObject()) { " Only fake objects are allowed in arrays" }
658+ fakeObj
659+ }
660+ }
661+ } else {
662+ val lValue = mkArrayIndexLValue(
663+ sort = sort,
664+ ref = array,
665+ index = bvIndex,
666+ type = arrayType
667+ )
668+ scope.calcOnState { memory.read(lValue) }
669+ }
610670
611671 return expr
612672 }
613673
614- private fun checkUndefinedOrNullPropertyRead (instance : UHeapRef ) = with (ctx) {
674+ fun checkUndefinedOrNullPropertyRead (instance : UHeapRef ) = with (ctx) {
615675 val neqNull = mkAnd(
616676 mkHeapRefEq(instance, mkUndefinedValue()).not (),
617677 mkHeapRefEq(instance, mkTsNullValue()).not ()
@@ -623,6 +683,24 @@ class TsExprResolver(
623683 )
624684 }
625685
686+ fun checkNegativeIndexRead (index : UExpr <TsSizeSort >) = with (ctx) {
687+ val condition = mkBvSignedGreaterOrEqualExpr(index, mkBv(0 ))
688+
689+ scope.fork(
690+ condition,
691+ blockOnFalseState = allocateException(EtsStringType ) // TODO incorrect exception type
692+ )
693+ }
694+
695+ fun checkReadingInRange (index : UExpr <TsSizeSort >, length : UExpr <TsSizeSort >) = with (ctx) {
696+ val condition = mkBvSignedLessExpr(index, length)
697+
698+ scope.fork(
699+ condition,
700+ blockOnFalseState = allocateException(EtsStringType ) // TODO incorrect exception type
701+ )
702+ }
703+
626704 private fun allocateException (type : EtsType ): (TsState ) -> Unit = { state ->
627705 val address = state.memory.allocConcrete(type)
628706 state.throwExceptionWithoutStackFrameDrop(address, type)
@@ -675,9 +753,10 @@ class TsExprResolver(
675753 val fakeRef = if (ref.isFakeObject()) {
676754 ref
677755 } else {
678- mkFakeValue(scope, bool, fp, ref)
756+ mkFakeValue(scope, bool, fp, ref).also {
757+ lValuesToAllocatedFakeObjects + = refLValue to it
758+ }
679759 }
680-
681760 memory.write(refLValue, fakeRef.asExpr(addressSort), guard = trueExpr)
682761
683762 fakeRef
@@ -696,32 +775,46 @@ class TsExprResolver(
696775 // TODO It is a hack for array's length
697776 if (value.field.name == " length" ) {
698777 if (value.instance.type is EtsArrayType ) {
699- val lengthLValue = mkArrayLengthLValue(instanceRef, value.instance.type as EtsArrayType )
778+ val arrayType = value.instance.type as EtsArrayType
779+ val lengthLValue = mkArrayLengthLValue(instanceRef, arrayType)
700780 val length = scope.calcOnState { memory.read(lengthLValue) }
781+ scope.doWithState { pathConstraints + = mkBvSignedGreaterOrEqualExpr(length, mkBv(0 )) }
782+
701783 return mkBvToFpExpr(fp64Sort, fpRoundingModeSortDefaultValue(), length.asExpr(sizeSort), signed = true )
702784 }
703785
704786 // TODO: handle "length" property for arrays inside fake objects
705787 if (instanceRef.isFakeObject()) {
706788 val fakeType = instanceRef.getFakeType(scope)
707- // TODO: replace '.isTrue' with fork or assert
708- if (fakeType.refTypeExpr.isTrue) {
709- val refLValue = getIntermediateRefLValue(instanceRef.address)
710- val obj = scope.calcOnState { memory.read(refLValue) }
711- // TODO: fix array type. It should be the same as the type used when "writing" the length.
712- // However, current value.instance typically has 'unknown' type, and the best we can do here is
713- // to pretend that this is an array-like object (with "array length", not just "length" field),
714- // and "cast" instance to "unknown[]". The same could be done for any length writes, making
715- // the array type (for length) consistent (unknown everywhere), but less precise.
716- val lengthLValue = mkArrayLengthLValue(obj, EtsArrayType (EtsUnknownType , 1 ))
717- val length = scope.calcOnState { memory.read(lengthLValue) }
718- return mkBvToFpExpr(
719- fp64Sort,
720- fpRoundingModeSortDefaultValue(),
721- length.asExpr(sizeSort),
722- signed = true
723- )
789+
790+ // If we want to get length from a fake object, we assume that it is an array.
791+ scope.doWithState { pathConstraints + = fakeType.refTypeExpr }
792+
793+ val refLValue = getIntermediateRefLValue(instanceRef.address)
794+ val obj = scope.calcOnState { memory.read(refLValue) }
795+
796+ val type = value.instance.type
797+ val arrayType = type as ? EtsArrayType ? : run {
798+ check(type is EtsAnyType || type is EtsUnknownType ) {
799+ " Expected EtsArrayType, EtsAnyType or EtsUnknownType, but got $type "
800+ }
801+
802+ // We don't know the type of the array, since it is a fake object
803+ // If we'd know the type, we would have used it instead of creating a fake object
804+ EtsArrayType (EtsUnknownType , dimensions = 1 )
724805 }
806+ val lengthLValue = mkArrayLengthLValue(obj, arrayType)
807+ val length = scope.calcOnState { memory.read(lengthLValue) }
808+
809+ scope.doWithState { pathConstraints + = mkBvSignedGreaterOrEqualExpr(length, mkBv(0 )) }
810+
811+ return mkBvToFpExpr(
812+ fp64Sort,
813+ fpRoundingModeSortDefaultValue(),
814+ length.asExpr(sizeSort),
815+ signed = true
816+ )
817+
725818 }
726819 }
727820
@@ -779,6 +872,12 @@ class TsExprResolver(
779872 }
780873
781874 override fun visit (expr : EtsNewArrayExpr ): UExpr <out USort >? = with (ctx) {
875+ val arrayType = expr.type
876+
877+ require(arrayType is EtsArrayType ) {
878+ " Expected EtsArrayType in newArrayExpr, but got ${arrayType::class .simpleName} "
879+ }
880+
782881 scope.calcOnState {
783882 val size = resolve(expr.size) ? : return @calcOnState null
784883
@@ -809,7 +908,10 @@ class TsExprResolver(
809908 blockOnFalseState = allocateException(EtsStringType ) // TODO incorrect exception type
810909 )
811910
812- val arrayType = EtsArrayType (EtsUnknownType , 1 ) // TODO: expr.type
911+ if (arrayType.elementType is EtsArrayType ) {
912+ TODO (" Multidimensional arrays are not supported yet, https://github.com/UnitTestBot/usvm/issues/287" )
913+ }
914+
813915 val address = memory.allocateArray(arrayType, sizeSort, bvSize)
814916
815917 address
0 commit comments