Skip to content

Commit 1de14ed

Browse files
committed
Instead of caching importedSymbols statically once (or twice), instead we resolve the import declaration back to the namespace (declaration) and update the symbol on-chance.
Adding an 'import' edge
1 parent 541dea1 commit 1de14ed

File tree

26 files changed

+500
-163
lines changed

26 files changed

+500
-163
lines changed

Diff for: cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt

+33-33
Original file line numberDiff line numberDiff line change
@@ -370,19 +370,19 @@ class ScopeManager : ScopeProvider {
370370
is ProblemDeclaration,
371371
is IncludeDeclaration -> {
372372
// directly add problems and includes to the global scope
373-
this.globalScope?.addDeclaration(declaration, addToAST)
373+
this.globalScope?.addDeclaration(declaration, addToAST, this)
374374
}
375375
is ValueDeclaration -> {
376376
val scope = this.firstScopeIsInstanceOrNull<ValueDeclarationScope>()
377-
scope?.addDeclaration(declaration, addToAST)
377+
scope?.addDeclaration(declaration, addToAST, this)
378378
}
379379
is ImportDeclaration,
380380
is EnumDeclaration,
381381
is RecordDeclaration,
382382
is NamespaceDeclaration,
383383
is TemplateDeclaration -> {
384384
val scope = this.firstScopeIsInstanceOrNull<StructureDeclarationScope>()
385-
scope?.addDeclaration(declaration, addToAST)
385+
scope?.addDeclaration(declaration, addToAST, this)
386386
}
387387
}
388388
}
@@ -571,52 +571,52 @@ class ScopeManager : ScopeProvider {
571571
* the given [Name]. It also does this recursively.
572572
*/
573573
fun resolveParentAlias(name: Name, scope: Scope?): Name {
574-
var parentName = name.parent ?: return name
575-
parentName = resolveParentAlias(parentName, scope)
574+
if (name.parent == null) {
575+
return name
576+
}
576577

577-
// Build a new name based on the eventual resolved parent alias
578-
var newName =
579-
if (parentName != name.parent) {
580-
Name(name.localName, parentName, delimiter = name.delimiter)
581-
} else {
582-
name
583-
}
584-
var decl =
585-
scope?.lookupSymbol(parentName.localName)?.singleOrNull {
586-
it is NamespaceDeclaration || it is RecordDeclaration
587-
}
588-
if (decl != null && parentName != decl.name) {
578+
val parentName = resolveParentAlias(name.parent, scope)
579+
580+
// Look for an alias in the current scope. This is also resolves partial FQNs to their full
581+
// FQN
582+
var newScope =
583+
scope
584+
?.lookupSymbol(parentName.localName) {
585+
it is NamespaceDeclaration || it is RecordDeclaration
586+
}
587+
?.map { nameScopeMap[it.name] }
588+
?.toSet()
589+
?.singleOrNull()
590+
if (newScope != null) {
589591
// This is probably an already resolved alias so, we take this one
590-
return Name(newName.localName, decl.name, delimiter = newName.delimiter)
592+
return adjustNameIfNecessary(newScope.name, parentName, name)
591593
}
592594

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

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

617610
return newName
618611
}
619612

613+
private fun adjustNameIfNecessary(newParentName: Name, oldParentName: Name, name: Name): Name =
614+
if (newParentName != oldParentName) {
615+
Name(name.localName, newParentName, delimiter = name.delimiter)
616+
} else {
617+
name
618+
}
619+
620620
/**
621621
* Directly jumps to a given scope. Returns the previous scope. Do not forget to set the scope
622622
* back to the old scope after performing the actions inside this scope.

Diff for: cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt

+3-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend
3030
import de.fraunhofer.aisec.cpg.graph.Node.Companion.EMPTY_NAME
3131
import de.fraunhofer.aisec.cpg.graph.NodeBuilder.log
3232
import de.fraunhofer.aisec.cpg.graph.declarations.*
33+
import de.fraunhofer.aisec.cpg.graph.edges.scopes.ImportStyle
3334
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression
3435
import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewArrayExpression
3536
import de.fraunhofer.aisec.cpg.graph.types.Type
@@ -449,15 +450,15 @@ fun MetadataProvider.newNamespaceDeclaration(
449450
@JvmOverloads
450451
fun MetadataProvider.newImportDeclaration(
451452
import: Name,
452-
wildcardImport: Boolean = false,
453+
style: ImportStyle,
453454
alias: Name? = null,
454455
rawNode: Any? = null,
455456
): ImportDeclaration {
456457
val node = ImportDeclaration()
457458
node.applyMetadata(this, "", rawNode)
458459
node.import = import
459460
node.alias = alias
460-
node.wildcardImport = wildcardImport
461+
node.style = style
461462
if (alias != null) {
462463
node.name = alias
463464
} else {

Diff for: cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt

+6
Original file line numberDiff line numberDiff line change
@@ -1360,6 +1360,12 @@ val Node.translationUnit: TranslationUnitDeclaration?
13601360
return firstParentOrNull<TranslationUnitDeclaration>()
13611361
}
13621362

1363+
/** Returns the [TranslationResult] where this node is located in. */
1364+
val Node.translationResult: TranslationResult?
1365+
get() {
1366+
return firstParentOrNull<TranslationResult>()
1367+
}
1368+
13631369
/** Returns the [Component] where this node is located in. */
13641370
val Node.component: Component?
13651371
get() {

Diff for: cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt

+2-5
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import de.fraunhofer.aisec.cpg.graph.*
2929
import de.fraunhofer.aisec.cpg.graph.edges.Edge.Companion.propertyEqualsList
3030
import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf
3131
import de.fraunhofer.aisec.cpg.graph.edges.ast.astOptionalEdgeOf
32-
import de.fraunhofer.aisec.cpg.graph.edges.flows.Invoke
3332
import de.fraunhofer.aisec.cpg.graph.edges.flows.Invokes
3433
import de.fraunhofer.aisec.cpg.graph.edges.unwrapping
3534
import de.fraunhofer.aisec.cpg.graph.edges.unwrappingIncoming
@@ -73,10 +72,8 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder, EOGStart
7372
Invokes<FunctionDeclaration>(this, CallExpression::invokeEdges, outgoing = false)
7473

7574
/** Virtual property for accessing [calledByEdges] without property edges. */
76-
val calledBy by
77-
unwrappingIncoming<CallExpression, FunctionDeclaration, FunctionDeclaration, Invoke>(
78-
FunctionDeclaration::calledByEdges
79-
)
75+
val calledBy: MutableList<CallExpression> by
76+
unwrappingIncoming(FunctionDeclaration::calledByEdges)
8077

8178
/** The list of return types. The default is an empty list. */
8279
var returnTypes = listOf<Type>()

Diff for: cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ImportDeclaration.kt

+8-8
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ package de.fraunhofer.aisec.cpg.graph.declarations
2727

2828
import de.fraunhofer.aisec.cpg.PopulatedByPass
2929
import de.fraunhofer.aisec.cpg.graph.Name
30+
import de.fraunhofer.aisec.cpg.graph.edges.scopes.ImportStyle
3031
import de.fraunhofer.aisec.cpg.graph.scopes.FileScope
3132
import de.fraunhofer.aisec.cpg.graph.scopes.NameScope
3233
import de.fraunhofer.aisec.cpg.graph.scopes.Scope
@@ -43,7 +44,7 @@ import org.neo4j.ogm.annotation.typeconversion.Convert
4344
*
4445
* ### Examples (Go)
4546
*
46-
* In Go, we usually import the package itself as a symbol.
47+
* In Go, we usually import the package itself as a symbol (see [ImportStyle.IMPORT_NAMESPACE]).
4748
*
4849
* ```Go
4950
* package p
@@ -101,7 +102,8 @@ import org.neo4j.ogm.annotation.typeconversion.Convert
101102
* ```
102103
*
103104
* The imported symbol is then visible within the current [Scope] of the [ImportDeclaration]. In the
104-
* example [name] and [import] is set to `std::string`, [wildcardImport] is `false`.
105+
* example [name] and [import] is set to `std::string`, [style] is
106+
* [ImportStyle.IMPORT_SINGLE_SYMBOL_FROM_NAMESPACE].
105107
*
106108
* Another possibility is to import a complete namespace, or to be more precise import all symbols
107109
* of the specified namespace into the current scope.
@@ -118,7 +120,8 @@ import org.neo4j.ogm.annotation.typeconversion.Convert
118120
* }
119121
* ```
120122
*
121-
* In this example, the [name] and [import] is set to `std` and [wildcardImport] is `true`.
123+
* In this example, the [name] and [import] is set to `std` and [style] is
124+
* [ImportStyle.IMPORT_ALL_SYMBOLS_FROM_NAMESPACE].
122125
*/
123126
class ImportDeclaration : Declaration() {
124127

@@ -149,11 +152,8 @@ class ImportDeclaration : Declaration() {
149152
*/
150153
var importURL: String? = null
151154

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

158158
/**
159159
* A list of symbols that this declaration imports. This will be populated by

Diff for: cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ParameterDeclaration.kt

+21-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
*/
2626
package de.fraunhofer.aisec.cpg.graph.declarations
2727

28+
import de.fraunhofer.aisec.cpg.graph.ArgumentHolder
2829
import de.fraunhofer.aisec.cpg.graph.HasDefault
2930
import de.fraunhofer.aisec.cpg.graph.edges.ast.astOptionalEdgeOf
3031
import de.fraunhofer.aisec.cpg.graph.edges.unwrapping
@@ -33,7 +34,7 @@ import java.util.*
3334
import org.neo4j.ogm.annotation.Relationship
3435

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

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

5960
override fun hashCode() = Objects.hash(super.hashCode(), isVariadic, defaultValue)
61+
62+
override fun addArgument(expression: Expression) {
63+
if (defaultValue == null) {
64+
defaultValue = expression
65+
}
66+
}
67+
68+
override fun replaceArgument(old: Expression, new: Expression): Boolean {
69+
if (defaultValue == old) {
70+
defaultValue = new
71+
return true
72+
}
73+
74+
return false
75+
}
76+
77+
override fun hasArgument(expression: Expression): Boolean {
78+
return defaultValue == expression
79+
}
6080
}

Diff for: cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/Extensions.kt

+15
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,21 @@ fun <
7171
return edge.unwrap().IncomingDelegate<NodeType, IncomingType>()
7272
}
7373

74+
/** See [UnwrappedEdgeSet.IncomingDelegate]. */
75+
fun <
76+
IncomingType : Node,
77+
PropertyType : Node,
78+
NodeType : Node,
79+
EdgeType : Edge<PropertyType>,
80+
> NodeType.unwrappingIncoming(
81+
edgeProperty: KProperty1<NodeType, EdgeSet<PropertyType, EdgeType>>
82+
): UnwrappedEdgeSet<PropertyType, EdgeType>.IncomingDelegate<NodeType, IncomingType> {
83+
// Create an unwrapped container out of the edge property...
84+
edgeProperty.isAccessible = true
85+
val edge = edgeProperty.call(this)
86+
return edge.unwrap().IncomingDelegate<NodeType, IncomingType>()
87+
}
88+
7489
/** See [UnwrappedEdgeSet.Delegate]. */
7590
fun <PropertyType : Node, NodeType : Node, EdgeType : Edge<PropertyType>> NodeType.unwrapping(
7691
edgeProperty: KProperty1<NodeType, EdgeSet<PropertyType, EdgeType>>

Diff for: cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/UnwrappedEdgeSet.kt

+13
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,19 @@ class UnwrappedEdgeSet<NodeType : Node, EdgeType : Edge<NodeType>>(
5959
}
6060
}
6161

62+
/** See [UnwrappedEdgeList.IncomingDelegate], but as a [MutableSet] instead of [MutableList]. */
63+
@Transient
64+
inner class IncomingDelegate<ThisType : Node, IncomingType>() {
65+
operator fun getValue(thisRef: ThisType, property: KProperty<*>): MutableSet<IncomingType> {
66+
@Suppress("UNCHECKED_CAST")
67+
return this@UnwrappedEdgeSet as MutableSet<IncomingType>
68+
}
69+
70+
operator fun setValue(thisRef: ThisType, property: KProperty<*>, value: Set<IncomingType>) {
71+
@Suppress("UNCHECKED_CAST") this@UnwrappedEdgeSet.resetTo(value as Collection<NodeType>)
72+
}
73+
}
74+
6275
operator fun <ThisType : Node> provideDelegate(
6376
thisRef: ThisType,
6477
prop: KProperty<*>,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright (c) 2025, Fraunhofer AISEC. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* $$$$$$\ $$$$$$$\ $$$$$$\
17+
* $$ __$$\ $$ __$$\ $$ __$$\
18+
* $$ / \__|$$ | $$ |$$ / \__|
19+
* $$ | $$$$$$$ |$$ |$$$$\
20+
* $$ | $$ ____/ $$ |\_$$ |
21+
* $$ | $$\ $$ | $$ | $$ |
22+
* \$$$$$ |$$ | \$$$$$ |
23+
* \______/ \__| \______/
24+
*
25+
*/
26+
package de.fraunhofer.aisec.cpg.graph.edges.scopes
27+
28+
import de.fraunhofer.aisec.cpg.graph.Node
29+
import de.fraunhofer.aisec.cpg.graph.declarations.ImportDeclaration
30+
import de.fraunhofer.aisec.cpg.graph.edges.Edge
31+
import de.fraunhofer.aisec.cpg.graph.edges.collections.EdgeSet
32+
import de.fraunhofer.aisec.cpg.graph.edges.collections.MirroredEdgeCollection
33+
import de.fraunhofer.aisec.cpg.graph.scopes.NamespaceScope
34+
import de.fraunhofer.aisec.cpg.graph.scopes.Scope
35+
import kotlin.reflect.KProperty
36+
37+
/**
38+
* The style of the import. This can be used to distinguish between different import modes, such as
39+
* importing a single symbol from a namespace, importing a whole namespace or importing all symbols
40+
* from a namespace.
41+
*/
42+
enum class ImportStyle {
43+
/**
44+
* Imports a single symbol from the target namespace. The current scope will contain a symbol
45+
* with the same name as the imported symbol.
46+
*
47+
* Note: Some languages support importing more than one symbol at a time. In this case, the list
48+
* is split into multiple [ImportDeclaration] nodes (and [Import] edges).
49+
*/
50+
IMPORT_SINGLE_SYMBOL_FROM_NAMESPACE,
51+
52+
/**
53+
* Imports the target namespace as a single symbol. The current scope will contain a symbol with
54+
* the same name as the imported namespace.
55+
*/
56+
IMPORT_NAMESPACE,
57+
58+
/**
59+
* Imports all symbols from the target namespace. The current scope will contain one new symbol
60+
* for each symbol in the namespace.
61+
*
62+
* This is also known as a "wildcard" import.
63+
*/
64+
IMPORT_ALL_SYMBOLS_FROM_NAMESPACE,
65+
}
66+
67+
/**
68+
* This edge represents the import of a [NamespaceScope] into another [Scope]. The [style] of import
69+
* (e.g., whether only a certain symbol or the whole namespace is imported) is determined by the
70+
* [declaration].
71+
*/
72+
class Import(start: Scope, end: NamespaceScope, var declaration: ImportDeclaration? = null) :
73+
Edge<NamespaceScope>(start, end) {
74+
75+
override var labels = setOf("SCOPE_IMPORT")
76+
77+
val style: ImportStyle?
78+
get() = declaration?.style
79+
80+
init {
81+
declaration?.import?.let { this.name = it.localName }
82+
}
83+
}
84+
85+
/** A container to manage [Import] edges. */
86+
class Imports(
87+
thisRef: Node,
88+
override var mirrorProperty: KProperty<MutableCollection<Import>>,
89+
outgoing: Boolean = true,
90+
) :
91+
EdgeSet<NamespaceScope, Import>(
92+
thisRef,
93+
init = { start, end -> Import(start as Scope, end) },
94+
outgoing = outgoing,
95+
),
96+
MirroredEdgeCollection<NamespaceScope, Import>

0 commit comments

Comments
 (0)