Skip to content

Commit

Permalink
Node Builder V2
Browse files Browse the repository at this point in the history
Work in progress
  • Loading branch information
oxisto committed Nov 29, 2024
1 parent 897955d commit f88a81a
Show file tree
Hide file tree
Showing 6 changed files with 398 additions and 157 deletions.
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
@@ -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
153 changes: 2 additions & 151 deletions cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,11 @@
*/
package de.fraunhofer.aisec.cpg.graph

import de.fraunhofer.aisec.cpg.TranslationContext
import de.fraunhofer.aisec.cpg.frontends.*
import de.fraunhofer.aisec.cpg.graph.Node.Companion.EMPTY_NAME
import de.fraunhofer.aisec.cpg.graph.NodeBuilder.LOGGER
import de.fraunhofer.aisec.cpg.graph.NodeBuilder.log
import de.fraunhofer.aisec.cpg.graph.scopes.Scope
import de.fraunhofer.aisec.cpg.graph.statements.expressions.*
import de.fraunhofer.aisec.cpg.helpers.getCodeOfSubregion
import de.fraunhofer.aisec.cpg.passes.inference.IsImplicitProvider
import de.fraunhofer.aisec.cpg.passes.inference.IsInferredProvider
import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation
import de.fraunhofer.aisec.cpg.sarif.Region
import java.net.URI
Expand All @@ -48,116 +43,6 @@ object NodeBuilder {
}
}

/**
* 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,
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."
)

// 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
}

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
)
}

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

/**
* Generates a [Name] object from the given [name]. If [localNameOnly] is set, only the localName is
* used, otherwise the [namespace] is added to generate a fqn if the [name] is not a fqn anyway.
Expand Down Expand Up @@ -233,20 +118,6 @@ fun NamespaceProvider.fqn(localName: String): Name {
return this.namespace.fqn(localName)
}

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

/**
* A small helper function that can be used in building a [Node] with [Node.isImplicit] set to true.
* In this case, no "rawNode" exists that can be used for the node builder. But, in order to
Expand Down Expand Up @@ -299,8 +170,8 @@ fun <T : Node, AstNode> T.codeAndLocationFromOtherRawNode(rawNode: AstNode?): T
* are between the child nodes.
*
* @param parentNode Used to extract the code for this node.
* @param newLineType The char(s) used to describe a new line, usually either "\n" or "\r\n". This
* is needed because the location block spanning the children usually comprises more than one
* @param lineBreakSequence The char(s) used to describe a new line, usually either "\n" or "\r\n".
* This is needed because the location block spanning the children usually comprises more than one
* line.
*/
context(CodeAndLocationProvider<AstNode>)
Expand Down Expand Up @@ -369,23 +240,3 @@ fun <T : Node, AstNode> T.codeAndLocationFromChildren(

return this
}

/**
* 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.
*/
private 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)
}
Loading

0 comments on commit f88a81a

Please sign in to comment.