Skip to content
New issue

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

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

Already on GitHub? # to your account

Cleanup of SymbolResolver #1777

Merged
merged 9 commits into from
Oct 4, 2024
Merged
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
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
Loading