Skip to content

Commit

Permalink
Caching symbols on symbol resolution
Browse files Browse the repository at this point in the history
This PR caches symbol resolutions. This can can significantly speed up the resolution process.
  • Loading branch information
oxisto committed Oct 16, 2023
1 parent 54d3e3d commit 28e2b4d
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 24 deletions.
74 changes: 50 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 @@ -34,6 +34,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.*
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block
import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference
import de.fraunhofer.aisec.cpg.graph.statements.expressions.ReferenceTag
import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType
import de.fraunhofer.aisec.cpg.graph.types.IncompleteType
import de.fraunhofer.aisec.cpg.graph.types.Type
Expand Down Expand Up @@ -74,6 +75,12 @@ 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 unique tags (computed with [Reference.buildUniqueTag]) and their respective
* [ValueDeclaration]. This is used by [resolveReference] as a caching mechanism.
*/
private val symbolTable = mutableMapOf<ReferenceTag, ValueDeclaration>()

/**
* In some languages, we can define aliases for names. An example is renaming package imports in
* Go, e.g., to avoid name conflicts.
Expand Down Expand Up @@ -615,37 +622,56 @@ class ScopeManager : ScopeProvider {
*/
@JvmOverloads
fun resolveReference(ref: Reference, startScope: Scope? = currentScope): ValueDeclaration? {
// Retrieve a unique tag for the particular reference, based on the current scope
val tag = ref.buildUniqueTag(startScope)

// If we find a match in our symbol table, we can immediately return the declaration
var decl = symbolTable[tag]
if (decl != null) {
return decl
}

val (scope, name) = extractScope(ref, startScope)

// Try to resolve value declarations according to our criteria
return 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.hasSignature(fptrType.parameters)
}
// 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
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.hasSignature(fptrType.parameters)
}
// 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()

return@resolve false
}
.firstOrNull()
// Update the symbol cache, if we found a declaration for the tag
if (decl != null) {
symbolTable[tag] = decl
}

return decl
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,23 @@ abstract class Scope(
return this is LoopScope
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as Scope

if (astNode != other.astNode) return false
return name == other.name
}

override fun hashCode(): Int {
var result = astNode?.hashCode() ?: 0
result = 31 * result + (parent?.hashCode() ?: 0)
result = 31 * result + (name?.hashCode() ?: 0)
return result
}

/** Returns the [GlobalScope] of this scope by traversing its parents upwards. */
val globalScope: Scope?
get() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.Declaration
import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration
import de.fraunhofer.aisec.cpg.graph.edge.Properties
import de.fraunhofer.aisec.cpg.graph.scopes.Scope
import de.fraunhofer.aisec.cpg.graph.types.HasType
import de.fraunhofer.aisec.cpg.graph.types.Type
import de.fraunhofer.aisec.cpg.passes.SymbolResolver
Expand Down Expand Up @@ -153,4 +154,15 @@ open class Reference : Expression(), HasType.TypeObserver {
prev.registerTypeObserver(this)
}
}

/**
* This function builds a unique tag for the particular reference, based on the [startScope].
* Its purpose is to cache symbol resolutions, similar to LLVMs system of Unified Symbol
* Resolution (USR).
*/
fun buildUniqueTag(startScope: Scope?): ReferenceTag {
return Objects.hash(this.name, startScope)
}
}

typealias ReferenceTag = Int

0 comments on commit 28e2b4d

Please sign in to comment.