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

Extracted call/cast replacement into separate pass #1499

Merged
merged 2 commits into from
Apr 5, 2024
Merged
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 @@ -507,6 +507,7 @@ private constructor(
registerPass<TypeResolver>()
registerPass<ControlFlowSensitiveDFGPass>()
registerPass<FilenameMapper>()
registerPass<ReplaceCallCastPass>()
useDefaultPasses = true
return this
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration
import de.fraunhofer.aisec.cpg.graph.scopes.GlobalScope
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.MemberExpression
import de.fraunhofer.aisec.cpg.graph.types.Type
import de.fraunhofer.aisec.cpg.passes.ReplaceCallCastPass
import de.fraunhofer.aisec.cpg.passes.SymbolResolver

/**
Expand Down Expand Up @@ -227,22 +229,30 @@ 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() = "_"
}

/**
* A language trait, that specifies that this language has global variables directly in the
* [GlobalScope], i.e,. not within a namespace, but directly contained in a
* [GlobalScope], i.e., not within a namespace, but directly contained in a
* [TranslationUnitDeclaration].
*/
interface HasGlobalVariables {
interface HasGlobalVariables : LanguageTrait {
val globalVariableScopeClass: Class<out Node>
}

/**
* A language trait, that specifies that the language has so-called functional style casts, meaning
* that they look like regular call expressions. Since we can therefore not distinguish between a
* [CallExpression] and a [CastExpression], we need to employ an additional pass
* ([ReplaceCallCastPass]) after the initial language frontends are done.
*/
interface HasFunctionalCasts : LanguageTrait
38 changes: 33 additions & 5 deletions cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,21 @@ 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
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
Expand Down Expand Up @@ -112,16 +117,36 @@ sealed class Pass<T : Node>(final override val ctx: TranslationContext) :
* [RequiredFrontend]
*/
fun runsWithCurrentFrontend(usedFrontends: Collection<LanguageFrontend<*, *>>): Boolean {
if (!this.javaClass.isAnnotationPresent(RequiredFrontend::class.java)) return true
val requiredFrontend = this.javaClass.getAnnotation(RequiredFrontend::class.java).value
val requiredFrontend = this::class.findAnnotation<RequiredFrontend>() ?: 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<RequiresLanguageTrait>()
for (requiresLanguageTrait in requiresLanguageTraits) {
if (!language::class.isSubclassOf(requiresLanguageTrait.value)) {
return false
}
}

return true
}

companion object {
val log: Logger = LoggerFactory.getLogger(Pass::class.java)
}

Expand Down Expand Up @@ -264,7 +289,10 @@ private inline fun <reified T : Node> 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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* 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

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) {
oxisto marked this conversation as resolved.
Show resolved Hide resolved
private lateinit var walker: SubgraphWalker.ScopedWalker

override fun accept(tu: TranslationUnitDeclaration) {
walker = SubgraphWalker.ScopedWalker(ctx.scopeManager)
walker.registerHandler { _, parent, node ->
when (node) {
is CallExpression -> handleCall(node, parent)
}
}

walker.iterate(tu)
}

private fun handleCall(call: CallExpression, parent: Node?) {
// Make sure, we are not accidentally handling construct expressions (since they also derive
// from call expressions)
if (call is ConstructExpression) {
return
}

// We need to check, whether the "callee" refers to a type and if yes, convert it into a
// cast expression. And this is only really necessary, if the function call has a single
// argument.
var callee = call.callee
if (parent != null && callee != null && call.arguments.size == 1) {
val language = parent.language

var pointer = false
// If the argument is a UnaryOperator, unwrap them
if (callee is UnaryOperator && callee.operatorCode == "*") {
pointer = true
callee = callee.input
}

// First, check if this is a built-in type
if (language?.builtInTypes?.contains(callee.name.toString()) == true) {
walker.replaceCallWithCast(callee.name.toString(), parent, call, false)
} else {
// If not, then this could still refer to an existing type. We need to make sure
// that we take the current namespace into account
val fqn =
if (callee.name.parent == null) {
scopeManager.currentNamespace.fqn(callee.name.localName)
} else {
callee.name
}

if (typeManager.typeExists(fqn.toString())) {
walker.replaceCallWithCast(fqn, parent, call, pointer)
}
}
}
}

override fun cleanup() {
// Nothing to do
}
}

context(ContextProvider)
oxisto marked this conversation as resolved.
Show resolved Hide resolved
fun SubgraphWalker.ScopedWalker.replaceCallWithCast(
typeName: CharSequence,
parent: Node,
call: CallExpression,
pointer: Boolean,
) {
val cast = newCastExpression()
cast.code = call.code
cast.language = call.language
cast.location = call.location
cast.castType =
if (pointer) {
call.objectType(typeName).pointer()
} else {
call.objectType(typeName)
}
cast.expression = call.arguments.single()
cast.name = cast.castType.name

if (parent !is ArgumentHolder) {
Pass.log.error(
"Parent AST node of call expression is not an argument holder. Cannot convert to cast expression. Further analysis might not be entirely accurate."
)
return
}

val success = parent.replaceArgument(call, cast)
if (!success) {
Pass.log.error(
"Replacing call expression with cast expression was not successful. Further analysis might not be entirely accurate."
)
} else {
call.disconnectFromGraph()

// Make sure to inform the walker about our change
this.registerReplacement(call, cast)
}
}
Original file line number Diff line number Diff line change
@@ -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<out LanguageTrait>)
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ open class CPPLanguage :
HasComplexCallResolution,
HasStructs,
HasClasses,
HasUnknownType {
HasUnknownType,
HasFunctionalCasts {
override val fileExtensions = listOf("cpp", "cc", "cxx", "hpp", "hh")
override val elaboratedTypeSpecifier = listOf("class", "struct", "union", "enum")
override val unknownTypeString = listOf("auto")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import de.fraunhofer.aisec.cpg.passes.order.ExecuteBefore
* type information.
*/
@ExecuteBefore(EvaluationOrderGraphPass::class)
@ExecuteBefore(ReplaceCallCastPass::class)
@DependsOn(TypeResolver::class)
class CXXExtraPass(ctx: TranslationContext) : ComponentPass(ctx) {
override fun accept(component: Component) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ class GoLanguage :
HasGenerics,
HasStructs,
HasFirstClassFunctions,
HasAnonymousIdentifier {
HasAnonymousIdentifier,
HasFunctionalCasts {
override val fileExtensions = listOf("go")
override val namespaceDelimiter = "."
@Transient override val frontend = GoLanguageFrontend::class
Expand Down
Loading
Loading