diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt index a4dbfa8fecb..83aeeb1540d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt @@ -98,7 +98,7 @@ private constructor( } else { // Execute all passes in sequence for (pass in config.registeredPasses.flatten()) { - executePassSequential(pass, ctx, result, executedFrontends) + executePass(pass, ctx, result, executedFrontends) if (result.isCancelled) { log.warn("Analysis interrupted, stopping Pass evaluation") } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index 0fe5c18c41f..cf8976c1a2d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -35,7 +35,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.FunctionType import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.graph.types.UnknownType -import de.fraunhofer.aisec.cpg.passes.executePassSequential +import de.fraunhofer.aisec.cpg.passes.executePass fun LanguageFrontend<*, *>.translationResult( init: TranslationResult.() -> Unit @@ -49,7 +49,7 @@ fun LanguageFrontend<*, *>.translationResult( node.addComponent(component) init(node) - ctx.config.registeredPasses.flatten().forEach { executePassSequential(it, ctx, node, listOf()) } + ctx.config.registeredPasses.flatten().forEach { executePass(it, ctx, node, listOf()) } return node } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/SubgraphWalker.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/SubgraphWalker.kt index 88b31736e85..58ceb9ab9d1 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/SubgraphWalker.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/SubgraphWalker.kt @@ -141,17 +141,22 @@ object SubgraphWalker { val ast = field.getAnnotation(AST::class.java) if (ast != null) { try { - // disable access mechanisms - field.trySetAccessible() - var obj = field[node] - - // restore old state - field.isAccessible = false + // We need to synchronize access to the field, because otherwise different + // threads might restore the isAccessible property while this thread is still + // accessing the field + var obj = + synchronized(field) { + // disable access mechanisms + field.trySetAccessible() + val obj = field[node] + + // restore old state + field.isAccessible = false + obj + } + ?: continue // skip, if null - if (obj == null) { - continue - } var outgoing = true // default if (field.getAnnotation(Relationship::class.java) != null) { outgoing = diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt index 916964fbfa1..d174b178b46 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt @@ -42,20 +42,20 @@ import org.slf4j.LoggerFactory /** * A [TranslationResultPass] is a pass that operates on a [TranslationResult]. If used with - * [executePassSequential], one [Pass] object is instantiated for the whole [TranslationResult]. + * [executePass], one [Pass] object is instantiated for the whole [TranslationResult]. */ abstract class TranslationResultPass(ctx: TranslationContext) : Pass(ctx) /** - * A [ComponentPass] is a pass that operates on a [Component]. If used with [executePassSequential], - * one [Pass] object is instantiated for each [Component] in a [TranslationResult]. + * A [ComponentPass] is a pass that operates on a [Component]. If used with [executePass], one + * [Pass] object is instantiated for each [Component] in a [TranslationResult]. */ abstract class ComponentPass(ctx: TranslationContext) : Pass(ctx) /** * A [TranslationUnitPass] is a pass that operates on a [TranslationUnitDeclaration]. If used with - * [executePassSequential], one [Pass] object is instantiated for each [TranslationUnitDeclaration] - * in a [Component]. + * [executePass], one [Pass] object is instantiated for each [TranslationUnitDeclaration] in a + * [Component]. */ abstract class TranslationUnitPass(ctx: TranslationContext) : Pass(ctx) @@ -126,9 +126,9 @@ fun executePassesInParallel( executedFrontends: Collection> ) { // Execute a single pass directly sequentially and return - var pass = classes.singleOrNull() + val pass = classes.singleOrNull() if (pass != null) { - executePassSequential(pass, ctx, result, executedFrontends) + executePass(pass, ctx, result, executedFrontends) return } @@ -143,9 +143,7 @@ fun executePassesInParallel( val futures = classes.map { - CompletableFuture.supplyAsync { - executePassSequential(it, ctx, result, executedFrontends) - } + CompletableFuture.supplyAsync { executePass(it, ctx, result, executedFrontends) } } futures.map(CompletableFuture::join) @@ -153,11 +151,14 @@ fun executePassesInParallel( } /** - * Creates a new [Pass] (based on [cls]) and executes it sequentially on the nodes of [result]. - * Depending on the type of pass, this will either execute the pass directly on the overall result, - * loop through each component or through each translation unit. + * Creates a new [Pass] (based on [cls]) and executes it sequentially on all target nodes of + * [result]. + * + * Depending on the type of pass, this will either execute the pass directly on the overall result + * (in case of a [TranslationUnitPass]) or loop through each component or through each translation + * unit. The individual loop elements become the "target" of the execution of [consumeTarget]. */ -fun executePassSequential( +fun executePass( cls: KClass>, ctx: TranslationContext, result: TranslationResult, @@ -172,33 +173,67 @@ fun executePassSequential( cls.primaryConstructor?.call(ctx) ?: throw TranslationException("Could not create prototype pass") + // Collect our "targets" based on the type and granularity of the pass and consume them by the + // pass. when (prototype) { - is TranslationResultPass -> { - executePass((prototype as TranslationResultPass)::class, ctx, result, executedFrontends) - } - is ComponentPass -> { - for (component in result.components) { - executePass((prototype as ComponentPass)::class, ctx, component, executedFrontends) - } - } - is TranslationUnitPass -> { - for (component in result.components) { - for (tu in component.translationUnits) { - executePass( - (prototype as TranslationUnitPass)::class, - ctx, - tu, - executedFrontends - ) - } - } - } + is TranslationResultPass -> + consumeTargets( + (prototype as TranslationResultPass)::class, + ctx, + listOf(result), + executedFrontends + ) + is ComponentPass -> + consumeTargets( + (prototype as ComponentPass)::class, + ctx, + result.components, + executedFrontends + ) + is TranslationUnitPass -> + consumeTargets( + (prototype as TranslationUnitPass)::class, + ctx, + result.components.flatMap { it.translationUnits }, + executedFrontends + ) } bench.stop() } -inline fun executePass( +/** + * This function is a wrapper around [consumeTarget] to apply it to all [targets]. This is primarily + * needed because of very delicate type inference work of the Kotlin compiler. + * + * Depending on the configuration of [TranslationConfiguration.useParallelPasses], the individual + * targets will either be consumed sequentially or in parallel. + */ +private inline fun consumeTargets( + cls: KClass>, + ctx: TranslationContext, + targets: List, + executedFrontends: Collection> +) { + if (ctx.config.useParallelPasses) { + val futures = + targets.map { + CompletableFuture.supplyAsync { consumeTarget(cls, ctx, it, executedFrontends) } + } + futures.forEach(CompletableFuture?>::join) + } else { + targets.forEach { consumeTarget(cls, ctx, it, executedFrontends) } + } +} + +/** + * This function creates a new [Pass] object, based on the class specified in [cls] and consumes the + * [target] with the pass. The target type depends on the type of pass, e.g., a + * [TranslationUnitDeclaration] or a whole [Component]. When passes are executed in parallel, + * different instances of the same [Pass] class are executed at the same time (on different [target] + * nodes) using this function. + */ +private inline fun consumeTarget( cls: KClass>, ctx: TranslationContext, target: T,