Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ jobs:
uses: gradle/actions/setup-gradle@v5
- name: Build ${{ env.version }}
run: |
# The distZip/distTar and shadowJar/shadowDistZip/shadowDistTar slow down the build and are not needed in CI
./gradlew --parallel spotlessCheck -x spotlessApply build -x distZip -x distTar -x shadowJar -x shadowDistZip -x shadowDistTar koverXmlReport koverHtmlReport performanceTest integrationTest
id: build
env:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,22 +67,27 @@ private constructor(
private val isCancelled = AtomicBoolean(false)

/**
* Kicks off the analysis.
* Kicks off the analysis and reports progress through [callbacks].
*
* This method orchestrates all passes that will do the main work.
*
* @param ctx - The [TranslationContext] to use for the analysis. If none is provided, a new one
* @param ctx The [TranslationContext] to use for the analysis. If none is provided, a new one
* is created.
* @param callbacks Callback(s) that are notified after frontend parsing and after each pass.
* @return a [CompletableFuture] with the [TranslationResult].
*/
fun analyze(ctx: TranslationContext? = null): CompletableFuture<TranslationResult> {
fun analyze(
ctx: TranslationContext? = null,
callbacks: Collection<TranslationProgressCallback>? = null,
): CompletableFuture<TranslationResult> {
// We wrap the analysis in a CompletableFuture, i.e. in an async task.
return CompletableFuture.supplyAsync {
analyzeNonAsync(ctx = ctx ?: TranslationContext(config))
analyzeNonAsync(ctx = ctx ?: TranslationContext(config), callbacks = callbacks)
}
}

private fun analyzeNonAsync(ctx: TranslationContext): TranslationResult {
private fun analyzeNonAsync(
ctx: TranslationContext,
callbacks: Collection<TranslationProgressCallback>?,
): TranslationResult {
var executedFrontends = setOf<LanguageFrontend<*, *>>()

// Build a new translation result
Expand All @@ -95,10 +100,20 @@ private constructor(
// Parse Java/C/CPP files
val bench = Benchmark(this.javaClass, "Executing Language Frontend", false, result)
executedFrontends = runFrontends(ctx, result)
callbacks?.forEach { callback ->
runCatching { callback.afterFrontends(ctx, result, executedFrontends) }
.onFailure {
log.warn(
"Progress callback {} failed after frontend execution",
callback::class.simpleName ?: callback.javaClass.simpleName,
it,
)
}
}
ctx.executedFrontends.addAll(executedFrontends)
bench.addMeasurement()

executePassesSequentially(ctx, result, executedFrontends)
executePassesSequentially(ctx, result, executedFrontends, callbacks)
} catch (ex: TranslationException) {
throw CompletionException(ex)
} finally {
Expand Down Expand Up @@ -138,7 +153,7 @@ private constructor(
* * is `true`.
*/
@Throws(TranslationException::class)
private fun runFrontends(
fun runFrontends(
ctx: TranslationContext,
result: TranslationResult,
): Set<LanguageFrontend<*, *>> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2026, Fraunhofer AISEC. All rights reserved.
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 de.fraunhofer.aisec.cpg

import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend
import de.fraunhofer.aisec.cpg.graph.Node
import de.fraunhofer.aisec.cpg.passes.Pass
import kotlin.reflect.KClass

/**
* Callback interface for reporting key progress steps of the CPG construction.
*
* Register one or multiple callbacks via [TranslationManager.analyze].
*/
interface TranslationProgressCallback {
/** Called after all configured frontends finished running. */
fun afterFrontends(
ctx: TranslationContext,
result: TranslationResult,
executedFrontends: Set<LanguageFrontend<*, *>>,
) {}

/** Called after a pass has finished execution. */
fun afterPass(
pass: KClass<out Pass<out Node>>,
ctx: TranslationContext,
result: TranslationResult,
nodes: Collection<Node>,
) {}
}
31 changes: 25 additions & 6 deletions cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import de.fraunhofer.aisec.cpg.helpers.Benchmark
import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.ScopedWalker
import de.fraunhofer.aisec.cpg.helpers.mapFilteredTo
import de.fraunhofer.aisec.cpg.helpers.orderEOGStartersBasedOnDependencies
import de.fraunhofer.aisec.cpg.passes.Pass.Companion.log
import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn
import de.fraunhofer.aisec.cpg.passes.configuration.ExecuteBefore
import de.fraunhofer.aisec.cpg.passes.configuration.ExecuteFirst
Expand Down Expand Up @@ -310,6 +311,7 @@ fun executePassesSequentially(
ctx: TranslationContext,
result: TranslationResult,
executedFrontends: Set<LanguageFrontend<*, *>>,
callbacks: Collection<TranslationProgressCallback>? = null,
) {
// Execute all passes in sequence. First convert the list of passes to a queue
val queue = ArrayDeque<KClass<out Pass<out Node>>>()
Expand All @@ -334,7 +336,7 @@ fun executePassesSequentially(
}

// Execute it
executePass(pass, ctx, result, executedFrontends)
executePass(pass, ctx, result, executedFrontends, callbacks)

// Increment executions
executions[pass] = numExec + 1
Expand Down Expand Up @@ -373,6 +375,7 @@ fun executePass(
ctx: TranslationContext,
result: TranslationResult,
executedFrontends: Collection<LanguageFrontend<*, *>>,
callbacks: Collection<TranslationProgressCallback>? = null,
) {
val bench = Benchmark(cls.java, "Executing Pass", false, result)

Expand All @@ -391,22 +394,25 @@ fun executePass(
(prototype as TranslationResultPass)::class,
ctx,
prototype.sort(result),
executedFrontends,
result,
callbacks,
)
is ComponentPass ->
consumeTargets(
(prototype as ComponentPass)::class,
ctx,
prototype.sort(result),
executedFrontends,
result,
callbacks,
)
is TranslationUnitPass ->
consumeTargets(
(prototype as TranslationUnitPass)::class,
ctx,
// Execute them in the "sorted" order (if available)
prototype.sort(result),
executedFrontends,
result,
callbacks,
)
is EOGStarterPass -> {
consumeTargets(
Expand All @@ -417,7 +423,8 @@ fun executePass(
} else {
prototype.sort(result)
},
executedFrontends,
result,
callbacks,
)
}
}
Expand All @@ -436,9 +443,21 @@ inline fun <reified T : Node> consumeTargets(
cls: KClass<out Pass<T>>,
ctx: TranslationContext,
targets: Collection<T>,
executedFrontends: Collection<LanguageFrontend<*, *>>,
result: TranslationResult,
callbacks: Collection<TranslationProgressCallback>? = null,
) {
targets.forEach { consumeTarget(cls, ctx, it) }
callbacks?.forEach { callback ->
runCatching { callback.afterPass(cls, ctx, result, targets) }
.onFailure {
log.warn(
"Progress callback {} failed after pass {}",
callback::class.simpleName ?: callback.javaClass.simpleName,
cls.simpleName,
it,
)
}
}
}

/**
Expand Down
Loading
Loading