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 3f85b97642..05cfbb534c 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 @@ -27,16 +27,14 @@ package de.fraunhofer.aisec.cpg.frontends import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.graph.HasOperatorCode import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration 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.scopes.* +import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.passes.ReplaceCallCastPass -import de.fraunhofer.aisec.cpg.passes.SymbolResolver +import de.fraunhofer.aisec.cpg.passes.* /** * A language trait is a feature or trait that is common to a group of programming languages. Any @@ -230,3 +228,13 @@ interface HasFunctionalCasts : LanguageTrait * multiple functions can share the same name with different parameters. */ interface HasFunctionOverloading : LanguageTrait + +/** A language trait that specifies that this language allows overloading of operators. */ +interface HasOperatorOverloading : LanguageTrait { + + /** + * A map of operator codes and function names acting as overloaded operators. The key is the + * [HasOperatorCode.operatorCode] and the value is the name of the function. + */ + val overloadedOperatorNames: Map +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt index 5d6fcb260f..a1f2331021 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt @@ -95,6 +95,29 @@ fun MetadataProvider.newMethodDeclaration( return node } +/** + * Creates a new [OperatorDeclaration]. The [MetadataProvider] receiver will be used to fill + * different meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin + * requires an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional + * prepended argument. + */ +@JvmOverloads +fun MetadataProvider.newOperatorDeclaration( + name: CharSequence, + operatorCode: String, + recordDeclaration: RecordDeclaration? = null, + rawNode: Any? = null +): MethodDeclaration { + val node = OperatorDeclaration() + node.applyMetadata(this, name, rawNode, defaultNamespace = recordDeclaration?.name) + + node.operatorCode = operatorCode + node.recordDeclaration = recordDeclaration + + log(node) + return node +} + /** * Creates a new [ConstructorDeclaration]. The [MetadataProvider] receiver will be used to fill * different meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 77c6cb28f8..4200759a06 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -530,6 +530,10 @@ val Node?.casts: List val Node?.methods: List get() = this.allChildren() +/** Returns all [OperatorDeclaration] children in this graph, starting with this [Node]. */ +val Node?.operators: List + get() = this.allChildren() + /** Returns all [FieldDeclaration] children in this graph, starting with this [Node]. */ val Node?.fields: List get() = this.allChildren() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasOperatorCode.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasOperatorCode.kt index 68005bbbc9..f226b9c010 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasOperatorCode.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasOperatorCode.kt @@ -25,8 +25,6 @@ */ package de.fraunhofer.aisec.cpg.graph -import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression - /** A simple interface to denote that the implementing class has some kind of [operatorCode]. */ interface HasOperatorCode { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/OperatorDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/OperatorDeclaration.kt new file mode 100644 index 0000000000..89cba6d9f5 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/OperatorDeclaration.kt @@ -0,0 +1,48 @@ +/* + * 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.graph.declarations + +import de.fraunhofer.aisec.cpg.frontends.HasOperatorOverloading +import de.fraunhofer.aisec.cpg.graph.HasOperatorCode +import de.fraunhofer.aisec.cpg.graph.types.ObjectType + +/** + * Some languages allow to either overload operators or to add custom operators to classes (see + * [HasOperatorOverloading]). In both cases, this special function class denotes that this handles + * this particular operator call. + * + * We need to derive from [MethodDeclaration] because all operators have a base class on which they + * operate on. Therefore, we need to associate them with an [ObjectType] and/or [RecordDeclaration]. + * There are some very special cases for C++, where we can have a global operator for a particular + * class. In this case we just pretend like it is a method operator. + */ +class OperatorDeclaration : MethodDeclaration(), HasOperatorCode { + /** The operator code which this operator declares. */ + override var operatorCode: String? = null + + val isPrefix: Boolean = false + val isPostfix: Boolean = false +} diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt index c8e328f590..1ada76c8b6 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt @@ -30,6 +30,7 @@ import de.fraunhofer.aisec.cpg.frontends.* import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.edge.Properties +import de.fraunhofer.aisec.cpg.graph.scopes.Symbol import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression import de.fraunhofer.aisec.cpg.graph.types.* @@ -47,11 +48,55 @@ open class CPPLanguage : HasClasses, HasUnknownType, HasFunctionalCasts, - HasFunctionOverloading { + HasFunctionOverloading, + HasOperatorOverloading { override val fileExtensions = listOf("cpp", "cc", "cxx", "c++", "hpp", "hh") override val elaboratedTypeSpecifier = listOf("class", "struct", "union", "enum") override val unknownTypeString = listOf("auto") + override val overloadedOperatorNames: Map + get() = + mapOf( + // Arithmetic operators. See + // https://en.cppreference.com/w/cpp/language/operator_arithmetic + "+" to "operator+", + "-" to "operator-", + "*" to "operator+", + "/" to "operator/", + "%" to "operator%", + "&" to "operator&", + "|" to "operator|", + "^" to "operator^", + "<<" to "operator<<", + ">>" to "operator>>", + + // Increment/decrement operators. See + // https://en.cppreference.com/w/cpp/language/operator_incdec + "++" to "operator++", + "--" to "operator--", + + // Comparison operators. See + // https://en.cppreference.com/w/cpp/language/operator_comparison + "==" to "operator==", + "!=" to "operator!=", + "<" to "operator<", + ">" to "operator>", + "<=" to "operator<=", + "=>" to "operator=>", + + // Member access operators. See + // https://en.cppreference.com/w/cpp/language/operator_member_access + "[]" to "operator[]", + "*" to "operator*", + "&" to "operator&", + "->" to "operator->", + "->*" to "operator->*", + + // Other operators. See https://en.cppreference.com/w/cpp/language/operator_other + "()" to "operator()", + "," to "operator,", + ) + /** * The list of built-in types. See https://en.cppreference.com/w/cpp/language/types for a * reference. We only list equivalent types here and use the canonical form of integer values. diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt index 0b83ba0909..0fd21a046c 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.ResolveInFrontend +import de.fraunhofer.aisec.cpg.frontends.HasOperatorOverloading import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.scopes.NameScope @@ -33,7 +34,6 @@ import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.helpers.Util -import java.util.* import java.util.function.Supplier import org.eclipse.cdt.core.dom.ast.* import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier @@ -158,7 +158,7 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : * checks if the scope is a namespace or a record and if the name matches to the record (in case * of a constructor). */ - private fun createFunctionOrMethodOrConstructor( + private fun createAppropriateFunction( name: Name, scope: Scope?, ctx: IASTNode, @@ -168,9 +168,14 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : val func = when { + // Check if it's an operator + name.isKnownOperatorName -> { + // retrieve the operator code + var operatorCode = name.localName.drop("operator".length) + newOperatorDeclaration(name, operatorCode, rawNode = ctx) + } // Check, if it's a constructor. This is the case if the local names of the function - // and the - // record declaration match + // and the record declaration match holder is RecordDeclaration && name.localName == holder.name.localName -> { newConstructorDeclaration(name, holder, rawNode = ctx) } @@ -219,9 +224,11 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : /* * As always, there are some special cases to consider and one of those are C++ operators. - * They are regarded as functions and eclipse CDT for some reason introduces a whitespace in the function name, which will complicate things later on + * They are regarded as functions and eclipse CDT for some reason introduces a whitespace + * in the function name, which will complicate things later on. But we only want to replace + * the whitespace for "standard" operators. */ - if (name.startsWith("operator")) { + if (nameDecl.name is CPPASTOperatorName && name.replace(" ", "").isKnownOperatorName) { name = name.replace(" ", "") } val declaration: FunctionDeclaration @@ -242,15 +249,11 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : frontend.scopeManager.currentNamespace.fqn(parent.toString()).toString() ) - declaration = createFunctionOrMethodOrConstructor(name, parentScope, ctx.parent) + declaration = createAppropriateFunction(name, parentScope, ctx.parent) } else if (frontend.scopeManager.isInRecord) { // If the current scope is already a record, it's a method declaration = - createFunctionOrMethodOrConstructor( - name, - frontend.scopeManager.currentScope, - ctx.parent - ) + createAppropriateFunction(name, frontend.scopeManager.currentScope, ctx.parent) } else { // a plain old function, outside any named scope declaration = newFunctionDeclaration(name, rawNode = ctx.parent) @@ -506,6 +509,17 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : frontend.declarationHandler.handle(member) } } + + /** Checks whether the [Name] for a function is a known operator name. */ + val Name.isKnownOperatorName: Boolean + get() { + var language = language + if (language !is HasOperatorOverloading) { + return false + } + + return language.overloadedOperatorNames.containsValue(this.localName) + } } /** diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXDeclarationTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXDeclarationTest.kt index bf197aeb8a..6bfae94a61 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXDeclarationTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXDeclarationTest.kt @@ -232,4 +232,18 @@ class CXXDeclarationTest { assertEquals(assertResolvedType("ABC::A"), a.type) } } + + @Test + fun testArithmeticOperator() { + val file = File("src/test/resources/cxx/operators/arithmetic.cpp") + val result = + analyze(listOf(file), file.parentFile.toPath(), true) { + it.registerLanguage() + } + assertNotNull(result) + + var plusplus = result.operators["operator++"] + assertNotNull(plusplus) + assertEquals("++", plusplus.operatorCode) + } } diff --git a/cpg-language-cxx/src/test/resources/cxx/operators/arithmetic.cpp b/cpg-language-cxx/src/test/resources/cxx/operators/arithmetic.cpp new file mode 100644 index 0000000000..a888301d6b --- /dev/null +++ b/cpg-language-cxx/src/test/resources/cxx/operators/arithmetic.cpp @@ -0,0 +1,27 @@ +class Integer { +public: + Integer(int i) : i(i) {} + void operator++(int) { + i++; + } + + Integer operator+(int j) { + return Integer(this->i+j); + } + + Integer operator+(Integer &j) { + return Integer(this->i+j.i); + } + + void test() {}; + + int i; +}; + +int main() { + Integer i(5); + i++; + + Integer j = i + 2; + auto k = i + j; +} \ No newline at end of file