Skip to content

Commit

Permalink
Cleanup of SymbolResolver (#1777)
Browse files Browse the repository at this point in the history
  • Loading branch information
oxisto authored Oct 4, 2024
1 parent b7cfbdf commit c27b13d
Show file tree
Hide file tree
Showing 7 changed files with 507 additions and 400 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,12 @@ var Type.recordDeclaration: RecordDeclaration?
}
}

/**
* This interfaces specifies that this node (most likely a [Declaration]) declares a type. This is
* used by [TypeResolver.resolveType] to find appropriate symbols and declarations.
*/
interface DeclaresType {

/** The [Type] that is being declared. */
val declaredType: Type
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
*/
package de.fraunhofer.aisec.cpg.passes

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.RecordDeclaration
import de.fraunhofer.aisec.cpg.graph.types.DeclaresType
Expand All @@ -34,6 +36,7 @@ import de.fraunhofer.aisec.cpg.graph.types.Type
import de.fraunhofer.aisec.cpg.graph.types.recordDeclaration
import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker
import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn
import de.fraunhofer.aisec.cpg.passes.inference.tryRecordInference

/**
* The purpose of this [Pass] is to establish a relationship between [Type] nodes (more specifically
Expand All @@ -59,65 +62,87 @@ open class TypeResolver(ctx: TranslationContext) : ComponentPass(ctx) {
}
}

companion object {
context(ContextProvider)
fun resolveType(type: Type): Boolean {
// Let's start by looking up the type according to their name and scope. We exclusively
// filter for nodes that implement DeclaresType, because otherwise we will get a lot of
// constructor declarations and such with the same name. It seems this is ok since most
// languages will prefer structs/classes over functions when resolving types.
var symbols =
ctx?.scopeManager?.lookupSymbolByName(type.name, startScope = type.scope) {
it is DeclaresType
} ?: listOf()

// We need to have a single match, otherwise we have an ambiguous type and we cannot
// normalize it.
// TODO: Maybe we should have a warning in this case?
var declares = symbols.filterIsInstance<DeclaresType>().singleOrNull()

// Check for a possible typedef
var target = ctx?.scopeManager?.typedefFor(type.name, type.scope)
if (target != null) {
if (target.typeOrigin == Type.Origin.UNRESOLVED && type != target) {
// Make sure our typedef target is resolved
resolveType(target)
}

var originDeclares = target.recordDeclaration
var name = target.name
log.debug("Aliasing type {} in {} scope to {}", type.name, type.scope, name)
type.declaredFrom = originDeclares
type.recordDeclaration = originDeclares
type.typeOrigin = Type.Origin.RESOLVED
return true
/**
* This function tries to "resolve" a [Type] back to the original declaration that declared it
* (see [DeclaresType]). More specifically, it harmonises the type's name to the FQN of the
* declared type and sets the [Type.declaredFrom] (and [ObjectType.recordDeclaration]) property.
* It also sets [Type.typeOrigin] to [Type.Origin.RESOLVED] to mark it as resolved.
*
* The high-level approach looks like the following:
* - First, we check if this type refers to a typedef (see [ScopeManager.typedefFor]). If yes,
* we need to make sure that the target type is resolved and then resolve the type to the
* target type's declaration.
* - If no typedef is used, [ScopeManager.lookupSymbolByName] is used to look up declarations by
* the type's name, starting at its [Type.scope]. Depending on the type, this can be
* unqualified or qualified. We filter exclusively for declarations that implement
* [DeclaresType].
* - If this yields no declaration, we try to infer a record declaration using
* [tryRecordInference].
* - Finally, we set the type's name to the resolved type, set [Type.declaredFrom],
* [ObjectType.recordDeclaration], sync [Type.superTypes] with the declaration and set
* [Type.typeOrigin] to [Type.Origin.RESOLVED].
*/
fun resolveType(type: Type): Boolean {
// Check for a possible typedef
var target = scopeManager.typedefFor(type.name, type.scope)
if (target != null) {
if (target.typeOrigin == Type.Origin.UNRESOLVED && type != target) {
// Make sure our typedef target is resolved
resolveType(target)
}

if (declares == null) {
declares = ctx?.tryRecordInference(type, locationHint = type)
}
var originDeclares = target.recordDeclaration
var name = target.name
log.debug("Aliasing type {} in {} scope to {}", type.name, type.scope, name)
type.declaredFrom = originDeclares
type.recordDeclaration = originDeclares
type.typeOrigin = Type.Origin.RESOLVED
return true
}

// If we found the "real" declared type, we can normalize the name of our scoped type
// and
// set the name to the declared type.
if (declares != null) {
var declaredType = declares.declaredType
log.debug(
"Resolving type {} in {} scope to {}",
type.name,
type.scope,
declaredType.name
)
type.name = declaredType.name
type.declaredFrom = declares
type.recordDeclaration = declares as? RecordDeclaration
type.typeOrigin = Type.Origin.RESOLVED
type.superTypes.addAll(declaredType.superTypes)
return true
}
// Let's start by looking up the type according to their name and scope. We exclusively
// filter for nodes that implement DeclaresType, because otherwise we will get a lot of
// constructor declarations and such with the same name. It seems this is ok since most
// languages will prefer structs/classes over functions when resolving types.
var symbols =
scopeManager
.lookupSymbolByName(type.name, startScope = type.scope) { it is DeclaresType }
.filterIsInstance<DeclaresType>()

// We need to have a single match, otherwise we have an ambiguous type, and we cannot
// normalize it.
if (symbols.size > 1) {
log.warn(
"Lookup of type {} returned more than one symbol which declares a type, this is an ambiguity and the following analysis might not be correct.",
name
)
}
var declares = symbols.singleOrNull()

// If we did not find any declaration, we can try to infer a record declaration for it
if (declares == null) {
declares = tryRecordInference(type, locationHint = type)
}

return false
// If we found the "real" declared type, we can normalize the name of our scoped type
// and set the name to the declared type.
if (declares != null) {
var declaredType = declares.declaredType
log.debug(
"Resolving type {} in {} scope to {}",
type.name,
type.scope,
declaredType.name
)
type.name = declaredType.name
type.declaredFrom = declares
type.recordDeclaration = declares as? RecordDeclaration
type.typeOrigin = Type.Origin.RESOLVED
type.superTypes.addAll(declaredType.superTypes)
return true
}

return false
}

private fun handleNode(node: Node?) {
Expand All @@ -135,6 +160,7 @@ open class TypeResolver(ctx: TranslationContext) : ComponentPass(ctx) {
// Nothing to do
}

/** Resolves all types in [TypeManager.firstOrderTypes] using [resolveType]. */
fun resolveFirstOrderTypes() {
for (type in typeManager.firstOrderTypes.sortedBy { it.name }) {
if (type is ObjectType && type.typeOrigin == Type.Origin.UNRESOLVED) {
Expand Down
Loading

0 comments on commit c27b13d

Please sign in to comment.