Skip to content

Commit 1f2b646

Browse files
committed
Finalizing IfsdUnitManager and IfdsUnitRunner interfaces
1 parent 32ac939 commit 1f2b646

20 files changed

+363
-349
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2022 UnitTestBot contributors (utbot.org)
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jacodb.analysis.engine
18+
19+
import org.jacodb.api.analysis.JcApplicationGraph
20+
import java.util.concurrent.ConcurrentHashMap
21+
22+
abstract class AbstractAnalyzer(private val graph: JcApplicationGraph) : Analyzer {
23+
protected val verticesWithTraceGraphNeeded: MutableSet<IfdsVertex> = ConcurrentHashMap.newKeySet()
24+
25+
abstract val isMainAnalyzer: Boolean
26+
27+
override fun handleNewEdge(edge: IfdsEdge): List<AnalysisDependentEvent> {
28+
return if (isMainAnalyzer && edge.v.statement in graph.exitPoints(edge.method)) {
29+
listOf(NewSummaryFact(SummaryEdgeFact(edge)))
30+
} else {
31+
emptyList()
32+
}
33+
}
34+
35+
override fun handleNewCrossUnitCall(fact: CrossUnitCallFact): List<AnalysisDependentEvent> {
36+
return if (isMainAnalyzer) {
37+
verticesWithTraceGraphNeeded.add(fact.callerVertex)
38+
listOf(NewSummaryFact(fact), EdgeForOtherRunnerQuery(IfdsEdge(fact.calleeVertex, fact.calleeVertex)))
39+
} else {
40+
emptyList()
41+
}
42+
}
43+
44+
override fun handleIfdsResult(ifdsResult: IfdsResult): List<AnalysisDependentEvent> {
45+
val traceGraphs = verticesWithTraceGraphNeeded.map {
46+
ifdsResult.resolveTraceGraph(it)
47+
}
48+
49+
return traceGraphs.map {
50+
NewSummaryFact(TraceGraphFact(it))
51+
}
52+
}
53+
}

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/FlowFunctions.kt renamed to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AnalyzerFactory.kt

Lines changed: 3 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package org.jacodb.analysis.engine
1919
import org.jacodb.api.JcMethod
2020
import org.jacodb.api.analysis.JcApplicationGraph
2121
import org.jacodb.api.cfg.JcInst
22-
import java.util.concurrent.ConcurrentHashMap
2322

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

88-
fun handleNewCrossUnitCall(fact: CrossUnitCallFact, manager: IfdsUnitManager<*>)
87+
fun handleNewCrossUnitCall(fact: CrossUnitCallFact): List<AnalysisDependentEvent>
8988

9089
/**
9190
* This method is called once by [BaseIfdsUnitRunnerFactory] when the propagation of facts is finished
9291
* (normally or due to cancellation).
9392
*
9493
* @return [SummaryFact]s that can be obtained after the facts propagation was completed.
9594
*/
96-
fun handleIfdsResult(ifdsResult: IfdsResult, manager: IfdsUnitManager<*>)
97-
}
98-
99-
abstract class AbstractAnalyzer(private val graph: JcApplicationGraph) : Analyzer {
100-
protected val verticesWithTraceGraphNeeded: MutableSet<IfdsVertex> = ConcurrentHashMap.newKeySet()
101-
102-
abstract val saveSummaryEdgesAndCrossUnitCalls: Boolean
103-
104-
override fun handleNewEdge(edge: IfdsEdge, manager: IfdsUnitManager<*>) {
105-
if (saveSummaryEdgesAndCrossUnitCalls && edge.v.statement in graph.exitPoints(edge.method)) {
106-
manager.uploadSummaryFact(SummaryEdgeFact(edge))
107-
}
108-
}
109-
110-
override fun handleNewCrossUnitCall(fact: CrossUnitCallFact, manager: IfdsUnitManager<*>) {
111-
if (saveSummaryEdgesAndCrossUnitCalls) {
112-
manager.uploadSummaryFact(fact)
113-
verticesWithTraceGraphNeeded.add(fact.callerVertex)
114-
}
115-
}
116-
117-
override fun handleIfdsResult(ifdsResult: IfdsResult, manager: IfdsUnitManager<*>) {
118-
val traceGraphs = verticesWithTraceGraphNeeded.map {
119-
ifdsResult.resolveTraceGraph(it)
120-
}
121-
122-
traceGraphs.forEach {
123-
manager.uploadSummaryFact(TraceGraphFact(it))
124-
}
125-
}
95+
fun handleIfdsResult(ifdsResult: IfdsResult): List<AnalysisDependentEvent>
12696
}
12797

12898
/**

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BaseIfdsUnitRunnerFactory.kt

Lines changed: 65 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,19 @@
1616

1717
package org.jacodb.analysis.engine
1818

19-
import kotlinx.coroutines.CancellationException
2019
import kotlinx.coroutines.CoroutineScope
2120
import kotlinx.coroutines.CoroutineStart
21+
import kotlinx.coroutines.NonCancellable
2222
import kotlinx.coroutines.channels.Channel
2323
import kotlinx.coroutines.coroutineScope
24-
import kotlinx.coroutines.flow.Flow
25-
import kotlinx.coroutines.flow.asFlow
2624
import kotlinx.coroutines.flow.filter
25+
import kotlinx.coroutines.flow.flow
2726
import kotlinx.coroutines.flow.launchIn
2827
import kotlinx.coroutines.flow.map
2928
import kotlinx.coroutines.flow.onEach
3029
import kotlinx.coroutines.isActive
3130
import kotlinx.coroutines.launch
31+
import kotlinx.coroutines.withContext
3232
import org.jacodb.api.JcMethod
3333
import org.jacodb.api.analysis.ApplicationGraph
3434
import org.jacodb.api.analysis.JcApplicationGraph
@@ -63,38 +63,22 @@ private class BaseIfdsUnitRunner<UnitType>(
6363
private val manager: IfdsUnitManager<UnitType>,
6464
private val unitResolver: UnitResolver<UnitType>,
6565
override val unit: UnitType,
66-
startMethods: List<JcMethod>,
66+
private val startMethods: List<JcMethod>,
6767
scope: CoroutineScope
6868
) : IfdsUnitRunner<UnitType> {
6969

7070
private val pathEdges: MutableSet<IfdsEdge> = ConcurrentHashMap.newKeySet()
7171
private val summaryEdges: MutableMap<IfdsVertex, MutableSet<IfdsVertex>> = mutableMapOf()
7272
private val callSitesOf: MutableMap<IfdsVertex, MutableSet<IfdsEdge>> = mutableMapOf()
7373
private val pathEdgesPreds: MutableMap<IfdsEdge, MutableSet<PathEdgePredecessor>> = ConcurrentHashMap()
74-
private val visitedMethods: MutableSet<JcMethod> = mutableSetOf()
7574

76-
private val flowSpace get() = analyzer.flowFunctions
75+
private val flowSpace = analyzer.flowFunctions
7776

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

83-
84-
init {
85-
// Adding initial facts to workList
86-
for (method in startMethods) {
87-
require(unitResolver.resolve(method) == unit)
88-
for (sPoint in graph.entryPoint(method)) {
89-
for (sFact in flowSpace.obtainPossibleStartFacts(sPoint)) {
90-
val vertex = IfdsVertex(sPoint, sFact)
91-
val edge = IfdsEdge(vertex, vertex)
92-
propagate(edge, PathEdgePredecessor(edge, PredecessorKind.NoPredecessor))
93-
}
94-
}
95-
}
96-
}
97-
9882
/**
9983
* This method should be called each time new path edge is observed.
10084
* It will check if the edge is new and, if success, add it to [workList]
@@ -103,16 +87,18 @@ private class BaseIfdsUnitRunner<UnitType>(
10387
* @param edge the new path edge
10488
* @param pred the description of predecessor of the edge
10589
*/
106-
private fun propagate(edge: IfdsEdge, pred: PathEdgePredecessor): Boolean {
90+
private suspend fun propagate(edge: IfdsEdge, pred: PathEdgePredecessor): Boolean {
10791
require(unitResolver.resolve(edge.method) == unit)
10892

10993
pathEdgesPreds.computeIfAbsent(edge) {
11094
ConcurrentHashMap.newKeySet()
11195
}.add(pred)
11296

11397
if (pathEdges.add(edge)) {
114-
require(workList.trySend(edge).isSuccess)
115-
analyzer.handleNewEdge(edge, manager)
98+
workList.send(edge)
99+
analyzer.handleNewEdge(edge).forEach {
100+
manager.handleEvent(it, this)
101+
}
116102
return true
117103
}
118104
return false
@@ -132,21 +118,13 @@ private class BaseIfdsUnitRunner<UnitType>(
132118
*/
133119
private suspend fun runTabulationAlgorithm(): Unit = coroutineScope {
134120
while (isActive) {
135-
val curEdge = workList.tryReceive().getOrNull() ?: run {//withTimeoutOrNull(20) { workList.receive() } ?: run {
136-
manager.updateQueueStatus(true, this@BaseIfdsUnitRunner)
121+
val curEdge = workList.tryReceive().getOrNull() ?: run {
122+
manager.handleEvent(QueueEmptinessChanged(true), this@BaseIfdsUnitRunner)
137123
workList.receive().also {
138-
manager.updateQueueStatus(false, this@BaseIfdsUnitRunner)
124+
manager.handleEvent(QueueEmptinessChanged(false), this@BaseIfdsUnitRunner)
139125
}
140126
}
141127

142-
if (visitedMethods.add(curEdge.method)) {
143-
// Listen for incoming updates
144-
manager
145-
.subscribeForSummaryEdgesOf(curEdge.method, this@BaseIfdsUnitRunner)
146-
.onEach { propagate(it, PathEdgePredecessor(it, PredecessorKind.Unknown)) }
147-
.launchIn(this)
148-
}
149-
150128
val (u, v) = curEdge
151129
val (curVertex, curFact) = v
152130

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

171-
// Requesting to analyze callee from sVertex, similar to lines 14-16 of RHS95
172-
// Also, receiving summary edges for callee that start from sVertex
173-
val exitVertices: Flow<IfdsVertex> = if (callee.isExtern) {
174-
// manager.addEdgeForOtherRunner(IfdsEdge(sVertex, sVertex))
175-
manager
176-
.subscribeForSummaryEdgesOf(callee, this@BaseIfdsUnitRunner)
177-
.filter { it.u == sVertex && it.v.statement in graph.exitPoints(callee) }
178-
.map { it.v }
179-
} else {
180-
val nextEdge = IfdsEdge(sVertex, sVertex)
181-
propagate(nextEdge, PathEdgePredecessor(curEdge, PredecessorKind.CallToStart))
182-
183-
// .toList() is needed below to avoid ConcurrentModificationException
184-
summaryEdges[sVertex].orEmpty().toList().asFlow()
185-
}
186-
187-
// Propagation through summary edges
188-
exitVertices.onEach { (eStatement, eFact) ->
189-
val finalFacts =
190-
flowSpace.obtainExitToReturnSiteFlowFunction(curVertex, returnSite, eStatement)
191-
.compute(eFact)
149+
val handleExitVertex: suspend (IfdsVertex) -> Unit = { (eStatement, eFact) ->
150+
val finalFacts = flowSpace
151+
.obtainExitToReturnSiteFlowFunction(curVertex, returnSite, eStatement)
152+
.compute(eFact)
192153
for (finalFact in finalFacts) {
193154
val summaryEdge = IfdsEdge(IfdsVertex(sPoint, sFact), IfdsVertex(eStatement, eFact))
194155
val newEdge = IfdsEdge(u, IfdsVertex(returnSite, finalFact))
195156
propagate(newEdge, PathEdgePredecessor(curEdge, PredecessorKind.ThroughSummary(summaryEdge)))
196157
}
197-
}.launchIn(this)
158+
}
198159

199-
// Saving additional info
200160
if (callee.isExtern) {
201-
analyzer.handleNewCrossUnitCall(CrossUnitCallFact(v, sVertex), manager)
161+
// Notify about cross-unit call
162+
analyzer.handleNewCrossUnitCall(CrossUnitCallFact(v, sVertex)).forEach {
163+
manager.handleEvent(it, this@BaseIfdsUnitRunner)
164+
}
165+
166+
// Waiting for exit vertices and handling them
167+
val exitVertices = flow {
168+
manager.handleEvent(
169+
SubscriptionForSummaries(callee, this@flow),
170+
this@BaseIfdsUnitRunner
171+
)
172+
}
173+
exitVertices
174+
.filter { it.u == sVertex }
175+
.map { it.v }
176+
.onEach(handleExitVertex)
177+
.launchIn(this)
202178
} else {
179+
// Save info about call for summary-facts that will be found later
203180
callSitesOf.getOrPut(sVertex) { mutableSetOf() }.add(curEdge)
181+
182+
// Initiating analysis for callee
183+
val nextEdge = IfdsEdge(sVertex, sVertex)
184+
propagate(nextEdge, PathEdgePredecessor(curEdge, PredecessorKind.CallToStart))
185+
186+
// Handling already-found summary edges
187+
// .toList() is needed below to avoid ConcurrentModificationException
188+
for (exitVertex in summaryEdges[sVertex].orEmpty().toList()) {
189+
handleExitVertex(exitVertex)
190+
}
204191
}
205192
}
206193
}
@@ -234,7 +221,6 @@ private class BaseIfdsUnitRunner<UnitType>(
234221
}
235222
}
236223
}
237-
// yield()
238224
}
239225
}
240226

@@ -253,20 +239,33 @@ private class BaseIfdsUnitRunner<UnitType>(
253239
*/
254240
private suspend fun analyze() = coroutineScope {
255241
try {
242+
// Adding initial facts to workList
243+
for (method in startMethods) {
244+
require(unitResolver.resolve(method) == unit)
245+
for (sPoint in graph.entryPoint(method)) {
246+
for (sFact in flowSpace.obtainPossibleStartFacts(sPoint)) {
247+
val vertex = IfdsVertex(sPoint, sFact)
248+
val edge = IfdsEdge(vertex, vertex)
249+
propagate(edge, PathEdgePredecessor(edge, PredecessorKind.NoPredecessor))
250+
}
251+
}
252+
}
253+
256254
runTabulationAlgorithm()
257-
} catch (_: EmptyQueueCancellationException) {
258255
} finally {
259-
analyzer.handleIfdsResult(ifdsResult, manager)
256+
withContext(NonCancellable) {
257+
analyzer.handleIfdsResult(ifdsResult).forEach {
258+
manager.handleEvent(it, this@BaseIfdsUnitRunner)
259+
}
260+
}
260261
}
261262
}
262263

263264
override val job = scope.launch(start = CoroutineStart.LAZY) {
264265
analyze()
265266
}
266267

267-
override fun submitNewEdge(edge: IfdsEdge) {
268+
override suspend fun submitNewEdge(edge: IfdsEdge) {
268269
propagate(edge, PathEdgePredecessor(edge, PredecessorKind.Unknown))
269270
}
270-
}
271-
272-
internal object EmptyQueueCancellationException: CancellationException()
271+
}

0 commit comments

Comments
 (0)