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..c85180efd91 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 @@ -833,7 +833,7 @@ 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 @@ -849,6 +849,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,7 +1003,7 @@ class ScopeManager : ScopeProvider { return findSymbols(name).filterIsInstance().singleOrNull() } - fun typedefFor(alias: Type): Type? { + fun typedefFor(alias: Name): Type? { var current = currentScope // We need to build a path from the current scope to the top most one. This ensures us that @@ -1010,7 +1020,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 +1031,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 +1043,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 +1074,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..c9b335bf4f1 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,9 +228,13 @@ 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) return applicable ?: alias } } 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/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/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..d0b3fd6f2d0 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) } @@ -218,7 +226,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 +284,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 +306,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/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..e543504218f 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 @@ -297,10 +297,20 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { var holder = scope?.astNode - // If we could not find a scope, but we have an FQN, we can try to infer a namespace + // 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) { - holder = tryNamespaceInference(parentName, type) + // 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) + } } val record = @@ -308,9 +318,12 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { .startInference(ctx) ?.inferRecordDeclaration(type, kind, locationHint) - // update the type's record + // 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) { - type.recordDeclaration = record + typeManager.firstOrderTypes + .filter { it.name == record.name } + .forEach { it.recordDeclaration = record } } return record @@ -1055,10 +1068,10 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { } } - fun tryNamespaceInference(name: Name, type: Type): NamespaceDeclaration? { + fun tryNamespaceInference(name: Name, locationHint: Node?): NamespaceDeclaration? { return scopeManager.globalScope ?.astNode ?.startInference(ctx) - ?.inferNamespaceDeclaration(name, null, type) + ?.inferNamespaceDeclaration(name, null, locationHint) } } 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..ab53c03e769 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,87 @@ 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.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() + var declaredType = declares?.declaredType + + // 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 (declaredType != null) { + var name = declaredType.name + log.debug("Resolving type {} in {} scope to {}", type.name, type.scope, name) + type.name = name + type.declaredFrom = declares + type.typeOrigin = Type.Origin.RESOLVED + 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.filter { it.typeOrigin == Type.Origin.UNRESOLVED }) { + if (type is ObjectType) { + resolveType(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-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..133495f0e95 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 @@ -519,24 +519,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 +541,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 +580,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 +685,7 @@ open class CXXLanguageFrontend(language: Language, ctx: Tra } } - return objectType(fqn, generics) + return objectType(fqn, generics, rawNode = name) } var typeName = @@ -692,14 +695,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 11d47fdb842..535e2a84a65 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 @@ -646,21 +646,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/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 c02a689d28e..254f1898cef 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 @@ -1351,6 +1351,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 +1404,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 +1681,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/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/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(); +}