From 9d7b75b19d3184d56d0cd68661ee91dd6c833d1e Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 15 Nov 2023 21:15:35 +0100 Subject: [PATCH 1/5] Removed `parent` from `hashCode` of `Scope` This was causing MAJOR performance problems, since we are using the `hashCode` in the symbol resolver for caching. This had the effect that on deeply nested scopes, performance dropped quite largely. --- .../main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/Scope.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/Scope.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/Scope.kt index 02610fc6a17..91e970c3c7c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/Scope.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/Scope.kt @@ -94,7 +94,6 @@ abstract class Scope( override fun hashCode(): Int { var result = astNode?.hashCode() ?: 0 - result = 31 * result + (parent?.hashCode() ?: 0) result = 31 * result + (name?.hashCode() ?: 0) return result } From 13920f9d0e1e16e9396218853a1c6ea002466aec Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 15 Nov 2023 21:56:02 +0100 Subject: [PATCH 2/5] Comparing scope in case of a cache hit to avoid collisions --- .../de/fraunhofer/aisec/cpg/ScopeManager.kt | 26 +++++++++++-------- .../graph/statements/expressions/Reference.kt | 13 +++++++--- .../aisec/cpg/passes/SymbolResolverTest.kt | 2 +- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt index 49fff98607b..07755b54be0 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt @@ -76,10 +76,11 @@ class ScopeManager : ScopeProvider { data class Alias(var from: Name, var to: Name) /** - * A cache map of unique tags (computed with [Reference.buildUniqueTag]) and their respective - * [ValueDeclaration]. This is used by [resolveReference] as a caching mechanism. + * A cache map of reference tags (computed with [Reference.referenceTag]) and their respective + * pair of original [Reference] and resolved [ValueDeclaration]. This is used by + * [resolveReference] as a caching mechanism. */ - private val symbolTable = mutableMapOf() + private val symbolTable = mutableMapOf>() /** * In some languages, we can define aliases for names. An example is renaming package imports in @@ -623,18 +624,21 @@ class ScopeManager : ScopeProvider { val startScope = ref.scope // Retrieve a unique tag for the particular reference based on the current scope - val tag = ref.uniqueTag - - // If we find a match in our symbol table, we can immediately return the declaration - var decl = symbolTable[tag] - if (decl != null) { - return decl + val tag = ref.referenceTag + + // If we find a match in our symbol table, we can immediately return the declaration. We + // need to be careful about potential collisions in our tags, since they are based on the + // hash-code of the scope. We therefore take the extra precaution to compare the scope in + // case we get a hit. This should not take too much performance overhead. + val pair = symbolTable[tag] + if (pair != null && ref.scope == pair.first.scope) { + return pair.second } val (scope, name) = extractScope(ref, startScope) // Try to resolve value declarations according to our criteria - decl = + val decl = resolve(scope) { if (it.name.lastPartsMatch(name)) { val helper = ref.resolutionHelper @@ -666,7 +670,7 @@ class ScopeManager : ScopeProvider { // Update the symbol cache, if we found a declaration for the tag if (decl != null) { - symbolTable[tag] = decl + symbolTable[tag] = Pair(ref, decl) } return decl diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Reference.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Reference.kt index 5eddd269f1d..57ce5a3cba2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Reference.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Reference.kt @@ -32,6 +32,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.edge.Properties +import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.passes.SymbolResolver @@ -155,11 +156,15 @@ open class Reference : Expression(), HasType.TypeObserver { } /** - * This function builds a unique tag for the particular reference, based on the [startScope]. - * Its purpose is to cache symbol resolutions, similar to LLVMs system of Unified Symbol - * Resolution (USR). + * This function builds a tag for the particular reference, based on its [name], + * [resolutionHelper] and [scope]. Its purpose is to cache symbol resolutions, similar to LLVMs + * system of Unified Symbol Resolution (USR). Please be aware, that this tag is not guaranteed + * to be 100 % unique, especially if the language frontend is missing [Node.location] + * information (of the [Scope.astNode]. Therefore, its usage should be similar to a [hashCode], + * so that in case of an equal hash-code, a [equals] comparison (in this case of the [scope]) is + * needed. */ - val uniqueTag: ReferenceTag + val referenceTag: ReferenceTag get() { return Objects.hash(this.name, this.resolutionHelper, this.scope) } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverTest.kt index 24415bf6ee1..51458582dde 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverTest.kt @@ -84,7 +84,7 @@ class SymbolResolverTest { refs.forEach { // Build a unique tag based on the scope of the reference is in (since this is usually // the start scope) - val list = map.computeIfAbsent(it.uniqueTag) { mutableListOf() } + val list = map.computeIfAbsent(it.referenceTag) { mutableListOf() } list += it // All elements in the list must have the same scope and name From 374d21300f3df0abe947a73fc615268c68cb2c85 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Thu, 16 Nov 2023 12:14:14 +0100 Subject: [PATCH 3/5] Make control-flow sensitive DFG pass configurable by complexity This calculates a simple cylic complexity for functions and adds a configuration option to specify a max for the control-flow sensitive DFG pass. --- .../aisec/cpg/TranslationConfiguration.kt | 15 ++++++-- .../graph/declarations/FunctionDeclaration.kt | 36 +++++++++++++++++++ .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 19 ++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt index 5bd7104a661..9bb2823112c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt @@ -108,7 +108,8 @@ private constructor( inferenceConfiguration: InferenceConfiguration, compilationDatabase: CompilationDatabase?, matchCommentsToNodes: Boolean, - addIncludesToGraph: Boolean + addIncludesToGraph: Boolean, + maxComplexityForDFG: Int?, ) { /** This list contains all languages which we want to translate. */ val languages: List> @@ -165,6 +166,8 @@ private constructor( /** This sub configuration object holds all information about inference and smart-guessing. */ val inferenceConfiguration: InferenceConfiguration + val maxComplexityForDFG: Int? + init { registeredPasses = passes this.languages = languages @@ -178,6 +181,7 @@ private constructor( this.compilationDatabase = compilationDatabase this.matchCommentsToNodes = matchCommentsToNodes this.addIncludesToGraph = addIncludesToGraph + this.maxComplexityForDFG = maxComplexityForDFG } /** Returns a list of all analyzed files. */ @@ -228,6 +232,7 @@ private constructor( private var matchCommentsToNodes = false private var addIncludesToGraph = true private var useDefaultPasses = false + private var maxComplexityForDFG: Int? = null fun symbols(symbols: Map): Builder { this.symbols = symbols @@ -409,6 +414,11 @@ private constructor( return this } + fun maxComplexityForDFG(max: Int): Builder { + this.maxComplexityForDFG = max + return this + } + /** * Loads and registers an additional [Language] based on a fully qualified class name (FQN). */ @@ -594,7 +604,8 @@ private constructor( inferenceConfiguration, compilationDatabase, matchCommentsToNodes, - addIncludesToGraph + addIncludesToGraph, + maxComplexityForDFG ) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt index 709e89db775..4f186407d74 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt @@ -30,7 +30,10 @@ import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate +import de.fraunhofer.aisec.cpg.graph.statements.CaseStatement +import de.fraunhofer.aisec.cpg.graph.statements.IfStatement import de.fraunhofer.aisec.cpg.graph.statements.Statement +import de.fraunhofer.aisec.cpg.graph.statements.SwitchStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression @@ -264,6 +267,15 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder, Resoluti return list } + /** + * This returns a simple heuristic for the complexity of a function declaration, based on the + * level of nesting + */ + val complexity: Int + get() { + return this.body?.cyclicComplexity ?: 0 + } + companion object { const val WHITESPACE = " " const val BRACKET_LEFT = "(" @@ -271,3 +283,27 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder, Resoluti const val BRACKET_RIGHT = ")" } } + +val Statement.cyclicComplexity: Int + get() { + var i = 0 + for (stmt in (this as? Block)?.statements ?: listOf()) { + when (stmt) { + is IfStatement -> { + // add one for each branch (and include the children) + stmt.thenStatement?.let { i += it.cyclicComplexity + 1 } + stmt.elseStatement?.let { i += it.cyclicComplexity + 1 } + } + is SwitchStatement -> { + // forward it to the block containing the case statements + stmt.statement?.let { i += it.cyclicComplexity } + } + is CaseStatement -> { + // add one for each branch (and include the children) + stmt.caseExpression?.let { i += it.cyclicComplexity } + } + } + } + + return i + } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index 656ecbed93c..1e0fe6d84c7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -61,9 +61,28 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni * @param node every node in the TranslationResult */ protected fun handle(node: Node) { + val max = config.maxComplexityForDFG + if (node is FunctionDeclaration) { + // Skip empty functions + if (node.body == null) { + return + } + + // Calculate the complexity of the function and see, if it exceeds our threshold + if (max != null) { + val c = node.body?.cyclicComplexity ?: 0 + if (c > max) { + log.info( + "Ignoring function ${node.name} because its complexity (${c}) is greater than the configured maximum (${max})" + ) + return + } + } + clearFlowsOfVariableDeclarations(node) val startState = DFGPassState>() + startState.declarationsState.push(node, PowersetLattice(setOf())) val finalState = iterateEOG(node.nextEOGEdges, startState, ::transfer) as? DFGPassState ?: return From b58cdab1c79c090789175d9f858905b1d7f127ba Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Thu, 16 Nov 2023 13:29:39 +0100 Subject: [PATCH 4/5] Extracted configuration in a more generic pass-specific config --- .../aisec/cpg/TranslationConfiguration.kt | 19 +++++++++++------- .../graph/declarations/FunctionDeclaration.kt | 20 ++++++++++--------- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 12 +++++++++-- .../de/fraunhofer/aisec/cpg/passes/Pass.kt | 6 ++++++ 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt index 9bb2823112c..89a45e840c3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt @@ -109,7 +109,7 @@ private constructor( compilationDatabase: CompilationDatabase?, matchCommentsToNodes: Boolean, addIncludesToGraph: Boolean, - maxComplexityForDFG: Int?, + passConfigurations: Map>, PassConfiguration>, ) { /** This list contains all languages which we want to translate. */ val languages: List> @@ -166,7 +166,7 @@ private constructor( /** This sub configuration object holds all information about inference and smart-guessing. */ val inferenceConfiguration: InferenceConfiguration - val maxComplexityForDFG: Int? + val passConfigurations: Map>, PassConfiguration> init { registeredPasses = passes @@ -181,7 +181,7 @@ private constructor( this.compilationDatabase = compilationDatabase this.matchCommentsToNodes = matchCommentsToNodes this.addIncludesToGraph = addIncludesToGraph - this.maxComplexityForDFG = maxComplexityForDFG + this.passConfigurations = passConfigurations } /** Returns a list of all analyzed files. */ @@ -232,7 +232,8 @@ private constructor( private var matchCommentsToNodes = false private var addIncludesToGraph = true private var useDefaultPasses = false - private var maxComplexityForDFG: Int? = null + private var passConfigurations: MutableMap>, PassConfiguration> = + mutableMapOf() fun symbols(symbols: Map): Builder { this.symbols = symbols @@ -414,11 +415,15 @@ private constructor( return this } - fun maxComplexityForDFG(max: Int): Builder { - this.maxComplexityForDFG = max + fun > configurePass(clazz: KClass, config: PassConfiguration): Builder { + this.passConfigurations[clazz] = config return this } + inline fun > configurePass(config: PassConfiguration): Builder { + return this.configurePass(T::class, config) + } + /** * Loads and registers an additional [Language] based on a fully qualified class name (FQN). */ @@ -605,7 +610,7 @@ private constructor( compilationDatabase, matchCommentsToNodes, addIncludesToGraph, - maxComplexityForDFG + passConfigurations ) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt index 4f186407d74..72f4b9061b5 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt @@ -268,12 +268,11 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder, Resoluti } /** - * This returns a simple heuristic for the complexity of a function declaration, based on the - * level of nesting + * This returns a simple heuristic for the complexity of a function declaration. */ val complexity: Int get() { - return this.body?.cyclicComplexity ?: 0 + return this.body?.cyclomaticComplexity ?: 0 } companion object { @@ -284,23 +283,26 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder, Resoluti } } -val Statement.cyclicComplexity: Int +/** + * This is a very basic implementation of Cyclomatic Complexity. + */ +val Statement.cyclomaticComplexity: Int get() { var i = 0 - for (stmt in (this as? Block)?.statements ?: listOf()) { + for (stmt in (this as? StatementHolder)?.statements ?: listOf(this)) { when (stmt) { is IfStatement -> { // add one for each branch (and include the children) - stmt.thenStatement?.let { i += it.cyclicComplexity + 1 } - stmt.elseStatement?.let { i += it.cyclicComplexity + 1 } + stmt.thenStatement?.let { i += it.cyclomaticComplexity + 1 } + stmt.elseStatement?.let { i += it.cyclomaticComplexity + 1 } } is SwitchStatement -> { // forward it to the block containing the case statements - stmt.statement?.let { i += it.cyclicComplexity } + stmt.statement?.let { i += it.cyclomaticComplexity } } is CaseStatement -> { // add one for each branch (and include the children) - stmt.caseExpression?.let { i += it.cyclicComplexity } + stmt.caseExpression?.let { i += it.cyclomaticComplexity } } } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index 1e0fe6d84c7..6663704287f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -47,6 +47,14 @@ import kotlin.contracts.contract @DependsOn(DFGPass::class) open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { + class Configuration( + /** + * This specifies the maximum complexity (as calculated per [Statement.cyclomaticComplexity] a + * [FunctionDeclaration] must have in order to be considered. + */ + var maxComplexity: Int? = null + ) : PassConfiguration() + override fun cleanup() { // Nothing to do } @@ -61,7 +69,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni * @param node every node in the TranslationResult */ protected fun handle(node: Node) { - val max = config.maxComplexityForDFG + val max = passConfig()?.maxComplexity if (node is FunctionDeclaration) { // Skip empty functions @@ -71,7 +79,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni // Calculate the complexity of the function and see, if it exceeds our threshold if (max != null) { - val c = node.body?.cyclicComplexity ?: 0 + val c = node.body?.cyclomaticComplexity ?: 0 if (c > max) { log.info( "Ignoring function ${node.name} because its complexity (${c}) is greater than the configured maximum (${max})" 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 2384b3b182d..3c35e53c474 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 @@ -63,6 +63,8 @@ abstract class TranslationUnitPass(ctx: TranslationContext) : Pass(final override val ctx: TranslationContext) : val log: Logger = LoggerFactory.getLogger(Pass::class.java) } + + fun passConfig(): T? { + return this.config.passConfigurations[this::class] as? T + } } /** From 5f3b1e1281a5431386c22bc9fa63cdcf7615207e Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Thu, 16 Nov 2023 18:50:41 +0100 Subject: [PATCH 5/5] Added unit test --- .../aisec/cpg/graph/builder/Fluent.kt | 6 +- .../graph/declarations/FunctionDeclaration.kt | 17 ++-- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 6 +- .../passes/ControlFlowSensitiveDFGPassTest.kt | 77 +++++++++++++++++++ 4 files changed, 90 insertions(+), 16 deletions(-) create mode 100644 cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPassTest.kt 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 b760a9e50e7..b82594f538d 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 @@ -759,9 +759,9 @@ fun LanguageFrontend<*, *>.loopBody(init: Block.() -> Unit): Block { } /** - * Creates a new [Block] in the Fluent Node DSL and sets it to the [WhileStatement.statement] of the - * nearest enclosing [WhileStatement]. The [init] block can be used to create further sub-nodes as - * well as configuring the created node itself. + * Creates a new [Block] in the Fluent Node DSL and sets it to the [ForEachStatement.statement] of + * the nearest enclosing [ForEachStatement]. The [init] block can be used to create further + * sub-nodes as well as configuring the created node itself. */ context(ForEachStatement) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt index 72f4b9061b5..2089b147200 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt @@ -30,10 +30,7 @@ import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate -import de.fraunhofer.aisec.cpg.graph.statements.CaseStatement -import de.fraunhofer.aisec.cpg.graph.statements.IfStatement -import de.fraunhofer.aisec.cpg.graph.statements.Statement -import de.fraunhofer.aisec.cpg.graph.statements.SwitchStatement +import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression @@ -267,9 +264,7 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder, Resoluti return list } - /** - * This returns a simple heuristic for the complexity of a function declaration. - */ + /** This returns a simple heuristic for the complexity of a function declaration. */ val complexity: Int get() { return this.body?.cyclomaticComplexity ?: 0 @@ -283,14 +278,16 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder, Resoluti } } -/** - * This is a very basic implementation of Cyclomatic Complexity. - */ +/** This is a very basic implementation of Cyclomatic Complexity. */ val Statement.cyclomaticComplexity: Int get() { var i = 0 for (stmt in (this as? StatementHolder)?.statements ?: listOf(this)) { when (stmt) { + is ForEachStatement -> { + // add one and include the children + i += (stmt.statement?.cyclomaticComplexity ?: 0) + 1 + } is IfStatement -> { // add one for each branch (and include the children) stmt.thenStatement?.let { i += it.cyclomaticComplexity + 1 } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index 6663704287f..21f5cb40c52 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -49,8 +49,8 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni class Configuration( /** - * This specifies the maximum complexity (as calculated per [Statement.cyclomaticComplexity] a - * [FunctionDeclaration] must have in order to be considered. + * This specifies the maximum complexity (as calculated per [Statement.cyclomaticComplexity] + * a [FunctionDeclaration] must have in order to be considered. */ var maxComplexity: Int? = null ) : PassConfiguration() @@ -105,7 +105,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni for ((key, value) in finalState.generalState) { if (key is TupleDeclaration) { // We need a little hack for tuple statements to set the index. We have the - // outer part (i.e., the tuple) here but we generate the DFG edges to the + // outer part (i.e., the tuple) here, but we generate the DFG edges to the // elements. We have the indices here, so it's amazing. key.elements.forEachIndexed { i, element -> element.addAllPrevDFG( diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPassTest.kt new file mode 100644 index 00000000000..f877ec91560 --- /dev/null +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPassTest.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023, 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.passes + +import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.frontends.TestLanguage +import de.fraunhofer.aisec.cpg.graph.builder.* +import kotlin.test.Test +import kotlin.test.assertNotNull + +class ControlFlowSensitiveDFGPassTest { + @Test + fun testConfiguration() { + val result = getForEachTest() + assertNotNull(result) + } + + fun getForEachTest() = + ControlDependenceGraphPassTest.testFrontend( + TranslationConfiguration.builder() + .registerLanguage(TestLanguage("::")) + .defaultPasses() + .registerPass() + .configurePass( + ControlFlowSensitiveDFGPass.Configuration(maxComplexity = 0) + ) + .build() + ) + .build { + translationResult { + translationUnit("forEach.cpp") { + // The main method + function("main", t("int")) { + body { + declare { variable("i", t("int")) { literal(0, t("int")) } } + forEachStmt { + declare { variable("loopVar", t("string")) } + call("magicFunction") + loopBody { + call("printf") { + literal("loop: \${}\n", t("string")) + ref("loopVar") + } + } + } + call("printf") { literal("1\n", t("string")) } + + returnStmt { ref("i") } + } + } + } + } + } +}