Skip to content

Commit

Permalink
Merge branch 'main' into fix-1347
Browse files Browse the repository at this point in the history
  • Loading branch information
oxisto authored Nov 21, 2023
2 parents 81e5adc + b748a07 commit 3031575
Show file tree
Hide file tree
Showing 12 changed files with 195 additions and 25 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
- v*.**
paths-ignore:
- "docs/**"
merge_group:
pull_request:
types: [opened, synchronize, reopened]
paths-ignore:
Expand Down
26 changes: 15 additions & 11 deletions cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<ReferenceTag, ValueDeclaration>()
private val symbolTable = mutableMapOf<ReferenceTag, Pair<Reference, ValueDeclaration>>()

/**
* In some languages, we can define aliases for names. An example is renaming package imports in
Expand Down Expand Up @@ -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<ValueDeclaration>(scope) {
if (it.name.lastPartsMatch(name)) {
val helper = ref.resolutionHelper
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ private constructor(
inferenceConfiguration: InferenceConfiguration,
compilationDatabase: CompilationDatabase?,
matchCommentsToNodes: Boolean,
addIncludesToGraph: Boolean
addIncludesToGraph: Boolean,
passConfigurations: Map<KClass<out Pass<*>>, PassConfiguration>,
) {
/** This list contains all languages which we want to translate. */
val languages: List<Language<*>>
Expand Down Expand Up @@ -165,6 +166,8 @@ private constructor(
/** This sub configuration object holds all information about inference and smart-guessing. */
val inferenceConfiguration: InferenceConfiguration

val passConfigurations: Map<KClass<out Pass<*>>, PassConfiguration>

init {
registeredPasses = passes
this.languages = languages
Expand All @@ -178,6 +181,7 @@ private constructor(
this.compilationDatabase = compilationDatabase
this.matchCommentsToNodes = matchCommentsToNodes
this.addIncludesToGraph = addIncludesToGraph
this.passConfigurations = passConfigurations
}

/** Returns a list of all analyzed files. */
Expand Down Expand Up @@ -228,6 +232,8 @@ private constructor(
private var matchCommentsToNodes = false
private var addIncludesToGraph = true
private var useDefaultPasses = false
private var passConfigurations: MutableMap<KClass<out Pass<*>>, PassConfiguration> =
mutableMapOf()

fun symbols(symbols: Map<String, String>): Builder {
this.symbols = symbols
Expand Down Expand Up @@ -409,6 +415,15 @@ private constructor(
return this
}

fun <T : Pass<*>> configurePass(clazz: KClass<T>, config: PassConfiguration): Builder {
this.passConfigurations[clazz] = config
return this
}

inline fun <reified T : Pass<*>> configurePass(config: PassConfiguration): Builder {
return this.configurePass(T::class, config)
}

/**
* Loads and registers an additional [Language] based on a fully qualified class name (FQN).
*/
Expand Down Expand Up @@ -594,7 +609,8 @@ private constructor(
inferenceConfiguration,
compilationDatabase,
matchCommentsToNodes,
addIncludesToGraph
addIncludesToGraph,
passConfigurations
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +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.Statement
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
Expand Down Expand Up @@ -264,10 +264,45 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder, Resoluti
return list
}

/** This returns a simple heuristic for the complexity of a function declaration. */
val complexity: Int
get() {
return this.body?.cyclomaticComplexity ?: 0
}

companion object {
const val WHITESPACE = " "
const val BRACKET_LEFT = "("
const val COMMA = ","
const val BRACKET_RIGHT = ")"
}
}

/** 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 }
stmt.elseStatement?.let { i += it.cyclomaticComplexity + 1 }
}
is SwitchStatement -> {
// forward it to the block containing the case statements
stmt.statement?.let { i += it.cyclomaticComplexity }
}
is CaseStatement -> {
// add one for each branch (and include the children)
stmt.caseExpression?.let { i += it.cyclomaticComplexity }
}
}
}

return i
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -61,9 +69,28 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni
* @param node every node in the TranslationResult
*/
protected fun handle(node: Node) {
val max = passConfig<Configuration>()?.maxComplexity

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?.cyclomaticComplexity ?: 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<Set<Node>>()

startState.declarationsState.push(node, PowersetLattice(setOf()))
val finalState =
iterateEOG(node.nextEOGEdges, startState, ::transfer) as? DFGPassState ?: return
Expand All @@ -78,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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ abstract class TranslationUnitPass(ctx: TranslationContext) : Pass<TranslationUn
*/
interface PassTarget

open class PassConfiguration {}

/**
* Represents an abstract class that enhances the graph before it is persisted. Passes can exist at
* three different levels:
Expand Down Expand Up @@ -109,6 +111,10 @@ sealed class Pass<T : PassTarget>(final override val ctx: TranslationContext) :

val log: Logger = LoggerFactory.getLogger(Pass::class.java)
}

fun <T : PassConfiguration> passConfig(): T? {
return this.config.passConfigurations[this::class] as? T
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ControlFlowSensitiveDFGPass>()
.configurePass<ControlFlowSensitiveDFGPass>(
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") }
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[versions]
kotlin = "1.9.0"
neo4j = "4.0.6"
log4j = "2.21.0"
log4j = "2.22.0"
sonarqube = "4.4.1.3373"
spotless = "6.22.0"
nexus-publish = "1.3.0"
Expand Down

0 comments on commit 3031575

Please sign in to comment.