Skip to content

Commit

Permalink
Symbol resolver with EOG power
Browse files Browse the repository at this point in the history
Co-Authored-By: KuechA <[email protected]>
  • Loading branch information
oxisto and KuechA committed Oct 9, 2023
1 parent a0bb37f commit b28ab58
Show file tree
Hide file tree
Showing 74 changed files with 1,793 additions and 1,292 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
package de.fraunhofer.aisec.cpg.analysis.fsm

import de.fraunhofer.aisec.cpg.graph.Node
import de.fraunhofer.aisec.cpg.graph.declarations.Declaration
import de.fraunhofer.aisec.cpg.graph.declarations.ParameterDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration
import de.fraunhofer.aisec.cpg.graph.edge.Properties
Expand Down Expand Up @@ -291,7 +290,8 @@ open class DFAOrderEvaluator(
if (
node is MemberCallExpression &&
node.base is Reference &&
consideredBases.contains((node.base as Reference).refersTo as Declaration)
(node.base as Reference).refersTo != null &&
consideredBases.contains((node.base as Reference).refersTo!!)
) {
allUsedBases.add((node.base as Reference).refersTo)
}
Expand Down
51 changes: 27 additions & 24 deletions cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -615,20 +615,7 @@ class ScopeManager : ScopeProvider {
*/
@JvmOverloads
fun resolveReference(ref: Reference, startScope: Scope? = currentScope): ValueDeclaration? {
// Unfortunately, we still have an issue about duplicate declarations because header files
// are included multiple times, so we need to exclude the C++ frontend (for now).
val language = ref.language
val (scope, name) =
if (
language?.name?.localName != "CLanguage" &&
(language?.name?.localName != "CPPLanguage")
) {
// For all other languages, we can extract the scope information out of the name and
// start our search at the dedicated scope.
extractScope(ref, startScope)
} else {
Pair(scope, ref.name)
}
val (scope, name) = extractScope(ref, startScope)

// Try to resolve value declarations according to our criteria
return resolve<ValueDeclaration>(scope) {
Expand Down Expand Up @@ -676,7 +663,7 @@ class ScopeManager : ScopeProvider {

val func =
resolve<FunctionDeclaration>(scope) {
it.name.lastPartsMatch(name) && it.hasSignature(call.signature)
it.name.lastPartsMatch(name) && it.hasSignature(call)
}

return func
Expand All @@ -686,13 +673,17 @@ class ScopeManager : ScopeProvider {
* This function extracts a possible scope out of a [Name], e.g. if the name is fully qualified.
* This also resolves possible name aliases (e.g. because of imports). It returns a pair of a
* scope (if found) as well as the name, which is possibly adjusted for the aliases.
*
* Note: Currently only *fully* qualified names are properly resolved. This function will
* probably return imprecise results for partially qualified names, e.g. if a name `A` inside
* `B` points to `A::B`, rather than to `A`.
*/
fun extractScope(node: Node, scope: Scope? = currentScope): Pair<Scope?, Name> {
var name: Name = node.name
var s = scope

// First, we need to check, whether we have some kind of scoping.
if (node.name.parent != null) {
if (node.name.isQualified()) {
// extract the scope name, it is usually a name space, but could probably be something
// else as well in other languages
var scopeName = node.name.parent
Expand All @@ -714,9 +705,9 @@ class ScopeManager : ScopeProvider {
Util.errorWithFileLocation(
node,
LOGGER,
"Could not find the scope $scopeName needed to resolve the call ${node.name}. Falling back to the default (current) scope"
"Could not find the scope $scopeName needed to resolve the call ${node.name}"
)
s
scope
} else {
scopes[0]
}
Expand All @@ -729,7 +720,7 @@ class ScopeManager : ScopeProvider {
* Directly jumps to a given scope. Returns the previous scope. Do not forget to set the scope
* back to the old scope after performing the actions inside this scope.
*
* Handle with care, here be dragons. Should not be exposed outside of the cpg-core module.
* Handle with care, here be dragons. Should not be exposed outside the cpg-core module.
*/
@PleaseBeCareful
internal fun jumpTo(scope: Scope?): Scope? {
Expand Down Expand Up @@ -774,24 +765,35 @@ class ScopeManager : ScopeProvider {
searchScope: Scope?,
stopIfFound: Boolean = false,
noinline predicate: (T) -> Boolean
): List<T> {
return resolve(T::class.java, searchScope, stopIfFound, predicate)
}

fun <T : Declaration> resolve(
klass: Class<T>,
searchScope: Scope?,
stopIfFound: Boolean = false,
predicate: (T) -> Boolean
): List<T> {
var scope = searchScope
val declarations = mutableListOf<T>()

while (scope != null) {
if (scope is ValueDeclarationScope) {
declarations.addAll(scope.valueDeclarations.filterIsInstance<T>().filter(predicate))
declarations.addAll(
scope.valueDeclarations.filterIsInstance(klass).filter(predicate)
)
}

if (scope is StructureDeclarationScope) {
var list = scope.structureDeclarations.filterIsInstance<T>().filter(predicate)
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<T>().filter(predicate)
list = declaration.templates.filterIsInstance(klass).filter(predicate)
}
}
}
Expand Down Expand Up @@ -832,11 +834,12 @@ class ScopeManager : ScopeProvider {
/**
* Retrieves the [RecordDeclaration] for the given name in the given scope.
*
* @param scope the scope
* @param name the name
* * @param scope the scope. Default is [currentScope]
*
* @return the declaration, or null if it does not exist
*/
fun getRecordForName(scope: Scope, name: Name): RecordDeclaration? {
fun getRecordForName(name: Name, scope: Scope? = currentScope): RecordDeclaration? {
return resolve<RecordDeclaration>(scope, true) { it.name.lastPartsMatch(name) }
.firstOrNull()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -444,8 +444,7 @@ private constructor(
* This will register
* - [TypeHierarchyResolver]
* - [ImportResolver]
* - [VariableUsageResolver]
* - [CallResolver]
* - [SymbolResolver]
* - [DFGPass]
* - [EvaluationOrderGraphPass]
* - [TypeResolver]
Expand All @@ -457,9 +456,9 @@ private constructor(
fun defaultPasses(): Builder {
registerPass<TypeHierarchyResolver>()
registerPass<ImportResolver>()
registerPass<VariableUsageResolver>()
registerPass<CallResolver>() // creates CG
registerPass<SymbolResolver>()
registerPass<DFGPass>()
registerPass<DynamicInvokeResolver>()
registerPass<EvaluationOrderGraphPass>() // creates EOG
registerPass<TypeResolver>()
registerPass<ControlFlowSensitiveDFGPass>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,12 @@ internal fun Type.getAncestors(depth: Int): Set<Type.Ancestor> {
* Checks, if this [Type] is either derived from or equals to [superType]. This is forwarded to the
* [Language] of the [Type] and can be overridden by the individual languages.
*/
fun Type.isDerivedFrom(superType: Type): Boolean {
return this.language?.isDerivedFrom(this, superType) ?: false
fun Type.isDerivedFrom(
superType: Type,
hint: HasType? = null,
superHint: HasType? = null
): Boolean {
return this.language?.isDerivedFrom(this, superType, hint, superHint) ?: false
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,16 @@ abstract class Language<T : LanguageFrontend<*, *>> : Node() {
return true
}

open fun isDerivedFrom(type: Type, superType: Type): Boolean {
/**
* This function checks, if [type] is derived from [superType]. Optionally, the nodes that hold
* the respective type can be supplied as [hint] and [superHint].
*/
open fun isDerivedFrom(
type: Type,
superType: Type,
hint: HasType?,
superHint: HasType?
): Boolean {
// Retrieve all ancestor types of our type (more concretely of the root type)
val root = type.root
val superTypes = root.ancestors.map { it.type }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,13 @@ package de.fraunhofer.aisec.cpg.frontends

import de.fraunhofer.aisec.cpg.ScopeManager
import de.fraunhofer.aisec.cpg.TranslationContext
import de.fraunhofer.aisec.cpg.graph.Name
import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration
import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression
import de.fraunhofer.aisec.cpg.graph.types.Type
import de.fraunhofer.aisec.cpg.passes.CallResolver
import java.util.regex.Pattern
import de.fraunhofer.aisec.cpg.passes.SymbolResolver

/**
* A language trait is a feature or trait that is common to a group of programming languages. Any
Expand Down Expand Up @@ -116,7 +114,7 @@ interface HasComplexCallResolution : LanguageTrait {
call: CallExpression,
ctx: TranslationContext,
currentTU: TranslationUnitDeclaration,
callResolver: CallResolver
callResolver: SymbolResolver
): List<FunctionDeclaration>

/**
Expand All @@ -131,7 +129,7 @@ interface HasComplexCallResolution : LanguageTrait {
fun refineInvocationCandidatesFromRecord(
recordDeclaration: RecordDeclaration,
call: CallExpression,
namePattern: Pattern,
name: String,
ctx: TranslationContext
): List<FunctionDeclaration>
}
Expand Down Expand Up @@ -170,7 +168,6 @@ interface HasSuperClasses : LanguageTrait {
callee: MemberExpression,
curClass: RecordDeclaration,
scopeManager: ScopeManager,
recordMap: Map<Name, RecordDeclaration>
): Boolean
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,11 @@ fun MetadataProvider.newCallExpression(
val node = CallExpression()
node.applyMetadata(this, fqn, rawNode, code, true)

// Set the call expression as resolution helper for the callee
if (callee is Reference) {
callee.resolutionHelper = node
}

node.callee = callee
node.template = template

Expand Down Expand Up @@ -331,6 +336,11 @@ fun MetadataProvider.newMemberCallExpression(
code,
)

// Set the call expression as resolution helper for the callee
if (callee is Reference) {
callee.resolutionHelper = node
}

node.callee = callee
node.isStatic = isStatic

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,12 @@ open class Node : IVisitable<Node>, Persistable, LanguageProvider, ScopeProvider
code == other.code &&
comment == other.comment &&
location == other.location &&
file == other.file &&
// We need to exclude "file" here, because in C++ the same header node can be
// imported in two different files and in this case, the "file" property will be
// different. Since want to squash those equal nodes, we will only consider all the
// other attributes, including "location" (which contains the *original* file
// location in the header file), but not "file".
// file == other.file &&
isImplicit == other.isImplicit
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2023, Fraunhofer AISEC. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* $$$$$$\ $$$$$$$\ $$$$$$\
* $$ __$$\ $$ __$$\ $$ __$$\
* $$ / \__|$$ | $$ |$$ / \__|
* $$ | $$$$$$$ |$$ |$$$$\
* $$ | $$ ____/ $$ |\_$$ |
* $$ | $$\ $$ | $$ | $$ |
* \$$$$$ |$$ | \$$$$$ |
* \______/ \__| \______/
*
*/
package de.fraunhofer.aisec.cpg.graph

import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration
import de.fraunhofer.aisec.cpg.passes.SymbolResolver

/**
* This interface needs to be implemented by [Node]s in the graph, wo serve as an entry-point to
* resolution, so that we can follow the EOG path from these nodes and resolve all symbols
* accordingly in the [SymbolResolver].
*
* In some cases, the [Node] that implements this interface will add itself, for example in a
* [FunctionDeclaration], so that we can use all functions as an entry-point to symbol resolution.
* In other cases, certain child nodes might be added to [resolutionStartNodes], for example to add
* all top-level declarations in a [TranslationUnitDeclaration].
*
* The common denominator is that all the nodes contained in [resolutionStartNodes] **start** an EOG
* path, i.e,. they should have a valid [Node.nextEOG], but an empty [Node.prevEOG].
*/
interface ResolutionStartHolder {
val resolutionStartNodes: List<Node>
}
Original file line number Diff line number Diff line change
Expand Up @@ -438,12 +438,12 @@ context(Holder<out Statement>)

fun LanguageFrontend<*, *>.memberCall(
localName: CharSequence,
member: Expression,
base: Expression,
isStatic: Boolean = false,
init: (MemberCallExpression.() -> Unit)? = null
): MemberCallExpression {
// Try to parse the name
val node = newMemberCallExpression(newMemberExpression(localName, member), isStatic)
val node = newMemberCallExpression(newMemberExpression(localName, base), isStatic)
if (init != null) {
init(node)
}
Expand Down Expand Up @@ -684,9 +684,9 @@ fun LanguageFrontend<*, *>.whileCondition(init: WhileStatement.() -> Expression)
}

/**
* <<<<<<< HEAD Configures the [DoStatement.condition] in the Fluent Node DSL of the nearest
* enclosing [DoStatement]. The [init] block can be used to create further sub-nodes as well as
* configuring the created node itself.
* Configures the [DoStatement.condition] in the Fluent Node DSL of the nearest enclosing
* [DoStatement]. The [init] block can be used to create further sub-nodes as well as configuring
* the created node itself.
*/
context(DoStatement)

Expand All @@ -695,13 +695,9 @@ fun LanguageFrontend<*, *>.whileCondition(init: DoStatement.() -> Expression): E
}

/**
* Creates a new [CompoundStatement] in the Fluent Node DSL and sets it to the
* [IfStatement.thenStatement] of the nearest enclosing [IfStatement]. The [init] block can be used
* to create further sub-nodes as well as configuring the created node itself.
* =======
* Creates a new [Block] in the Fluent Node DSL and sets it to the [IfStatement.thenStatement] of
* the nearest enclosing [IfStatement]. The [init] block can be used to create further sub-nodes as
* well as configuring the created node itself. >>>>>>> main
* well as configuring the created node itself.
*/
context(IfStatement)

Expand Down Expand Up @@ -748,9 +744,9 @@ fun LanguageFrontend<*, *>.loopBody(init: Block.() -> Unit): Block {
}

/**
* Creates a new [CompoundStatement] in the Fluent Node DSL and sets it to the
* [DoStatement.statement] of the nearest enclosing [DoStatement]. The [init] block can be used to
* create further sub-nodes as well as configuring the created node itself.
* Creates a new [Block] in the Fluent Node DSL and sets it to the [DoStatement.statement] of the
* nearest enclosing [WhileStatement]. The [init] block can be used to create further sub-nodes as
* well as configuring the created node itself.
*/
context(DoStatement)

Expand Down Expand Up @@ -1439,3 +1435,14 @@ private fun <T : Node> LanguageFrontend<*, *>.scopeIfNecessary(
scopeManager.leaveScope(node)
}
}

context(MethodDeclaration)

fun LanguageFrontend<*, *>.receiver(name: String, type: Type): VariableDeclaration {
val node = newVariableDeclaration(name, type)

this@MethodDeclaration.receiver = node
scopeManager.addDeclaration(node)

return node
}
Loading

0 comments on commit b28ab58

Please sign in to comment.