Skip to content

Commit

Permalink
Finalizing IfsdUnitManager and IfdsUnitRunner interfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
volivan239 committed Aug 31, 2023
1 parent 32ac939 commit 1f2b646
Show file tree
Hide file tree
Showing 20 changed files with 363 additions and 349 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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.analysis.engine

import org.jacodb.api.analysis.JcApplicationGraph
import java.util.concurrent.ConcurrentHashMap

abstract class AbstractAnalyzer(private val graph: JcApplicationGraph) : Analyzer {
protected val verticesWithTraceGraphNeeded: MutableSet<IfdsVertex> = ConcurrentHashMap.newKeySet()

abstract val isMainAnalyzer: Boolean

override fun handleNewEdge(edge: IfdsEdge): List<AnalysisDependentEvent> {
return if (isMainAnalyzer && edge.v.statement in graph.exitPoints(edge.method)) {
listOf(NewSummaryFact(SummaryEdgeFact(edge)))
} else {
emptyList()
}
}

override fun handleNewCrossUnitCall(fact: CrossUnitCallFact): List<AnalysisDependentEvent> {
return if (isMainAnalyzer) {
verticesWithTraceGraphNeeded.add(fact.callerVertex)
listOf(NewSummaryFact(fact), EdgeForOtherRunnerQuery(IfdsEdge(fact.calleeVertex, fact.calleeVertex)))
} else {
emptyList()
}
}

override fun handleIfdsResult(ifdsResult: IfdsResult): List<AnalysisDependentEvent> {
val traceGraphs = verticesWithTraceGraphNeeded.map {
ifdsResult.resolveTraceGraph(it)
}

return traceGraphs.map {
NewSummaryFact(TraceGraphFact(it))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package org.jacodb.analysis.engine
import org.jacodb.api.JcMethod
import org.jacodb.api.analysis.JcApplicationGraph
import org.jacodb.api.cfg.JcInst
import java.util.concurrent.ConcurrentHashMap

/**
* Interface for flow functions -- mappings of kind DomainFact -> Collection of DomainFacts
Expand Down Expand Up @@ -83,46 +82,17 @@ interface Analyzer {
*
* @return [SummaryFact]s that are produced by this edge, that need to be saved to summary.
*/
fun handleNewEdge(edge: IfdsEdge, manager: IfdsUnitManager<*>)
fun handleNewEdge(edge: IfdsEdge): List<AnalysisDependentEvent>

fun handleNewCrossUnitCall(fact: CrossUnitCallFact, manager: IfdsUnitManager<*>)
fun handleNewCrossUnitCall(fact: CrossUnitCallFact): List<AnalysisDependentEvent>

/**
* This method is called once by [BaseIfdsUnitRunnerFactory] when the propagation of facts is finished
* (normally or due to cancellation).
*
* @return [SummaryFact]s that can be obtained after the facts propagation was completed.
*/
fun handleIfdsResult(ifdsResult: IfdsResult, manager: IfdsUnitManager<*>)
}

abstract class AbstractAnalyzer(private val graph: JcApplicationGraph) : Analyzer {
protected val verticesWithTraceGraphNeeded: MutableSet<IfdsVertex> = ConcurrentHashMap.newKeySet()

abstract val saveSummaryEdgesAndCrossUnitCalls: Boolean

override fun handleNewEdge(edge: IfdsEdge, manager: IfdsUnitManager<*>) {
if (saveSummaryEdgesAndCrossUnitCalls && edge.v.statement in graph.exitPoints(edge.method)) {
manager.uploadSummaryFact(SummaryEdgeFact(edge))
}
}

override fun handleNewCrossUnitCall(fact: CrossUnitCallFact, manager: IfdsUnitManager<*>) {
if (saveSummaryEdgesAndCrossUnitCalls) {
manager.uploadSummaryFact(fact)
verticesWithTraceGraphNeeded.add(fact.callerVertex)
}
}

override fun handleIfdsResult(ifdsResult: IfdsResult, manager: IfdsUnitManager<*>) {
val traceGraphs = verticesWithTraceGraphNeeded.map {
ifdsResult.resolveTraceGraph(it)
}

traceGraphs.forEach {
manager.uploadSummaryFact(TraceGraphFact(it))
}
}
fun handleIfdsResult(ifdsResult: IfdsResult): List<AnalysisDependentEvent>
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@

package org.jacodb.analysis.engine

import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jacodb.api.JcMethod
import org.jacodb.api.analysis.ApplicationGraph
import org.jacodb.api.analysis.JcApplicationGraph
Expand Down Expand Up @@ -63,38 +63,22 @@ private class BaseIfdsUnitRunner<UnitType>(
private val manager: IfdsUnitManager<UnitType>,
private val unitResolver: UnitResolver<UnitType>,
override val unit: UnitType,
startMethods: List<JcMethod>,
private val startMethods: List<JcMethod>,
scope: CoroutineScope
) : IfdsUnitRunner<UnitType> {

private val pathEdges: MutableSet<IfdsEdge> = ConcurrentHashMap.newKeySet()
private val summaryEdges: MutableMap<IfdsVertex, MutableSet<IfdsVertex>> = mutableMapOf()
private val callSitesOf: MutableMap<IfdsVertex, MutableSet<IfdsEdge>> = mutableMapOf()
private val pathEdgesPreds: MutableMap<IfdsEdge, MutableSet<PathEdgePredecessor>> = ConcurrentHashMap()
private val visitedMethods: MutableSet<JcMethod> = mutableSetOf()

private val flowSpace get() = analyzer.flowFunctions
private val flowSpace = analyzer.flowFunctions

/**
* Queue containing all unprocessed path edges.
*/
private val workList = Channel<IfdsEdge>(Channel.UNLIMITED)


init {
// Adding initial facts to workList
for (method in startMethods) {
require(unitResolver.resolve(method) == unit)
for (sPoint in graph.entryPoint(method)) {
for (sFact in flowSpace.obtainPossibleStartFacts(sPoint)) {
val vertex = IfdsVertex(sPoint, sFact)
val edge = IfdsEdge(vertex, vertex)
propagate(edge, PathEdgePredecessor(edge, PredecessorKind.NoPredecessor))
}
}
}
}

/**
* This method should be called each time new path edge is observed.
* It will check if the edge is new and, if success, add it to [workList]
Expand All @@ -103,16 +87,18 @@ private class BaseIfdsUnitRunner<UnitType>(
* @param edge the new path edge
* @param pred the description of predecessor of the edge
*/
private fun propagate(edge: IfdsEdge, pred: PathEdgePredecessor): Boolean {
private suspend fun propagate(edge: IfdsEdge, pred: PathEdgePredecessor): Boolean {
require(unitResolver.resolve(edge.method) == unit)

pathEdgesPreds.computeIfAbsent(edge) {
ConcurrentHashMap.newKeySet()
}.add(pred)

if (pathEdges.add(edge)) {
require(workList.trySend(edge).isSuccess)
analyzer.handleNewEdge(edge, manager)
workList.send(edge)
analyzer.handleNewEdge(edge).forEach {
manager.handleEvent(it, this)
}
return true
}
return false
Expand All @@ -132,21 +118,13 @@ private class BaseIfdsUnitRunner<UnitType>(
*/
private suspend fun runTabulationAlgorithm(): Unit = coroutineScope {
while (isActive) {
val curEdge = workList.tryReceive().getOrNull() ?: run {//withTimeoutOrNull(20) { workList.receive() } ?: run {
manager.updateQueueStatus(true, this@BaseIfdsUnitRunner)
val curEdge = workList.tryReceive().getOrNull() ?: run {
manager.handleEvent(QueueEmptinessChanged(true), this@BaseIfdsUnitRunner)
workList.receive().also {
manager.updateQueueStatus(false, this@BaseIfdsUnitRunner)
manager.handleEvent(QueueEmptinessChanged(false), this@BaseIfdsUnitRunner)
}
}

if (visitedMethods.add(curEdge.method)) {
// Listen for incoming updates
manager
.subscribeForSummaryEdgesOf(curEdge.method, this@BaseIfdsUnitRunner)
.onEach { propagate(it, PathEdgePredecessor(it, PredecessorKind.Unknown)) }
.launchIn(this)
}

val (u, v) = curEdge
val (curVertex, curFact) = v

Expand All @@ -168,39 +146,48 @@ private class BaseIfdsUnitRunner<UnitType>(
for (sFact in factsAtStart) {
val sVertex = IfdsVertex(sPoint, sFact)

// Requesting to analyze callee from sVertex, similar to lines 14-16 of RHS95
// Also, receiving summary edges for callee that start from sVertex
val exitVertices: Flow<IfdsVertex> = if (callee.isExtern) {
// manager.addEdgeForOtherRunner(IfdsEdge(sVertex, sVertex))
manager
.subscribeForSummaryEdgesOf(callee, this@BaseIfdsUnitRunner)
.filter { it.u == sVertex && it.v.statement in graph.exitPoints(callee) }
.map { it.v }
} else {
val nextEdge = IfdsEdge(sVertex, sVertex)
propagate(nextEdge, PathEdgePredecessor(curEdge, PredecessorKind.CallToStart))

// .toList() is needed below to avoid ConcurrentModificationException
summaryEdges[sVertex].orEmpty().toList().asFlow()
}

// Propagation through summary edges
exitVertices.onEach { (eStatement, eFact) ->
val finalFacts =
flowSpace.obtainExitToReturnSiteFlowFunction(curVertex, returnSite, eStatement)
.compute(eFact)
val handleExitVertex: suspend (IfdsVertex) -> Unit = { (eStatement, eFact) ->
val finalFacts = flowSpace
.obtainExitToReturnSiteFlowFunction(curVertex, returnSite, eStatement)
.compute(eFact)
for (finalFact in finalFacts) {
val summaryEdge = IfdsEdge(IfdsVertex(sPoint, sFact), IfdsVertex(eStatement, eFact))
val newEdge = IfdsEdge(u, IfdsVertex(returnSite, finalFact))
propagate(newEdge, PathEdgePredecessor(curEdge, PredecessorKind.ThroughSummary(summaryEdge)))
}
}.launchIn(this)
}

// Saving additional info
if (callee.isExtern) {
analyzer.handleNewCrossUnitCall(CrossUnitCallFact(v, sVertex), manager)
// Notify about cross-unit call
analyzer.handleNewCrossUnitCall(CrossUnitCallFact(v, sVertex)).forEach {
manager.handleEvent(it, this@BaseIfdsUnitRunner)
}

// Waiting for exit vertices and handling them
val exitVertices = flow {
manager.handleEvent(
SubscriptionForSummaries(callee, this@flow),
this@BaseIfdsUnitRunner
)
}
exitVertices
.filter { it.u == sVertex }
.map { it.v }
.onEach(handleExitVertex)
.launchIn(this)
} else {
// Save info about call for summary-facts that will be found later
callSitesOf.getOrPut(sVertex) { mutableSetOf() }.add(curEdge)

// Initiating analysis for callee
val nextEdge = IfdsEdge(sVertex, sVertex)
propagate(nextEdge, PathEdgePredecessor(curEdge, PredecessorKind.CallToStart))

// Handling already-found summary edges
// .toList() is needed below to avoid ConcurrentModificationException
for (exitVertex in summaryEdges[sVertex].orEmpty().toList()) {
handleExitVertex(exitVertex)
}
}
}
}
Expand Down Expand Up @@ -234,7 +221,6 @@ private class BaseIfdsUnitRunner<UnitType>(
}
}
}
// yield()
}
}

Expand All @@ -253,20 +239,33 @@ private class BaseIfdsUnitRunner<UnitType>(
*/
private suspend fun analyze() = coroutineScope {
try {
// Adding initial facts to workList
for (method in startMethods) {
require(unitResolver.resolve(method) == unit)
for (sPoint in graph.entryPoint(method)) {
for (sFact in flowSpace.obtainPossibleStartFacts(sPoint)) {
val vertex = IfdsVertex(sPoint, sFact)
val edge = IfdsEdge(vertex, vertex)
propagate(edge, PathEdgePredecessor(edge, PredecessorKind.NoPredecessor))
}
}
}

runTabulationAlgorithm()
} catch (_: EmptyQueueCancellationException) {
} finally {
analyzer.handleIfdsResult(ifdsResult, manager)
withContext(NonCancellable) {
analyzer.handleIfdsResult(ifdsResult).forEach {
manager.handleEvent(it, this@BaseIfdsUnitRunner)
}
}
}
}

override val job = scope.launch(start = CoroutineStart.LAZY) {
analyze()
}

override fun submitNewEdge(edge: IfdsEdge) {
override suspend fun submitNewEdge(edge: IfdsEdge) {
propagate(edge, PathEdgePredecessor(edge, PredecessorKind.Unknown))
}
}

internal object EmptyQueueCancellationException: CancellationException()
}
Loading

0 comments on commit 1f2b646

Please sign in to comment.