From bea15bacceb0c5d7b14bf23841834addae40130c Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Sat, 30 Nov 2024 16:57:53 +0100 Subject: [PATCH] Extracted demo into example language frontend --- .../aisec/cpg/frontends/LanguageFrontend.kt | 7 +- .../fraunhofer/aisec/cpg/NodeBuilderV2Test.kt | 70 ++++----- .../aisec/cpg/example/ExampleLanguage.kt | 148 ++++++++++++++++++ .../cpg/example/RawExampleLanguageParser.kt | 43 +++++ 4 files changed, 227 insertions(+), 41 deletions(-) create mode 100644 cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/example/ExampleLanguage.kt create mode 100644 cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/example/RawExampleLanguageParser.kt diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt index e6e97aa12d..e12d77b11b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt @@ -42,7 +42,12 @@ import org.slf4j.LoggerFactory * after having processed the files, i.e., it won't be available in passes. * * More information can be found in the - * [GitHub wiki page](https://github.com/Fraunhofer-AISEC/cpg/wiki/Language-Frontends). + * [Documentation](https://fraunhofer-aisec.github.io/cpg/CPG/impl/language/#languagefrontend). + * + * @param AstNode This type parameter should be set to a class that is common to all AST nodes in + * the language. + * @param TypeNode This type parameter should be set to a class that is common to all type nodes in + * the language. */ abstract class LanguageFrontend( /** The language this frontend works for. */ diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/NodeBuilderV2Test.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/NodeBuilderV2Test.kt index 4d84f1a9ff..801ce81509 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/NodeBuilderV2Test.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/NodeBuilderV2Test.kt @@ -25,52 +25,20 @@ */ package de.fraunhofer.aisec.cpg -import de.fraunhofer.aisec.cpg.frontends.Handler +import de.fraunhofer.aisec.cpg.example.ExampleLanguageFrontend +import de.fraunhofer.aisec.cpg.example.RawFileNode +import de.fraunhofer.aisec.cpg.example.RawFunctionNode +import de.fraunhofer.aisec.cpg.example.RawParameterNode +import de.fraunhofer.aisec.cpg.example.RawTypeNode import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.declarations.Declaration -import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.ProblemDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.plusAssign +import java.io.File import kotlin.test.Test +import kotlin.test.assertIs import kotlin.test.assertNotNull -class RawFileDeclarationNode(var children: List) : RawDeclarationNode() - -class RawFunctionDeclarationNode : RawDeclarationNode() - -open class RawDeclarationNode : RawNode() - -open class RawNode - -private class TestDeclarationHandler(frontend: TestLanguageFrontend) : - Handler(::ProblemDeclaration, frontend) { - override fun handle(node: RawDeclarationNode): Declaration { - when (node) { - is RawFunctionDeclarationNode -> handleFunction(node) - is RawFileDeclarationNode -> handleFile(node) - } - - return ProblemDeclaration() - } - - private fun handleFile(node: RawFileDeclarationNode): TranslationUnitDeclaration { - return translationUnitDeclaration("test.file").globalScope { - for (child in node.children) { - this += handle(child) - } - } - } - - private fun handleFunction(node: RawFunctionDeclarationNode): FunctionDeclaration { - return functionDeclaration("main", rawNode = node).withScope { - val param = parameterDeclaration("argc", objectType("int")) - - this += param - } - } -} - class NodeBuilderV2Test { @Test fun testBuild() { @@ -97,4 +65,26 @@ class NodeBuilderV2Test { val param = func.parameters["argc"] assertNotNull(param) } + + @Test + fun testFrontend() { + val file = + RawFileNode( + "file.example", + children = + listOf( + RawFunctionNode( + "main", + params = listOf(RawParameterNode("argc", type = RawTypeNode("int"))) + ) + ) + ) + + val frontend = ExampleLanguageFrontend(rawFileNode = file) + val node = frontend.parse(File("")) + assertIs(node) + + val main = node.functions["main"] + assertNotNull(main) + } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/example/ExampleLanguage.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/example/ExampleLanguage.kt new file mode 100644 index 0000000000..67dc3a02ad --- /dev/null +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/example/ExampleLanguage.kt @@ -0,0 +1,148 @@ +/* + * 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.example + +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.TypeManager +import de.fraunhofer.aisec.cpg.frontends.Handler +import de.fraunhofer.aisec.cpg.frontends.HasClasses +import de.fraunhofer.aisec.cpg.frontends.Language +import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.Declaration +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.ParameterDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.ProblemDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.functionDeclaration +import de.fraunhofer.aisec.cpg.graph.globalScope +import de.fraunhofer.aisec.cpg.graph.objectType +import de.fraunhofer.aisec.cpg.graph.parameterDeclaration +import de.fraunhofer.aisec.cpg.graph.plusAssign +import de.fraunhofer.aisec.cpg.graph.translationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.types.IntegerType +import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.withScope +import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation +import java.io.File +import kotlin.reflect.KClass + +/** + * This is an example language, that demonstrate how to construct a [Language]. Our language has the + * following features: + * - It uses the `.example` file extension + * - It has classes and namespaces + * - Namespaces are separated by a `::` delimiter + * - It features some built-in types, such as `int` + */ +class ExampleLanguage : Language(), HasClasses { + override val fileExtensions: List = listOf("example") + override val namespaceDelimiter: String = "::" + override val frontend: KClass = ExampleLanguageFrontend::class + override val builtInTypes: Map = mapOf("int" to IntegerType()) + override val compoundAssignmentOperators = setOf() +} + +/** + * This is an example language frontend, that demonstrates how to translate languages into the CPG. + * In this case, we want to translate our [ExampleLanguage]. It takes nodes that derive from + * [RawNode] -- these would come from a real parser in a real language -- and translates them into a + * CPG [Node]. + */ +class ExampleLanguageFrontend( + language: ExampleLanguage = ExampleLanguage(), + ctx: TranslationContext = + TranslationContext( + TranslationConfiguration.builder().build(), + ScopeManager(), + TypeManager() + ), + var rawFileNode: RawFileNode +) : LanguageFrontend(language, ctx) { + + /** + * We want to store an instance of our [Handler] here, in this case one that takes care of a + * [Declaration]. + */ + private var declarationHandler = ExampleDeclarationHandler(this) + + override fun parse(file: File): TranslationUnitDeclaration { + // In a real language frontend, we would read the AST from the contents of the file, but + // since we are not fully implementing a parser, we are using the rawFileNode from our + // constructor instead. + val parsedFile = rawFileNode + + return translationUnitDeclaration(parsedFile.name).globalScope { + for (child in parsedFile.children) { + this += declarationHandler.handle(child) + } + } + } + + override fun typeOf(type: RawTypeNode): Type { + return objectType(type.name) + } + + override fun codeOf(astNode: RawNode): String? { + return astNode.code + } + + override fun locationOf(astNode: RawNode): PhysicalLocation? { + return null + } + + override fun setComment(node: Node, astNode: RawNode) {} +} + +class ExampleDeclarationHandler(frontend: ExampleLanguageFrontend) : + Handler( + ::ProblemDeclaration, + frontend + ) { + override fun handle(node: RawDeclarationNode): Declaration { + return when (node) { + is RawFunctionNode -> handleFunction(node) + is RawParameterNode -> handleParameter(node) + else -> ProblemDeclaration() + } + } + + private fun handleFunction(node: RawFunctionNode): FunctionDeclaration { + return functionDeclaration(node.name, rawNode = node).withScope { + for (param in node.params) { + val decl = handle(param) + + this += decl + } + } + } + + private fun handleParameter(node: RawParameterNode): ParameterDeclaration { + return parameterDeclaration(node.name, type = frontend.typeOf(node.type)) + } +} diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/example/RawExampleLanguageParser.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/example/RawExampleLanguageParser.kt new file mode 100644 index 0000000000..4a104a8d16 --- /dev/null +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/example/RawExampleLanguageParser.kt @@ -0,0 +1,43 @@ +/* + * 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.example + +class RawFileNode(name: String, val children: List) : RawDeclarationNode(name) + +class RawFunctionNode(name: String, val params: List) : RawDeclarationNode(name) + +class RawParameterNode(name: String, val type: RawTypeNode) : RawDeclarationNode(name) + +open class RawDeclarationNode(val name: String) : RawNode() + +/** This node represents some sort of type in the example language. */ +open class RawTypeNode(val name: String) + +/** + * This node represents an in-memory object of a raw node of our example language that a parser + * would create. It is the base node for all other classes. + */ +open class RawNode(val code: String? = null)