From 919c57ec4e80161e62e268cb2696ca8491523b96 Mon Sep 17 00:00:00 2001 From: Max Nigmatulin Date: Wed, 20 Sep 2023 12:19:33 +0300 Subject: [PATCH 1/2] Add GNN path selector --- Game_env/test_model.onnx | Bin 0 -> 82137 bytes buildSrc/src/main/kotlin/Versions.kt | 2 + .../kotlin/usvm.kotlin-conventions.gradle.kts | 1 + .../src/main/kotlin/org/usvm/PathTrieNode.kt | 13 +- .../src/main/kotlin/org/usvm/ps/BlockGraph.kt | 149 ++++++++++++ .../org/usvm/ps/BlockGraphPathSelector.kt | 196 ++++++++++++++++ .../kotlin/org/usvm/ps/GNNPathSelector.kt | 221 ++++++++++++++++++ .../kotlin/org/usvm/ps/PathSelectorFactory.kt | 5 + .../org/usvm/statistics/ApplicationGraph.kt | 2 + .../org/usvm/statistics/CoverageStatistics.kt | 4 + .../org/usvm/statistics/StepsStatistics.kt | 18 ++ .../org/usvm/machine/JcApplicationGraph.kt | 5 + .../machine/interpreter/JcExprResolver.kt | 2 +- .../usvm/machine/interpreter/JcInterpreter.kt | 2 +- .../org/usvm/machine/state/JcMethodResult.kt | 5 +- .../kotlin/org/usvm/machine/state/JcState.kt | 3 + .../org/usvm/machine/state/JcStateUtils.kt | 4 +- .../main/kotlin/org/usvm/UMachineOptions.kt | 7 +- 18 files changed, 631 insertions(+), 8 deletions(-) create mode 100644 Game_env/test_model.onnx create mode 100644 usvm-core/src/main/kotlin/org/usvm/ps/BlockGraph.kt create mode 100644 usvm-core/src/main/kotlin/org/usvm/ps/BlockGraphPathSelector.kt create mode 100644 usvm-core/src/main/kotlin/org/usvm/ps/GNNPathSelector.kt create mode 100644 usvm-core/src/main/kotlin/org/usvm/statistics/StepsStatistics.kt diff --git a/Game_env/test_model.onnx b/Game_env/test_model.onnx new file mode 100644 index 0000000000000000000000000000000000000000..d67d151a47ab74565da0010d7a74a645c4d17875 GIT binary patch literal 82137 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 D^dj(z literal 0 HcmV?d00001 diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 8f765786f8..b3b0b91976 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -7,6 +7,8 @@ object Versions { const val jcdb = "1.2.0" const val mockk = "1.13.4" const val junitParams = "5.9.3" + const val serialization = "1.5.1" + const val onnxruntime = "1.15.1" const val logback = "1.4.8" // versions for jvm samples diff --git a/buildSrc/src/main/kotlin/usvm.kotlin-conventions.gradle.kts b/buildSrc/src/main/kotlin/usvm.kotlin-conventions.gradle.kts index 2d3c8a49c4..1f8c90aa72 100644 --- a/buildSrc/src/main/kotlin/usvm.kotlin-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/usvm.kotlin-conventions.gradle.kts @@ -20,6 +20,7 @@ dependencies { implementation(kotlin("stdlib-jdk8")) implementation(kotlin("reflect")) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}") + implementation("com.microsoft.onnxruntime", "onnxruntime", Versions.onnxruntime) testImplementation(kotlin("test")) } diff --git a/usvm-core/src/main/kotlin/org/usvm/PathTrieNode.kt b/usvm-core/src/main/kotlin/org/usvm/PathTrieNode.kt index 13ae4d9b88..f8425720b9 100644 --- a/usvm-core/src/main/kotlin/org/usvm/PathTrieNode.kt +++ b/usvm-core/src/main/kotlin/org/usvm/PathTrieNode.kt @@ -34,6 +34,11 @@ sealed class PathsTrieNode, Stateme */ abstract val depth: Int + /** + * States that were forked from this node + */ + abstract val accumulatedForks: MutableCollection + /** * Adds a new label to [labels] collection. */ @@ -77,9 +82,10 @@ class PathsTrieNodeImpl, Statement> depth = parentNode.depth + 1, parent = parentNode, states = hashSetOf(state), - statement = statement + statement = statement, ) { parentNode.children[statement] = this + parentNode.accumulatedForks.addAll(this.states) } internal constructor(parentNode: PathsTrieNodeImpl, statement: Statement, state: State) : this( @@ -89,11 +95,14 @@ class PathsTrieNodeImpl, Statement> statement = statement ) { parentNode.children[statement] = this + parentNode.accumulatedForks.addAll(this.states) parentNode.states -= state } override val labels: MutableSet = hashSetOf() + override val accumulatedForks: MutableCollection = mutableSetOf() + override fun addLabel(label: Any) { labels.add(label) } @@ -115,6 +124,8 @@ class RootNode, Statement> : PathsT override val labels: MutableSet = hashSetOf() + override val accumulatedForks: MutableCollection = mutableSetOf() + override val depth: Int = 0 override fun addLabel(label: Any) { diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/BlockGraph.kt b/usvm-core/src/main/kotlin/org/usvm/ps/BlockGraph.kt new file mode 100644 index 0000000000..612cd8f36b --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/ps/BlockGraph.kt @@ -0,0 +1,149 @@ +package org.usvm.ps + +import org.usvm.statistics.ApplicationGraph + +data class Block( + val id: Int, + var path: MutableList = mutableListOf(), + + var parents: MutableSet> = mutableSetOf(), + var children: MutableSet> = mutableSetOf() +) { + override fun hashCode(): Int = id + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Block<*> + + if (id != other.id) return false + + return true + } +} + +class BlockGraph( + initialStatement: Statement, + private val applicationGraph: ApplicationGraph, +) { + val root: Block + private var nextBlockId: Int = 0 + private val blockStatementMapping = HashMap, MutableList>() + + val blocks: Collection> + get() = blockStatementMapping.keys + + init { + root = buildGraph(initialStatement) + } + + fun getGraphBlock(statement: Statement): Block? { + blockStatementMapping.forEach { + if (statement in it.value) { + return it.key + } + } + return null + } + + private fun initializeGraphBlockWith(statement: Statement): Block { + val currentBlock = Block(nextBlockId++, path = mutableListOf(statement)) + blockStatementMapping.computeIfAbsent(currentBlock) { mutableListOf() }.add(statement) + return currentBlock + } + + private fun createAndLinkWithPreds(statement: Statement): Block { + val currentBlock = initializeGraphBlockWith(statement) + for (pred in applicationGraph.predecessors(statement)) { + getGraphBlock(pred)?.children?.add(currentBlock) + getGraphBlock(pred)?.let { currentBlock.parents.add(it) } + } + return currentBlock + } + + private fun Statement.inBlock() = getGraphBlock(this) != null + + private fun ApplicationGraph.filterStmtSuccsNotInBlock( + statement: Statement, + forceNewBlock: Boolean + ): Sequence> { + return this.successors(statement).filter { !it.inBlock() }.map { Pair(it, forceNewBlock) } + } + + fun buildGraph(initial: Statement): Block { + val root = initializeGraphBlockWith(initial) + var currentBlock = root + val statementQueue = ArrayDeque>() + + val initialHasMultipleSuccessors = applicationGraph.successors(initial).count() > 1 + statementQueue.addAll( + applicationGraph.filterStmtSuccsNotInBlock( + initial, + forceNewBlock = initialHasMultipleSuccessors + ) + ) + + while (statementQueue.isNotEmpty()) { + val (currentStatement, forceNew) = statementQueue.removeFirst() + + if (forceNew) { + // don't need to add `currentStatement` succs, we did it earlier + createAndLinkWithPreds(currentStatement) + continue + } + + // if statement is a call or if statement has multiple successors: next statements start new block + if (applicationGraph.callees(currentStatement).any() || applicationGraph.successors(currentStatement).count() > 1) { + currentBlock.path.add(currentStatement) + blockStatementMapping.computeIfAbsent(currentBlock) { mutableListOf() }.add(currentStatement) + statementQueue.addAll(applicationGraph.filterStmtSuccsNotInBlock(currentStatement, forceNewBlock = true)) + continue + } + + // if statement has multiple ins: next statements start new block + if (applicationGraph.predecessors(currentStatement).count() > 1) { + currentBlock = createAndLinkWithPreds(currentStatement) + blockStatementMapping.computeIfAbsent(currentBlock) { mutableListOf() }.add(currentStatement) + statementQueue.addAll(applicationGraph.filterStmtSuccsNotInBlock(currentStatement, forceNewBlock = true)) + continue + } + + currentBlock.path.add(currentStatement) + blockStatementMapping.computeIfAbsent(currentBlock) { mutableListOf() }.add(currentStatement) + statementQueue.addAll(applicationGraph.filterStmtSuccsNotInBlock(currentStatement, forceNewBlock = false)) + } + + return root + } + + fun getEdges(): List { + return blocks.flatMap { block -> + block.children.map { GameMapEdge(it.id, block.id, GameEdgeLabel(0)) } + } + } + + fun getVertices(): Collection> = blocks + + fun getBlockFeatures( + block: Block, isCovered: (Statement) -> Boolean, + inCoverageZone: (Statement) -> Boolean, + isVisited: (Statement) -> Boolean, + stateIdsInBlock: List + ): BlockFeatures { + val firstStatement = block.path.first() + val lastStatement = block.path.last() + val visitedByState = isVisited(lastStatement) + val touchedByState = visitedByState || isVisited(firstStatement) + + return BlockFeatures( + id = block.id, + inCoverageZone = inCoverageZone(firstStatement), + basicBlockSize = block.path.size, + coveredByTest = isCovered(firstStatement), + visitedByState = visitedByState, + touchedByState = touchedByState, + states = stateIdsInBlock + ) + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/BlockGraphPathSelector.kt b/usvm-core/src/main/kotlin/org/usvm/ps/BlockGraphPathSelector.kt new file mode 100644 index 0000000000..f45cab5941 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/ps/BlockGraphPathSelector.kt @@ -0,0 +1,196 @@ +package org.usvm.ps + +import org.usvm.* +import org.usvm.constraints.UPathConstraints +import org.usvm.statistics.* +import java.io.File +import kotlin.io.path.Path + +data class GameEdgeLabel( + val token: Int +) + +data class GameMapEdge( + val vertexFrom: Int, + val vertexTo: Int, + val label: GameEdgeLabel, +) + +data class BlockFeatures( + val uid: Int = 0, + val id: Int, + val basicBlockSize: Int, + val inCoverageZone: Boolean, + val coveredByTest: Boolean, + val visitedByState: Boolean, + val touchedByState: Boolean, + val states: List, +) + +data class StateHistoryElem( + val graphVertexId: Int, + val numOfVisits: Int, +) + +data class StateFeatures( + val id: StateId, + val position: Int = 0, + val predictedUsefulness: Int = 42, + val pathConditionSize: Int, + val visitedAgainVertices: Int, + val visitedNotCoveredVerticesInZone: Int, + val visitedNotCoveredVerticesOutOfZone: Int, + val history: List, + val children: List, +) + +open class BlockGraphPathSelector, Statement, Method>( + private val coverageStatistics: CoverageStatistics, + val applicationGraph: ApplicationGraph +) : UPathSelector { + protected val states: MutableList = mutableListOf() + private val visitedStatements = HashSet() + + private val filename: String + + protected val blockGraph: BlockGraph + + init { + val method = applicationGraph.methodOf(coverageStatistics.getUncoveredStatements().first()) + filename = method.toString().dropWhile { it != ')' }.drop(1) + blockGraph = BlockGraph(applicationGraph.entryPoints(method).first(), applicationGraph) + } + + private fun getNonThrowingLeaves(root: Block): Collection> { + val queue = ArrayDeque>() + val visited = HashSet>() + val leaves = mutableListOf>() + queue.addAll(root.children) + + while (queue.isNotEmpty()) { + val next = queue.removeFirst() + if (next.children.isEmpty() && next.path.none { applicationGraph.isThrowing(it) }) { + leaves.add(next) + } + visited.add(next) + queue.addAll(next.children) + } + + return leaves + } + + fun getStateFeatures(state: State): StateFeatures { + val blockHistory = mutableListOf>() + + var lastBlock: Block = blockGraph.root + blockHistory.add(lastBlock) + for (statement in state.listPath()) { + if (statement !in lastBlock.path) { + val someBlockOpt = blockGraph.getGraphBlock(statement) + // if `statement` already has a block + if (someBlockOpt != null) { + blockHistory.add(someBlockOpt) + lastBlock = someBlockOpt + } + else { // encountered non-explored statements, extend block graph + val callRoot = blockGraph.buildGraph(statement) + val callExitBlocksToConnect = getNonThrowingLeaves(callRoot) + + // if `statement` is last in prev block -> connect to `lastBlock` children + if (statement == lastBlock.path.last() && statement != state.listPath().last()) { + lastBlock.children.forEach { lastBlockChild -> + callExitBlocksToConnect.forEach { callExitBlock -> + callExitBlock.children.add(lastBlockChild) + } + } + } else { // connect to last block itself + callExitBlocksToConnect.forEach { externalExitBlock -> + externalExitBlock.children.add(lastBlock) + } + } + } + } + } + + var visitedNotCoveredVerticesInZone = 0 + var visitedNotCoveredVerticesOutOfZone = 0 + + for (block in blockHistory.map { block -> + blockGraph.getBlockFeatures( + block = block, + isCovered = ::isCovered, + inCoverageZone = ::inCoverageZone, + isVisited = ::isVisited, + stateIdsInBlock = states.filter { it.currentStatement in block.path }.map { it.id } + ) + }) { + if (block.visitedByState && !block.coveredByTest) { + if (block.inCoverageZone) { + visitedNotCoveredVerticesInZone += 1 + } else visitedNotCoveredVerticesOutOfZone += 1 + } + } + + return StateFeatures( + id = state.id, + pathConditionSize = state.pathConstraints.size(), + visitedAgainVertices = state.listPath().count() - state.listPath().distinct().count(), + visitedNotCoveredVerticesInZone = visitedNotCoveredVerticesInZone, + visitedNotCoveredVerticesOutOfZone = visitedNotCoveredVerticesOutOfZone, + history = blockHistory.map { block -> + StateHistoryElem( + block.id, + blockHistory.count { block.id == it.id }) + }, + children = state.pathLocation.accumulatedForks.map { it.id } + ) + } + + protected fun isCovered(statement: Statement): Boolean { + return statement in coverageStatistics.getUncoveredStatements() + } + + protected fun inCoverageZone(statement: Statement): Boolean { + return coverageStatistics.inCoverageZone(applicationGraph.methodOf(statement)) + } + + protected fun isVisited(statement: Statement) = statement in visitedStatements + + override fun isEmpty(): Boolean { + return states.isEmpty() + } + + override fun peek(): State { + return states.first() + } + + override fun update(state: State) {} + + override fun add(states: Collection) { + this.states += states + } + + override fun remove(state: State) { + states.remove(state) + } +} + +fun UPathConstraints.size(): Int { + return numericConstraints.constraints().count() + + this.equalityConstraints.distinctReferences.count() + + this.equalityConstraints.equalReferences.count() + + this.equalityConstraints.referenceDisequalities.count() + + this.equalityConstraints.nullableDisequalities.count() + + this.logicalConstraints.count() + + this.typeConstraints.symbolicRefToTypeRegion.count() // TODO: maybe throw out? +} + +fun, Statement> UState<*, *, Statement, *, *, State>.listPath(): List { + val statements = mutableListOf() + var current: PathsTrieNode? = this.pathLocation + while (current !is RootNode && current != null) { + statements.add(current.statement) + current = current.parent + } + return statements +} diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/GNNPathSelector.kt b/usvm-core/src/main/kotlin/org/usvm/ps/GNNPathSelector.kt new file mode 100644 index 0000000000..94bfbf6187 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/ps/GNNPathSelector.kt @@ -0,0 +1,221 @@ +package org.usvm.ps + +import ai.onnxruntime.OnnxTensor +import ai.onnxruntime.OrtEnvironment +import ai.onnxruntime.OrtSession +import org.usvm.UState +import org.usvm.statistics.ApplicationGraph +import org.usvm.statistics.CoverageStatistics +import java.nio.FloatBuffer +import java.nio.LongBuffer +import kotlin.collections.set +import kotlin.io.path.Path + +data class GraphNative( + val gameVertex: List>, + val stateVertex: List>, + val gameVertexToGameVertex: List>, + val gameVertexHistoryStateVertexIndex: List>, + val gameVertexHistoryStateVertexAttrs: List, + val gameVertexInStateVertex: List>, + val stateVertexParentOfStateVertex: List>, + val stateMap: Map +) + +open class GNNPathSelector, Statement, Method>( + applicationGraph: ApplicationGraph, + private val coverageStatistics: CoverageStatistics, +) : BlockGraphPathSelector( + coverageStatistics, + applicationGraph +) { + companion object { + private val gnnModelPath = Path("/Users/emax/Data/usvm/Game_env/test_model.onnx").toString() + private var env: OrtEnvironment = OrtEnvironment.getEnvironment() + private var gnnSession: OrtSession = env.createSession(gnnModelPath) + } + + fun coverage(): Float { + return coverageStatistics.getTotalCoverage() + } + + override fun peek(): State { + val nativeInput = createNativeInput() + + val gameVertexTensor = onnxFloatTensor(nativeInput.gameVertex) + val stateVertexTensor = onnxFloatTensor(nativeInput.stateVertex) + val gameVertexToGameVertexTensor = onnxLongTensor(nativeInput.gameVertexToGameVertex.transpose()) + val gameVertexHistoryStateVertexIndexTensor = + onnxLongTensor(nativeInput.gameVertexHistoryStateVertexIndex.transpose()) + val gameVertexHistoryStateVertexAttrsTensor = + onnxLongTensor(nativeInput.gameVertexHistoryStateVertexAttrs.map { listOf(it) }) + val gameVertexInStateVertexTensor = onnxLongTensor(nativeInput.gameVertexInStateVertex.transpose()) + val stateVertexParentOfStateVertexTensor = + onnxLongTensor(nativeInput.stateVertexParentOfStateVertex.transpose()) + + val res = gnnSession.run( + mapOf( + "game_vertex" to gameVertexTensor, + "state_vertex" to stateVertexTensor, + "game_vertex to game_vertex" to gameVertexToGameVertexTensor, + "game_vertex history state_vertex index" to gameVertexHistoryStateVertexIndexTensor, + "game_vertex history state_vertex attrs" to gameVertexHistoryStateVertexAttrsTensor, + "game_vertex in state_vertex" to gameVertexInStateVertexTensor, + "state_vertex parent_of state_vertex" to stateVertexParentOfStateVertexTensor + ) + ) + + val predictedStatesRanks = + (res["out"].get().value as Array<*>).map { it as FloatArray }.map { it.toList() }.toList() + val chosenStateId = predictState(predictedStatesRanks, nativeInput.stateMap) + + return states.single { state -> state.id.toInt() == chosenStateId } + } + + private fun createNativeInput(): GraphNative { + val nodesState = mutableListOf>() + val nodesVertex = mutableListOf>() + val edgesIndexVSHistory = mutableListOf>() + val edgesAttrVS = mutableListOf() + val edgesIndexSS = mutableListOf>() + val edgesIndexVSIn = mutableListOf>() + + val stateMap = mutableMapOf() + val vertexMap = mutableMapOf() + + val statesFeatures = states.map { getStateFeatures(it) } + + for ((stateIndexOrder, stateFeatures) in statesFeatures.withIndex()) { + stateMap[stateFeatures.id.toInt()] = stateIndexOrder + nodesState.add(stateFeatures.toList()) + } + + for ((vertexIndexOrder, vertex) in blockGraph.getVertices().withIndex()) { + vertexMap[vertex.id] = vertexIndexOrder + val blockFeatures = blockGraph.getBlockFeatures( + block = vertex, + isCovered = ::isCovered, + inCoverageZone = ::inCoverageZone, + isVisited = ::isVisited, + stateIdsInBlock = states.filter { it.currentStatement in vertex.path }.map { it.id } + ) + nodesVertex.add(blockFeatures.toList()) + } + + val edgesIndexVV = blockGraph.getEdges().map { listOf(vertexMap[it.vertexFrom]!!, vertexMap[it.vertexTo]!!) } + + for ((stateIndexOrder, stateFeatures) in statesFeatures.withIndex()) { + for (historyEdge in stateFeatures.history) { + val vertexTo = vertexMap[historyEdge.graphVertexId]!! + edgesIndexVSHistory.add(listOf(vertexTo, stateIndexOrder)) + edgesAttrVS.add(historyEdge.numOfVisits) + } + } + + for (stateFeatures in statesFeatures) { + for (childId in stateFeatures.children) { + if (childId.toInt() in stateMap.keys) + edgesIndexSS.add(listOf(stateMap[stateFeatures.id.toInt()]!!, stateMap[childId.toInt()]!!)) + } + } + + for (vertex in blockGraph.getVertices()) { + for (state in states) { + edgesIndexVSIn.add(listOf(vertexMap[vertex.id]!!, stateMap[state.id.toInt()]!!)) + } + } + + return GraphNative( + gameVertex = nodesVertex, + stateVertex = nodesState, + gameVertexToGameVertex = edgesIndexVV, + gameVertexHistoryStateVertexIndex = edgesIndexVSHistory, + gameVertexHistoryStateVertexAttrs = edgesAttrVS, + gameVertexInStateVertex = edgesIndexVSIn, + stateVertexParentOfStateVertex = edgesIndexSS, + stateMap = stateMap + ) + } + + private fun List.create2DFloatBuffer(shape: Pair): OnnxTensor { + val longArrayOfShape = longArrayOf(shape.first.toLong(), shape.second.toLong()) + return OnnxTensor.createTensor( + env, + FloatBuffer.wrap(this.toFloatArray()), + longArrayOfShape + ) + } + + private fun List.create2DLongBuffer(shape: Pair): OnnxTensor { + val longArrayOfShape = longArrayOf(shape.first.toLong(), shape.second.toLong()) + return OnnxTensor.createTensor( + env, + LongBuffer.wrap(this.toLongArray()), + longArrayOfShape + ) + } + + private fun onnxFloatTensor(data: List>): OnnxTensor { + return data.flatten().map { it.toFloat() }.create2DFloatBuffer(get2DShape(data)) + } + + private fun onnxLongTensor(data: List>): OnnxTensor { + return data.flatten().map { it.toLong() }.create2DLongBuffer(get2DShape(data)) + } +} + +private fun predictState(stateRank: List>, stateMap: Map): Int { + val reverseStateMap = stateMap.entries.associate { (k, v) -> v to k } + + val stateRankMapping = stateRank.mapIndexed { orderIndex, rank -> + reverseStateMap[orderIndex]!! to rank + } + + return stateRankMapping.maxBy { it.second.sum() }.first +} + +private fun get2DShape(data: List>): Pair { + if (data.isEmpty()) { + return Pair(0, 0) + } + if (data[0].isEmpty()) { + return Pair(data.size, 0) + } + return Pair(data.size, data[0].size) +} + +private fun List>.transpose(): List> { + if (this.isEmpty()) { + return listOf(listOf(), listOf()) + } + + val (rows, cols) = get2DShape(this) + return List(cols) { j -> + List(rows) { i -> + this[i][j] + } + } +} + +private fun Boolean.toInt(): Int = if (this) 1 else 0 + +private fun BlockFeatures.toList(): List { + return listOf( + this.inCoverageZone.toInt(), + this.basicBlockSize, + this.coveredByTest.toInt(), + this.visitedByState.toInt(), + this.touchedByState.toInt() + ) +} + +private fun StateFeatures.toList(): List { + return listOf( + this.position, + this.predictedUsefulness, + this.pathConditionSize, + this.visitedAgainVertices, + this.visitedNotCoveredVerticesInZone, + this.visitedNotCoveredVerticesOutOfZone + ) +} diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt index 321c04ff56..9b1a933fcc 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt @@ -84,6 +84,11 @@ fun , State : USta applicationGraph, random ) + + PathSelectionStrategy.GNN -> GNNPathSelector( + applicationGraph, + requireNotNull(coverageStatistics()) { "Coverage statistics is required for Hetero GNN path selector" }, + ) } } diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/ApplicationGraph.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/ApplicationGraph.kt index 48fab1befe..aa02c7fdd2 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/ApplicationGraph.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/ApplicationGraph.kt @@ -13,4 +13,6 @@ interface ApplicationGraph { fun methodOf(node: Statement): Method fun statementsOf(method: Method): Sequence + + fun isThrowing(node: Statement): Boolean } diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/CoverageStatistics.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/CoverageStatistics.kt index d2751472f8..2d345ec347 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/CoverageStatistics.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/CoverageStatistics.kt @@ -89,6 +89,10 @@ class CoverageStatistics> : UMachineObserver { + + private var steps = 0 + override fun onState(parent: State, forks: Sequence) { + steps += 1 + } + + fun getStepsCount() = steps +} diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcApplicationGraph.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcApplicationGraph.kt index f0a0e2bb4a..405159e5cb 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcApplicationGraph.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcApplicationGraph.kt @@ -5,6 +5,7 @@ import org.jacodb.api.JcClasspath import org.jacodb.api.JcMethod import org.jacodb.api.JcTypedMethod import org.jacodb.api.cfg.JcInst +import org.jacodb.api.cfg.JcThrowInst import org.jacodb.api.ext.toType import org.jacodb.impl.features.HierarchyExtensionImpl import org.jacodb.impl.features.SyncUsagesExtension @@ -73,4 +74,8 @@ class JcApplicationGraph( return statements } + + override fun isThrowing(node: JcInst): Boolean { + return node is JcThrowInst + } } diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt index 7fd0043522..c7d6fb55e4 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt @@ -805,7 +805,7 @@ class JcExprResolver( fun allocateException(type: JcRefType): (JcState) -> Unit = { state -> // TODO should we consider exceptions with negative addresses? val address = state.memory.allocConcrete(type) - state.throwExceptionWithoutStackFrameDrop(address, type) + state.throwExceptionWithoutStackFrameDrop(address, type, false) } fun checkArrayIndex(idx: USizeExpr, length: USizeExpr) = with(ctx) { diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreter.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreter.kt index 5e6f15c3b9..372d81288b 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreter.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreter.kt @@ -334,7 +334,7 @@ class JcInterpreter( val address = resolver.resolveJcExpr(stmt.throwable)?.asExpr(ctx.addressSort) ?: return scope.calcOnState { - throwExceptionWithoutStackFrameDrop(address, stmt.throwable.type) + throwExceptionWithoutStackFrameDrop(address, stmt.throwable.type, expected = true) } } diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcMethodResult.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcMethodResult.kt index f0f05b2663..29c5688d68 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcMethodResult.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcMethodResult.kt @@ -31,8 +31,9 @@ sealed interface JcMethodResult { open class JcException( val address: UHeapRef, val type: JcType, - val symbolicStackTrace: List> + val symbolicStackTrace: List>, + val expected: Boolean, ) : JcMethodResult { - override fun toString(): String = "${this::class.simpleName}: Address: $address, type: ${type.typeName}" + override fun toString(): String = "${this::class.simpleName}: Address: $address, type: ${type.typeName}, is expected: $expected" } } diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcState.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcState.kt index 066c9ded34..ae05f53271 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcState.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcState.kt @@ -47,6 +47,9 @@ class JcState( override val isExceptional: Boolean get() = methodResult is JcMethodResult.JcException + val isExceptionalAndNotExpected: Boolean + get() = isExceptional && !(methodResult as JcMethodResult.JcException).expected + override fun toString(): String = buildString { appendLine("Instruction: $lastStmt") if (isExceptional) appendLine("Exception: $methodResult") diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcStateUtils.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcStateUtils.kt index 4dafd26f58..9b7a8e112e 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcStateUtils.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcStateUtils.kt @@ -37,8 +37,8 @@ fun JcState.returnValue(valueToReturn: UExpr) { /** * Create an unprocessed exception with the [address] and the [type] and assign it to the [JcState.methodResult]. */ -fun JcState.throwExceptionWithoutStackFrameDrop(address: UHeapRef, type: JcType) { - methodResult = JcMethodResult.JcException(address, type, callStack.stackTrace(lastStmt)) +fun JcState.throwExceptionWithoutStackFrameDrop(address: UHeapRef, type: JcType, expected: Boolean) { + methodResult = JcMethodResult.JcException(address, type, callStack.stackTrace(lastStmt), expected) } fun JcState.throwExceptionAndDropStackFrame() { diff --git a/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt b/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt index 797a631e09..b2e52287af 100644 --- a/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt +++ b/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt @@ -77,7 +77,12 @@ enum class PathSelectionStrategy { * reachability. * States are selected randomly with distribution based on distance to targets. */ - TARGETED_CALL_STACK_LOCAL_RANDOM + TARGETED_CALL_STACK_LOCAL_RANDOM, + + /** + * Selects state with the best score according to GNN + */ + GNN } enum class PathSelectorCombinationStrategy { From 961053db8d1acce2337280238573e62270a2b4a0 Mon Sep 17 00:00:00 2001 From: Max Nigmatulin Date: Wed, 20 Sep 2023 12:36:42 +0300 Subject: [PATCH 2/2] Fix CI, refactor code --- .../main/kotlin/org/usvm/ps/BlockGraphPathSelector.kt | 10 ++++------ .../src/test/kotlin/org/usvm/TestApplicationGraph.kt | 2 ++ .../kotlin/org/usvm/machine/SampleApplicationGraph.kt | 2 ++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/BlockGraphPathSelector.kt b/usvm-core/src/main/kotlin/org/usvm/ps/BlockGraphPathSelector.kt index f45cab5941..87e549337e 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ps/BlockGraphPathSelector.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ps/BlockGraphPathSelector.kt @@ -2,9 +2,8 @@ package org.usvm.ps import org.usvm.* import org.usvm.constraints.UPathConstraints -import org.usvm.statistics.* -import java.io.File -import kotlin.io.path.Path +import org.usvm.statistics.ApplicationGraph +import org.usvm.statistics.CoverageStatistics data class GameEdgeLabel( val token: Int @@ -91,8 +90,7 @@ open class BlockGraphPathSelector UPathConstraints.size(): Int { this.typeConstraints.symbolicRefToTypeRegion.count() // TODO: maybe throw out? } -fun, Statement> UState<*, *, Statement, *, *, State>.listPath(): List { +fun , Statement> UState<*, *, Statement, *, *, State>.listPath(): List { val statements = mutableListOf() var current: PathsTrieNode? = this.pathLocation while (current !is RootNode && current != null) { diff --git a/usvm-core/src/test/kotlin/org/usvm/TestApplicationGraph.kt b/usvm-core/src/test/kotlin/org/usvm/TestApplicationGraph.kt index e87ad7f549..d85252a5d8 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TestApplicationGraph.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TestApplicationGraph.kt @@ -104,6 +104,8 @@ private class TestApplicationGraphBuilderImpl : TestApplicationGraphBuilder, App .map { TestInstruction(method, it) } .asSequence() } + + override fun isThrowing(node: TestInstruction) = false } internal fun appGraph(init: TestApplicationGraphBuilder.() -> Unit): ApplicationGraph { diff --git a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleApplicationGraph.kt b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleApplicationGraph.kt index fc5675da0c..35f99a74ac 100644 --- a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleApplicationGraph.kt +++ b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleApplicationGraph.kt @@ -137,6 +137,8 @@ class SampleApplicationGraph( return info.method } + override fun isThrowing(node: Stmt) = false + private data class StmtInfo( val method: Method<*>, val predecessors: List,