Skip to content

Commit

Permalink
[WIP] add cfg for panda
Browse files Browse the repository at this point in the history
  • Loading branch information
MForest7 committed Feb 7, 2024
1 parent 81f875e commit d363961
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 33 deletions.
1 change: 1 addition & 0 deletions jacodb-panda-static/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@

package org.jacodb.panda.staticvm

import org.jacodb.api.core.cfg.Graph

data class SimpleDirectedGraph<T>(
val nodes: MutableSet<T> = mutableSetOf(),
private val predecessorsMap: MutableMap<T, MutableList<T>> = mutableMapOf(),
private val successorsMap: MutableMap<T, MutableList<T>> = mutableMapOf()
) {
fun predecessors(t: T): List<T> = predecessorsMap.getOrDefault(t, emptyList())
private val predecessorsMap: MutableMap<T, MutableSet<T>> = mutableMapOf(),
private val successorsMap: MutableMap<T, MutableSet<T>> = mutableMapOf()
) : Graph<T> {
override fun predecessors(node: T): Set<T> = predecessorsMap.getOrDefault(node, emptySet())

fun successors(t: T): List<T> = successorsMap.getOrDefault(t, emptyList())
override fun successors(node: T): Set<T> = successorsMap.getOrDefault(node, emptySet())

fun withNode(node: T): SimpleDirectedGraph<T> {
nodes.add(node)
Expand All @@ -33,16 +35,19 @@ data class SimpleDirectedGraph<T>(
fun withEdge(from: T, to: T): SimpleDirectedGraph<T> {
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 <K> Collection<K>.intersectMutable(other: Collection<K>) =
toSet().intersect(other.toSet()).toMutableSet()

fun induced(subset: Collection<T>) = 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<SimpleDirectedGraph<T>> = components(nodes) {
Expand Down Expand Up @@ -70,28 +75,30 @@ data class SimpleDirectedGraph<T>(
lhs.successorsMap.plus(rhs.successorsMap).toMutableMap()
)
}

override fun iterator(): Iterator<T> = nodes.asIterable().iterator()
}

fun <T> search(start: T, successors: (T) -> List<T>, visitor: (T) -> Unit): List<T> {
fun <T> search(start: T, successors: (T) -> List<T>, visitor: (T) -> Unit): Set<T> {
val visited = hashSetOf<T>()
fun dfs(node: T) = node.takeIf(visited::add)?.also(visitor)?.let { successors(it).forEach(::dfs) }
dfs(start)
return visited.toList()
return visited
}

fun <T> components(starts: Iterable<T>, successors: (T) -> List<T>): List<List<T>> {
fun <T> components(starts: Iterable<T>, successors: (T) -> Collection<T>): List<Set<T>> {
val visited = hashSetOf<T>()
return starts.mapNotNull { start ->
if (start !in visited) search(start, { successors(it).filter { it !in visited } }, visited::add)
else null
}
}

fun <T> reachable(starts: Iterable<T>, successors: (T) -> List<T>): List<T> {
val visited = hashSetOf<T>()
starts.forEach { start ->
if (start !in visited)
visited.addAll(search(start, { successors(it).filter { it !in visited } }, {}))
fun <T, A> Iterable<T>.applyFold(initial: A, operation: A.(T) -> Unit) = fold(initial) { acc, elem ->
acc.apply { operation(elem) } }

fun <T> reachable(starts: Iterable<T>, successors: (T) -> Collection<T>): Set<T> =
starts.applyFold(hashSetOf()) { start ->
if (!contains(start))
addAll(search(start, { successors(it).filterNot(this::contains) }, {}))
}
return visited.toList()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2022 UnitTestBot contributors (utbot.org)
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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<PandaMethod, PandaInst> {
private val callersMap = project.methods
.flatMap { it.flowGraph().instructions }
.flatMap { inst -> callees(inst).map { it to inst } }
.groupBy(Pair<PandaMethod, PandaInst>::first, Pair<PandaMethod, PandaInst>::second)

override fun predecessors(node: PandaInst): Sequence<PandaInst> =
node.location.method.flowGraph().predecessors(node).asSequence()
override fun successors(node: PandaInst): Sequence<PandaInst> =
node.location.method.flowGraph().predecessors(node).asSequence()

override fun callees(node: PandaInst): Sequence<PandaMethod> = 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<PandaInst>
= callersMap[method]?.asSequence() ?: emptySequence()

override fun entryPoint(method: PandaMethod): Sequence<PandaInst> =
method.flowGraph().entries.asSequence()

override fun exitPoints(method: PandaMethod): Sequence<PandaInst> =
method.flowGraph().exits.asSequence()

override fun methodOf(node: PandaInst): PandaMethod =
node.location.method
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -57,19 +57,18 @@ data class PandaField(
}
}

data class PandaMethod(
class PandaMethod(
val project: PandaProject,
val declaringClassType: PandaClassName,
val returnType: PandaTypeName,
val args: List<PandaTypeName>,
val name: String,
val body: SimpleDirectedGraph<PandaBasicBlockInfo>?,
val access: AccessFlags
) : CoreMethod<JcInst> {
) : CoreMethod<PandaInst> {
private val flowGraphValue by lazy { PandaControlFlowGraph.of(this) }

override fun flowGraph(): ControlFlowGraph<JcInst> {
TODO("Not yet implemented")
}
override fun flowGraph(): ControlFlowGraph<PandaInst> = flowGraphValue

override fun equals(other: Any?): Boolean {
if (this === other) return true
Expand Down Expand Up @@ -103,7 +102,7 @@ data class PandaMethod(
get() = TODO("Not yet implemented")
}

data class PandaClass(
class PandaClass(
val project: PandaProject,
val declaredFields: List<PandaField>,
val declaredMethods: List<PandaMethod>,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2022 UnitTestBot contributors (utbot.org)
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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<PandaInst>
) : ControlFlowGraph<PandaInst>, Graph<PandaInst> 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<PandaInst>()) { (from, to) -> withEdge(from, to) }

return PandaControlFlowGraph(method, instList, graph)
}
}

override val entries: List<PandaInst> = listOfNotNull(instList.firstOrNull())

override val exits: List<PandaInst> = setOfNotNull(instList.lastOrNull())
.plus(instList.filterIsInstance<PandaTerminatingInst>())
.toList()

override val instructions: List<PandaInst> = instList.instructions

override fun iterator(): Iterator<PandaInst> = instList.iterator()
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,41 @@ data class PandaInstList(override val instructions: List<PandaInst>) : InstList<

override fun getOrNull(index: Int): PandaInst? = instructions.getOrNull(index)

override fun toMutableList(): MutableInstList<PandaInst> {
TODO("Not yet implemented")
}
override fun toMutableList(): MutableInstList<PandaInst> = PandaMutableInstList(instructions.toMutableList())

override fun iterator(): Iterator<PandaInst> = instructions.iterator()
}
}

data class PandaMutableInstList(override val instructions: MutableList<PandaInst>) : MutableInstList<PandaInst> {
override fun insertBefore(inst: PandaInst, vararg newInstructions: PandaInst) =
insertBefore(inst, newInstructions.toList())

override fun insertBefore(inst: PandaInst, newInstructions: Collection<PandaInst>): 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<PandaInst>): Unit =
instructions.run { addAll(indexOf(inst) + 1, newInstructions) }

override fun remove(inst: PandaInst): Boolean = instructions.remove(inst)

override fun removeAll(inst: Collection<PandaInst>): 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<PandaInst> = this

override fun iterator(): Iterator<PandaInst> = instructions.iterator()

}
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand Down
7 changes: 6 additions & 1 deletion jacodb-panda-static/src/test/kotlin/IrDeserializationTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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)
}
}

0 comments on commit d363961

Please sign in to comment.