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

Node Builder V2 #1874

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,11 @@ class ScopeManager : ScopeProvider {
return leaveScope
}

/** Declares this [declaration] as a symbol in the [currentScope]. */
fun declare(declaration: Declaration) {
currentScope?.addSymbol(declaration.symbol, declaration)
}

/**
* This function MUST be called when a language frontend first handles a [Declaration]. It adds
* a declaration to the scope manager, taking into account the currently active scope.
Expand All @@ -380,7 +385,7 @@ class ScopeManager : ScopeProvider {
fun addDeclaration(declaration: Declaration?, addToAST: Boolean = true) {
if (declaration != null) {
// New stuff here
currentScope?.addSymbol(declaration.symbol, declaration)
declare(declaration)
}

// Legacy stuff here
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ import org.slf4j.LoggerFactory
* after having processed the files, i.e., it won't be available in passes.
*
* More information can be found in the
* [GitHub wiki page](https://github.com/Fraunhofer-AISEC/cpg/wiki/Language-Frontends).
* [Documentation](https://fraunhofer-aisec.github.io/cpg/CPG/impl/language/#languagefrontend).
*
* @param AstNode This type parameter should be set to a class that is common to all AST nodes in
* the language.
* @param TypeNode This type parameter should be set to a class that is common to all type nodes in
* the language.
*/
abstract class LanguageFrontend<AstNode, TypeNode>(
/** The language this frontend works for. */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/*
* Copyright (c) 2024, 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

import de.fraunhofer.aisec.cpg.TranslationContext
import de.fraunhofer.aisec.cpg.frontends.Handler
import de.fraunhofer.aisec.cpg.frontends.Language
import de.fraunhofer.aisec.cpg.frontends.TranslationException
import de.fraunhofer.aisec.cpg.graph.Node.Companion.EMPTY_NAME
import de.fraunhofer.aisec.cpg.graph.NodeBuilder.LOGGER
import de.fraunhofer.aisec.cpg.graph.scopes.Scope
import de.fraunhofer.aisec.cpg.passes.inference.IsImplicitProvider
import de.fraunhofer.aisec.cpg.passes.inference.IsInferredProvider
import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation

/**
* This interfaces serves as base for different entities that provide some kind of meta-data for a
* [Node], such as its language, code or location.
*/
interface MetadataProvider

/**
* A simple interface that everything, that supplies a language, should implement. Examples include
* each [Node], but also transformation steps, such as [Handler].
*/
interface LanguageProvider : MetadataProvider {
val language: Language<*>?
}

/**
* This interface denotes that the class is able to provide source code and location information for
* a specific node.
*/
interface CodeAndLocationProvider<in AstNode> : MetadataProvider {
/** Returns the raw code of the supplied [AstNode]. */
fun codeOf(astNode: AstNode): String?

/** Returns the [PhysicalLocation] of the supplied [AstNode]. */
fun locationOf(astNode: AstNode): PhysicalLocation?
}

/**
* This interfaces serves as a base for entities that provide the current scope / name prefix. This
* is reserved for future use.
*/
interface ScopeProvider : MetadataProvider {
val scope: Scope?
}

/**
* This interface denotes that the class is able to provide the current namespace. The
* [applyMetadata] will use this information to set the parent of a [Name].
*/
interface NamespaceProvider : MetadataProvider {
val namespace: Name?
}

/**
* Applies various metadata on this [Node], based on the kind of provider in [provider]. This can
* include:
* - Setting [Node.code] and [Node.location], if a [CodeAndLocationProvider] is given
* - Setting [Node.location], if a [LanguageProvider] is given
* - Setting [Node.scope]. if a [ScopeProvider] is given
* - Setting [Node.isInferred], if an [IsInferredProvider] is given
*
* Note, that one provider can implement multiple provider interfaces.
*/
fun Node.applyMetadata(
provider: MetadataProvider?,
name: CharSequence? = EMPTY_NAME,
rawNode: Any? = null,

Check warning on line 94 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/MetadataProvider.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/MetadataProvider.kt#L93-L94

Added lines #L93 - L94 were not covered by tests
localNameOnly: Boolean = false,
defaultNamespace: Name? = null,
) {
// We definitely need a context provider, because otherwise we cannot set the context and the
// node cannot access necessary information about the current translation context it lives in.
this.ctx =
(provider as? ContextProvider)?.ctx
?: throw TranslationException(
"Trying to create a node without a ContextProvider. This will fail."

Check warning on line 103 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/MetadataProvider.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/MetadataProvider.kt#L102-L103

Added lines #L102 - L103 were not covered by tests
)

// We try to set the code and especially the location as soon as possible because the hashCode
// implementation of the Node class relies on it. Otherwise, we could have a problem that the
// location is not yet set, but the node is put into a hashmap. In this case the hashCode is
// calculated based on an empty location and if we would later set the location, we would have a
// mismatch. Each language frontend and also each handler implements CodeAndLocationProvider, so
// calling a node builder from these should already set the location.
if (provider is CodeAndLocationProvider<*> && rawNode != null) {
@Suppress("UNCHECKED_CAST")
setCodeAndLocation(provider as CodeAndLocationProvider<Any>, rawNode)
}

if (provider is LanguageProvider) {
this.language = provider.language
}

if (provider is IsInferredProvider) {
this.isInferred = provider.isInferred
}

if (provider is IsImplicitProvider) {
this.isImplicit = provider.isImplicit

Check warning on line 126 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/MetadataProvider.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/MetadataProvider.kt#L126

Added line #L126 was not covered by tests
}

if (provider is ScopeProvider) {
this.scope = provider.scope
} else {
LOGGER.warn(
"No scope provider was provided when creating the node {}. This might be an error",
name

Check warning on line 134 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/MetadataProvider.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/MetadataProvider.kt#L132-L134

Added lines #L132 - L134 were not covered by tests
)
}

if (name != null) {
val namespace =
if (provider is NamespaceProvider) {
provider.namespace ?: defaultNamespace
} else {
defaultNamespace
}
this.name = this.newName(name, localNameOnly, namespace)
}
}

/**
* This internal function sets the code and location according to the [CodeAndLocationProvider].
* This also performs some checks, e.g., if the config disabled setting the code.
*/
internal fun <AstNode> Node.setCodeAndLocation(
provider: CodeAndLocationProvider<AstNode>,
rawNode: AstNode
) {
if (this.ctx?.config?.codeInNodes == true) {
// only set code, if it's not already set or empty
val code = provider.codeOf(rawNode)
if (code != null) {
this.code = code
} else {
LOGGER.warn("Unexpected: No code for node {}", rawNode)
}
}
this.location = provider.locationOf(rawNode)
}

interface ContextProvider : MetadataProvider {
val ctx: TranslationContext?
}

/**
* This [MetadataProvider] makes sure that we can type our node builder functions correctly. For
* language frontend and handlers, [T] should be set to the type of the raw node. For passes, [T]
* should be set to [Nothing], since we do not have raw nodes there.
*
* Note: This does not work yet to 100 % satisfaction and is therefore not yet activated in the
* builders.
*/
interface RawNodeTypeProvider<T> : MetadataProvider
Loading
Loading