@@ -5,6 +5,7 @@ import io.ksmt.utils.asExpr
5
5
import mu.KotlinLogging
6
6
import org.jacodb.ets.model.EtsAddExpr
7
7
import org.jacodb.ets.model.EtsAndExpr
8
+ import org.jacodb.ets.model.EtsAnyType
8
9
import org.jacodb.ets.model.EtsArrayAccess
9
10
import org.jacodb.ets.model.EtsArrayType
10
11
import org.jacodb.ets.model.EtsAwaitExpr
@@ -14,6 +15,7 @@ import org.jacodb.ets.model.EtsBitNotExpr
14
15
import org.jacodb.ets.model.EtsBitOrExpr
15
16
import org.jacodb.ets.model.EtsBitXorExpr
16
17
import org.jacodb.ets.model.EtsBooleanConstant
18
+ import org.jacodb.ets.model.EtsBooleanType
17
19
import org.jacodb.ets.model.EtsCastExpr
18
20
import org.jacodb.ets.model.EtsConstant
19
21
import org.jacodb.ets.model.EtsDeleteExpr
@@ -44,6 +46,7 @@ import org.jacodb.ets.model.EtsNotExpr
44
46
import org.jacodb.ets.model.EtsNullConstant
45
47
import org.jacodb.ets.model.EtsNullishCoalescingExpr
46
48
import org.jacodb.ets.model.EtsNumberConstant
49
+ import org.jacodb.ets.model.EtsNumberType
47
50
import org.jacodb.ets.model.EtsOrExpr
48
51
import org.jacodb.ets.model.EtsParameterRef
49
52
import org.jacodb.ets.model.EtsPostDecExpr
@@ -77,15 +80,16 @@ import org.jacodb.ets.utils.getDeclaredLocals
77
80
import org.usvm.UAddressSort
78
81
import org.usvm.UBoolExpr
79
82
import org.usvm.UBoolSort
83
+ import org.usvm.UConcreteHeapRef
80
84
import org.usvm.UExpr
81
85
import org.usvm.UHeapRef
82
86
import org.usvm.USort
83
87
import org.usvm.api.allocateArray
84
88
import org.usvm.dataflow.ts.infer.tryGetKnownType
85
89
import org.usvm.dataflow.ts.util.type
86
- import org.usvm.isTrue
87
90
import org.usvm.machine.TsConcreteMethodCallStmt
88
91
import org.usvm.machine.TsContext
92
+ import org.usvm.machine.TsSizeSort
89
93
import org.usvm.machine.TsVirtualMethodCallStmt
90
94
import org.usvm.machine.interpreter.TsStepScope
91
95
import org.usvm.machine.interpreter.isInitialized
@@ -590,6 +594,9 @@ class TsExprResolver(
590
594
591
595
override fun visit (value : EtsArrayAccess ): UExpr <out USort >? = with (ctx) {
592
596
val array = resolve(value.array)?.asExpr(addressSort) ? : return null
597
+
598
+ checkUndefinedOrNullPropertyRead(array) ? : return null
599
+
593
600
val index = resolve(value.index)?.asExpr(fp64Sort) ? : return null
594
601
val bvIndex = mkFpToBvExpr(
595
602
roundingMode = fpRoundingModeSortDefaultValue(),
@@ -598,20 +605,74 @@ class TsExprResolver(
598
605
isSigned = true ,
599
606
).asExpr(sizeSort)
600
607
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)
608
634
609
- check(expr.isFakeObject()) { " Only fake objects are allowed in arrays" }
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)
657
+
658
+ fakeObj
659
+ }
660
+
661
+ }
662
+ } else {
663
+ val lValue = mkArrayIndexLValue(
664
+ sort = sort,
665
+ ref = array,
666
+ index = bvIndex,
667
+ type = arrayType
668
+ )
669
+ scope.calcOnState { memory.read(lValue) }
670
+ }
610
671
611
672
return expr
612
673
}
613
674
614
- private fun checkUndefinedOrNullPropertyRead (instance : UHeapRef ) = with (ctx) {
675
+ fun checkUndefinedOrNullPropertyRead (instance : UHeapRef ) = with (ctx) {
615
676
val neqNull = mkAnd(
616
677
mkHeapRefEq(instance, mkUndefinedValue()).not (),
617
678
mkHeapRefEq(instance, mkTsNullValue()).not ()
@@ -623,6 +684,24 @@ class TsExprResolver(
623
684
)
624
685
}
625
686
687
+ fun checkNegativeIndexRead (index : UExpr <TsSizeSort >) = with (ctx) {
688
+ val condition = mkBvSignedGreaterOrEqualExpr(index, mkBv(0 ))
689
+
690
+ scope.fork(
691
+ condition,
692
+ blockOnFalseState = allocateException(EtsStringType ) // TODO incorrect exception type
693
+ )
694
+ }
695
+
696
+ fun checkReadingInRange (index : UExpr <TsSizeSort >, length : UExpr <TsSizeSort >) = with (ctx) {
697
+ val condition = mkBvSignedLessExpr(index, length)
698
+
699
+ scope.fork(
700
+ condition,
701
+ blockOnFalseState = allocateException(EtsStringType ) // TODO incorrect exception type
702
+ )
703
+ }
704
+
626
705
private fun allocateException (type : EtsType ): (TsState ) -> Unit = { state ->
627
706
val address = state.memory.allocConcrete(type)
628
707
state.throwExceptionWithoutStackFrameDrop(address, type)
@@ -675,9 +754,8 @@ class TsExprResolver(
675
754
val fakeRef = if (ref.isFakeObject()) {
676
755
ref
677
756
} else {
678
- mkFakeValue(scope, bool, fp, ref)
757
+ mkFakeValue(scope, bool, fp, ref). also { lValuesToAllocatedFakeObjects + = refLValue to it }
679
758
}
680
-
681
759
memory.write(refLValue, fakeRef.asExpr(addressSort), guard = trueExpr)
682
760
683
761
fakeRef
@@ -696,32 +774,46 @@ class TsExprResolver(
696
774
// TODO It is a hack for array's length
697
775
if (value.field.name == " length" ) {
698
776
if (value.instance.type is EtsArrayType ) {
699
- val lengthLValue = mkArrayLengthLValue(instanceRef, value.instance.type as EtsArrayType )
777
+ val arrayType = value.instance.type as EtsArrayType
778
+ val lengthLValue = mkArrayLengthLValue(instanceRef, arrayType)
700
779
val length = scope.calcOnState { memory.read(lengthLValue) }
780
+ scope.doWithState { pathConstraints + = mkBvSignedGreaterOrEqualExpr(length, mkBv(0 )) }
781
+
701
782
return mkBvToFpExpr(fp64Sort, fpRoundingModeSortDefaultValue(), length.asExpr(sizeSort), signed = true )
702
783
}
703
784
704
785
// TODO: handle "length" property for arrays inside fake objects
705
786
if (instanceRef.isFakeObject()) {
706
787
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
- )
788
+
789
+ // If we want to get length from a fake object, we assume that it is an array.
790
+ scope.assert (fakeType.refTypeExpr)
791
+
792
+ val refLValue = getIntermediateRefLValue(instanceRef.address)
793
+ val obj = scope.calcOnState { memory.read(refLValue) }
794
+
795
+ val type = value.instance.type
796
+ val arrayType = type as ? EtsArrayType ? : run {
797
+ check(type is EtsAnyType || type is EtsUnknownType ) {
798
+ " Expected EtsArrayType, EtsAnyType or EtsUnknownType, but got $type "
799
+ }
800
+
801
+ // We don't know the type of the array, since it is a fake object
802
+ // If we'd know the type, we would have used it instead of creating a fake object
803
+ EtsArrayType (EtsUnknownType , dimensions = 1 )
724
804
}
805
+ val lengthLValue = mkArrayLengthLValue(obj, arrayType)
806
+ val length = scope.calcOnState { memory.read(lengthLValue) }
807
+
808
+ scope.doWithState { pathConstraints + = mkBvSignedGreaterOrEqualExpr(length, mkBv(0 )) }
809
+
810
+ return mkBvToFpExpr(
811
+ fp64Sort,
812
+ fpRoundingModeSortDefaultValue(),
813
+ length.asExpr(sizeSort),
814
+ signed = true
815
+ )
816
+
725
817
}
726
818
}
727
819
@@ -809,7 +901,12 @@ class TsExprResolver(
809
901
blockOnFalseState = allocateException(EtsStringType ) // TODO incorrect exception type
810
902
)
811
903
812
- val arrayType = EtsArrayType (EtsUnknownType , 1 ) // TODO: expr.type
904
+ val arrayType = expr.type as EtsArrayType
905
+
906
+ if (arrayType.elementType is EtsArrayType ) {
907
+ TODO (" Multidimensional arrays are not supported yet, https://github.com/UnitTestBot/usvm/issues/287" )
908
+ }
909
+
813
910
val address = memory.allocateArray(arrayType, sizeSort, bvSize)
814
911
815
912
address
0 commit comments