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 266ac9604f..d158c9e094 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 @@ -32,10 +32,9 @@ import de.fraunhofer.aisec.cpg.graph.scopes.* import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.DeclaresType -import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType -import de.fraunhofer.aisec.cpg.graph.types.IncompleteType import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.helpers.Util +import de.fraunhofer.aisec.cpg.passes.SymbolResolver import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import java.util.* import java.util.function.Predicate @@ -72,13 +71,6 @@ class ScopeManager : ScopeProvider { /** Represents an alias with the name [to] for the particular name [from]. */ data class Alias(var from: Name, var to: Name) - /** - * A cache map of reference tags (computed with [Reference.referenceTag]) and their respective - * pair of original [Reference] and resolved [ValueDeclaration]. This is used by - * [resolveReference] as a caching mechanism. - */ - private val symbolTable = mutableMapOf>() - /** True, if the scope manager is currently in a [BlockScope]. */ val isInBlock: Boolean get() = this.firstScopeOrNull { it is BlockScope } != null @@ -300,24 +292,6 @@ class ScopeManager : ScopeProvider { } } - /** - * Similar to [enterScope], but does so in a "read-only" mode, e.g. it does not modify the scope - * tree and does not create new scopes on the fly, as [enterScope] does. - */ - fun enterScopeIfExists(nodeToScope: Node?) { - if (scopeMap.containsKey(nodeToScope)) { - val scope = scopeMap[nodeToScope] - - // we need a special handling of name spaces, because - // they are associated to more than one AST node - if (scope is NameScope) { - // update AST (see enterScope for an explanation) - scope.astNode = nodeToScope - } - currentScope = scope - } - } - /** * The counter-part of [enterScope]. Language frontends need to call this function, when the * scope of the currently processed AST node ends. There MUST have been a corresponding @@ -505,73 +479,6 @@ class ScopeManager : ScopeProvider { scope?.addTypedef(typedef) } - /** - * Resolves only references to Values in the current scope, static references to other visible - * records are not resolved over the ScopeManager. - * - * @param ref - * @return - */ - fun resolveReference(ref: Reference): ValueDeclaration? { - val startScope = ref.scope - - // Retrieve a unique tag for the particular reference based on the current scope - val tag = ref.referenceTag - - // If we find a match in our symbol table, we can immediately return the declaration. We - // need to be careful about potential collisions in our tags, since they are based on the - // hash-code of the scope. We therefore take the extra precaution to compare the scope in - // case we get a hit. This should not take too much performance overhead. - val pair = symbolTable[tag] - if (pair != null && ref.scope == pair.first.scope) { - return pair.second - } - - var (scope, name) = extractScope(ref, startScope) - if (scope == null) { - scope = startScope - } - - // Try to resolve value declarations according to our criteria - val decl = - resolve(scope) { - if (it.name.lastPartsMatch(name)) { - val helper = ref.resolutionHelper - return@resolve when { - // If the reference seems to point to a function (using a function - // pointer) the entire signature is checked for equality - helper?.type is FunctionPointerType && it is FunctionDeclaration -> { - val fptrType = helper.type as FunctionPointerType - // TODO(oxisto): Support multiple return values - val returnType = it.returnTypes.firstOrNull() ?: IncompleteType() - returnType == fptrType.returnType && - it.matchesSignature(fptrType.parameters) != - IncompatibleSignature - } - // If our language has first-class functions, we can safely return them - // as a reference - ref.language is HasFirstClassFunctions -> { - true - } - // Otherwise, we are not looking for functions here - else -> { - it !is FunctionDeclaration - } - } - } - - return@resolve false - } - .firstOrNull() - - // Update the symbol cache, if we found a declaration for the tag - if (decl != null) { - symbolTable[tag] = Pair(ref, decl) - } - - return decl - } - /** * 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. @@ -723,88 +630,6 @@ class ScopeManager : ScopeProvider { return ret } - /** - * Traverses the scope upwards and looks for declarations of type [T] which matches the - * condition [predicate]. - * - * It returns a list of all declarations that match the predicate, ordered by reachability in - * the scope stack. This means that "local" declarations will be in the list first, global items - * will be last. - * - * @param searchScope the scope to start the search in - * @param predicate predicate the element must match to - * @param - */ - internal inline fun resolve( - searchScope: Scope?, - stopIfFound: Boolean = false, - noinline predicate: (T) -> Boolean - ): List { - return resolve(T::class.java, searchScope, stopIfFound, predicate) - } - - internal fun resolve( - klass: Class, - searchScope: Scope?, - stopIfFound: Boolean = false, - predicate: (T) -> Boolean - ): List { - var scope = searchScope - val declarations = mutableListOf() - - while (scope != null) { - if (scope is ValueDeclarationScope) { - declarations.addAll( - scope.valueDeclarations.filterIsInstance(klass).filter(predicate) - ) - } - - if (scope is StructureDeclarationScope) { - var list = scope.structureDeclarations.filterIsInstance(klass).filter(predicate) - - // this was taken over from the old resolveStructureDeclaration. - // TODO(oxisto): why is this only when the list is empty? - if (list.isEmpty()) { - for (declaration in scope.structureDeclarations) { - if (declaration is RecordDeclaration) { - list = declaration.templates.filterIsInstance(klass).filter(predicate) - } - } - } - - declarations.addAll(list) - } - - // some (all?) languages require us to stop immediately if we found something on this - // scope. This is the case where function overloading is allowed, but only within the - // same scope - if (stopIfFound && declarations.isNotEmpty()) { - return declarations - } - - // go upwards in the scope tree - scope = scope.parent - } - - return declarations - } - - /** - * Resolves function templates of the given [CallExpression]. - * - * @param scope where we are searching for the FunctionTemplateDeclarations - * @param call CallExpression we want to resolve an invocation target for - * @return List of FunctionTemplateDeclaration that match the name provided in the - * CallExpression and therefore are invocation candidates - */ - @JvmOverloads - fun resolveFunctionTemplateDeclaration( - call: CallExpression, - scope: Scope? = currentScope - ): List { - return resolve(scope, true) { c -> c.name.lastPartsMatch(call.name) } - } - /** * Retrieves the [RecordDeclaration] for the given name in the given scope. * 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 4c61ebc182..52931b1ef8 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 @@ -265,32 +265,41 @@ class ImportResolver(ctx: TranslationContext) : ComponentPass(ctx) { } private fun handleImportDeclaration(import: ImportDeclaration) { - // We always need to search at the global scope because we are "importing" something, so by - // definition, this is not in the scope of the current file. - val scope = scopeManager.globalScope ?: return - // Let's do some importing. We need to import either a wildcard - if (import.wildcardImport) { - val list = scopeManager.lookupSymbolByName(import.import, import.location, scope) - val symbol = list.singleOrNull() - if (symbol != null) { - // In this case, the symbol must point to a name scope - val symbolScope = scopeManager.lookupScope(symbol) - if (symbolScope is NameScope) { - import.importedSymbols = symbolScope.symbols - } - } - } else { - // or a symbol directly - val list = - scopeManager - .lookupSymbolByName(import.import, import.location, scope) - .toMutableList() - import.importedSymbols = mutableMapOf(import.symbol to list) - } + import.updateImportedSymbols() } override fun cleanup() { // Nothing to do } } + +/** + * This function updates the [ImportDeclaration.importedSymbols]. This is done once at the beginning + * by the [ImportResolver]. However, we need to update this list once we infer new symbols in + * namespaces that are imported at a later stage (e.g., in the [TypeResolver]), otherwise they won't + * be visible to the later passes. + */ +context(Pass<*>) +fun ImportDeclaration.updateImportedSymbols() { + // We always need to search at the global scope because we are "importing" something, so by + // definition, this is not in the scope of the current file. + val scope = scopeManager.globalScope ?: return + + if (this.wildcardImport) { + val list = scopeManager.lookupSymbolByName(this.import, this.location, scope) + val symbol = list.singleOrNull() + if (symbol != null) { + // In this case, the symbol must point to a name scope + val symbolScope = scopeManager.lookupScope(symbol) + if (symbolScope is NameScope) { + this.importedSymbols = symbolScope.symbols + } + } + } else { + // or a symbol directly + val list = + scopeManager.lookupSymbolByName(this.import, this.location, scope).toMutableList() + this.importedSymbols = mutableMapOf(this.symbol to list) + } +} 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 42722b74f7..e7d98e1990 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 @@ -30,7 +30,6 @@ import de.fraunhofer.aisec.cpg.CallResolutionResult.SuccessKind.* 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.Symbol import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.* @@ -41,6 +40,7 @@ import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn import de.fraunhofer.aisec.cpg.passes.inference.startInference import de.fraunhofer.aisec.cpg.passes.inference.tryFieldInference import de.fraunhofer.aisec.cpg.passes.inference.tryFunctionInference +import de.fraunhofer.aisec.cpg.passes.inference.tryFunctionInferenceFromFunctionPointer import de.fraunhofer.aisec.cpg.passes.inference.tryVariableInference import de.fraunhofer.aisec.cpg.processing.strategy.Strategy import org.slf4j.Logger @@ -133,36 +133,6 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { return language is HasSuperClasses && reference.name.endsWith(language.superClassKeyword) } - /** This function seems to resolve function pointers pointing to a [MethodDeclaration]. */ - protected fun resolveMethodFunctionPointer( - reference: Reference, - type: FunctionPointerType - ): ValueDeclaration? { - var target = scopeManager.resolveReference(reference) - - // If we didn't find anything, we create a new function or method declaration - if (target == null) { - // Determine the scope where we want to start our inference - var (scope, _) = scopeManager.extractScope(reference) - if (scope !is NameScope) { - scope = null - } - - target = - (scope?.astNode ?: reference.translationUnit) - ?.startInference(ctx) - ?.inferFunctionDeclaration( - reference.name, - null, - false, - type.parameters, - type.returnType - ) - } - - return target - } - /** * This function handles symbol resolving for a [Reference]. After a successful lookup of the * symbol contained in [Reference.name], the property [Reference.refersTo] is set to the best @@ -187,6 +157,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { */ protected fun handleReference(currentClass: RecordDeclaration?, ref: Reference) { val language = ref.language + val helperType = ref.resolutionHelper?.type // Ignore references to anonymous identifiers, if the language supports it (e.g., the _ // identifier in Go) @@ -202,14 +173,33 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { return } + // If our resolution helper indicates that this reference is the target of a variable with a + // function pointer, we need to take the (return) type arguments of the function pointer + // into consideration + val predicate: ((Declaration) -> Boolean)? = + if (helperType is FunctionPointerType) { + { declaration -> + if (declaration is FunctionDeclaration) { + declaration.returnTypes == listOf(helperType.returnType) && + declaration.matchesSignature(helperType.parameters) != + IncompatibleSignature + } else { + false + } + } + } else { + null + } + // Find a list of candidate symbols. Currently, this is only used the in the "next-gen" call // resolution, but in future this will also be used in resolving regular references. - ref.candidates = scopeManager.lookupSymbolByName(ref.name, ref.location).toSet() + ref.candidates = + scopeManager.lookupSymbolByName(ref.name, ref.location, predicate = predicate).toSet() // Preparation for a future without legacy call resolving. Taking the first candidate is not // ideal since we are running into an issue with function pointers here (see workaround // below). - var wouldResolveTo = ref.candidates.singleOrNull() + var wouldResolveTo = ref.candidates.firstOrNull() // For now, we need to ignore reference expressions that are directly embedded into call // expressions, because they are the "callee" property. In the future, we will use this @@ -229,25 +219,6 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { } } - // Some stupid C++ workaround to use the legacy call resolver when we try to resolve targets - // for function pointers. At least we are only invoking the legacy resolver for a very small - // percentage of references now. - if (wouldResolveTo is FunctionDeclaration) { - // We need to invoke the legacy resolver, just to be sure - var legacy = scopeManager.resolveReference(ref) - - // This is just for us to catch these differences in symbol resolving in the future. The - // difference is pretty much only that the legacy system takes parameters of the - // function-pointer-type into account and the new system does not (yet), because it just - // takes the first match. This will be needed to solve in the future. - if (legacy != wouldResolveTo) { - log.warn( - "The legacy symbol resolution and the new system produced different results here. This needs to be investigated in the future. For now, we take the legacy result." - ) - wouldResolveTo = legacy - } - } - // Only consider resolving, if the language frontend did not specify a resolution. If we // already have populated the wouldResolveTo variable, we can re-use this instead of // resolving again @@ -258,14 +229,16 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { recordDeclType = currentClass.toType() } - val helperType = ref.resolutionHelper?.type - if (helperType is FunctionPointerType && refersTo == null) { - refersTo = resolveMethodFunctionPointer(ref, helperType) - } - // If we did not resolve the reference up to this point, we can try to infer the declaration if (refersTo == null) { - refersTo = tryVariableInference(ref) + // If its a function pointer, we can try to infer a function + refersTo = + if (helperType is FunctionPointerType) { + tryFunctionInferenceFromFunctionPointer(ref, helperType) + } else { + // Otherwise, we can try to infer a variable + tryVariableInference(ref) + } } if (refersTo != null) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt index 91d67e5b72..93a2c94b60 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt @@ -132,7 +132,14 @@ fun applyMissingParams( if (missingParam.refersTo == null) { val currentScope = scopeManager.currentScope scopeManager.jumpTo(missingParam.scope) - missingParam.refersTo = scopeManager.resolveReference(missingParam) + missingParam.refersTo = + scopeManager + .lookupSymbolByName( + missingParam.name, + missingParam.location, + missingParam.scope + ) + .singleOrNull() scopeManager.jumpTo(currentScope) } missingParam = missingParam.refersTo 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 e4345e98d2..986e45dcfa 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 @@ -29,6 +29,7 @@ import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.TypeManager import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.declarations.ImportDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.types.DeclaresType import de.fraunhofer.aisec.cpg.graph.types.ObjectType @@ -140,6 +141,9 @@ open class TypeResolver(ctx: TranslationContext) : ComponentPass(ctx) { t.recordDeclaration = node } } + } else if (node is ImportDeclaration) { + // Update the imports, as they might have changed because of inference + node.updateImportedSymbols() } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/PassHelper.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/PassHelper.kt index f88a8fc3dc..afed906662 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/PassHelper.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/PassHelper.kt @@ -47,6 +47,8 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference +import de.fraunhofer.aisec.cpg.graph.translationUnit +import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType import de.fraunhofer.aisec.cpg.graph.types.ObjectType import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.graph.types.recordDeclaration @@ -315,6 +317,22 @@ internal fun Pass<*>.tryFunctionInference( } } +/** This function tries to infer a missing [FunctionDeclaration] from a function pointer usage. */ +internal fun Pass<*>.tryFunctionInferenceFromFunctionPointer( + ref: Reference, + type: FunctionPointerType +): ValueDeclaration? { + // Determine the scope where we want to start our inference + var (scope, _) = scopeManager.extractScope(ref) + if (scope !is NameScope) { + scope = null + } + + return (scope?.astNode ?: ref.translationUnit) + ?.startInference(ctx) + ?.inferFunctionDeclaration(ref.name, null, false, type.parameters, type.returnType) +} + /** * Creates an inferred [FunctionDeclaration] for each suitable [Type] (which points to a * [RecordDeclaration]). 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 92987f60f1..2abcfe60ab 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 @@ -286,7 +286,7 @@ fun assertUsageOf(usingNode: Node?, usedNode: Node?) { fun assertUsageOfMemberAndBase(usingNode: Node?, usedBase: Node?, usedMember: Declaration?) { assertNotNull(usingNode) if (usingNode !is MemberExpression && !ENFORCE_MEMBER_EXPRESSION) { - // Assumtion here is that the target of the member portion of the expression and not the + // Assumption here is that the target of the member portion of the expression and not the // base is resolved assertUsageOf(usingNode, usedMember) } else { diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt index c5fc9815fd..94b451b06b 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt @@ -244,7 +244,11 @@ open class CPPLanguage : needsExactMatch: Boolean ): Pair> { val instantiationCandidates = - ctx.scopeManager.resolveFunctionTemplateDeclaration(templateCall) + ctx.scopeManager + .lookupSymbolByName(templateCall.name, templateCall.location, templateCall.scope) { + it is FunctionTemplateDeclaration + } + .filterIsInstance() for (functionTemplateDeclaration in instantiationCandidates) { val initializationType = mutableMapOf() 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 210aa97d68..b0ce0781f5 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 @@ -268,7 +268,6 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : } templateDeclaration.location = frontend.locationOf(ctx) - frontend.scopeManager.addDeclaration(templateDeclaration) frontend.scopeManager.enterScope(templateDeclaration) addTemplateParameters(ctx, templateDeclaration) @@ -284,6 +283,7 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : } addRealizationToScope(templateDeclaration) + frontend.scopeManager.addDeclaration(templateDeclaration) return templateDeclaration } diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt index 464b7bc4a2..2f5be5ec69 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.FunctionType import de.fraunhofer.aisec.cpg.graph.types.Type @@ -140,9 +141,11 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : } else { if (capture.isByReference) { val valueDeclaration = - frontend.scopeManager.resolveReference( - newReference(capture?.identifier?.toString()) - ) + frontend.scopeManager + .lookupSymbolByName(newName(capture.identifier?.toString() ?: "")) { + it is ValueDeclaration + } + .singleOrNull() as? ValueDeclaration valueDeclaration?.let { lambda.mutableVariables.add(it) } } } diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt index 77e3cc32ac..b4750f981f 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt @@ -348,9 +348,12 @@ class GoExtraPass(ctx: TranslationContext) : ComponentPass(ctx) { // Loop through the target variables (left-hand side) for ((idx, expr) in assign.lhs.withIndex()) { if (expr is Reference) { - // And try to resolve it - val ref = scopeManager.resolveReference(expr) - if (ref == null) { + // And try to resolve it as a variable + val ref = + scopeManager.lookupSymbolByName(expr.name, expr.location, expr.scope) { + it is VariableDeclaration + } + if (ref.isEmpty()) { // We need to implicitly declare it, if it's not declared before. val decl = newVariableDeclaration(expr.name, expr.autoType()) decl.language = expr.language diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/ExpressionHandler.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/ExpressionHandler.kt index 063d163c2a..1b6fbfd500 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/ExpressionHandler.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/ExpressionHandler.kt @@ -25,13 +25,11 @@ */ package de.fraunhofer.aisec.cpg.frontends.java -import com.github.javaparser.Range -import com.github.javaparser.TokenRange -import com.github.javaparser.ast.Node import com.github.javaparser.ast.body.VariableDeclarator import com.github.javaparser.ast.expr.* import com.github.javaparser.ast.expr.Expression import com.github.javaparser.resolution.UnsolvedSymbolException +import com.github.javaparser.resolution.declarations.ResolvedFieldDeclaration import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration import de.fraunhofer.aisec.cpg.frontends.Handler import de.fraunhofer.aisec.cpg.frontends.HandlerInterface @@ -244,171 +242,31 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : expr: Expression ): de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression { val fieldAccessExpr = expr.asFieldAccessExpr() - var base: de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression - // first, resolve the scope. this adds the necessary nodes, such as IDENTIFIER for the - // scope. - // it also acts as the first argument of the operator call - val scope = fieldAccessExpr.scope - if (scope.isNameExpr) { - var isStaticAccess = false - var baseType: Type - try { - val resolve = fieldAccessExpr.resolve() - if (resolve.asField().isStatic) { - isStaticAccess = true - } - baseType = this.objectType(resolve.asField().declaringType().qualifiedName) - } catch (ex: RuntimeException) { - isStaticAccess = true - val typeString = frontend.recoverTypeFromUnsolvedException(ex) - if (typeString != null) { - baseType = this.objectType(typeString) - } else { - // try to get the name - val name: String - val tokenRange = scope.asNameExpr().tokenRange - name = - if (tokenRange.isPresent) { - tokenRange.get().toString() - } else { - scope.asNameExpr().nameAsString - } - val qualifiedNameFromImports = frontend.getQualifiedNameFromImports(name) - baseType = - if (qualifiedNameFromImports != null) { - this.objectType(qualifiedNameFromImports) - } else { - log.info("Unknown base type 1 for {}", fieldAccessExpr) - unknownType() - } - } - } catch (ex: NoClassDefFoundError) { - isStaticAccess = true - val typeString = frontend.recoverTypeFromUnsolvedException(ex) - if (typeString != null) { - baseType = this.objectType(typeString) - } else { - val name: String - val tokenRange = scope.asNameExpr().tokenRange - name = - if (tokenRange.isPresent) { - tokenRange.get().toString() - } else { - scope.asNameExpr().nameAsString - } - val qualifiedNameFromImports = frontend.getQualifiedNameFromImports(name) - baseType = - if (qualifiedNameFromImports != null) { - this.objectType(qualifiedNameFromImports) - } else { - log.info("Unknown base type 1 for {}", fieldAccessExpr) - unknownType() - } - } - } - base = newReference(scope.asNameExpr().nameAsString, baseType, rawNode = scope) - base.isStaticAccess = isStaticAccess - } else if (scope.isFieldAccessExpr) { - base = - handle(scope) as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? - ?: newProblemExpression("Could not parse base") - var tester = base - while (tester is MemberExpression) { - // we need to check if any base is only a static access, otherwise, this is a member - // access - // to this base - tester = tester.base - } - if (tester is Reference && tester.isStaticAccess) { - // try to get the name - val name: String - val tokenRange = scope.asFieldAccessExpr().tokenRange - name = - if (tokenRange.isPresent) { - tokenRange.get().toString() - } else { - scope.asFieldAccessExpr().nameAsString - } - val qualifiedNameFromImports = frontend.getQualifiedNameFromImports(name) - val baseType = - if (qualifiedNameFromImports != null) { - this.objectType(qualifiedNameFromImports) - } else { - log.info("Unknown base type 2 for {}", fieldAccessExpr) - unknownType() - } - base = - newReference(scope.asFieldAccessExpr().nameAsString, baseType, rawNode = scope) - base.isStaticAccess = true - } - } else { - base = - handle(scope) as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? - ?: newProblemExpression("Could not parse base") - } - var fieldType: Type? + + var baseType = unknownType() + var fieldType = unknownType() + // We can "try" to resolve the field using JavaParser's logic. The main reason we WANT to do + // this is to get information about system types, as long as we are not fully doing that on + // our own. try { val symbol = fieldAccessExpr.resolve() - fieldType = - frontend.typeManager.getTypeParameter( - frontend.scopeManager.currentRecord, - symbol.asField().type.describe() - ) - if (fieldType == null) { - fieldType = frontend.typeOf(symbol.asField().type) + fieldType = frontend.typeOf(symbol.type) + + if (symbol.isField) { + baseType = objectType(symbol.asField().declaringType().qualifiedName) } - } catch (ex: RuntimeException) { - val typeString = frontend.recoverTypeFromUnsolvedException(ex) - fieldType = - if (typeString != null) { - this.objectType(typeString) - } else if (fieldAccessExpr.toString().endsWith(".length")) { - this.primitiveType("int") - } else { - log.info("Unknown field type for {}", fieldAccessExpr) - unknownType() - } - val memberExpression = - newMemberExpression( - fieldAccessExpr.name.identifier, - base, - fieldType, - ".", // there is only "." in java - rawNode = expr - ) - memberExpression.isStaticAccess = true - return memberExpression - } catch (ex: NoClassDefFoundError) { - val typeString = frontend.recoverTypeFromUnsolvedException(ex) - fieldType = - if (typeString != null) { - this.objectType(typeString) - } else if (fieldAccessExpr.toString().endsWith(".length")) { - this.primitiveType("int") - } else { - log.info("Unknown field type for {}", fieldAccessExpr) - unknownType() - } - val memberExpression = - newMemberExpression( - fieldAccessExpr.name.identifier, - base, - fieldType, - ".", - rawNode = expr - ) - memberExpression.isStaticAccess = true - return memberExpression - } - if (base.location == null) { - base.location = frontend.locationOf(fieldAccessExpr) - } + } catch (_: UnsolvedSymbolException) {} + + var base = + handle(fieldAccessExpr.scope) + as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression + base.type = baseType return newMemberExpression( fieldAccessExpr.name.identifier, base, fieldType, - ".", - rawNode = expr + operatorCode = ".", + rawNode = fieldAccessExpr ) } @@ -502,111 +360,22 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : ): de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? { val nameExpr = expr.asNameExpr() - // TODO this commented code breaks field accesses to fields that don't have a primitive - // type. - // How should this be handled correctly? - // try { - // ResolvedType resolvedType = nameExpr.calculateResolvedType(); - // if (resolvedType.isReferenceType()) { - // return newReference( - // nameExpr.getNameAsString(), - // new Type(((ReferenceTypeImpl) resolvedType).getQualifiedName()), - // nameExpr.toString()); - // } - // } catch ( - // UnsolvedSymbolException - // e) { // this might throw, e.g. if the type is simply not defined (i.e., syntax - // error) - // return newReference( - // nameExpr.getNameAsString(), new Type(UNKNOWN_TYPE), nameExpr.toString()); - // } - val name = this.parseName(nameExpr.nameAsString) - return try { + // Try to resolve it. We will remove this in a future where we do not really in the + // javaparser symbols anymore. This is mainly needed to resolve implicit "this.field" access + // as well as access to static fields of other classes - which we could resolve once we + // fully leverage the import system in the Java frontend. + try { val symbol = nameExpr.resolve() if (symbol.isField) { val field = symbol.asField() - if (!field.isStatic) { - // convert to FieldAccessExpr - val fieldAccessExpr = FieldAccessExpr(ThisExpr(), field.name) - expr.range.ifPresent { range: Range? -> fieldAccessExpr.setRange(range) } - expr.tokenRange.ifPresent { tokenRange: TokenRange? -> - fieldAccessExpr.setTokenRange(tokenRange) - } - expr.parentNode.ifPresent { newParentNode: Node? -> - fieldAccessExpr.setParentNode(newParentNode) - } - expr.replace(fieldAccessExpr) - fieldAccessExpr.parentNode.ifPresent { newParentNode: Node? -> - expr.setParentNode(newParentNode) - } - - // handle it as a field expression - handle(fieldAccessExpr) - as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? - } else { - val fieldAccessExpr = - FieldAccessExpr(NameExpr(field.declaringType().className), field.name) - expr.range.ifPresent { range: Range? -> fieldAccessExpr.setRange(range) } - expr.tokenRange.ifPresent { tokenRange: TokenRange? -> - fieldAccessExpr.setTokenRange(tokenRange) - } - expr.parentNode.ifPresent { newParentNode: Node? -> - fieldAccessExpr.setParentNode(newParentNode) - } - expr.replace(fieldAccessExpr) - fieldAccessExpr.parentNode.ifPresent { newParentNode: Node? -> - expr.setParentNode(newParentNode) - } - - // handle it as a field expression - handle(fieldAccessExpr) - as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? - } - } else { - // Resolve type first with ParameterizedType - var type: Type? = - frontend.typeManager.getTypeParameter( - frontend.scopeManager.currentRecord, - symbol.type.describe() - ) - if (type == null) { - type = frontend.typeOf(symbol.type) - } - newReference(symbol.name, type, rawNode = nameExpr) - } - } catch (ex: UnsolvedSymbolException) { - val typeString: String? = - if ( - ex.name.startsWith( - "We are unable to find the value declaration corresponding to" - ) - ) { - nameExpr.nameAsString - } else { - frontend.recoverTypeFromUnsolvedException(ex) - } - val t: Type - if (typeString == null) { - t = unknownType() - } else { - t = this.objectType(typeString) - t.typeOrigin = Type.Origin.GUESSED - } - val declaredReferenceExpression = newReference(name, t, rawNode = nameExpr) - val recordDeclaration = frontend.scopeManager.currentRecord - if (recordDeclaration != null && recordDeclaration.name.lastPartsMatch(name)) { - declaredReferenceExpression.refersTo = recordDeclaration + // handle it as a field expression + return handle(field.toFieldAccessExpr(nameExpr)) + as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? } - declaredReferenceExpression - } catch (ex: RuntimeException) { - val t = unknownType() - log.info("Unresolved symbol: {}", nameExpr.nameAsString) - newReference(nameExpr.nameAsString, t, rawNode = nameExpr) - } catch (ex: NoClassDefFoundError) { - val t = unknownType() - log.info("Unresolved symbol: {}", nameExpr.nameAsString) - newReference(nameExpr.nameAsString, t, rawNode = nameExpr) - } + } catch (_: UnsolvedSymbolException) {} + + val name = this.parseName(nameExpr.nameAsString) + return newReference(name, rawNode = expr) } private fun handleInstanceOfExpression(expr: Expression): BinaryOperator { @@ -889,3 +658,21 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : } } } + +fun ResolvedFieldDeclaration.toFieldAccessExpr(expr: NameExpr): FieldAccessExpr { + // Convert to FieldAccessExpr + val fieldAccessExpr = + if (this.isStatic) { + FieldAccessExpr(NameExpr(this.declaringType().className), this.name) + } else { + FieldAccessExpr(ThisExpr(), this.name) + } + + expr.range.ifPresent { fieldAccessExpr.setRange(it) } + expr.tokenRange.ifPresent { fieldAccessExpr.setTokenRange(it) } + expr.parentNode.ifPresent { fieldAccessExpr.setParentNode(it) } + expr.replace(fieldAccessExpr) + fieldAccessExpr.parentNode.ifPresent { expr.setParentNode(it) } + + return fieldAccessExpr +} diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontend.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontend.kt index 78609f9b7e..87769aa050 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontend.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontend.kt @@ -63,6 +63,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.helpers.Benchmark import de.fraunhofer.aisec.cpg.helpers.CommonPath import de.fraunhofer.aisec.cpg.passes.JavaExternalTypeHierarchyResolver +import de.fraunhofer.aisec.cpg.passes.JavaExtraPass import de.fraunhofer.aisec.cpg.passes.JavaImportResolver import de.fraunhofer.aisec.cpg.passes.configuration.RegisterExtraPass import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation @@ -78,6 +79,7 @@ import kotlin.jvm.optionals.getOrNull JavaExternalTypeHierarchyResolver::class ) // this pass is always required for Java @RegisterExtraPass(JavaImportResolver::class) +@RegisterExtraPass(JavaExtraPass::class) open class JavaLanguageFrontend(language: Language, ctx: TranslationContext) : LanguageFrontend(language, ctx) { @@ -141,6 +143,12 @@ open class JavaLanguageFrontend(language: Language, ctx: T scopeManager.addDeclaration(incl) } + // We create an implicit import for "java.lang.*" + val decl = + newImportDeclaration(parseName("java.lang"), wildcardImport = true) + .implicit("import java.lang.*") + scopeManager.addDeclaration(decl) + if (namespaceDeclaration != null) { scopeManager.leaveScope(namespaceDeclaration) } diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExtraPass.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExtraPass.kt new file mode 100644 index 0000000000..7984ca5dfc --- /dev/null +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExtraPass.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ +package de.fraunhofer.aisec.cpg.passes + +import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.codeAndLocationFrom +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.fqn +import de.fraunhofer.aisec.cpg.graph.newReference +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference +import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker +import de.fraunhofer.aisec.cpg.helpers.replace +import de.fraunhofer.aisec.cpg.nameIsType +import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn +import de.fraunhofer.aisec.cpg.passes.configuration.ExecuteBefore + +@DependsOn(TypeResolver::class) +@ExecuteBefore(SymbolResolver::class) +class JavaExtraPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { + private lateinit var walker: SubgraphWalker.ScopedWalker + + override fun accept(tu: TranslationUnitDeclaration) { + // Loop through all member expressions + walker = SubgraphWalker.ScopedWalker(ctx.scopeManager) + walker.registerHandler { _, parent, node -> + when (node) { + is MemberExpression -> handleMemberExpression(node, parent) + } + } + + walker.iterate(tu) + } + + fun handleMemberExpression(me: MemberExpression, parent: Node?) { + // For now, we are only interested in fields and not in calls, since this will open another + // can of worms + if (parent is CallExpression && parent.callee == me) return + + // Look at the "base" of the member expression and check if this is referring to a type + var base = me.base as? Reference + var type = base?.nameIsType() + if (type != null) { + // Our base refers to a type, so this is actually a static reference, which we + // model not as a member expression, but as a reference with an FQN. Let's build that + // and exchange the node + val ref = + newReference(type.name.fqn(me.name.localName), type = me.type) + .codeAndLocationFrom(me) + .apply { isStaticAccess = true } + walker.replace(parent, me, ref) + } + } + + override fun cleanup() { + // Nothing to do + } +} diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/SuperCallTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/SuperCallTest.kt index ca83ba515f..f7fc72b283 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/SuperCallTest.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/SuperCallTest.kt @@ -89,13 +89,16 @@ internal class SuperCallTest : BaseTest() { val methods = subClass.methods val field = findByUniqueName(subClass.fields, "field") val getField = findByUniqueName(methods, "getField") - var refs = getField.allChildren() + var refs = getField.refs val fieldRef = findByUniquePredicate(refs) { "field" == it.code } val getSuperField = findByUniqueName(methods, "getSuperField") refs = getSuperField.allChildren() val superFieldRef = findByUniquePredicate(refs) { "super.field" == it.code } - assertTrue(fieldRef.base is Reference) - assertRefersTo(fieldRef.base, getField.receiver) + // See https://github.com/Fraunhofer-AISEC/cpg/issues/1863. + // We only have a regular call expression here, if we do not resolve anything else in the + // frontend + /*assertTrue(fieldRef.base is Reference) + assertRefersTo(fieldRef.base, getField.receiver)*/ assertEquals(field, fieldRef.refersTo) assertTrue(superFieldRef.base is Reference) assertRefersTo(superFieldRef.base, getSuperField.receiver) diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StaticImportsTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StaticImportsTest.kt index 0def824fc9..df6b99abb5 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StaticImportsTest.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StaticImportsTest.kt @@ -112,17 +112,19 @@ internal class StaticImportsTest : BaseTest() { } } val testFields = a.fields - val staticField = findByUniqueName(testFields, "staticField") - val nonStaticField = findByUniqueName(testFields, "nonStaticField") + val staticField = a.fields["staticField"] + val inferredNonStaticField = b.fields["nonStaticField"] + assertNotNull(staticField) + assertNotNull(inferredNonStaticField) assertTrue(staticField.modifiers.contains("static")) - assertFalse(nonStaticField.modifiers.contains("static")) + assertFalse(inferredNonStaticField.modifiers.contains("static")) - val declaredReferences = main.allChildren() + val declaredReferences = main.refs val usage = findByUniqueName(declaredReferences, "staticField") - assertEquals(staticField, usage.refersTo) + assertRefersTo(usage, staticField) val nonStatic = findByUniqueName(declaredReferences, "nonStaticField") - assertNotEquals(nonStaticField, nonStatic.refersTo) - assertTrue(nonStatic.refersTo!!.isInferred) + assertRefersTo(nonStatic, inferredNonStaticField) + assertTrue(nonStatic.refersTo?.isInferred == true) } }