Skip to content

Commit d496174

Browse files
authored
Updated approximations (#314)
1 parent 94b2381 commit d496174

5 files changed

Lines changed: 180 additions & 1 deletion

File tree

usvm-core/src/main/kotlin/org/usvm/memory/HeapRefSplitting.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,25 @@ inline fun <R> foldHeapRefWithStaticAsSymbolic(
163163
blockOnSymbolic = blockOnSymbolic
164164
)
165165

166+
inline fun <R> foldHeapRefWithStaticAsConcrete(
167+
ref: UHeapRef,
168+
initial: R,
169+
initialGuard: UBoolExpr,
170+
ignoreNullRefs: Boolean = true,
171+
collapseHeapRefs: Boolean = true,
172+
blockOnConcrete: (R, GuardedExpr<UConcreteHeapRef>) -> R,
173+
blockOnSymbolic: (R, GuardedExpr<UHeapRef>) -> R,
174+
): R = foldHeapRef(
175+
ref,
176+
initial,
177+
initialGuard,
178+
ignoreNullRefs,
179+
collapseHeapRefs,
180+
staticIsConcrete = true,
181+
blockOnConcrete = blockOnConcrete,
182+
blockOnSymbolic = blockOnSymbolic
183+
)
184+
166185
inline fun <R> foldHeapRef2(
167186
ref0: UHeapRef,
168187
ref1: UHeapRef,

usvm-jvm/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ val `sample-approximations` by sourceSets.creating {
2424

2525
val approximations by configurations.creating
2626
val approximationsRepo = "com.github.UnitTestBot.java-stdlib-approximations"
27-
val approximationsVersion = "88c6be3469"
27+
val approximationsVersion = "aa9e358446"
2828

2929
dependencies {
3030
implementation(project(":usvm-core"))

usvm-jvm/src/main/kotlin/org/usvm/machine/JcApproximations.kt

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,19 +76,25 @@ import org.usvm.api.mapTypeStreamNotNull
7676
import org.usvm.api.memcpy
7777
import org.usvm.api.objectTypeEquals
7878
import org.usvm.api.objectTypeSubtype
79+
import org.usvm.api.readArrayIndex
80+
import org.usvm.api.readArrayLength
7981
import org.usvm.api.readField
8082
import org.usvm.api.writeField
8183
import org.usvm.collection.array.UArrayIndexLValue
8284
import org.usvm.collection.array.length.UArrayLengthLValue
8385
import org.usvm.collection.field.UFieldLValue
86+
import org.usvm.getIntValue
8487
import org.usvm.jvm.util.allInstanceFields
8588
import org.usvm.jvm.util.javaName
8689
import org.usvm.machine.interpreter.JcExprResolver
8790
import org.usvm.machine.interpreter.JcStepScope
8891
import org.usvm.machine.mocks.mockMethod
8992
import org.usvm.machine.state.JcState
9093
import org.usvm.machine.state.newStmt
94+
import org.usvm.machine.state.skipMethodInvocationAndBoxIfNeeded
9195
import org.usvm.machine.state.skipMethodInvocationWithValue
96+
import org.usvm.memory.foldHeapRefWithStaticAsConcrete
97+
import org.usvm.mkSizeExpr
9298
import org.usvm.sizeSort
9399
import org.usvm.types.first
94100
import org.usvm.types.singleOrNull
@@ -571,6 +577,84 @@ class JcMethodApproximationResolver(
571577
return false
572578
}
573579

580+
private fun JcState.arrayContentEquals(
581+
firstArray: UHeapRef,
582+
secondArray: UHeapRef,
583+
firstLength: UExpr<USizeSort>,
584+
secondLength: UExpr<USizeSort>,
585+
arrayType: JcArrayType,
586+
): UBoolExpr? = with(ctx) {
587+
val arrayDesciptor = arrayDescriptorOf(arrayType)
588+
val elementType = arrayType.elementType
589+
val elementSort = typeToSort(elementType)
590+
591+
val concreteLength =
592+
getIntValue(firstLength)
593+
?: getIntValue(secondLength)
594+
?: return@with null
595+
596+
val arrayEquals = List(concreteLength) {
597+
val idx = mkSizeExpr(it)
598+
val first = memory.readArrayIndex(firstArray, idx, arrayDesciptor, elementSort)
599+
val second =
600+
memory.readArrayIndex(secondArray, idx, arrayDesciptor, elementSort)
601+
mkEq(first, second)
602+
}
603+
604+
return@with mkAnd(arrayEquals)
605+
}
606+
607+
private fun JcState.arrayEquals(methodCall: JcMethodCall, firstArray: UHeapRef, secondArray: UHeapRef) = with(ctx) {
608+
val possibleElementTypes = primitiveTypes + cp.objectType
609+
val possibleArrayTypes = possibleElementTypes.map { cp.arrayTypeOf(it) }
610+
611+
val branches = mutableListOf<Pair<UBoolExpr, (JcState) -> Unit>>()
612+
var typeDiffersConstraint: UBoolExpr = trueExpr
613+
614+
val arrayRefsEqual = mkEq(firstArray, secondArray)
615+
val oneArrayIsNull = mkOr(mkEq(firstArray, nullRef), mkEq(secondArray, nullRef))
616+
branches += arrayRefsEqual to { state ->
617+
state.skipMethodInvocationAndBoxIfNeeded(methodCall, cp.boolean, trueExpr)
618+
}
619+
branches += mkAnd(mkNot(arrayRefsEqual), oneArrayIsNull) to { state ->
620+
state.skipMethodInvocationAndBoxIfNeeded(methodCall, cp.boolean, falseExpr)
621+
}
622+
val needToCheckContent = mkAnd(mkNot(arrayRefsEqual), mkNot(oneArrayIsNull))
623+
for (arrayType in possibleArrayTypes) {
624+
val typeConstraint = scope.calcOnState {
625+
mkAnd(
626+
memory.types.evalIsSubtype(firstArray, arrayType),
627+
memory.types.evalIsSubtype(secondArray, arrayType)
628+
)
629+
}
630+
typeDiffersConstraint = mkAnd(typeDiffersConstraint, mkNot(typeConstraint))
631+
val arrayDesciptor = arrayDescriptorOf(arrayType)
632+
val firstLength = memory.readArrayLength(firstArray, arrayDesciptor, sizeSort)
633+
val secondLength = memory.readArrayLength(secondArray, arrayDesciptor, sizeSort)
634+
val lengthsEqual = mkEq(firstLength, secondLength)
635+
636+
branches += mkAnd(needToCheckContent, typeConstraint, mkNot(lengthsEqual)) to { state ->
637+
state.skipMethodInvocationAndBoxIfNeeded(methodCall, cp.boolean, falseExpr)
638+
}
639+
640+
branches += mkAnd(needToCheckContent, typeConstraint, lengthsEqual) to { state ->
641+
val checkResult = state.arrayContentEquals(firstArray, secondArray, firstLength, secondLength, arrayType)
642+
if (checkResult == null) {
643+
// Unable to check
644+
state.skipMethodInvocationWithValue(methodCall, nullRef)
645+
} else {
646+
state.skipMethodInvocationAndBoxIfNeeded(methodCall, cp.boolean, checkResult)
647+
}
648+
}
649+
}
650+
651+
branches += typeDiffersConstraint to { state ->
652+
state.skipMethodInvocationAndBoxIfNeeded(methodCall, cp.boolean, falseExpr)
653+
}
654+
655+
scope.forkMulti(branches)
656+
}
657+
574658
private sealed interface StringConcatElement
575659
private data class StringConcatStrElement(val str: String) : StringConcatElement
576660
private data class StringConcatRefElement(val ref: UHeapRef) : StringConcatElement
@@ -956,6 +1040,19 @@ class JcMethodApproximationResolver(
9561040
val arg = it.arguments.single().asExpr(ctx.booleanSort)
9571041
scope.assert(arg)?.let { ctx.voidValue }
9581042
}
1043+
dispatchUsvmApiMethod(Engine::assumeSymbolic) {
1044+
val instance = it.arguments[0].asExpr(ctx.addressSort)
1045+
val condition = it.arguments[1].asExpr(ctx.booleanSort)
1046+
foldHeapRefWithStaticAsConcrete<Unit?>(
1047+
ref = instance,
1048+
initial = Unit,
1049+
initialGuard = ctx.trueExpr,
1050+
ignoreNullRefs = true,
1051+
collapseHeapRefs = true,
1052+
blockOnConcrete = { _, _ -> Unit },
1053+
blockOnSymbolic = { acc, ref -> scope.assert(ctx.mkImplies(ref.guard, condition)) ?: acc }
1054+
)?.let { ctx.voidValue }
1055+
}
9591056
dispatchUsvmApiMethod(Engine::makeSymbolicBoolean) {
9601057
scope.calcOnState { makeSymbolicPrimitive(ctx.booleanSort) }
9611058
}
@@ -1098,6 +1195,12 @@ class JcMethodApproximationResolver(
10981195
makeSymbolicArray(ctx.cp.objectType, sizeExpr)
10991196
}
11001197
}
1198+
dispatchUsvmApiMethod(Engine::arrayEquals) {
1199+
val first = it.arguments[0].asExpr(ctx.addressSort)
1200+
val second = it.arguments[1].asExpr(ctx.addressSort)
1201+
scope.doWithState { arrayEquals(it, first, second) }
1202+
null
1203+
}
11011204
dispatchMkList(Engine::makeSymbolicList) {
11021205
scope.calcOnState { mkSymbolicList(symbolicListType) }
11031206
}

usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcStateUtils.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package org.usvm.machine.state
22

3+
import org.jacodb.api.jvm.JcClassType
34
import org.jacodb.api.jvm.JcMethod
5+
import org.jacodb.api.jvm.JcPrimitiveType
46
import org.jacodb.api.jvm.JcType
57
import org.jacodb.api.jvm.cfg.JcArgument
68
import org.jacodb.api.jvm.cfg.JcDynamicCallExpr
79
import org.jacodb.api.jvm.cfg.JcInst
10+
import org.jacodb.api.jvm.ext.autoboxIfNeeded
811
import org.jacodb.api.jvm.ext.cfg.locals
12+
import org.jacodb.api.jvm.ext.findType
913
import org.usvm.UExpr
1014
import org.usvm.UHeapRef
1115
import org.usvm.USort
@@ -88,3 +92,29 @@ fun JcState.skipMethodInvocationWithValue(methodCall: JcMethodCall, value: UExpr
8892
methodResult = JcMethodResult.Success(methodCall.method, value)
8993
newStmt(methodCall.returnSite)
9094
}
95+
96+
fun JcState.skipMethodInvocationAndBoxIfNeeded(methodCall: JcMethodCall, valueType: JcType, value: UExpr<out USort>) {
97+
val typeSystem = ctx.typeSystem<JcType>()
98+
val methodReturnType = ctx.cp.findType(methodCall.method.returnType.typeName)
99+
when {
100+
valueType is JcPrimitiveType && methodReturnType is JcClassType -> {
101+
val boxedType = valueType.autoboxIfNeeded() as JcClassType
102+
check(typeSystem.isSupertype(methodReturnType, boxedType)) {
103+
"skipMethodInvocationAndBoxIfNeeded: Incorrect method return type"
104+
}
105+
val boxMethod = boxedType.declaredMethods.first {
106+
it.name == "valueOf" && it.isStatic && it.parameters.singleOrNull()?.type == valueType
107+
}
108+
methodResult = JcMethodResult.NoCall
109+
newStmt(JcConcreteMethodCallInst(methodCall.location, boxMethod.method, listOf(value), methodCall.returnSite))
110+
}
111+
112+
else -> {
113+
// TODO: implement unboxing if needed
114+
check(typeSystem.isSupertype(methodReturnType, valueType)) {
115+
"skipMethodInvocationAndBoxIfNeeded: Incorrect method return type"
116+
}
117+
skipMethodInvocationWithValue(methodCall, value)
118+
}
119+
}
120+
}

usvm-jvm/usvm-jvm-api/src/main/java/org/usvm/api/Engine.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,18 @@
55
import org.usvm.api.internal.SymbolicMapImpl;
66

77
import java.lang.reflect.Array;
8+
import java.util.Arrays;
89

910
public class Engine {
1011

1112
public static void assume(boolean expr) {
1213
assert expr;
1314
}
1415

16+
public static void assumeSymbolic(Object instance, boolean expr) {
17+
assert expr;
18+
}
19+
1520
@SuppressWarnings("unused")
1621
public static <T> T makeSymbolic(Class<T> clazz) {
1722
return null;
@@ -103,6 +108,28 @@ public static double[] makeSymbolicDoubleArray(int size) {
103108
return new double[size];
104109
}
105110

111+
public static Boolean arrayEquals(Object first, Object second) {
112+
if (first instanceof byte[] && second instanceof byte[])
113+
return Arrays.equals((byte[])first, (byte[])second);
114+
if (first instanceof char[] && second instanceof char[])
115+
return Arrays.equals((char[])first, (char[])second);
116+
if (first instanceof int[] && second instanceof int[])
117+
return Arrays.equals((int[])first, (int[])second);
118+
if (first instanceof long[] && second instanceof long[])
119+
return Arrays.equals((long[])first, (long[])second);
120+
if (first instanceof boolean[] && second instanceof boolean[])
121+
return Arrays.equals((boolean[])first, (boolean[])second);
122+
if (first instanceof float[] && second instanceof float[])
123+
return Arrays.equals((float[])first, (float[])second);
124+
if (first instanceof double[] && second instanceof double[])
125+
return Arrays.equals((double[])first, (double[])second);
126+
if (first instanceof short[] && second instanceof short[])
127+
return Arrays.equals((short[])first, (short[])second);
128+
if (first instanceof Object[] && second instanceof Object[])
129+
return Arrays.equals((Object[])first, (Object[])second);
130+
return false;
131+
}
132+
106133
public static <T> SymbolicList<T> makeSymbolicList() {
107134
return new SymbolicListImpl<>();
108135
}

0 commit comments

Comments
 (0)