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 4302f58cda..4b3635a408 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 @@ -846,9 +846,9 @@ class ScopeManager : ScopeProvider { * will also be used as a return value of this function. This can be useful, if you create * objects, such as a [Node] inside this scope and want to return it to the calling function. */ - fun withScope(scope: Scope?, init: () -> T): T { + fun withScope(scope: Scope?, init: (scope: Scope?) -> T): T { val oldScope = jumpTo(scope) - val ret = init() + val ret = init(scope) jumpTo(oldScope) return ret diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt index b43cfb106b..c9e955ead6 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt @@ -27,7 +27,6 @@ package de.fraunhofer.aisec.cpg.frontends import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.TranslationContext -import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration @@ -215,9 +214,7 @@ interface HasAnonymousIdentifier : LanguageTrait { * [GlobalScope], i.e., not within a namespace, but directly contained in a * [TranslationUnitDeclaration]. */ -interface HasGlobalVariables : LanguageTrait { - val globalVariableScopeClass: Class -} +interface HasGlobalVariables : LanguageTrait /** * A language trait, that specifies that the language has so-called functional style casts, meaning 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 91e970c3c7..fa0ddb8419 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 @@ -28,8 +28,10 @@ package de.fraunhofer.aisec.cpg.graph.scopes import com.fasterxml.jackson.annotation.JsonBackReference import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.Node.Companion.TO_STRING_STYLE import de.fraunhofer.aisec.cpg.graph.statements.LabelStatement import de.fraunhofer.aisec.cpg.helpers.neo4j.NameConverter +import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.GeneratedValue import org.neo4j.ogm.annotation.Id import org.neo4j.ogm.annotation.NodeEntity @@ -112,4 +114,14 @@ abstract class Scope( return scope } + + override fun toString(): String { + val builder = ToStringBuilder(this, TO_STRING_STYLE) + + if (name?.isNotEmpty() == true) { + builder.append("name", name) + } + + return builder.toString() + } } 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 495a0530f7..c31d88cf7c 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 @@ -31,6 +31,7 @@ import de.fraunhofer.aisec.cpg.frontends.* import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.scopes.NameScope +import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.graph.scopes.StructureDeclarationScope import de.fraunhofer.aisec.cpg.graph.statements.expressions.* @@ -231,11 +232,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { // TODO: we need to do proper scoping (and merge it with the code above), but for now // this just enables CXX static fields - if ( - refersTo == null && - language != null && - language.namespaceDelimiter in current.name.toString() - ) { + if (refersTo == null && language != null && current.name.isQualified()) { recordDeclType = getEnclosingTypeOf(current) val field = resolveMember(recordDeclType, current) if (field != null) { @@ -318,16 +315,24 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { * 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, - ): RecordDeclaration? { + private fun tryRecordInference(type: Type, locationHint: Node? = null): RecordDeclaration? { val kind = if (type.language is HasStructs) { "struct" } else { "class" } - val record = type.startInference(ctx)?.inferRecordDeclaration(type, currentTU, kind) + // Determine the scope where we want to start our inference + var (scope, _) = scopeManager.extractScope(type) + + if (scope !is NameScope) { + scope = null + } + + val record = + (scope?.astNode ?: currentTU) + .startInference(ctx) + ?.inferRecordDeclaration(type, kind, locationHint) // update the type's record if (record != null) { @@ -447,7 +452,35 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { if (member == null && record is EnumDeclaration) { member = record.entries[reference.name.localName] } - return member ?: handleUnknownField(containingClass, reference) + + if (member != null) { + return member + } + + // This is a little bit of a workaround, but at least this makes sure we are not inferring a + // record, where a namespace already exist + val (scope, _) = scopeManager.extractScope(reference, null) + return if (scope == null) { + handleUnknownField(containingClass, reference) + } else { + // Workaround needed for Java. If we already have a record scope, use the "old" + // inference function + when (scope) { + is RecordScope -> handleUnknownField(containingClass, reference) + is NameScope -> { + log.warn( + "We should infer a namespace variable ${reference.name} at this point, but this is not yet implemented." + ) + null + } + else -> { + log.warn( + "We should infer a variable ${reference.name} in ${scope}, but this is not yet implemented." + ) + null + } + } + } } // TODO(oxisto): Move to inference class @@ -463,7 +496,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) + record = tryRecordInference(base, locationHint = ref) } if (record == null) { @@ -800,11 +833,9 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { val root = it.root as? ObjectType var record = root?.recordDeclaration if (root != null && record == null) { - record = - it.startInference(ctx) - ?.inferRecordDeclaration(root, currentTU, locationHint = call) - // update the record declaration - root.recordDeclaration = record + // We access an unknown method of an unknown record. so we need to handle that + // along the way as well + record = tryRecordInference(root, locationHint = call) } record } 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 997119b4fc..f7eda4086a 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 @@ -111,7 +111,7 @@ class Inference internal constructor(val start: Node, override val ctx: Translat debugWithFileLocation( hint, log, - "Inferred a new {} declaration {} with parameter types {}", + "Inferred a new {} declaration {} with parameter types {} in $it", if (inferred is MethodDeclaration) "method" else "function", inferred.name, signature.map { it?.name } @@ -174,7 +174,7 @@ class Inference internal constructor(val start: Node, override val ctx: Translat * executing the commands in [init] (which needs to create an inferred node of [T]) as well as * restoring the previous scope. */ - private fun inferInScopeOf(start: Node, init: () -> T): T { + private fun inferInScopeOf(start: Node, init: (scope: Scope?) -> T): T { return scopeManager.withScope(scopeManager.lookupScope(start), init) } @@ -359,7 +359,6 @@ class Inference internal constructor(val start: Node, override val ctx: Translat */ fun inferRecordDeclaration( type: Type, - currentTU: TranslationUnitDeclaration, kind: String = "class", locationHint: Node? = null ): RecordDeclaration? { @@ -367,6 +366,18 @@ class Inference internal constructor(val start: Node, override val ctx: Translat return null } + // We assume that the start is either a record, a namespace or the translation unit + val record = start as? RecordDeclaration + val namespace = start as? NamespaceDeclaration + val tu = start as? TranslationUnitDeclaration + + // If all are null, we have the wrong type + if (record == null && namespace == null && tu == null) { + throw UnsupportedOperationException( + "Starting inference with the wrong type of start node" + ) + } + if (type !is ObjectType) { errorWithFileLocation( locationHint, @@ -376,7 +387,7 @@ class Inference internal constructor(val start: Node, override val ctx: Translat return null } - return inferInScopeOf(currentTU) { + return inferInScopeOf(start) { // This could be a class or a struct. We start with a class and may have to fine-tune // this later. val declaration = newRecordDeclaration(type.typeName, kind) @@ -385,7 +396,7 @@ class Inference internal constructor(val start: Node, override val ctx: Translat debugWithFileLocation( locationHint, log, - "Inferred a new record declaration ${declaration.name} (${declaration.kind})" + "Inferred a new record declaration ${declaration.name} (${declaration.kind}) in $it" ) // Update the type @@ -419,7 +430,7 @@ class Inference internal constructor(val start: Node, override val ctx: Translat debugWithFileLocation( hint, log, - "Inferred a new variable declaration {} with type {}", + "Inferred a new variable declaration {} with type {} in $it", inferred.name, inferred.type ) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/DFGFunctionSummariesTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/DFGFunctionSummariesTest.kt index 108a1387e8..f7cbe96c3f 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/DFGFunctionSummariesTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/DFGFunctionSummariesTest.kt @@ -91,11 +91,9 @@ class DFGFunctionSummariesTest { val listType = t("test.List") ctx?.let { val recordDecl = - listType - .startInference(it) + this@translationUnit.startInference(it) ?.inferRecordDeclaration( listType, - this@translationUnit ) listType.recordDeclaration = recordDecl recordDecl?.addSuperClass(objectType) @@ -105,11 +103,9 @@ class DFGFunctionSummariesTest { val specialListType = t("test.SpecialList") ctx?.let { val recordDecl = - specialListType - .startInference(it) + this@translationUnit.startInference(it) ?.inferRecordDeclaration( specialListType, - this@translationUnit ) specialListType.recordDeclaration = recordDecl recordDecl?.addSuperClass(listType) @@ -119,11 +115,9 @@ class DFGFunctionSummariesTest { val verySpecialListType = t("test.VerySpecialList") ctx?.let { val recordDecl = - specialListType - .startInference(it) + this@translationUnit.startInference(it) ?.inferRecordDeclaration( specialListType, - this@translationUnit ) specialListType.recordDeclaration = recordDecl recordDecl?.addSuperClass(listType) diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt index 1f809a89ca..0165071291 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt @@ -27,8 +27,6 @@ package de.fraunhofer.aisec.cpg.frontends.cxx import com.fasterxml.jackson.annotation.JsonIgnore import de.fraunhofer.aisec.cpg.frontends.* -import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.types.* import kotlin.reflect.KClass import org.neo4j.ogm.annotation.Transient @@ -53,9 +51,6 @@ open class CLanguage : val unaryOperators = listOf("--", "++", "-", "+", "*", "&", "~") - override val globalVariableScopeClass: Class - get() = TranslationUnitDeclaration::class.java - /** * All operators which perform and assignment and an operation using lhs and rhs. See * https://en.cppreference.com/w/c/language/operator_assignment 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 9283b1959b..266ffa9e7e 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 @@ -49,4 +49,22 @@ class CXXInferenceTest { assertContains(tu.declarations, global) } + + @Test + fun testInferClassInNamespace() { + val file = File("src/test/resources/cxx/inference.cpp") + val tu = + TestUtils.analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) { + it.registerLanguage() + it.loadIncludes(false) + it.addIncludesToGraph(false) + } + assertNotNull(tu) + + val util = tu.namespaces["util"] + assertNotNull(util) + + val someClass = util.records["SomeClass"] + assertNotNull(someClass) + } } diff --git a/cpg-language-cxx/src/test/resources/cxx/inference.cpp b/cpg-language-cxx/src/test/resources/cxx/inference.cpp index 220c261399..34e89e0bc7 100644 --- a/cpg-language-cxx/src/test/resources/cxx/inference.cpp +++ b/cpg-language-cxx/src/test/resources/cxx/inference.cpp @@ -2,9 +2,10 @@ // You can use `clang++ -std=c++20 inference.cpp` to check, if it will compile. #include "inference.h" -// To make it a little bit easier for our inference engine, we forward declare "constants" as a +// To make it a little bit easier for our inference engine, we forward declare "constants" and "util" as a // namespace, because otherwise we would not know whether this is a class or a namespace. namespace constants {}; +namespace util {}; int doSomething() { if(somethingGlobal == 4) { @@ -17,5 +18,12 @@ int doSomething() { } int main() { + util::SomeClass* some = new util::SomeClass(); + // this should trigger the inference + some->doSomething(); + + // repeat it to make sure we correctly resolve it now + some->doSomething(); + doSomething(); } \ No newline at end of file diff --git a/cpg-language-cxx/src/test/resources/cxx/inference.h b/cpg-language-cxx/src/test/resources/cxx/inference.h index cb7d38c10d..07605be702 100644 --- a/cpg-language-cxx/src/test/resources/cxx/inference.h +++ b/cpg-language-cxx/src/test/resources/cxx/inference.h @@ -5,3 +5,10 @@ int somethingGlobal = 0; namespace constants { double pi = std::numbers::pi; } + +namespace util { + class SomeClass { + public: + void doSomething() {}; + }; +} \ No newline at end of file