Skip to content

Commit

Permalink
Draft for type normalisation
Browse files Browse the repository at this point in the history
Fixes #1533
  • Loading branch information
oxisto committed Jun 27, 2024
1 parent 777dfe2 commit 19744c3
Show file tree
Hide file tree
Showing 60 changed files with 868 additions and 396 deletions.
39 changes: 27 additions & 12 deletions cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ class ScopeManager : ScopeProvider {
}

private fun getCurrentTypedefs(searchScope: Scope?): Collection<TypedefDeclaration> {
val typedefs = mutableMapOf<Type, TypedefDeclaration>()
val typedefs = mutableMapOf<Name, TypedefDeclaration>()

val path = mutableListOf<ValueDeclarationScope>()
var current = searchScope
Expand Down Expand Up @@ -781,7 +781,8 @@ class ScopeManager : ScopeProvider {
}

/**
* This function extracts a scope for the [Name], e.g. if the name is fully qualified.
* 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.
*
* The pair returns the extracted scope and a name that is adjusted by possible import aliases.
* The extracted scope is "responsible" for the name (e.g. declares the parent namespace) and
Expand Down Expand Up @@ -822,7 +823,7 @@ class ScopeManager : ScopeProvider {
LOGGER,
"Could not find the scope $scopeName needed to resolve $n"
)
scope
null
} else {
scopes[0]
}
Expand All @@ -833,13 +834,14 @@ class ScopeManager : ScopeProvider {

/**
* This function resolves a name alias (contained in an import alias) for the [Name.parent] of
* the given [Name].
* the given [Name]. This has also the major problem of only resolving one layer of aliases :(
*/
fun resolveParentAlias(name: Name, scope: Scope?): Name {
val parentName = name.parent ?: return name
var parentName = name.parent ?: return name
parentName = resolveParentAlias(parentName, scope)

// This is not 100 % ideal, but at least somewhat compatible to the previous approach
var newName = name
var newName = Name(name.localName, parentName, delimiter = name.delimiter)
var decl =
scope?.lookupSymbol(parentName.localName)?.singleOrNull {
it is NamespaceDeclaration || it is RecordDeclaration
Expand All @@ -849,6 +851,16 @@ class ScopeManager : ScopeProvider {
return Name(newName.localName, decl.name, delimiter = newName.delimiter)
}

// Some special handling of typedefs; this should somehow be merged with the above but not
// exactly sure how. The issue is that we cannot take the "name" of the typedef declaration,
// but we rather want its original type name.
// TODO: This really needs to be handled better somehow, maybe a common interface for
// typedefs, namespaces and records that return the correct name?
decl = scope?.lookupSymbol(parentName.localName)?.singleOrNull { it is TypedefDeclaration }
if ((decl as? TypedefDeclaration) != null) {
return Name(newName.localName, decl.type.name, delimiter = newName.delimiter)
}

// If we do not have a match yet, it could be that we are trying to resolve an FQN type
// during frontend translation. This is deprecated and will be replaced in the future
// by a system that also resolves type during symbol resolving. However, to support aliases
Expand Down Expand Up @@ -993,8 +1005,8 @@ class ScopeManager : ScopeProvider {
return findSymbols(name).filterIsInstance<RecordDeclaration>().singleOrNull()
}

fun typedefFor(alias: Type): Type? {
var current = currentScope
fun typedefFor(alias: Name, scope: Scope? = currentScope): Type? {
var current = scope

// We need to build a path from the current scope to the top most one. This ensures us that
// a local definition overwrites / shadows one that was there on a higher scope.
Expand All @@ -1010,7 +1022,7 @@ class ScopeManager : ScopeProvider {
// This process has several steps:
// First, do a quick local lookup, to see if we have a typedef our current scope
// (only do this if the name is not qualified)
if (!alias.name.isQualified() && current == currentScope) {
if (!alias.isQualified() && current == currentScope) {
val decl = current.typedefs[alias]
if (decl != null) {
return decl.type
Expand All @@ -1021,7 +1033,7 @@ class ScopeManager : ScopeProvider {
// qualified based on the current namespace
val key =
current.typedefs.keys.firstOrNull {
var lookupName = alias.name
var lookupName = alias

// If the lookup name is already a FQN, we can use the name directly
lookupName =
Expand All @@ -1033,7 +1045,7 @@ class ScopeManager : ScopeProvider {
currentNamespace?.fqn(lookupName.localName) ?: lookupName
}

it.name.lastPartsMatch(lookupName)
it.lastPartsMatch(lookupName)
}
if (key != null) {
return current.typedefs[key]?.type
Expand Down Expand Up @@ -1064,9 +1076,12 @@ class ScopeManager : ScopeProvider {
name: Name,
location: PhysicalLocation? = null,
startScope: Scope? = currentScope,
predicate: ((Declaration) -> Boolean)? = null,
): List<Declaration> {
val (scope, n) = extractScope(name, location, startScope)
val list = scope?.lookupSymbol(n.localName)?.toMutableList() ?: mutableListOf()
val list =
scope?.lookupSymbol(n.localName, predicate = predicate)?.toMutableList()
?: mutableListOf()

// If we have both the definition and the declaration of a function declaration in our list,
// we chose only the definition
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import de.fraunhofer.aisec.cpg.frontends.SupportsParallelParsing
import de.fraunhofer.aisec.cpg.frontends.TranslationException
import de.fraunhofer.aisec.cpg.graph.Component
import de.fraunhofer.aisec.cpg.graph.Name
import de.fraunhofer.aisec.cpg.graph.scopes.GlobalScope
import de.fraunhofer.aisec.cpg.helpers.Benchmark
import de.fraunhofer.aisec.cpg.passes.*
import java.io.File
Expand Down Expand Up @@ -308,6 +309,18 @@ private constructor(
// We want to merge everything into the final scope manager of the result
globalCtx.scopeManager.mergeFrom(parallelContexts.map { it.scopeManager })

// We also need to update all types that point to one of the "old" global scopes
// TODO(oxisto): This is really messy and instead we should have ONE global scope
// and individual file scopes beneath it
var newGlobalScope = globalCtx.scopeManager.globalScope
var types =
globalCtx.typeManager.firstOrderTypes.union(globalCtx.typeManager.secondOrderTypes)
types.forEach {
if (it.scope is GlobalScope) {
it.scope = newGlobalScope
}
}

log.info("Parallel parsing completed")

return usedFrontends
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ class TranslationResult(
val isCancelled: Boolean
get() = translationManager.isCancelled()

override var ctx: TranslationContext? = null
get() {
return finalCtx
}

/**
* Checks if only a single software component has been analyzed and returns its translation
* units. For multiple software components, it aggregates the results.
Expand Down
44 changes: 32 additions & 12 deletions cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg
import de.fraunhofer.aisec.cpg.frontends.CastNotPossible
import de.fraunhofer.aisec.cpg.frontends.CastResult
import de.fraunhofer.aisec.cpg.frontends.Language
import de.fraunhofer.aisec.cpg.graph.Name
import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.TemplateDeclaration
import de.fraunhofer.aisec.cpg.graph.scopes.Scope
Expand Down Expand Up @@ -227,11 +228,40 @@ class TypeManager {
return firstOrderTypes.any { type: Type -> type.root.name.toString() == name }
}

fun typeExists(name: Name): Type? {
return firstOrderTypes.firstOrNull { type: Type -> type.root.name == name }
}

fun resolvePossibleTypedef(alias: Type, scopeManager: ScopeManager): Type {
val finalToCheck = alias.root
val applicable = scopeManager.typedefFor(finalToCheck)
val applicable = scopeManager.typedefFor(finalToCheck.name, alias.scope)
return applicable ?: alias
}

/**
* This function returns the first (there should be only one) [Type] with the given [fqn] that
* is [Type.Origin.RESOLVED].
*/
fun lookupResolvedType(
fqn: String,
generics: List<Type>? = null,
language: Language<*>? = null
): Type? {
var primitiveType = language?.getSimpleTypeOf(fqn)
if (primitiveType != null) {
return primitiveType
}

return firstOrderTypes.firstOrNull {
(it.typeOrigin == Type.Origin.RESOLVED || it.typeOrigin == Type.Origin.GUESSED) &&
it.name.toString() == fqn &&
if (generics != null) {
(it as? ObjectType)?.generics == generics
} else {
true
}
}
}
}

val Type.ancestors: Set<Type.Ancestor>
Expand All @@ -242,17 +272,7 @@ val Type.ancestors: Set<Type.Ancestor>
internal fun Type.getAncestors(depth: Int): Set<Type.Ancestor> {
val types = mutableSetOf<Type.Ancestor>()

// Recursively call ourselves on our super types. There is a little hack here that we need to do
// for object types created from RecordDeclaration::toType() because their supertypes might not
// be set correctly. This would be better, if we change a RecordDeclaration to a
// ValueDeclaration and set the corresponding object type to its type.
val superTypes =
if (this is ObjectType) {
this.recordDeclaration?.superTypes ?: setOf()
} else {
superTypes
}

// Recursively call ourselves on our super types.
types += superTypes.flatMap { it.getAncestors(depth + 1) }

// Since the chain starts with our type, we add ourselves to it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,10 +204,12 @@ fun MetadataProvider.newTypedefDeclaration(
rawNode: Any? = null
): TypedefDeclaration {
val node = TypedefDeclaration()
node.applyMetadata(this, alias.typeName, rawNode, true)
node.applyMetadata(this, alias.typeName, rawNode)

node.type = targetType
node.alias = alias
// litle bit of a hack to make the type FQN
node.alias.name = node.name

log(node)
return node
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,16 @@ fun Type.ref(): Type {

/**
* This function returns an [ObjectType] with the given [name]. If a respective [Type] does not yet
* exist, it will be created In order to avoid unnecessary allocation of simple types, we do a
* pre-check within this function, whether a built-in type exist with the particular name.
* exist (in the current scope), it will be created. In order to avoid unnecessary allocation of
* simple types, we do a pre-check within this function, whether a built-in type exist with the
* particular name.
*/
@JvmOverloads
fun LanguageProvider.objectType(name: CharSequence, generics: List<Type> = listOf()): Type {
fun LanguageProvider.objectType(
name: CharSequence,
generics: List<Type> = listOf(),
rawNode: Any? = null
): Type {
// First, we check, whether this is a built-in type, to avoid necessary allocations of simple
// types
val builtIn = language?.getSimpleTypeOf(name.toString())
Expand All @@ -108,12 +113,15 @@ fun LanguageProvider.objectType(name: CharSequence, generics: List<Type> = listO
"Could not create type: translation context not available"
)

val scope = c.scopeManager.currentScope

synchronized(c.typeManager.firstOrderTypes) {
// We can try to look up the type by its name and return it, if it already exists.
var type =
c.typeManager.firstOrderTypes.firstOrNull {
it is ObjectType &&
it.name == name &&
it.scope == scope &&
it.generics == generics &&
it.language == language
}
Expand All @@ -124,9 +132,13 @@ fun LanguageProvider.objectType(name: CharSequence, generics: List<Type> = listO
// Otherwise, we either need to create the type because of the generics or because we do not
// know the type yet.
type = ObjectType(name, generics, false, language)
// Apply our usual metadata, such as scope, code, location, if we have any. Make sure only
// to refer by the local name because we will treat types as sort of references when
// creating them and resolve them later.
type.applyMetadata(this, name, rawNode = rawNode, localNameOnly = true)

// Piping it through register type will ensure that in any case we return the one unique
// type object for it.
// type object (per scope) for it.
return c.typeManager.registerType(type)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
*/
package de.fraunhofer.aisec.cpg.graph.declarations

import de.fraunhofer.aisec.cpg.graph.fqn

/**
* The declaration of a constructor within a [RecordDeclaration]. Is it essentially a special case
* of a [MethodDeclaration].
Expand All @@ -38,6 +40,9 @@ class ConstructorDeclaration : MethodDeclaration() {
if (recordDeclaration != null) {
// constructors always have implicitly the return type of their class
returnTypes = listOf(recordDeclaration.toType())

// also make sure, our name is updated to the FQN of the record
name = recordDeclaration.name.fqn(this.name.localName)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge
import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList
import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate
import de.fraunhofer.aisec.cpg.graph.statements.Statement
import de.fraunhofer.aisec.cpg.graph.types.DeclaresType
import de.fraunhofer.aisec.cpg.graph.types.ObjectType
import de.fraunhofer.aisec.cpg.graph.types.Type
import org.apache.commons.lang3.builder.ToStringBuilder
import org.neo4j.ogm.annotation.Relationship
import org.neo4j.ogm.annotation.Transient

/** Represents a C++ union/struct/class or Java class */
open class RecordDeclaration : Declaration(), DeclarationHolder, StatementHolder, EOGStarterHolder {
open class RecordDeclaration :
Declaration(), DeclarationHolder, StatementHolder, EOGStarterHolder, DeclaresType {
/** The kind, i.e. struct, class, union or enum. */
var kind: String? = null

Expand Down Expand Up @@ -229,4 +231,7 @@ open class RecordDeclaration : Declaration(), DeclarationHolder, StatementHolder
type.superTypes.addAll(this.superTypes)
return type
}

override val declaredType: Type
get() = toType()
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import java.util.*
import org.apache.commons.lang3.builder.ToStringBuilder

/** Represents a type alias definition as found in C/C++: `typedef unsigned long ulong;` */
class TypedefDeclaration : Declaration() {
class TypedefDeclaration : Declaration() /*, DeclaresType*/ {
/** The already existing type that is to be aliased */
var type: Type = UnknownType.getUnknownType(null)

Expand All @@ -52,4 +52,7 @@ class TypedefDeclaration : Declaration() {
.append("alias", alias)
.toString()
}

/*override val declaredType: Type
get() = alias*/
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class GlobalScope : StructureDeclarationScope(null) {

// Merge symbols lists
symbols.mergeFrom(other.symbols)
wildcardImports.addAll(other.wildcardImports)

for (symbolList in other.symbols) {
// Update the scope property of all nodes that live on the global scope to our new
Expand Down
Loading

0 comments on commit 19744c3

Please sign in to comment.