From bb4e009869ddf02192708ccf139f03bcd6a8e14a Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Mon, 15 Apr 2024 23:23:22 +0200 Subject: [PATCH] Draft for type normalisation Fixes #1533 --- .../de/fraunhofer/aisec/cpg/ScopeManager.kt | 37 +++-- .../de/fraunhofer/aisec/cpg/TypeManager.kt | 44 ++++-- .../aisec/cpg/graph/DeclarationBuilder.kt | 4 +- .../fraunhofer/aisec/cpg/graph/TypeBuilder.kt | 20 ++- .../declarations/ConstructorDeclaration.kt | 5 + .../graph/declarations/RecordDeclaration.kt | 7 +- .../graph/declarations/TypedefDeclaration.kt | 5 +- .../aisec/cpg/graph/scopes/GlobalScope.kt | 1 + .../aisec/cpg/graph/scopes/Scope.kt | 18 ++- .../cpg/graph/scopes/ValueDeclarationScope.kt | 11 +- .../cpg/graph/types/FunctionPointerType.kt | 6 - .../aisec/cpg/graph/types/NumericType.kt | 6 + .../aisec/cpg/graph/types/ObjectType.kt | 9 +- .../aisec/cpg/graph/types/PointerType.kt | 9 -- .../aisec/cpg/graph/types/ReferenceType.kt | 4 - .../aisec/cpg/graph/types/StringType.kt | 8 +- .../fraunhofer/aisec/cpg/graph/types/Type.kt | 51 ++++--- .../de/fraunhofer/aisec/cpg/passes/DFGPass.kt | 5 + .../aisec/cpg/passes/ImportResolver.kt | 2 - .../aisec/cpg/passes/SymbolResolver.kt | 116 ++++++++------- .../aisec/cpg/passes/TypeResolver.kt | 112 ++++++++++++--- .../passes/inference/DFGFunctionSummaries.kt | 40 +++++- .../aisec/cpg/passes/inference/Inference.kt | 2 +- .../cpg/graph/types/TypePropagationTest.kt | 6 +- .../de/fraunhofer/aisec/cpg/test/TestUtils.kt | 7 + .../cpg/frontends/cxx/CXXLanguageFrontend.kt | 59 ++++---- .../cpg/frontends/cxx/DeclarationHandler.kt | 20 +-- .../cpg/frontends/cxx/DeclaratorHandler.kt | 4 +- .../cpg/enhancements/types/TypedefTest.kt | 5 +- .../VariableResolverCppTest.kt | 5 +- .../cpg/frontends/cxx/CXXDeclarationTest.kt | 11 +- .../cpg/frontends/cxx/CXXInferenceTest.kt | 25 ++++ .../frontends/cxx/CXXLanguageFrontendTest.kt | 134 +++++++++--------- .../aisec/cpg/frontends/cxx/CXXResolveTest.kt | 10 +- .../src/test/resources/cxx/alias.cpp | 17 ++- .../src/test/resources/cxx/enum.cpp | 9 ++ .../src/test/resources/cxx/parenthesis.cpp | 2 + .../test/resources/cxx/tricky_inference.cpp | 37 +++++ .../src/test/resources/cxx/tricky_inference.h | 12 ++ .../src/test/resources/cxx/using.cpp | 54 ++++--- .../variables_extended/cpp/external_class.h | 1 + 41 files changed, 643 insertions(+), 297 deletions(-) create mode 100644 cpg-language-cxx/src/test/resources/cxx/enum.cpp create mode 100644 cpg-language-cxx/src/test/resources/cxx/tricky_inference.cpp create mode 100644 cpg-language-cxx/src/test/resources/cxx/tricky_inference.h diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt index 030ff15e5c3..17fd32ac742 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt @@ -568,7 +568,7 @@ class ScopeManager : ScopeProvider { } private fun getCurrentTypedefs(searchScope: Scope?): Collection { - val typedefs = mutableMapOf() + val typedefs = mutableMapOf() val path = mutableListOf() var current = searchScope @@ -781,7 +781,8 @@ class ScopeManager : ScopeProvider { } /** - * This function extracts a scope for the [Name], e.g. if the name is fully qualified. + * This function extracts a scope for the [Name], e.g. if the name is fully qualified. `null` is + * returned, if no scope can be extracted. * * The pair returns the extracted scope and a name that is adjusted by possible import aliases. * The extracted scope is "responsible" for the name (e.g. declares the parent namespace) and @@ -822,7 +823,7 @@ class ScopeManager : ScopeProvider { LOGGER, "Could not find the scope $scopeName needed to resolve $n" ) - scope + null } else { scopes[0] } @@ -833,13 +834,14 @@ class ScopeManager : ScopeProvider { /** * This function resolves a name alias (contained in an import alias) for the [Name.parent] of - * the given [Name]. + * the given [Name]. This has also the major problem of only resolving one layer of aliases :( */ fun resolveParentAlias(name: Name, scope: Scope?): Name { - val parentName = name.parent ?: return name + var parentName = name.parent ?: return name + parentName = resolveParentAlias(parentName, scope) // This is not 100 % ideal, but at least somewhat compatible to the previous approach - var newName = name + var newName = Name(name.localName, parentName, delimiter = name.delimiter) var decl = scope?.lookupSymbol(parentName.localName)?.singleOrNull { it is NamespaceDeclaration || it is RecordDeclaration @@ -849,6 +851,16 @@ class ScopeManager : ScopeProvider { return Name(newName.localName, decl.name, delimiter = newName.delimiter) } + // Some special handling of typedefs; this should somehow be merged with the above but not + // exactly sure how. The issue is that we cannot take the "name" of the typedef declaration, + // but we rather want its original type name. + // TODO: This really needs to be handled better somehow, maybe a common interface for + // typedefs, namespaces and records that return the correct name? + decl = scope?.lookupSymbol(parentName.localName)?.singleOrNull { it is TypedefDeclaration } + if ((decl as? TypedefDeclaration) != null) { + return Name(newName.localName, decl.type.name, delimiter = newName.delimiter) + } + // If we do not have a match yet, it could be that we are trying to resolve an FQN type // during frontend translation. This is deprecated and will be replaced in the future // by a system that also resolves type during symbol resolving. However, to support aliases @@ -993,8 +1005,8 @@ class ScopeManager : ScopeProvider { return findSymbols(name).filterIsInstance().singleOrNull() } - fun typedefFor(alias: Type): Type? { - var current = currentScope + fun typedefFor(alias: Name, scope: Scope? = currentScope): Type? { + var current = scope // We need to build a path from the current scope to the top most one. This ensures us that // a local definition overwrites / shadows one that was there on a higher scope. @@ -1010,7 +1022,7 @@ class ScopeManager : ScopeProvider { // This process has several steps: // First, do a quick local lookup, to see if we have a typedef our current scope // (only do this if the name is not qualified) - if (!alias.name.isQualified() && current == currentScope) { + if (!alias.isQualified() && current == currentScope) { val decl = current.typedefs[alias] if (decl != null) { return decl.type @@ -1021,7 +1033,7 @@ class ScopeManager : ScopeProvider { // qualified based on the current namespace val key = current.typedefs.keys.firstOrNull { - var lookupName = alias.name + var lookupName = alias // If the lookup name is already a FQN, we can use the name directly lookupName = @@ -1033,7 +1045,7 @@ class ScopeManager : ScopeProvider { currentNamespace?.fqn(lookupName.localName) ?: lookupName } - it.name.lastPartsMatch(lookupName) + it.lastPartsMatch(lookupName) } if (key != null) { return current.typedefs[key]?.type @@ -1064,9 +1076,10 @@ class ScopeManager : ScopeProvider { name: Name, location: PhysicalLocation? = null, startScope: Scope? = currentScope, + predicate: ((Declaration) -> Boolean)? = null, ): List { val (scope, n) = extractScope(name, location, startScope) - val list = scope?.lookupSymbol(n.localName)?.toMutableList() ?: mutableListOf() + val list = scope?.lookupSymbol(n.localName, predicate)?.toMutableList() ?: mutableListOf() // If we have both the definition and the declaration of a function declaration in our list, // we chose only the definition diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt index 34ab82aa754..02fe229740c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg import de.fraunhofer.aisec.cpg.frontends.CastNotPossible import de.fraunhofer.aisec.cpg.frontends.CastResult import de.fraunhofer.aisec.cpg.frontends.Language +import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TemplateDeclaration import de.fraunhofer.aisec.cpg.graph.scopes.Scope @@ -227,11 +228,40 @@ class TypeManager { return firstOrderTypes.any { type: Type -> type.root.name.toString() == name } } + fun typeExists(name: Name): Type? { + return firstOrderTypes.firstOrNull { type: Type -> type.root.name == name } + } + fun resolvePossibleTypedef(alias: Type, scopeManager: ScopeManager): Type { val finalToCheck = alias.root - val applicable = scopeManager.typedefFor(finalToCheck) + val applicable = scopeManager.typedefFor(finalToCheck.name, alias.scope) return applicable ?: alias } + + /** + * This function returns the first (there should be only one) [Type] with the given [fqn] that + * is [Type.Origin.RESOLVED]. + */ + fun lookupResolvedType( + fqn: String, + generics: List? = null, + language: Language<*>? = null + ): Type? { + var primitiveType = language?.getSimpleTypeOf(fqn) + if (primitiveType != null) { + return primitiveType + } + + return firstOrderTypes.firstOrNull { + it.typeOrigin == Type.Origin.RESOLVED && + it.name.toString() == fqn && + if (generics != null) { + (it as? ObjectType)?.generics == generics + } else { + true + } + } + } } val Type.ancestors: Set @@ -242,17 +272,7 @@ val Type.ancestors: Set internal fun Type.getAncestors(depth: Int): Set { val types = mutableSetOf() - // Recursively call ourselves on our super types. There is a little hack here that we need to do - // for object types created from RecordDeclaration::toType() because their supertypes might not - // be set correctly. This would be better, if we change a RecordDeclaration to a - // ValueDeclaration and set the corresponding object type to its type. - val superTypes = - if (this is ObjectType) { - this.recordDeclaration?.superTypes ?: setOf() - } else { - superTypes - } - + // Recursively call ourselves on our super types. types += superTypes.flatMap { it.getAncestors(depth + 1) } // Since the chain starts with our type, we add ourselves to it 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 c7b782e3f19..5d6fcb260fa 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 @@ -204,10 +204,12 @@ fun MetadataProvider.newTypedefDeclaration( rawNode: Any? = null ): TypedefDeclaration { val node = TypedefDeclaration() - node.applyMetadata(this, alias.typeName, rawNode, true) + node.applyMetadata(this, alias.typeName, rawNode) node.type = targetType node.alias = alias + // litle bit of a hack to make the type FQN + node.alias.name = node.name log(node) return node diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt index 29b7c8229d8..a0a0fde85a6 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt @@ -89,11 +89,16 @@ fun Type.ref(): Type { /** * This function returns an [ObjectType] with the given [name]. If a respective [Type] does not yet - * exist, it will be created In order to avoid unnecessary allocation of simple types, we do a - * pre-check within this function, whether a built-in type exist with the particular name. + * exist (in the current scope), it will be created. In order to avoid unnecessary allocation of + * simple types, we do a pre-check within this function, whether a built-in type exist with the + * particular name. */ @JvmOverloads -fun LanguageProvider.objectType(name: CharSequence, generics: List = listOf()): Type { +fun LanguageProvider.objectType( + name: CharSequence, + generics: List = listOf(), + rawNode: Any? = null +): Type { // First, we check, whether this is a built-in type, to avoid necessary allocations of simple // types val builtIn = language?.getSimpleTypeOf(name.toString()) @@ -108,12 +113,15 @@ fun LanguageProvider.objectType(name: CharSequence, generics: List = listO "Could not create type: translation context not available" ) + val scope = c.scopeManager.currentScope + synchronized(c.typeManager.firstOrderTypes) { // We can try to look up the type by its name and return it, if it already exists. var type = c.typeManager.firstOrderTypes.firstOrNull { it is ObjectType && it.name == name && + it.scope == scope && it.generics == generics && it.language == language } @@ -124,9 +132,13 @@ fun LanguageProvider.objectType(name: CharSequence, generics: List = listO // Otherwise, we either need to create the type because of the generics or because we do not // know the type yet. type = ObjectType(name, generics, false, language) + // Apply our usual metadata, such as scope, code, location, if we have any. Make sure only + // to refer by the local name because we will treat types as sort of references when + // creating them and resolve them later. + type.applyMetadata(this, name, rawNode = rawNode, localNameOnly = true) // Piping it through register type will ensure that in any case we return the one unique - // type object for it. + // type object (per scope) for it. return c.typeManager.registerType(type) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ConstructorDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ConstructorDeclaration.kt index e870063f57f..5ac7164da8c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ConstructorDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ConstructorDeclaration.kt @@ -25,6 +25,8 @@ */ package de.fraunhofer.aisec.cpg.graph.declarations +import de.fraunhofer.aisec.cpg.graph.fqn + /** * The declaration of a constructor within a [RecordDeclaration]. Is it essentially a special case * of a [MethodDeclaration]. @@ -38,6 +40,9 @@ class ConstructorDeclaration : MethodDeclaration() { if (recordDeclaration != null) { // constructors always have implicitly the return type of their class returnTypes = listOf(recordDeclaration.toType()) + + // also make sure, our name is updated to the FQN of the record + name = recordDeclaration.name.fqn(this.name.localName) } } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt index cadd923e8d8..31ead94be07 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt @@ -30,6 +30,7 @@ import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate import de.fraunhofer.aisec.cpg.graph.statements.Statement +import de.fraunhofer.aisec.cpg.graph.types.DeclaresType import de.fraunhofer.aisec.cpg.graph.types.ObjectType import de.fraunhofer.aisec.cpg.graph.types.Type import org.apache.commons.lang3.builder.ToStringBuilder @@ -37,7 +38,8 @@ import org.neo4j.ogm.annotation.Relationship import org.neo4j.ogm.annotation.Transient /** Represents a C++ union/struct/class or Java class */ -open class RecordDeclaration : Declaration(), DeclarationHolder, StatementHolder, EOGStarterHolder { +open class RecordDeclaration : + Declaration(), DeclarationHolder, StatementHolder, EOGStarterHolder, DeclaresType { /** The kind, i.e. struct, class, union or enum. */ var kind: String? = null @@ -229,4 +231,7 @@ open class RecordDeclaration : Declaration(), DeclarationHolder, StatementHolder type.superTypes.addAll(this.superTypes) return type } + + override val declaredType: Type + get() = toType() } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypedefDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypedefDeclaration.kt index f9e0f711eeb..f8aac316b51 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypedefDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypedefDeclaration.kt @@ -31,7 +31,7 @@ import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder /** Represents a type alias definition as found in C/C++: `typedef unsigned long ulong;` */ -class TypedefDeclaration : Declaration() { +class TypedefDeclaration : Declaration() /*, DeclaresType*/ { /** The already existing type that is to be aliased */ var type: Type = UnknownType.getUnknownType(null) @@ -52,4 +52,7 @@ class TypedefDeclaration : Declaration() { .append("alias", alias) .toString() } + + /*override val declaredType: Type + get() = alias*/ } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/GlobalScope.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/GlobalScope.kt index 5b0acf5034f..adabb565fc6 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/GlobalScope.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/GlobalScope.kt @@ -59,6 +59,7 @@ class GlobalScope : StructureDeclarationScope(null) { // Merge symbols lists symbols.mergeFrom(other.symbols) + wildcardImports.addAll(other.wildcardImports) for (symbolList in other.symbols) { // Update the scope property of all nodes that live on the global scope to our new diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/Scope.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/Scope.kt index 192250fc3b6..76002e30a64 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/Scope.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/Scope.kt @@ -102,8 +102,14 @@ abstract class Scope( } } - /** Looks up a list of [Declaration] nodes for the specified [symbol]. */ - fun lookupSymbol(symbol: Symbol): List { + /** + * Looks up a list of [Declaration] nodes for the specified [symbol]. Optionally, [predicate] + * can be used for additional filtering. + */ + fun lookupSymbol( + symbol: Symbol, + predicate: ((Declaration) -> Boolean)? = null + ): List { // First, try to look for the symbol in the current scope var scope: Scope? = this var list: MutableList? = null @@ -122,6 +128,11 @@ abstract class Scope( // We need to resolve any imported symbols list.replaceImports(symbol) + // Filter the list according to the predicate, if we have any + if (predicate != null) { + list = list.filter(predicate).toMutableList() + } + // If we have a hit, we can break the loop if (list.isNotEmpty()) { break @@ -206,8 +217,9 @@ private fun MutableList.replaceImports(symbol: Symbol) { val set = import.importedSymbols[symbol] if (set != null) { this.addAll(set) - this.remove(import) } + + this.remove(import) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/ValueDeclarationScope.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/ValueDeclarationScope.kt index 5675ee8cce6..8cad5bbb971 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/ValueDeclarationScope.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/ValueDeclarationScope.kt @@ -26,11 +26,11 @@ package de.fraunhofer.aisec.cpg.graph.scopes import de.fraunhofer.aisec.cpg.graph.DeclarationHolder +import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.TypedefDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration -import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.helpers.Util import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -45,11 +45,14 @@ open class ValueDeclarationScope(override var astNode: Node?) : Scope(astNode) { return symbols.flatMap { it.value }.filterIsInstance() } - /** A map of typedefs keyed by their alias. */ - @Transient val typedefs = mutableMapOf() + /** + * A map of typedefs keyed by their alias name. This is still needed as a bridge until we + * completely redesign the alias / typedef system. + */ + @Transient val typedefs = mutableMapOf() fun addTypedef(typedef: TypedefDeclaration) { - typedefs[typedef.alias] = typedef + typedefs[typedef.alias.name] = typedef } open fun addDeclaration(declaration: Declaration, addToAST: Boolean) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt index 92c1aa43046..5574cea7d28 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt @@ -81,12 +81,6 @@ class FunctionPointerType : Type { return this } - override fun isSimilar(t: Type?): Boolean { - return if (t is FunctionPointerType) { - parametersPropertyEdge == t.parametersPropertyEdge && returnType == t.returnType - } else false - } - override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is FunctionPointerType) return false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/NumericType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/NumericType.kt index 0b0d4a046e2..20a1db8032f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/NumericType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/NumericType.kt @@ -35,6 +35,12 @@ open class NumericType( language: Language<*>? = null, val modifier: Modifier = Modifier.SIGNED ) : ObjectType(typeName, listOf(), true, language) { + + init { + // Built-in types are always resolved + this.typeOrigin = Origin.RESOLVED + } + /** * NumericTypes can have a modifier. The default is signed. Some types (e.g. char in C) may be * neither of the signed/unsigned option. diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt index d7d364b77cf..ed8f88a32f4 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt @@ -46,8 +46,15 @@ open class ObjectType : Type { /** * Reference from the [ObjectType] to its class ([RecordDeclaration]), only if the class is * available. This is set by the [TypeResolver]. + * + * This also sets this type's [scope] to the [RecordDeclaration.scope]. */ - @PopulatedByPass(TypeResolver::class) var recordDeclaration: RecordDeclaration? = null + @PopulatedByPass(TypeResolver::class) + var recordDeclaration: RecordDeclaration? = null + set(value) { + field = value + this.scope = value?.scope + } @Relationship(value = "GENERICS", direction = Relationship.Direction.OUTGOING) var genericsPropertyEdges: MutableList> = mutableListOf() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/PointerType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/PointerType.kt index 13c65550030..bd4f6d8de2d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/PointerType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/PointerType.kt @@ -106,15 +106,6 @@ class PointerType : Type, SecondOrderType { val isArray: Boolean get() = pointerOrigin == PointerOrigin.ARRAY - override fun isSimilar(t: Type?): Boolean { - if (t !is PointerType) { - return false - } - return (referenceDepth == t.referenceDepth && - elementType.isSimilar(t.root) && - super.isSimilar(t)) - } - override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is PointerType) return false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.kt index b4096c70203..3ed69cfe7f0 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.kt @@ -68,10 +68,6 @@ class ReferenceType : Type, SecondOrderType { return elementType.dereference() } - override fun isSimilar(t: Type?): Boolean { - return t is ReferenceType && t.elementType == this && super.isSimilar(t) - } - override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is ReferenceType) return false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/StringType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/StringType.kt index 1b2fda27ce1..3ecbc2fd9e6 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/StringType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/StringType.kt @@ -31,4 +31,10 @@ class StringType( typeName: CharSequence = "", language: Language<*>? = null, generics: List = listOf() -) : ObjectType(typeName, generics, false, language) +) : ObjectType(typeName, generics, false, language) { + + init { + // Built-in types are always resolved + this.typeOrigin = Origin.RESOLVED + } +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt index 52bb462c729..bba1442f621 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt @@ -29,10 +29,12 @@ import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.parseName import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin import de.fraunhofer.aisec.cpg.passes.TypeHierarchyResolver +import de.fraunhofer.aisec.cpg.passes.TypeResolver import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.NodeEntity @@ -72,6 +74,12 @@ abstract class Type : Node { open var typeOrigin: Origin? = null + /** + * This points to the [DeclaresType] node (most likely a [Declaration]), that declares this + * type. At some point this should replace [ObjectType.recordDeclaration]. + */ + @PopulatedByPass(TypeResolver::class) var declaredFrom: DeclaresType? = null + constructor() { name = Name(EMPTY_NAME, null, language) } @@ -177,24 +185,19 @@ abstract class Type : Node { this is IncompleteType || this is ParameterizedType) - /** - * Required for possibleSubTypes to check if the new Type should be considered a subtype or not - * - * @param t other type the similarity is checked with - * @return True if the parameter t is equal to the current type (this) - */ - open fun isSimilar(t: Type?): Boolean { - return if (this == t) { - true - } else this.root.name == t?.root?.name - } - override fun equals(other: Any?): Boolean { if (this === other) return true - return other is Type && name == other.name && language == other.language + return other is Type && + name == other.name && + scope === other.scope && + language == other.language } - override fun hashCode() = Objects.hash(name, language) + /** + * We need a constant hashcode implementation because we need to change [name] and [scope] + * during the [TypeResolver], so we cannot use them for hashcode. + */ + override fun hashCode() = 1 override fun toString(): String { return ToStringBuilder(this, TO_STRING_STYLE).append("name", name).toString() @@ -218,7 +221,7 @@ abstract class Type : Node { // array val operations = mutableListOf() - var type: Type = this as Type + var type = this while (type is SecondOrderType) { var op = if (type is ReferenceType) { @@ -276,13 +279,12 @@ fun TypeOperations.apply(root: Type): Type { if (this.isNotEmpty()) { for (i in this.size - 1 downTo 0) { var wrap = this[i] - if (wrap == TypeOperation.REFERENCE) { - type = ReferenceType(type) - } else if (wrap == TypeOperation.ARRAY) { - type = type.reference(PointerType.PointerOrigin.ARRAY) - } else if (wrap == TypeOperation.POINTER) { - type = type.reference(PointerType.PointerOrigin.POINTER) - } + type = + when (wrap) { + TypeOperation.REFERENCE -> ReferenceType(type) + TypeOperation.ARRAY -> type.reference(PointerOrigin.ARRAY) + TypeOperation.POINTER -> type.reference(PointerOrigin.POINTER) + } } } @@ -299,3 +301,8 @@ var Type.recordDeclaration: RecordDeclaration? this.recordDeclaration = value } } + +interface DeclaresType { + + val declaredType: Type +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt index d799ef140ca..c08d6263d80 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt @@ -43,6 +43,11 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { private val callsInferredFunctions = mutableListOf() override fun accept(component: Component) { + log.info( + "Function summaries database has {} entries", + config.functionSummaries.functionToDFGEntryMap.size + ) + val inferDfgForUnresolvedCalls = config.inferenceConfiguration.inferDfgForUnresolvedSymbols val walker = IterativeGraphWalker() walker.registerOnNodeVisit { node, parent -> diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ImportResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ImportResolver.kt index ce80cdd98d3..eaa0b929819 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ImportResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ImportResolver.kt @@ -32,12 +32,10 @@ import de.fraunhofer.aisec.cpg.graph.declarations.ImportDeclaration import de.fraunhofer.aisec.cpg.graph.scopes.NameScope import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker -import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn /** * This pass looks for [ImportDeclaration] nodes and imports symbols into their respective [Scope] */ -@DependsOn(TypeHierarchyResolver::class) class ImportResolver(ctx: TranslationContext) : ComponentPass(ctx) { lateinit var walker: SubgraphWalker.ScopedWalker diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt index 516ffd27938..47358e02741 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt @@ -88,6 +88,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { protected val templateList = mutableListOf() override fun accept(component: Component) { + ctx.currentComponent = component walker = ScopedWalker(scopeManager) walker.registerHandler(::findTemplates) @@ -277,45 +278,6 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { return scopeManager.globalScope?.astNode?.startInference(ctx)?.inferVariableDeclaration(ref) } - /** - * Tries to infer a [RecordDeclaration] from an unresolved [Type]. This will return `null`, if - * inference was not possible, or if it was turned off in the [InferenceConfiguration]. - */ - private fun tryRecordInference(type: Type, locationHint: Node? = null): RecordDeclaration? { - val kind = - if (type.language is HasStructs) { - "struct" - } else { - "class" - } - // Determine the scope where we want to start our inference - var (scope, _) = scopeManager.extractScope(type) - - if (scope !is NameScope) { - scope = null - } - - var holder = scope?.astNode - - // If we could not find a scope, but we have an FQN, we can try to infer a namespace - var parentName = type.name.parent - if (scope == null && parentName != null) { - holder = tryNamespaceInference(parentName, type) - } - - val record = - (holder ?: currentTU) - .startInference(ctx) - ?.inferRecordDeclaration(type, kind, locationHint) - - // update the type's record - if (record != null) { - type.recordDeclaration = record - } - - return record - } - /** * We get the type of the "scope" this node is in. (e.g. for a field, we drop the field's name * and have the class) @@ -417,8 +379,8 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { } if (member == null) { member = - containingClass.superTypes - .flatMap { it.recordDeclaration?.fields ?: listOf() } + (containingClass.recordDeclaration?.superTypeDeclarations?.flatMap { it.fields } + ?: listOf()) .filter { it.name.localName == reference.name.localName } .map { it.definition } .firstOrNull() @@ -470,7 +432,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { if (record == null) { // We access an unknown field of an unknown record. so we need to handle that along the // way as well - record = tryRecordInference(base, locationHint = ref) + record = ctx.tryRecordInference(base, locationHint = ref) } if (record == null) { @@ -815,7 +777,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { if (records.isEmpty()) { records = listOfNotNull( - tryRecordInference(bestGuess?.root ?: unknownType(), locationHint = call) + ctx.tryRecordInference(bestGuess?.root ?: unknownType(), locationHint = call) ) } records = records.distinct() @@ -1054,11 +1016,69 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { } } } +} + +fun TranslationContext.tryNamespaceInference( + name: Name, + locationHint: Node? +): NamespaceDeclaration? { + return scopeManager.globalScope + ?.astNode + ?.startInference(this) + ?.inferNamespaceDeclaration(name, null, locationHint) +} + +/** + * Tries to infer a [RecordDeclaration] from an unresolved [Type]. This will return `null`, if + * inference was not possible, or if it was turned off in the [InferenceConfiguration]. + */ +fun TranslationContext.tryRecordInference( + type: Type, + locationHint: Node? = null +): RecordDeclaration? { + val kind = + if (type.language is HasStructs) { + "struct" + } else { + "class" + } + // Determine the scope where we want to start our inference + var (scope, _) = scopeManager.extractScope(type) + + if (scope !is NameScope) { + scope = null + } + + var holder = scope?.astNode + + // If we could not find a scope, but we have an FQN, we can try to infer a namespace (or a + // parent record) + var parentName = type.name.parent + if (scope == null && parentName != null) { + // At this point, we need to check whether we have any type reference to our parent + // name. If we have (e.g. it is used in a function parameter, variable, etc.), then we + // have a high chance that this is actually a parent record and not a namespace + var parentType = typeManager.typeExists(parentName) + holder = + if (parentType != null) { + tryRecordInference(parentType, locationHint = locationHint) + } else { + tryNamespaceInference(parentName, locationHint = locationHint) + } + } - fun tryNamespaceInference(name: Name, type: Type): NamespaceDeclaration? { - return scopeManager.globalScope - ?.astNode - ?.startInference(ctx) - ?.inferNamespaceDeclaration(name, null, type) + val record = + (holder ?: this.scopeManager.globalScope?.astNode) + ?.startInference(this) + ?.inferRecordDeclaration(type, kind, locationHint) + + // update the type's record. Because types are only unique per scope, we potentially need to + // update multiple type nodes, i.e., all type nodes whose FQN match the inferred record + if (record != null) { + typeManager.firstOrderTypes + .filter { it.name == record.name } + .forEach { it.recordDeclaration = record } } + + return record } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeResolver.kt index 3def0992f55..3aa47522036 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeResolver.kt @@ -26,40 +26,114 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.TranslationContext -import de.fraunhofer.aisec.cpg.graph.Component -import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration +import de.fraunhofer.aisec.cpg.graph.types.DeclaresType import de.fraunhofer.aisec.cpg.graph.types.ObjectType import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.processing.IVisitor -import de.fraunhofer.aisec.cpg.processing.strategy.Strategy +import de.fraunhofer.aisec.cpg.graph.types.recordDeclaration +import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker +import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn /** * The purpose of this [Pass] is to establish a relationship between [Type] nodes (more specifically * [ObjectType]s) and their [RecordDeclaration]. */ +@DependsOn(ImportResolver::class) open class TypeResolver(ctx: TranslationContext) : ComponentPass(ctx) { + + lateinit var walker: SubgraphWalker.ScopedWalker + override fun accept(component: Component) { - component.accept( - Strategy::AST_FORWARD, - object : IVisitor() { - /** - * Creates the [ObjectType.recordDeclaration] relationship between [ObjectType]s and - * [RecordDeclaration] with the same [Node.name]. - */ - fun visit(record: RecordDeclaration) { - for (t in typeManager.firstOrderTypes) { - if (t.name == record.name && t is ObjectType) { - // The node is the class of the type t - t.recordDeclaration = record - } - } + resolveFirstOrderTypes() + refreshNames() + + walker = SubgraphWalker.ScopedWalker(scopeManager) + walker.registerHandler(::handleNode) + walker.iterate(component) + } + + private fun refreshNames() { + for (type in typeManager.secondOrderTypes) { + type.refreshNames() + } + } + + private fun resolveType(type: Type): Boolean { + // Let's start by looking up the type according to their name and scope. We exclusively + // filter for nodes that implement DeclaresType, because otherwise we will get a lot of + // constructor declarations and such with the same name. It seems this is ok since most + // languages will prefer structs/classes over functions when resolving types. + var symbols = + scopeManager.findSymbols(type.name, startScope = type.scope) { it is DeclaresType } + + // We need to have a single match, otherwise we have an ambiguous type and we cannot + // normalize it. + // TODO: Maybe we should have a warning in this case? + var declares = symbols.filterIsInstance().singleOrNull() + + // Check for a possible typedef + var target = scopeManager.typedefFor(type.name, type.scope) + if (target != null) { + if (target.typeOrigin == Type.Origin.UNRESOLVED) { + // Make sure our typedef target is resolved + resolveType(target) + } + + var originDeclares = target.recordDeclaration + var name = target.name + log.debug("Aliasing type {} in {} scope to {}", type.name, type.scope, name) + type.declaredFrom = originDeclares + type.recordDeclaration = originDeclares + type.typeOrigin = Type.Origin.RESOLVED + return true + } + + if (declares == null) { + declares = ctx.tryRecordInference(type, locationHint = type) + } + + // If we found the "real" declared type, we can normalize the name of our scoped type and + // set the name to the declared type. + if (declares != null) { + var declaredType = declares.declaredType + log.debug( + "Resolving type {} in {} scope to {}", + type.name, + type.scope, + declaredType.name + ) + type.name = declaredType.name + type.declaredFrom = declares + type.recordDeclaration = declares as? RecordDeclaration + type.typeOrigin = Type.Origin.RESOLVED + type.superTypes.addAll(declaredType.superTypes) + return true + } + + return false + } + + private fun handleNode(node: Node?) { + if (node is RecordDeclaration) { + for (t in typeManager.firstOrderTypes) { + if (t.name == node.name && t is ObjectType) { + // The node is the class of the type t + t.recordDeclaration = node } } - ) + } } override fun cleanup() { // Nothing to do } + + fun resolveFirstOrderTypes() { + for (type in typeManager.firstOrderTypes) { + if (type is ObjectType && type.typeOrigin == Type.Origin.UNRESOLVED) { + resolveType(type) + } + } + } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/DFGFunctionSummaries.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/DFGFunctionSummaries.kt index 3b4a65b2e28..47a78280c0a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/DFGFunctionSummaries.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/DFGFunctionSummaries.kt @@ -31,17 +31,21 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.registerKotlinModule import de.fraunhofer.aisec.cpg.IncompatibleSignature -import de.fraunhofer.aisec.cpg.TranslationConfiguration.Builder import de.fraunhofer.aisec.cpg.ancestors import de.fraunhofer.aisec.cpg.frontends.CastNotPossible +import de.fraunhofer.aisec.cpg.graph.ContextProvider +import de.fraunhofer.aisec.cpg.graph.LanguageProvider import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.objectType import de.fraunhofer.aisec.cpg.graph.parseName import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.unknownType import de.fraunhofer.aisec.cpg.matchesSignature import de.fraunhofer.aisec.cpg.tryCast import java.io.File +import org.slf4j.Logger +import org.slf4j.LoggerFactory /** * If the user of the library registers one or multiple DFG-function summary files (via @@ -113,9 +117,11 @@ class DFGFunctionSummaries { private fun findFunctionDeclarationEntry(functionDecl: FunctionDeclaration): List? { if (functionToDFGEntryMap.isEmpty()) return null + val provider = functionDecl val language = functionDecl.language val languageName = language?.javaClass?.name val methodName = functionDecl.name + // The language and the method name have to match. If a signature is specified, it also has // to match to the one of the FunctionDeclaration, null indicates that we accept everything. val matchingEntries = @@ -125,9 +131,7 @@ class DFGFunctionSummaries { // Split the name if we have a FQN val entryMethodName = language.parseName(it.methodName) val entryRecord = - entryMethodName.parent?.let { - functionDecl.objectType(entryMethodName.parent) - } + entryMethodName.parent?.let { provider.lookupType(entryMethodName.parent) } methodName.lastPartsMatch( entryMethodName.localName ) && // The local name has to match @@ -144,13 +148,22 @@ class DFGFunctionSummaries { (it.signature == null || functionDecl.matchesSignature( it.signature.map { signatureType -> - functionDecl.objectType(signatureType) + provider.lookupType(signatureType) } ) != IncompatibleSignature) } else { false } } + + log.debug( + "Found {} matching entries for function declaration {} with parameter types {} in {}", + matchingEntries.size, + functionDecl.name, + functionDecl.signatureTypes.map(Node::name), + functionDecl.scope + ) + return if (matchingEntries.size == 1) { // Only one entry => We take this one. functionToDFGEntryMap[matchingEntries.single()] @@ -340,5 +353,22 @@ class DFGFunctionSummaries { files.forEach { dfgFunctionSummaries.addEntriesFromFile(it) } return dfgFunctionSummaries } + + val log: Logger = LoggerFactory.getLogger(DFGFunctionSummaries::class.java) + } + + fun ContextProvider.lookupType(fqn: CharSequence): Type { + // Try to look up the type from the specified FQN string + var type = + ctx?.typeManager?.lookupResolvedType( + fqn.toString(), + language = (this as? LanguageProvider)?.language + ) + return if (type == null) { + log.warn("Could not find specified type $fqn. Using UnknownType") + this.unknownType() + } else { + type + } } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt index aef654a59c7..9b047ffff14 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt @@ -446,7 +446,7 @@ class Inference internal constructor(val start: Node, override val ctx: Translat } } - fun inferNamespaceDeclaration(name: Name, path: String?, origin: Node): NamespaceDeclaration? { + fun inferNamespaceDeclaration(name: Name, path: String?, origin: Node?): NamespaceDeclaration? { if (!ctx.config.inferenceConfiguration.inferNamespaces) { return null } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt index 11599bb56ae..c7c08572006 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt @@ -219,11 +219,11 @@ class TypePropagationTest { val b = main.variables["b"] assertNotNull(b) - assertEquals(objectType("BaseClass").pointer(), b.type) + assertEquals(assertResolvedType("BaseClass").pointer(), b.type) assertEquals( setOf( - objectType("BaseClass").pointer(), - objectType("DerivedClass").pointer(), + assertResolvedType("BaseClass").pointer(), + assertResolvedType("DerivedClass").pointer(), ), b.assignedTypes ) diff --git a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/test/TestUtils.kt b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/test/TestUtils.kt index 06239afb823..0a625fb8d99 100644 --- a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/test/TestUtils.kt +++ b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/test/TestUtils.kt @@ -34,6 +34,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.test.TestUtils.ENFORCE_MEMBER_EXPRESSION import de.fraunhofer.aisec.cpg.test.TestUtils.ENFORCE_REFERENCES import java.io.File @@ -316,3 +317,9 @@ fun assertLocalName(localName: String, node: Node?, message: String? = null) { fun assertLiteralValue(expected: T, expr: Expression?, message: String? = null) { assertEquals(expected, assertIs>(expr).value, message) } + +context(ContextProvider) +fun assertResolvedType(fqn: String, generics: List? = null): Type { + var type = ctx?.typeManager?.lookupResolvedType(fqn, generics) + return assertNotNull(type) +} diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt index 5c3403f3c14..eec0f337add 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt @@ -65,7 +65,6 @@ import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTLiteralExpression import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTQualifiedName import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTTemplateId import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTTypeId -import org.eclipse.cdt.internal.core.model.ASTStringUtil import org.eclipse.cdt.internal.core.parser.IMacroDictionary import org.eclipse.cdt.internal.core.parser.scanner.InternalFileContent import org.eclipse.cdt.internal.core.parser.scanner.InternalFileContentProvider @@ -496,9 +495,6 @@ open class CXXLanguageFrontend(language: Language, ctx: Tra } fun typeOf(specifier: IASTDeclSpecifier, hint: Declaration? = null): Type { - // Retrieve the "name" of this type, including qualifiers. - val name = ASTStringUtil.getSignatureString(specifier, null) - var resolveTypeDef = false var type = @@ -519,24 +515,20 @@ open class CXXLanguageFrontend(language: Language, ctx: Tra resolveTypeDef = true typeOf(specifier.name) } else { - // Case b: Peek into our symbols. This is most likely limited to our current - // translation unit resolveTypeDef = true - val decl = scopeManager.getRecordForName(Name(name)) - - // We found a symbol, so we can use its name - if (decl != null) { - objectType(decl.name) + // It could be, that this is a parameterized type + val paramType = + typeManager.searchTemplateScopeForDefinedParameterizedTypes( + scopeManager.currentScope, + specifier.name.toString() + ) + if (paramType != null) { + paramType } else { - // It could be, that this is a parameterized type - val paramType = - typeManager.searchTemplateScopeForDefinedParameterizedTypes( - scopeManager.currentScope, - specifier.name.toString() - ) - // Otherwise, we keep it as a local name and hope for the best - paramType ?: typeOf(specifier.name) + // Otherwise, we keep it as a local name and the type normalizer will + // take care of it + typeOf(specifier.name) } } } @@ -545,13 +537,13 @@ open class CXXLanguageFrontend(language: Language, ctx: Tra // in handleSimpleDeclaration, but we might want to move it here resolveTypeDef = true - objectType(specifier.name.toString()) + objectType(specifier.name.toString(), rawNode = specifier) } is IASTElaboratedTypeSpecifier -> { resolveTypeDef = true // A class or struct - objectType(specifier.name.toString()) + objectType(specifier.name.toString(), rawNode = specifier) } else -> { unknownType() @@ -584,28 +576,35 @@ open class CXXLanguageFrontend(language: Language, ctx: Tra } // __typeof__ type specifier.type == IASTSimpleDeclSpecifier.t_typeof -> { - objectType("typeof(${specifier.declTypeExpression.rawSignature})") + objectType( + "typeof(${specifier.declTypeExpression.rawSignature})", + rawNode = specifier + ) } // A decl type specifier.type == IASTSimpleDeclSpecifier.t_decltype -> { - objectType("decltype(${specifier.declTypeExpression.rawSignature})") + objectType( + "decltype(${specifier.declTypeExpression.rawSignature})", + rawNode = specifier + ) } // The type of constructor declaration is always the declaration itself specifier.type == IASTSimpleDeclSpecifier.t_unspecified && hint is ConstructorDeclaration -> { - hint.name.parent?.let { objectType(it) } ?: unknownType() + hint.name.parent?.let { objectType(it, rawNode = specifier) } ?: unknownType() } // The type of conversion operator is also always the declaration itself specifier.type == IASTSimpleDeclSpecifier.t_unspecified && hint is MethodDeclaration && hint.name.localName == "operator#0" -> { - hint.name.parent?.let { objectType(it) } ?: unknownType() + hint.name.parent?.let { objectType(it, rawNode = specifier) } ?: unknownType() } // The type of conversion operator is also always the declaration itself specifier.type == IASTSimpleDeclSpecifier.t_unspecified && hint is MethodDeclaration && hint.name.localName == "operator#0*" -> { - hint.name.parent?.let { objectType(it).pointer() } ?: unknownType() + hint.name.parent?.let { objectType(it, rawNode = specifier).pointer() } + ?: unknownType() } // The type of destructor is unspecified, but we model it as a void type to make it // compatible with other methods. @@ -682,7 +681,7 @@ open class CXXLanguageFrontend(language: Language, ctx: Tra } } - return objectType(fqn, generics) + return objectType(fqn, generics, rawNode = name) } var typeName = @@ -692,14 +691,10 @@ open class CXXLanguageFrontend(language: Language, ctx: Tra parseName(name.toString()) } - // Rather hacky, but currently the only way to do this, until we redesign the aliases in the - // scope manager to be valid for a scope and not for a location - val location = currentTU?.location - // We need to take name(space) aliases into account. typeName = scopeManager.resolveParentAlias(typeName, scopeManager.currentScope) - return objectType(typeName) + return objectType(typeName, rawNode = name) } /** diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt index 71c3ecd172f..74451bf7ca8 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt @@ -541,10 +541,8 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : // typedef'd name is called S. However, to make things a little bit easier // we also transfer the name to the record declaration. ctx.declarators.firstOrNull()?.name?.toString()?.let { - primaryDeclaration?.name = parseName(it) - // We need to inform the later steps that we want to take the name - // of this declaration as the basis for the result type of the typedef - useNameOfDeclarator = true + primaryDeclaration.name = parseName(it) + useNameOfDeclarator = false } } frontend.processAttributes(primaryDeclaration, ctx) @@ -647,21 +645,25 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : // Loop through its members for (enumerator in declSpecifier.enumerators) { + // Enums are a bit complicated. Their fully qualified name (in C++) includes the enum + // class, so e.g. `MyEnum::THIS'. In order to do that, we need to be in the `MyEnum` + // scope when we create it. But, the symbol of the enum can both be resolved using just + // the enum constant `THIS` as well as `MyEnum::THIS` (at least in C++11). So we need to + // put the symbol both in the outer scope as well as the enum's scope. + frontend.scopeManager.enterScope(enum) val enumConst = newEnumConstantDeclaration( enumerator.name.toString(), rawNode = enumerator, ) + frontend.scopeManager.addDeclaration(enumConst) + frontend.scopeManager.leaveScope(enum) // In C/C++, default enums are of type int enumConst.type = primitiveType("int") - // We need to make them visible to the enclosing scope. However, we do NOT - // want to add it to the AST of the enclosing scope, but to the AST of the - // EnumDeclaration + // Also put the symbol in the outer scope (but do not add AST nodes) frontend.scopeManager.addDeclaration(enumConst, false) - - entries += enumConst } enum.entries = entries 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 210ab9a5dfa..0b83ba09098 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 @@ -451,7 +451,9 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : // Handle C++ classes if (ctx is CPPASTCompositeTypeSpecifier) { recordDeclaration.superClasses = - ctx.baseSpecifiers.map { objectType(it.nameSpecifier.toString()) }.toMutableList() + ctx.baseSpecifiers + .map { objectType(it.nameSpecifier.toString(), rawNode = it) } + .toMutableList() } frontend.scopeManager.addDeclaration(recordDeclaration) diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypedefTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypedefTest.kt index 2f31de4b2fe..319cb8b0774 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypedefTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypedefTest.kt @@ -28,7 +28,6 @@ package de.fraunhofer.aisec.cpg.enhancements.types import de.fraunhofer.aisec.cpg.frontends.cxx.CPPLanguage import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration -import de.fraunhofer.aisec.cpg.graph.objectType import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType import de.fraunhofer.aisec.cpg.graph.types.IntegerType import de.fraunhofer.aisec.cpg.graph.types.NumericType @@ -80,7 +79,7 @@ internal class TypedefTest : BaseTest() { assertEquals(NumericType.Modifier.UNSIGNED, returnType.modifier) assertEquals(uintfp1.type, uintfp2?.type) - val type = tu.ctx?.scopeManager?.typedefFor(objectType("test")) + val type = tu.ctx?.scopeManager?.typedefFor(Name("test")) assertIs(type) assertLocalName("uint8_t", type) } @@ -159,7 +158,7 @@ internal class TypedefTest : BaseTest() { val fPtr2 = tu.variables["intFptr2"] assertEquals(fPtr1?.type, fPtr2?.type) - val type = tu.ctx?.scopeManager?.typedefFor(objectType("type_B")) + val type = tu.ctx?.scopeManager?.typedefFor(Name("type_B")) assertLocalName("template_class_A", type) assertIs(type) assertEquals(listOf(primitiveType("int"), primitiveType("int")), type.generics) diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/variable_resolution/VariableResolverCppTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/variable_resolution/VariableResolverCppTest.kt index efe7fcc58ce..eba1826ee98 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/variable_resolution/VariableResolverCppTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/variable_resolution/VariableResolverCppTest.kt @@ -74,7 +74,10 @@ internal class VariableResolverCppTest : BaseTest() { listOf("scope_variables.cpp", "external_class.cpp").map { topLevel.resolve(it).toFile() } - val result = analyze(files, topLevel, true) { it.registerLanguage() } + val result = + analyze(files, topLevel, true) { + it.registerLanguage().useUnityBuild(true) + } val calls = result.calls { it.name.localName == "printLog" } val records = result.records val functions = result.functions 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 af5f0613e0a..908f2ff0a28 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 @@ -203,9 +203,18 @@ class CXXDeclarationTest { val manipulateString = result.functions["manipulateString"] assertNotNull(manipulateString) + assertFalse(manipulateString.isInferred) + + val size = result.functions["size"] + assertNotNull(size) + assertFalse(size.isInferred) // We should be able to resolve all calls to manipulateString - val calls = result.calls("manipulateString") + var calls = result.calls("manipulateString") calls.forEach { assertContains(it.invokes, manipulateString) } + + // We should be able to resolve all calls to size + calls = result.calls("size") + calls.forEach { assertContains(it.invokes, size) } } } diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXInferenceTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXInferenceTest.kt index 57782ca1d0e..b104e30113a 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXInferenceTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXInferenceTest.kt @@ -31,6 +31,7 @@ import java.io.File import kotlin.test.Test import kotlin.test.assertContains import kotlin.test.assertNotNull +import kotlin.test.assertTrue class CXXInferenceTest { @Test @@ -67,4 +68,28 @@ class CXXInferenceTest { val someClass = util.records["SomeClass"] assertNotNull(someClass) } + + @Test + fun testTrickyInference() { + val file = File("src/test/resources/cxx/tricky_inference.cpp") + val tu = + analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) { + it.registerLanguage() + it.loadIncludes(false) + it.addIncludesToGraph(false) + } + assertNotNull(tu) + + val some = tu.namespaces["some"] + assertNotNull(some) + assertTrue(some.isInferred) + + val json = some.records["json"] + assertNotNull(json) + assertTrue(json.isInferred) + + val iterator = json.records["iterator"] + assertNotNull(iterator) + assertTrue(iterator.isInferred) + } } diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt index ec9ec9fb936..a61639add92 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt @@ -55,31 +55,36 @@ internal class CXXLanguageFrontendTest : BaseTest() { analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) { it.registerLanguage() } - val main = tu.functions["main"] - assertNotNull(main) + with(tu) { + val main = tu.functions["main"] + assertNotNull(main) - val decl = main - val ls = decl.variables["ls"] - assertNotNull(ls) - assertEquals(tu.objectType("std::vector", listOf(tu.objectType("int"))), ls.type) - assertLocalName("ls", ls) + val decl = main + val ls = decl.variables["ls"] + assertNotNull(ls) + assertEquals( + assertResolvedType("std::vector", listOf(assertResolvedType("int"))), + ls.type + ) + assertLocalName("ls", ls) - val forEachStatement = decl.forEachLoops.firstOrNull() - assertNotNull(forEachStatement) + val forEachStatement = decl.forEachLoops.firstOrNull() + assertNotNull(forEachStatement) - // should loop over ls - assertEquals(ls, (forEachStatement.iterable as Reference).refersTo) + // should loop over ls + assertEquals(ls, (forEachStatement.iterable as Reference).refersTo) - // should declare auto i (so far no concrete type inferrable) - val stmt = forEachStatement.variable - assertNotNull(stmt) - assertTrue(stmt is DeclarationStatement) - assertTrue(stmt.isSingleDeclaration()) + // should declare auto i (so far no concrete type inferrable) + val stmt = forEachStatement.variable + assertNotNull(stmt) + assertTrue(stmt is DeclarationStatement) + assertTrue(stmt.isSingleDeclaration()) - val i = stmt.singleDeclaration as VariableDeclaration - assertNotNull(i) - assertLocalName("i", i) - assertIs(i.type) + val i = stmt.singleDeclaration as VariableDeclaration + assertNotNull(i) + assertLocalName("i", i) + assertIs(i.type) + } } @Test @@ -137,7 +142,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { val sizeof = i.initializer as? TypeIdExpression assertNotNull(sizeof) assertLocalName("sizeof", sizeof) - assertEquals(tu.objectType("std::size_t"), sizeof.type) + assertEquals(assertResolvedType("std::size_t"), sizeof.type) val typeInfo = funcDecl.variables["typeInfo"] assertNotNull(typeInfo) @@ -146,7 +151,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertNotNull(typeid) assertLocalName("typeid", typeid) - assertEquals(objectType("std::type_info").ref(), typeid.type) + assertEquals(assertResolvedType("std::type_info").ref(), typeid.type) val j = funcDecl.variables["j"] assertNotNull(j) @@ -155,7 +160,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertNotNull(sizeof) assertNotNull(alignOf) assertLocalName("alignof", alignOf) - assertEquals(tu.objectType("std::size_t"), alignOf.type) + assertEquals(assertResolvedType("std::size_t"), alignOf.type) } } @@ -173,16 +178,16 @@ internal class CXXLanguageFrontendTest : BaseTest() { val e = main.variables["e"] assertNotNull(e) - assertEquals(objectType("ExtendedClass").pointer(), e.type) + assertEquals(assertResolvedType("ExtendedClass").pointer(), e.type) val b = main.variables["b"] assertNotNull(b) - assertEquals(objectType("BaseClass").pointer(), b.type) + assertEquals(assertResolvedType("BaseClass").pointer(), b.type) // initializer var cast = b.initializer as? CastExpression assertNotNull(cast) - assertEquals(objectType("BaseClass").pointer(), cast.castType) + assertEquals(assertResolvedType("BaseClass").pointer(), cast.castType) val staticCast = main.assigns.getOrNull(0) assertNotNull(staticCast) @@ -386,7 +391,10 @@ internal class CXXLanguageFrontendTest : BaseTest() { (statements[0] as DeclarationStatement).getSingleDeclarationAs( VariableDeclaration::class.java ) - assertEquals(objectType("SSL_CTX").pointer(), declFromMultiplicateExpression.type) + assertEquals( + assertResolvedType("SSL_CTX").pointer(), + declFromMultiplicateExpression.type + ) assertLocalName("ptr", declFromMultiplicateExpression) val withInitializer = @@ -899,20 +907,20 @@ internal class CXXLanguageFrontendTest : BaseTest() { (statement.statements[0] as DeclarationStatement).singleDeclaration as VariableDeclaration // type should be Integer - assertEquals(tu.objectType("Integer"), i.type) + assertEquals(assertResolvedType("Integer"), i.type) // initializer should be a construct expression var constructExpr = i.initializer as? ConstructExpression assertNotNull(constructExpr) // type of the construct expression should also be Integer - assertEquals(tu.objectType("Integer"), constructExpr.type) + assertEquals(assertResolvedType("Integer"), constructExpr.type) // auto (Integer) m val m = (statement.statements[6] as DeclarationStatement).singleDeclaration as VariableDeclaration // type should be Integer* - assertEquals(objectType("Integer").pointer(), m.type) + assertEquals(assertResolvedType("Integer").pointer(), m.type) val constructor = constructExpr.constructor assertNotNull(constructor) @@ -923,19 +931,19 @@ internal class CXXLanguageFrontendTest : BaseTest() { val newExpression = m.initializer as? NewExpression assertNotNull(newExpression) // type of the new expression should also be Integer* - assertEquals(objectType("Integer").pointer(), newExpression.type) + assertEquals(assertResolvedType("Integer").pointer(), newExpression.type) // initializer should be a construct expression constructExpr = newExpression.initializer as? ConstructExpression assertNotNull(constructExpr) // type of the construct expression should be Integer - assertEquals(objectType("Integer"), constructExpr.type) + assertEquals(assertResolvedType("Integer"), constructExpr.type) // argument should be named k and of type m val k = constructExpr.arguments[0] as Reference assertLocalName("k", k) // type of the construct expression should also be Integer - assertEquals(primitiveType("int"), k.type) + assertEquals(assertResolvedType("int"), k.type) } } @@ -1256,7 +1264,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { var cast = count.initializer assertIs(cast) - assertLocalName("size_t", cast.castType) + assertLocalName("int", cast.castType) assertLiteralValue(42, cast.expression) val addr = tu.variables["addr"] @@ -1351,6 +1359,26 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertNotNull((returnStatement.returnValue as? Reference)?.refersTo) } + @Test + @Throws(Exception::class) + fun testEnumCPP() { + val file = File("src/test/resources/cxx/enum.cpp") + val tu = + analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) { + it.registerLanguage() + } + // TU should only contain two AST declarations (EnumDeclaration and FunctionDeclaration), + // but NOT any EnumConstantDeclarations + assertEquals(2, tu.declarations.size) + + val main = tu.functions["main"] + assertNotNull(main) + + val returnStatement = main.bodyOrNull() + assertNotNull(returnStatement) + assertNotNull((returnStatement.returnValue as? Reference)?.refersTo) + } + @Test @Throws(Exception::class) fun testStruct() { @@ -1384,7 +1412,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { it.registerLanguage() } with(tu) { - val typedefs = tu.ctx?.scopeManager?.typedefFor(objectType("MyStruct")) + val typedefs = tu.ctx?.scopeManager?.typedefFor(Name("MyStruct")) assertLocalName("__myStruct", typedefs) val main = tu.functions["main"] @@ -1661,36 +1689,14 @@ internal class CXXLanguageFrontendTest : BaseTest() { } assertNotNull(result) - val std = result.namespaces["std"] - assertNotNull(std) - - val string = std.records["string"] - assertNotNull(string) - - val cStr = string.methods["c_str"] - assertNotNull(cStr) - - /*val cStrCall = result.mcalls["c_str"] - assertNotNull(cStrCall) - assertInvokes(cStrCall, cStr)*/ - - var scope = - result.functions["function1"]?.body?.let { - result.finalCtx.scopeManager.lookupScope(it) - } - assertNotNull(scope) - - var lookup = scope.lookupSymbol("string").singleOrNull() - assertEquals(string, lookup) - - scope = - result.functions["function2"]?.body?.let { - result.finalCtx.scopeManager.lookupScope(it) - } - assertNotNull(scope) + // There should be no type "string" anymore, only "std::string" + assertFalse(result.finalCtx.typeManager.typeExists("string")) + assertTrue(result.finalCtx.typeManager.typeExists("std::string")) - lookup = scope.lookupSymbol("string").singleOrNull() - assertEquals(string, lookup) + // the same applies to "inner::secret" + assertFalse(result.finalCtx.typeManager.typeExists("secret")) + assertFalse(result.finalCtx.typeManager.typeExists("inner::secret")) + assertTrue(result.finalCtx.typeManager.typeExists("std::inner::secret")) } @Test diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXResolveTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXResolveTest.kt index f4c28ca79f3..0d0c2aba9c9 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXResolveTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXResolveTest.kt @@ -52,22 +52,22 @@ class CXXResolveTest { val main = tu.functions["main"] assertNotNull(main) - // 0, and 1 are construct expressions -> our "real" calls start at index 2 - val aFoo = main.calls.getOrNull(2) + // 0, 1 and 2 are construct expressions -> our "real" calls start at index 3 + val aFoo = main.calls.getOrNull(3) assertIs(aFoo) assertLocalName("foo", aFoo) assertLocalName("a", aFoo.base) // a.foo should connect to A::foo assertLocalName("A", (aFoo.invokes.firstOrNull() as? MethodDeclaration)?.recordDeclaration) - val bFoo = main.calls.getOrNull(3) + val bFoo = main.calls.getOrNull(4) assertIs(bFoo) assertLocalName("foo", bFoo) assertLocalName("b", bFoo.base) // b.foo should connect to B::foo assertLocalName("B", (bFoo.invokes.firstOrNull() as? MethodDeclaration)?.recordDeclaration) - val foo = main.calls.getOrNull(4) + val foo = main.calls.getOrNull(5) assertNotNull(foo) // foo should be connected to an inferred non-method function @@ -77,7 +77,7 @@ class CXXResolveTest { assertFalse(func is MethodDeclaration) assertTrue(func.isInferred) - val cFoo = main.calls.getOrNull(5) + val cFoo = main.calls.getOrNull(6) assertNotNull(cFoo) // c.foo should connect to C::foo diff --git a/cpg-language-cxx/src/test/resources/cxx/alias.cpp b/cpg-language-cxx/src/test/resources/cxx/alias.cpp index 50e5a2828d1..d156333d2a7 100644 --- a/cpg-language-cxx/src/test/resources/cxx/alias.cpp +++ b/cpg-language-cxx/src/test/resources/cxx/alias.cpp @@ -1,5 +1,12 @@ namespace std { - class string {}; + class string { +public: + int size() { + return 1; + } + + class iterator {}; + }; } // this is completely equivalent to a typedef @@ -20,4 +27,12 @@ int main() { manipulateString(s1); manipulateString(s2); + + s1.size(); + s2.size(); + + mystring1::iterator it1; + mystring2::iterator it2; + std::string it3; + estd::string it4; } diff --git a/cpg-language-cxx/src/test/resources/cxx/enum.cpp b/cpg-language-cxx/src/test/resources/cxx/enum.cpp new file mode 100644 index 00000000000..a30c3d618ec --- /dev/null +++ b/cpg-language-cxx/src/test/resources/cxx/enum.cpp @@ -0,0 +1,9 @@ +enum MyEnum { + THIS = 0, + THAT = 1, + THE_OTHER = 2, +}; + +int main() { + return MyEnum::THE_OTHER; +} \ No newline at end of file diff --git a/cpg-language-cxx/src/test/resources/cxx/parenthesis.cpp b/cpg-language-cxx/src/test/resources/cxx/parenthesis.cpp index 2ec6ce42785..3c28a674cc1 100644 --- a/cpg-language-cxx/src/test/resources/cxx/parenthesis.cpp +++ b/cpg-language-cxx/src/test/resources/cxx/parenthesis.cpp @@ -3,6 +3,8 @@ #include #include +typedef int size_t; + int main() { // this cast could be mistaken for a call expression size_t count = (size_t)(42); diff --git a/cpg-language-cxx/src/test/resources/cxx/tricky_inference.cpp b/cpg-language-cxx/src/test/resources/cxx/tricky_inference.cpp new file mode 100644 index 00000000000..6031949d9d9 --- /dev/null +++ b/cpg-language-cxx/src/test/resources/cxx/tricky_inference.cpp @@ -0,0 +1,37 @@ +// The headers are just there to make it compile with clang, but we will not parse headers. +// You can use `clang++ -std=c++20 tricky_inference.cpp` to check, if it will compile. +#include "tricky_inference.h" + +// We do not know "some::json", but this is a typedef to it. +// One could argue that "using some::json" might be more appropriate, +// but this is inspired by actual real-world code. But, this actually +// gives us the advantage, that we know that "json" is a type and not +// a namespace, so we need to use this information. +using json = some::json; + +// Next, we are creating some kind of class that only uses our "json" +// object, but does not actually call any functions on it. We could +// probably conform at this point, that we are dealing with a record +// and could infer it (since we do not know it). +class wrapper { +public: + json* get() { + return &j; + } + +private: + json j; +}; + +// For some more complexity, let's refer to a sub-class of it +void iterator(json::iterator& it) { + if (!it.hasNext()) { + return; + } +} + +// And lastly, finally call a method on it, so we can know it's +// a class. +void* get_data(json* j) { + return j->data; +} \ No newline at end of file diff --git a/cpg-language-cxx/src/test/resources/cxx/tricky_inference.h b/cpg-language-cxx/src/test/resources/cxx/tricky_inference.h new file mode 100644 index 00000000000..543a7d1b115 --- /dev/null +++ b/cpg-language-cxx/src/test/resources/cxx/tricky_inference.h @@ -0,0 +1,12 @@ +namespace some { + class json { +public: + class iterator { +public: + bool hasNext() { + return false; + } + }; + void* data; + }; +} \ No newline at end of file diff --git a/cpg-language-cxx/src/test/resources/cxx/using.cpp b/cpg-language-cxx/src/test/resources/cxx/using.cpp index 2d29ec435fb..bf7070364cc 100644 --- a/cpg-language-cxx/src/test/resources/cxx/using.cpp +++ b/cpg-language-cxx/src/test/resources/cxx/using.cpp @@ -1,26 +1,38 @@ namespace std - { - class string - { - public: - const char *c_str() const - { - return "mock"; - } - }; - } - -void function1() { - using namespace std; - string s; - s.c_str(); -} + namespace inner { + class secret { + void someFunction() { + secret* s = new secret(); + } + }; + } -void function2() + class string { - using std::string; - string s; - s.c_str(); - } + public: + const char *c_str() const + { + return "mock"; + } + }; + class other { + class sub {}; + private: + void doSomething() { + inner::secret secret; + sub sub; + other::sub sub2; + } + }; +} + +using namespace std; + +int main() +{ + inner::secret secret; + string s; + s.c_str(); +} diff --git a/cpg-language-cxx/src/test/resources/variables_extended/cpp/external_class.h b/cpg-language-cxx/src/test/resources/variables_extended/cpp/external_class.h index 8893c535b03..c1a5355dfa6 100644 --- a/cpg-language-cxx/src/test/resources/variables_extended/cpp/external_class.h +++ b/cpg-language-cxx/src/test/resources/variables_extended/cpp/external_class.h @@ -1,3 +1,4 @@ +#pragma once using namespace std; static string staticVarName;