Skip to content

Commit 9a0b139

Browse files
authored
Different fixes (#278)
1 parent b9aa62b commit 9a0b139

18 files changed

Lines changed: 826 additions & 253 deletions

File tree

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

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.usvm.machine
22

33
import io.ksmt.sort.KFp64Sort
44
import io.ksmt.utils.asExpr
5+
import org.jacodb.ets.model.EtsAliasType
56
import org.jacodb.ets.model.EtsAnyType
67
import org.jacodb.ets.model.EtsArrayType
78
import org.jacodb.ets.model.EtsBooleanType
@@ -28,8 +29,10 @@ import org.usvm.collection.field.UFieldLValue
2829
import org.usvm.isTrue
2930
import org.usvm.machine.expr.TsUndefinedSort
3031
import org.usvm.machine.expr.TsUnresolvedSort
32+
import org.usvm.machine.expr.TsVoidSort
33+
import org.usvm.machine.expr.TsVoidValue
3134
import org.usvm.machine.interpreter.TsStepScope
32-
import org.usvm.machine.types.FakeType
35+
import org.usvm.machine.types.EtsFakeType
3336
import org.usvm.memory.UReadOnlyMemory
3437
import org.usvm.types.single
3538
import org.usvm.util.mkFieldLValue
@@ -46,6 +49,10 @@ class TsContext(
4649

4750
val unresolvedSort: TsUnresolvedSort = TsUnresolvedSort(this)
4851

52+
val voidSort: TsVoidSort by lazy { TsVoidSort(this) }
53+
val voidValue: TsVoidValue by lazy { TsVoidValue(this) }
54+
55+
4956
/**
5057
* In TS we treat undefined value as a null reference in other objects.
5158
* For real null represented in the language we create another reference.
@@ -66,6 +73,7 @@ class TsContext(
6673
is EtsRefType -> addressSort
6774
is EtsAnyType -> unresolvedSort
6875
is EtsUnknownType -> unresolvedSort
76+
is EtsAliasType -> typeToSort(type.originalType)
6977
else -> TODO("${type::class.simpleName} is not yet supported: $type")
7078
}
7179

@@ -78,12 +86,12 @@ class TsContext(
7886
// since we do not rely on array types in any way.
7987
fun arrayDescriptorOf(type: EtsArrayType): EtsType = EtsUnknownType
8088

81-
fun UConcreteHeapRef.getFakeType(memory: UReadOnlyMemory<*>): FakeType {
89+
fun UConcreteHeapRef.getFakeType(memory: UReadOnlyMemory<*>): EtsFakeType {
8290
check(isFakeObject())
83-
return memory.typeStreamOf(this).single() as FakeType
91+
return memory.typeStreamOf(this).single() as EtsFakeType
8492
}
8593

86-
fun UConcreteHeapRef.getFakeType(scope: TsStepScope): FakeType =
94+
fun UConcreteHeapRef.getFakeType(scope: TsStepScope): EtsFakeType =
8795
scope.calcOnState { getFakeType(memory) }
8896

8997
@OptIn(ExperimentalContracts::class)
@@ -107,19 +115,19 @@ class TsContext(
107115
boolSort -> {
108116
val lvalue = getIntermediateBoolLValue(ref.address)
109117
memory.write(lvalue, asExpr(boolSort), guard = trueExpr)
110-
memory.types.allocate(ref.address, FakeType.mkBool(this@TsContext))
118+
memory.types.allocate(ref.address, EtsFakeType.mkBool(this@TsContext))
111119
}
112120

113121
fp64Sort -> {
114122
val lValue = getIntermediateFpLValue(ref.address)
115123
memory.write(lValue, asExpr(fp64Sort), guard = trueExpr)
116-
memory.types.allocate(ref.address, FakeType.mkFp(this@TsContext))
124+
memory.types.allocate(ref.address, EtsFakeType.mkFp(this@TsContext))
117125
}
118126

119127
addressSort -> {
120128
val lValue = getIntermediateRefLValue(ref.address)
121129
memory.write(lValue, asExpr(addressSort), guard = trueExpr)
122-
memory.types.allocate(ref.address, FakeType.mkRef(this@TsContext))
130+
memory.types.allocate(ref.address, EtsFakeType.mkRef(this@TsContext))
123131
}
124132

125133
else -> TODO("Not yet supported")

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import org.usvm.api.makeSymbolicPrimitive
1010
import org.usvm.isFalse
1111
import org.usvm.machine.TsContext
1212
import org.usvm.machine.interpreter.TsStepScope
13+
import org.usvm.machine.types.EtsFakeType
1314
import org.usvm.machine.types.ExprWithTypeConstraint
14-
import org.usvm.machine.types.FakeType
1515
import org.usvm.types.single
1616
import org.usvm.util.boolToFp
1717

@@ -23,7 +23,7 @@ fun TsContext.mkTruthyExpr(
2323
val falseBranchGround = makeSymbolicPrimitive(boolSort)
2424

2525
val conjuncts = mutableListOf<ExprWithTypeConstraint<UBoolSort>>()
26-
val possibleType = memory.types.getTypeStream(expr.asExpr(addressSort)).single() as FakeType
26+
val possibleType = memory.types.getTypeStream(expr.asExpr(addressSort)).single() as EtsFakeType
2727

2828
scope.doWithState {
2929
pathConstraints += possibleType.mkExactlyOneTypeConstraint(this@mkTruthyExpr)

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

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ import org.jacodb.ets.model.EtsBitOrExpr
1515
import org.jacodb.ets.model.EtsBitXorExpr
1616
import org.jacodb.ets.model.EtsBooleanConstant
1717
import org.jacodb.ets.model.EtsCastExpr
18-
import org.jacodb.ets.model.EtsClassSignature
19-
import org.jacodb.ets.model.EtsClassType
2018
import org.jacodb.ets.model.EtsConstant
2119
import org.jacodb.ets.model.EtsDeleteExpr
2220
import org.jacodb.ets.model.EtsDivExpr
@@ -99,10 +97,13 @@ import org.usvm.machine.state.TsState
9997
import org.usvm.machine.state.lastStmt
10098
import org.usvm.machine.state.localsCount
10199
import org.usvm.machine.state.newStmt
102-
import org.usvm.machine.types.AuxiliaryType
100+
import org.usvm.machine.types.EtsAuxiliaryType
103101
import org.usvm.machine.types.mkFakeValue
104102
import org.usvm.memory.ULValue
105103
import org.usvm.sizeSort
104+
import org.usvm.util.EtsFieldResolutionResult
105+
import org.usvm.util.EtsHierarchy
106+
import org.usvm.util.createFakeField
106107
import org.usvm.util.isResolved
107108
import org.usvm.util.mkArrayIndexLValue
108109
import org.usvm.util.mkArrayLengthLValue
@@ -125,7 +126,8 @@ const val ADHOC_STRING__STRING = 2222.0 // 'string'
125126
class TsExprResolver(
126127
private val ctx: TsContext,
127128
private val scope: TsStepScope,
128-
private val localToIdx: (EtsMethod, EtsValue) -> Int,
129+
private val localToIdx: (EtsMethod, EtsValue) -> Int?,
130+
private val hierarchy: EtsHierarchy,
129131
) : EtsEntity.Visitor<UExpr<out USort>?> {
130132

131133
val simpleValueResolver: TsSimpleValueResolver =
@@ -630,24 +632,33 @@ class TsExprResolver(
630632
instance: EtsLocal?,
631633
instanceRef: UHeapRef,
632634
field: EtsFieldSignature,
635+
hierarchy: EtsHierarchy,
633636
): UExpr<out USort>? = with(ctx) {
634637
val resolvedAddr = if (instanceRef.isFakeObject()) instanceRef.extractRef(scope) else instanceRef
635638
scope.doWithState {
636-
pathConstraints += if (field.enclosingClass != EtsClassSignature.UNKNOWN) {
637-
// If we know an enclosing class of the field,
638-
// we can add a type constraint about the instance type.
639-
// Probably, it's redundant since either both class and field
640-
// know exactly their types or none of them.
641-
val type = EtsClassType(field.enclosingClass)
642-
memory.types.evalIsSubtype(resolvedAddr, type)
643-
} else {
644-
// Otherwise, we add a type constraint that every type containing such field is fine
645-
memory.types.evalIsSubtype(resolvedAddr, AuxiliaryType(setOf(field.name)))
646-
}
639+
// If we accessed some field, we make an assumption that
640+
// this field should present in the object.
641+
// That's not true in the common case for TS, but that's the decision we made.
642+
val auxiliaryType = EtsAuxiliaryType(properties = setOf(field.name))
643+
pathConstraints += memory.types.evalIsSubtype(resolvedAddr, auxiliaryType)
647644
}
648645

649-
val etsField = resolveEtsField(instance, field)
650-
val sort = typeToSort(etsField.type)
646+
val etsField = resolveEtsField(instance, field, hierarchy)
647+
648+
val sort = when (etsField) {
649+
is EtsFieldResolutionResult.Empty -> {
650+
logger.error("Field $etsField not found, creating fake field")
651+
// If we didn't find any real fields, let's create a fake one.
652+
// It is possible due to mistakes in the IR or if the field was added explicitly
653+
// in the code.
654+
// Probably, the right behaviour here is to fork the state.
655+
resolvedAddr.createFakeField(field.name, scope)
656+
addressSort
657+
}
658+
659+
is EtsFieldResolutionResult.Unique -> typeToSort(etsField.field.type)
660+
is EtsFieldResolutionResult.Ambiguous -> unresolvedSort
661+
}
651662

652663
if (sort == unresolvedSort) {
653664
val boolLValue = mkFieldLValue(boolSort, instanceRef, field)
@@ -714,7 +725,7 @@ class TsExprResolver(
714725
}
715726
}
716727

717-
return handleFieldRef(value.instance, instanceRef, value.field)
728+
return handleFieldRef(value.instance, instanceRef, value.field, hierarchy)
718729
}
719730

720731
override fun visit(value: EtsStaticFieldRef): UExpr<out USort>? = with(ctx) {
@@ -747,7 +758,7 @@ class TsExprResolver(
747758
}
748759
}
749760

750-
return handleFieldRef(instance = null, instanceRef, value.field)
761+
return handleFieldRef(instance = null, instanceRef, value.field, hierarchy)
751762
}
752763

753764
// endregion
@@ -811,14 +822,39 @@ class TsExprResolver(
811822
class TsSimpleValueResolver(
812823
private val ctx: TsContext,
813824
private val scope: TsStepScope,
814-
private val localToIdx: (EtsMethod, EtsValue) -> Int,
825+
private val localToIdx: (EtsMethod, EtsValue) -> Int?,
815826
) : EtsValue.Visitor<UExpr<out USort>?> {
816827

817828
private fun resolveLocal(local: EtsValue): ULValue<*, USort> {
818829
val currentMethod = scope.calcOnState { lastEnteredMethod }
819830
val entrypoint = scope.calcOnState { entrypoint }
820831

821832
val localIdx = localToIdx(currentMethod, local)
833+
834+
// If there is no local variable corresponding to the local,
835+
// we treat it as a field of some global object with the corresponding name.
836+
// It helps us to support global variables that are missed in the IR.
837+
if (localIdx == null) {
838+
require(local is EtsLocal)
839+
840+
val globalObject = scope.calcOnState { globalObject }
841+
842+
val localName = local.name
843+
// Check whether this local was already created or not
844+
if (localName in scope.calcOnState { addedArtificialLocals }) {
845+
return mkFieldLValue(ctx.addressSort, globalObject, local.name)
846+
}
847+
848+
logger.warn { "Cannot resolve local $local" }
849+
850+
globalObject.createFakeField(localName, scope)
851+
scope.doWithState {
852+
addedArtificialLocals += localName
853+
}
854+
855+
return mkFieldLValue(ctx.addressSort, globalObject, local.name)
856+
}
857+
822858
val sort = scope.calcOnState {
823859
val type = local.tryGetKnownType(currentMethod)
824860
getOrPutSortForLocal(localIdx, type)

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import io.ksmt.KAst
44
import io.ksmt.cache.hash
55
import io.ksmt.cache.structurallyEqual
66
import io.ksmt.expr.KBitVec32Value
7+
import io.ksmt.expr.KExpr
78
import io.ksmt.expr.KFp64Value
89
import io.ksmt.expr.printer.ExpressionPrinter
910
import io.ksmt.expr.transformer.KTransformerBase
@@ -49,6 +50,29 @@ class TsUnresolvedSort(ctx: TsContext) : USort(ctx) {
4950
}
5051
}
5152

53+
class TsVoidValue(ctx: TsContext) : UExpr<TsVoidSort>(ctx) {
54+
override val sort: TsVoidSort
55+
get() = tctx.voidSort
56+
57+
override fun accept(transformer: KTransformerBase): KExpr<TsVoidSort> = this
58+
59+
override fun print(printer: ExpressionPrinter) {
60+
printer.append("void")
61+
}
62+
63+
override fun internEquals(other: Any): Boolean = structurallyEqual(other)
64+
65+
override fun internHashCode(): Int = hash()
66+
}
67+
68+
class TsVoidSort(ctx: TsContext) : USort(ctx) {
69+
override fun <T> accept(visitor: KSortVisitor<T>): T = error("Should not be called")
70+
71+
override fun print(builder: StringBuilder) {
72+
builder.append("void sort")
73+
}
74+
}
75+
5276
fun UExpr<out USort>.toConcreteBoolValue(): Boolean = when (this) {
5377
ctx.trueExpr -> true
5478
ctx.falseExpr -> false

0 commit comments

Comments
 (0)