Skip to content

Commit 4b8042a

Browse files
authored
Execute ComponentPass passes according to the dependencies between components (#2010)
1 parent 7495654 commit 4b8042a

File tree

10 files changed

+277
-92
lines changed

10 files changed

+277
-92
lines changed

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationResult.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,15 @@ import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf
3434
import de.fraunhofer.aisec.cpg.graph.edges.unwrapping
3535
import de.fraunhofer.aisec.cpg.helpers.MeasurementHolder
3636
import de.fraunhofer.aisec.cpg.helpers.StatisticsHolder
37+
import de.fraunhofer.aisec.cpg.passes.ImportDependencies
38+
import de.fraunhofer.aisec.cpg.passes.ImportResolver
3739
import de.fraunhofer.aisec.cpg.passes.Pass
3840
import de.fraunhofer.aisec.cpg.persistence.DoNotPersist
41+
import de.fraunhofer.aisec.cpg.processing.strategy.Strategy
3942
import java.util.*
4043
import java.util.concurrent.ConcurrentHashMap
4144
import org.neo4j.ogm.annotation.Relationship
45+
import org.neo4j.ogm.annotation.Transient
4246

4347
/**
4448
* The global (intermediate) result of the translation. A [LanguageFrontend] will initially populate
@@ -62,6 +66,14 @@ class TranslationResult(
6266
*/
6367
val components by unwrapping(TranslationResult::componentEdges)
6468

69+
/**
70+
* The import dependencies of [Component] nodes of this translation result. The preferred way to
71+
* access this is via [Strategy.COMPONENTS_LEAST_IMPORTS].
72+
*/
73+
@Transient
74+
@PopulatedByPass(ImportResolver::class)
75+
var componentDependencies: ImportDependencies<Component>? = null
76+
6577
/**
6678
* Scratch storage that can be used by passes to store additional information in this result.
6779
* Callers must ensure that keys are unique. It is recommended to prefix them with the class

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Component.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import de.fraunhofer.aisec.cpg.graph.edges.unwrapping
3535
import de.fraunhofer.aisec.cpg.passes.ImportDependencies
3636
import de.fraunhofer.aisec.cpg.passes.ImportResolver
3737
import de.fraunhofer.aisec.cpg.persistence.DoNotPersist
38+
import de.fraunhofer.aisec.cpg.processing.strategy.Strategy
3839
import java.io.File
3940
import org.neo4j.ogm.annotation.Relationship
4041
import org.neo4j.ogm.annotation.Transient
@@ -52,9 +53,13 @@ open class Component : Node() {
5253
/** All translation units belonging to this application. */
5354
val translationUnits by unwrapping(Component::translationUnitEdges)
5455

56+
/**
57+
* The import dependencies of [TranslationUnitDeclaration] nodes of this component. The
58+
* preferred way to access this is via [Strategy.TRANSLATION_UNITS_LEAST_IMPORTS].
59+
*/
5560
@Transient
5661
@PopulatedByPass(ImportResolver::class)
57-
var importDependencies = ImportDependencies(translationUnits)
62+
var translationUnitDependencies: ImportDependencies<TranslationUnitDeclaration>? = null
5863

5964
@Synchronized
6065
fun addTranslationUnit(tu: TranslationUnitDeclaration) {

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1360,6 +1360,12 @@ val Node.translationUnit: TranslationUnitDeclaration?
13601360
return firstParentOrNull<TranslationUnitDeclaration>()
13611361
}
13621362

1363+
/** Returns the [Component] where this node is located in. */
1364+
val Node.component: Component?
1365+
get() {
1366+
return firstParentOrNull<Component>()
1367+
}
1368+
13631369
/**
13641370
* This helper function be used to find out if a particular expression (usually a [CallExpression]
13651371
* or a [Reference]) is imported through a [ImportDeclaration].

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ImportResolver.kt

Lines changed: 136 additions & 76 deletions
Large diffs are not rendered by default.

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ import de.fraunhofer.aisec.cpg.passes.configuration.ExecuteLast
4242
import de.fraunhofer.aisec.cpg.passes.configuration.ExecuteLate
4343
import de.fraunhofer.aisec.cpg.passes.configuration.RequiredFrontend
4444
import de.fraunhofer.aisec.cpg.passes.configuration.RequiresLanguageTrait
45+
import de.fraunhofer.aisec.cpg.processing.strategy.Strategy
46+
import de.fraunhofer.aisec.cpg.processing.strategy.Strategy.TRANSLATION_UNITS_LEAST_IMPORTS
4547
import java.util.concurrent.CompletableFuture
4648
import java.util.function.Consumer
4749
import kotlin.reflect.KClass
@@ -272,14 +274,19 @@ fun executePass(
272274
consumeTargets(
273275
(prototype as ComponentPass)::class,
274276
ctx,
275-
result.components,
277+
// Execute them in the "sorted" order (if available)
278+
(Strategy::COMPONENTS_LEAST_IMPORTS)(result).asSequence().toList(),
276279
executedFrontends,
277280
)
278281
is TranslationUnitPass ->
279282
consumeTargets(
280283
(prototype as TranslationUnitPass)::class,
281284
ctx,
282-
result.components.flatMap { it.translationUnits },
285+
// Execute them in the "sorted" order (if available)
286+
(Strategy::COMPONENTS_LEAST_IMPORTS)(result)
287+
.asSequence()
288+
.flatMap { (Strategy::TRANSLATION_UNITS_LEAST_IMPORTS)(it).asSequence() }
289+
.toList(),
283290
executedFrontends,
284291
)
285292
is EOGStarterPass -> {

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,12 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) {
139139

140140
// Resolve symbols in our translation units in the order depending on their import
141141
// dependencies
142-
component.importDependencies.sortedTranslationUnits.forEach {
143-
log.debug("Resolving symbols of translation unit {}", it.name)
142+
for (tu in (Strategy::TRANSLATION_UNITS_LEAST_IMPORTS)(component)) {
143+
log.debug("Resolving symbols of translation unit {}", tu.name)
144144

145145
// Gather all resolution EOG starters; and make sure they really do not have a
146146
// predecessor, otherwise we might analyze a node multiple times
147-
val nodes = it.allEOGStarters.filter { it.prevEOGEdges.isEmpty() }
147+
val nodes = tu.allEOGStarters.filter { it.prevEOGEdges.isEmpty() }
148148

149149
for (node in nodes) {
150150
walker.iterate(node)

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/processing/strategy/Strategy.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,19 @@
2525
*/
2626
package de.fraunhofer.aisec.cpg.processing.strategy
2727

28+
import de.fraunhofer.aisec.cpg.TranslationResult
29+
import de.fraunhofer.aisec.cpg.graph.Component
2830
import de.fraunhofer.aisec.cpg.graph.Node
31+
import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration
2932
import java.util.*
33+
import org.slf4j.Logger
34+
import org.slf4j.LoggerFactory
3035

3136
/** Strategies (iterators) for traversing graphs to be used by visitors. */
3237
object Strategy {
38+
39+
val log: Logger = LoggerFactory.getLogger(Strategy::class.java)
40+
3341
/**
3442
* Do not traverse any nodes.
3543
*
@@ -55,6 +63,26 @@ object Strategy {
5563
return x.nextEOGEdges.filter { !it.unreachable }.map { it.end }.iterator()
5664
}
5765

66+
fun COMPONENTS_LEAST_IMPORTS(x: TranslationResult): Iterator<Component> {
67+
return x.componentDependencies?.sorted?.iterator()
68+
?: x.components.iterator().also {
69+
log.warn(
70+
"Strategy for components with least import dependencies was requested, but no import dependency information is available."
71+
)
72+
log.warn("Please make sure that the ImportResolver pass was run successfully.")
73+
}
74+
}
75+
76+
fun TRANSLATION_UNITS_LEAST_IMPORTS(x: Component): Iterator<TranslationUnitDeclaration> {
77+
return x.translationUnitDependencies?.sorted?.iterator()
78+
?: x.translationUnits.iterator().also {
79+
log.warn(
80+
"Strategy for translation units with least import dependencies was requested, but no import dependency information is available."
81+
)
82+
log.warn("Please make sure that the ImportResolver pass was run successfully.")
83+
}
84+
}
85+
5886
/**
5987
* Traverse Evaluation Order Graph in backward direction.
6088
*

cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ class FluentTest {
176176
var app = result.components.firstOrNull()
177177
assertNotNull(app)
178178

179-
ImportResolver(result.finalCtx).accept(app)
179+
ImportResolver(result.finalCtx).accept(result)
180180
EvaluationOrderGraphPass(result.finalCtx).accept(app.translationUnits.first())
181181
SymbolResolver(result.finalCtx).accept(app)
182182

cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ImportResolverTest.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,25 +113,25 @@ class ImportResolverTest {
113113

114114
// a has 0 dependencies
115115
var a =
116-
app.importDependencies.entries
117-
.filter { it.key.name.toString() == "file.a" }
118-
.firstOrNull()
116+
app.translationUnitDependencies?.entries?.firstOrNull {
117+
it.key.name.toString() == "file.a"
118+
}
119119
assertNotNull(a)
120120
assertEquals(0, a.value.size)
121121

122122
// c has 0 dependencies
123123
var c =
124-
app.importDependencies.entries
125-
.filter { it.key.name.toString() == "file.c" }
126-
.firstOrNull()
124+
app.translationUnitDependencies?.entries?.firstOrNull {
125+
it.key.name.toString() == "file.c"
126+
}
127127
assertNotNull(c)
128128
assertEquals(0, c.value.size)
129129

130130
// b has two dependencies (a, c)
131131
var b =
132-
app.importDependencies.entries
133-
.filter { it.key.name.toString() == "file.b" }
134-
.firstOrNull()
132+
app.translationUnitDependencies?.entries?.firstOrNull {
133+
it.key.name.toString() == "file.b"
134+
}
135135
assertNotNull(b)
136136
assertEquals(2, b.value.size)
137137
assertEquals(setOf(a.key, c.key), b.value)

cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/processing/VisitorTest.kt

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,19 @@
2626
package de.fraunhofer.aisec.cpg.processing
2727

2828
import de.fraunhofer.aisec.cpg.GraphExamples
29+
import de.fraunhofer.aisec.cpg.ScopeManager
30+
import de.fraunhofer.aisec.cpg.TranslationConfiguration
31+
import de.fraunhofer.aisec.cpg.TranslationContext
32+
import de.fraunhofer.aisec.cpg.TranslationManager
33+
import de.fraunhofer.aisec.cpg.TranslationResult
34+
import de.fraunhofer.aisec.cpg.TypeManager
2935
import de.fraunhofer.aisec.cpg.frontends.TranslationException
3036
import de.fraunhofer.aisec.cpg.graph.*
3137
import de.fraunhofer.aisec.cpg.graph.Node
3238
import de.fraunhofer.aisec.cpg.graph.declarations.*
3339
import de.fraunhofer.aisec.cpg.graph.statements
3440
import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement
41+
import de.fraunhofer.aisec.cpg.passes.ImportDependencies
3542
import de.fraunhofer.aisec.cpg.processing.strategy.Strategy
3643
import de.fraunhofer.aisec.cpg.test.*
3744
import java.util.concurrent.ExecutionException
@@ -130,6 +137,66 @@ class VisitorTest : BaseTest() {
130137
assertEquals(2, returnStatements.size)
131138
}
132139

140+
@Test
141+
fun testFallbackComponentLeastImported() {
142+
val component1 = Component().also { it.name = Name("component1") }
143+
val component2 = Component().also { it.name = Name("component2") }
144+
145+
val tr =
146+
TranslationResult(
147+
translationManager = TranslationManager.builder().build(),
148+
finalCtx =
149+
TranslationContext(
150+
config = TranslationConfiguration.builder().build(),
151+
scopeManager = ScopeManager(),
152+
typeManager = TypeManager(),
153+
),
154+
)
155+
tr.components += component1
156+
tr.components += component2
157+
158+
// will trigger fallback as we have no dependency information
159+
val fallback = Strategy.COMPONENTS_LEAST_IMPORTS(tr).asSequence().toList()
160+
assertEquals(listOf(component1, component2), fallback)
161+
162+
tr.componentDependencies =
163+
ImportDependencies<Component>(tr.components).also {
164+
it.add(component1, component2)
165+
it
166+
}
167+
168+
// will use sorted
169+
val sorted = Strategy.COMPONENTS_LEAST_IMPORTS(tr).asSequence().toList()
170+
assertEquals(listOf(component2, component1), sorted)
171+
assertEquals(tr.componentDependencies?.sorted, sorted)
172+
}
173+
174+
@Test
175+
fun testFallbackTULeastImported() {
176+
val component = Component()
177+
178+
val tr1 = TranslationUnitDeclaration().also { it.name = Name("tr1") }
179+
val tr2 = TranslationUnitDeclaration().also { it.name = Name("tr2") }
180+
181+
component.translationUnits += tr1
182+
component.translationUnits += tr2
183+
184+
// will trigger fallback as we have no dependency information
185+
val fallback = Strategy.TRANSLATION_UNITS_LEAST_IMPORTS(component).asSequence().toList()
186+
assertEquals(listOf(tr1, tr2), fallback)
187+
188+
component.translationUnitDependencies =
189+
ImportDependencies<TranslationUnitDeclaration>(component.translationUnits).also {
190+
it.add(tr1, tr2)
191+
it
192+
}
193+
194+
// will use sorted
195+
val sorted = Strategy.TRANSLATION_UNITS_LEAST_IMPORTS(component).asSequence().toList()
196+
assertEquals(listOf(tr2, tr1), sorted)
197+
assertEquals(component.translationUnitDependencies?.sorted, sorted)
198+
}
199+
133200
companion object {
134201
private var recordDeclaration: RecordDeclaration? = null
135202

0 commit comments

Comments
 (0)