Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various improvements to the import system #2020

Merged
merged 1 commit into from
Feb 10, 2025
Merged
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
66 changes: 33 additions & 33 deletions cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -370,19 +370,19 @@ class ScopeManager : ScopeProvider {
is ProblemDeclaration,
is IncludeDeclaration -> {
// directly add problems and includes to the global scope
this.globalScope?.addDeclaration(declaration, addToAST)
this.globalScope?.addDeclaration(declaration, addToAST, this)
}
is ValueDeclaration -> {
val scope = this.firstScopeIsInstanceOrNull<ValueDeclarationScope>()
scope?.addDeclaration(declaration, addToAST)
scope?.addDeclaration(declaration, addToAST, this)
}
is ImportDeclaration,
is EnumDeclaration,
is RecordDeclaration,
is NamespaceDeclaration,
is TemplateDeclaration -> {
val scope = this.firstScopeIsInstanceOrNull<StructureDeclarationScope>()
scope?.addDeclaration(declaration, addToAST)
scope?.addDeclaration(declaration, addToAST, this)
}
}
}
Expand Down Expand Up @@ -571,52 +571,52 @@ class ScopeManager : ScopeProvider {
* the given [Name]. It also does this recursively.
*/
fun resolveParentAlias(name: Name, scope: Scope?): Name {
var parentName = name.parent ?: return name
parentName = resolveParentAlias(parentName, scope)
if (name.parent == null) {
return name
}

// Build a new name based on the eventual resolved parent alias
var newName =
if (parentName != name.parent) {
Name(name.localName, parentName, delimiter = name.delimiter)
} else {
name
}
var decl =
scope?.lookupSymbol(parentName.localName)?.singleOrNull {
it is NamespaceDeclaration || it is RecordDeclaration
}
if (decl != null && parentName != decl.name) {
val parentName = resolveParentAlias(name.parent, scope)

// Look for an alias in the current scope. This is also resolves partial FQNs to their full
// FQN
var newScope =
scope
?.lookupSymbol(parentName.localName) {
it is NamespaceDeclaration || it is RecordDeclaration
}
?.map { nameScopeMap[it.name] }
?.toSet()
?.singleOrNull()
if (newScope != null) {
// This is probably an already resolved alias so, we take this one
return Name(newName.localName, decl.name, delimiter = newName.delimiter)
return adjustNameIfNecessary(newScope.name, parentName, name)
oxisto marked this conversation as resolved.
Show resolved Hide resolved
}

// Some special handling of typedefs; this should somehow be merged with the above but not
// exactly sure how. The issue is that we cannot take the "name" of the typedef declaration,
// but we rather want its original type name.
// TODO: This really needs to be handled better somehow, maybe a common interface for
// typedefs, namespaces and records that return the correct name?
decl = scope?.lookupSymbol(parentName.localName)?.singleOrNull { it is TypedefDeclaration }
val decl =
scope?.lookupSymbol(parentName.localName)?.singleOrNull { it is TypedefDeclaration }
if ((decl as? TypedefDeclaration) != null) {
return Name(newName.localName, decl.type.name, delimiter = newName.delimiter)
return adjustNameIfNecessary(decl.type.name, parentName, name)
}

// If we do not have a match yet, it could be that we are trying to resolve an FQN type
// during frontend translation. This is deprecated and will be replaced in the future
// by a system that also resolves type during symbol resolving. However, to support aliases
// from imports in this intermediate stage, we have to look for unresolved import
// declarations and also take their aliases into account
decl =
scope
?.lookupSymbol(parentName.localName)
?.filterIsInstance<ImportDeclaration>()
?.singleOrNull()
if (decl != null && decl.importedSymbols.isEmpty() && parentName != decl.import) {
newName = Name(newName.localName, decl.import, delimiter = newName.delimiter)
}
// Otherwise, just build a new name based on the eventual resolved parent alias (if
// necessary)
var newName = adjustNameIfNecessary(parentName, name.parent, name)

return newName
}

private fun adjustNameIfNecessary(newParentName: Name, oldParentName: Name, name: Name): Name =
if (newParentName != oldParentName) {
Name(name.localName, newParentName, delimiter = name.delimiter)
} else {
name
}

/**
* Directly jumps to a given scope. Returns the previous scope. Do not forget to set the scope
* back to the old scope after performing the actions inside this scope.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend
import de.fraunhofer.aisec.cpg.graph.Node.Companion.EMPTY_NAME
import de.fraunhofer.aisec.cpg.graph.NodeBuilder.log
import de.fraunhofer.aisec.cpg.graph.declarations.*
import de.fraunhofer.aisec.cpg.graph.edges.scopes.ImportStyle
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewArrayExpression
import de.fraunhofer.aisec.cpg.graph.types.Type
Expand Down Expand Up @@ -449,15 +450,15 @@ fun MetadataProvider.newNamespaceDeclaration(
@JvmOverloads
fun MetadataProvider.newImportDeclaration(
import: Name,
wildcardImport: Boolean = false,
style: ImportStyle,
alias: Name? = null,
rawNode: Any? = null,
): ImportDeclaration {
val node = ImportDeclaration()
node.applyMetadata(this, "", rawNode)
node.import = import
node.alias = alias
node.wildcardImport = wildcardImport
node.style = style
if (alias != null) {
node.name = alias
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1360,6 +1360,12 @@ val Node.translationUnit: TranslationUnitDeclaration?
return firstParentOrNull<TranslationUnitDeclaration>()
}

/** Returns the [TranslationResult] where this node is located in. */
val Node.translationResult: TranslationResult?
get() {
return firstParentOrNull<TranslationResult>()
}

/** Returns the [Component] where this node is located in. */
val Node.component: Component?
get() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import de.fraunhofer.aisec.cpg.graph.*
import de.fraunhofer.aisec.cpg.graph.edges.Edge.Companion.propertyEqualsList
import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf
import de.fraunhofer.aisec.cpg.graph.edges.ast.astOptionalEdgeOf
import de.fraunhofer.aisec.cpg.graph.edges.flows.Invoke
import de.fraunhofer.aisec.cpg.graph.edges.flows.Invokes
import de.fraunhofer.aisec.cpg.graph.edges.unwrapping
import de.fraunhofer.aisec.cpg.graph.edges.unwrappingIncoming
Expand Down Expand Up @@ -73,10 +72,8 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder, EOGStart
Invokes<FunctionDeclaration>(this, CallExpression::invokeEdges, outgoing = false)

/** Virtual property for accessing [calledByEdges] without property edges. */
val calledBy by
unwrappingIncoming<CallExpression, FunctionDeclaration, FunctionDeclaration, Invoke>(
FunctionDeclaration::calledByEdges
)
val calledBy: MutableList<CallExpression> by
unwrappingIncoming(FunctionDeclaration::calledByEdges)

/** The list of return types. The default is an empty list. */
var returnTypes = listOf<Type>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ package de.fraunhofer.aisec.cpg.graph.declarations

import de.fraunhofer.aisec.cpg.PopulatedByPass
import de.fraunhofer.aisec.cpg.graph.Name
import de.fraunhofer.aisec.cpg.graph.edges.scopes.ImportStyle
import de.fraunhofer.aisec.cpg.graph.scopes.FileScope
import de.fraunhofer.aisec.cpg.graph.scopes.NameScope
import de.fraunhofer.aisec.cpg.graph.scopes.Scope
Expand All @@ -43,7 +44,7 @@ import org.neo4j.ogm.annotation.typeconversion.Convert
*
* ### Examples (Go)
*
* In Go, we usually import the package itself as a symbol.
* In Go, we usually import the package itself as a symbol (see [ImportStyle.IMPORT_NAMESPACE]).
*
* ```Go
* package p
Expand Down Expand Up @@ -101,7 +102,8 @@ import org.neo4j.ogm.annotation.typeconversion.Convert
* ```
*
* The imported symbol is then visible within the current [Scope] of the [ImportDeclaration]. In the
* example [name] and [import] is set to `std::string`, [wildcardImport] is `false`.
* example [name] and [import] is set to `std::string`, [style] is
* [ImportStyle.IMPORT_SINGLE_SYMBOL_FROM_NAMESPACE].
*
* Another possibility is to import a complete namespace, or to be more precise import all symbols
* of the specified namespace into the current scope.
Expand All @@ -118,7 +120,8 @@ import org.neo4j.ogm.annotation.typeconversion.Convert
* }
* ```
*
* In this example, the [name] and [import] is set to `std` and [wildcardImport] is `true`.
* In this example, the [name] and [import] is set to `std` and [style] is
* [ImportStyle.IMPORT_ALL_SYMBOLS_FROM_NAMESPACE].
*/
class ImportDeclaration : Declaration() {

Expand Down Expand Up @@ -149,11 +152,8 @@ class ImportDeclaration : Declaration() {
*/
var importURL: String? = null

/**
* Specifies that [name] is pointing to a [NameScope] and that all [Scope.symbols] of that name
* scope need to be imported into the scope this declaration lives in.
*/
var wildcardImport: Boolean = false
/** The import style. */
var style: ImportStyle = ImportStyle.IMPORT_SINGLE_SYMBOL_FROM_NAMESPACE

/**
* A list of symbols that this declaration imports. This will be populated by
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
*/
package de.fraunhofer.aisec.cpg.graph.declarations

import de.fraunhofer.aisec.cpg.graph.ArgumentHolder
import de.fraunhofer.aisec.cpg.graph.HasDefault
import de.fraunhofer.aisec.cpg.graph.edges.ast.astOptionalEdgeOf
import de.fraunhofer.aisec.cpg.graph.edges.unwrapping
Expand All @@ -33,7 +34,7 @@ import java.util.*
import org.neo4j.ogm.annotation.Relationship

/** A declaration of a function or nontype template parameter. */
class ParameterDeclaration : ValueDeclaration(), HasDefault<Expression?> {
class ParameterDeclaration : ValueDeclaration(), HasDefault<Expression?>, ArgumentHolder {
var isVariadic = false

@Relationship(value = "DEFAULT", direction = Relationship.Direction.OUTGOING)
Expand All @@ -57,4 +58,23 @@ class ParameterDeclaration : ValueDeclaration(), HasDefault<Expression?> {
}

override fun hashCode() = Objects.hash(super.hashCode(), isVariadic, defaultValue)

override fun addArgument(expression: Expression) {
if (defaultValue == null) {
defaultValue = expression
}
}

override fun replaceArgument(old: Expression, new: Expression): Boolean {
if (defaultValue == old) {
defaultValue = new
return true
}

return false
}

override fun hasArgument(expression: Expression): Boolean {
return defaultValue == expression
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ fun <
return edge.unwrap().IncomingDelegate<NodeType, IncomingType>()
}

/** See [UnwrappedEdgeSet.IncomingDelegate]. */
fun <
IncomingType : Node,
PropertyType : Node,
NodeType : Node,
EdgeType : Edge<PropertyType>,
> NodeType.unwrappingIncoming(
edgeProperty: KProperty1<NodeType, EdgeSet<PropertyType, EdgeType>>
): UnwrappedEdgeSet<PropertyType, EdgeType>.IncomingDelegate<NodeType, IncomingType> {
// Create an unwrapped container out of the edge property...
edgeProperty.isAccessible = true
val edge = edgeProperty.call(this)
return edge.unwrap().IncomingDelegate<NodeType, IncomingType>()
}

/** See [UnwrappedEdgeSet.Delegate]. */
fun <PropertyType : Node, NodeType : Node, EdgeType : Edge<PropertyType>> NodeType.unwrapping(
edgeProperty: KProperty1<NodeType, EdgeSet<PropertyType, EdgeType>>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@ class UnwrappedEdgeSet<NodeType : Node, EdgeType : Edge<NodeType>>(
}
}

/** See [UnwrappedEdgeList.IncomingDelegate], but as a [MutableSet] instead of [MutableList]. */
@Transient
inner class IncomingDelegate<ThisType : Node, IncomingType>() {
operator fun getValue(thisRef: ThisType, property: KProperty<*>): MutableSet<IncomingType> {
@Suppress("UNCHECKED_CAST")
return this@UnwrappedEdgeSet as MutableSet<IncomingType>
}

operator fun setValue(thisRef: ThisType, property: KProperty<*>, value: Set<IncomingType>) {
@Suppress("UNCHECKED_CAST") [email protected](value as Collection<NodeType>)
}
}

operator fun <ThisType : Node> provideDelegate(
thisRef: ThisType,
prop: KProperty<*>,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright (c) 2025, 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.graph.edges.scopes

import de.fraunhofer.aisec.cpg.graph.Node
import de.fraunhofer.aisec.cpg.graph.declarations.ImportDeclaration
import de.fraunhofer.aisec.cpg.graph.edges.Edge
import de.fraunhofer.aisec.cpg.graph.edges.collections.EdgeSet
import de.fraunhofer.aisec.cpg.graph.edges.collections.MirroredEdgeCollection
import de.fraunhofer.aisec.cpg.graph.scopes.NamespaceScope
import de.fraunhofer.aisec.cpg.graph.scopes.Scope
import kotlin.reflect.KProperty

/**
* The style of the import. This can be used to distinguish between different import modes, such as
* importing a single symbol from a namespace, importing a whole namespace or importing all symbols
* from a namespace.
*/
enum class ImportStyle {
/**
* Imports a single symbol from the target namespace. The current scope will contain a symbol
* with the same name as the imported symbol.
*
* Note: Some languages support importing more than one symbol at a time. In this case, the list
* is split into multiple [ImportDeclaration] nodes (and [Import] edges).
*/
IMPORT_SINGLE_SYMBOL_FROM_NAMESPACE,

/**
* Imports the target namespace as a single symbol. The current scope will contain a symbol with
* the same name as the imported namespace.
*/
IMPORT_NAMESPACE,

/**
* Imports all symbols from the target namespace. The current scope will contain one new symbol
* for each symbol in the namespace.
*
* This is also known as a "wildcard" import.
*/
IMPORT_ALL_SYMBOLS_FROM_NAMESPACE,
}

/**
* This edge represents the import of a [NamespaceScope] into another [Scope]. The [style] of import
* (e.g., whether only a certain symbol or the whole namespace is imported) is determined by the
* [declaration].
*/
class Import(start: Scope, end: NamespaceScope, var declaration: ImportDeclaration? = null) :
Edge<NamespaceScope>(start, end) {

override var labels = setOf("SCOPE_IMPORT")

val style: ImportStyle?
get() = declaration?.style

init {
declaration?.import?.let { this.name = it.localName }
}
}

/** A container to manage [Import] edges. */
class Imports(
thisRef: Node,
override var mirrorProperty: KProperty<MutableCollection<Import>>,
outgoing: Boolean = true,
) :
EdgeSet<NamespaceScope, Import>(
thisRef,
init = { start, end -> Import(start as Scope, end) },
outgoing = outgoing,
),
MirroredEdgeCollection<NamespaceScope, Import>
Loading
Loading