Skip to content

Commit

Permalink
Merge branch 'main' into ak/update-docs20241204
Browse files Browse the repository at this point in the history
  • Loading branch information
KuechA authored Dec 4, 2024
2 parents 801ab63 + 0281c1e commit 8ddf01c
Show file tree
Hide file tree
Showing 70 changed files with 1,467 additions and 613 deletions.
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ cpg-neo4j @peckto

build.gradle.kts @oxisto
.github @oxisto

cpg-language-ini @maximiliankaul
10 changes: 9 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,19 @@ jobs:
id: build
env:
VERSION: ${{ env.version }}
- name: Prepare report.xml for Codecov
run: |
# this is needed because codecov incorrectly reports lines that have no coverage information (good or bad) as a miss
# See https://github.com/codecov/feedback/issues/564 and https://github.com/Kotlin/kotlinx-kover/issues/699.
# Actually these lines should just not exist in the coverage XML file, since they are only structural elements, such
# as brackets.
cat cpg-all/build/reports/kover/report.xml | grep -v 'mi="0" ci="0" mb="0" cb="0"' > cpg-all/build/reports/kover/report-codecov.xml
rm cpg-all/build/reports/kover/report.xml
- name: Upload Code Coverage
uses: codecov/codecov-action@v5
with:
fail_ci_if_error: true
files: ./cpg-all/build/reports/kover/report.xml
files: ./cpg-all/build/reports/kover/report-codecov.xml
token: ${{ secrets.CODECOV_TOKEN }}
verbose: true
- name: Prepare test and coverage reports
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ The current state of languages is:
| C++ | cpg-language-cxx | [main](https://github.com/Fraunhofer-AISEC/cpg) | `maintained` |
| Python | cpg-language-python | [main](https://github.com/Fraunhofer-AISEC/cpg) | `maintained` |
| Go | cpg-language-go | [main](https://github.com/Fraunhofer-AISEC/cpg) | `maintained` |
| INI | cpg-language-ini | [main](https://github.com/Fraunhofer-AISEC/cpg) | `maintained` |
| JVM (Bytecode) | cpg-language-jvm | [main](https://github.com/Fraunhofer-AISEC/cpg) | `incubating` |
| LLVM | cpg-language-llvm | [main](https://github.com/Fraunhofer-AISEC/cpg) | `incubating` |
| TypeScript/JavaScript | cpg-language-typescript | [main](https://github.com/Fraunhofer-AISEC/cpg) | `experimental` |
Expand Down
6 changes: 6 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,9 @@ val enableJVMFrontend: Boolean by extra {
enableJVMFrontend.toBoolean()
}
project.logger.lifecycle("JVM frontend is ${if (enableJVMFrontend) "enabled" else "disabled"}")

val enableINIFrontend: Boolean by extra {
val enableINIFrontend: String? by project
enableINIFrontend.toBoolean()
}
project.logger.lifecycle("INI frontend is ${if (enableINIFrontend) "enabled" else "disabled"}")
9 changes: 9 additions & 0 deletions buildSrc/src/main/kotlin/cpg.common-conventions.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import org.gradle.accessors.dm.LibrariesForLibs
import org.jetbrains.dokka.gradle.DokkaTask
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.gradle.api.services.BuildService;
Expand Down Expand Up @@ -164,3 +165,11 @@ kover {
}
}
}

// Common dependencies that we need for all modules
val libs = the<LibrariesForLibs>() // necessary to be able to use the version catalog in buildSrc
dependencies {
implementation(libs.apache.commons.lang3)
implementation(libs.neo4j.ogm.core)
implementation(libs.jackson)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ val enableLLVMFrontend: Boolean by rootProject.extra
val enableTypeScriptFrontend: Boolean by rootProject.extra
val enableRubyFrontend: Boolean by rootProject.extra
val enableJVMFrontend: Boolean by rootProject.extra
val enableINIFrontend: Boolean by rootProject.extra

dependencies {
if (enableJavaFrontend) {
Expand Down Expand Up @@ -46,4 +47,8 @@ dependencies {
api(project(":cpg-language-ruby"))
kover(project(":cpg-language-ruby"))
}
if (enableINIFrontend) {
api(project(":cpg-language-ini"))
kover(project(":cpg-language-ini"))
}
}
2 changes: 2 additions & 0 deletions configure_frontends.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,5 @@ answerRuby=$(ask "Do you want to enable the Ruby frontend? (currently $(getPrope
setProperty "enableRubyFrontend" $answerRuby
answerJVM=$(ask "Do you want to enable the JVM frontend? (currently $(getProperty "enableJVMFrontend"))")
setProperty "enableJVMFrontend" $answerJVM
answerINI=$(ask "Do you want to enable the INI frontend? (currently $(getProperty "enableINIFrontend"))")
setProperty "enableINIFrontend" $answerINI
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class TranslatePlugin : Plugin {
" .optionalLanguage(\"de.fraunhofer.aisec.cpg.frontends.golang.GoLanguage\")" +
" .optionalLanguage(\"de.fraunhofer.aisec.cpg.frontends.typescript.TypeScriptLanguage\")" +
" .optionalLanguage(\"de.fraunhofer.aisec.cpg.frontends.ruby.RubyLanguage\")" +
" .optionalLanguage(\"de.fraunhofer.aisec.cpg.frontends.ini.IniFileLanguage\")" +
" .defaultPasses()\n" +
" .useParallelPasses(false)\n" +
" .configurePass<de.fraunhofer.aisec.cpg.passes.ControlFlowSensitiveDFGPass>(\n" +
Expand Down
5 changes: 1 addition & 4 deletions cpg-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,10 @@ tasks.test {
}

dependencies {
api(libs.apache.commons.lang3)
api(libs.neo4j.ogm.core)
api(libs.jackson)
api(libs.slf4j.api)

implementation(libs.bundles.log4j)
implementation(libs.kotlin.reflect)

implementation(libs.jacksonyml)

testImplementation(libs.junit.params)
Expand Down
120 changes: 67 additions & 53 deletions cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,6 @@ class ScopeManager : ScopeProvider {
*/
private val symbolTable = mutableMapOf<ReferenceTag, Pair<Reference, ValueDeclaration>>()

/** True, if the scope manager is currently in a [BlockScope]. */
val isInBlock: Boolean
get() = this.firstScopeOrNull { it is BlockScope } != null
/** True, if the scope manager is currently in a [FunctionScope]. */
val isInFunction: Boolean
get() = this.firstScopeOrNull { it is FunctionScope } != null
Expand All @@ -92,13 +89,16 @@ class ScopeManager : ScopeProvider {
val globalScope: GlobalScope?
get() = scopeMap[null] as? GlobalScope

/** The current block, according to the scope that is currently active. */
val currentBlock: Block?
get() = this.firstScopeIsInstanceOrNull<BlockScope>()?.astNode as? Block
/** The current function, according to the scope that is currently active. */
val currentFunction: FunctionDeclaration?
get() = this.firstScopeIsInstanceOrNull<FunctionScope>()?.astNode as? FunctionDeclaration

/** The current block, according to the scope that is currently active. */
val currentBlock: Block?
get() =
currentScope?.astNode as? Block
?: currentScope?.astNode?.firstParentOrNull { it is Block } as? Block

/**
* The current method in the active scope tree, this ensures that 'this' keywords are mapped
* correctly if a method contains a lambda or other types of function declarations
Expand Down Expand Up @@ -218,11 +218,11 @@ class ScopeManager : ScopeProvider {
* new scope, this function needs to be called. Appropriate scopes will then be created
* on-the-fly, if they do not exist.
*
* The scope manager has an internal association between the type of scope, e.g. a [BlockScope]
* The scope manager has an internal association between the type of scope, e.g. a [LocalScope]
* and the CPG node it represents, e.g. a [Block].
*
* Afterwards, all calls to [addDeclaration] will be distributed to the
* [de.fraunhofer.aisec.cpg.graph.DeclarationHolder] that is currently in-scope.
* Afterward, all calls to [addDeclaration] will be distributed to the [DeclarationHolder] that
* is currently in-scope.
*/
fun enterScope(nodeToScope: Node) {
var newScope: Scope? = null
Expand All @@ -231,21 +231,21 @@ class ScopeManager : ScopeProvider {
if (!scopeMap.containsKey(nodeToScope)) {
newScope =
when (nodeToScope) {
is Block -> BlockScope(nodeToScope)
is WhileStatement,
is DoStatement,
is AssertStatement -> LoopScope(nodeToScope)
is AssertStatement,
is ForStatement,
is ForEachStatement -> LoopScope(nodeToScope as Statement)
is SwitchStatement -> SwitchScope(nodeToScope)
is ForEachStatement,
is SwitchStatement,
is TryStatement,
is IfStatement,
is CatchClause,
is Block -> LocalScope(nodeToScope)
is FunctionDeclaration -> FunctionScope(nodeToScope)
is IfStatement -> ValueDeclarationScope(nodeToScope)
is CatchClause -> ValueDeclarationScope(nodeToScope)
is RecordDeclaration -> RecordScope(nodeToScope)
is TemplateDeclaration -> TemplateScope(nodeToScope)
is TryStatement -> TryScope(nodeToScope)
is TranslationUnitDeclaration -> FileScope(nodeToScope)
is NamespaceDeclaration -> newNameScopeIfNecessary(nodeToScope)
is NamespaceDeclaration -> newNamespaceIfNecessary(nodeToScope)
else -> {
LOGGER.error(
"No known scope for AST node of type {}",
Expand All @@ -266,23 +266,23 @@ class ScopeManager : ScopeProvider {
}

/**
* A small internal helper function used by [enterScope] to create a [NameScope].
* A small internal helper function used by [enterScope] to create a [NamespaceScope].
*
* The issue with name scopes, such as a namespace, is that it can exist across several files,
* i.e. translation units, represented by different [NamespaceDeclaration] nodes. But, in order
* to make namespace resolution work across files, only one [NameScope] must exist that holds
* all declarations, such as classes, independently of the translation units. Therefore, we need
* to check, whether such as node already exists. If it does already exist:
* - we update the scope map so that the current [NamespaceDeclaration] points to the existing
* [NameScope]
* [NamespaceScope]
* - we return null, indicating to [enterScope], that no new scope needs to be pushed by
* [enterScope].
*
* Otherwise, we return a new name scope.
* Otherwise, we return a new namespace scope.
*/
private fun newNameScopeIfNecessary(nodeToScope: NamespaceDeclaration): NameScope? {
private fun newNamespaceIfNecessary(nodeToScope: NamespaceDeclaration): NamespaceScope? {
val existingScope =
filterScopes { it is NameScope && it.name == nodeToScope.name }.firstOrNull()
filterScopes { it is NamespaceScope && it.name == nodeToScope.name }.firstOrNull()

return if (existingScope != null) {
// update the AST node to this namespace declaration
Expand All @@ -296,25 +296,7 @@ class ScopeManager : ScopeProvider {
// does not need to push a new scope
null
} else {
NameScope(nodeToScope)
}
}

/**
* Similar to [enterScope], but does so in a "read-only" mode, e.g. it does not modify the scope
* tree and does not create new scopes on the fly, as [enterScope] does.
*/
fun enterScopeIfExists(nodeToScope: Node?) {
if (scopeMap.containsKey(nodeToScope)) {
val scope = scopeMap[nodeToScope]

// we need a special handling of name spaces, because
// they are associated to more than one AST node
if (scope is NameScope) {
// update AST (see enterScope for an explanation)
scope.astNode = nodeToScope
}
currentScope = scope
NamespaceScope(nodeToScope)
}
}

Expand Down Expand Up @@ -813,8 +795,10 @@ class ScopeManager : ScopeProvider {
*
* @return the declaration, or null if it does not exist
*/
fun getRecordForName(name: Name): RecordDeclaration? {
return lookupSymbolByName(name).filterIsInstance<RecordDeclaration>().singleOrNull()
fun getRecordForName(name: Name, language: Language<*>?): RecordDeclaration? {
return lookupSymbolByName(name, language)
.filterIsInstance<RecordDeclaration>()
.singleOrNull()
}

fun typedefFor(alias: Name, scope: Scope? = currentScope): Type? {
Expand Down Expand Up @@ -874,6 +858,18 @@ class ScopeManager : ScopeProvider {
override val scope: Scope?
get() = currentScope

/**
* A convenience function to call [lookupSymbolByName] with the properties of [node]. The
* arguments [scope] and [predicate] are forwarded.
*/
fun lookupSymbolByNameOfNode(
node: Node,
scope: Scope? = node.scope,
predicate: ((Declaration) -> Boolean)? = null,
): List<Declaration> {
return lookupSymbolByName(node.name, node.language, node.location, scope, predicate)
}

/**
* This function tries to convert a [Node.name] into a [Symbol] and then performs a lookup of
* this symbol. This can either be an "unqualified lookup" if [name] is not qualified or a
Expand All @@ -885,12 +881,13 @@ class ScopeManager : ScopeProvider {
* function overloading. But it will only return list of declarations within the same scope; the
* list cannot be spread across different scopes.
*
* This means that as soon one or more declarations for the symbol are found in a "local" scope,
* these shadow all other occurrences of the same / symbol in a "higher" scope and only the ones
* from the lower ones will be returned.
* This means that as soon one or more declarations (of the matching [language]) for the symbol
* are found in a "local" scope, these shadow all other occurrences of the same / symbol in a
* "higher" scope and only the ones from the lower ones will be returned.
*/
fun lookupSymbolByName(
name: Name,
language: Language<*>?,
location: PhysicalLocation? = null,
startScope: Scope? = currentScope,
predicate: ((Declaration) -> Boolean)? = null,
Expand All @@ -900,14 +897,25 @@ class ScopeManager : ScopeProvider {
// We need to differentiate between a qualified and unqualified lookup. We have a qualified
// lookup, if the scope is not null. In this case we need to stay within the specified scope
val list =
if (scope != null) {
scope.lookupSymbol(n.localName, thisScopeOnly = true, predicate = predicate)
} else {
when {
scope != null -> {
scope
.lookupSymbol(
n.localName,
languageOnly = language,
thisScopeOnly = true,
predicate = predicate
)
.toMutableList()
}
else -> {
// Otherwise, we can look up the symbol alone (without any FQN) starting from
// the startScope
startScope?.lookupSymbol(n.localName, predicate = predicate)
startScope
?.lookupSymbol(n.localName, languageOnly = language, predicate = predicate)
?.toMutableList() ?: mutableListOf()
}
?.toMutableList() ?: return listOf()
}

// If we have both the definition and the declaration of a function declaration in our list,
// we chose only the definition
Expand All @@ -932,9 +940,15 @@ class ScopeManager : ScopeProvider {
* It is important to know that the lookup needs to be unique, so if multiple declarations match
* this symbol, a warning is triggered and null is returned.
*/
fun lookupUniqueTypeSymbolByName(name: Name, startScope: Scope?): DeclaresType? {
fun lookupUniqueTypeSymbolByName(
name: Name,
language: Language<*>?,
startScope: Scope?
): DeclaresType? {
var symbols =
lookupSymbolByName(name = name, startScope = startScope) { it is DeclaresType }
lookupSymbolByName(name = name, language = language, startScope = startScope) {
it is DeclaresType
}
.filterIsInstance<DeclaresType>()

// We need to have a single match, otherwise we have an ambiguous type, and we cannot
Expand Down
Loading

0 comments on commit 8ddf01c

Please sign in to comment.