Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removing legacy resolveReference function #1861

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 1 addition & 176 deletions cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<ReferenceTag, Pair<Reference, ValueDeclaration>>()

/** True, if the scope manager is currently in a [BlockScope]. */
val isInBlock: Boolean
get() = this.firstScopeOrNull { it is BlockScope } != null
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<ValueDeclaration>(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.
Expand Down Expand Up @@ -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 <T>
*/
internal inline fun <reified T : Declaration> resolve(
searchScope: Scope?,
stopIfFound: Boolean = false,
noinline predicate: (T) -> Boolean
): List<T> {
return resolve(T::class.java, searchScope, stopIfFound, predicate)
}

internal 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(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<FunctionTemplateDeclaration> {
return resolve(scope, true) { c -> c.name.lastPartsMatch(call.name) }
}

/**
* Retrieves the [RecordDeclaration] for the given name in the given scope.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Loading
Loading