From ccc157d8f78db86d73414b53f0c39f74d576d8ab Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Thu, 29 May 2025 18:03:27 +0300 Subject: [PATCH 01/82] Hacks --- .../org/usvm/machine/expr/TsExprResolver.kt | 85 +++++++++++++++---- .../usvm/machine/interpreter/TsInterpreter.kt | 50 +++++++++-- .../kotlin/org/usvm/util/EtsFieldResolver.kt | 32 ++++--- .../test/kotlin/org/usvm/project/Photos.kt | 19 +++++ .../org/usvm/util/TsMethodTestRunner.kt | 2 +- 5 files changed, 147 insertions(+), 41 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index 082324130d..2262c26ad0 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -106,8 +106,8 @@ import org.usvm.machine.types.EtsAuxiliaryType import org.usvm.machine.types.mkFakeValue import org.usvm.memory.ULValue import org.usvm.sizeSort -import org.usvm.util.EtsFieldResolutionResult import org.usvm.util.EtsHierarchy +import org.usvm.util.EtsPropertyResolution import org.usvm.util.createFakeField import org.usvm.util.isResolved import org.usvm.util.mkArrayIndexLValue @@ -116,6 +116,7 @@ import org.usvm.util.mkFieldLValue import org.usvm.util.mkRegisterStackLValue import org.usvm.util.resolveEtsField import org.usvm.util.throwExceptionWithoutStackFrameDrop +import org.usvm.util.type private val logger = KotlinLogging.logger {} @@ -528,6 +529,17 @@ class TsExprResolver( } } + if (expr.callee.name == "\$r") { + // TODO hack + return scope.calcOnState { + val mockSymbol = memory.mocker.createMockSymbol(trackedLiteral = null, addressSort, ownership) + + scope.assert(mkEq(mockSymbol, ctx.mkTsNullValue()).not()) + + mockSymbol + } + } + return when (val result = scope.calcOnState { methodResult }) { is TsMethodResult.Success -> { scope.doWithState { methodResult = TsMethodResult.NoCall } @@ -539,21 +551,51 @@ class TsExprResolver( } is TsMethodResult.NoCall -> { - // TODO: spawn VirtualCall when method resolution fails - val method = resolveStaticMethod(expr.callee) + val resolutionResult = resolveStaticMethod(expr.callee) - if (method == null) { + if (resolutionResult is EtsPropertyResolution.Empty) { logger.error { "Could not resolve static call: ${expr.callee}" } scope.assert(falseExpr) return null } - val instance = scope.calcOnState { getStaticInstance(method.enclosingClass!!) } + // static virtual call + if (resolutionResult is EtsPropertyResolution.Ambiguous) { + val resolvedArgs = expr.args.map { resolve(it) ?: return null } + + val staticInstances = scope.calcOnState { + resolutionResult.properties.take(1).map { getStaticInstance(it.enclosingClass!!) } + } + + // TODO take 1 is an error + val concreteCalls = resolutionResult.properties.take(1).mapIndexed { index, value -> + TsConcreteMethodCallStmt( + callee = value, + instance = staticInstances[index], + args = resolvedArgs, + returnSite = scope.calcOnState { lastStmt } + ) + } + + val blocks = concreteCalls.map { + ctx.mkTrue() to { state: TsState -> state.newStmt(it) } + } // TODO error with true Expr + + logger.warn { "Forking on ${blocks.size} branches" } + + scope.forkMulti(blocks) + + return null + } + + require(resolutionResult is EtsPropertyResolution.Unique) + + val instance = scope.calcOnState { getStaticInstance(resolutionResult.property.enclosingClass!!) } val resolvedArgs = expr.args.map { resolve(it) ?: return null } val concreteCall = TsConcreteMethodCallStmt( - callee = method, + callee = resolutionResult.property, instance = instance, args = resolvedArgs, returnSite = scope.calcOnState { lastStmt }, @@ -567,25 +609,22 @@ class TsExprResolver( private fun resolveStaticMethod( method: EtsMethodSignature, - ): EtsMethod? { + ): EtsPropertyResolution { // Perfect signature: if (method.enclosingClass.name != UNKNOWN_CLASS_NAME) { val classes = ctx.scene.projectAndSdkClasses.filter { it.name == method.enclosingClass.name } - if (classes.size != 1) return null + if (classes.size != 1) return EtsPropertyResolution.Empty val clazz = classes.single() val methods = clazz.methods.filter { it.name == method.name } - if (methods.size != 1) return null - return methods.single() + return EtsPropertyResolution.create(methods) } // Unknown signature: val methods = ctx.scene.projectAndSdkClasses .flatMap { it.methods } .filter { it.name == method.name } - if (methods.size == 1) return methods.single() - // error("Cannot resolve method $method") - return null + return EtsPropertyResolution.create(methods) } override fun visit(expr: EtsPtrCallExpr): UExpr? { @@ -730,7 +769,7 @@ class TsExprResolver( val etsField = resolveEtsField(instance, field, hierarchy) val sort = when (etsField) { - is EtsFieldResolutionResult.Empty -> { + is EtsPropertyResolution.Empty -> { logger.error("Field $etsField not found, creating fake field") // If we didn't find any real fields, let's create a fake one. // It is possible due to mistakes in the IR or if the field was added explicitly @@ -740,10 +779,13 @@ class TsExprResolver( addressSort } - is EtsFieldResolutionResult.Unique -> typeToSort(etsField.field.type) - is EtsFieldResolutionResult.Ambiguous -> unresolvedSort + is EtsPropertyResolution.Unique -> typeToSort(etsField.property.type) + is EtsPropertyResolution.Ambiguous -> unresolvedSort } + // Если мы записали что-то в объект в виде bool, а потом считали это из поля bool | null, то здесь будет unresolvedSort, + // мы проигнорируем что уже туда писали и считаем не оттуда + if (sort == unresolvedSort) { val boolLValue = mkFieldLValue(boolSort, instanceRef, field) val fpLValue = mkFieldLValue(fp64Sort, instanceRef, field) @@ -874,6 +916,17 @@ class TsExprResolver( } else { expr.type } + + if (expr.type.typeName == "Boolean") { + val clazz = scene.sdkClasses.filter { it.name == "Boolean" }.maxBy { it.methods.size } + return@with scope.calcOnState { memory.allocConcrete(clazz.type) } + } + + if (expr.type.typeName == "Number") { + val clazz = scene.sdkClasses.filter { it.name == "Number" }.maxBy { it.methods.size } + return@with scope.calcOnState { memory.allocConcrete(clazz.type) } + } + scope.calcOnState { memory.allocConcrete(resolvedType) } } diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index 5df7982eea..b7d59fd7ea 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -5,6 +5,7 @@ import mu.KotlinLogging import org.jacodb.ets.model.EtsArrayAccess import org.jacodb.ets.model.EtsArrayType import org.jacodb.ets.model.EtsAssignStmt +import org.jacodb.ets.model.EtsBooleanType import org.jacodb.ets.model.EtsCallStmt import org.jacodb.ets.model.EtsClassType import org.jacodb.ets.model.EtsIfStmt @@ -14,6 +15,7 @@ import org.jacodb.ets.model.EtsMethod import org.jacodb.ets.model.EtsMethodSignature import org.jacodb.ets.model.EtsNopStmt import org.jacodb.ets.model.EtsNullType +import org.jacodb.ets.model.EtsNumberType import org.jacodb.ets.model.EtsParameterRef import org.jacodb.ets.model.EtsRefType import org.jacodb.ets.model.EtsReturnStmt @@ -65,7 +67,7 @@ import org.usvm.machine.types.toAuxiliaryType import org.usvm.sizeSort import org.usvm.targets.UTargetsSet import org.usvm.types.single -import org.usvm.util.EtsFieldResolutionResult +import org.usvm.util.EtsPropertyResolution import org.usvm.util.mkArrayIndexLValue import org.usvm.util.mkArrayLengthLValue import org.usvm.util.mkFieldLValue @@ -171,7 +173,7 @@ class TsInterpreter( if (isAllocatedConcreteHeapRef(resolvedInstance)) { val type = scope.calcOnState { memory.typeStreamOf(resolvedInstance) }.single() if (type is EtsClassType) { - val classes = scene.projectAndSdkClasses.filter { it.name == type.typeName } + val classes = graph.hierarchy.classesForType(type) if (classes.isEmpty()) { logger.warn { "Could not resolve class: ${type.typeName}" } scope.assert(falseExpr) @@ -185,7 +187,9 @@ class TsInterpreter( val cls = classes.single() val method = cls.methods .singleOrNull { it.name == stmt.callee.name } - ?: TODO("Overloads are not supported yet") + ?: run { + TODO("Overloads are not supported yet") + } concreteMethods += method } else { logger.warn { @@ -348,6 +352,8 @@ class TsInterpreter( val (posStmt, negStmt) = graph.successors(stmt).take(2).toList() + logger.warn { "Forking on if stmt $stmt" } + scope.forkWithBlackList( boolExpr, posStmt, @@ -487,9 +493,9 @@ class TsInterpreter( // If there is no such field, we create a fake field for the expr val sort = when (etsField) { - is EtsFieldResolutionResult.Empty -> unresolvedSort - is EtsFieldResolutionResult.Unique -> typeToSort(etsField.field.type) - is EtsFieldResolutionResult.Ambiguous -> unresolvedSort + is EtsPropertyResolution.Empty -> unresolvedSort + is EtsPropertyResolution.Unique -> typeToSort(etsField.property.type) + is EtsPropertyResolution.Ambiguous -> unresolvedSort } if (sort == unresolvedSort) { @@ -501,7 +507,37 @@ class TsInterpreter( memory.write(lValue, fakeObject, guard = trueExpr) } else { val lValue = mkFieldLValue(sort, instance, lhv.field) - memory.write(lValue, expr.asExpr(lValue.sort), guard = trueExpr) + if (lValue.sort != expr.sort) { + if (expr.isFakeObject()) { + val lhvType = lhv.type + val value = when (lhvType) { + is EtsBooleanType -> { + scope.calcOnState { + pathConstraints += expr.getFakeType(scope).boolTypeExpr + expr.extractBool(scope) + } + } + is EtsNumberType -> { + scope.calcOnState { + pathConstraints += expr.getFakeType(scope).fpTypeExpr + expr.extractFp(scope) + } + } + else -> { + scope.calcOnState { + pathConstraints += expr.getFakeType(scope).refTypeExpr + expr.extractRef(scope) + } + } + } + + memory.write(lValue, value.asExpr(lValue.sort), guard = trueExpr) + } else { + error("TODO") + } + } else { + memory.write(lValue, expr.asExpr(lValue.sort), guard = trueExpr) + } } } diff --git a/usvm-ts/src/main/kotlin/org/usvm/util/EtsFieldResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/util/EtsFieldResolver.kt index 8b7884ec5c..b205cdd9e2 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/util/EtsFieldResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/util/EtsFieldResolver.kt @@ -16,12 +16,10 @@ fun TsContext.resolveEtsField( instance: EtsLocal?, field: EtsFieldSignature, hierarchy: EtsHierarchy, -): EtsFieldResolutionResult { +): EtsPropertyResolution { // Perfect signature: if (field.enclosingClass.name != UNKNOWN_CLASS_NAME) { - val classes = scene.projectAndSdkClasses.filter { cls -> - cls.name == field.enclosingClass.name - } + val classes = scene.projectAndSdkClasses.filter { it.signature == field.enclosingClass } if (classes.isEmpty()) { error("Cannot resolve class ${field.enclosingClass.name}") } @@ -31,7 +29,7 @@ fun TsContext.resolveEtsField( val clazz = classes.single() val fields = clazz.getAllFields(hierarchy).filter { it.name == field.name } if (fields.size == 1) { - return EtsFieldResolutionResult.create(fields.single()) + return EtsPropertyResolution.create(fields.single()) } } @@ -41,12 +39,12 @@ fun TsContext.resolveEtsField( when (instanceType) { is EtsClassType -> { val field = tryGetSingleField(scene, instanceType.signature.name, field.name, hierarchy) - if (field != null) return EtsFieldResolutionResult.create(field) + if (field != null) return EtsPropertyResolution.create(field) } is EtsUnclearRefType -> { val field = tryGetSingleField(scene, instanceType.typeName, field.name, hierarchy) - if (field != null) return EtsFieldResolutionResult.create(field) + if (field != null) return EtsPropertyResolution.create(field) } } } @@ -55,7 +53,7 @@ fun TsContext.resolveEtsField( cls.getAllFields(hierarchy).filter { it.name == field.name } } - return EtsFieldResolutionResult.create(fields) + return EtsPropertyResolution.create(fields) } private fun tryGetSingleField( @@ -118,19 +116,19 @@ fun EtsClass.getAllProperties(hierarchy: EtsHierarchy): Pair, return allFields to allMethods } -sealed class EtsFieldResolutionResult { - data class Unique(val field: EtsField) : EtsFieldResolutionResult() - data class Ambiguous(val fields: List) : EtsFieldResolutionResult() - data object Empty : EtsFieldResolutionResult() +sealed class EtsPropertyResolution { + data class Unique(val property: T) : EtsPropertyResolution() + data class Ambiguous(val properties: List) : EtsPropertyResolution() + data object Empty : EtsPropertyResolution() companion object { - fun create(field: EtsField) = Unique(field) + fun create(property: T) = Unique(property) - fun create(fields: List): EtsFieldResolutionResult { + fun create(properties: List): EtsPropertyResolution { return when { - fields.isEmpty() -> Empty - fields.size == 1 -> Unique(fields.single()) - else -> Ambiguous(fields) + properties.isEmpty() -> Empty + properties.size == 1 -> Unique(properties.single()) + else -> Ambiguous(properties) } } } diff --git a/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt b/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt index 40ab15d739..66f34b4bc7 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt @@ -1,6 +1,9 @@ package org.usvm.project import org.jacodb.ets.model.EtsScene +import org.jacodb.ets.utils.CONSTRUCTOR_NAME +import org.jacodb.ets.utils.INSTANCE_INIT_METHOD_NAME +import org.jacodb.ets.utils.STATIC_INIT_METHOD_NAME import org.jacodb.ets.utils.getDeclaredLocals import org.jacodb.ets.utils.getLocals import org.jacodb.ets.utils.loadEtsProjectFromIR @@ -72,6 +75,9 @@ class RunOnPhotosProject : TsMethodTestRunner() { .filterNot { it.isStatic } .filterNot { it.name.startsWith("%AM") } .filterNot { it.name == "build" } + .filterNot { it.name == INSTANCE_INIT_METHOD_NAME } + .filterNot { it.name == STATIC_INIT_METHOD_NAME } + .filterNot { it.name == CONSTRUCTOR_NAME } } val tsOptions = TsOptions() TsMachine(scene, options, tsOptions).use { machine -> @@ -79,4 +85,17 @@ class RunOnPhotosProject : TsMethodTestRunner() { states.let {} } } + + @Test + fun `test on particular method`() { + val method = scene.projectClasses + .flatMap { it.methods } + .single { it.name == "createActionBar" && it.enclosingClass?.name == "NewAlbumBarModel" } + + val tsOptions = TsOptions() + TsMachine(scene, options, tsOptions).use { machine -> + val states = machine.analyze(listOf(method)) + states.let {} + } + } } diff --git a/usvm-ts/src/test/kotlin/org/usvm/util/TsMethodTestRunner.kt b/usvm-ts/src/test/kotlin/org/usvm/util/TsMethodTestRunner.kt index 58d5b1702c..065d2ec150 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/util/TsMethodTestRunner.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/util/TsMethodTestRunner.kt @@ -341,7 +341,7 @@ abstract class TsMethodTestRunner : TestRunner Date: Thu, 29 May 2025 23:57:00 +0300 Subject: [PATCH 02/82] More hacks --- .../org/usvm/machine/expr/TsExprResolver.kt | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index 2262c26ad0..d68330cd58 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -483,6 +483,36 @@ class TsExprResolver( return TODO() } + // TODO write tests + if (expr.callee.name == "push") { + let {} + } + + if (expr.callee.name == "push" && expr.instance.type is EtsArrayType) { + return scope.calcOnState { + val resolvedInstance = resolve(expr.instance)?.asExpr(ctx.addressSort) ?: return@calcOnState null + val lengthLValue = mkArrayLengthLValue( + resolvedInstance, + EtsArrayType(EtsUnknownType, dimensions = 1) + ) + val length = memory.read(lengthLValue) + val newLength = mkBvAddExpr(length, 1.toBv()) + memory.write(lengthLValue, newLength, guard = ctx.trueExpr) + val resolvedArg = resolve(expr.args.single()) ?: return@calcOnState null + + // TODO check sorts compatibility + val newIndexLValue = mkArrayIndexLValue( + resolvedArg.sort, + resolvedInstance, + length, + EtsArrayType(EtsUnknownType, dimensions = 1) + ) + memory.write(newIndexLValue, resolvedArg.asExpr(newIndexLValue.sort), guard = ctx.trueExpr) + + newLength + } + } + return when (val result = scope.calcOnState { methodResult }) { is TsMethodResult.Success -> { scope.doWithState { methodResult = TsMethodResult.NoCall } From bde95201429cd8a7243f25862a29cf077c350cfc Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Fri, 30 May 2025 16:06:36 +0300 Subject: [PATCH 03/82] Some more fixes --- .../main/kotlin/org/usvm/machine/TsContext.kt | 17 +++- .../main/kotlin/org/usvm/machine/TsMachine.kt | 86 +++++++++++++++---- .../org/usvm/machine/expr/TsExprResolver.kt | 26 +++++- .../usvm/machine/interpreter/TsInterpreter.kt | 13 +-- .../test/kotlin/org/usvm/project/Photos.kt | 6 +- .../main/kotlin/org/usvm/UMachineOptions.kt | 1 + 6 files changed, 118 insertions(+), 31 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt index 026f3c35bb..9fdf2054c1 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt @@ -6,8 +6,10 @@ import org.jacodb.ets.model.EtsAliasType import org.jacodb.ets.model.EtsAnyType import org.jacodb.ets.model.EtsArrayType import org.jacodb.ets.model.EtsBooleanType +import org.jacodb.ets.model.EtsGenericType import org.jacodb.ets.model.EtsNullType import org.jacodb.ets.model.EtsNumberType +import org.jacodb.ets.model.EtsRawType import org.jacodb.ets.model.EtsRefType import org.jacodb.ets.model.EtsScene import org.jacodb.ets.model.EtsStringType @@ -76,7 +78,20 @@ class TsContext( is EtsAnyType -> unresolvedSort is EtsUnknownType -> unresolvedSort is EtsAliasType -> typeToSort(type.originalType) - else -> TODO("${type::class.simpleName} is not yet supported: $type") + is EtsGenericType -> { + if (type.constraint == null && type.defaultType == null) { + unresolvedSort + } else { + TODO("Not yet implemented") + } + } + else -> { + if (type is EtsRawType && type.kind == "EnumValueType") { + unresolvedSort + } else { + TODO("${type::class.simpleName} is not yet supported: $type") + } + } } fun arrayDescriptorOf(type: EtsArrayType): EtsType { diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt index d82ac0ab88..59b54ce0c5 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt @@ -8,6 +8,7 @@ import org.usvm.CoverageZone import org.usvm.StateCollectionStrategy import org.usvm.UMachine import org.usvm.UMachineOptions +import org.usvm.UPathSelector import org.usvm.api.targets.TsTarget import org.usvm.machine.interpreter.TsInterpreter import org.usvm.machine.state.TsMethodResult @@ -26,6 +27,7 @@ import org.usvm.statistics.collectors.TargetsReachedStatesCollector import org.usvm.statistics.constraints.SoftConstraintsObserver import org.usvm.statistics.distances.CfgStatisticsImpl import org.usvm.statistics.distances.PlainCallGraphStatistics +import org.usvm.stopstrategies.StopStrategy import org.usvm.stopstrategies.createStopStrategy import org.usvm.util.humanReadableSignature import kotlin.time.Duration.Companion.seconds @@ -72,15 +74,39 @@ class TsMachine( val timeStatistics = TimeStatistics() - val pathSelector = createPathSelector( - initialStates = initialStates, - options = options, - applicationGraph = graph, - timeStatistics = timeStatistics, - coverageStatisticsFactory = { coverageStatistics }, - cfgStatisticsFactory = { cfgStatistics }, - callGraphStatisticsFactory = { callGraphStatistics }, - ) + val pathSelector = object : UPathSelector { + val internalSelector = createPathSelector( + initialStates = initialStates, + options = options, + applicationGraph = graph, + timeStatistics = timeStatistics, + coverageStatisticsFactory = { coverageStatistics }, + cfgStatisticsFactory = { cfgStatistics }, + callGraphStatisticsFactory = { callGraphStatistics } + ) + + override fun isEmpty(): Boolean { + return internalSelector.isEmpty() + } + + override fun peek(): TsState { + return internalSelector.peek() + } + + override fun remove(state: TsState) { + logger.warn { "Selector size -1" } + return internalSelector.remove(state) + } + + override fun add(states: Collection) { + logger.warn { "Selector size +1" } + return internalSelector.add(states) + } + + override fun update(state: TsState) { + return internalSelector.update(state) + } + } val statesCollector = when (options.stateCollectionStrategy) { @@ -101,14 +127,30 @@ class TsMachine( val stepsStatistics = StepsStatistics() - val stopStrategy = createStopStrategy( - options, - targets, - timeStatisticsFactory = { timeStatistics }, - stepsStatisticsFactory = { stepsStatistics }, - coverageStatisticsFactory = { coverageStatistics }, - getCollectedStatesCount = { statesCollector.collectedStates.size } - ) + val stopStrategy = object : StopStrategy { + val strategy = createStopStrategy( + options, + targets, + timeStatisticsFactory = + { timeStatistics }, + stepsStatisticsFactory = + { stepsStatistics }, + coverageStatisticsFactory = + { coverageStatistics }, + getCollectedStatesCount = + { statesCollector.collectedStates.size } + ) + + override fun shouldStop(): Boolean { + val result = strategy.shouldStop() + + if (result) { + logger.warn { "Stop strategy finished execution $strategy" } + } + + return result + } + } observers.add(timeStatistics) observers.add(stepsStatistics) @@ -127,6 +169,16 @@ class TsMachine( ) } + val endReasonObserver = object : UMachineObserver { + override fun onStateTerminated(state: TsState, stateReachable: Boolean) { + logger.warn { + "State is terminated on ${state.currentStatement} in the method ${state.currentStatement.method}" + } + } + } + + observers += endReasonObserver + run( interpreter, pathSelector, diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index d68330cd58..ff1322970e 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -54,6 +54,7 @@ import org.jacodb.ets.model.EtsPostIncExpr import org.jacodb.ets.model.EtsPreDecExpr import org.jacodb.ets.model.EtsPreIncExpr import org.jacodb.ets.model.EtsPtrCallExpr +import org.jacodb.ets.model.EtsRawType import org.jacodb.ets.model.EtsRemExpr import org.jacodb.ets.model.EtsRightShiftExpr import org.jacodb.ets.model.EtsStaticCallExpr @@ -484,10 +485,6 @@ class TsExprResolver( } // TODO write tests - if (expr.callee.name == "push") { - let {} - } - if (expr.callee.name == "push" && expr.instance.type is EtsArrayType) { return scope.calcOnState { val resolvedInstance = resolve(expr.instance)?.asExpr(ctx.addressSort) ?: return@calcOnState null @@ -813,6 +810,7 @@ class TsExprResolver( is EtsPropertyResolution.Ambiguous -> unresolvedSort } + // TODO // Если мы записали что-то в объект в виде bool, а потом считали это из поля bool | null, то здесь будет unresolvedSort, // мы проигнорируем что уже туда писали и считаем не оттуда @@ -835,6 +833,26 @@ class TsExprResolver( lValuesToAllocatedFakeObjects += refLValue to it } } + + // TODO ambiguous enum fields resolution + if (etsField is EtsPropertyResolution.Unique) { + val fieldType = etsField.property.type + if (fieldType is EtsRawType && fieldType.kind == "EnumValueType") { + val fakeType = fakeRef.getFakeType(scope) + pathConstraints += ctx.mkOr( + fakeType.fpTypeExpr, + fakeType.refTypeExpr + ) + + // val supertype = TODO() + // TODO add enum type as a constraint + // pathConstraints += memory.types.evalIsSubtype( + // ref, + // TODO() + // ) + } + } + memory.write(refLValue, fakeRef.asExpr(addressSort), guard = trueExpr) fakeRef diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index b7d59fd7ea..158b012ec6 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -136,9 +136,7 @@ class TsInterpreter( val s = e.stackTrace[0] "Exception .(${s.fileName}:${s.lineNumber}): $e}" } - - // Kill the state causing error - scope.assert(ctx.falseExpr) + // todo are exceptional states properly removed? } return scope.stepResult() @@ -208,12 +206,14 @@ class TsInterpreter( concreteMethods += methods } + val limitedConcreteMethods = concreteMethods.take(5) + logger.info { - "Preparing to fork on ${concreteMethods.size} methods: ${ - concreteMethods.map { "${it.signature.enclosingClass.name}::${it.name}" } + "Preparing to fork on ${limitedConcreteMethods.size} methods out of ${concreteMethods.size}: ${ + limitedConcreteMethods.map { "${it.signature.enclosingClass.name}::${it.name}" } }" } - val conditionsWithBlocks = concreteMethods.map { method -> + val conditionsWithBlocks = limitedConcreteMethods.map { method -> val concreteCall = stmt.toConcrete(method) val block = { state: TsState -> state.newStmt(concreteCall) } val type = requireNotNull(method.enclosingClass).type @@ -533,6 +533,7 @@ class TsInterpreter( memory.write(lValue, value.asExpr(lValue.sort), guard = trueExpr) } else { + // probably it is a enum, check for it error("TODO") } } else { diff --git a/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt b/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt index 66f34b4bc7..fa161bdb75 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt @@ -6,7 +6,7 @@ import org.jacodb.ets.utils.INSTANCE_INIT_METHOD_NAME import org.jacodb.ets.utils.STATIC_INIT_METHOD_NAME import org.jacodb.ets.utils.getDeclaredLocals import org.jacodb.ets.utils.getLocals -import org.jacodb.ets.utils.loadEtsProjectFromIR +import org.jacodb.ets.utils.loadEtsProjectAutoConvert import org.junit.jupiter.api.condition.EnabledIf import org.usvm.api.TsTestValue import org.usvm.machine.TsMachine @@ -21,7 +21,7 @@ import kotlin.test.Test class RunOnPhotosProject : TsMethodTestRunner() { companion object { - private const val PROJECT_PATH = "/projects/Demo_Photos/etsir/entry" + private const val PROJECT_PATH = "/projects/Demo_Photos/source/entry" private const val SDK_PATH = "/sdk/ohos/etsir" @JvmStatic @@ -34,7 +34,7 @@ class RunOnPhotosProject : TsMethodTestRunner() { val projectPath = getResourcePath(PROJECT_PATH) val sdkPath = getResourcePathOrNull(SDK_PATH) ?: error("Could not load SDK from resources '$SDK_PATH'. Try running './gradlew generateSdkIR' to generate it.") - loadEtsProjectFromIR(projectPath, sdkPath) + loadEtsProjectAutoConvert(projectPath, sdkPath) } @Test diff --git a/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt b/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt index b2989d2685..de5646aaa8 100644 --- a/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt +++ b/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt @@ -259,4 +259,5 @@ data class UMachineOptions( * Limit loop iterations. * */ val loopIterationLimit: Int? = null, + // TODO fork limit ) From 4d0cf6ba159d7a5ff64db4dc2a0feb1fc9a7c900 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Fri, 30 May 2025 16:28:36 +0300 Subject: [PATCH 04/82] Tmp commit --- .../kotlin/org/usvm/machine/interpreter/TsInterpreter.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index 158b012ec6..f46c8fa91a 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -129,7 +129,12 @@ class TsInterpreter( is EtsCallStmt -> visitCallStmt(scope, stmt) is EtsThrowStmt -> visitThrowStmt(scope, stmt) is EtsNopStmt -> visitNopStmt(scope, stmt) - else -> error("Unknown stmt: $stmt") + else -> { + logger.error { "Unknown stmt: $stmt" } + scope.doWithState { + newStmt(graph.successors(stmt).single()) + } + } } } catch (e: Exception) { logger.error { @@ -517,12 +522,14 @@ class TsInterpreter( expr.extractBool(scope) } } + is EtsNumberType -> { scope.calcOnState { pathConstraints += expr.getFakeType(scope).fpTypeExpr expr.extractFp(scope) } } + else -> { scope.calcOnState { pathConstraints += expr.getFakeType(scope).refTypeExpr From a4954f9a906bfbba7a752b3f0474dbe94b372298 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Fri, 6 Jun 2025 17:11:58 +0300 Subject: [PATCH 05/82] Input arrays support --- .../kotlin/org/usvm/machine/expr/TsExprResolver.kt | 4 +--- .../kotlin/org/usvm/machine/expr/TsExpressions.kt | 11 +++++++++++ .../org/usvm/machine/operator/TsBinaryOperator.kt | 4 ++-- .../src/main/kotlin/org/usvm/machine/state/TsState.kt | 1 + 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index ff1322970e..beae3da2c6 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -829,9 +829,7 @@ class TsExprResolver( val fakeRef = if (ref.isFakeObject()) { ref } else { - mkFakeValue(scope, bool, fp, ref).also { - lValuesToAllocatedFakeObjects += refLValue to it - } + mkFakeValue(scope, bool, fp, ref).also { lValuesToAllocatedFakeObjects += refLValue to it } } // TODO ambiguous enum fields resolution diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExpressions.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExpressions.kt index 9a3343b5a5..c07be5b8ba 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExpressions.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExpressions.kt @@ -100,3 +100,14 @@ fun UExpr<*>.extractInt(): Int { } error("Cannot extract Int from $this") } + +/** + * Extracts an integer value from [this] expression if possible. + * Otherwise, throws an error. + */ +fun UExpr.extractInt(): Int { + if (this@extractInt is KBitVec32Value) { + return intValue + } + error("Cannot extract int from $this") +} diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/operator/TsBinaryOperator.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/operator/TsBinaryOperator.kt index 722f86f07b..fae34cebe8 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/operator/TsBinaryOperator.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/operator/TsBinaryOperator.kt @@ -573,6 +573,7 @@ sealed interface TsBinaryOperator { lhsValue = lhs.extractBool(scope) lhsType.boolTypeExpr } + } fp64Sort -> { lhsValue = lhs.extractFp(scope) @@ -583,7 +584,6 @@ sealed interface TsBinaryOperator { lhsValue = lhs.extractRef(scope) lhsType.refTypeExpr } - else -> error("Unsupported sort ${rhs.sort}") } } @@ -595,6 +595,7 @@ sealed interface TsBinaryOperator { rhsValue = rhs.extractBool(scope) rhsType.boolTypeExpr } + } fp64Sort -> { rhsValue = rhs.extractFp(scope) @@ -605,7 +606,6 @@ sealed interface TsBinaryOperator { rhsValue = rhs.extractRef(scope) rhsType.refTypeExpr } - else -> error("Unsupported sort ${lhs.sort}") } } diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt index 38d8b66d18..4451bf9853 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt @@ -49,6 +49,7 @@ class TsState( val globalObject: UConcreteHeapRef = memory.allocStatic(EtsClassType(EtsClassSignature.UNKNOWN)), val addedArtificialLocals: MutableSet = hashSetOf(), val lValuesToAllocatedFakeObjects: MutableList, UConcreteHeapRef>> = mutableListOf(), + val lValuesToAllocatedFakeObjects: MutableList, UConcreteHeapRef>> = mutableListOf(), ) : UState( ctx = ctx, initOwnership = ownership, From 5d4dc68f1211fe224a82d5bb93585263b6ef8f04 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Mon, 9 Jun 2025 16:34:56 +0300 Subject: [PATCH 06/82] Format --- .../src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt | 4 +++- .../src/main/kotlin/org/usvm/machine/expr/TsExpressions.kt | 4 ++-- .../kotlin/org/usvm/machine/operator/TsBinaryOperator.kt | 5 ++--- usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt | 1 - 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index beae3da2c6..ff1322970e 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -829,7 +829,9 @@ class TsExprResolver( val fakeRef = if (ref.isFakeObject()) { ref } else { - mkFakeValue(scope, bool, fp, ref).also { lValuesToAllocatedFakeObjects += refLValue to it } + mkFakeValue(scope, bool, fp, ref).also { + lValuesToAllocatedFakeObjects += refLValue to it + } } // TODO ambiguous enum fields resolution diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExpressions.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExpressions.kt index c07be5b8ba..4ec91ffe81 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExpressions.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExpressions.kt @@ -105,9 +105,9 @@ fun UExpr<*>.extractInt(): Int { * Extracts an integer value from [this] expression if possible. * Otherwise, throws an error. */ -fun UExpr.extractInt(): Int { +fun UExpr<*>.extractInt(): Int { if (this@extractInt is KBitVec32Value) { return intValue } - error("Cannot extract int from $this") + error("Cannot extract Int from $this") } diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/operator/TsBinaryOperator.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/operator/TsBinaryOperator.kt index fae34cebe8..2a1094918b 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/operator/TsBinaryOperator.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/operator/TsBinaryOperator.kt @@ -573,7 +573,6 @@ sealed interface TsBinaryOperator { lhsValue = lhs.extractBool(scope) lhsType.boolTypeExpr } - } fp64Sort -> { lhsValue = lhs.extractFp(scope) @@ -584,6 +583,7 @@ sealed interface TsBinaryOperator { lhsValue = lhs.extractRef(scope) lhsType.refTypeExpr } + else -> error("Unsupported sort ${rhs.sort}") } } @@ -595,8 +595,6 @@ sealed interface TsBinaryOperator { rhsValue = rhs.extractBool(scope) rhsType.boolTypeExpr } - } - fp64Sort -> { rhsValue = rhs.extractFp(scope) rhsType.fpTypeExpr @@ -606,6 +604,7 @@ sealed interface TsBinaryOperator { rhsValue = rhs.extractRef(scope) rhsType.refTypeExpr } + else -> error("Unsupported sort ${lhs.sort}") } } diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt index 4451bf9853..38d8b66d18 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt @@ -49,7 +49,6 @@ class TsState( val globalObject: UConcreteHeapRef = memory.allocStatic(EtsClassType(EtsClassSignature.UNKNOWN)), val addedArtificialLocals: MutableSet = hashSetOf(), val lValuesToAllocatedFakeObjects: MutableList, UConcreteHeapRef>> = mutableListOf(), - val lValuesToAllocatedFakeObjects: MutableList, UConcreteHeapRef>> = mutableListOf(), ) : UState( ctx = ctx, initOwnership = ownership, From 7cf9061fe1253656a837bf6f26faa9a08078f54b Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Tue, 10 Jun 2025 18:13:39 +0300 Subject: [PATCH 07/82] Operators implementation --- .../kotlin/org/usvm/machine/expr/TsExprResolver.kt | 13 ++++++++----- .../test/kotlin/org/usvm/util/TsMethodTestRunner.kt | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index ff1322970e..bcc26039da 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -117,7 +117,6 @@ import org.usvm.util.mkFieldLValue import org.usvm.util.mkRegisterStackLValue import org.usvm.util.resolveEtsField import org.usvm.util.throwExceptionWithoutStackFrameDrop -import org.usvm.util.type private val logger = KotlinLogging.logger {} @@ -418,13 +417,17 @@ class TsExprResolver( } override fun visit(expr: EtsLtEqExpr): UExpr? { - logger.warn { "visit(${expr::class.simpleName}) is not implemented yet" } - error("Not supported $expr") + val eq = resolve(EtsEqExpr(expr.left, expr.right)) ?: return null + val lt = resolve(EtsLtExpr(expr.left, expr.right)) ?: return null + + return ctx.mkOr(eq.asExpr(ctx.boolSort), lt.asExpr(ctx.boolSort)) } override fun visit(expr: EtsGtEqExpr): UExpr? { - logger.warn { "visit(${expr::class.simpleName}) is not implemented yet" } - error("Not supported $expr") + val eq = resolve(EtsEqExpr(expr.left, expr.right)) ?: return null + val gt = resolve(EtsGtExpr(expr.left, expr.right)) ?: return null + + return ctx.mkOr(eq.asExpr(ctx.boolSort), gt.asExpr(ctx.boolSort)) } override fun visit(expr: EtsInExpr): UExpr? { diff --git a/usvm-ts/src/test/kotlin/org/usvm/util/TsMethodTestRunner.kt b/usvm-ts/src/test/kotlin/org/usvm/util/TsMethodTestRunner.kt index 065d2ec150..58d5b1702c 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/util/TsMethodTestRunner.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/util/TsMethodTestRunner.kt @@ -341,7 +341,7 @@ abstract class TsMethodTestRunner : TestRunner Date: Tue, 10 Jun 2025 18:17:51 +0300 Subject: [PATCH 08/82] Some hacks --- .../src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index bcc26039da..664e7ebd90 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -292,8 +292,8 @@ class TsExprResolver( ) } - logger.warn { "visit(${expr::class.simpleName}) is not implemented yet" } - error("Not supported $expr") + logger.error { "visit(${expr::class.simpleName}) is not implemented yet" } + return mkFp64(ADHOC_STRING) } override fun visit(expr: EtsDeleteExpr): UExpr? { From 8c9e9f4be0d582ab6a4f0dcc20db4c492c575ec5 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Wed, 11 Jun 2025 11:04:20 +0300 Subject: [PATCH 09/82] Fix a class signature lookup --- usvm-ts/src/main/kotlin/org/usvm/util/EtsFieldResolver.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/util/EtsFieldResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/util/EtsFieldResolver.kt index b205cdd9e2..e59d43cd2b 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/util/EtsFieldResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/util/EtsFieldResolver.kt @@ -19,7 +19,7 @@ fun TsContext.resolveEtsField( ): EtsPropertyResolution { // Perfect signature: if (field.enclosingClass.name != UNKNOWN_CLASS_NAME) { - val classes = scene.projectAndSdkClasses.filter { it.signature == field.enclosingClass } + val classes = hierarchy.classesForType(EtsClassType(field.enclosingClass)) if (classes.isEmpty()) { error("Cannot resolve class ${field.enclosingClass.name}") } From 689a90245a6e1cfd84be7bccf181b6cb3b48d280 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Wed, 11 Jun 2025 11:42:00 +0300 Subject: [PATCH 10/82] Support simple casts --- buildSrc/src/main/kotlin/Dependencies.kt | 2 +- .../main/kotlin/org/usvm/machine/TsContext.kt | 9 ++---- .../org/usvm/machine/expr/TsExprResolver.kt | 30 +++++++++++++++++-- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 03474c51d5..482eec6836 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -6,7 +6,7 @@ object Versions { const val clikt = "5.0.0" const val detekt = "1.23.7" const val ini4j = "0.5.4" - const val jacodb = "4ff7243d3a" + const val jacodb = "77b83e4127" const val juliet = "1.3.2" const val junit = "5.9.3" const val kotlin = "2.1.0" diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt index 9fdf2054c1..c9110f2ed7 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt @@ -6,10 +6,10 @@ import org.jacodb.ets.model.EtsAliasType import org.jacodb.ets.model.EtsAnyType import org.jacodb.ets.model.EtsArrayType import org.jacodb.ets.model.EtsBooleanType +import org.jacodb.ets.model.EtsEnumValueType import org.jacodb.ets.model.EtsGenericType import org.jacodb.ets.model.EtsNullType import org.jacodb.ets.model.EtsNumberType -import org.jacodb.ets.model.EtsRawType import org.jacodb.ets.model.EtsRefType import org.jacodb.ets.model.EtsScene import org.jacodb.ets.model.EtsStringType @@ -85,12 +85,9 @@ class TsContext( TODO("Not yet implemented") } } + is EtsEnumValueType -> unresolvedSort else -> { - if (type is EtsRawType && type.kind == "EnumValueType") { - unresolvedSort - } else { - TODO("${type::class.simpleName} is not yet supported: $type") - } + TODO("${type::class.simpleName} is not yet supported: $type") } } diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index 664e7ebd90..4d7e31fab1 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -55,6 +55,7 @@ import org.jacodb.ets.model.EtsPreDecExpr import org.jacodb.ets.model.EtsPreIncExpr import org.jacodb.ets.model.EtsPtrCallExpr import org.jacodb.ets.model.EtsRawType +import org.jacodb.ets.model.EtsRefType import org.jacodb.ets.model.EtsRemExpr import org.jacodb.ets.model.EtsRightShiftExpr import org.jacodb.ets.model.EtsStaticCallExpr @@ -262,8 +263,33 @@ class TsExprResolver( } override fun visit(expr: EtsCastExpr): UExpr<*>? = with(ctx) { - logger.warn { "visit(${expr::class.simpleName}) is not implemented yet" } - error("Not supported $expr") + val resolvedExpr = resolve(expr.arg) ?: return@with null + return when (resolvedExpr.sort) { + fp64Sort -> { + TODO() + } + + boolSort -> { + TODO() + } + + addressSort -> { + scope.calcOnState { + val instance = resolvedExpr.asExpr(addressSort) + + if (expr.type !is EtsRefType) { + TODO("Not supported yet") + } + + pathConstraints += memory.types.evalIsSubtype(instance, expr.type) + instance + } + } + + else -> { + error("Unsupported cast from ${expr.arg} to ${expr.type}") + } + } } override fun visit(expr: EtsTypeOfExpr): UExpr? = with(ctx) { From 760624b8d252c1bb30e544c4284300cce86f0567 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Wed, 11 Jun 2025 11:51:56 +0300 Subject: [PATCH 11/82] Clear logs --- .../main/kotlin/org/usvm/machine/TsMachine.kt | 53 ++++--------------- .../org/usvm/machine/expr/TsExprResolver.kt | 2 +- .../usvm/machine/interpreter/TsInterpreter.kt | 9 +--- 3 files changed, 12 insertions(+), 52 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt index 59b54ce0c5..fa07056a96 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt @@ -8,7 +8,6 @@ import org.usvm.CoverageZone import org.usvm.StateCollectionStrategy import org.usvm.UMachine import org.usvm.UMachineOptions -import org.usvm.UPathSelector import org.usvm.api.targets.TsTarget import org.usvm.machine.interpreter.TsInterpreter import org.usvm.machine.state.TsMethodResult @@ -74,39 +73,15 @@ class TsMachine( val timeStatistics = TimeStatistics() - val pathSelector = object : UPathSelector { - val internalSelector = createPathSelector( - initialStates = initialStates, - options = options, - applicationGraph = graph, - timeStatistics = timeStatistics, - coverageStatisticsFactory = { coverageStatistics }, - cfgStatisticsFactory = { cfgStatistics }, - callGraphStatisticsFactory = { callGraphStatistics } - ) - - override fun isEmpty(): Boolean { - return internalSelector.isEmpty() - } - - override fun peek(): TsState { - return internalSelector.peek() - } - - override fun remove(state: TsState) { - logger.warn { "Selector size -1" } - return internalSelector.remove(state) - } - - override fun add(states: Collection) { - logger.warn { "Selector size +1" } - return internalSelector.add(states) - } - - override fun update(state: TsState) { - return internalSelector.update(state) - } - } + val pathSelector = createPathSelector( + initialStates = initialStates, + options = options, + applicationGraph = graph, + timeStatistics = timeStatistics, + coverageStatisticsFactory = { coverageStatistics }, + cfgStatisticsFactory = { cfgStatistics }, + callGraphStatisticsFactory = { callGraphStatistics } + ) val statesCollector = when (options.stateCollectionStrategy) { @@ -169,16 +144,6 @@ class TsMachine( ) } - val endReasonObserver = object : UMachineObserver { - override fun onStateTerminated(state: TsState, stateReachable: Boolean) { - logger.warn { - "State is terminated on ${state.currentStatement} in the method ${state.currentStatement.method}" - } - } - } - - observers += endReasonObserver - run( interpreter, pathSelector, diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index 4d7e31fab1..7e7c53a509 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -1083,7 +1083,7 @@ class TsSimpleValueResolver( return mkFieldLValue(ctx.addressSort, globalObject, local.name) } - logger.warn { "Cannot resolve local $local" } + logger.warn { "Cannot resolve local $local, creating a fake field of the global object" } globalObject.createFakeField(localName, scope) scope.doWithState { diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index f46c8fa91a..9c19722508 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -245,7 +245,7 @@ class TsInterpreter( val entryPoint = graph.entryPoints(stmt.callee).singleOrNull() if (entryPoint == null) { - logger.warn { "No entry point for method: ${stmt.callee}" } + logger.warn { "No entry point for method: ${stmt.callee}, mocking the call" } // If the method doesn't have entry points, // we go through it, we just mock the call mockMethodCall(scope, stmt.callee.signature) @@ -357,8 +357,6 @@ class TsInterpreter( val (posStmt, negStmt) = graph.successors(stmt).take(2).toList() - logger.warn { "Forking on if stmt $stmt" } - scope.forkWithBlackList( boolExpr, posStmt, @@ -644,10 +642,7 @@ class TsInterpreter( local.name to localIdx }.toMap() } - map[local.name] ?: run { - logger.error("Local not declared: $local") - return null - } + map[local.name] } // Note: 'this' has index 'n' From 4bd35bacf29e59eed39ad828c07dd59ee519c25d Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Wed, 11 Jun 2025 14:20:43 +0300 Subject: [PATCH 12/82] Add todo --- .../main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index 9c19722508..7fe65c2254 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -538,8 +538,7 @@ class TsInterpreter( memory.write(lValue, value.asExpr(lValue.sort), guard = trueExpr) } else { - // probably it is a enum, check for it - error("TODO") + TODO("Support enums fields") } } else { memory.write(lValue, expr.asExpr(lValue.sort), guard = trueExpr) From ada1c486620719dd675fb971dafade6622561dc0 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Wed, 18 Jun 2025 16:16:32 +0300 Subject: [PATCH 13/82] Rebase fix --- .../kotlin/org/usvm/machine/expr/TsExpressions.kt | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExpressions.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExpressions.kt index 4ec91ffe81..9a3343b5a5 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExpressions.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExpressions.kt @@ -100,14 +100,3 @@ fun UExpr<*>.extractInt(): Int { } error("Cannot extract Int from $this") } - -/** - * Extracts an integer value from [this] expression if possible. - * Otherwise, throws an error. - */ -fun UExpr<*>.extractInt(): Int { - if (this@extractInt is KBitVec32Value) { - return intValue - } - error("Cannot extract Int from $this") -} From 0dda9c035427d2a6d1e753d70349077b2fe4b192 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Thu, 19 Jun 2025 15:58:33 +0300 Subject: [PATCH 14/82] Tmp changes --- .../src/main/kotlin/org/usvm/StepScope.kt | 4 +- .../org/usvm/machine/expr/TsExprResolver.kt | 48 +++++++++++---- .../usvm/machine/interpreter/TsInterpreter.kt | 15 ++--- .../org/usvm/machine/types/TsTypeSystem.kt | 3 +- .../main/kotlin/org/usvm/util/EtsHierarchy.kt | 2 +- .../test/kotlin/org/usvm/project/Photos.kt | 58 +++++++++++-------- .../org/usvm/util/TsMethodTestRunner.kt | 8 +-- .../org/usvm/test/util/checkers/Matchers.kt | 2 +- 8 files changed, 90 insertions(+), 50 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/StepScope.kt b/usvm-core/src/main/kotlin/org/usvm/StepScope.kt index a22e8bd8dc..294271f090 100644 --- a/usvm-core/src/main/kotlin/org/usvm/StepScope.kt +++ b/usvm-core/src/main/kotlin/org/usvm/StepScope.kt @@ -56,7 +56,9 @@ class StepScope, Type, Statement, contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - check(canProcessFurtherOnCurrentStep) { "Caller should check before processing the current hop further" } + check(canProcessFurtherOnCurrentStep) { + "Caller should check before processing the current hop further" + } return originalState.block() } diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index 7e7c53a509..3c46639760 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -17,6 +17,7 @@ import org.jacodb.ets.model.EtsBitXorExpr import org.jacodb.ets.model.EtsBooleanConstant import org.jacodb.ets.model.EtsBooleanType import org.jacodb.ets.model.EtsCastExpr +import org.jacodb.ets.model.EtsClassType import org.jacodb.ets.model.EtsConstant import org.jacodb.ets.model.EtsDeleteExpr import org.jacodb.ets.model.EtsDivExpr @@ -277,6 +278,14 @@ class TsExprResolver( scope.calcOnState { val instance = resolvedExpr.asExpr(addressSort) + if (instance.isFakeObject()) { + val fakeType = instance.getFakeType(scope) + pathConstraints += fakeType.refTypeExpr + val refValue = instance.extractRef(scope) + pathConstraints += memory.types.evalIsSubtype(refValue, expr.type) + return@calcOnState instance + } + if (expr.type !is EtsRefType) { TODO("Not supported yet") } @@ -554,7 +563,11 @@ class TsExprResolver( val resolved = resolve(expr.instance) ?: return null if (resolved.sort != addressSort) { - logger.warn { "Calling method on non-ref instance is not yet supported" } + if (expr.callee.name == "valueOf" && expr.args.isEmpty()) { + return resolve(expr.instance) + } + + logger.warn { "Calling method on non-ref instance is not yet supported, instruction $expr" } scope.assert(falseExpr) return null } @@ -585,6 +598,13 @@ class TsExprResolver( } } + if (expr.callee.name == "Boolean") { + check(expr.args.size == 1) { "Boolean constructor should have exactly one argument" } + return resolveAfterResolved(expr.args.single()) { + mkTruthyExpr(it, scope) + } + } + if (expr.callee.name == "\$r") { // TODO hack return scope.calcOnState { @@ -612,6 +632,7 @@ class TsExprResolver( if (resolutionResult is EtsPropertyResolution.Empty) { logger.error { "Could not resolve static call: ${expr.callee}" } scope.assert(falseExpr) + // TODO mocks return null } @@ -668,8 +689,14 @@ class TsExprResolver( ): EtsPropertyResolution { // Perfect signature: if (method.enclosingClass.name != UNKNOWN_CLASS_NAME) { - val classes = ctx.scene.projectAndSdkClasses.filter { it.name == method.enclosingClass.name } - if (classes.size != 1) return EtsPropertyResolution.Empty + val classes = hierarchy.classesForType(EtsClassType(method.enclosingClass)) + if (classes.size > 1) { + val methods = classes.map { it.methods.single { it.name == method.name } } + return EtsPropertyResolution.create(methods) + } + + if (classes.isEmpty()) return EtsPropertyResolution.Empty + val clazz = classes.single() val methods = clazz.methods.filter { it.name == method.name } return EtsPropertyResolution.create(methods) @@ -814,13 +841,6 @@ class TsExprResolver( hierarchy: EtsHierarchy, ): UExpr? = with(ctx) { val resolvedAddr = if (instanceRef.isFakeObject()) instanceRef.extractRef(scope) else instanceRef - scope.doWithState { - // If we accessed some field, we make an assumption that - // this field should present in the object. - // That's not true in the common case for TS, but that's the decision we made. - val auxiliaryType = EtsAuxiliaryType(properties = setOf(field.name)) - pathConstraints += memory.types.evalIsSubtype(resolvedAddr, auxiliaryType) - } val etsField = resolveEtsField(instance, field, hierarchy) @@ -839,6 +859,14 @@ class TsExprResolver( is EtsPropertyResolution.Ambiguous -> unresolvedSort } + scope.doWithState { + // If we accessed some field, we make an assumption that + // this field should present in the object. + // That's not true in the common case for TS, but that's the decision we made. + val auxiliaryType = EtsAuxiliaryType(properties = setOf(field.name)) + pathConstraints += memory.types.evalIsSubtype(resolvedAddr, auxiliaryType) + } + // TODO // Если мы записали что-то в объект в виде bool, а потом считали это из поля bool | null, то здесь будет unresolvedSort, // мы проигнорируем что уже туда писали и считаем не оттуда diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index 7fe65c2254..0ea33e7241 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -141,6 +141,7 @@ class TsInterpreter( val s = e.stackTrace[0] "Exception .(${s.fileName}:${s.lineNumber}): $e}" } + return StepResult(forkedStates = emptySequence(), originalStateAlive = false) // todo are exceptional states properly removed? } @@ -213,11 +214,11 @@ class TsInterpreter( val limitedConcreteMethods = concreteMethods.take(5) - logger.info { - "Preparing to fork on ${limitedConcreteMethods.size} methods out of ${concreteMethods.size}: ${ - limitedConcreteMethods.map { "${it.signature.enclosingClass.name}::${it.name}" } - }" - } + // logger.info { + // "Preparing to fork on ${limitedConcreteMethods.size} methods out of ${concreteMethods.size}: ${ + // limitedConcreteMethods.map { "${it.signature.enclosingClass.name}::${it.name}" } + // }" + // } val conditionsWithBlocks = limitedConcreteMethods.map { method -> val concreteCall = stmt.toConcrete(method) val block = { state: TsState -> state.newStmt(concreteCall) } @@ -229,7 +230,7 @@ class TsInterpreter( ?: uncoveredInstance.asExpr(addressSort) // TODO mistake, should be separated into several hierarchies // or evalTypeEqual with several concrete types - memory.types.evalTypeEquals(ref, type) + memory.types.evalIsSubtype(ref, type) // TODO error } constraint to block } @@ -245,7 +246,7 @@ class TsInterpreter( val entryPoint = graph.entryPoints(stmt.callee).singleOrNull() if (entryPoint == null) { - logger.warn { "No entry point for method: ${stmt.callee}, mocking the call" } + // logger.warn { "No entry point for method: ${stmt.callee}, mocking the call" } // If the method doesn't have entry points, // we go through it, we just mock the call mockMethodCall(scope, stmt.callee.signature) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/types/TsTypeSystem.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/types/TsTypeSystem.kt index fb9206d07c..e100e461ea 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/types/TsTypeSystem.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/types/TsTypeSystem.kt @@ -191,8 +191,9 @@ class TsTypeSystem( if (classes.isEmpty() || superClasses.isEmpty()) return false // TODO log return classes.any { cls -> + val ancestors = hierarchy.getAncestor(cls) superClasses.any { superClass -> - superClass in hierarchy.getAncestor(cls) + superClass in ancestors } } } diff --git a/usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt b/usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt index 359c64a003..68adb4e9ed 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt @@ -58,7 +58,7 @@ class EtsHierarchy(private val scene: EtsScene) { val signature = superClassSignature.copy(name = typeName) val classesWithTheSameName = resolveMap[typeName] ?: run { - logger.error("No class with $superClassSignature found in the Scene") + // logger.error("No class with $superClassSignature found in the Scene") return emptyList() } diff --git a/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt b/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt index fa161bdb75..712a928220 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt @@ -4,11 +4,8 @@ import org.jacodb.ets.model.EtsScene import org.jacodb.ets.utils.CONSTRUCTOR_NAME import org.jacodb.ets.utils.INSTANCE_INIT_METHOD_NAME import org.jacodb.ets.utils.STATIC_INIT_METHOD_NAME -import org.jacodb.ets.utils.getDeclaredLocals -import org.jacodb.ets.utils.getLocals import org.jacodb.ets.utils.loadEtsProjectAutoConvert import org.junit.jupiter.api.condition.EnabledIf -import org.usvm.api.TsTestValue import org.usvm.machine.TsMachine import org.usvm.machine.TsOptions import org.usvm.util.TsMethodTestRunner @@ -39,30 +36,41 @@ class RunOnPhotosProject : TsMethodTestRunner() { @Test fun `test run on each method`() { - println("Total classes: ${scene.projectAndSdkClasses.size}") - for (clazz in scene.projectAndSdkClasses) { - println() - println("CLASS: ${clazz.name} in ${clazz.signature.file}") - for (method in clazz.methods) { - println() - println("METHOD: ${clazz.name}::${method.name}(${method.parameters.joinToString()})") - if (method.cfg.stmts.isEmpty()) { - println("CFG is empty") - continue - } - if (method.getLocals() != method.getDeclaredLocals()) { - println( - "Locals mismatch:\n getLocals() = ${ - method.getLocals().sortedBy { it.name } - }\n getDeclaredLocals() = ${ - method.getDeclaredLocals().sortedBy { it.name } - }" - ) - // continue + val exceptions = mutableListOf() + val classes = scene.projectClasses.filterNot { it.name.startsWith("%AC") } + + println("Total classes: ${classes.size}") + + classes + // .filter { it.name == "NewAlbumPage" } + .forEach { clazz -> + val methods = clazz.methods + .filterNot { it.cfg.stmts.isEmpty() } + .filterNot { it.isStatic } + .filterNot { it.name.startsWith("%AM") } + .filterNot { it.name == "build" } + .filterNot { it.name == INSTANCE_INIT_METHOD_NAME } + .filterNot { it.name == STATIC_INIT_METHOD_NAME } + .filterNot { it.name == CONSTRUCTOR_NAME } + + if (methods.isEmpty()) return@forEach + + runCatching { + val tsOptions = TsOptions() + TsMachine(scene, options, tsOptions).use { machine -> + val states = machine.analyze(methods) + states.let {} } - discoverProperties(method = method) + }.onFailure { + exceptions += it } } + + val exc = exceptions.groupBy { it } + println( + exc.values.sortedBy + { it.size }.asReversed().map + { it.first() }) } @Test @@ -90,7 +98,7 @@ class RunOnPhotosProject : TsMethodTestRunner() { fun `test on particular method`() { val method = scene.projectClasses .flatMap { it.methods } - .single { it.name == "createActionBar" && it.enclosingClass?.name == "NewAlbumBarModel" } + .single { it.name == "onSelect" && it.enclosingClass?.name == "AlbumSetPage" } val tsOptions = TsOptions() TsMachine(scene, options, tsOptions).use { machine -> diff --git a/usvm-ts/src/test/kotlin/org/usvm/util/TsMethodTestRunner.kt b/usvm-ts/src/test/kotlin/org/usvm/util/TsMethodTestRunner.kt index 58d5b1702c..f5e4ea8a95 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/util/TsMethodTestRunner.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/util/TsMethodTestRunner.kt @@ -30,7 +30,7 @@ import org.usvm.test.util.TestRunner import org.usvm.test.util.checkers.AnalysisResultsNumberMatcher import org.usvm.test.util.checkers.ignoreNumberOfAnalysisResults import kotlin.reflect.KClass -import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds typealias CoverageChecker = (TsMethodCoverage) -> Boolean @@ -340,10 +340,10 @@ abstract class TsMethodTestRunner : TestRunner "No analysis results received" } -) { it > 0 } +) { it >= 0 } val noResultsExpected = AnalysisResultsNumberMatcher( description = "No executions expected", From 78a134d2195f9610450e2f80bc6f1281019e1bca Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Fri, 20 Jun 2025 11:56:57 +0300 Subject: [PATCH 15/82] Some fixes --- .../usvm/machine/interpreter/TsInterpreter.kt | 24 +++---- .../test/kotlin/org/usvm/project/DemoCalc.kt | 62 +++++++++++-------- 2 files changed, 50 insertions(+), 36 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index 0ea33e7241..fe4a232dc4 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -26,6 +26,7 @@ import org.jacodb.ets.model.EtsThis import org.jacodb.ets.model.EtsThrowStmt import org.jacodb.ets.model.EtsType import org.jacodb.ets.model.EtsUndefinedType +import org.jacodb.ets.model.EtsUnionType import org.jacodb.ets.model.EtsUnknownType import org.jacodb.ets.model.EtsValue import org.jacodb.ets.model.EtsVoidType @@ -215,9 +216,9 @@ class TsInterpreter( val limitedConcreteMethods = concreteMethods.take(5) // logger.info { - // "Preparing to fork on ${limitedConcreteMethods.size} methods out of ${concreteMethods.size}: ${ - // limitedConcreteMethods.map { "${it.signature.enclosingClass.name}::${it.name}" } - // }" + // "Preparing to fork on ${limitedConcreteMethods.size} methods out of ${concreteMethods.size}: ${ + // limitedConcreteMethods.map { "${it.signature.enclosingClass.name}::${it.name}" } + // }" // } val conditionsWithBlocks = limitedConcreteMethods.map { method -> val concreteCall = stmt.toConcrete(method) @@ -686,18 +687,19 @@ class TsInterpreter( val parameterType = param.type if (parameterType is EtsRefType) { - val resolvedParameterType = graph.cp - .projectAndSdkClasses - .singleOrNull { it.name == parameterType.typeName } - ?.type - ?: parameterType + val resolvedParameterType = graph.hierarchy.classesForType(parameterType) + + if (resolvedParameterType.isEmpty()) { + logger.error("Cannot resolve class for parameter type: $parameterType") + return@forEachIndexed // TODO should be an error + } // Because of structural equality in TS we cannot determine the exact type // Therefore, we create information about the fields the type must consist - val auxiliaryType = (resolvedParameterType as? EtsClassType)?.toAuxiliaryType(graph.hierarchy) - ?: resolvedParameterType - state.pathConstraints += state.memory.types.evalIsSubtype(ref, auxiliaryType) + val types = resolvedParameterType.mapNotNull { it.type.toAuxiliaryType(graph.hierarchy) } + val auxiliaryType = EtsUnionType(types) // TODO error + state.pathConstraints += state.memory.types.evalIsSubtype(ref, auxiliaryType) state.pathConstraints += mkNot(mkHeapRefEq(ref, mkTsNullValue())) state.pathConstraints += mkNot(mkHeapRefEq(ref, mkUndefinedValue())) } diff --git a/usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt b/usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt index 9fc8ea5c9e..49c8a3ed2b 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt @@ -1,11 +1,11 @@ package org.usvm.project import org.jacodb.ets.model.EtsScene -import org.jacodb.ets.utils.getDeclaredLocals -import org.jacodb.ets.utils.getLocals +import org.jacodb.ets.utils.CONSTRUCTOR_NAME +import org.jacodb.ets.utils.INSTANCE_INIT_METHOD_NAME +import org.jacodb.ets.utils.STATIC_INIT_METHOD_NAME import org.jacodb.ets.utils.loadEtsProjectFromIR import org.junit.jupiter.api.condition.EnabledIf -import org.usvm.api.TsTestValue import org.usvm.machine.TsMachine import org.usvm.machine.TsOptions import org.usvm.util.TsMethodTestRunner @@ -35,30 +35,41 @@ class RunOnDemoCalcProject : TsMethodTestRunner() { @Test fun `test run on each method`() { - println("Total classes: ${scene.projectAndSdkClasses.size}") - for (clazz in scene.projectAndSdkClasses) { - println() - println("CLASS: ${clazz.name} in ${clazz.signature.file}") - for (method in clazz.methods) { - println() - println("METHOD: ${clazz.name}::${method.name}(${method.parameters.joinToString()})") - if (method.cfg.stmts.isEmpty()) { - println("CFG is empty") - continue - } - if (method.getLocals() != method.getDeclaredLocals()) { - println( - "Locals mismatch:\n getLocals() = ${ - method.getLocals().sortedBy { it.name } - }\n getDeclaredLocals() = ${ - method.getDeclaredLocals().sortedBy { it.name } - }" - ) - // continue + val exceptions = mutableListOf() + val classes = scene.projectClasses.filterNot { it.name.startsWith("%AC") } + + println("Total classes: ${classes.size}") + + classes + // .filter { it.name == "NewAlbumPage" } + .forEach { clazz -> + val methods = clazz.methods + .filterNot { it.cfg.stmts.isEmpty() } + .filterNot { it.isStatic } + .filterNot { it.name.startsWith("%AM") } + .filterNot { it.name == "build" } + .filterNot { it.name == INSTANCE_INIT_METHOD_NAME } + .filterNot { it.name == STATIC_INIT_METHOD_NAME } + .filterNot { it.name == CONSTRUCTOR_NAME } + + if (methods.isEmpty()) return@forEach + + runCatching { + val tsOptions = TsOptions() + TsMachine(scene, options, tsOptions).use { machine -> + val states = machine.analyze(methods) + states.let {} + } + }.onFailure { + exceptions += it } - discoverProperties(method = method) } - } + + val exc = exceptions.groupBy { it } + println( + exc.values.sortedBy + { it.size }.asReversed().map + { it.first() }) } @Test @@ -72,6 +83,7 @@ class RunOnDemoCalcProject : TsMethodTestRunner() { .filterNot { it.name.startsWith("%AM") } .filterNot { it.name == "build" } } + .filter { it.name == "createKvStore" && it.enclosingClass?.name == "KvStoreModel" } val tsOptions = TsOptions() TsMachine(scene, options, tsOptions).use { machine -> val states = machine.analyze(methods) From 0a79a2faa83666b624d4dfa9748ff7e33ec4a80e Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Fri, 20 Jun 2025 12:55:45 +0300 Subject: [PATCH 16/82] Fix mocker return value --- .../main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index fe4a232dc4..37ed1519d1 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -732,7 +732,7 @@ class TsInterpreter( private fun mockMethodCall(scope: TsStepScope, method: EtsMethodSignature) { scope.doWithState { if (method.returnType is EtsVoidType) { - methodResult = TsMethodResult.Success.MockedCall(method, ctx.voidValue) + methodResult = TsMethodResult.Success.MockedCall(method, ctx.mkUndefinedValue()) return@doWithState } From 9049cfc53f7e7925e81550f2e24cccf41eb56572 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 25 Jun 2025 14:03:23 +0300 Subject: [PATCH 17/82] Resolve external locals --- .../org/usvm/machine/expr/TsExprResolver.kt | 20 +++++++++--- .../usvm/machine/interpreter/TsInterpreter.kt | 3 +- .../kotlin/org/usvm/util/EtsFieldResolver.kt | 2 +- .../main/kotlin/org/usvm/util/EtsHierarchy.kt | 4 ++- .../test/kotlin/org/usvm/project/DemoCalc.kt | 32 +++++++++++++------ .../test/kotlin/org/usvm/project/Photos.kt | 6 ++-- 6 files changed, 46 insertions(+), 21 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index 3c46639760..5eeb08af1f 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -846,7 +846,7 @@ class TsExprResolver( val sort = when (etsField) { is EtsPropertyResolution.Empty -> { - logger.error("Field $etsField not found, creating fake field") + logger.error("Field $field not found, creating fake field") // If we didn't find any real fields, let's create a fake one. // It is possible due to mistakes in the IR or if the field was added explicitly // in the code. @@ -1108,17 +1108,27 @@ class TsSimpleValueResolver( val localName = local.name // Check whether this local was already created or not if (localName in scope.calcOnState { addedArtificialLocals }) { - return mkFieldLValue(ctx.addressSort, globalObject, local.name) + val sort = ctx.typeToSort(local.type) + if (sort is TsUnresolvedSort) { + return mkFieldLValue(ctx.addressSort, globalObject, local.name) + } else { + return mkFieldLValue(sort, globalObject, local.name) + } } - logger.warn { "Cannot resolve local $local, creating a fake field of the global object" } + logger.warn { "Cannot resolve local $local, creating a field of the global object" } - globalObject.createFakeField(localName, scope) scope.doWithState { addedArtificialLocals += localName } - return mkFieldLValue(ctx.addressSort, globalObject, local.name) + val sort = ctx.typeToSort(local.type) + if (sort is TsUnresolvedSort) { + globalObject.createFakeField(localName, scope) + return mkFieldLValue(ctx.addressSort, globalObject, local.name) + } else { + return mkFieldLValue(sort, globalObject, local.name) + } } val sort = scope.calcOnState { diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index 37ed1519d1..a06f9aa695 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -139,8 +139,7 @@ class TsInterpreter( } } catch (e: Exception) { logger.error { - val s = e.stackTrace[0] - "Exception .(${s.fileName}:${s.lineNumber}): $e}" + "Exception: $e\n${e.stackTrace.take(3).joinToString("\n") { " $it" }}" } return StepResult(forkedStates = emptySequence(), originalStateAlive = false) // todo are exceptional states properly removed? diff --git a/usvm-ts/src/main/kotlin/org/usvm/util/EtsFieldResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/util/EtsFieldResolver.kt index e59d43cd2b..b8bc9b6b82 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/util/EtsFieldResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/util/EtsFieldResolver.kt @@ -135,4 +135,4 @@ sealed class EtsPropertyResolution { } typealias EtsMethodName = String -typealias EtsFieldName = String \ No newline at end of file +typealias EtsFieldName = String diff --git a/usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt b/usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt index 68adb4e9ed..ad7ba431dc 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt @@ -94,7 +94,9 @@ class EtsHierarchy(private val scene: EtsScene) { } fun classesForType(etsClassType: EtsRefType): Collection { - require(etsClassType is EtsClassType || etsClassType is EtsUnclearRefType) + require(etsClassType is EtsClassType || etsClassType is EtsUnclearRefType) { + "Expected EtsClassType or EtsUnclearRefType, but got ${etsClassType::class.simpleName}" + } // We don't want to remove names like "$AC2$FieldAccess.createObject" val typeName = etsClassType.typeName.removeTrashFromTheName() diff --git a/usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt b/usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt index 49c8a3ed2b..4ec977abc5 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt @@ -1,6 +1,8 @@ package org.usvm.project import org.jacodb.ets.model.EtsScene +import org.jacodb.ets.utils.ANONYMOUS_CLASS_PREFIX +import org.jacodb.ets.utils.ANONYMOUS_METHOD_PREFIX import org.jacodb.ets.utils.CONSTRUCTOR_NAME import org.jacodb.ets.utils.INSTANCE_INIT_METHOD_NAME import org.jacodb.ets.utils.STATIC_INIT_METHOD_NAME @@ -36,17 +38,16 @@ class RunOnDemoCalcProject : TsMethodTestRunner() { @Test fun `test run on each method`() { val exceptions = mutableListOf() - val classes = scene.projectClasses.filterNot { it.name.startsWith("%AC") } + val classes = scene.projectClasses.filterNot { it.name.startsWith(ANONYMOUS_CLASS_PREFIX) } println("Total classes: ${classes.size}") classes - // .filter { it.name == "NewAlbumPage" } - .forEach { clazz -> - val methods = clazz.methods + .forEach { cls -> + val methods = cls.methods .filterNot { it.cfg.stmts.isEmpty() } .filterNot { it.isStatic } - .filterNot { it.name.startsWith("%AM") } + .filterNot { it.name.startsWith(ANONYMOUS_METHOD_PREFIX) } .filterNot { it.name == "build" } .filterNot { it.name == INSTANCE_INIT_METHOD_NAME } .filterNot { it.name == STATIC_INIT_METHOD_NAME } @@ -75,19 +76,30 @@ class RunOnDemoCalcProject : TsMethodTestRunner() { @Test fun `test run on all methods`() { val methods = scene.projectClasses - .filterNot { it.name.startsWith("%AC") } - .flatMap { - it.methods + .filterNot { cls -> cls.name.startsWith(ANONYMOUS_CLASS_PREFIX) } + .flatMap { cls -> + cls.methods .filterNot { it.cfg.stmts.isEmpty() } .filterNot { it.isStatic } - .filterNot { it.name.startsWith("%AM") } + .filterNot { it.name.startsWith(ANONYMOUS_METHOD_PREFIX) } .filterNot { it.name == "build" } } - .filter { it.name == "createKvStore" && it.enclosingClass?.name == "KvStoreModel" } val tsOptions = TsOptions() TsMachine(scene, options, tsOptions).use { machine -> val states = machine.analyze(methods) states.let {} } } + + @Test + fun `test on particular method`() { + val method = scene.projectClasses + .flatMap { it.methods } + .single { it.name == "createKvStore" && it.enclosingClass?.name == "KvStoreModel" } + val tsOptions = TsOptions() + TsMachine(scene, options, tsOptions).use { machine -> + val states = machine.analyze(listOf(method)) + states.let {} + } + } } diff --git a/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt b/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt index 712a928220..5e7d9d757f 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt @@ -1,6 +1,8 @@ package org.usvm.project import org.jacodb.ets.model.EtsScene +import org.jacodb.ets.utils.ANONYMOUS_CLASS_PREFIX +import org.jacodb.ets.utils.ANONYMOUS_METHOD_PREFIX import org.jacodb.ets.utils.CONSTRUCTOR_NAME import org.jacodb.ets.utils.INSTANCE_INIT_METHOD_NAME import org.jacodb.ets.utils.STATIC_INIT_METHOD_NAME @@ -37,7 +39,7 @@ class RunOnPhotosProject : TsMethodTestRunner() { @Test fun `test run on each method`() { val exceptions = mutableListOf() - val classes = scene.projectClasses.filterNot { it.name.startsWith("%AC") } + val classes = scene.projectClasses.filterNot { it.name.startsWith(ANONYMOUS_CLASS_PREFIX) } println("Total classes: ${classes.size}") @@ -47,7 +49,7 @@ class RunOnPhotosProject : TsMethodTestRunner() { val methods = clazz.methods .filterNot { it.cfg.stmts.isEmpty() } .filterNot { it.isStatic } - .filterNot { it.name.startsWith("%AM") } + .filterNot { it.name.startsWith(ANONYMOUS_METHOD_PREFIX) } .filterNot { it.name == "build" } .filterNot { it.name == INSTANCE_INIT_METHOD_NAME } .filterNot { it.name == STATIC_INIT_METHOD_NAME } From 399dd4dc687fb52e9222f2db11f9e712bee7af7e Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 25 Jun 2025 14:05:46 +0300 Subject: [PATCH 18/82] Rename 'expr' arg in observer --- .../src/main/kotlin/org/usvm/machine/TsInterpreterObserver.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/TsInterpreterObserver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/TsInterpreterObserver.kt index 43203c52ca..b90fda829f 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/TsInterpreterObserver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/TsInterpreterObserver.kt @@ -24,7 +24,7 @@ interface TsInterpreterObserver : UInterpreterObserver { fun onCallWithUnresolvedArguments( simpleValueResolver: TsSimpleValueResolver, - stmt: EtsCallExpr, + expr: EtsCallExpr, scope: TsStepScope, ) { // default empty implementation From 0312be1f39fd42548a24a65bb2c0bf57af1368ac Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 25 Jun 2025 14:06:23 +0300 Subject: [PATCH 19/82] Format --- .../kotlin/org/usvm/machine/interpreter/TsInterpreter.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index a06f9aa695..de5ca3626a 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -394,7 +394,12 @@ class TsInterpreter( scope ) - is TsMethodResult.Success -> observer?.onAssignStatement(exprResolver.simpleValueResolver, stmt, scope) + is TsMethodResult.Success -> observer?.onAssignStatement( + exprResolver.simpleValueResolver, + stmt, + scope + ) + is TsMethodResult.TsException -> error("Exceptions must be processed earlier") } From e72ec327de10e2b73897dc7a5967b4a8284e06ab Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 25 Jun 2025 14:20:01 +0300 Subject: [PATCH 20/82] Add special case for "Aux[length] is a supertype of Array" --- .../src/main/kotlin/org/usvm/machine/types/TsTypeSystem.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/types/TsTypeSystem.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/types/TsTypeSystem.kt index e100e461ea..2dccd9e76b 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/types/TsTypeSystem.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/types/TsTypeSystem.kt @@ -158,6 +158,13 @@ class TsTypeSystem( } if (unwrappedSupertype is EtsAuxiliaryType) { + if (unwrappedType is EtsArrayType) { + if (unwrappedSupertype.properties == setOf("length")) { + // Special case: array length is a structural property + return true + } + } + if (unwrappedType !is EtsClassType) return false // TODO arrays? val classes = hierarchy.classesForType(unwrappedType) From 09e8cb8958ecfba4bee1b7ea5c78f08cc70e911d Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 25 Jun 2025 14:29:17 +0300 Subject: [PATCH 21/82] Handle EtsArrayType in 'classesForType' --- usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt b/usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt index ad7ba431dc..a85446058e 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt @@ -1,6 +1,7 @@ package org.usvm.util import mu.KotlinLogging +import org.jacodb.ets.model.EtsArrayType import org.jacodb.ets.model.EtsClass import org.jacodb.ets.model.EtsClassSignature import org.jacodb.ets.model.EtsClassType @@ -94,6 +95,10 @@ class EtsHierarchy(private val scene: EtsScene) { } fun classesForType(etsClassType: EtsRefType): Collection { + if (etsClassType is EtsArrayType) { + return scene.sdkClasses.filter { it.name == "Array" } + } + require(etsClassType is EtsClassType || etsClassType is EtsUnclearRefType) { "Expected EtsClassType or EtsUnclearRefType, but got ${etsClassType::class.simpleName}" } From 733c861e9cc54e01675ce0501c6ff5491ce4faa9 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 25 Jun 2025 15:52:55 +0300 Subject: [PATCH 22/82] Expand and re-construct ITE with fake branches --- .../usvm/machine/interpreter/TsInterpreter.kt | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index de5ca3626a..30bc61078c 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -37,6 +37,7 @@ import org.usvm.StepScope import org.usvm.UAddressSort import org.usvm.UExpr import org.usvm.UInterpreter +import org.usvm.UIteExpr import org.usvm.api.allocateArrayInitialized import org.usvm.api.evalTypeEquals import org.usvm.api.makeSymbolicPrimitive @@ -228,9 +229,35 @@ class TsInterpreter( val ref = stmt.instance.asExpr(addressSort) .takeIf { !it.isFakeObject() } ?: uncoveredInstance.asExpr(addressSort) + + // TODO: adhoc: "expand" ITE + if (ref is UIteExpr<*>) { + val trueBranch = ref.trueBranch + val falseBranch = ref.falseBranch + if (trueBranch.isFakeObject() || falseBranch.isFakeObject()) { + val unwrappedTrueExpr = if (trueBranch.isFakeObject()) { + scope.assert(trueBranch.getFakeType(scope).refTypeExpr) + trueBranch.extractRef(scope) + } else { + trueBranch.asExpr(addressSort) + } + val unwrappedFalseExpr = if (falseBranch.isFakeObject()) { + scope.assert(falseBranch.getFakeType(scope).refTypeExpr) + falseBranch.extractRef(scope) + } else { + falseBranch.asExpr(addressSort) + } + return@calcOnState mkIte( + condition = ref.condition, + trueBranch = memory.types.evalIsSubtype(unwrappedTrueExpr, type), + falseBranch = memory.types.evalIsSubtype(unwrappedFalseExpr, type), + ) + } + } + // TODO mistake, should be separated into several hierarchies // or evalTypeEqual with several concrete types - memory.types.evalIsSubtype(ref, type) // TODO error + memory.types.evalIsSubtype(ref, type) } constraint to block } From 8c372764c4cab6dd7e5f583899425156ce79b593 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 25 Jun 2025 15:53:26 +0300 Subject: [PATCH 23/82] Handle ptr call --- .../org/usvm/machine/interpreter/TsInterpreter.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index 30bc61078c..5e5745d303 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -17,6 +17,7 @@ import org.jacodb.ets.model.EtsNopStmt import org.jacodb.ets.model.EtsNullType import org.jacodb.ets.model.EtsNumberType import org.jacodb.ets.model.EtsParameterRef +import org.jacodb.ets.model.EtsPtrCallExpr import org.jacodb.ets.model.EtsRefType import org.jacodb.ets.model.EtsReturnStmt import org.jacodb.ets.model.EtsStaticFieldRef @@ -430,6 +431,11 @@ class TsInterpreter( is TsMethodResult.TsException -> error("Exceptions must be processed earlier") } + if (it is EtsPtrCallExpr) { + mockMethodCall(scope, it.callee) + return + } + if (!tsOptions.interproceduralAnalysis && methodResult == TsMethodResult.NoCall) { mockMethodCall(scope, it.callee) return @@ -629,6 +635,11 @@ class TsInterpreter( return } + if (stmt.expr is EtsPtrCallExpr) { + mockMethodCall(scope, stmt.expr.callee) + return + } + if (tsOptions.interproceduralAnalysis) { val exprResolver = exprResolverWithScope(scope) exprResolver.resolve(stmt.expr) ?: return From 336c4b2c406c69992778d57859f374a40e522833 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 25 Jun 2025 15:55:07 +0300 Subject: [PATCH 24/82] Remove unnecessary doWithState --- .../main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index 5e5745d303..58740624a7 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -161,9 +161,7 @@ class TsInterpreter( // TODO support primitives calls // We ignore the possibility of method call on primitives. // Therefore, the fake object should be unwrapped. - scope.doWithState { - scope.assert(concreteRef.getFakeType(scope).refTypeExpr) - } + scope.assert(concreteRef.getFakeType(scope).refTypeExpr) concreteRef.extractRef(scope) } else { concreteRef From a63c04194a43587e48398ccfea9f1f030532a7a0 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 25 Jun 2025 15:55:33 +0300 Subject: [PATCH 25/82] USE CALLEE INSTEAD OF METHOD --- .../main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index 58740624a7..668954d590 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -647,7 +647,7 @@ class TsInterpreter( } // intraprocedural analysis - mockMethodCall(scope, stmt.method.signature) + mockMethodCall(scope, stmt.expr.callee) } private fun visitThrowStmt(scope: TsStepScope, stmt: EtsThrowStmt) { From 5ce84c9eaceba33a378baaf62170acbb3ab7cc5f Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 25 Jun 2025 15:55:51 +0300 Subject: [PATCH 26/82] Do not warn about unresolved 'then' method --- .../main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index 668954d590..555145b9ee 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -205,7 +205,9 @@ class TsInterpreter( } else { val methods = resolveEtsMethods(stmt.callee) if (methods.isEmpty()) { - logger.warn { "Could not resolve method: ${stmt.callee}" } + if (stmt.callee.name !in listOf("then")) { + logger.warn { "Could not resolve method: ${stmt.callee}" } + } scope.assert(falseExpr) return } From d86341947921cad688ecc68cfd6ba1d08c77002b Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 25 Jun 2025 16:01:04 +0300 Subject: [PATCH 27/82] Cherry-pick main --- .../src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt | 2 +- .../main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index 5eeb08af1f..fb5fb7b125 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -328,7 +328,7 @@ class TsExprResolver( } logger.error { "visit(${expr::class.simpleName}) is not implemented yet" } - return mkFp64(ADHOC_STRING) + error("Not supported $expr") } override fun visit(expr: EtsDeleteExpr): UExpr? { diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index 555145b9ee..9e83f34c24 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -729,6 +729,8 @@ class TsInterpreter( val parameterType = param.type if (parameterType is EtsRefType) { + val argLValue = mkRegisterStackLValue(addressSort, i) + val ref = state.memory.read(argLValue).asExpr(addressSort) val resolvedParameterType = graph.hierarchy.classesForType(parameterType) if (resolvedParameterType.isEmpty()) { @@ -742,6 +744,7 @@ class TsInterpreter( val auxiliaryType = EtsUnionType(types) // TODO error state.pathConstraints += state.memory.types.evalIsSubtype(ref, auxiliaryType) + state.pathConstraints += mkNot(mkHeapRefEq(ref, mkTsNullValue())) state.pathConstraints += mkNot(mkHeapRefEq(ref, mkUndefinedValue())) } From 3b09c31c267594a3ccc82e26329d79f5dae134db Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 25 Jun 2025 16:22:29 +0300 Subject: [PATCH 28/82] Fix printing of exceptions --- usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt | 8 ++++---- usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt b/usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt index 4ec977abc5..e4f511b088 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt @@ -67,10 +67,10 @@ class RunOnDemoCalcProject : TsMethodTestRunner() { } val exc = exceptions.groupBy { it } - println( - exc.values.sortedBy - { it.size }.asReversed().map - { it.first() }) + println("Total exceptions: ${exc.size}") + for (es in exc.values.sortedBy { it.size }.asReversed()) { + println("${es.first()}") + } } @Test diff --git a/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt b/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt index 5e7d9d757f..99fbb2ba68 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt @@ -69,10 +69,10 @@ class RunOnPhotosProject : TsMethodTestRunner() { } val exc = exceptions.groupBy { it } - println( - exc.values.sortedBy - { it.size }.asReversed().map - { it.first() }) + println("Total exceptions: ${exc.size}") + for (es in exc.values.sortedBy { it.size }.asReversed()) { + println("${es.first()}") + } } @Test From 226cc5d25bb639e0f9f2b7f3ee0ee2a88aee9c33 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 25 Jun 2025 16:22:40 +0300 Subject: [PATCH 29/82] Support 'toString' --- usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index fb5fb7b125..b03ae17cf0 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -519,7 +519,7 @@ class TsExprResolver( } if (expr.callee.name == "toString") { - return TODO() + return mkStringConstant("I am string", scope) } // TODO write tests From 5a71b32a9cc0b7418b1e6086ceffe02aab3c1cd5 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 25 Jun 2025 16:22:47 +0300 Subject: [PATCH 30/82] Format --- usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index b03ae17cf0..39fcbe5199 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -1122,7 +1122,7 @@ class TsSimpleValueResolver( addedArtificialLocals += localName } - val sort = ctx.typeToSort(local.type) + val sort = ctx.typeToSort(local.type) if (sort is TsUnresolvedSort) { globalObject.createFakeField(localName, scope) return mkFieldLValue(ctx.addressSort, globalObject, local.name) From e17b09e3f4f2b6d03980be6b2eb980502910dcb7 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 25 Jun 2025 16:23:11 +0300 Subject: [PATCH 31/82] Handle fake objects and ITE with fake branches --- .../org/usvm/machine/expr/TsExprResolver.kt | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index 39fcbe5199..ac5b6a1be5 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -86,6 +86,7 @@ import org.usvm.UBoolSort import org.usvm.UConcreteHeapRef import org.usvm.UExpr import org.usvm.UHeapRef +import org.usvm.UIteExpr import org.usvm.USort import org.usvm.api.allocateArray import org.usvm.api.evalTypeEquals @@ -319,7 +320,41 @@ class TsExprResolver( condition = mkHeapRefEq(ref, mkUndefinedValue()), trueBranch = mkStringConstant("undefined", scope), falseBranch = mkIte( - condition = scope.calcOnState { memory.types.evalTypeEquals(ref, EtsStringType) }, + condition = scope.calcOnState { + val unwrappedRef = if (ref.isFakeObject()) { + scope.assert(ref.getFakeType(scope).refTypeExpr) + ref.extractRef(scope) + } else { + ref.asExpr(addressSort) + } + + // TODO: adhoc: "expand" ITE + if (unwrappedRef is UIteExpr<*>) { + val trueBranch = unwrappedRef.trueBranch + val falseBranch = unwrappedRef.falseBranch + if (trueBranch.isFakeObject() || falseBranch.isFakeObject()) { + val unwrappedTrueExpr = if (trueBranch.isFakeObject()) { + scope.assert(trueBranch.getFakeType(scope).refTypeExpr) + trueBranch.extractRef(scope) + } else { + trueBranch.asExpr(addressSort) + } + val unwrappedFalseExpr = if (falseBranch.isFakeObject()) { + scope.assert(falseBranch.getFakeType(scope).refTypeExpr) + falseBranch.extractRef(scope) + } else { + falseBranch.asExpr(addressSort) + } + return@calcOnState mkIte( + condition = unwrappedRef.condition, + trueBranch = memory.types.evalTypeEquals(unwrappedTrueExpr, EtsStringType), + falseBranch = memory.types.evalTypeEquals(unwrappedFalseExpr, EtsStringType), + ) + } + } + + memory.types.evalTypeEquals(unwrappedRef, EtsStringType) + }, trueBranch = mkStringConstant("string", scope), falseBranch = mkStringConstant("object", scope), ) From f0fd064c64985b31b505de622e709e5d8f1db696 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Thu, 26 Jun 2025 15:41:39 +0300 Subject: [PATCH 32/82] Fix virtual invokes --- .../usvm/machine/interpreter/TsInterpreter.kt | 32 +++++++++++++++++-- .../org/usvm/machine/types/TsTypeSystem.kt | 2 +- .../kotlin/org/usvm/util/EtsFieldResolver.kt | 6 ++-- .../main/kotlin/org/usvm/util/EtsHierarchy.kt | 2 +- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index 9e83f34c24..dddaca2c26 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -36,6 +36,7 @@ import org.jacodb.ets.utils.getDeclaredLocals import org.usvm.StepResult import org.usvm.StepScope import org.usvm.UAddressSort +import org.usvm.UConcreteHeapRef import org.usvm.UExpr import org.usvm.UInterpreter import org.usvm.UIteExpr @@ -69,6 +70,7 @@ import org.usvm.machine.types.mkFakeValue import org.usvm.machine.types.toAuxiliaryType import org.usvm.sizeSort import org.usvm.targets.UTargetsSet +import org.usvm.types.TypesResult import org.usvm.types.single import org.usvm.util.EtsPropertyResolution import org.usvm.util.mkArrayIndexLValue @@ -214,14 +216,40 @@ class TsInterpreter( concreteMethods += methods } - val limitedConcreteMethods = concreteMethods.take(5) + + val possibleTypes = scope.calcOnState { models.first().typeStreamOf(resolvedInstance as UConcreteHeapRef) } + .take(1000) + + if (possibleTypes !is TypesResult.SuccessfulTypesResult) { + error("TODO")// is it right? + } + + val possibleTypesSet = possibleTypes.types.toSet() + val methodsDeclaringClasses = concreteMethods.mapNotNull { it.enclosingClass } // is it right? + val typeSystem = typeSystem() + + // take only possible classes with a corresponding method + val intersectedTypes = possibleTypesSet.filter { possibleType -> + methodsDeclaringClasses.any { typeSystem.isSupertype(it.type, possibleType) } + } + + // + val chosenMethods = intersectedTypes.flatMap { + graph.hierarchy.classesForType(it as EtsRefType) + .asSequence() + // TODO wrong order, ancestors are unordered + .map { graph.hierarchy.getAncestors(it) } + .map { it.first { it.methods.any { it.name == stmt.callee.name } } } + .map { it.methods.first { it.name == stmt.callee.name } } + }.toList().take(10) // TODO check it // logger.info { // "Preparing to fork on ${limitedConcreteMethods.size} methods out of ${concreteMethods.size}: ${ // limitedConcreteMethods.map { "${it.signature.enclosingClass.name}::${it.name}" } // }" // } - val conditionsWithBlocks = limitedConcreteMethods.map { method -> + + val conditionsWithBlocks = chosenMethods.mapIndexed { i, method -> val concreteCall = stmt.toConcrete(method) val block = { state: TsState -> state.newStmt(concreteCall) } val type = requireNotNull(method.enclosingClass).type diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/types/TsTypeSystem.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/types/TsTypeSystem.kt index 2dccd9e76b..41ff0541eb 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/types/TsTypeSystem.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/types/TsTypeSystem.kt @@ -198,7 +198,7 @@ class TsTypeSystem( if (classes.isEmpty() || superClasses.isEmpty()) return false // TODO log return classes.any { cls -> - val ancestors = hierarchy.getAncestor(cls) + val ancestors = hierarchy.getAncestors(cls) superClasses.any { superClass -> superClass in ancestors } diff --git a/usvm-ts/src/main/kotlin/org/usvm/util/EtsFieldResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/util/EtsFieldResolver.kt index b8bc9b6b82..5515850437 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/util/EtsFieldResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/util/EtsFieldResolver.kt @@ -86,11 +86,11 @@ private fun tryGetSingleField( } fun EtsClass.getAllFields(hierarchy: EtsHierarchy): List { - return hierarchy.getAncestor(this).flatMap { it.fields } + return hierarchy.getAncestors(this).flatMap { it.fields } } fun EtsClass.getAllMethods(hierarchy: EtsHierarchy): List { - return hierarchy.getAncestor(this).flatMap { it.methods } + return hierarchy.getAncestors(this).flatMap { it.methods } } fun EtsClass.getAllPropertiesCombined(hierarchy: EtsHierarchy): Set { @@ -102,7 +102,7 @@ fun EtsClass.getAllProperties(hierarchy: EtsHierarchy): Pair, val allFields = hashSetOf() val allMethods = hashSetOf() - val classes = hierarchy.getAncestor(this) + val classes = hierarchy.getAncestors(this) classes.forEach { val fieldNames = it.fields.map { it.name } diff --git a/usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt b/usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt index a85446058e..55278b1b6a 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt @@ -82,7 +82,7 @@ class EtsHierarchy(private val scene: EtsScene) { result } - fun getAncestor(clazz: EtsClass): Set { + fun getAncestors(clazz: EtsClass): Set { return ancestors[clazz] ?: run { error("TODO") } From e1dbb6296b5bcec634e2961afc200a4a96736e1f Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Thu, 26 Jun 2025 16:12:42 +0300 Subject: [PATCH 33/82] Small dot modification --- buildSrc/src/main/kotlin/Dependencies.kt | 2 +- .../src/main/kotlin/org/usvm/machine/state/TsState.kt | 11 +++++++++++ .../test/kotlin/org/usvm/util/TsMethodTestRunner.kt | 7 ++++--- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 482eec6836..eab8f1422e 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -6,7 +6,7 @@ object Versions { const val clikt = "5.0.0" const val detekt = "1.23.7" const val ini4j = "0.5.4" - const val jacodb = "77b83e4127" + const val jacodb = "238c1ccd14" const val juliet = "1.3.2" const val junit = "5.9.3" const val kotlin = "2.1.0" diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt index 38d8b66d18..404bff72e4 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt @@ -8,6 +8,8 @@ import org.jacodb.ets.model.EtsMethod import org.jacodb.ets.model.EtsStmt import org.jacodb.ets.model.EtsType import org.jacodb.ets.model.EtsValue +import org.jacodb.ets.utils.toHighlightedDot +import org.jacodb.ets.utils.view import org.usvm.PathNode import org.usvm.UCallStack import org.usvm.UConcreteHeapRef @@ -155,6 +157,15 @@ class TsState( ) } + fun renderEntryPointGraph() { + view( + entrypoint.cfg.toHighlightedDot( + pathNode.allStatements.toSet(), + currentStatement, + ) + ) + } + override val isExceptional: Boolean get() = methodResult is TsMethodResult.TsException } diff --git a/usvm-ts/src/test/kotlin/org/usvm/util/TsMethodTestRunner.kt b/usvm-ts/src/test/kotlin/org/usvm/util/TsMethodTestRunner.kt index f5e4ea8a95..1dcc2921bf 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/util/TsMethodTestRunner.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/util/TsMethodTestRunner.kt @@ -30,6 +30,7 @@ import org.usvm.test.util.TestRunner import org.usvm.test.util.checkers.AnalysisResultsNumberMatcher import org.usvm.test.util.checkers.ignoreNumberOfAnalysisResults import kotlin.reflect.KClass +import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds typealias CoverageChecker = (TsMethodCoverage) -> Boolean @@ -340,10 +341,10 @@ abstract class TsMethodTestRunner : TestRunner Date: Fri, 27 Jun 2025 16:28:45 +0300 Subject: [PATCH 34/82] Add visualization --- buildSrc/src/main/kotlin/Dependencies.kt | 2 +- .../usvm/machine/interpreter/TsInterpreter.kt | 6 ++++ .../kotlin/org/usvm/machine/state/TsState.kt | 35 ++++++++++++++----- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index eab8f1422e..77f695106e 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -6,7 +6,7 @@ object Versions { const val clikt = "5.0.0" const val detekt = "1.23.7" const val ini4j = "0.5.4" - const val jacodb = "238c1ccd14" + const val jacodb = "b9c0280198" const val juliet = "1.3.2" const val junit = "5.9.3" const val kotlin = "2.1.0" diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index dddaca2c26..816b470c73 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -124,6 +124,10 @@ class TsInterpreter( // if exception, see above // if no call, visit + scope.doWithState { + renderGraph() + } + try { when (stmt) { is TsVirtualMethodCallStmt -> visitVirtualMethodCall(scope, stmt) @@ -313,6 +317,8 @@ class TsInterpreter( } scope.doWithState { + registerCallee(stmt.returnSite, stmt.callee.cfg) + val args = mutableListOf>() val numActual = stmt.args.size val numFormal = stmt.callee.parameters.size diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt index 404bff72e4..b4dc03e0ce 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt @@ -1,5 +1,6 @@ package org.usvm.machine.state +import org.jacodb.ets.model.EtsBlockCfg import org.jacodb.ets.model.EtsClass import org.jacodb.ets.model.EtsClassSignature import org.jacodb.ets.model.EtsClassType @@ -8,8 +9,9 @@ import org.jacodb.ets.model.EtsMethod import org.jacodb.ets.model.EtsStmt import org.jacodb.ets.model.EtsType import org.jacodb.ets.model.EtsValue -import org.jacodb.ets.utils.toHighlightedDot -import org.jacodb.ets.utils.view +import org.jacodb.ets.utils.InterproceduralCfg +import org.jacodb.ets.utils.renderDotOverwrite +import org.jacodb.ets.utils.toHighlightedDotWithCalls import org.usvm.PathNode import org.usvm.UCallStack import org.usvm.UConcreteHeapRef @@ -22,6 +24,7 @@ import org.usvm.collections.immutable.implementations.immutableMap.UPersistentHa import org.usvm.collections.immutable.internal.MutabilityOwnership import org.usvm.collections.immutable.persistentHashMapOf import org.usvm.constraints.UPathConstraints +import org.usvm.dataflow.ts.util.toMap import org.usvm.machine.TsContext import org.usvm.memory.ULValue import org.usvm.memory.UMemory @@ -33,6 +36,9 @@ import org.usvm.util.type * [lValuesToAllocatedFakeObjects] contains records of l-values that were allocated with newly created fake objects. * It is important for result interpreters to be able to restore the order of fake objects allocation and * their assignment to evaluated l-values. + * + * [discoveredCallees] is a map from each [EtsStmt] with EtsCallExpr inside and its parent block id to its + * analyzed callee CFG. This grows dynamically as you step through the calls. */ class TsState( ctx: TsContext, @@ -51,6 +57,7 @@ class TsState( val globalObject: UConcreteHeapRef = memory.allocStatic(EtsClassType(EtsClassSignature.UNKNOWN)), val addedArtificialLocals: MutableSet = hashSetOf(), val lValuesToAllocatedFakeObjects: MutableList, UConcreteHeapRef>> = mutableListOf(), + var discoveredCallees: UPersistentHashMap, EtsBlockCfg> = persistentHashMapOf(), ) : UState( ctx = ctx, initOwnership = ownership, @@ -88,6 +95,16 @@ class TsState( localToSortStack.removeLast() } + fun registerCallee(stmt: EtsStmt, cfg: EtsBlockCfg) { + val parentId = stmt.location.method.cfg.blocks.indexOfFirst { it.statements.contains(stmt) } + .takeIf { it >= 0 } ?: error("Statement $stmt is not found in the method CFG") + + val key = stmt to parentId + if (!discoveredCallees.containsKey(key)) { + discoveredCallees = discoveredCallees.put(key, cfg, ownership) + } + } + fun pushSortsForArguments( instance: EtsLocal?, args: List, @@ -154,16 +171,18 @@ class TsState( globalObject = globalObject, addedArtificialLocals = addedArtificialLocals, lValuesToAllocatedFakeObjects = lValuesToAllocatedFakeObjects.toMutableList(), + discoveredCallees = discoveredCallees, ) } - fun renderEntryPointGraph() { - view( - entrypoint.cfg.toHighlightedDot( - pathNode.allStatements.toSet(), - currentStatement, - ) + fun renderGraph() { + val graph = InterproceduralCfg(main = entrypoint.cfg, callees = discoveredCallees.toMap()) + val dot = graph.toHighlightedDotWithCalls( + pathStmts = pathNode.allStatements.toSet(), + currentStmt = currentStatement ) + + renderDotOverwrite(dot) } override val isExceptional: Boolean From 4a3fcc3a19dce60e9b47372e872e39902cdef6eb Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Fri, 27 Jun 2025 16:31:20 +0300 Subject: [PATCH 35/82] Add visualization --- buildSrc/src/main/kotlin/Dependencies.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 77f695106e..ca1b92c8d0 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -6,7 +6,7 @@ object Versions { const val clikt = "5.0.0" const val detekt = "1.23.7" const val ini4j = "0.5.4" - const val jacodb = "b9c0280198" + const val jacodb = "cfb5539dcd" const val juliet = "1.3.2" const val junit = "5.9.3" const val kotlin = "2.1.0" From 281a96c85e27a67f39e844b2f420eb27628a8d1f Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Mon, 30 Jun 2025 12:06:24 +0300 Subject: [PATCH 36/82] Visualization --- buildSrc/src/main/kotlin/Dependencies.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index ca1b92c8d0..17f3f22abc 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -6,7 +6,7 @@ object Versions { const val clikt = "5.0.0" const val detekt = "1.23.7" const val ini4j = "0.5.4" - const val jacodb = "cfb5539dcd" + const val jacodb = "5889d3c784" const val juliet = "1.3.2" const val junit = "5.9.3" const val kotlin = "2.1.0" From a5770959abf6c1d48449976036d97f16dba23553 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Mon, 30 Jun 2025 12:17:20 +0300 Subject: [PATCH 37/82] Visualization --- usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt | 5 +++++ usvm-ts/src/main/kotlin/org/usvm/machine/TsOptions.kt | 1 + .../org/usvm/machine/interpreter/TsInterpreter.kt | 4 ---- .../main/kotlin/org/usvm/util/TsStateVisualizer.kt | 11 +++++++++++ 4 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 usvm-ts/src/main/kotlin/org/usvm/util/TsStateVisualizer.kt diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt index fa07056a96..9e3a8d0ec8 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt @@ -28,6 +28,7 @@ import org.usvm.statistics.distances.CfgStatisticsImpl import org.usvm.statistics.distances.PlainCallGraphStatistics import org.usvm.stopstrategies.StopStrategy import org.usvm.stopstrategies.createStopStrategy +import org.usvm.util.TsStateVisualizer import org.usvm.util.humanReadableSignature import kotlin.time.Duration.Companion.seconds @@ -96,6 +97,10 @@ class TsMachine( val observers = mutableListOf>(coverageStatistics) observers.add(statesCollector) + if (tsOptions.enableVisualization) { + observers += TsStateVisualizer() + } + if (options.useSoftConstraints) { observers.add(SoftConstraintsObserver()) } diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/TsOptions.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/TsOptions.kt index cb2d36d91b..950dd9aff7 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/TsOptions.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/TsOptions.kt @@ -2,4 +2,5 @@ package org.usvm.machine data class TsOptions( val interproceduralAnalysis: Boolean = true, + val enableVisualization: Boolean = false, ) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index 816b470c73..9e527b910f 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -124,10 +124,6 @@ class TsInterpreter( // if exception, see above // if no call, visit - scope.doWithState { - renderGraph() - } - try { when (stmt) { is TsVirtualMethodCallStmt -> visitVirtualMethodCall(scope, stmt) diff --git a/usvm-ts/src/main/kotlin/org/usvm/util/TsStateVisualizer.kt b/usvm-ts/src/main/kotlin/org/usvm/util/TsStateVisualizer.kt new file mode 100644 index 0000000000..e99f5d745e --- /dev/null +++ b/usvm-ts/src/main/kotlin/org/usvm/util/TsStateVisualizer.kt @@ -0,0 +1,11 @@ +package org.usvm.util + +import org.usvm.machine.TsInterpreterObserver +import org.usvm.machine.state.TsState +import org.usvm.statistics.UMachineObserver + +class TsStateVisualizer : TsInterpreterObserver, UMachineObserver { + override fun onStatePeeked(state: TsState) { + state.renderGraph() + } +} \ No newline at end of file From e7e3757187843b2b721e9db9f6266fcc90114ec6 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Mon, 30 Jun 2025 16:26:02 +0300 Subject: [PATCH 38/82] Move test --- .../test/kotlin/org/usvm/project/Photos.kt | 6 ++-- .../usvm/samples/arrays/AllocatedArrays.kt | 31 +------------------ .../org/usvm/samples/arrays/InputArrays.kt | 29 +++++++++++++++++ .../samples/arrays/AllocatedArrays.ts | 13 -------- .../resources/samples/arrays/InputArrays.ts | 13 ++++++++ 5 files changed, 46 insertions(+), 46 deletions(-) diff --git a/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt b/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt index 99fbb2ba68..41ae13d4ff 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt @@ -7,7 +7,7 @@ import org.jacodb.ets.utils.CONSTRUCTOR_NAME import org.jacodb.ets.utils.INSTANCE_INIT_METHOD_NAME import org.jacodb.ets.utils.STATIC_INIT_METHOD_NAME import org.jacodb.ets.utils.loadEtsProjectAutoConvert -import org.junit.jupiter.api.condition.EnabledIf +import org.junit.jupiter.api.Disabled import org.usvm.machine.TsMachine import org.usvm.machine.TsOptions import org.usvm.util.TsMethodTestRunner @@ -15,8 +15,8 @@ import org.usvm.util.getResourcePath import org.usvm.util.getResourcePathOrNull import kotlin.test.Test -@EnabledIf("projectAvailable") -// @Disabled +// @EnabledIf("projectAvailable") +@Disabled class RunOnPhotosProject : TsMethodTestRunner() { companion object { diff --git a/usvm-ts/src/test/kotlin/org/usvm/samples/arrays/AllocatedArrays.kt b/usvm-ts/src/test/kotlin/org/usvm/samples/arrays/AllocatedArrays.kt index 70d8be3e9d..250690c3df 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/samples/arrays/AllocatedArrays.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/samples/arrays/AllocatedArrays.kt @@ -109,33 +109,4 @@ class AllocatedArrays : TsMethodTestRunner() { { r -> r is TsTestValue.TsException }, ) } - - @Test - fun `test readFakeObjectAndWriteFakeObject`() { - val method = getMethod(className, "readFakeObjectAndWriteFakeObject") - discoverProperties, TsTestValue, TsTestValue>( - method = method, - { x, y, r -> - val fst = x.values[0] - val fstCondition = fst is TsTestValue.TsNumber && fst.number == 1.0 - val sndCondition = y is TsTestValue.TsNumber && y.number == 2.0 - val resultCondition = r is TsTestValue.TsArray<*> && r.values[0] == y - - fstCondition && sndCondition && resultCondition - }, - { x, y, r -> - val fst = x.values[0] - val fstCondition = fst is TsTestValue.TsNumber && fst.number == 1.0 - val sndCondition = y !is TsTestValue.TsNumber || y.number != 2.0 - val resultCondition = r is TsTestValue.TsArray<*> && r.values[0] == y - - fstCondition && sndCondition && resultCondition - }, - { x, y, r -> - val fst = x.values[0] - val condition = fst !is TsTestValue.TsNumber || fst.number != 1.0 - condition && r is TsTestValue.TsArray<*> && r.values == x.values - }, - ) - } -} \ No newline at end of file +} diff --git a/usvm-ts/src/test/kotlin/org/usvm/samples/arrays/InputArrays.kt b/usvm-ts/src/test/kotlin/org/usvm/samples/arrays/InputArrays.kt index c808a9f989..907d8df949 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/samples/arrays/InputArrays.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/samples/arrays/InputArrays.kt @@ -127,4 +127,33 @@ class InputArrays : TsMethodTestRunner() { }, ) } + + @Test + fun `test readFakeObjectAndWriteFakeObject`() { + val method = getMethod(className, "readFakeObjectAndWriteFakeObject") + discoverProperties, TsTestValue, TsTestValue>( + method = method, + { x, y, r -> + val fst = x.values[0] + val fstCondition = fst is TsTestValue.TsNumber && fst.number == 1.0 + val sndCondition = y is TsTestValue.TsNumber && y.number == 2.0 + val resultCondition = r is TsTestValue.TsArray<*> && r.values[0] == y + + fstCondition && sndCondition && resultCondition + }, + { x, y, r -> + val fst = x.values[0] + val fstCondition = fst is TsTestValue.TsNumber && fst.number == 1.0 + val sndCondition = y !is TsTestValue.TsNumber || y.number != 2.0 + val resultCondition = r is TsTestValue.TsArray<*> && r.values[0] == y + + fstCondition && sndCondition && resultCondition + }, + { x, y, r -> + val fst = x.values[0] + val condition = fst !is TsTestValue.TsNumber || fst.number != 1.0 + condition && r is TsTestValue.TsArray<*> && r.values == x.values + }, + ) + } } diff --git a/usvm-ts/src/test/resources/samples/arrays/AllocatedArrays.ts b/usvm-ts/src/test/resources/samples/arrays/AllocatedArrays.ts index cb74a0db8d..ccf4c505dd 100644 --- a/usvm-ts/src/test/resources/samples/arrays/AllocatedArrays.ts +++ b/usvm-ts/src/test/resources/samples/arrays/AllocatedArrays.ts @@ -63,17 +63,4 @@ class AllocatedArrays { x[3] = 4 return x } - - readFakeObjectAndWriteFakeObject(x: any[], y) { - if (x[0] === 1) { - x[0] = y - if (x[0] === 2) { - return x - } else { - return x - } - } - - return x - } } diff --git a/usvm-ts/src/test/resources/samples/arrays/InputArrays.ts b/usvm-ts/src/test/resources/samples/arrays/InputArrays.ts index 2917303c0f..be8eec7410 100644 --- a/usvm-ts/src/test/resources/samples/arrays/InputArrays.ts +++ b/usvm-ts/src/test/resources/samples/arrays/InputArrays.ts @@ -45,4 +45,17 @@ class InputArrays { } return x[0] } + + readFakeObjectAndWriteFakeObject(x: any[], y) { + if (x[0] === 1) { + x[0] = y + if (x[0] === 2) { + return x + } else { + return x + } + } + + return x + } } From 63caf797b217e1b7f29d2a8f028ebd45666ba441 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Mon, 30 Jun 2025 16:31:31 +0300 Subject: [PATCH 39/82] Fix arrays --- .../kotlin/org/usvm/machine/interpreter/TsInterpreter.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index 9e527b910f..eaa73fe86a 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -761,6 +761,11 @@ class TsInterpreter( if (parameterType is EtsRefType) { val argLValue = mkRegisterStackLValue(addressSort, i) val ref = state.memory.read(argLValue).asExpr(addressSort) + if (parameterType is EtsArrayType) { + state.pathConstraints += state.memory.types.evalTypeEquals(ref, parameterType) + return@forEachIndexed + } + val resolvedParameterType = graph.hierarchy.classesForType(parameterType) if (resolvedParameterType.isEmpty()) { From c17d54659dba1c315873b36d75b08a9635fd6e0b Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Tue, 1 Jul 2025 12:31:09 +0300 Subject: [PATCH 40/82] Fix type streams --- .../src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt | 3 ++- .../main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index ac5b6a1be5..8664a5f083 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -899,7 +899,8 @@ class TsExprResolver( // this field should present in the object. // That's not true in the common case for TS, but that's the decision we made. val auxiliaryType = EtsAuxiliaryType(properties = setOf(field.name)) - pathConstraints += memory.types.evalIsSubtype(resolvedAddr, auxiliaryType) + // assert is required to update models + scope.assert(memory.types.evalIsSubtype(resolvedAddr, auxiliaryType)) } // TODO diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index eaa73fe86a..856c127c5b 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -560,7 +560,8 @@ class TsInterpreter( // If we access some field, we expect that the object must have this field. // It is not always true for TS, but we decided to process it so. val supertype = EtsAuxiliaryType(properties = setOf(lhv.field.name)) - pathConstraints += memory.types.evalIsSubtype(instance, supertype) + // assert is required to update models + scope.assert(memory.types.evalIsSubtype(instance, supertype)) } // If there is no such field, we create a fake field for the expr From 3ab240ec596c5200a043e6c45b40b7270ad2deca Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Tue, 1 Jul 2025 14:24:14 +0300 Subject: [PATCH 41/82] Remove forking warning --- usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index 8664a5f083..7ad2c6c152 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -693,8 +693,6 @@ class TsExprResolver( ctx.mkTrue() to { state: TsState -> state.newStmt(it) } } // TODO error with true Expr - logger.warn { "Forking on ${blocks.size} branches" } - scope.forkMulti(blocks) return null From 0dd77963f78d7fc1e8bec32fb200b38f0df24c3b Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Tue, 1 Jul 2025 14:25:19 +0300 Subject: [PATCH 42/82] Warn only when necessary --- usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt b/usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt index 55278b1b6a..213a58b50e 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/util/EtsHierarchy.kt @@ -49,7 +49,9 @@ class EtsHierarchy(private val scene: EtsScene) { } } - logger.warn { "Ancestors map is built in $time ms" } + if (time > 100) { + logger.warn { "Ancestors map is built in $time ms" } + } return@lazy result } From 258a0d8ba962aced0ef60f1f1c34760946c4b027 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Tue, 1 Jul 2025 14:58:59 +0300 Subject: [PATCH 43/82] Refine operators --- .../usvm/machine/operator/TsBinaryOperator.kt | 201 ++++-------------- 1 file changed, 47 insertions(+), 154 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/operator/TsBinaryOperator.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/operator/TsBinaryOperator.kt index 2a1094918b..f5c8be7829 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/operator/TsBinaryOperator.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/operator/TsBinaryOperator.kt @@ -595,6 +595,7 @@ sealed interface TsBinaryOperator { rhsValue = rhs.extractBool(scope) rhsType.boolTypeExpr } + fp64Sort -> { rhsValue = rhs.extractFp(scope) rhsType.fpTypeExpr @@ -629,7 +630,7 @@ sealed interface TsBinaryOperator { // Strict equality checks that both sides are of the same type, // therefore they have to be processed in the other methods. // Otherwise, they would have the same sorts. - return lhs.ctx.mkFalse() + return mkFalse() } } @@ -974,52 +975,39 @@ sealed interface TsBinaryOperator { } } - data object Sub : TsBinaryOperator { - override fun TsContext.onBool( - lhs: UBoolExpr, - rhs: UBoolExpr, - scope: TsStepScope, - ): UExpr<*> { - return mkFpSubExpr(fpRoundingModeSortDefaultValue(), boolToFp(lhs), boolToFp(rhs)) - } - - override fun TsContext.onFp( + data object Sub : TsArithmeticOperator { + override fun TsContext.apply( lhs: UExpr, rhs: UExpr, - scope: TsStepScope, - ): UExpr<*> { + ): UExpr { return mkFpSubExpr(fpRoundingModeSortDefaultValue(), lhs, rhs) } + } - override fun TsContext.onRef( - lhs: UHeapRef, - rhs: UHeapRef, - scope: TsStepScope, - ): UExpr<*> { - return internalResolve(lhs, rhs, scope) + data object Mul : TsArithmeticOperator { + override fun TsContext.apply( + lhs: UExpr, + rhs: UExpr, + ): UExpr { + return mkFpMulExpr(fpRoundingModeSortDefaultValue(), lhs, rhs) } + } - override fun TsContext.resolveFakeObject( - lhs: UExpr<*>, - rhs: UExpr<*>, - scope: TsStepScope, - ): UExpr<*> { - return internalResolve(lhs, rhs, scope) + data object Div : TsArithmeticOperator { + override fun TsContext.apply( + lhs: UExpr, + rhs: UExpr, + ): UExpr { + return mkFpDivExpr(fpRoundingModeSortDefaultValue(), lhs, rhs) } + } - override fun TsContext.internalResolve( - lhs: UExpr<*>, - rhs: UExpr<*>, - scope: TsStepScope, - ): UExpr<*> { - val lhsNumeric = mkNumericExpr(lhs, scope) - val rhsNumeric = mkNumericExpr(rhs, scope) - - return mkFpSubExpr( - fpRoundingModeSortDefaultValue(), - lhsNumeric, - rhsNumeric - ) + data object Rem : TsArithmeticOperator { + override fun TsContext.apply( + lhs: UExpr, + rhs: UExpr, + ): UExpr { + return mkFpRemExpr(lhs, rhs) } } @@ -1107,10 +1095,8 @@ sealed interface TsBinaryOperator { ): UExpr<*> { check(lhs.isFakeObject() || rhs.isFakeObject()) - return scope.calcOnState { - val lhsTruthyExpr = mkTruthyExpr(lhs, scope) - iteWriteIntoFakeObject(scope, lhsTruthyExpr, lhs, rhs) - } + val lhsTruthyExpr = mkTruthyExpr(lhs, scope) + return iteWriteIntoFakeObject(scope, lhsTruthyExpr, lhs, rhs) } override fun TsContext.internalResolve( @@ -1121,9 +1107,7 @@ sealed interface TsBinaryOperator { check(!lhs.isFakeObject() && !rhs.isFakeObject()) val lhsTruthyExpr = mkTruthyExpr(lhs, scope) - return scope.calcOnState { - iteWriteIntoFakeObject(scope, lhsTruthyExpr, lhs, rhs) - } + return iteWriteIntoFakeObject(scope, lhsTruthyExpr, lhs, rhs) } } @@ -1132,7 +1116,7 @@ sealed interface TsBinaryOperator { lhs: UBoolExpr, rhs: UBoolExpr, scope: TsStepScope, - ): UExpr<*> { + ): UBoolExpr { return mkAnd(lhs.not(), rhs) } @@ -1140,7 +1124,7 @@ sealed interface TsBinaryOperator { lhs: UExpr, rhs: UExpr, scope: TsStepScope, - ): UExpr<*> { + ): UBoolExpr { return mkFpLessExpr(lhs, rhs) } @@ -1148,7 +1132,7 @@ sealed interface TsBinaryOperator { lhs: UHeapRef, rhs: UHeapRef, scope: TsStepScope, - ): UExpr<*> { + ): UBoolExpr { TODO("Not yet implemented") } @@ -1156,7 +1140,7 @@ sealed interface TsBinaryOperator { lhs: UExpr<*>, rhs: UExpr<*>, scope: TsStepScope, - ): UExpr<*> { + ): UBoolExpr { TODO("Not yet implemented") } @@ -1164,7 +1148,7 @@ sealed interface TsBinaryOperator { lhs: UExpr<*>, rhs: UExpr<*>, scope: TsStepScope, - ): UExpr<*> { + ): UBoolExpr { TODO("Not yet implemented") } } @@ -1174,7 +1158,7 @@ sealed interface TsBinaryOperator { lhs: UBoolExpr, rhs: UBoolExpr, scope: TsStepScope, - ): UExpr<*> { + ): UBoolExpr { return mkAnd(lhs, rhs.not()) } @@ -1182,7 +1166,7 @@ sealed interface TsBinaryOperator { lhs: UExpr, rhs: UExpr, scope: TsStepScope, - ): UExpr<*> { + ): UBoolExpr { return mkFpGreaterExpr(lhs, rhs) } @@ -1190,7 +1174,7 @@ sealed interface TsBinaryOperator { lhs: UHeapRef, rhs: UHeapRef, scope: TsStepScope, - ): UExpr<*> { + ): UBoolExpr { TODO("Not yet implemented") } @@ -1198,7 +1182,7 @@ sealed interface TsBinaryOperator { lhs: UExpr<*>, rhs: UExpr<*>, scope: TsStepScope, - ): UExpr<*> { + ): UBoolExpr { TODO("Not yet implemented") } @@ -1206,64 +1190,17 @@ sealed interface TsBinaryOperator { lhs: UExpr<*>, rhs: UExpr<*>, scope: TsStepScope, - ): UExpr<*> { + ): UBoolExpr { TODO("Not yet implemented") } } - data object Mul : TsBinaryOperator { - override fun TsContext.onBool( - lhs: UBoolExpr, - rhs: UBoolExpr, - scope: TsStepScope, - ): UExpr { - val left = mkNumericExpr(lhs, scope) - val right = mkNumericExpr(rhs, scope) - return mkFpMulExpr(fpRoundingModeSortDefaultValue(), left, right) - } - - override fun TsContext.onFp( + sealed interface TsArithmeticOperator : TsBinaryOperator { + fun TsContext.apply( lhs: UExpr, rhs: UExpr, - scope: TsStepScope, - ): UExpr { - return mkFpMulExpr(fpRoundingModeSortDefaultValue(), lhs, rhs) - } - - override fun TsContext.onRef( - lhs: UHeapRef, - rhs: UHeapRef, - scope: TsStepScope, - ): UExpr { - val left = mkNumericExpr(lhs, scope) - val right = mkNumericExpr(rhs, scope) - return mkFpMulExpr(fpRoundingModeSortDefaultValue(), left, right) - } + ): UExpr - override fun TsContext.resolveFakeObject( - lhs: UExpr<*>, - rhs: UExpr<*>, - scope: TsStepScope, - ): UExpr { - check(lhs.isFakeObject() || rhs.isFakeObject()) - val left = mkNumericExpr(lhs, scope) - val right = mkNumericExpr(rhs, scope) - return mkFpMulExpr(fpRoundingModeSortDefaultValue(), left, right) - } - - override fun TsContext.internalResolve( - lhs: UExpr<*>, - rhs: UExpr<*>, - scope: TsStepScope, - ): UExpr { - check(!lhs.isFakeObject() && !rhs.isFakeObject()) - val left = mkNumericExpr(lhs, scope) - val right = mkNumericExpr(rhs, scope) - return mkFpMulExpr(fpRoundingModeSortDefaultValue(), left, right) - } - } - - data object Div : TsBinaryOperator { override fun TsContext.onBool( lhs: UBoolExpr, rhs: UBoolExpr, @@ -1271,7 +1208,7 @@ sealed interface TsBinaryOperator { ): UExpr { val left = mkNumericExpr(lhs, scope) val right = mkNumericExpr(rhs, scope) - return mkFpDivExpr(fpRoundingModeSortDefaultValue(), left, right) + return apply(left, right) } override fun TsContext.onFp( @@ -1279,7 +1216,7 @@ sealed interface TsBinaryOperator { rhs: UExpr, scope: TsStepScope, ): UExpr { - return mkFpDivExpr(fpRoundingModeSortDefaultValue(), lhs, rhs) + return apply(lhs, rhs) } override fun TsContext.onRef( @@ -1289,7 +1226,7 @@ sealed interface TsBinaryOperator { ): UExpr { val left = mkNumericExpr(lhs, scope) val right = mkNumericExpr(rhs, scope) - return mkFpDivExpr(fpRoundingModeSortDefaultValue(), left, right) + return apply(left, right) } override fun TsContext.resolveFakeObject( @@ -1299,7 +1236,7 @@ sealed interface TsBinaryOperator { ): UExpr { val left = mkNumericExpr(lhs, scope) val right = mkNumericExpr(rhs, scope) - return mkFpDivExpr(fpRoundingModeSortDefaultValue(), left, right) + return apply(left, right) } override fun TsContext.internalResolve( @@ -1309,51 +1246,7 @@ sealed interface TsBinaryOperator { ): UExpr { val left = mkNumericExpr(lhs, scope) val right = mkNumericExpr(rhs, scope) - return mkFpDivExpr(fpRoundingModeSortDefaultValue(), left, right) - } - } - - data object Rem : TsBinaryOperator { - override fun TsContext.onBool( - lhs: UBoolExpr, - rhs: UBoolExpr, - scope: TsStepScope, - ): UExpr { - return internalResolve(lhs, rhs, scope) - } - - override fun TsContext.onFp( - lhs: UExpr, - rhs: UExpr, - scope: TsStepScope, - ): UExpr { - return internalResolve(lhs, rhs, scope) - } - - override fun TsContext.onRef( - lhs: UHeapRef, - rhs: UHeapRef, - scope: TsStepScope, - ): UExpr { - return internalResolve(lhs, rhs, scope) - } - - override fun TsContext.resolveFakeObject( - lhs: UExpr<*>, - rhs: UExpr<*>, - scope: TsStepScope, - ): UExpr { - return internalResolve(lhs, rhs, scope) - } - - override fun TsContext.internalResolve( - lhs: UExpr<*>, - rhs: UExpr<*>, - scope: TsStepScope, - ): UExpr { - val lhsExpr = mkNumericExpr(lhs, scope) - val rhsExpr = mkNumericExpr(rhs, scope) - return mkFpRemExpr(lhsExpr, rhsExpr) + return apply(left, right) } } } From 1b92d9cb954b3d9bc9c776ccedbaf808cc5f63b2 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Tue, 1 Jul 2025 15:05:52 +0300 Subject: [PATCH 44/82] Mark flaky test as repeated --- usvm-ts/src/test/kotlin/org/usvm/samples/types/TypeStream.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/usvm-ts/src/test/kotlin/org/usvm/samples/types/TypeStream.kt b/usvm-ts/src/test/kotlin/org/usvm/samples/types/TypeStream.kt index cbcbae38a5..93ab7021d2 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/samples/types/TypeStream.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/samples/types/TypeStream.kt @@ -1,6 +1,7 @@ package org.usvm.samples.types import org.jacodb.ets.model.EtsScene +import org.junit.jupiter.api.RepeatedTest import org.junit.jupiter.api.Test import org.usvm.api.TsTestValue import org.usvm.util.TsMethodTestRunner @@ -30,7 +31,7 @@ class TypeStream : TsMethodTestRunner() { ) } - @Test + @RepeatedTest(10, failureThreshold = 1) fun `use unique field`() { val method = getMethod(className, "useUniqueField") discoverProperties( @@ -51,4 +52,4 @@ class TypeStream : TsMethodTestRunner() { { value, r -> value.name == "SecondChild" && r.number == 3.0 }, ) } -} \ No newline at end of file +} From 61d633ce5e67849d098ce7966c32f9c9188284f2 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Tue, 1 Jul 2025 15:47:15 +0300 Subject: [PATCH 45/82] Make `withMode` generic extension --- .../kotlin/org/usvm/util/TsTestResolver.kt | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/usvm-ts/src/test/kotlin/org/usvm/util/TsTestResolver.kt b/usvm-ts/src/test/kotlin/org/usvm/util/TsTestResolver.kt index d7b6075001..0f3d7e17a5 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/util/TsTestResolver.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/util/TsTestResolver.kt @@ -82,8 +82,8 @@ class TsTestResolver { } } - val before = beforeMemoryScope.withMode(ResolveMode.MODEL) { (this as MemoryScope).resolveState() } - val after = afterMemoryScope.withMode(ResolveMode.CURRENT) { (this as MemoryScope).resolveState() } + val before = beforeMemoryScope.withMode(ResolveMode.MODEL) { resolveState() } + val after = afterMemoryScope.withMode(ResolveMode.CURRENT) { resolveState() } return TsTest(method, before, after, result, trace = emptyList()) } @@ -490,19 +490,6 @@ open class TsTestStateResolver( internal var resolveMode: ResolveMode = ResolveMode.ERROR - internal inline fun withMode( - resolveMode: ResolveMode, - body: TsTestStateResolver.() -> R, - ): R { - val prevValue = this.resolveMode - try { - this.resolveMode = resolveMode - return body() - } finally { - this.resolveMode = prevValue - } - } - fun evaluateInModel(expr: UExpr): UExpr { return model.eval(expr) } @@ -518,3 +505,16 @@ open class TsTestStateResolver( enum class ResolveMode { MODEL, CURRENT, ERROR } + +internal inline fun S.withMode( + resolveMode: ResolveMode, + body: S.() -> R, +): R { + val prevValue = this.resolveMode + try { + this.resolveMode = resolveMode + return body() + } finally { + this.resolveMode = prevValue + } +} From 55f0a2f29a5ca95621e04b85188841f5e8e92438 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Tue, 1 Jul 2025 15:55:52 +0300 Subject: [PATCH 46/82] Remove empty lines --- usvm-ts/src/test/kotlin/org/usvm/util/TsTestResolver.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/usvm-ts/src/test/kotlin/org/usvm/util/TsTestResolver.kt b/usvm-ts/src/test/kotlin/org/usvm/util/TsTestResolver.kt index 0f3d7e17a5..b3aaafef1f 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/util/TsTestResolver.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/util/TsTestResolver.kt @@ -136,9 +136,7 @@ class TsTestResolver { fun resolveState(): TsParametersState { val thisInstance = resolveThisInstance() val parameters = resolveParameters() - val globals = resolveGlobals() - return TsParametersState(thisInstance, parameters, globals) } } From d2e972bbaaa991fbda405cabe8e27567c3eb9741 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Tue, 1 Jul 2025 16:48:00 +0300 Subject: [PATCH 47/82] Assert instance inside fake object is not null. Make tests more lenient --- .../org/usvm/machine/expr/TsExprResolver.kt | 5 ++ .../org/usvm/samples/arrays/InputArrays.kt | 6 ++- .../org/usvm/samples/types/TypeStream.kt | 49 ++++++++++++++----- .../org/usvm/util/TsMethodTestRunner.kt | 3 +- .../resources/samples/types/TypeStream.ts | 36 +++++++------- 5 files changed, 67 insertions(+), 32 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index 7ad2c6c152..68e5b3e205 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -957,6 +957,11 @@ class TsExprResolver( override fun visit(value: EtsInstanceFieldRef): UExpr? = with(ctx) { val instanceRef = resolve(value.instance)?.asExpr(addressSort) ?: return null + if (instanceRef.isFakeObject()) { + val ref = instanceRef.extractRef(scope) + checkUndefinedOrNullPropertyRead(ref) ?: return null + } + checkUndefinedOrNullPropertyRead(instanceRef) ?: return null // TODO It is a hack for array's length diff --git a/usvm-ts/src/test/kotlin/org/usvm/samples/arrays/InputArrays.kt b/usvm-ts/src/test/kotlin/org/usvm/samples/arrays/InputArrays.kt index 907d8df949..e70dc93f38 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/samples/arrays/InputArrays.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/samples/arrays/InputArrays.kt @@ -18,11 +18,13 @@ class InputArrays : TsMethodTestRunner() { { x, r -> r is TsTestValue.TsException }, { x, r -> r as TsTestValue.TsNumber - (x.values[0] as TsTestValue.TsNumber).number == 1.0 && r.number == 1.0 + val x0 = x.values[0] as TsTestValue.TsNumber + x0.number == 1.0 && r.number == 1.0 }, { x, r -> r as TsTestValue.TsNumber - (x.values[0] as TsTestValue.TsNumber).number != 1.0 && r.number == 2.0 + val x0 = x.values[0] as TsTestValue.TsNumber + x0.number != 1.0 && r.number == 2.0 }, invariants = arrayOf( { _, r -> diff --git a/usvm-ts/src/test/kotlin/org/usvm/samples/types/TypeStream.kt b/usvm-ts/src/test/kotlin/org/usvm/samples/types/TypeStream.kt index 93ab7021d2..5d87ce6a46 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/samples/types/TypeStream.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/samples/types/TypeStream.kt @@ -16,7 +16,7 @@ class TypeStream : TsMethodTestRunner() { val method = getMethod(className, "ancestorId") discoverProperties( method = method, - { value, r -> r.name == value.name }, + { x, r -> r.name == x.name } ) } @@ -25,31 +25,58 @@ class TypeStream : TsMethodTestRunner() { val method = getMethod(className, "virtualInvokeForAncestor") discoverProperties( method = method, - { value, r -> value.name == "Parent" && r.number == 1.0 }, - { value, r -> value.name == "FirstChild" && r.number == 2.0 }, - { value, r -> value.name == "SecondChild" && r.number == 3.0 }, + { x, r -> x.name == "Parent" && r.number == 1.0 }, + { x, r -> x.name == "FirstChild" && r.number == 2.0 }, + { x, r -> x.name == "SecondChild" && r.number == 3.0 }, + invariants = arrayOf( + { _, r -> r.number in listOf(1.0, 2.0, 3.0) } + ) ) } @RepeatedTest(10, failureThreshold = 1) fun `use unique field`() { val method = getMethod(className, "useUniqueField") - discoverProperties( + discoverProperties( method = method, + { x, r -> + x as TsTestValue.TsClass + r as TsTestValue.TsNumber + x.name == "FirstChild" && r.number == 1.0 + }, invariants = arrayOf( - { value, r -> value.name == "FirstChild" && r.number == 1.0 } + { _, r -> + r !is TsTestValue.TsNumber || r.number == 1.0 + } ) ) } - @Test + @RepeatedTest(10, failureThreshold = 1) fun `use non unique field`() { val method = getMethod(className, "useNonUniqueField") - discoverProperties( + discoverProperties( method = method, - { value, r -> value.name == "Parent" && r.number == 1.0 }, - { value, r -> value.name == "FirstChild" && r.number == 2.0 }, - { value, r -> value.name == "SecondChild" && r.number == 3.0 }, + { x, r -> + x as TsTestValue.TsClass + r as TsTestValue.TsNumber + x.name == "Parent" && r.number == 1.0 + }, + { x, r -> + x as TsTestValue.TsClass + r as TsTestValue.TsNumber + x.name == "FirstChild" && r.number == 2.0 + }, + { x, r -> + x as TsTestValue.TsClass + r as TsTestValue.TsNumber + x.name == "SecondChild" && r.number == 3.0 + }, + invariants = arrayOf( + { _, r -> + r !is TsTestValue.TsNumber || r.number in listOf(1.0, 2.0, 3.0) + } + ) ) } } diff --git a/usvm-ts/src/test/kotlin/org/usvm/util/TsMethodTestRunner.kt b/usvm-ts/src/test/kotlin/org/usvm/util/TsMethodTestRunner.kt index 1dcc2921bf..ce5198b8f7 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/util/TsMethodTestRunner.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/util/TsMethodTestRunner.kt @@ -329,10 +329,11 @@ abstract class TsMethodTestRunner : TestRunner val states = machine.analyze(listOf(method)) - states.map { state -> + val resolved = states.map { state -> val resolver = TsTestResolver() resolver.resolve(method, state) } + resolved } } diff --git a/usvm-ts/src/test/resources/samples/types/TypeStream.ts b/usvm-ts/src/test/resources/samples/types/TypeStream.ts index ea2c6e5e6b..dcbf7fa7c6 100644 --- a/usvm-ts/src/test/resources/samples/types/TypeStream.ts +++ b/usvm-ts/src/test/resources/samples/types/TypeStream.ts @@ -3,17 +3,17 @@ class TypeStream { ancestorId(ancestor: Parent): Parent { - return ancestor + return ancestor; } virtualInvokeForAncestor(ancestor: Parent): number { - const number = ancestor.virtualMethod(); - if (number == 100) { - return 1 - } else if (number == 200) { - return 2 + const virtualInvokeResult = ancestor.virtualMethod(); + if (virtualInvokeResult == 100) { + return 1; + } else if (virtualInvokeResult == 200) { + return 2; } else { - return 3 + return 3; } } @@ -21,12 +21,12 @@ class TypeStream { // noinspection JSUnusedLocalSymbols const _ = value.firstChildField; const virtualInvokeResult = value.virtualMethod(); - if (virtualInvokeResult == 100) { - return -1 // unreachable - } else if (virtualInvokeResult == 200) { - return 1 + if (virtualInvokeResult == 200) { + return 1; + } else if (virtualInvokeResult == 100) { + return -1; // unreachable } else { - return -2 // unreachable + return -1; // unreachable } } @@ -35,17 +35,17 @@ class TypeStream { const _ = value.parentField; const virtualInvokeResult = value.virtualMethod(); if (virtualInvokeResult == 100) { - return 1 + return 1; } else if (virtualInvokeResult == 200) { - return 2 + return 2; } else { - return 3 + return 3; } } } class Parent { - parentField: number = -10 + parentField: number = -10; virtualMethod(): number { return 100; @@ -53,7 +53,7 @@ class Parent { } class FirstChild extends Parent { - firstChildField: number = 10 + firstChildField: number = 10; override virtualMethod(): number { return 200; @@ -61,7 +61,7 @@ class FirstChild extends Parent { } class SecondChild extends Parent { - secondChildField: number = 20 + secondChildField: number = 20; override virtualMethod(): number { return 300; From 0e4b1a4c6892238578ae0b275d29df8799ed62f5 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Tue, 1 Jul 2025 16:49:24 +0300 Subject: [PATCH 48/82] Unify --- usvm-ts/src/test/kotlin/org/usvm/util/TsTestResolver.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/usvm-ts/src/test/kotlin/org/usvm/util/TsTestResolver.kt b/usvm-ts/src/test/kotlin/org/usvm/util/TsTestResolver.kt index b3aaafef1f..31d2cfdfa8 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/util/TsTestResolver.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/util/TsTestResolver.kt @@ -355,8 +355,7 @@ open class TsTestStateResolver( model.eval(type.refTypeExpr).isTrue -> { val lValue = getIntermediateRefLValue(expr.address) val value = finalStateMemory.read(lValue) - val ref = model.eval(value) - resolveExpr(ref) + resolveExpr(model.eval(value)) } else -> error("Unsupported") From 34dd6a5e5d6cf04b1906ba457b4d6d1708411875 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Tue, 1 Jul 2025 17:26:15 +0300 Subject: [PATCH 49/82] Fix type stream tests --- .../usvm/machine/interpreter/TsInterpreter.kt | 3 +- .../org/usvm/samples/types/TypeStream.kt | 63 ++++++++++++++----- .../resources/samples/types/TypeStream.ts | 38 ++++++----- 3 files changed, 75 insertions(+), 29 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index 856c127c5b..6b1effda69 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -286,7 +286,8 @@ class TsInterpreter( // TODO mistake, should be separated into several hierarchies // or evalTypeEqual with several concrete types - memory.types.evalIsSubtype(ref, type) + // memory.types.evalIsSubtype(ref, type) + memory.types.evalTypeEquals(ref, type) } constraint to block } diff --git a/usvm-ts/src/test/kotlin/org/usvm/samples/types/TypeStream.kt b/usvm-ts/src/test/kotlin/org/usvm/samples/types/TypeStream.kt index 5d87ce6a46..b63c97403f 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/samples/types/TypeStream.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/samples/types/TypeStream.kt @@ -12,24 +12,53 @@ class TypeStream : TsMethodTestRunner() { override val scene: EtsScene = loadSampleScene(className, folderPrefix = "types") @Test - fun `test an ancestor as argument`() { - val method = getMethod(className, "ancestorId") - discoverProperties( + fun `test ancestor instanceof`() { + val method = getMethod(className, "instanceOf") + discoverProperties( method = method, - { x, r -> r.name == x.name } + { x, r -> + x.name == "FirstChild" && r.number == 1.0 + }, + { x, r -> + x.name == "SecondChild" && r.number == 2.0 + }, + { x, r -> + x.name == "Parent" && r.number == 3.0 + }, + invariants = arrayOf( + { x, r -> + x.name in listOf("Parent", "FirstChild", "SecondChild") + }, + { _, r -> + r.number in listOf(1.0, 2.0, 3.0) + }, + { _, r -> r.number != -1.0 } + ) ) } @Test fun `test virtual invoke on an ancestor`() { - val method = getMethod(className, "virtualInvokeForAncestor") + val method = getMethod(className, "virtualInvokeOnAncestor") discoverProperties( method = method, - { x, r -> x.name == "Parent" && r.number == 1.0 }, - { x, r -> x.name == "FirstChild" && r.number == 2.0 }, - { x, r -> x.name == "SecondChild" && r.number == 3.0 }, + { x, r -> + x.name == "FirstChild" && r.number == 1.0 + }, + { x, r -> + x.name == "SecondChild" && r.number == 2.0 + }, + { x, r -> + x.name == "Parent" && r.number == 3.0 + }, invariants = arrayOf( - { _, r -> r.number in listOf(1.0, 2.0, 3.0) } + { x, r -> + x.name in listOf("Parent", "FirstChild", "SecondChild") + }, + { _, r -> + r.number in listOf(1.0, 2.0, 3.0) + }, + { _, r -> r.number != -1.0 } ) ) } @@ -45,9 +74,12 @@ class TypeStream : TsMethodTestRunner() { x.name == "FirstChild" && r.number == 1.0 }, invariants = arrayOf( + { x, _ -> + x !is TsTestValue.TsClass || x.name == "FirstChild" + }, { _, r -> r !is TsTestValue.TsNumber || r.number == 1.0 - } + }, ) ) } @@ -60,22 +92,25 @@ class TypeStream : TsMethodTestRunner() { { x, r -> x as TsTestValue.TsClass r as TsTestValue.TsNumber - x.name == "Parent" && r.number == 1.0 + x.name == "FirstChild" && r.number == 1.0 }, { x, r -> x as TsTestValue.TsClass r as TsTestValue.TsNumber - x.name == "FirstChild" && r.number == 2.0 + x.name == "SecondChild" && r.number == 2.0 }, { x, r -> x as TsTestValue.TsClass r as TsTestValue.TsNumber - x.name == "SecondChild" && r.number == 3.0 + x.name == "Parent" && r.number == 3.0 }, invariants = arrayOf( { _, r -> r !is TsTestValue.TsNumber || r.number in listOf(1.0, 2.0, 3.0) - } + }, + { _, r -> + r !is TsTestValue.TsNumber || r.number != -1.0 + }, ) ) } diff --git a/usvm-ts/src/test/resources/samples/types/TypeStream.ts b/usvm-ts/src/test/resources/samples/types/TypeStream.ts index dcbf7fa7c6..37e3d06a2a 100644 --- a/usvm-ts/src/test/resources/samples/types/TypeStream.ts +++ b/usvm-ts/src/test/resources/samples/types/TypeStream.ts @@ -2,32 +2,41 @@ // noinspection JSUnusedGlobalSymbols class TypeStream { - ancestorId(ancestor: Parent): Parent { - return ancestor; + instanceOf(ancestor: Parent): number { + if (ancestor instanceof FirstChild) { + return 1; + } else if (ancestor instanceof SecondChild) { + return 2; + } else if (ancestor instanceof Parent) { + return 3; + } + return -1; // unreachable } - virtualInvokeForAncestor(ancestor: Parent): number { + virtualInvokeOnAncestor(ancestor: Parent): number { const virtualInvokeResult = ancestor.virtualMethod(); if (virtualInvokeResult == 100) { return 1; } else if (virtualInvokeResult == 200) { return 2; - } else { + } else if (virtualInvokeResult == 300) { return 3; } + return -1; // unreachable } useUniqueField(value: any): number { // noinspection JSUnusedLocalSymbols const _ = value.firstChildField; const virtualInvokeResult = value.virtualMethod(); - if (virtualInvokeResult == 200) { + if (virtualInvokeResult == 100) { return 1; - } else if (virtualInvokeResult == 100) { - return -1; // unreachable - } else { - return -1; // unreachable + } else if (virtualInvokeResult == 200) { + return 2; // unreachable + } else if (virtualInvokeResult == 300) { + return 3; // unreachable } + return -1; // unreachable } useNonUniqueField(value: any): number { @@ -38,17 +47,18 @@ class TypeStream { return 1; } else if (virtualInvokeResult == 200) { return 2; - } else { + } else if (virtualInvokeResult == 300) { return 3; } + return -1; // unreachable } } class Parent { - parentField: number = -10; + parentField: number = 42; virtualMethod(): number { - return 100; + return 300; } } @@ -56,7 +66,7 @@ class FirstChild extends Parent { firstChildField: number = 10; override virtualMethod(): number { - return 200; + return 100; } } @@ -64,6 +74,6 @@ class SecondChild extends Parent { secondChildField: number = 20; override virtualMethod(): number { - return 300; + return 200; } } From f59eb5580c4abcefc2b733a9917d9634db568091 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Tue, 1 Jul 2025 17:49:19 +0300 Subject: [PATCH 50/82] Use isSubtype+isSupertype --- .../org/usvm/machine/interpreter/TsInterpreter.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index 6b1effda69..dfbd668eed 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -216,7 +216,6 @@ class TsInterpreter( concreteMethods += methods } - val possibleTypes = scope.calcOnState { models.first().typeStreamOf(resolvedInstance as UConcreteHeapRef) } .take(1000) @@ -234,13 +233,13 @@ class TsInterpreter( } // - val chosenMethods = intersectedTypes.flatMap { - graph.hierarchy.classesForType(it as EtsRefType) + val chosenMethods = intersectedTypes.flatMap { clazz -> + graph.hierarchy.classesForType(clazz as EtsRefType) .asSequence() // TODO wrong order, ancestors are unordered .map { graph.hierarchy.getAncestors(it) } .map { it.first { it.methods.any { it.name == stmt.callee.name } } } - .map { it.methods.first { it.name == stmt.callee.name } } + .map { clazz to it.methods.first { it.name == stmt.callee.name } } }.toList().take(10) // TODO check it // logger.info { @@ -249,7 +248,7 @@ class TsInterpreter( // }" // } - val conditionsWithBlocks = chosenMethods.mapIndexed { i, method -> + val conditionsWithBlocks = chosenMethods.mapIndexed { i, (clazz, method) -> val concreteCall = stmt.toConcrete(method) val block = { state: TsState -> state.newStmt(concreteCall) } val type = requireNotNull(method.enclosingClass).type @@ -286,8 +285,10 @@ class TsInterpreter( // TODO mistake, should be separated into several hierarchies // or evalTypeEqual with several concrete types - // memory.types.evalIsSubtype(ref, type) - memory.types.evalTypeEquals(ref, type) + mkAnd( + memory.types.evalIsSubtype(ref, clazz), + memory.types.evalIsSupertype(ref, type) + ) } constraint to block } From 61ad56023572a2109b3823180660ac136dd2619d Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Tue, 1 Jul 2025 18:17:57 +0300 Subject: [PATCH 51/82] Fix test with super --- usvm-ts/src/test/kotlin/org/usvm/samples/Call.kt | 1 - usvm-ts/src/test/resources/samples/Call.ts | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/usvm-ts/src/test/kotlin/org/usvm/samples/Call.kt b/usvm-ts/src/test/kotlin/org/usvm/samples/Call.kt index 2f69267f2b..10c237030f 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/samples/Call.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/samples/Call.kt @@ -200,7 +200,6 @@ class Call : TsMethodTestRunner() { ) } - @Disabled("Calls to super are broken in IR") @Test fun `test virtual child`() { val method = getMethod(className, "callVirtualChild") diff --git a/usvm-ts/src/test/resources/samples/Call.ts b/usvm-ts/src/test/resources/samples/Call.ts index 2433879763..ae58e985bd 100644 --- a/usvm-ts/src/test/resources/samples/Call.ts +++ b/usvm-ts/src/test/resources/samples/Call.ts @@ -198,6 +198,10 @@ class Parent { } class Child extends Parent { + constructor() { + // super(); + } + override virtualMethod(): number { return 200; } From 0014070984421ef7fb336a64484955d1715e4c05 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Tue, 1 Jul 2025 18:19:22 +0300 Subject: [PATCH 52/82] Add test with non-overridden virtual call --- .../org/usvm/machine/interpreter/TsInterpreter.kt | 14 ++++++++------ usvm-ts/src/test/kotlin/org/usvm/samples/Call.kt | 10 ++++++++++ usvm-ts/src/test/resources/samples/Call.ts | 9 +++++++++ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index dfbd668eed..b5f806eaee 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -191,12 +191,14 @@ class TsInterpreter( return } val cls = classes.single() - val method = cls.methods - .singleOrNull { it.name == stmt.callee.name } - ?: run { - TODO("Overloads are not supported yet") - } - concreteMethods += method + // val method = cls.methods + // .singleOrNull { it.name == stmt.callee.name } + // ?: run { + // TODO("Overloads are not supported yet") + // } + // concreteMethods += method + val suitableMethods = cls.methods.filter { it.name == stmt.callee.name } + concreteMethods += suitableMethods } else { logger.warn { "Could not resolve method: ${stmt.callee} on type: $type" diff --git a/usvm-ts/src/test/kotlin/org/usvm/samples/Call.kt b/usvm-ts/src/test/kotlin/org/usvm/samples/Call.kt index 10c237030f..4a0854c08d 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/samples/Call.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/samples/Call.kt @@ -209,6 +209,16 @@ class Call : TsMethodTestRunner() { ) } + @Disabled("Non-overridden virtual calls are not supported yet") + @Test + fun `test base call`() { + val method = getMethod(className, "callBaseMethod") + discoverProperties( + method = method, + { r -> r.number == 42.0 }, + ) + } + @Test fun `test virtual dispatch`() { val method = getMethod(className, "virtualDispatch") diff --git a/usvm-ts/src/test/resources/samples/Call.ts b/usvm-ts/src/test/resources/samples/Call.ts index ae58e985bd..3341a30e5d 100644 --- a/usvm-ts/src/test/resources/samples/Call.ts +++ b/usvm-ts/src/test/resources/samples/Call.ts @@ -118,6 +118,11 @@ class Call { return obj.virtualMethod(); // 200 } + callBaseMethod(): number { + let obj: Child = new Child(); + return obj.baseMethod(); // 42 + } + virtualDispatch(obj: Parent): number { if (obj instanceof Child) { return obj.virtualMethod(); // 200 @@ -195,6 +200,10 @@ class Parent { virtualMethod(): number { return 100; } + + baseMethod(): number { + return 42; + } } class Child extends Parent { From f6fe2da80dce57cb9a15a4ee860642150cabc584 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 2 Jul 2025 01:02:53 +0300 Subject: [PATCH 53/82] Show stop reason --- usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt index 9e3a8d0ec8..d2fd85decd 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt @@ -125,7 +125,7 @@ class TsMachine( val result = strategy.shouldStop() if (result) { - logger.warn { "Stop strategy finished execution $strategy" } + logger.warn { "Stop strategy finished execution: ${strategy.stopReason()}" } } return result From 98964a9e7c872511ce7a6acd15ab4b4fdc2329b5 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 2 Jul 2025 01:02:59 +0300 Subject: [PATCH 54/82] Format --- .../src/main/kotlin/org/usvm/machine/TsContext.kt | 8 ++++---- .../src/main/kotlin/org/usvm/machine/TsMachine.kt | 12 ++++-------- usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt | 11 +++++++++-- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt index c9110f2ed7..e1052fbdeb 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt @@ -78,6 +78,8 @@ class TsContext( is EtsAnyType -> unresolvedSort is EtsUnknownType -> unresolvedSort is EtsAliasType -> typeToSort(type.originalType) + is EtsEnumValueType -> unresolvedSort + is EtsGenericType -> { if (type.constraint == null && type.defaultType == null) { unresolvedSort @@ -85,10 +87,8 @@ class TsContext( TODO("Not yet implemented") } } - is EtsEnumValueType -> unresolvedSort - else -> { - TODO("${type::class.simpleName} is not yet supported: $type") - } + + else -> TODO("${type::class.simpleName} is not yet supported: $type") } fun arrayDescriptorOf(type: EtsArrayType): EtsType { diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt index d2fd85decd..7d7002900a 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt @@ -111,14 +111,10 @@ class TsMachine( val strategy = createStopStrategy( options, targets, - timeStatisticsFactory = - { timeStatistics }, - stepsStatisticsFactory = - { stepsStatistics }, - coverageStatisticsFactory = - { coverageStatistics }, - getCollectedStatesCount = - { statesCollector.collectedStates.size } + timeStatisticsFactory = { timeStatistics }, + stepsStatisticsFactory = { stepsStatistics }, + coverageStatisticsFactory = { coverageStatistics }, + getCollectedStatesCount = { statesCollector.collectedStates.size }, ) override fun shouldStop(): Boolean { diff --git a/usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt b/usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt index e4f511b088..543cf67375 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt @@ -24,14 +24,19 @@ class RunOnDemoCalcProject : TsMethodTestRunner() { @JvmStatic private fun projectAvailable(): Boolean { - return getResourcePathOrNull(PROJECT_PATH) != null && getResourcePathOrNull(SDK_PATH) != null + val isProjectPresent = getResourcePathOrNull(PROJECT_PATH) != null + val isSdkPreset = getResourcePathOrNull(SDK_PATH) != null + return isProjectPresent && isSdkPreset } } override val scene: EtsScene = run { val projectPath = getResourcePath(PROJECT_PATH) val sdkPath = getResourcePathOrNull(SDK_PATH) - ?: error("Could not load SDK from resources '$SDK_PATH'. Try running './gradlew generateSdkIR' to generate it.") + ?: error( + "Could not load SDK from resources '$SDK_PATH'. " + + "Try running './gradlew generateSdkIR' to generate it." + ) loadEtsProjectFromIR(projectPath, sdkPath) } @@ -84,6 +89,7 @@ class RunOnDemoCalcProject : TsMethodTestRunner() { .filterNot { it.name.startsWith(ANONYMOUS_METHOD_PREFIX) } .filterNot { it.name == "build" } } + val tsOptions = TsOptions() TsMachine(scene, options, tsOptions).use { machine -> val states = machine.analyze(methods) @@ -96,6 +102,7 @@ class RunOnDemoCalcProject : TsMethodTestRunner() { val method = scene.projectClasses .flatMap { it.methods } .single { it.name == "createKvStore" && it.enclosingClass?.name == "KvStoreModel" } + val tsOptions = TsOptions() TsMachine(scene, options, tsOptions).use { machine -> val states = machine.analyze(listOf(method)) From 0616f030a0288764ca3238a1a9fa149c57376271 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 2 Jul 2025 01:12:04 +0300 Subject: [PATCH 55/82] Rename DemoPhotos --- .../usvm/project/{Photos.kt => DemoPhotos.kt} | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) rename usvm-ts/src/test/kotlin/org/usvm/project/{Photos.kt => DemoPhotos.kt} (82%) diff --git a/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt b/usvm-ts/src/test/kotlin/org/usvm/project/DemoPhotos.kt similarity index 82% rename from usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt rename to usvm-ts/src/test/kotlin/org/usvm/project/DemoPhotos.kt index 41ae13d4ff..69520e64cc 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/project/Photos.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/project/DemoPhotos.kt @@ -17,7 +17,7 @@ import kotlin.test.Test // @EnabledIf("projectAvailable") @Disabled -class RunOnPhotosProject : TsMethodTestRunner() { +class RunOnDemoPhotosProject : TsMethodTestRunner() { companion object { private const val PROJECT_PATH = "/projects/Demo_Photos/source/entry" @@ -25,28 +25,32 @@ class RunOnPhotosProject : TsMethodTestRunner() { @JvmStatic private fun projectAvailable(): Boolean { - return getResourcePathOrNull(PROJECT_PATH) != null && getResourcePathOrNull(SDK_PATH) != null + val isProjectPresent = getResourcePathOrNull(PROJECT_PATH) != null + val isSdkPreset = getResourcePathOrNull(SDK_PATH) != null + return isProjectPresent && isSdkPreset } } override val scene: EtsScene = run { val projectPath = getResourcePath(PROJECT_PATH) val sdkPath = getResourcePathOrNull(SDK_PATH) - ?: error("Could not load SDK from resources '$SDK_PATH'. Try running './gradlew generateSdkIR' to generate it.") + ?: error( + "Could not load SDK from resources '$SDK_PATH'. " + + "Try running './gradlew generateSdkIR' to generate it." + ) loadEtsProjectAutoConvert(projectPath, sdkPath) } @Test fun `test run on each method`() { val exceptions = mutableListOf() - val classes = scene.projectClasses.filterNot { it.name.startsWith(ANONYMOUS_CLASS_PREFIX) } + val classes = scene.projectClasses + .filterNot { it.name.startsWith(ANONYMOUS_CLASS_PREFIX) } println("Total classes: ${classes.size}") - classes - // .filter { it.name == "NewAlbumPage" } - .forEach { clazz -> - val methods = clazz.methods + classes.forEach { cls -> + val methods = cls.methods .filterNot { it.cfg.stmts.isEmpty() } .filterNot { it.isStatic } .filterNot { it.name.startsWith(ANONYMOUS_METHOD_PREFIX) } @@ -78,9 +82,9 @@ class RunOnPhotosProject : TsMethodTestRunner() { @Test fun `test run on all methods`() { val methods = scene.projectClasses - .filterNot { it.name.startsWith("%AC") } - .flatMap { - it.methods + .filterNot { it.name.startsWith(ANONYMOUS_CLASS_PREFIX) } + .flatMap { cls -> + cls.methods .filterNot { it.cfg.stmts.isEmpty() } .filterNot { it.isStatic } .filterNot { it.name.startsWith("%AM") } @@ -89,6 +93,7 @@ class RunOnPhotosProject : TsMethodTestRunner() { .filterNot { it.name == STATIC_INIT_METHOD_NAME } .filterNot { it.name == CONSTRUCTOR_NAME } } + val tsOptions = TsOptions() TsMachine(scene, options, tsOptions).use { machine -> val states = machine.analyze(methods) From 571386ddc4bc18da7494f41674c270e271f7c8ed Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 2 Jul 2025 01:13:15 +0300 Subject: [PATCH 56/82] Cleanup --- usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt b/usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt index 543cf67375..27a86eb396 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt @@ -89,7 +89,7 @@ class RunOnDemoCalcProject : TsMethodTestRunner() { .filterNot { it.name.startsWith(ANONYMOUS_METHOD_PREFIX) } .filterNot { it.name == "build" } } - + val tsOptions = TsOptions() TsMachine(scene, options, tsOptions).use { machine -> val states = machine.analyze(methods) From 74b457c042b3aca8f909a030a90b03259d6ffcda Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 2 Jul 2025 01:13:22 +0300 Subject: [PATCH 57/82] Use constant --- usvm-ts/src/test/kotlin/org/usvm/project/DemoPhotos.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usvm-ts/src/test/kotlin/org/usvm/project/DemoPhotos.kt b/usvm-ts/src/test/kotlin/org/usvm/project/DemoPhotos.kt index 69520e64cc..5e06a6335d 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/project/DemoPhotos.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/project/DemoPhotos.kt @@ -87,7 +87,7 @@ class RunOnDemoPhotosProject : TsMethodTestRunner() { cls.methods .filterNot { it.cfg.stmts.isEmpty() } .filterNot { it.isStatic } - .filterNot { it.name.startsWith("%AM") } + .filterNot { it.name.startsWith(ANONYMOUS_METHOD_PREFIX) } .filterNot { it.name == "build" } .filterNot { it.name == INSTANCE_INIT_METHOD_NAME } .filterNot { it.name == STATIC_INIT_METHOD_NAME } From 914dd4370c0d8a240f659405432ace26e15969d8 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 2 Jul 2025 16:19:19 +0300 Subject: [PATCH 58/82] Use timeout 120s --- usvm-ts/src/test/kotlin/org/usvm/project/DemoPhotos.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/usvm-ts/src/test/kotlin/org/usvm/project/DemoPhotos.kt b/usvm-ts/src/test/kotlin/org/usvm/project/DemoPhotos.kt index 5e06a6335d..1c4bf3e3ab 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/project/DemoPhotos.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/project/DemoPhotos.kt @@ -7,16 +7,17 @@ import org.jacodb.ets.utils.CONSTRUCTOR_NAME import org.jacodb.ets.utils.INSTANCE_INIT_METHOD_NAME import org.jacodb.ets.utils.STATIC_INIT_METHOD_NAME import org.jacodb.ets.utils.loadEtsProjectAutoConvert -import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.condition.EnabledIf import org.usvm.machine.TsMachine import org.usvm.machine.TsOptions import org.usvm.util.TsMethodTestRunner import org.usvm.util.getResourcePath import org.usvm.util.getResourcePathOrNull import kotlin.test.Test +import kotlin.time.Duration.Companion.seconds -// @EnabledIf("projectAvailable") -@Disabled +@EnabledIf("projectAvailable") +// @Disabled class RunOnDemoPhotosProject : TsMethodTestRunner() { companion object { @@ -63,7 +64,7 @@ class RunOnDemoPhotosProject : TsMethodTestRunner() { runCatching { val tsOptions = TsOptions() - TsMachine(scene, options, tsOptions).use { machine -> + TsMachine(scene, options.copy(timeout = 120.seconds), tsOptions).use { machine -> val states = machine.analyze(methods) states.let {} } From aa515cbeb3c7c37f31911b8e72c664bf1233b0b0 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 2 Jul 2025 16:20:04 +0300 Subject: [PATCH 59/82] Add AnyType to top type stream, handle Any in isSupertype --- .../main/kotlin/org/usvm/machine/types/TsTypeSystem.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/types/TsTypeSystem.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/types/TsTypeSystem.kt index 41ff0541eb..ee131ee94b 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/types/TsTypeSystem.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/types/TsTypeSystem.kt @@ -62,6 +62,8 @@ class TsTypeSystem( // "unknown" is the safe supertype: every value is assignable to unknown. if (unwrappedSupertype is EtsUnknownType) return true + // "any" is the universal subtype: any can be assigned to any type. + if (unwrappedType is EtsAnyType) return true // "never" is the universal subtype: never can be assigned to any type. if (unwrappedType is EtsNeverType) return true @@ -75,12 +77,12 @@ class TsTypeSystem( // As supertype, null/undefined only accept their own or any/unknown. if (unwrappedSupertype is EtsNullType) { // null supertype rules - return unwrappedType is EtsAnyType || unwrappedType is EtsUnknownType + return unwrappedType is EtsUnknownType } if (unwrappedSupertype is EtsUndefinedType) { // undefined supertype rules - return unwrappedType is EtsAnyType || unwrappedType is EtsUnknownType + return unwrappedType is EtsUnknownType } // Primitive types @@ -296,7 +298,7 @@ class TsTypeSystem( is EtsUnclearRefType, is EtsClassType -> if ((t as? EtsClassType)?.signature == EtsHierarchy.OBJECT_CLASS.signature) { // TODO change it - scene.projectAndSdkClasses.asSequence().map { it.type } + EtsStringType + scene.projectAndSdkClasses.asSequence().map { it.type } + EtsStringType + EtsAnyType } else { hierarchy.classesForType(t) .asSequence() From 52cfb932f122fd788f563b85c1fced15568ec466 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 2 Jul 2025 16:32:27 +0300 Subject: [PATCH 60/82] Move dot utils --- .../kotlin/org/usvm/machine/state/TsState.kt | 14 ------- .../src/main/kotlin/org/usvm/util/Utils.kt | 37 +++++++++++++++++++ 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt index b4dc03e0ce..a46d93c770 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt @@ -9,9 +9,6 @@ import org.jacodb.ets.model.EtsMethod import org.jacodb.ets.model.EtsStmt import org.jacodb.ets.model.EtsType import org.jacodb.ets.model.EtsValue -import org.jacodb.ets.utils.InterproceduralCfg -import org.jacodb.ets.utils.renderDotOverwrite -import org.jacodb.ets.utils.toHighlightedDotWithCalls import org.usvm.PathNode import org.usvm.UCallStack import org.usvm.UConcreteHeapRef @@ -24,7 +21,6 @@ import org.usvm.collections.immutable.implementations.immutableMap.UPersistentHa import org.usvm.collections.immutable.internal.MutabilityOwnership import org.usvm.collections.immutable.persistentHashMapOf import org.usvm.constraints.UPathConstraints -import org.usvm.dataflow.ts.util.toMap import org.usvm.machine.TsContext import org.usvm.memory.ULValue import org.usvm.memory.UMemory @@ -175,16 +171,6 @@ class TsState( ) } - fun renderGraph() { - val graph = InterproceduralCfg(main = entrypoint.cfg, callees = discoveredCallees.toMap()) - val dot = graph.toHighlightedDotWithCalls( - pathStmts = pathNode.allStatements.toSet(), - currentStmt = currentStatement - ) - - renderDotOverwrite(dot) - } - override val isExceptional: Boolean get() = methodResult is TsMethodResult.TsException } diff --git a/usvm-ts/src/main/kotlin/org/usvm/util/Utils.kt b/usvm-ts/src/main/kotlin/org/usvm/util/Utils.kt index b05ba6d4ca..1265d05b1e 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/util/Utils.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/util/Utils.kt @@ -9,16 +9,22 @@ import org.jacodb.ets.model.EtsMethod import org.jacodb.ets.model.EtsScene import org.jacodb.ets.model.EtsType import org.jacodb.ets.model.EtsUnclearRefType +import org.jacodb.ets.utils.InterproceduralCfg +import org.jacodb.ets.utils.toHighlightedDotWithCalls import org.usvm.UBoolSort import org.usvm.UConcreteHeapRef import org.usvm.UExpr import org.usvm.UHeapRef +import org.usvm.dataflow.ts.util.toMap import org.usvm.machine.TsContext import org.usvm.machine.expr.tctx import org.usvm.machine.interpreter.TsStepScope import org.usvm.machine.state.TsMethodResult import org.usvm.machine.state.TsState import org.usvm.machine.types.mkFakeValue +import java.nio.file.Path +import kotlin.io.path.createTempDirectory +import kotlin.io.path.writeText // Built-in KContext.bvToBool has identical implementation. fun TsContext.boolToFp(expr: UExpr): UExpr = @@ -75,3 +81,34 @@ fun UHeapRef.createFakeField(fieldName: String, scope: TsStepScope): UConcreteHe return fakeObject } + +fun TsState.renderGraph() { + val graph = InterproceduralCfg(main = entrypoint.cfg, callees = discoveredCallees.toMap()) + val dot = graph.toHighlightedDotWithCalls( + pathStmts = pathNode.allStatements.toSet(), + currentStmt = currentStatement, + ) + + myRenderDot(dot) +} + +fun myRenderDot( + dot: String, + outDir: Path = createTempDirectory(), + baseName: String = "cfg", + dotCmd: String = "dot", + format: String = "svg", // "svg", "png", "pdf" + viewerCmd: String? = when { + System.getProperty("os.name").startsWith("Mac") -> "open" + System.getProperty("os.name").startsWith("Win") -> "cmd /c start" + else -> "xdg-open" + }, +) { + val dotFile = outDir.resolve("$baseName.dot") + val svgFile = outDir.resolveSibling("$baseName.$format") + dotFile.writeText(dot) + Runtime.getRuntime().exec("$dotCmd $dotFile -T$format -o $svgFile").waitFor() + if (viewerCmd != null) { + Runtime.getRuntime().exec("$viewerCmd $svgFile").waitFor() + } +} From 88539113df40b62b673e8aeffc83afd9ffea9916 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 2 Jul 2025 16:32:32 +0300 Subject: [PATCH 61/82] Format --- .../kotlin/org/usvm/machine/interpreter/TsInterpreter.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index b5f806eaee..078b4f1fa2 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -218,8 +218,9 @@ class TsInterpreter( concreteMethods += methods } - val possibleTypes = scope.calcOnState { models.first().typeStreamOf(resolvedInstance as UConcreteHeapRef) } - .take(1000) + val possibleTypes = scope.calcOnState { + models.first().typeStreamOf(resolvedInstance as UConcreteHeapRef).take(1000) + } if (possibleTypes !is TypesResult.SuccessfulTypesResult) { error("TODO")// is it right? From ce6b9574ce7cf89134a42448dcbef75364533593 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 2 Jul 2025 16:32:50 +0300 Subject: [PATCH 62/82] Register callee for static initializer --- usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index 68e5b3e205..76edc49926 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -1035,6 +1035,7 @@ class TsExprResolver( scope.doWithState { markInitialized(clazz) pushSortsForArguments(instance = null, args = emptyList(), localToIdx) + registerCallee(currentStatement, initializer.cfg) callStack.push(initializer, currentStatement) memory.stack.push(arrayOf(instanceRef), initializer.localsCount) newStmt(initializer.cfg.stmts.first()) From fa44f6afedff9fbefca9f624899a4e09842f2419 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 2 Jul 2025 16:33:06 +0300 Subject: [PATCH 63/82] Filter some warnings --- .../src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index 76edc49926..78c0d37a5c 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -879,7 +879,9 @@ class TsExprResolver( val sort = when (etsField) { is EtsPropertyResolution.Empty -> { - logger.error("Field $field not found, creating fake field") + if (field.name !in listOf("i", "LogLevel")) { + logger.warn { "Field $field not found, creating fake field" } + } // If we didn't find any real fields, let's create a fake one. // It is possible due to mistakes in the IR or if the field was added explicitly // in the code. From a8760bfb56f0a342f45756f03486e9c691988e0e Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 2 Jul 2025 16:34:10 +0300 Subject: [PATCH 64/82] Move TsState visualization utils --- .../kotlin/org/usvm/util/TsStateVisualizer.kt | 39 ++++++++++++++++++- .../src/main/kotlin/org/usvm/util/Utils.kt | 31 --------------- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/util/TsStateVisualizer.kt b/usvm-ts/src/main/kotlin/org/usvm/util/TsStateVisualizer.kt index e99f5d745e..c7c10485e0 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/util/TsStateVisualizer.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/util/TsStateVisualizer.kt @@ -1,11 +1,48 @@ package org.usvm.util +import org.jacodb.ets.utils.InterproceduralCfg +import org.jacodb.ets.utils.toHighlightedDotWithCalls +import org.usvm.dataflow.ts.util.toMap import org.usvm.machine.TsInterpreterObserver import org.usvm.machine.state.TsState import org.usvm.statistics.UMachineObserver +import java.nio.file.Path +import kotlin.io.path.createTempDirectory +import kotlin.io.path.writeText class TsStateVisualizer : TsInterpreterObserver, UMachineObserver { override fun onStatePeeked(state: TsState) { state.renderGraph() } -} \ No newline at end of file +} + +fun TsState.renderGraph() { + val graph = InterproceduralCfg(main = entrypoint.cfg, callees = discoveredCallees.toMap()) + val dot = graph.toHighlightedDotWithCalls( + pathStmts = pathNode.allStatements.toSet(), + currentStmt = currentStatement, + ) + + myRenderDot(dot) +} + +fun myRenderDot( + dot: String, + outDir: Path = createTempDirectory(), + baseName: String = "cfg", + dotCmd: String = "dot", + format: String = "svg", // "svg", "png", "pdf" + viewerCmd: String? = when { + System.getProperty("os.name").startsWith("Mac") -> "open" + System.getProperty("os.name").startsWith("Win") -> "cmd /c start" + else -> "xdg-open" + }, +) { + val dotFile = outDir.resolve("$baseName.dot") + val svgFile = outDir.resolveSibling("$baseName.$format") + dotFile.writeText(dot) + Runtime.getRuntime().exec("$dotCmd $dotFile -T$format -o $svgFile").waitFor() + if (viewerCmd != null) { + Runtime.getRuntime().exec("$viewerCmd $svgFile").waitFor() + } +} diff --git a/usvm-ts/src/main/kotlin/org/usvm/util/Utils.kt b/usvm-ts/src/main/kotlin/org/usvm/util/Utils.kt index 1265d05b1e..aec008bf21 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/util/Utils.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/util/Utils.kt @@ -81,34 +81,3 @@ fun UHeapRef.createFakeField(fieldName: String, scope: TsStepScope): UConcreteHe return fakeObject } - -fun TsState.renderGraph() { - val graph = InterproceduralCfg(main = entrypoint.cfg, callees = discoveredCallees.toMap()) - val dot = graph.toHighlightedDotWithCalls( - pathStmts = pathNode.allStatements.toSet(), - currentStmt = currentStatement, - ) - - myRenderDot(dot) -} - -fun myRenderDot( - dot: String, - outDir: Path = createTempDirectory(), - baseName: String = "cfg", - dotCmd: String = "dot", - format: String = "svg", // "svg", "png", "pdf" - viewerCmd: String? = when { - System.getProperty("os.name").startsWith("Mac") -> "open" - System.getProperty("os.name").startsWith("Win") -> "cmd /c start" - else -> "xdg-open" - }, -) { - val dotFile = outDir.resolve("$baseName.dot") - val svgFile = outDir.resolveSibling("$baseName.$format") - dotFile.writeText(dot) - Runtime.getRuntime().exec("$dotCmd $dotFile -T$format -o $svgFile").waitFor() - if (viewerCmd != null) { - Runtime.getRuntime().exec("$viewerCmd $svgFile").waitFor() - } -} From 553a67a16948417d6569d2989927bf06ab8c7793 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 2 Jul 2025 16:47:45 +0300 Subject: [PATCH 65/82] Remove commented code --- usvm-ts/src/test/kotlin/org/usvm/project/DemoPhotos.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/usvm-ts/src/test/kotlin/org/usvm/project/DemoPhotos.kt b/usvm-ts/src/test/kotlin/org/usvm/project/DemoPhotos.kt index 1c4bf3e3ab..bca881d048 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/project/DemoPhotos.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/project/DemoPhotos.kt @@ -17,7 +17,6 @@ import kotlin.test.Test import kotlin.time.Duration.Companion.seconds @EnabledIf("projectAvailable") -// @Disabled class RunOnDemoPhotosProject : TsMethodTestRunner() { companion object { From 176cac1e3934b721af9e687a88faf08f6ea0450b Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 2 Jul 2025 16:50:55 +0300 Subject: [PATCH 66/82] Handle fake object *inside* neqNull check --- .../org/usvm/machine/expr/TsExprResolver.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index 78c0d37a5c..684ca3ee1d 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -833,9 +833,15 @@ class TsExprResolver( } fun checkUndefinedOrNullPropertyRead(instance: UHeapRef) = with(ctx) { + val ref = if (instance.isFakeObject()) { + instance.extractRef(scope) + } else { + instance + } + val neqNull = mkAnd( - mkHeapRefEq(instance, mkUndefinedValue()).not(), - mkHeapRefEq(instance, mkTsNullValue()).not() + mkHeapRefEq(ref, mkUndefinedValue()).not(), + mkHeapRefEq(ref, mkTsNullValue()).not(), ) scope.fork( @@ -959,11 +965,6 @@ class TsExprResolver( override fun visit(value: EtsInstanceFieldRef): UExpr? = with(ctx) { val instanceRef = resolve(value.instance)?.asExpr(addressSort) ?: return null - if (instanceRef.isFakeObject()) { - val ref = instanceRef.extractRef(scope) - checkUndefinedOrNullPropertyRead(ref) ?: return null - } - checkUndefinedOrNullPropertyRead(instanceRef) ?: return null // TODO It is a hack for array's length From db104d20bd446f82d0976c0b687403b119edd94b Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 2 Jul 2025 17:24:01 +0300 Subject: [PATCH 67/82] Show 5 lines from stacktrace --- .../main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index 078b4f1fa2..71d73d81d6 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -143,7 +143,7 @@ class TsInterpreter( } } catch (e: Exception) { logger.error { - "Exception: $e\n${e.stackTrace.take(3).joinToString("\n") { " $it" }}" + "Exception: $e\n${e.stackTrace.take(5).joinToString("\n") { " $it" }}" } return StepResult(forkedStates = emptySequence(), originalStateAlive = false) // todo are exceptional states properly removed? From 3b665e6cba230af25bcfe3b9cd5c6045690216e8 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 2 Jul 2025 17:24:14 +0300 Subject: [PATCH 68/82] Extract ref from fake object recursively --- .../src/main/kotlin/org/usvm/machine/TsContext.kt | 12 ++++++++++++ .../kotlin/org/usvm/machine/expr/TsExprResolver.kt | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt index e1052fbdeb..59fe46d9d5 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt @@ -164,6 +164,18 @@ class TsContext( } } + fun UHeapRef.extractRefOrSelf(scope: TsStepScope): UHeapRef { + var current = this + while (current.isFakeObject()) { + val fakeType = current.getFakeType(scope) + scope.doWithState { + pathConstraints += fakeType.refTypeExpr + } + current = current.extractRef(scope) + } + return current + } + fun createFakeObjectRef(): UConcreteHeapRef { val address = mkAddressCounter().freshAllocatedAddress() + MAGIC_OFFSET return mkConcreteHeapRef(address) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index 684ca3ee1d..f6e55936d9 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -879,7 +879,7 @@ class TsExprResolver( field: EtsFieldSignature, hierarchy: EtsHierarchy, ): UExpr? = with(ctx) { - val resolvedAddr = if (instanceRef.isFakeObject()) instanceRef.extractRef(scope) else instanceRef + val resolvedAddr = instanceRef.extractRefOrSelf(scope) val etsField = resolveEtsField(instance, field, hierarchy) From 041339e916212c668e8b63d0f4677bbf8a5015ac Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 2 Jul 2025 17:28:30 +0300 Subject: [PATCH 69/82] Kill the state on non-ref instance field access --- .../main/kotlin/org/usvm/machine/expr/TsExprResolver.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index f6e55936d9..08ac24f9a1 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -963,7 +963,12 @@ class TsExprResolver( } override fun visit(value: EtsInstanceFieldRef): UExpr? = with(ctx) { - val instanceRef = resolve(value.instance)?.asExpr(addressSort) ?: return null + val instanceRefResolved = resolve(value.instance) ?: return null + if (instanceRefResolved.sort != addressSort) { + scope.assert(falseExpr) + return null + } + val instanceRef = instanceRefResolved.asExpr(addressSort) checkUndefinedOrNullPropertyRead(instanceRef) ?: return null From 274e11a2c3751334cef64d847ef7783b7349c4c6 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Thu, 3 Jul 2025 12:14:33 +0300 Subject: [PATCH 70/82] Fix tests --- .../org/usvm/machine/interpreter/TsInterpreter.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index 71d73d81d6..5cf2e128bc 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -2,6 +2,7 @@ package org.usvm.machine.interpreter import io.ksmt.utils.asExpr import mu.KotlinLogging +import org.jacodb.ets.model.EtsAnyType import org.jacodb.ets.model.EtsArrayAccess import org.jacodb.ets.model.EtsArrayType import org.jacodb.ets.model.EtsAssignStmt @@ -227,11 +228,22 @@ class TsInterpreter( } val possibleTypesSet = possibleTypes.types.toSet() + + if (possibleTypesSet.singleOrNull() == EtsAnyType) { + mockMethodCall(scope, stmt.callee) + scope.calcOnState { + newStmt(stmt.returnSite) + } + return@with + } + + val filteredPossibleTypesSet = possibleTypesSet - EtsAnyType + val methodsDeclaringClasses = concreteMethods.mapNotNull { it.enclosingClass } // is it right? val typeSystem = typeSystem() // take only possible classes with a corresponding method - val intersectedTypes = possibleTypesSet.filter { possibleType -> + val intersectedTypes = filteredPossibleTypesSet.filter { possibleType -> methodsDeclaringClasses.any { typeSystem.isSupertype(it.type, possibleType) } } From bd9f8bf7e4dc1a63e0421d8c8cf64f8b6af2c91a Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Thu, 3 Jul 2025 13:18:23 +0300 Subject: [PATCH 71/82] Remove redundant change --- usvm-core/src/main/kotlin/org/usvm/StepScope.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/StepScope.kt b/usvm-core/src/main/kotlin/org/usvm/StepScope.kt index 294271f090..a22e8bd8dc 100644 --- a/usvm-core/src/main/kotlin/org/usvm/StepScope.kt +++ b/usvm-core/src/main/kotlin/org/usvm/StepScope.kt @@ -56,9 +56,7 @@ class StepScope, Type, Statement, contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - check(canProcessFurtherOnCurrentStep) { - "Caller should check before processing the current hop further" - } + check(canProcessFurtherOnCurrentStep) { "Caller should check before processing the current hop further" } return originalState.block() } From 243149b5fa42ee116ec73dc0d0f72a9270b3f407 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Thu, 3 Jul 2025 14:17:48 +0300 Subject: [PATCH 72/82] Fix issue with fake type in path constraints --- .../src/main/kotlin/org/usvm/machine/TsContext.kt | 12 ++++++------ .../kotlin/org/usvm/machine/expr/TsExprResolver.kt | 14 +++++++++++--- usvm-ts/src/main/kotlin/org/usvm/util/Utils.kt | 12 ++++++------ .../test/kotlin/org/usvm/samples/FieldAccess.kt | 10 ++++++++++ usvm-ts/src/test/resources/samples/FieldAccess.ts | 10 ++++++++++ 5 files changed, 43 insertions(+), 15 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt index 59fe46d9d5..b5dbeaf43c 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt @@ -164,13 +164,13 @@ class TsContext( } } - fun UHeapRef.extractRefOrSelf(scope: TsStepScope): UHeapRef { + fun UHeapRef.extractRefIfRequired(scope: TsStepScope): UHeapRef { var current = this - while (current.isFakeObject()) { - val fakeType = current.getFakeType(scope) - scope.doWithState { - pathConstraints += fakeType.refTypeExpr - } + if (current.isFakeObject()) { + // val fakeType = current.getFakeType(scope) + // scope.doWithState { + // pathConstraints += fakeType.refTypeExpr + // } current = current.extractRef(scope) } return current diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index 08ac24f9a1..aeac78f8f6 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -879,7 +879,7 @@ class TsExprResolver( field: EtsFieldSignature, hierarchy: EtsHierarchy, ): UExpr? = with(ctx) { - val resolvedAddr = instanceRef.extractRefOrSelf(scope) + val resolvedAddr = instanceRef.extractRefIfRequired(scope) val etsField = resolveEtsField(instance, field, hierarchy) @@ -906,7 +906,14 @@ class TsExprResolver( // That's not true in the common case for TS, but that's the decision we made. val auxiliaryType = EtsAuxiliaryType(properties = setOf(field.name)) // assert is required to update models - scope.assert(memory.types.evalIsSubtype(resolvedAddr, auxiliaryType)) + + // if (resolvedAddr.isFakeObject()) { + // val extractedRef = resolvedAddr.extractRef(scope) + // scope.assert(resolvedAddr.getFakeType(scope).refTypeExpr) + // pathConstraints += memory.types.evalIsSubtype(extractedRef, auxiliaryType) + // } else { + scope.assert(memory.types.evalIsSubtype(resolvedAddr, auxiliaryType)) + // } } // TODO @@ -957,7 +964,7 @@ class TsExprResolver( fakeRef } } else { - val lValue = mkFieldLValue(sort, instanceRef, field) + val lValue = mkFieldLValue(sort, resolvedAddr, field) scope.calcOnState { memory.read(lValue) } } } @@ -965,6 +972,7 @@ class TsExprResolver( override fun visit(value: EtsInstanceFieldRef): UExpr? = with(ctx) { val instanceRefResolved = resolve(value.instance) ?: return null if (instanceRefResolved.sort != addressSort) { + logger.error("InstanceFieldRef access on not address sort: $instanceRefResolved") scope.assert(falseExpr) return null } diff --git a/usvm-ts/src/main/kotlin/org/usvm/util/Utils.kt b/usvm-ts/src/main/kotlin/org/usvm/util/Utils.kt index aec008bf21..8315e690b7 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/util/Utils.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/util/Utils.kt @@ -9,22 +9,16 @@ import org.jacodb.ets.model.EtsMethod import org.jacodb.ets.model.EtsScene import org.jacodb.ets.model.EtsType import org.jacodb.ets.model.EtsUnclearRefType -import org.jacodb.ets.utils.InterproceduralCfg -import org.jacodb.ets.utils.toHighlightedDotWithCalls import org.usvm.UBoolSort import org.usvm.UConcreteHeapRef import org.usvm.UExpr import org.usvm.UHeapRef -import org.usvm.dataflow.ts.util.toMap import org.usvm.machine.TsContext import org.usvm.machine.expr.tctx import org.usvm.machine.interpreter.TsStepScope import org.usvm.machine.state.TsMethodResult import org.usvm.machine.state.TsState import org.usvm.machine.types.mkFakeValue -import java.nio.file.Path -import kotlin.io.path.createTempDirectory -import kotlin.io.path.writeText // Built-in KContext.bvToBool has identical implementation. fun TsContext.boolToFp(expr: UExpr): UExpr = @@ -74,6 +68,12 @@ fun UHeapRef.createFakeField(fieldName: String, scope: TsStepScope): UConcreteHe val fpValue = scope.calcOnState { memory.read(fpLValue) } val refValue = scope.calcOnState { memory.read(refLValue) } + with(ctx) { + if (refValue.isFakeObject()) { + return refValue + } + } + val fakeObject = ctx.mkFakeValue(scope, boolValue, fpValue, refValue) scope.doWithState { memory.write(lValue, fakeObject.asExpr(ctx.addressSort), guard = ctx.trueExpr) diff --git a/usvm-ts/src/test/kotlin/org/usvm/samples/FieldAccess.kt b/usvm-ts/src/test/kotlin/org/usvm/samples/FieldAccess.kt index d5d8cd3c84..71f8c04637 100644 --- a/usvm-ts/src/test/kotlin/org/usvm/samples/FieldAccess.kt +++ b/usvm-ts/src/test/kotlin/org/usvm/samples/FieldAccess.kt @@ -120,4 +120,14 @@ class FieldAccess : TsMethodTestRunner() { { r -> r.number == 1.0 }, ) } + + @Test + fun `test read from nested fake objects`() { + val method = getMethod(className, "readFromNestedFakeObjects") + discoverProperties( + method = method, + { r -> r.number == 1.0 }, + { r -> r.number == 2.0 }, + ) + } } diff --git a/usvm-ts/src/test/resources/samples/FieldAccess.ts b/usvm-ts/src/test/resources/samples/FieldAccess.ts index d65eff3762..75bbeacbe6 100644 --- a/usvm-ts/src/test/resources/samples/FieldAccess.ts +++ b/usvm-ts/src/test/resources/samples/FieldAccess.ts @@ -89,4 +89,14 @@ class FieldAccess { obj.x = [obj.x]; return obj.x.length; } + + readFromNestedFakeObjects(): number { + let x = Foo.Bar.x; + let y = Foo.Bar.y; + if (x === undefined) { + return 1; + } else { + return 2; + } + } } From 853a615d35c91ca2603521a3f80a900dd6e78164 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Thu, 3 Jul 2025 14:19:50 +0300 Subject: [PATCH 73/82] Use y --- usvm-ts/src/test/resources/samples/FieldAccess.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usvm-ts/src/test/resources/samples/FieldAccess.ts b/usvm-ts/src/test/resources/samples/FieldAccess.ts index 75bbeacbe6..ace8aed692 100644 --- a/usvm-ts/src/test/resources/samples/FieldAccess.ts +++ b/usvm-ts/src/test/resources/samples/FieldAccess.ts @@ -93,7 +93,7 @@ class FieldAccess { readFromNestedFakeObjects(): number { let x = Foo.Bar.x; let y = Foo.Bar.y; - if (x === undefined) { + if (x === undefined && y === undefined) { return 1; } else { return 2; From 090172bd96de9f4138fec5d8ea6fd668e715e126 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Thu, 3 Jul 2025 14:20:04 +0300 Subject: [PATCH 74/82] Format tests --- usvm-ts/src/test/resources/samples/Call.ts | 74 ++++++++++--------- .../src/test/resources/samples/FieldAccess.ts | 16 ++-- .../test/resources/samples/InstanceFields.ts | 14 ++-- .../test/resources/samples/InstanceMethods.ts | 20 ++--- usvm-ts/src/test/resources/samples/Null.ts | 4 +- usvm-ts/src/test/resources/samples/Numeric.ts | 70 +++++++++--------- .../test/resources/samples/StaticFields.ts | 10 +-- .../test/resources/samples/StaticMethods.ts | 20 ++--- usvm-ts/src/test/resources/samples/Strings.ts | 49 ++++++------ usvm-ts/src/test/resources/samples/Truthy.ts | 8 +- .../test/resources/samples/TypeCoercion.ts | 44 +++++------ .../src/test/resources/samples/Undefined.ts | 4 +- .../samples/arrays/AllocatedArrays.ts | 58 +++++++-------- .../resources/samples/arrays/InputArrays.ts | 40 +++++----- .../test/resources/samples/operators/Add.ts | 54 +++++++------- .../test/resources/samples/operators/And.ts | 72 +++++++++--------- .../resources/samples/operators/Division.ts | 24 +++--- .../resources/samples/operators/Equality.ts | 42 +++++------ .../test/resources/samples/operators/Neg.ts | 20 ++--- .../test/resources/samples/operators/Or.ts | 46 ++++++------ .../resources/samples/operators/Remainder.ts | 26 +++---- .../resources/samples/types/ObjectUsage.ts | 4 +- .../samples/types/StructuralEquality.ts | 2 +- 23 files changed, 362 insertions(+), 359 deletions(-) diff --git a/usvm-ts/src/test/resources/samples/Call.ts b/usvm-ts/src/test/resources/samples/Call.ts index 3341a30e5d..218800ff1d 100644 --- a/usvm-ts/src/test/resources/samples/Call.ts +++ b/usvm-ts/src/test/resources/samples/Call.ts @@ -2,86 +2,92 @@ // noinspection JSUnusedGlobalSymbols class Call { + static fifty(): number { + return 50; + } + callSimple(): number { - return this.fortyTwo() + return this.fortyTwo(); } fortyTwo(): number { - return 42 + return 42; } fib(n: number): number { - if (n != n) return 0 - if (n < 0) return -1 - if (n > 10) return -100 - if (n == 0) return 1 - if (n == 1) return 1 - return this.fib(n - 1) + this.fib(n - 2) + if (n != n) return 0; + if (n < 0) return -1; + if (n > 10) return -100; + if (n == 0) return 1; + if (n == 1) return 1; + return this.fib(n - 1) + this.fib(n - 2); } callConcrete(): number { - let x = new A() - return x.foo() + let x = new A(); + return x.foo(); } callHidden(): number { let x: any = new B(); - return x.foo() + return x.foo(); } f(x: any, ...args: any[]): number { - if (x == undefined) return 0 - return args.length + 1 + if (x == undefined) return 0; + return args.length + 1; } callNoVararg(): number { - return this.f(5) // 1 + return this.f(5); // 1 } callVararg1(): number { - return this.f(5, 10) // 2 + return this.f(5, 10); // 2 } callVararg2(): number { - return this.f(5, 10, 20) // 3 + return this.f(5, 10, 20); // 3 } callVarargArray(): number { - return this.f(5, [10, 20]) // 2 + return this.f(5, [10, 20]); // 2 } g(x: any, y: any): number { - if (x == undefined) return 0 - if (y == undefined) return 1 - return 2 + if (x == undefined) return 0; + if (y == undefined) return 1; + return 2; } callNormal(): number { - return this.g(5, 10) // 2 + return this.g(5, 10); // 2 } callSingle(): number { - return this.g(5) // 1 + return this.g(5); // 1 } callNone(): number { - return this.g() // 0 + return this.g(); // 0 } callUndefined(): number { - return this.g(undefined, 20) // 0 + return this.g(undefined, 20); // 0 } callExtra(): number { - return this.g(5, 10, 20) // 2 + return this.g(5, 10, 20); // 2 } overloaded(a: number): number; + overloaded(a: string): number; + overloaded(a: any): number { - if (typeof a === 'number') return 1 - if (typeof a === 'string') return 2 - return -1 + if (typeof a === 'number') return 1; + if (typeof a === 'string') return 2; + return -1; } callOverloadedNumber(): number { @@ -96,10 +102,6 @@ class Call { return new N1.C().foo(); } - static fifty(): number { - return 50; - } - callStatic(): number { return Call.fifty(); } @@ -166,13 +168,13 @@ class Call { class A { foo(): number { - return 10 + return 10; } } class B { foo(): number { - return 20 + return 20; } } @@ -183,7 +185,7 @@ function makeA(): A { namespace N1 { class C { foo(): number { - return 30 + return 30; } } } @@ -191,7 +193,7 @@ namespace N1 { namespace N2 { class C { foo(): number { - return 40 + return 40; } } } diff --git a/usvm-ts/src/test/resources/samples/FieldAccess.ts b/usvm-ts/src/test/resources/samples/FieldAccess.ts index ace8aed692..fb7e95b3eb 100644 --- a/usvm-ts/src/test/resources/samples/FieldAccess.ts +++ b/usvm-ts/src/test/resources/samples/FieldAccess.ts @@ -47,30 +47,26 @@ class FieldAccess { } createWithField(): number { - return {a: 15}.a; + return { a: 15 }.a; } factoryCreatedObject(): number { return this.createObject().x; } - private createObject(): { x: number } { - return {x: 42}; - } - conditionalFieldAccess(a: SimpleClass): number { if (a.x === 1.1) return 14; return 10; } nestedFieldAccess(): number { - const obj = {inner: new SimpleClass()}; + const obj = { inner: new SimpleClass() }; obj.inner.x = 7; return obj.inner.x; } arrayFieldAccess(): number { - const obj = {arr: [7, 3, 6]}; + const obj = { arr: [7, 3, 6] }; obj.arr[1] = 5; return obj.arr[1]; } @@ -84,7 +80,7 @@ class FieldAccess { circularTypeChanges(): number { const obj = new SimpleClass(); - obj.x = {value: 5}; + obj.x = { value: 5 }; obj.x = obj.x.value; obj.x = [obj.x]; return obj.x.length; @@ -99,4 +95,8 @@ class FieldAccess { return 2; } } + + private createObject(): { x: number } { + return { x: 42 }; + } } diff --git a/usvm-ts/src/test/resources/samples/InstanceFields.ts b/usvm-ts/src/test/resources/samples/InstanceFields.ts index e298b88494..a89721bc4a 100644 --- a/usvm-ts/src/test/resources/samples/InstanceFields.ts +++ b/usvm-ts/src/test/resources/samples/InstanceFields.ts @@ -3,21 +3,21 @@ class InstanceFields { returnSingleField(x: { a: number }): number { - return x.a + return x.a; } dispatchOverField(x: { a: number }): number { - if (x.a == 1) return 1 - if (x.a == 2) return 2 - return 100 + if (x.a == 1) return 1; + if (x.a == 2) return 2; + return 100; } returnSumOfTwoFields(x: { a: number, b: number }): number { - return x.a + x.b + return x.a + x.b; } assignField(x: { a: number }): number { - x.a = 10 - return x.a + x.a = 10; + return x.a; } } diff --git a/usvm-ts/src/test/resources/samples/InstanceMethods.ts b/usvm-ts/src/test/resources/samples/InstanceMethods.ts index f26d2a1f4c..0f78bf7216 100644 --- a/usvm-ts/src/test/resources/samples/InstanceMethods.ts +++ b/usvm-ts/src/test/resources/samples/InstanceMethods.ts @@ -3,21 +3,21 @@ class InstanceMethods { noArguments(): number { - return 42 + return 42; } singleArgument(a: number): number { - if (a != a) return a - if (a == 1) return a - if (a == 2) return a - return 100 + if (a != a) return a; + if (a == 1) return a; + if (a == 2) return a; + return 100; } manyArguments(a: number, b: number, c: number, d: number): number { - if (a == 1) return a - if (b == 2) return b - if (c == 3) return c - if (d == 4) return d - return 100 + if (a == 1) return a; + if (b == 2) return b; + if (c == 3) return c; + if (d == 4) return d; + return 100; } } diff --git a/usvm-ts/src/test/resources/samples/Null.ts b/usvm-ts/src/test/resources/samples/Null.ts index 9dfc16a306..dd18bedfb8 100644 --- a/usvm-ts/src/test/resources/samples/Null.ts +++ b/usvm-ts/src/test/resources/samples/Null.ts @@ -3,7 +3,7 @@ class Null { isNull(x): number { - if (x === null) return 1 - return 2 + if (x === null) return 1; + return 2; } } diff --git a/usvm-ts/src/test/resources/samples/Numeric.ts b/usvm-ts/src/test/resources/samples/Numeric.ts index 4ad0361efe..0d252487e7 100644 --- a/usvm-ts/src/test/resources/samples/Numeric.ts +++ b/usvm-ts/src/test/resources/samples/Numeric.ts @@ -3,92 +3,92 @@ class Numeric { numberToNumber(x: number): number { - if (x != x) return Number(x) // NaN - if (x == 0) return Number(x) // 0 - if (x > 0) return Number(x) // x (>0) - if (x < 0) return Number(x) // x (<0) + if (x != x) return Number(x); // NaN + if (x == 0) return Number(x); // 0 + if (x > 0) return Number(x); // x (>0) + if (x < 0) return Number(x); // x (<0) // unreachable } boolToNumber(x: boolean): number { - if (x) return Number(x) // 1 - if (!x) return Number(x) // 0 + if (x) return Number(x); // 1 + if (!x) return Number(x); // 0 // unreachable } undefinedToNumber(): number { - let x = undefined - return Number(x) // NaN + let x = undefined; + return Number(x); // NaN } nullToNumber(): number { - let x = null - return Number(x) // 0 + let x = null; + return Number(x); // 0 } emptyStringToNumber(): number { - let x = "" - return Number(x) // 0 + let x = ""; + return Number(x); // 0 } numberStringToNumber(): number { - let x = "42" - return Number(x) // 42 + let x = "42"; + return Number(x); // 42 } stringToNumber(): number { - let x = "hello" - return Number(x) // NaN + let x = "hello"; + return Number(x); // NaN } emptyArrayToNumber(): number { - let x = [] - return Number(x) // 0 + let x = []; + return Number(x); // 0 } singleNumberArrayToNumber(): number { - let x = [42] - return Number(x) // 42 + let x = [42]; + return Number(x); // 42 } singleUndefinedArrayToNumber(): number { - let x = [undefined] - return Number(x) // 0 + let x = [undefined]; + return Number(x); // 0 } singleObjectArrayToNumber(): number { - let x = [{}] - return Number(x) // NaN + let x = [{}]; + return Number(x); // NaN } singleCustomFortyTwoObjectArrayToNumber(): number { - let x = [new FortyTwo()] - return Number(x) // 42 + let x = [new FortyTwo()]; + return Number(x); // 42 } multipleElementArrayToNumber(): number { - let x = [5, 100] - return Number(x) // NaN + let x = [5, 100]; + return Number(x); // NaN } emptyObjectToNumber(): number { - let x = {} - return Number(x) // NaN + let x = {}; + return Number(x); // NaN } objectToNumber(): number { - let x = {a: 42} - return Number(x) // NaN + let x = { a: 42 }; + return Number(x); // NaN } customFortyTwoObjectToNumber(): number { - let x = new FortyTwo() - return Number(x) // 42 + let x = new FortyTwo(); + return Number(x); // 42 } } class FortyTwo { toString() { - return "42" + return "42"; } } diff --git a/usvm-ts/src/test/resources/samples/StaticFields.ts b/usvm-ts/src/test/resources/samples/StaticFields.ts index 4589f1786d..b5d2c98a89 100644 --- a/usvm-ts/src/test/resources/samples/StaticFields.ts +++ b/usvm-ts/src/test/resources/samples/StaticFields.ts @@ -51,6 +51,10 @@ class StaticParent { class StaticChild extends StaticParent { override static id = 200; + static getId(): number { + return this.id; + } + getParentId(): number { return StaticParent.id; } @@ -58,10 +62,6 @@ class StaticChild extends StaticParent { getChildId(): number { return StaticChild.id; } - - static getId(): number { - return this.id; - } } // Test: Boolean static toggle @@ -97,7 +97,7 @@ class StaticNull { // Test: Object static operations class StaticObject { - static config: Config = {enabled: true, count: 10}; + static config: Config = { enabled: true, count: 10 }; static modifyAndGet(): Config { this.config.increment(); diff --git a/usvm-ts/src/test/resources/samples/StaticMethods.ts b/usvm-ts/src/test/resources/samples/StaticMethods.ts index 608a140f5d..ddce155fef 100644 --- a/usvm-ts/src/test/resources/samples/StaticMethods.ts +++ b/usvm-ts/src/test/resources/samples/StaticMethods.ts @@ -3,21 +3,21 @@ class StaticMethods { static noArguments(): number { - return 42 + return 42; } static singleArgument(a: number): number { - if (a != a) return a - if (a == 1) return a - if (a == 2) return a - return 100 + if (a != a) return a; + if (a == 1) return a; + if (a == 2) return a; + return 100; } static manyArguments(a: number, b: number, c: number, d: number): number { - if (a == 1) return a - if (b == 2) return b - if (c == 3) return c - if (d == 4) return d - return 100 + if (a == 1) return a; + if (b == 2) return b; + if (c == 3) return c; + if (d == 4) return d; + return 100; } } diff --git a/usvm-ts/src/test/resources/samples/Strings.ts b/usvm-ts/src/test/resources/samples/Strings.ts index c2428b01ff..25f33ba7a9 100644 --- a/usvm-ts/src/test/resources/samples/Strings.ts +++ b/usvm-ts/src/test/resources/samples/Strings.ts @@ -3,74 +3,75 @@ class Strings { typeOfString(): string { - let x = "hello" - return typeof x // "string" + let x = "hello"; + return typeof x; // "string" } typeOfNumber(): string { - let x = 42 - return typeof x // "number" + let x = 42; + return typeof x; // "number" } typeOfBoolean(): string { - let x = true - return typeof x // "boolean" + let x = true; + return typeof x; // "boolean" } typeOfUndefined(): string { - let x = undefined - return typeof x // "undefined" + let x = undefined; + return typeof x; // "undefined" } typeOfNull(): string { - let x = null - return typeof x // "object" + let x = null; + return typeof x; // "object" } typeOfObject(): string { - let x = {} - return typeof x // "object" + let x = {}; + return typeof x; // "object" } typeOfArray(): string { - let x = [5, 42] - return typeof x // "object" + let x = [5, 42]; + return typeof x; // "object" } typeOfFunction(): string { - let x = function() {} - return typeof x // "function" + let x = function () { + }; + return typeof x; // "function" } typeOfInputString(x: string): string { - return typeof x // "string" + return typeof x; // "string" } typeOfInputNumber(x: number): string { - return typeof x // "number" + return typeof x; // "number" } typeOfInputBoolean(x: boolean): string { - return typeof x // "boolean" + return typeof x; // "boolean" } typeOfInputUndefined(x: undefined): string { - return typeof x // "undefined" + return typeof x; // "undefined" } typeOfInputNull(x: null): string { - return typeof x // "object" + return typeof x; // "object" } typeOfInputObject(x: {}): string { - return typeof x // "object" + return typeof x; // "object" } typeOfInputArray(x: any[]): string { - return typeof x // "object" + return typeof x; // "object" } typeOfInputFunction(x: Function): string { - return typeof x // "function" + return typeof x; // "function" } } diff --git a/usvm-ts/src/test/resources/samples/Truthy.ts b/usvm-ts/src/test/resources/samples/Truthy.ts index 52e198a11a..384607b057 100644 --- a/usvm-ts/src/test/resources/samples/Truthy.ts +++ b/usvm-ts/src/test/resources/samples/Truthy.ts @@ -3,9 +3,9 @@ class Truthy { arrayTruthy() { - if (![]) return -1 // unreachable - if (![0]) return -1 // unreachable - if (![1]) return -1 // unreachable - return 0 + if (![]) return -1; // unreachable + if (![0]) return -1; // unreachable + if (![1]) return -1; // unreachable + return 0; } } diff --git a/usvm-ts/src/test/resources/samples/TypeCoercion.ts b/usvm-ts/src/test/resources/samples/TypeCoercion.ts index c0e8937f05..a9bef91624 100644 --- a/usvm-ts/src/test/resources/samples/TypeCoercion.ts +++ b/usvm-ts/src/test/resources/samples/TypeCoercion.ts @@ -4,50 +4,50 @@ class TypeCoercion { argWithConst(a: number): number { - if (a == true) return 1 - return 0 + if (a == true) return 1; + return 0; } argWithArg(a: boolean, b: number): number { if (a + b == 10.0) { - return 1 + return 1; } else { - return 0 + return 0; } } dualBoolean(a: number): number { if (a) { if (a == false) { - return 1 // unreachable code + return 1; // unreachable code } if (a == true) { - return 2 + return 2; } - return 3 + return 3; } // a == 0 - return -1 + return -1; } dualBooleanWithoutTypes(a: string) { if (a) { if (a == false) { - return 1 // REACHABLE code + return 1; // REACHABLE code } if (a == true) { - return 2 + return 2; } - return 3 + return 3; } // a == 0 - return -1 + return -1; } unreachableByType(a: number, b: boolean): number { @@ -59,40 +59,40 @@ class TypeCoercion { No branch can enter this if statement */ if (a && !b) { - return 0 + return 0; } else { - return 1 + return 1; } } - return 2 + return 2; } transitiveCoercion(a: number, b: boolean, c: number): number { if (a == b) { if (c && (a == c)) { - return 1 + return 1; } else { - return 2 + return 2; } } - return 3 + return 3; } transitiveCoercionNoTypes(a, b, c): number { if (a == b) { if (c != 0 && c != 1) { if (c && (a == c)) { - return 1 + return 1; } else { - return 2 + return 2; } } else { - return 4 + return 4; } } - return 3 + return 3; } } diff --git a/usvm-ts/src/test/resources/samples/Undefined.ts b/usvm-ts/src/test/resources/samples/Undefined.ts index 1b5b59c529..88f59c5e25 100644 --- a/usvm-ts/src/test/resources/samples/Undefined.ts +++ b/usvm-ts/src/test/resources/samples/Undefined.ts @@ -3,7 +3,7 @@ class Undefined { isUndefined(x): number { - if (x === undefined) return 1 - return 2 + if (x === undefined) return 1; + return 2; } } diff --git a/usvm-ts/src/test/resources/samples/arrays/AllocatedArrays.ts b/usvm-ts/src/test/resources/samples/arrays/AllocatedArrays.ts index ccf4c505dd..1e683c710a 100644 --- a/usvm-ts/src/test/resources/samples/arrays/AllocatedArrays.ts +++ b/usvm-ts/src/test/resources/samples/arrays/AllocatedArrays.ts @@ -3,64 +3,64 @@ class AllocatedArrays { createConstantArrayOfNumbers() { - let x = [1, 2, 3] - if (x[0] === 1) return 1 - return -1 + let x = [1, 2, 3]; + if (x[0] === 1) return 1; + return -1; } createAndReturnConstantArrayOfNumbers() { - return [1, 2, 3] + return [1, 2, 3]; } createAndAccessArrayOfBooleans() { - let x = [true, false, true] - if (x[0] === true) return 1 - return -1 + let x = [true, false, true]; + if (x[0] === true) return 1; + return -1; } createArrayOfBooleans() { - let x = [true, false, true] - return x + let x = [true, false, true]; + return x; } createMixedArray() { - let x = [1.1, true, undefined] - return x + let x = [1.1, true, undefined]; + return x; } createArrayOfUnknownValues(a, b, c) { - let x = [a, b, c] - if (x[0] === 1.1) return x - if (x[1] === true) return x - if (x[2] === undefined) return x - return x + let x = [a, b, c]; + if (x[0] === 1.1) return x; + if (x[1] === true) return x; + if (x[2] === undefined) return x; + return x; } createArrayOfNumbersAndPutDifferentTypes() { - let x = [1, 2, 3] - x[1] = true - x[2] = undefined - x[0] = null - return x + let x = [1, 2, 3]; + x[1] = true; + x[2] = undefined; + x[0] = null; + return x; } allocatedArrayLengthExpansion() { - let x = [1, 2, 3] + let x = [1, 2, 3]; if (x.length == 3) { - x[5] = 5 + x[5] = 5; if (x.length == 6) { - return x + return x; } else { - return -1 // unreachable + return -1; // unreachable } } else { - return -1 // unreachable + return -1; // unreachable } } writeInTheIndexEqualToLength() { - let x = [1, 2, 3] - x[3] = 4 - return x + let x = [1, 2, 3]; + x[3] = 4; + return x; } } diff --git a/usvm-ts/src/test/resources/samples/arrays/InputArrays.ts b/usvm-ts/src/test/resources/samples/arrays/InputArrays.ts index be8eec7410..200a4c8814 100644 --- a/usvm-ts/src/test/resources/samples/arrays/InputArrays.ts +++ b/usvm-ts/src/test/resources/samples/arrays/InputArrays.ts @@ -3,9 +3,9 @@ class InputArrays { inputArrayOfNumbers(x: number[]) { - if (x[0] === 1) return 1 - if (x[0] === undefined) return -1 - return 2 + if (x[0] === 1) return 1; + if (x[0] === undefined) return -1; + return 2; } writeIntoInputArray(x: number[]) { @@ -17,45 +17,45 @@ class InputArrays { } idForArrayOfNumbers(x: number[]) { - return x + return x; } arrayOfBooleans(x: boolean[]) { - if (x[0] === true) return 1 - return -1 + if (x[0] === true) return 1; + return -1; } arrayOfUnknownValues(x: any[]) { - if (x[0] === 1.1) return x - if (x[1] === true) return x - if (x[2] === undefined) return x - return x + if (x[0] === 1.1) return x; + if (x[1] === true) return x; + if (x[2] === undefined) return x; + return x; } writeIntoArrayOfUnknownValues(x: any[]) { - x[1] = true - x[2] = undefined - x[0] = null - return x + x[1] = true; + x[2] = undefined; + x[0] = null; + return x; } rewriteFakeValueInArray(x: any[]) { if (x[0] === 1) { - x[0] = null + x[0] = null; } - return x[0] + return x[0]; } readFakeObjectAndWriteFakeObject(x: any[], y) { if (x[0] === 1) { - x[0] = y + x[0] = y; if (x[0] === 2) { - return x + return x; } else { - return x + return x; } } - return x + return x; } } diff --git a/usvm-ts/src/test/resources/samples/operators/Add.ts b/usvm-ts/src/test/resources/samples/operators/Add.ts index 50da6ca4f8..b438e648cf 100644 --- a/usvm-ts/src/test/resources/samples/operators/Add.ts +++ b/usvm-ts/src/test/resources/samples/operators/Add.ts @@ -5,70 +5,70 @@ class Add { addBoolAndBool(a: boolean, b: boolean): number { let res = a + b; - if (res == 0 && (a || b)) return -1 // unreachable - if (res < 0 || res > 2) return -1 // unreachable - if (res == 1 && a && b) return -1 // unreachable - if (res == 2 && !(a && b)) return -1 // unreachable + if (res == 0 && (a || b)) return -1; // unreachable + if (res < 0 || res > 2) return -1; // unreachable + if (res == 1 && a && b) return -1; // unreachable + if (res == 2 && !(a && b)) return -1; // unreachable - if (res == 0) return 1 // false false - if (res == 1 && !a) return 2 // false true - if (res == 1 && !b) return 3 // true false - if (res == 2) return 4 // true true + if (res == 0) return 1; // false false + if (res == 1 && !a) return 2; // false true + if (res == 1 && !b) return 3; // true false + if (res == 2) return 4; // true true - return -1 // unreachable + return -1; // unreachable } addBoolAndNumber(a: boolean, b: number): number { let res = a + b; - if (res == 0 && a) return 1 // true -1 - if (res == 0 && !a) return 2 // false 0 + if (res == 0 && a) return 1; // true -1 + if (res == 0 && !a) return 2; // false 0 - if (res == 6 && a) return 3 + if (res == 6 && a) return 3; - if (b != b) return res // _ NaN + if (b != b) return res; // _ NaN - return 4 + return 4; } addNumberAndNumber(a: number, b: number): number { let res = a + b; - if (a != a) return res - if (b != b) return res + if (a != a) return res; + if (b != b) return res; - if (res == 1.1) return res + if (res == 1.1) return res; - return 0 + return 0; } addNumberAndUndefined(a: number): number { let res = a + undefined; - if (res == 0) return -1 // unreachable + if (res == 0) return -1; // unreachable - return res + return res; } addNumberAndNull(a: number): number { let res = a + null; - if (res != a) return res + if (res != a) return res; - return res + return res; } addUnknownValues(a, b) { let res = a + b; - if (a === undefined || b === undefined) return res + if (a === undefined || b === undefined) return res; - if (res != res) return res + if (res != res) return res; - if (res == 7) return res + if (res == 7) return res; - if (a === null && b === null) return res + if (a === null && b === null) return res; - return 42 + return 42; } } diff --git a/usvm-ts/src/test/resources/samples/operators/And.ts b/usvm-ts/src/test/resources/samples/operators/And.ts index a7b6b2a916..b66b06b88d 100644 --- a/usvm-ts/src/test/resources/samples/operators/And.ts +++ b/usvm-ts/src/test/resources/samples/operators/And.ts @@ -3,10 +3,10 @@ class And { andOfBooleanAndBoolean(a: boolean, b: boolean): number { - if (a && b) return 1 - if (a) return 2 - if (b) return 3 - return 4 + if (a && b) return 1; + if (a) return 2; + if (b) return 3; + return 4; } // (a) (b) (a && b) @@ -20,15 +20,15 @@ class And { // 0.0 NaN 0.0 // 0.0 0.0 0.0 andOfNumberAndNumber(a: number, b: number): number { - if (a && b) return 1 // (a!=0 && !a.isNaN) && (b!=0 && !b.isNaN) - if (a && (b != b)) return 2 // (a!=0 && !a.isNaN) && (b.isNaN) - if (a) return 3 // (a!=0 && !a.isNaN) && (b==0) - if ((a != a) && b) return 4 // (a.isNaN) && (b!=0 && !b.isNaN) - if ((a != a) && (b != b)) return 5 // (a.isNaN) && (b.isNaN) - if ((a != a)) return 6 // (a.isNaN) && (b==0) - if (b) return 7 // (a==0) && (b!=0 && !b.isNaN) - if (b != b) return 8 // (a==0) && (b.isNaN) - return 9 // (a==0) && (b==0) + if (a && b) return 1; // (a!=0 && !a.isNaN) && (b!=0 && !b.isNaN) + if (a && (b != b)) return 2; // (a!=0 && !a.isNaN) && (b.isNaN) + if (a) return 3; // (a!=0 && !a.isNaN) && (b==0) + if ((a != a) && b) return 4; // (a.isNaN) && (b!=0 && !b.isNaN) + if ((a != a) && (b != b)) return 5; // (a.isNaN) && (b.isNaN) + if ((a != a)) return 6; // (a.isNaN) && (b==0) + if (b) return 7; // (a==0) && (b!=0 && !b.isNaN) + if (b != b) return 8; // (a==0) && (b.isNaN) + return 9; // (a==0) && (b==0) } // (a) (b) (a && b) @@ -39,12 +39,12 @@ class And { // false NaN false // false 0.0 false andOfBooleanAndNumber(a: boolean, b: number): number { - if (a && b) return 1 // a && (b!=0 && !b.isNaN) - if (a && (b != b)) return 2 // a && (b.isNaN) - if (a) return 3 // a && (b==0) - if (b) return 4 // !a && (b!=0 && !b.isNaN) - if (b != b) return 5 // !a && (b.isNaN) - return 6 // !a && (b==0) + if (a && b) return 1; // a && (b!=0 && !b.isNaN) + if (a && (b != b)) return 2; // a && (b.isNaN) + if (a) return 3; // a && (b==0) + if (b) return 4; // !a && (b!=0 && !b.isNaN) + if (b != b) return 5; // !a && (b.isNaN) + return 6; // !a && (b==0) } // (a) (b) (a && b) @@ -55,31 +55,31 @@ class And { // 0.0 true 0.0 // 0.0 false 0.0 andOfNumberAndBoolean(a: number, b: boolean): number { - if (a && b) return 1 // (a!=0 && !a.isNaN) && b - if (a) return 2 // (a!=0 && !a.isNaN) && !b - if ((a != a) && b) return 3 // (a.isNaN) && b - if (a != a) return 4 // (a.isNaN) && !b - if (b) return 5 // (a==0) && b - return 6 // (a==0) && !b + if (a && b) return 1; // (a!=0 && !a.isNaN) && b + if (a) return 2; // (a!=0 && !a.isNaN) && !b + if ((a != a) && b) return 3; // (a.isNaN) && b + if (a != a) return 4; // (a.isNaN) && !b + if (b) return 5; // (a==0) && b + return 6; // (a==0) && !b } andOfObjectAndObject(a: object, b: object): number { - if (a && b) return 1 - if (a) return 2 - if (b) return 3 - return 4 + if (a && b) return 1; + if (a) return 2; + if (b) return 3; + return 4; } andOfUnknown(a, b): number { - if (a && b) return 1 - if (a) return 2 - if (b) return 3 - return 4 + if (a && b) return 1; + if (a) return 2; + if (b) return 3; + return 4; } truthyUnknown(a, b): number { - if (a) return 1 - if (b) return 2 - return 99 + if (a) return 1; + if (b) return 2; + return 99; } } diff --git a/usvm-ts/src/test/resources/samples/operators/Division.ts b/usvm-ts/src/test/resources/samples/operators/Division.ts index d31c459b78..c75bb22fe3 100644 --- a/usvm-ts/src/test/resources/samples/operators/Division.ts +++ b/usvm-ts/src/test/resources/samples/operators/Division.ts @@ -6,54 +6,54 @@ class Division { let res = a / b; if (res == 4) { - return res + return res; } if (res == Infinity) { - return res + return res; } if (res == -Infinity) { - return res + return res; } if (res != res) { - return res + return res; } - return res + return res; } booleanDivision(a: boolean, b: boolean): number { let res = a / b; if (res == 0) { - return res + return res; } - return res + return res; } mixedDivision(a: number, b: boolean): number { let res = a / b; if (res == 4) { - return res + return res; } if (res == Infinity) { - return res + return res; } if (res == -Infinity) { - return res + return res; } if (res != res) { - return res + return res; } - return res + return res; } unknownDivision(a, b): number { diff --git a/usvm-ts/src/test/resources/samples/operators/Equality.ts b/usvm-ts/src/test/resources/samples/operators/Equality.ts index a829db3456..771497cdf6 100644 --- a/usvm-ts/src/test/resources/samples/operators/Equality.ts +++ b/usvm-ts/src/test/resources/samples/operators/Equality.ts @@ -3,44 +3,44 @@ class Equality { eqBoolWithBool(a: boolean): number { - if (a == true) return 1 - if (a == false) return 2 - return -1 // unreachable + if (a == true) return 1; + if (a == false) return 2; + return -1; // unreachable } eqNumberWithNumber(a: number): number { - if (a != a) return 1 - if (a == 42) return 2 - return 3 + if (a != a) return 1; + if (a == 42) return 2; + return 3; } eqStringWithString(a: string): number { - if (a == "123") return 1 - return 2 + if (a == "123") return 1; + return 2; } eqBigintWithBigint(a: bigint): number { - if (a == 42n) return 1 - return 2 + if (a == 42n) return 1; + return 2; } eqObjectWithObject(a: object): number { - if (a == {b: 0}) return -1 // unreachable - if (a == a) return 1 - return -1 // unreachable + if (a == { b: 0 }) return -1; // unreachable + if (a == a) return 1; + return -1; // unreachable } eqArrayWithArray(a: any[]): number { - if (a == []) return -1 // unreachable - if (a == a) return 1 - return -1 // unreachable + if (a == []) return -1; // unreachable + if (a == a) return 1; + return -1; // unreachable } eqArrayWithBoolean(): number { - if ([] == true) return -1 // unreachable - if ([0] == true) return -1 // unreachable - if ([1] == false) return -1 // unreachable - if ([42] == false) return -1 // unreachable - return 0 + if ([] == true) return -1; // unreachable + if ([0] == true) return -1; // unreachable + if ([1] == false) return -1; // unreachable + if ([42] == false) return -1; // unreachable + return 0; } } diff --git a/usvm-ts/src/test/resources/samples/operators/Neg.ts b/usvm-ts/src/test/resources/samples/operators/Neg.ts index 41d5e309d6..e2625ae03a 100644 --- a/usvm-ts/src/test/resources/samples/operators/Neg.ts +++ b/usvm-ts/src/test/resources/samples/operators/Neg.ts @@ -3,23 +3,23 @@ class Neg { negateNumber(x: number): number { - let y = -x - if (x != x) return y // -NaN == NaN - if (x == 0) return y // -0 == 0 - if (x > 0) return y // -(x>0) == (x<0) - if (x < 0) return y // -(x<0) == (x>0) + let y = -x; + if (x != x) return y; // -NaN == NaN + if (x == 0) return y; // -0 == 0 + if (x > 0) return y; // -(x>0) == (x<0) + if (x < 0) return y; // -(x<0) == (x>0) // unreachable } negateBoolean(x: boolean): number { - let y = -x - if (x) return y // -true == -1 - if (!x) return y // -false == -0 + let y = -x; + if (x) return y; // -true == -1 + if (!x) return y; // -false == -0 // unreachable } negateUndefined(): number { - let x = undefined - return -x // -undefined == NaN + let x = undefined; + return -x; // -undefined == NaN } } diff --git a/usvm-ts/src/test/resources/samples/operators/Or.ts b/usvm-ts/src/test/resources/samples/operators/Or.ts index b04ffd669d..7a374d1909 100644 --- a/usvm-ts/src/test/resources/samples/operators/Or.ts +++ b/usvm-ts/src/test/resources/samples/operators/Or.ts @@ -9,14 +9,14 @@ class Or { // true true true 1 orOfBooleanAndBoolean(a: boolean, b: boolean): number { if (a || b) { - if (a && b) return 1 // (a) && (b) - if (a) return 2 // (a) && (!b) - if (b) return 3 // (!a) && (b) - return 0 + if (a && b) return 1; // (a) && (b) + if (a) return 2; // (a) && (!b) + if (b) return 3; // (!a) && (b) + return 0; } - if (a) return 0 - if (b) return 0 - return 4 // (!a) && (!b) + if (a) return 0; + if (b) return 0; + return 4; // (!a) && (!b) } // (a) (b) (a || b) Case @@ -32,31 +32,31 @@ class Or { orOfNumberAndNumber(a: number, b: number): number { if (a || b) { if (a) { - if (b) return 1 // (a) && (b) - if (b != b) return 2 // (a) && (b.isNaN) - if (b == 0) return 3 // (a) && (b==0) - return 0 + if (b) return 1; // (a) && (b) + if (b != b) return 2; // (a) && (b.isNaN) + if (b == 0) return 3; // (a) && (b==0) + return 0; } if (a != a) { - if (b) return 4 // (a.isNaN) && (b) - return 0 + if (b) return 4; // (a.isNaN) && (b) + return 0; } if (a == 0) { - if (b) return 7 // (a==0) && (b) - return 0 + if (b) return 7; // (a==0) && (b) + return 0; } - return 0 + return 0; } if (a != a) { - if (b != b) return 5 // (a.isNaN) && (b.isNaN) - if (b == 0) return 6 // (a.isNaN) && (b==0) - return 0 + if (b != b) return 5; // (a.isNaN) && (b.isNaN) + if (b == 0) return 6; // (a.isNaN) && (b==0) + return 0; } if (a == 0) { - if (b != b) return 8 // (a==0) && (b.isNaN) - if (b == 0) return 9 // (a==0) && (b==0) - return 0 + if (b != b) return 8; // (a==0) && (b.isNaN) + if (b == 0) return 9; // (a==0) && (b==0) + return 0; } - return 0 // unreachable + return 0; // unreachable } } diff --git a/usvm-ts/src/test/resources/samples/operators/Remainder.ts b/usvm-ts/src/test/resources/samples/operators/Remainder.ts index 2e1c6fea16..b57443385f 100644 --- a/usvm-ts/src/test/resources/samples/operators/Remainder.ts +++ b/usvm-ts/src/test/resources/samples/operators/Remainder.ts @@ -6,58 +6,58 @@ class Remainder { let res = a % b; if (a != a || b != b) { - return res // NaN + return res; // NaN } if (a == Infinity || a == -Infinity) { - return res // NaN + return res; // NaN } if (a == 0 && b == 0) { - return res // NaN + return res; // NaN } if (b == 0) { - return res // NaN + return res; // NaN } if (b == Infinity || b == -Infinity) { - return res // a + return res; // a } if (a == 0) { - return res // a + return res; // a } if (res == 4) { - return res + return res; } - return res + return res; } booleanRemainder(a: boolean, b: boolean): number { let res = a % b; if (res == 0) { - return res + return res; } - return res + return res; } mixedRemainder(a: number, b: boolean): number { let res = a % b; if (res == 4) { - return res + return res; } if (res != res) { - return res + return res; } - return res + return res; } unknownRemainder(a, b): number { diff --git a/usvm-ts/src/test/resources/samples/types/ObjectUsage.ts b/usvm-ts/src/test/resources/samples/types/ObjectUsage.ts index d3f8b51a02..fc8fdd72bc 100644 --- a/usvm-ts/src/test/resources/samples/types/ObjectUsage.ts +++ b/usvm-ts/src/test/resources/samples/types/ObjectUsage.ts @@ -3,7 +3,7 @@ class ObjectUsage { objectAsParameter(x: {}): number { - if (x === undefined) return -1 - return 42 + if (x === undefined) return -1; + return 42; } } diff --git a/usvm-ts/src/test/resources/samples/types/StructuralEquality.ts b/usvm-ts/src/test/resources/samples/types/StructuralEquality.ts index fba8af6974..3d4d008b38 100644 --- a/usvm-ts/src/test/resources/samples/types/StructuralEquality.ts +++ b/usvm-ts/src/test/resources/samples/types/StructuralEquality.ts @@ -48,6 +48,6 @@ class Example { // noinspection UnnecessaryLocalVariableJS const y: C = new D(new A(bar)); - return y + return y; } } From 0e3fe6b600935deb1ee9e2ece77f948f6683e39b Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Thu, 3 Jul 2025 14:22:57 +0300 Subject: [PATCH 75/82] Use '!in' --- usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt index a46d93c770..00556fcf2b 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/state/TsState.kt @@ -96,7 +96,7 @@ class TsState( .takeIf { it >= 0 } ?: error("Statement $stmt is not found in the method CFG") val key = stmt to parentId - if (!discoveredCallees.containsKey(key)) { + if (key !in discoveredCallees) { discoveredCallees = discoveredCallees.put(key, cfg, ownership) } } From f196a980c8f2400fc22adeac488aded73e07cf01 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Thu, 3 Jul 2025 14:39:28 +0300 Subject: [PATCH 76/82] Suppress warning about deprecated `Runtime.exec` --- usvm-ts/src/main/kotlin/org/usvm/util/TsStateVisualizer.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/usvm-ts/src/main/kotlin/org/usvm/util/TsStateVisualizer.kt b/usvm-ts/src/main/kotlin/org/usvm/util/TsStateVisualizer.kt index c7c10485e0..9620489d05 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/util/TsStateVisualizer.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/util/TsStateVisualizer.kt @@ -26,6 +26,7 @@ fun TsState.renderGraph() { myRenderDot(dot) } +@Suppress("Deprecated") fun myRenderDot( dot: String, outDir: Path = createTempDirectory(), From e971786b7e56acd2936e6c52b4e2f820b88863b4 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Thu, 3 Jul 2025 14:42:26 +0300 Subject: [PATCH 77/82] Fix suppression --- usvm-ts/src/main/kotlin/org/usvm/util/TsStateVisualizer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/util/TsStateVisualizer.kt b/usvm-ts/src/main/kotlin/org/usvm/util/TsStateVisualizer.kt index 9620489d05..4cec881225 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/util/TsStateVisualizer.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/util/TsStateVisualizer.kt @@ -26,7 +26,7 @@ fun TsState.renderGraph() { myRenderDot(dot) } -@Suppress("Deprecated") +@Suppress("DEPRECATION") fun myRenderDot( dot: String, outDir: Path = createTempDirectory(), From 8500b6351276c13133372d05379e76a34a843f5b Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Thu, 3 Jul 2025 15:38:34 +0300 Subject: [PATCH 78/82] Refactor UHeapRef.extractRefIfRequired to simplify fake object handling --- usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt index b5dbeaf43c..5a21a762cb 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt @@ -165,15 +165,10 @@ class TsContext( } fun UHeapRef.extractRefIfRequired(scope: TsStepScope): UHeapRef { - var current = this - if (current.isFakeObject()) { - // val fakeType = current.getFakeType(scope) - // scope.doWithState { - // pathConstraints += fakeType.refTypeExpr - // } - current = current.extractRef(scope) + if (isFakeObject()) { + return extractRef(scope) } - return current + return this } fun createFakeObjectRef(): UConcreteHeapRef { From 262d2459ebd5ee9d53cdc091584ddc0c793864d6 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Thu, 3 Jul 2025 15:55:53 +0300 Subject: [PATCH 79/82] TODO in cast expressions --- usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt | 2 +- .../kotlin/org/usvm/machine/expr/TsExprResolver.kt | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt index 7d7002900a..b3d05421a7 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/TsMachine.kt @@ -81,7 +81,7 @@ class TsMachine( timeStatistics = timeStatistics, coverageStatisticsFactory = { coverageStatistics }, cfgStatisticsFactory = { cfgStatistics }, - callGraphStatisticsFactory = { callGraphStatistics } + callGraphStatisticsFactory = { callGraphStatistics }, ) val statesCollector = diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index aeac78f8f6..609fef0bec 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -268,11 +268,13 @@ class TsExprResolver( val resolvedExpr = resolve(expr.arg) ?: return@with null return when (resolvedExpr.sort) { fp64Sort -> { - TODO() + logger.error("Unsupported cast from fp ${expr.arg} to ${expr.type}") + TODO("Not yet implemented https://github.com/UnitTestBot/usvm/issues/299") } boolSort -> { - TODO() + logger.error("Unsupported cast from boolean ${expr.arg} to ${expr.type}") + TODO("Not yet implemented https://github.com/UnitTestBot/usvm/issues/299") } addressSort -> { @@ -288,7 +290,8 @@ class TsExprResolver( } if (expr.type !is EtsRefType) { - TODO("Not supported yet") + logger.error("Unsupported cast from non-ref ${expr.arg} to ${expr.type}") + TODO("Not supported yet https://github.com/UnitTestBot/usvm/issues/299") } pathConstraints += memory.types.evalIsSubtype(instance, expr.type) @@ -912,7 +915,7 @@ class TsExprResolver( // scope.assert(resolvedAddr.getFakeType(scope).refTypeExpr) // pathConstraints += memory.types.evalIsSubtype(extractedRef, auxiliaryType) // } else { - scope.assert(memory.types.evalIsSubtype(resolvedAddr, auxiliaryType)) + scope.assert(memory.types.evalIsSubtype(resolvedAddr, auxiliaryType)) // } } From aed737b474c8cbc6a240f9361a41a8b25ab813a6 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Thu, 3 Jul 2025 16:40:30 +0300 Subject: [PATCH 80/82] Cleanup --- .../main/kotlin/org/usvm/machine/TsContext.kt | 21 +++++- .../org/usvm/machine/expr/TsExprResolver.kt | 64 ++++++------------- .../usvm/machine/interpreter/TsInterpreter.kt | 21 +----- .../kotlin/org/usvm/util/EtsFieldResolver.kt | 6 +- .../main/kotlin/org/usvm/UMachineOptions.kt | 1 - 5 files changed, 43 insertions(+), 70 deletions(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt index 5a21a762cb..cffa31ed41 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/TsContext.kt @@ -31,10 +31,12 @@ import org.usvm.api.allocateConcreteRef import org.usvm.api.typeStreamOf import org.usvm.collection.field.UFieldLValue import org.usvm.isTrue +import org.usvm.machine.Constants.Companion.MAGIC_OFFSET import org.usvm.machine.expr.TsUndefinedSort import org.usvm.machine.expr.TsUnresolvedSort import org.usvm.machine.expr.TsVoidSort import org.usvm.machine.expr.TsVoidValue +import org.usvm.machine.expr.tctx import org.usvm.machine.interpreter.TsStepScope import org.usvm.machine.types.EtsFakeType import org.usvm.memory.UReadOnlyMemory @@ -164,13 +166,22 @@ class TsContext( } } - fun UHeapRef.extractRefIfRequired(scope: TsStepScope): UHeapRef { + fun UHeapRef.unwrapRef(scope: TsStepScope): UHeapRef { if (isFakeObject()) { return extractRef(scope) } return this } + fun UHeapRef.unwrapRefWithPathConstraint(scope: TsStepScope): UHeapRef = with(tctx) { + if (this@unwrapRefWithPathConstraint.isFakeObject()) { + scope.assert(getFakeType(scope).refTypeExpr) + extractRef(scope) + } else { + asExpr(addressSort) + } + } + fun createFakeObjectRef(): UConcreteHeapRef { val address = mkAddressCounter().freshAllocatedAddress() + MAGIC_OFFSET return mkConcreteHeapRef(address) @@ -254,7 +265,13 @@ class TsContext( } } -const val MAGIC_OFFSET = 1000000 +class Constants { + companion object { + const val STATIC_METHODS_FORK_LIMIT = 5 + const val MAGIC_OFFSET = 1000000 + } +} + enum class IntermediateLValueField { BOOL, FP, REF diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index 609fef0bec..54ca88fad1 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -92,6 +92,7 @@ import org.usvm.api.allocateArray import org.usvm.api.evalTypeEquals import org.usvm.dataflow.ts.infer.tryGetKnownType import org.usvm.dataflow.ts.util.type +import org.usvm.machine.Constants import org.usvm.machine.TsConcreteMethodCallStmt import org.usvm.machine.TsContext import org.usvm.machine.TsSizeSort @@ -324,30 +325,17 @@ class TsExprResolver( trueBranch = mkStringConstant("undefined", scope), falseBranch = mkIte( condition = scope.calcOnState { - val unwrappedRef = if (ref.isFakeObject()) { - scope.assert(ref.getFakeType(scope).refTypeExpr) - ref.extractRef(scope) - } else { - ref.asExpr(addressSort) - } + val unwrappedRef = ref.unwrapRefWithPathConstraint(scope) // TODO: adhoc: "expand" ITE if (unwrappedRef is UIteExpr<*>) { val trueBranch = unwrappedRef.trueBranch val falseBranch = unwrappedRef.falseBranch if (trueBranch.isFakeObject() || falseBranch.isFakeObject()) { - val unwrappedTrueExpr = if (trueBranch.isFakeObject()) { - scope.assert(trueBranch.getFakeType(scope).refTypeExpr) - trueBranch.extractRef(scope) - } else { - trueBranch.asExpr(addressSort) - } - val unwrappedFalseExpr = if (falseBranch.isFakeObject()) { - scope.assert(falseBranch.getFakeType(scope).refTypeExpr) - falseBranch.extractRef(scope) - } else { - falseBranch.asExpr(addressSort) - } + val unwrappedTrueExpr = + trueBranch.asExpr(addressSort).unwrapRefWithPathConstraint(scope) + val unwrappedFalseExpr = + falseBranch.asExpr(addressSort).unwrapRefWithPathConstraint(scope) return@calcOnState mkIte( condition = unwrappedRef.condition, trueBranch = memory.types.evalTypeEquals(unwrappedTrueExpr, EtsStringType), @@ -557,10 +545,10 @@ class TsExprResolver( } if (expr.callee.name == "toString") { - return mkStringConstant("I am string", scope) + return mkStringConstant("I am a string", scope) } - // TODO write tests + // TODO write tests https://github.com/UnitTestBot/usvm/issues/300 if (expr.callee.name == "push" && expr.instance.type is EtsArrayType) { return scope.calcOnState { val resolvedInstance = resolve(expr.instance)?.asExpr(ctx.addressSort) ?: return@calcOnState null @@ -573,7 +561,7 @@ class TsExprResolver( memory.write(lengthLValue, newLength, guard = ctx.trueExpr) val resolvedArg = resolve(expr.args.single()) ?: return@calcOnState null - // TODO check sorts compatibility + // TODO check sorts compatibility https://github.com/UnitTestBot/usvm/issues/300 val newIndexLValue = mkArrayIndexLValue( resolvedArg.sort, resolvedInstance, @@ -644,7 +632,6 @@ class TsExprResolver( } if (expr.callee.name == "\$r") { - // TODO hack return scope.calcOnState { val mockSymbol = memory.mocker.createMockSymbol(trackedLiteral = null, addressSort, ownership) @@ -670,7 +657,6 @@ class TsExprResolver( if (resolutionResult is EtsPropertyResolution.Empty) { logger.error { "Could not resolve static call: ${expr.callee}" } scope.assert(falseExpr) - // TODO mocks return null } @@ -678,12 +664,15 @@ class TsExprResolver( if (resolutionResult is EtsPropertyResolution.Ambiguous) { val resolvedArgs = expr.args.map { resolve(it) ?: return null } + val staticProperties = resolutionResult.properties.take( + Constants.STATIC_METHODS_FORK_LIMIT + ) + val staticInstances = scope.calcOnState { - resolutionResult.properties.take(1).map { getStaticInstance(it.enclosingClass!!) } + staticProperties.map { getStaticInstance(it.enclosingClass!!) } } - // TODO take 1 is an error - val concreteCalls = resolutionResult.properties.take(1).mapIndexed { index, value -> + val concreteCalls = staticProperties.mapIndexed { index, value -> TsConcreteMethodCallStmt( callee = value, instance = staticInstances[index], @@ -694,7 +683,7 @@ class TsExprResolver( val blocks = concreteCalls.map { ctx.mkTrue() to { state: TsState -> state.newStmt(it) } - } // TODO error with true Expr + } scope.forkMulti(blocks) @@ -722,7 +711,7 @@ class TsExprResolver( private fun resolveStaticMethod( method: EtsMethodSignature, - ): EtsPropertyResolution { + ): EtsPropertyResolution { // Perfect signature: if (method.enclosingClass.name != UNKNOWN_CLASS_NAME) { val classes = hierarchy.classesForType(EtsClassType(method.enclosingClass)) @@ -836,11 +825,7 @@ class TsExprResolver( } fun checkUndefinedOrNullPropertyRead(instance: UHeapRef) = with(ctx) { - val ref = if (instance.isFakeObject()) { - instance.extractRef(scope) - } else { - instance - } + val ref = instance.unwrapRef() val neqNull = mkAnd( mkHeapRefEq(ref, mkUndefinedValue()).not(), @@ -882,7 +867,7 @@ class TsExprResolver( field: EtsFieldSignature, hierarchy: EtsHierarchy, ): UExpr? = with(ctx) { - val resolvedAddr = instanceRef.extractRefIfRequired(scope) + val resolvedAddr = instanceRef.unwrapRef(scope) val etsField = resolveEtsField(instance, field, hierarchy) @@ -909,20 +894,9 @@ class TsExprResolver( // That's not true in the common case for TS, but that's the decision we made. val auxiliaryType = EtsAuxiliaryType(properties = setOf(field.name)) // assert is required to update models - - // if (resolvedAddr.isFakeObject()) { - // val extractedRef = resolvedAddr.extractRef(scope) - // scope.assert(resolvedAddr.getFakeType(scope).refTypeExpr) - // pathConstraints += memory.types.evalIsSubtype(extractedRef, auxiliaryType) - // } else { scope.assert(memory.types.evalIsSubtype(resolvedAddr, auxiliaryType)) - // } } - // TODO - // Если мы записали что-то в объект в виде bool, а потом считали это из поля bool | null, то здесь будет unresolvedSort, - // мы проигнорируем что уже туда писали и считаем не оттуда - if (sort == unresolvedSort) { val boolLValue = mkFieldLValue(boolSort, instanceRef, field) val fpLValue = mkFieldLValue(fp64Sort, instanceRef, field) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt index 5cf2e128bc..8ec9f2be2f 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/interpreter/TsInterpreter.kt @@ -147,7 +147,6 @@ class TsInterpreter( "Exception: $e\n${e.stackTrace.take(5).joinToString("\n") { " $it" }}" } return StepResult(forkedStates = emptySequence(), originalStateAlive = false) - // todo are exceptional states properly removed? } return scope.stepResult() @@ -192,12 +191,6 @@ class TsInterpreter( return } val cls = classes.single() - // val method = cls.methods - // .singleOrNull { it.name == stmt.callee.name } - // ?: run { - // TODO("Overloads are not supported yet") - // } - // concreteMethods += method val suitableMethods = cls.methods.filter { it.name == stmt.callee.name } concreteMethods += suitableMethods } else { @@ -278,18 +271,8 @@ class TsInterpreter( val trueBranch = ref.trueBranch val falseBranch = ref.falseBranch if (trueBranch.isFakeObject() || falseBranch.isFakeObject()) { - val unwrappedTrueExpr = if (trueBranch.isFakeObject()) { - scope.assert(trueBranch.getFakeType(scope).refTypeExpr) - trueBranch.extractRef(scope) - } else { - trueBranch.asExpr(addressSort) - } - val unwrappedFalseExpr = if (falseBranch.isFakeObject()) { - scope.assert(falseBranch.getFakeType(scope).refTypeExpr) - falseBranch.extractRef(scope) - } else { - falseBranch.asExpr(addressSort) - } + val unwrappedTrueExpr = trueBranch.asExpr(addressSort).unwrapRefWithPathConstraint(scope) + val unwrappedFalseExpr = falseBranch.asExpr(addressSort).unwrapRefWithPathConstraint(scope) return@calcOnState mkIte( condition = ref.condition, trueBranch = memory.types.evalIsSubtype(unwrappedTrueExpr, type), diff --git a/usvm-ts/src/main/kotlin/org/usvm/util/EtsFieldResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/util/EtsFieldResolver.kt index 5515850437..695e79e21c 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/util/EtsFieldResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/util/EtsFieldResolver.kt @@ -16,7 +16,7 @@ fun TsContext.resolveEtsField( instance: EtsLocal?, field: EtsFieldSignature, hierarchy: EtsHierarchy, -): EtsPropertyResolution { +): EtsPropertyResolution { // Perfect signature: if (field.enclosingClass.name != UNKNOWN_CLASS_NAME) { val classes = hierarchy.classesForType(EtsClassType(field.enclosingClass)) @@ -116,7 +116,7 @@ fun EtsClass.getAllProperties(hierarchy: EtsHierarchy): Pair, return allFields to allMethods } -sealed class EtsPropertyResolution { +sealed class EtsPropertyResolution { data class Unique(val property: T) : EtsPropertyResolution() data class Ambiguous(val properties: List) : EtsPropertyResolution() data object Empty : EtsPropertyResolution() @@ -124,7 +124,7 @@ sealed class EtsPropertyResolution { companion object { fun create(property: T) = Unique(property) - fun create(properties: List): EtsPropertyResolution { + fun create(properties: List): EtsPropertyResolution { return when { properties.isEmpty() -> Empty properties.size == 1 -> Unique(properties.single()) diff --git a/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt b/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt index de5646aaa8..b2989d2685 100644 --- a/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt +++ b/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt @@ -259,5 +259,4 @@ data class UMachineOptions( * Limit loop iterations. * */ val loopIterationLimit: Int? = null, - // TODO fork limit ) From 129922481ff3d522188e2b73031c712969537daf Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Thu, 3 Jul 2025 16:42:21 +0300 Subject: [PATCH 81/82] Fix matchers --- .../src/main/kotlin/org/usvm/test/util/checkers/Matchers.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usvm-util/src/main/kotlin/org/usvm/test/util/checkers/Matchers.kt b/usvm-util/src/main/kotlin/org/usvm/test/util/checkers/Matchers.kt index 7a678de8d0..ee71dc8bcf 100644 --- a/usvm-util/src/main/kotlin/org/usvm/test/util/checkers/Matchers.kt +++ b/usvm-util/src/main/kotlin/org/usvm/test/util/checkers/Matchers.kt @@ -18,7 +18,7 @@ fun between(bounds: IntRange) = AnalysisResultsNumberMatcher( val ignoreNumberOfAnalysisResults = AnalysisResultsNumberMatcher( description = "Allow any number of results except zero", matcherFailedMessage = { _ -> "No analysis results received" } -) { it >= 0 } +) { it > 0 } val noResultsExpected = AnalysisResultsNumberMatcher( description = "No executions expected", @@ -32,4 +32,4 @@ class AnalysisResultsNumberMatcher( ) { operator fun invoke(x: Int) = cmp(x) override fun toString() = description -} \ No newline at end of file +} From d6dca5d8f96d1990ee7239ed7e18a63a8521134c Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Thu, 3 Jul 2025 16:47:33 +0300 Subject: [PATCH 82/82] Fix matchers --- usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt index 54ca88fad1..c061506033 100644 --- a/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt +++ b/usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt @@ -825,7 +825,7 @@ class TsExprResolver( } fun checkUndefinedOrNullPropertyRead(instance: UHeapRef) = with(ctx) { - val ref = instance.unwrapRef() + val ref = instance.unwrapRef(scope) val neqNull = mkAnd( mkHeapRefEq(ref, mkUndefinedValue()).not(),