diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt index 9902a941f1..d24211a352 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt @@ -229,13 +229,13 @@ interface HasShortCircuitOperators : LanguageTrait { * A language trait, that specifies that this language treats functions "first-class citizens", * meaning they can be assigned to variables and passed as arguments to other functions. */ -interface HasFirstClassFunctions +interface HasFirstClassFunctions : LanguageTrait /** * A language trait, that specifies that this language has an "anonymous" identifier, used for * unused parameters or suppressed assignments. */ -interface HasAnonymousIdentifier { +interface HasAnonymousIdentifier : LanguageTrait { val anonymousIdentifier: String get() = "_" } @@ -245,7 +245,7 @@ interface HasAnonymousIdentifier { * [GlobalScope], i.e., not within a namespace, but directly contained in a * [TranslationUnitDeclaration]. */ -interface HasGlobalVariables { +interface HasGlobalVariables : LanguageTrait { val globalVariableScopeClass: Class } @@ -255,4 +255,4 @@ interface HasGlobalVariables { * [CallExpression] and a [CastExpression], we need to employ an additional pass * ([ReplaceCallCastPass]) after the initial language frontends are done. */ -interface HasFunctionalCasts +interface HasFunctionalCasts : LanguageTrait diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt index 37b78a5a52..3d3371ec01 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend +import de.fraunhofer.aisec.cpg.frontends.LanguageTrait import de.fraunhofer.aisec.cpg.frontends.TranslationException import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration @@ -35,9 +36,13 @@ import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.helpers.Benchmark import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.ScopedWalker import de.fraunhofer.aisec.cpg.passes.order.RequiredFrontend +import de.fraunhofer.aisec.cpg.passes.order.RequiresLanguageTrait import java.util.concurrent.CompletableFuture import java.util.function.Consumer import kotlin.reflect.KClass +import kotlin.reflect.full.findAnnotation +import kotlin.reflect.full.findAnnotations +import kotlin.reflect.full.isSubclassOf import kotlin.reflect.full.primaryConstructor import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -112,16 +117,36 @@ sealed class Pass(final override val ctx: TranslationContext) : * [RequiredFrontend] */ fun runsWithCurrentFrontend(usedFrontends: Collection>): Boolean { - if (!this.javaClass.isAnnotationPresent(RequiredFrontend::class.java)) return true - val requiredFrontend = this.javaClass.getAnnotation(RequiredFrontend::class.java).value + val requiredFrontend = this::class.findAnnotation() ?: return true for (used in usedFrontends) { - if (used.javaClass == requiredFrontend.java) return true + if (used::class == requiredFrontend.value) return true } return false } - companion object { + /** + * Checks, if the pass requires a specific [LanguageTrait] and if the current target of the pass + * has this trait. + * + * @return true, if the pass does not require a specific language trait or if it matches the + * [RequiresLanguageTrait]. + */ + fun runsWithLanguageTrait(language: Language<*>?): Boolean { + if (language == null) { + return true + } + val requiresLanguageTraits = this::class.findAnnotations() + for (requiresLanguageTrait in requiresLanguageTraits) { + if (!language::class.isSubclassOf(requiresLanguageTrait.value)) { + return false + } + } + + return true + } + + companion object { val log: Logger = LoggerFactory.getLogger(Pass::class.java) } @@ -264,7 +289,10 @@ private inline fun consumeTarget( val realClass = checkForReplacement(cls, language, ctx.config) val pass = realClass.primaryConstructor?.call(ctx) - if (pass?.runsWithCurrentFrontend(executedFrontends) == true) { + if ( + pass?.runsWithCurrentFrontend(executedFrontends) == true && + pass.runsWithLanguageTrait(language) + ) { pass.accept(target) pass.cleanup() return pass diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ReplaceCallCastPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ReplaceCallCastPass.kt index 9e76ae228c..dffd71f57f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ReplaceCallCastPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ReplaceCallCastPass.kt @@ -26,27 +26,37 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.frontends.Handler import de.fraunhofer.aisec.cpg.frontends.HasFunctionalCasts +import de.fraunhofer.aisec.cpg.frontends.Language +import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CastExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConstructExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator +import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.passes.order.DependsOn import de.fraunhofer.aisec.cpg.passes.order.ExecuteBefore +import de.fraunhofer.aisec.cpg.passes.order.RequiresLanguageTrait +/** + * If a [Language] has the trait [HasFunctionalCasts], we cannot distinguish between a + * [CallExpression] and a [CastExpression] during the initial translation. This stems from the fact + * that we might not know all the types yet. We therefore need to handle them as regular call + * expression in a [LanguageFrontend] or [Handler] and then later replace them with a + * [CastExpression], if the [CallExpression.callee] refers to name of a [Type] rather than a + * function. + */ @ExecuteBefore(EvaluationOrderGraphPass::class) @DependsOn(TypeResolver::class) +@RequiresLanguageTrait(HasFunctionalCasts::class) class ReplaceCallCastPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { private lateinit var walker: SubgraphWalker.ScopedWalker override fun accept(tu: TranslationUnitDeclaration) { - // We need to make sure that the language has a specific trait - if (tu.language !is HasFunctionalCasts) { - return - } - walker = SubgraphWalker.ScopedWalker(ctx.scopeManager) walker.registerHandler { _, parent, node -> when (node) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/RequiresLanguageTrait.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/RequiresLanguageTrait.kt new file mode 100644 index 0000000000..88930d4404 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/RequiresLanguageTrait.kt @@ -0,0 +1,38 @@ +/* + * 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.passes.order + +import de.fraunhofer.aisec.cpg.frontends.Language +import de.fraunhofer.aisec.cpg.frontends.LanguageTrait +import kotlin.reflect.KClass + +/** + * This annotation can only enable a pass if its target language implements a given [LanguageTrait]. + * + * This annotation is [Repeatable]. In this case, all specified language traits must exist on the + * [Language]. + */ +@Repeatable annotation class RequiresLanguageTrait(val value: KClass)