From d36396115a3f035d33af2beacbadb6864e5c367e Mon Sep 17 00:00:00 2001 From: Artem Trubnikov Date: Wed, 7 Feb 2024 15:15:19 +0300 Subject: [PATCH] [WIP] add cfg for panda --- jacodb-panda-static/build.gradle.kts | 1 + .../org/jacodb/panda/staticvm/GraphUtils.kt | 47 +++++++++------- .../panda/staticvm/PandaApplicationGraph.kt | 54 +++++++++++++++++++ .../org/jacodb/panda/staticvm/PandaClasses.kt | 13 +++-- .../panda/staticvm/PandaControlFlowGraph.kt | 51 ++++++++++++++++++ .../jacodb/panda/staticvm/PandaInstList.kt | 40 ++++++++++++-- .../panda/staticvm/PandaInstListBuilder.kt | 2 +- .../src/test/kotlin/IrDeserializationTest.kt | 7 ++- 8 files changed, 182 insertions(+), 33 deletions(-) create mode 100644 jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/PandaApplicationGraph.kt create mode 100644 jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/PandaControlFlowGraph.kt diff --git a/jacodb-panda-static/build.gradle.kts b/jacodb-panda-static/build.gradle.kts index f1858b811..3270b46a0 100644 --- a/jacodb-panda-static/build.gradle.kts +++ b/jacodb-panda-static/build.gradle.kts @@ -10,6 +10,7 @@ dependencies { api(project(":jacodb-core")) api(project(":jacodb-api-jvm")) api(project(":jacodb-api-core")) + api(project(":jacodb-analysis")) implementation(Libs.kotlin_logging) implementation(Libs.kotlinx_serialization_json) diff --git a/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/GraphUtils.kt b/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/GraphUtils.kt index ab8d077ce..d12350af6 100644 --- a/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/GraphUtils.kt +++ b/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/GraphUtils.kt @@ -16,14 +16,16 @@ package org.jacodb.panda.staticvm +import org.jacodb.api.core.cfg.Graph + data class SimpleDirectedGraph( val nodes: MutableSet = mutableSetOf(), - private val predecessorsMap: MutableMap> = mutableMapOf(), - private val successorsMap: MutableMap> = mutableMapOf() -) { - fun predecessors(t: T): List = predecessorsMap.getOrDefault(t, emptyList()) + private val predecessorsMap: MutableMap> = mutableMapOf(), + private val successorsMap: MutableMap> = mutableMapOf() +) : Graph { + override fun predecessors(node: T): Set = predecessorsMap.getOrDefault(node, emptySet()) - fun successors(t: T): List = successorsMap.getOrDefault(t, emptyList()) + override fun successors(node: T): Set = successorsMap.getOrDefault(node, emptySet()) fun withNode(node: T): SimpleDirectedGraph { nodes.add(node) @@ -33,16 +35,19 @@ data class SimpleDirectedGraph( fun withEdge(from: T, to: T): SimpleDirectedGraph { nodes.add(from) nodes.add(to) - predecessorsMap.getOrPut(to, ::mutableListOf).add(from) - successorsMap.getOrPut(from, ::mutableListOf).add(to) + predecessorsMap.getOrPut(to, ::mutableSetOf).add(from) + successorsMap.getOrPut(from, ::mutableSetOf).add(to) return this } + private fun Collection.intersectMutable(other: Collection) = + toSet().intersect(other.toSet()).toMutableSet() + fun induced(subset: Collection) = SimpleDirectedGraph( - nodes.intersect(subset.toSet()).toMutableSet(), - predecessorsMap.mapNotNull { (key, value) -> if (subset.contains(key)) key to subset.intersect(value.toSet()).toMutableList() else null } + nodes.intersectMutable(subset), + predecessorsMap.mapNotNull { (key, value) -> if (subset.contains(key)) key to subset.intersectMutable(value) else null } .toMap().toMutableMap(), - successorsMap.mapNotNull { (key, value) -> if (subset.contains(key)) key to subset.intersect(value.toSet()).toMutableList() else null } + successorsMap.mapNotNull { (key, value) -> if (subset.contains(key)) key to subset.intersectMutable(value) else null } .toMap().toMutableMap() ) fun weaklyConnectedComponents(): List> = components(nodes) { @@ -70,16 +75,18 @@ data class SimpleDirectedGraph( lhs.successorsMap.plus(rhs.successorsMap).toMutableMap() ) } + + override fun iterator(): Iterator = nodes.asIterable().iterator() } -fun search(start: T, successors: (T) -> List, visitor: (T) -> Unit): List { +fun search(start: T, successors: (T) -> List, visitor: (T) -> Unit): Set { val visited = hashSetOf() fun dfs(node: T) = node.takeIf(visited::add)?.also(visitor)?.let { successors(it).forEach(::dfs) } dfs(start) - return visited.toList() + return visited } -fun components(starts: Iterable, successors: (T) -> List): List> { +fun components(starts: Iterable, successors: (T) -> Collection): List> { val visited = hashSetOf() return starts.mapNotNull { start -> if (start !in visited) search(start, { successors(it).filter { it !in visited } }, visited::add) @@ -87,11 +94,11 @@ fun components(starts: Iterable, successors: (T) -> List): List reachable(starts: Iterable, successors: (T) -> List): List { - val visited = hashSetOf() - starts.forEach { start -> - if (start !in visited) - visited.addAll(search(start, { successors(it).filter { it !in visited } }, {})) +fun Iterable.applyFold(initial: A, operation: A.(T) -> Unit) = fold(initial) { acc, elem -> + acc.apply { operation(elem) } } + +fun reachable(starts: Iterable, successors: (T) -> Collection): Set = + starts.applyFold(hashSetOf()) { start -> + if (!contains(start)) + addAll(search(start, { successors(it).filterNot(this::contains) }, {})) } - return visited.toList() -} diff --git a/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/PandaApplicationGraph.kt b/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/PandaApplicationGraph.kt new file mode 100644 index 000000000..069b13be1 --- /dev/null +++ b/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/PandaApplicationGraph.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.panda.staticvm + +import org.jacodb.api.core.analysis.ApplicationGraph + +class PandaApplicationGraph( + private val project: PandaProject +) : ApplicationGraph { + private val callersMap = project.methods + .flatMap { it.flowGraph().instructions } + .flatMap { inst -> callees(inst).map { it to inst } } + .groupBy(Pair::first, Pair::second) + + override fun predecessors(node: PandaInst): Sequence = + node.location.method.flowGraph().predecessors(node).asSequence() + override fun successors(node: PandaInst): Sequence = + node.location.method.flowGraph().predecessors(node).asSequence() + + override fun callees(node: PandaInst): Sequence = when (node) { + is PandaAssignInst -> when (val expr = node.rhv) { + is PandaStaticCallExpr -> sequenceOf(expr.method) + is PandaVirtualCallExpr -> sequenceOf(expr.method) + else -> emptySequence() + } + else -> emptySequence() + } + + override fun callers(method: PandaMethod): Sequence + = callersMap[method]?.asSequence() ?: emptySequence() + + override fun entryPoint(method: PandaMethod): Sequence = + method.flowGraph().entries.asSequence() + + override fun exitPoints(method: PandaMethod): Sequence = + method.flowGraph().exits.asSequence() + + override fun methodOf(node: PandaInst): PandaMethod = + node.location.method +} \ No newline at end of file diff --git a/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/PandaClasses.kt b/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/PandaClasses.kt index 5af173d12..ec500ff5d 100644 --- a/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/PandaClasses.kt +++ b/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/PandaClasses.kt @@ -20,7 +20,7 @@ import org.jacodb.api.core.CoreMethod import org.jacodb.api.core.cfg.ControlFlowGraph import org.jacodb.api.jvm.cfg.JcInst -data class PandaField( +class PandaField( val project: PandaProject, val declaringClassType: PandaClassName, val type: PandaTypeName, @@ -57,7 +57,7 @@ data class PandaField( } } -data class PandaMethod( +class PandaMethod( val project: PandaProject, val declaringClassType: PandaClassName, val returnType: PandaTypeName, @@ -65,11 +65,10 @@ data class PandaMethod( val name: String, val body: SimpleDirectedGraph?, val access: AccessFlags -) : CoreMethod { +) : CoreMethod { + private val flowGraphValue by lazy { PandaControlFlowGraph.of(this) } - override fun flowGraph(): ControlFlowGraph { - TODO("Not yet implemented") - } + override fun flowGraph(): ControlFlowGraph = flowGraphValue override fun equals(other: Any?): Boolean { if (this === other) return true @@ -103,7 +102,7 @@ data class PandaMethod( get() = TODO("Not yet implemented") } -data class PandaClass( +class PandaClass( val project: PandaProject, val declaredFields: List, val declaredMethods: List, diff --git a/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/PandaControlFlowGraph.kt b/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/PandaControlFlowGraph.kt new file mode 100644 index 000000000..ee0f60427 --- /dev/null +++ b/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/PandaControlFlowGraph.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.panda.staticvm + +import org.jacodb.api.core.cfg.ControlFlowGraph +import org.jacodb.api.core.cfg.Graph + +class PandaControlFlowGraph private constructor( + val method: PandaMethod, + val instList: PandaInstList, + private val graph: SimpleDirectedGraph +) : ControlFlowGraph, Graph by graph { + companion object { + fun of(method: PandaMethod): PandaControlFlowGraph { + val instList = PandaInstListBuilder(method).build() + val graph = instList.flatMapIndexed { index, inst -> + when (inst) { + is PandaBranchingInst -> inst.successors.map { instList[it.index] } + is PandaTerminatingInst -> emptyList() + else -> listOf(instList[index + 1]) + }.map { inst to it } + }.applyFold(SimpleDirectedGraph()) { (from, to) -> withEdge(from, to) } + + return PandaControlFlowGraph(method, instList, graph) + } + } + + override val entries: List = listOfNotNull(instList.firstOrNull()) + + override val exits: List = setOfNotNull(instList.lastOrNull()) + .plus(instList.filterIsInstance()) + .toList() + + override val instructions: List = instList.instructions + + override fun iterator(): Iterator = instList.iterator() +} \ No newline at end of file diff --git a/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/PandaInstList.kt b/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/PandaInstList.kt index 154c730fa..464a4b8ba 100644 --- a/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/PandaInstList.kt +++ b/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/PandaInstList.kt @@ -31,9 +31,41 @@ data class PandaInstList(override val instructions: List) : InstList< override fun getOrNull(index: Int): PandaInst? = instructions.getOrNull(index) - override fun toMutableList(): MutableInstList { - TODO("Not yet implemented") - } + override fun toMutableList(): MutableInstList = PandaMutableInstList(instructions.toMutableList()) override fun iterator(): Iterator = instructions.iterator() -} \ No newline at end of file +} + +data class PandaMutableInstList(override val instructions: MutableList) : MutableInstList { + override fun insertBefore(inst: PandaInst, vararg newInstructions: PandaInst) = + insertBefore(inst, newInstructions.toList()) + + override fun insertBefore(inst: PandaInst, newInstructions: Collection): Unit = + instructions.run { addAll(indexOf(inst), newInstructions) } + + override fun insertAfter(inst: PandaInst, vararg newInstructions: PandaInst) = + insertAfter(inst, newInstructions.toList()) + + override fun insertAfter(inst: PandaInst, newInstructions: Collection): Unit = + instructions.run { addAll(indexOf(inst) + 1, newInstructions) } + + override fun remove(inst: PandaInst): Boolean = instructions.remove(inst) + + override fun removeAll(inst: Collection): Boolean = instructions.removeAll(inst) + + override val size: Int + get() = instructions.size + override val indices: IntRange + get() = instructions.indices + override val lastIndex: Int + get() = instructions.lastIndex + + override fun get(index: Int): PandaInst = instructions[index] + + override fun getOrNull(index: Int): PandaInst? = instructions.getOrNull(index) + + override fun toMutableList(): MutableInstList = this + + override fun iterator(): Iterator = instructions.iterator() + +} diff --git a/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/PandaInstListBuilder.kt b/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/PandaInstListBuilder.kt index ef43351b7..c2724427c 100644 --- a/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/PandaInstListBuilder.kt +++ b/jacodb-panda-static/src/main/kotlin/org/jacodb/panda/staticvm/PandaInstListBuilder.kt @@ -131,7 +131,7 @@ class PandaInstListBuilder( } } - fun build() = buildImpl().let { instList } + fun build() = buildImpl().let { PandaInstList(instList) } private fun newLocal(name: String, type: PandaType) = PandaLocalVar(name, type).also { locals[it.name] = it } diff --git a/jacodb-panda-static/src/test/kotlin/IrDeserializationTest.kt b/jacodb-panda-static/src/test/kotlin/IrDeserializationTest.kt index 788d5e1d2..92b14504b 100644 --- a/jacodb-panda-static/src/test/kotlin/IrDeserializationTest.kt +++ b/jacodb-panda-static/src/test/kotlin/IrDeserializationTest.kt @@ -17,8 +17,12 @@ import kotlinx.serialization.ExperimentalSerializationApi import org.junit.jupiter.api.Test import kotlinx.serialization.json.decodeFromStream +import org.jacodb.analysis.library.JcSingletonUnitResolver +import org.jacodb.analysis.library.UnusedVariableRunnerFactory +import org.jacodb.panda.staticvm.PandaApplicationGraph import org.jacodb.panda.staticvm.PandaInstListBuilder import org.jacodb.panda.staticvm.PandaProgramInfo +import org.jacodb.analysis.runAnalysis import java.io.FileInputStream @@ -35,6 +39,7 @@ internal class IrDeserializationTest { val methodNode = project.methods.find { it.name == methodName } val builder = PandaInstListBuilder(methodNode!!) val insts = builder.build() - print(insts) + + val applicationGraph = PandaApplicationGraph(project) } } \ No newline at end of file