16
16
17
17
package org.jacodb.analysis.engine
18
18
19
- import kotlinx.coroutines.CancellationException
20
19
import kotlinx.coroutines.CoroutineScope
21
20
import kotlinx.coroutines.CoroutineStart
21
+ import kotlinx.coroutines.NonCancellable
22
22
import kotlinx.coroutines.channels.Channel
23
23
import kotlinx.coroutines.coroutineScope
24
- import kotlinx.coroutines.flow.Flow
25
- import kotlinx.coroutines.flow.asFlow
26
24
import kotlinx.coroutines.flow.filter
25
+ import kotlinx.coroutines.flow.flow
27
26
import kotlinx.coroutines.flow.launchIn
28
27
import kotlinx.coroutines.flow.map
29
28
import kotlinx.coroutines.flow.onEach
30
29
import kotlinx.coroutines.isActive
31
30
import kotlinx.coroutines.launch
31
+ import kotlinx.coroutines.withContext
32
32
import org.jacodb.api.JcMethod
33
33
import org.jacodb.api.analysis.ApplicationGraph
34
34
import org.jacodb.api.analysis.JcApplicationGraph
@@ -63,38 +63,22 @@ private class BaseIfdsUnitRunner<UnitType>(
63
63
private val manager : IfdsUnitManager <UnitType >,
64
64
private val unitResolver : UnitResolver <UnitType >,
65
65
override val unit : UnitType ,
66
- startMethods : List <JcMethod >,
66
+ private val startMethods : List <JcMethod >,
67
67
scope : CoroutineScope
68
68
) : IfdsUnitRunner<UnitType> {
69
69
70
70
private val pathEdges: MutableSet <IfdsEdge > = ConcurrentHashMap .newKeySet()
71
71
private val summaryEdges: MutableMap <IfdsVertex , MutableSet <IfdsVertex >> = mutableMapOf ()
72
72
private val callSitesOf: MutableMap <IfdsVertex , MutableSet <IfdsEdge >> = mutableMapOf ()
73
73
private val pathEdgesPreds: MutableMap <IfdsEdge , MutableSet <PathEdgePredecessor >> = ConcurrentHashMap ()
74
- private val visitedMethods: MutableSet <JcMethod > = mutableSetOf ()
75
74
76
- private val flowSpace get() = analyzer.flowFunctions
75
+ private val flowSpace = analyzer.flowFunctions
77
76
78
77
/* *
79
78
* Queue containing all unprocessed path edges.
80
79
*/
81
80
private val workList = Channel <IfdsEdge >(Channel .UNLIMITED )
82
81
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
-
98
82
/* *
99
83
* This method should be called each time new path edge is observed.
100
84
* It will check if the edge is new and, if success, add it to [workList]
@@ -103,16 +87,18 @@ private class BaseIfdsUnitRunner<UnitType>(
103
87
* @param edge the new path edge
104
88
* @param pred the description of predecessor of the edge
105
89
*/
106
- private fun propagate (edge : IfdsEdge , pred : PathEdgePredecessor ): Boolean {
90
+ private suspend fun propagate (edge : IfdsEdge , pred : PathEdgePredecessor ): Boolean {
107
91
require(unitResolver.resolve(edge.method) == unit)
108
92
109
93
pathEdgesPreds.computeIfAbsent(edge) {
110
94
ConcurrentHashMap .newKeySet()
111
95
}.add(pred)
112
96
113
97
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
+ }
116
102
return true
117
103
}
118
104
return false
@@ -132,21 +118,13 @@ private class BaseIfdsUnitRunner<UnitType>(
132
118
*/
133
119
private suspend fun runTabulationAlgorithm (): Unit = coroutineScope {
134
120
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)
137
123
workList.receive().also {
138
- manager.updateQueueStatus( false , this @BaseIfdsUnitRunner)
124
+ manager.handleEvent( QueueEmptinessChanged ( false ) , this @BaseIfdsUnitRunner)
139
125
}
140
126
}
141
127
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
-
150
128
val (u, v) = curEdge
151
129
val (curVertex, curFact) = v
152
130
@@ -168,39 +146,48 @@ private class BaseIfdsUnitRunner<UnitType>(
168
146
for (sFact in factsAtStart) {
169
147
val sVertex = IfdsVertex (sPoint, sFact)
170
148
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)
192
153
for (finalFact in finalFacts) {
193
154
val summaryEdge = IfdsEdge (IfdsVertex (sPoint, sFact), IfdsVertex (eStatement, eFact))
194
155
val newEdge = IfdsEdge (u, IfdsVertex (returnSite, finalFact))
195
156
propagate(newEdge, PathEdgePredecessor (curEdge, PredecessorKind .ThroughSummary (summaryEdge)))
196
157
}
197
- }.launchIn( this )
158
+ }
198
159
199
- // Saving additional info
200
160
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 )
202
178
} else {
179
+ // Save info about call for summary-facts that will be found later
203
180
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
+ }
204
191
}
205
192
}
206
193
}
@@ -234,7 +221,6 @@ private class BaseIfdsUnitRunner<UnitType>(
234
221
}
235
222
}
236
223
}
237
- // yield()
238
224
}
239
225
}
240
226
@@ -253,20 +239,33 @@ private class BaseIfdsUnitRunner<UnitType>(
253
239
*/
254
240
private suspend fun analyze () = coroutineScope {
255
241
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
+
256
254
runTabulationAlgorithm()
257
- } catch (_: EmptyQueueCancellationException ) {
258
255
} finally {
259
- analyzer.handleIfdsResult(ifdsResult, manager)
256
+ withContext(NonCancellable ) {
257
+ analyzer.handleIfdsResult(ifdsResult).forEach {
258
+ manager.handleEvent(it, this @BaseIfdsUnitRunner)
259
+ }
260
+ }
260
261
}
261
262
}
262
263
263
264
override val job = scope.launch(start = CoroutineStart .LAZY ) {
264
265
analyze()
265
266
}
266
267
267
- override fun submitNewEdge (edge : IfdsEdge ) {
268
+ override suspend fun submitNewEdge (edge : IfdsEdge ) {
268
269
propagate(edge, PathEdgePredecessor (edge, PredecessorKind .Unknown ))
269
270
}
270
- }
271
-
272
- internal object EmptyQueueCancellationException: CancellationException()
271
+ }
0 commit comments