From 1730f8bde2f2189da2f080a3c3dfddd2060fe666 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Fri, 3 Mar 2023 09:21:39 +0100 Subject: [PATCH] added missing pass --- .../aisec/cpg/passes/GoTypeCastingPass.kt | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoTypeCastingPass.kt diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoTypeCastingPass.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoTypeCastingPass.kt new file mode 100644 index 0000000000..ea3f09f4c2 --- /dev/null +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoTypeCastingPass.kt @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2023, 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.TranslationResult +import de.fraunhofer.aisec.cpg.frontends.Language +import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend +import de.fraunhofer.aisec.cpg.frontends.golang.GoLanguage +import de.fraunhofer.aisec.cpg.graph.* +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.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.types.TypeParser +import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker +import de.fraunhofer.aisec.cpg.passes.order.ExecuteBefore + +/** + * In Go, it is possible to convert compatible types by "calling" the type name as a function, such + * as + * + * ```go + * var i = int(2.0) + * ``` + * + * This is also possible with more complex types, such as interfaces or aliased types, as long as + * they are compatible. Because types in the same package can be defined in multiple files, we + * cannot decide during the frontend run. Therefore, we need to execute this pass before the + * [CallResolver] and convert certain [CallExpression] nodes into a [CastExpression]. + */ +@ExecuteBefore(CallResolver::class) +@ExecuteBefore(DFGPass::class) +class GoTypeCastingPass : Pass() { + override fun accept(t: TranslationResult) { + scopeManager = t.scopeManager + + var walker = SubgraphWalker.ScopedWalker(scopeManager) + walker.registerHandler { _, parent, node -> + // We are only interested in CallExpressions + if (node is CallExpression) { + handleCall(node, parent) + } + } + + for (tu in t.translationUnits) { + walker.iterate(tu) + } + } + + private fun handleCall(call: CallExpression, parent: Node?) { + // 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. + val callee = call.callee + if (parent != null && callee is DeclaredReferenceExpression && call.arguments.size == 1) { + val language = parent.language ?: GoLanguage() + + // First, check if this is a built-in type + if (language.primitiveTypes.contains(callee.name.toString())) { + replaceCallWithCast(callee.name.toString(), language, parent, call) + } 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.getInstance().typeExists(fqn.toString())) { + replaceCallWithCast(fqn, language, parent, call) + } + } + } + } + + private fun replaceCallWithCast( + typeName: CharSequence, + language: Language, + parent: Node, + call: CallExpression, + ) { + val cast = parent.newCastExpression(call.code) + cast.location = call.location + cast.castType = TypeParser.createFrom(typeName, false, language) + cast.expression = call.arguments.single() + + if (parent !is ArgumentHolder) { + 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) { + log.error( + "Replacing call expression with cast expression was not successful. Further analysis might not be entirely accurate." + ) + } else { + call.disconnectFromGraph() + } + } + + override fun cleanup() { + // Nothing to do + } +}