Skip to content

Commit

Permalink
Introduction of TypeScript symbolic machine (#197)
Browse files Browse the repository at this point in the history
  • Loading branch information
CaelmBleidd authored Jul 23, 2024
1 parent 22760b0 commit f4422a3
Show file tree
Hide file tree
Showing 20 changed files with 1,315 additions and 0 deletions.
5 changes: 5 additions & 0 deletions buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ object Libs {
name = "jacodb-api-jvm",
version = Versions.jacodb
)
val jacodb_ets = dep(
group = jacodbPackage,
name = "jacodb-ets",
version = Versions.jacodb
)
val jacodb_api_common = dep(
group = jacodbPackage,
name = "jacodb-api-common",
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ rootProject.name = "usvm"

include("usvm-core")
include("usvm-jvm")
include("usvm-ts")
include("usvm-util")
include("usvm-jvm-instrumentation")
include("usvm-sample-language")
Expand Down
23 changes: 23 additions & 0 deletions usvm-ts/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
plugins {
id("usvm.kotlin-conventions")
}

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

implementation(Libs.jacodb_core)
implementation(Libs.jacodb_ets)

implementation(Libs.ksmt_yices)
implementation(Libs.ksmt_cvc5)
implementation(Libs.ksmt_symfpu)
implementation(Libs.ksmt_runner)

testImplementation(Libs.mockk)
testImplementation(Libs.junit_jupiter_params)
testImplementation(Libs.logback)

// https://mvnrepository.com/artifact/org.burningwave/core
// Use it to export all modules to all
testImplementation("org.burningwave:core:12.62.7")
}
35 changes: 35 additions & 0 deletions usvm-ts/src/main/kotlin/org/usvm/TSApplicationGraph.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.usvm

import org.jacodb.ets.base.EtsStmt
import org.jacodb.ets.graph.EtsApplicationGraph
import org.jacodb.ets.model.EtsFile
import org.jacodb.ets.model.EtsMethod
import org.usvm.statistics.ApplicationGraph

class TSApplicationGraph(project: EtsFile) : ApplicationGraph<EtsMethod, EtsStmt> {
private val applicationGraph = EtsApplicationGraph(project)

override fun predecessors(node: EtsStmt): Sequence<EtsStmt> =
applicationGraph.predecessors(node)

override fun successors(node: EtsStmt): Sequence<EtsStmt> =
applicationGraph.successors(node)

override fun callees(node: EtsStmt): Sequence<EtsMethod> =
applicationGraph.callees(node)

override fun callers(method: EtsMethod): Sequence<EtsStmt> =
applicationGraph.callers(method)

override fun entryPoints(method: EtsMethod): Sequence<EtsStmt> =
applicationGraph.entryPoints(method)

override fun exitPoints(method: EtsMethod): Sequence<EtsStmt> =
applicationGraph.exitPoints(method)

override fun methodOf(node: EtsStmt): EtsMethod =
applicationGraph.methodOf(node)

override fun statementsOf(method: EtsMethod): Sequence<EtsStmt> =
method.cfg.stmts.asSequence()
}
46 changes: 46 additions & 0 deletions usvm-ts/src/main/kotlin/org/usvm/TSComponents.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.usvm

import io.ksmt.solver.yices.KYicesSolver
import io.ksmt.solver.z3.KZ3Solver
import io.ksmt.symfpu.solver.KSymFpuSolver
import org.jacodb.ets.base.EtsType
import org.usvm.solver.USolverBase
import org.usvm.solver.UTypeSolver
import org.usvm.types.UTypeSystem

class TSComponents(
private val typeSystem: TSTypeSystem,
private val options: UMachineOptions,
) : UComponents<EtsType, TSSizeSort> {
private val closeableResources = mutableListOf<AutoCloseable>()

override val useSolverForForks: Boolean
get() = TODO("Not yet implemented")

override fun <Context : UContext<TSSizeSort>> mkSizeExprProvider(ctx: Context): USizeExprProvider<TSSizeSort> {
TODO("Not yet implemented")
}

override fun mkTypeSystem(
ctx: UContext<TSSizeSort>,
): UTypeSystem<EtsType> = typeSystem

override fun <Context : UContext<TSSizeSort>> mkSolver(ctx: Context): USolverBase<EtsType> {
val (translator, decoder) = buildTranslatorAndLazyDecoder(ctx)

val smtSolver = when (options.solverType) {
SolverType.YICES -> KSymFpuSolver(KYicesSolver(ctx), ctx)
SolverType.Z3 -> KZ3Solver(ctx)
}

closeableResources += smtSolver

val typeSolver = UTypeSolver(typeSystem)

return USolverBase(ctx, smtSolver, typeSolver, translator, decoder, options.solverTimeout)
}

fun close() {
closeableResources.forEach(AutoCloseable::close)
}
}
5 changes: 5 additions & 0 deletions usvm-ts/src/main/kotlin/org/usvm/TSContext.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.usvm

typealias TSSizeSort = UBv32Sort

class TSContext(components: TSComponents) : UContext<TSSizeSort>(components)
77 changes: 77 additions & 0 deletions usvm-ts/src/main/kotlin/org/usvm/TSInterpreter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package org.usvm

import io.ksmt.utils.asExpr
import org.jacodb.ets.base.EtsStmt
import org.jacodb.ets.base.EtsType
import org.jacodb.ets.model.EtsMethod
import org.usvm.forkblacklists.UForkBlackList
import org.usvm.memory.URegisterStackLValue
import org.usvm.solver.USatResult
import org.usvm.state.TSMethodResult
import org.usvm.state.TSState
import org.usvm.state.lastStmt
import org.usvm.targets.UTargetsSet

class TSInterpreter(
private val ctx: TSContext,
private val applicationGraph: TSApplicationGraph,
) : UInterpreter<TSState>() {
private val forkBlackList: UForkBlackList<TSState, EtsStmt> = UForkBlackList.createDefault()

override fun step(state: TSState): StepResult<TSState> {
val stmt = state.lastStmt
val scope = StepScope(state, forkBlackList)

val result = state.methodResult
if (result is TSMethodResult.TSException) {
// TODO catch processing
scope.doWithState {
val returnSite = callStack.pop()

if (callStack.isNotEmpty()) {
memory.stack.pop()
}

if (returnSite != null) {
newStmt(returnSite)
}
}

return scope.stepResult()
}

// TODO: interpreter
stmt.nextStmt?.let { nextStmt ->
scope.doWithState { newStmt(nextStmt) }
}

return scope.stepResult()
}

fun getInitialState(method: EtsMethod, targets: List<TSTarget>): TSState {
val state = TSState(ctx, method, targets = UTargetsSet.from(targets))

with(ctx) {
val params = List(method.parameters.size) { idx ->
URegisterStackLValue(addressSort, idx)
}
val refs = params.map { state.memory.read(it) }

// TODO check correctness of constraints and process this instance
state.pathConstraints += mkAnd(refs.map { mkEq(it.asExpr(addressSort), nullRef).not() })
}

val solver = ctx.solver<EtsType>()
val model = (solver.check(state.pathConstraints) as USatResult).model
state.models = listOf(model)

state.callStack.push(method, returnSite = null)
state.memory.stack.push(method.parameters.size, method.localsCount)
state.pathNode += method.cfg.instructions.first()

return state
}

// TODO: expand with interpreter implementation
private val EtsStmt.nextStmt get() = applicationGraph.successors(this).firstOrNull()
}
110 changes: 110 additions & 0 deletions usvm-ts/src/main/kotlin/org/usvm/TSMachine.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package org.usvm

import org.jacodb.ets.base.EtsStmt
import org.jacodb.ets.model.EtsFile
import org.jacodb.ets.model.EtsMethod
import org.usvm.ps.createPathSelector
import org.usvm.state.TSMethodResult
import org.usvm.state.TSState
import org.usvm.statistics.CompositeUMachineObserver
import org.usvm.statistics.CoverageStatistics
import org.usvm.statistics.StepsStatistics
import org.usvm.statistics.TimeStatistics
import org.usvm.statistics.UMachineObserver
import org.usvm.statistics.collectors.AllStatesCollector
import org.usvm.statistics.collectors.CoveredNewStatesCollector
import org.usvm.statistics.collectors.TargetsReachedStatesCollector
import org.usvm.statistics.distances.CfgStatisticsImpl
import org.usvm.statistics.distances.PlainCallGraphStatistics
import org.usvm.stopstrategies.createStopStrategy
import kotlin.time.Duration.Companion.seconds

class TSMachine(
private val project: EtsFile,
private val options: UMachineOptions,
) : UMachine<TSState>() {
private val typeSystem = TSTypeSystem(typeOperationsTimeout = 1.seconds, project)
private val components = TSComponents(typeSystem, options)
private val ctx = TSContext(components)
private val applicationGraph = TSApplicationGraph(project)
private val interpreter = TSInterpreter(ctx, applicationGraph)
private val cfgStatistics = CfgStatisticsImpl(applicationGraph)

fun analyze(
methods: List<EtsMethod>,
targets: List<TSTarget> = emptyList(),
): List<TSState> {
val initialStates = mutableMapOf<EtsMethod, TSState>()
methods.forEach { initialStates[it] = interpreter.getInitialState(it, targets) }

val methodsToTrackCoverage =
when (options.coverageZone) {
CoverageZone.METHOD, CoverageZone.TRANSITIVE -> methods.toHashSet()
CoverageZone.CLASS -> TODO("Unsupported yet")
}

val coverageStatistics = CoverageStatistics<EtsMethod, EtsStmt, TSState>(
methodsToTrackCoverage,
applicationGraph
)

val callGraphStatistics: PlainCallGraphStatistics<EtsMethod> =
when (options.targetSearchDepth) {
0u -> PlainCallGraphStatistics()
else -> TODO("Unsupported yet")
}

val timeStatistics = TimeStatistics<EtsMethod, TSState>()

val pathSelector = createPathSelector(
initialStates,
options,
applicationGraph,
timeStatistics,
{ coverageStatistics },
{ cfgStatistics },
{ callGraphStatistics },
)

val statesCollector =
when (options.stateCollectionStrategy) {
StateCollectionStrategy.COVERED_NEW -> CoveredNewStatesCollector<TSState>(coverageStatistics) {
it.methodResult is TSMethodResult.TSException
}

StateCollectionStrategy.REACHED_TARGET -> TargetsReachedStatesCollector()
StateCollectionStrategy.ALL -> AllStatesCollector()
}

val observers = mutableListOf<UMachineObserver<TSState>>(coverageStatistics)
observers.add(statesCollector)

val stepsStatistics = StepsStatistics<EtsMethod, TSState>()

val stopStrategy = createStopStrategy(
options,
targets,
timeStatisticsFactory = { timeStatistics },
stepsStatisticsFactory = { stepsStatistics },
coverageStatisticsFactory = { coverageStatistics },
getCollectedStatesCount = { statesCollector.collectedStates.size }
)

observers.add(timeStatistics)
observers.add(stepsStatistics)

run(
interpreter,
pathSelector,
observer = CompositeUMachineObserver(observers),
isStateTerminated = { state -> state.callStack.isEmpty() },
stopStrategy = stopStrategy
)

return statesCollector.collectedStates
}

override fun close() {
components.close()
}
}
6 changes: 6 additions & 0 deletions usvm-ts/src/main/kotlin/org/usvm/TSTarget.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.usvm

import org.jacodb.ets.base.EtsStmt
import org.usvm.targets.UTarget

class TSTarget : UTarget<EtsStmt, TSTarget>()
11 changes: 11 additions & 0 deletions usvm-ts/src/main/kotlin/org/usvm/TSTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.usvm

import org.jacodb.ets.base.EtsStmt

class TSTest(
val parameters: List<Any>,
val resultValue: Any?,
val trace: List<EtsStmt>? = null,
)

class TSMethodCoverage
36 changes: 36 additions & 0 deletions usvm-ts/src/main/kotlin/org/usvm/TSTypeSystem.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.usvm

import org.jacodb.ets.base.EtsType
import org.jacodb.ets.model.EtsFile
import org.usvm.types.UTypeStream
import org.usvm.types.UTypeSystem
import kotlin.time.Duration

class TSTypeSystem(
override val typeOperationsTimeout: Duration,
val project: EtsFile,
) : UTypeSystem<EtsType> {
override fun isSupertype(supertype: EtsType, type: EtsType): Boolean {
TODO("Not yet implemented")
}

override fun hasCommonSubtype(type: EtsType, types: Collection<EtsType>): Boolean {
TODO("Not yet implemented")
}

override fun isFinal(type: EtsType): Boolean {
TODO("Not yet implemented")
}

override fun isInstantiable(type: EtsType): Boolean {
TODO("Not yet implemented")
}

override fun findSubtypes(type: EtsType): Sequence<EtsType> {
TODO("Not yet implemented")
}

override fun topTypeStream(): UTypeStream<EtsType> {
TODO("Not yet implemented")
}
}
8 changes: 8 additions & 0 deletions usvm-ts/src/main/kotlin/org/usvm/Util.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.usvm

import org.jacodb.ets.base.EtsStmt
import org.usvm.state.TSState

fun TSState.newStmt(stmt: EtsStmt) {
pathNode += stmt
}
Loading

0 comments on commit f4422a3

Please sign in to comment.