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

Make translation context available everywhere #2059

Draft
wants to merge 3 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 @@ -881,9 +881,8 @@ class ScopeManager : ScopeProvider {
): TranslationUnitDeclaration? {
// TODO(oxisto): This workaround is needed because it seems that not all types have a proper
// context :(. In this case we need to fall back to the global scope's astNode, which can
// be
// error-prone in a multi-language scenario.
return if (source.ctx == null) {
// be error-prone in a multi-language scenario.
return if (!source.isInitialized) {
globalScope?.astNode as? TranslationUnitDeclaration
} else {
source.language.translationUnitForInference<TypeToInfer>(source)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,7 @@ class TranslationResult(
val isCancelled: Boolean
get() = translationManager.isCancelled()

override var ctx: TranslationContext? = null
get() {
return finalCtx
}
override var ctx: TranslationContext = finalCtx

/**
* Checks if only a single software component has been analyzed and returns its translation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ abstract class Handler<ResultNode : Node?, HandlerNode, L : LanguageFrontend<in
CodeAndLocationProvider<HandlerNode> by frontend,
ScopeProvider by frontend,
NamespaceProvider by frontend,
ContextProvider by frontend,
RawNodeTypeProvider<HandlerNode> {
protected val map = HashMap<Class<out HandlerNode>, HandlerInterface<ResultNode, HandlerNode>>()
private val typeOfT: Class<*>?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ abstract class Language<T : LanguageFrontend<*, *>> : Node() {
null,
source,
false,
source.ctx!!,
source.ctx,
null,
needsExactMatch = true,
)
Expand Down Expand Up @@ -416,7 +416,7 @@ abstract class Language<T : LanguageFrontend<*, *>> : Node() {
* @param source the source that was responsible for the inference
*/
fun <TypeToInfer : Node> translationUnitForInference(source: Node): TranslationUnitDeclaration {
val tu = source.ctx?.currentComponent?.translationUnits?.firstOrNull()
val tu = source.ctx.currentComponent?.translationUnits?.firstOrNull()
if (tu == null) {
throw TranslationException(
"No translation unit found that should be used for inference"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ abstract class LanguageFrontend<AstNode, TypeNode>(

/**
* The translation context, which contains all necessary managers used in this frontend parsing
* process. Note, that different contexts could passed to frontends, e.g., in parallel parsing
* to supply different managers to different frontends.
* process. Note, that different contexts could be passed to frontends, e.g., in parallel
* parsing to supply different managers to different frontends.
*/
final override var ctx: TranslationContext,
) :
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ open class Component : Node() {
@DoNotPersist
val topLevel: File?
get() {
return ctx?.config?.topLevels?.get(this.name.localName)
return ctx.config.topLevels[this.name.localName]
}

/**
Expand Down
16 changes: 13 additions & 3 deletions cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,18 @@ abstract class Node :
* to managers such as the [TypeManager] instance which is responsible for this particular node.
* All managers are bundled in [TranslationContext]. It is set in [Node.applyMetadata] when a
* [ContextProvider] is provided.
*
* Note: We EXPECT that this property is set before any other property is accessed and therefore
* this is a lateinit variable. In the past this was a nullable variable, and we had many null
* checks in the code, which then threw exceptions somewhere late in the analysis. Now, the
* program will abort sooner if the context is not set correctly.
*
* In the future, we might re-structure our node system and make this a constructor parameter.
*/
@get:JsonIgnore @Transient override var ctx: TranslationContext? = null
@get:JsonIgnore @Transient override lateinit var ctx: TranslationContext

val isInitialized: Boolean
get() = this::ctx.isInitialized

/** This property holds the full name using our new [Name] class. */
@Convert(NameConverter::class) override var name: Name = Name(EMPTY_NAME)
Expand Down Expand Up @@ -376,8 +386,8 @@ abstract class Node :
*/
inline fun <reified T : Node> T.applyWithScope(block: T.() -> Unit): T {
return this.apply {
ctx?.scopeManager?.enterScope(this)
ctx.scopeManager.enterScope(this)
block()
ctx?.scopeManager?.leaveScope(this)
ctx.scopeManager.leaveScope(this)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ 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 {
interface LanguageProvider : ContextProvider {
val language: Language<*>
}

Expand Down Expand Up @@ -234,7 +234,7 @@ fun NamespaceProvider.fqn(localName: String): Name {
}

interface ContextProvider : MetadataProvider {
val ctx: TranslationContext?
val ctx: TranslationContext
}

/**
Expand Down Expand Up @@ -378,7 +378,7 @@ private fun <AstNode> Node.setCodeAndLocation(
provider: CodeAndLocationProvider<AstNode>,
rawNode: AstNode,
) {
if (this.ctx?.config?.codeInNodes == true) {
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,23 +106,17 @@ fun LanguageProvider.objectType(
return builtIn
}

// Otherwise, we need to create a new type and register it at the type manager
val c =
(this as? ContextProvider)?.ctx
?: throw TranslationException(
"Could not create type: translation context not available"
)

// Otherwise, we either need to create the type because of the generics or because we do not
// know the type yet.
var type = ObjectType(name, generics, false, language)

// Apply our usual metadata, such as scope, code, location, if we have any. Make sure only
// to refer by the local name because we will treat types as sort of references when
// creating them and resolve them later.
type.applyMetadata(this, name, rawNode = rawNode, doNotPrependNamespace = true)

// Piping it through register type will ensure that we know the type and can resolve it later
return c.typeManager.registerType(type)
return this.ctx.typeManager.registerType(type)
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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.types

import de.fraunhofer.aisec.cpg.frontends.Language
import de.fraunhofer.aisec.cpg.graph.LanguageProvider
import de.fraunhofer.aisec.cpg.graph.applyMetadata

/** Creates a new [FunctionType]. */
fun LanguageProvider.functionType(
typeName: String = "",
parameters: List<Type> = listOf(),
returnTypes: List<Type> = listOf(),
language: Language<*>,
rawNode: Any? = null,
): FunctionType {
var type = FunctionType(typeName, parameters, returnTypes, language)
type.applyMetadata(this, typeName, rawNode = rawNode, doNotPrependNamespace = true)

return this.ctx.typeManager.registerType(type)
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
package de.fraunhofer.aisec.cpg.graph.types

import de.fraunhofer.aisec.cpg.frontends.Language
import de.fraunhofer.aisec.cpg.frontends.TranslationException
import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
import de.fraunhofer.aisec.cpg.graph.unknownType

Expand All @@ -38,7 +37,7 @@ import de.fraunhofer.aisec.cpg.graph.unknownType
*/
class FunctionType
@JvmOverloads
constructor(
internal constructor(
typeName: String = "",
var parameters: List<Type> = listOf(),
var returnTypes: List<Type> = listOf(),
Expand Down Expand Up @@ -73,8 +72,7 @@ constructor(
func.language,
)

val c = func.ctx ?: throw TranslationException("context not available")
return c.typeManager.registerType(type)
return func.ctx.typeManager.registerType(type)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ class DFGFunctionSummaries {
val language = functionDecl.language
val languageName = language.javaClass.name
val methodName = functionDecl.name
val typeManager = functionDecl.ctx?.typeManager ?: return null
val typeManager = functionDecl.ctx.typeManager

// The language and the method name have to match. If a signature is specified, it also has
// to match to the one of the FunctionDeclaration, null indicates that we accept everything.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,32 +88,25 @@ class DFGFunctionSummariesTest {
// We need three types with a type hierarchy.
val objectType = t("test.Object")
val listType = t("test.List")
ctx?.let {
val recordDecl =
startInference(it)?.inferRecordDeclaration(listType)
listType.recordDeclaration = recordDecl
recordDecl?.addSuperClass(objectType)
listType.superTypes.add(objectType)
}
var recordDecl =
startInference(ctx)?.inferRecordDeclaration(listType)
listType.recordDeclaration = recordDecl
recordDecl?.addSuperClass(objectType)
listType.superTypes.add(objectType)

val specialListType = t("test.SpecialList")
ctx?.let {
val recordDecl =
startInference(it)?.inferRecordDeclaration(specialListType)
specialListType.recordDeclaration = recordDecl
recordDecl?.addSuperClass(listType)
specialListType.superTypes.add(listType)
}
recordDecl =
startInference(ctx)?.inferRecordDeclaration(specialListType)
specialListType.recordDeclaration = recordDecl
recordDecl?.addSuperClass(listType)
specialListType.superTypes.add(listType)

val verySpecialListType = t("test.VerySpecialList")
ctx?.let {
val recordDecl =
startInference(it)
?.inferRecordDeclaration(verySpecialListType)
verySpecialListType.recordDeclaration = recordDecl
recordDecl?.addSuperClass(specialListType)
verySpecialListType.superTypes.add(listType)
}
recordDecl =
startInference(ctx)?.inferRecordDeclaration(verySpecialListType)
verySpecialListType.recordDeclaration = recordDecl
recordDecl?.addSuperClass(specialListType)
verySpecialListType.superTypes.add(listType)
}

function("main", t("int")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,50 +25,53 @@
*/
package de.fraunhofer.aisec.cpg.graph

import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend
import de.fraunhofer.aisec.cpg.graph.builder.plus
import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration
import de.fraunhofer.aisec.cpg.graph.edges.flows.CallingContextIn
import de.fraunhofer.aisec.cpg.graph.edges.flows.ContextSensitiveDataflow
import de.fraunhofer.aisec.cpg.graph.edges.flows.PartialDataflowGranularity
import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class ExpressionBuilderTest {
@Test
fun testDuplicateWithDFGProperties() {
val node1 = Literal<Int>()
val node2 = Reference()
val granularity = PartialDataflowGranularity(FieldDeclaration())
val callingContextIn = CallingContextIn(CallExpression())
node1.prevDFGEdges.addContextSensitive(node2, granularity, callingContextIn)
with(TestLanguageFrontend()) {
val node1 = newLiteral(42)
val node2 = newReference("foo")
val granularity = PartialDataflowGranularity(FieldDeclaration())
val callingContextIn = CallingContextIn(CallExpression())
node1.prevDFGEdges.addContextSensitive(node2, granularity, callingContextIn)

val clone = node1.duplicate(false)
val clonedPrevDFG = clone.prevDFGEdges.single()
assertTrue(clonedPrevDFG is ContextSensitiveDataflow)
assertEquals(callingContextIn, clonedPrevDFG.callingContext)
assertEquals(granularity, clonedPrevDFG.granularity)
val clone = node1.duplicate(false)
val clonedPrevDFG = clone.prevDFGEdges.single()
assertTrue(clonedPrevDFG is ContextSensitiveDataflow)
assertEquals(callingContextIn, clonedPrevDFG.callingContext)
assertEquals(granularity, clonedPrevDFG.granularity)

assertEquals(setOf<Node>(node1, clone), node2.nextDFG)
assertEquals(setOf<Node>(node1, clone), node2.nextDFG)
}
}

@Test
fun testDuplicateWithDFGProperties2() {
val node1 = Literal<Int>()
val node2 = Reference()
val granularity = PartialDataflowGranularity(FieldDeclaration())
val callingContextIn = CallingContextIn(CallExpression())
node1.nextDFGEdges.addContextSensitive(node2, granularity, callingContextIn)
with(TestLanguageFrontend()) {
val node1 = newLiteral(42)
val node2 = newReference("foo")
val granularity = PartialDataflowGranularity(FieldDeclaration())
val callingContextIn = CallingContextIn(CallExpression())
node1.nextDFGEdges.addContextSensitive(node2, granularity, callingContextIn)

val clone = node1.duplicate(false)
val clonedPrevDFG = clone.nextDFGEdges.single()
assertTrue(clonedPrevDFG is ContextSensitiveDataflow)
assertEquals(callingContextIn, clonedPrevDFG.callingContext)
assertEquals(granularity, clonedPrevDFG.granularity)
val clone = node1.duplicate(false)
val clonedPrevDFG = clone.nextDFGEdges.single()
assertTrue(clonedPrevDFG is ContextSensitiveDataflow)
assertEquals(callingContextIn, clonedPrevDFG.callingContext)
assertEquals(granularity, clonedPrevDFG.granularity)

assertEquals(setOf<Node>(node1, clone), node2.prevDFG)
assertEquals(setOf<Node>(node1, clone), node2.prevDFG)
}
}
}
Loading
Loading