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

Cleanup and speed-up of type resolution #1690

Closed
wants to merge 19 commits into from
Closed
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 @@ -31,10 +31,12 @@
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.ResolveCallExpressionAmbiguityPass
import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation
import java.util.*
import java.util.function.Predicate
Expand Down Expand Up @@ -1004,6 +1006,56 @@

return list
}

/**
* This function tries to look up the symbol contained in [name] (using [findSymbols]) and
* returns a [DeclaresType] node, if this name resolved to something which declares a type.
*/
fun findTypeDeclaration(name: Name, startScope: Scope?): DeclaresType? {
var symbols =
findSymbols(name = name, startScope = startScope) { 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) {
LOGGER.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
)
}

return symbols.singleOrNull()
}

/**
* This function checks, whether there exists a [Type] for the given combination of a [name] and
* [scope] and returns it. It returns null, if no such type exists.
*
* This is needed in passes that need to replace potential identifiers with [Type] nodes,
* because of ambiguities (e.g. [ResolveCallExpressionAmbiguityPass]).
*/
fun findTypeWithNameAndScope(
name: Name,
language: Language<*>?,
scope: Scope? = currentScope

Check warning on line 1041 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt#L1041

Added line #L1041 was not covered by tests
): Type? {
// First, check if it is a simple type
var type = language?.getSimpleTypeOf(name)
if (type != null) {
return type
}

// This could also be a typedef
type = typedefFor(name, scope)
if (type != null) {
return type
}

// Otherwise, try to find a type declaration with the given combination of symbol and name
var declares = findTypeDeclaration(name, scope)
return declares?.declaredType
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,17 +309,22 @@ private constructor(
// We want to merge everything into the final scope manager of the result
globalCtx.scopeManager.mergeFrom(parallelContexts.map { it.scopeManager })

var b =
Benchmark(TranslationManager::class.java, "Updating global scopes in all types", true)
// 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)
globalCtx.typeManager.firstOrderTypesMap.values
.flatten()
.union(globalCtx.typeManager.secondOrderTypes)
types.forEach {
if (it.scope is GlobalScope) {
it.scope = newGlobalScope
}
}
b.stop()

log.info("Parallel parsing completed")

Expand Down
46 changes: 36 additions & 10 deletions cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,16 @@ class TypeManager {
MutableMap<TemplateDeclaration, MutableList<ParameterizedType>> =
ConcurrentHashMap()

val firstOrderTypes: MutableSet<Type> = ConcurrentHashMap.newKeySet()
/**
* A map that contains all first order types organized by their type name as key. This is
* extremely helpful for retrieving all possibly types for a given type name, which is done
* often during frontend parsing and symbol resolving.
*/
val firstOrderTypesMap = ConcurrentHashMap<String, MutableList<Type>>()

/** Retrieves the list of all *resolved* first order types. */
val resolvedFirstOrderTypes: MutableSet<Type> = mutableSetOf()

val secondOrderTypes: MutableSet<Type> = ConcurrentHashMap.newKeySet()

/**
Expand Down Expand Up @@ -197,9 +206,13 @@ class TypeManager {
}

if (t.isFirstOrderType) {
var types =
firstOrderTypesMap.computeIfAbsent(t.name.toString()) {
Collections.synchronizedList(mutableListOf())
}
// Make sure we only ever return one unique object per type
if (!firstOrderTypes.add(t)) {
return firstOrderTypes.first { it == t && it is T } as T
if (!types.add(t)) {
return types.first { it == t && it is T } as T
} else {
log.trace(
"Registering unique first order type {}{}",
Expand All @@ -222,9 +235,9 @@ class TypeManager {
return t
}

/** Checks, whether a [Type] with the given [name] exists. */
fun typeExists(name: CharSequence): Boolean {
return firstOrderTypes.any { type: Type -> type.root.name == name }
/** Checks, whether a resolved [Type] with the given [name] exists. */
fun resolvedTypeExists(name: CharSequence): Boolean {
return resolvedFirstOrderTypes.any { type: Type -> type.name == name }
}

fun resolvePossibleTypedef(alias: Type, scopeManager: ScopeManager): Type {
Expand All @@ -235,7 +248,7 @@ class TypeManager {

/**
* This function returns the first (there should be only one) [Type] with the given [fqn] that
* is [Type.Origin.RESOLVED].
* is [Type.ResolutionState.RESOLVED].
*/
fun lookupResolvedType(
fqn: CharSequence,
Expand All @@ -247,16 +260,29 @@ class TypeManager {
return primitiveType
}

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

/**
* This function marks a type as [Type.ResolutionState.RESOLVED] and adds it to the
* [resolvedFirstOrderTypes].
*/
fun markAsResolved(type: Type) {
// Mark it as RESOLVED
type.resolutionState = Type.ResolutionState.RESOLVED

if (type.isFirstOrderType) {
// Add it to our resolved first order type list
resolvedFirstOrderTypes += type
}
}
}

val Type.ancestors: Set<Type.Ancestor>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@ interface HasSuperClasses : LanguageTrait {
): Boolean
}

/**
* A language trait, that specifies that this language has support for implicit receiver, e.g., that
* one can omit references to a base such as `this`.
*/
interface HasImplicitReceiver : LanguageTrait {

val receiverName: String
}

/**
* A language trait, that specifies that this language has certain qualifiers. If so, we should
* consider them when parsing the types.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,10 @@ fun LanguageProvider.objectType(

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 list = c.typeManager.firstOrderTypesMap[name.toString()]
if (list != null) {
var type =
c.typeManager.firstOrderTypes.firstOrNull {
list.firstOrNull {
it is ObjectType &&
it.name == name &&
it.scope == scope &&
Expand All @@ -128,19 +128,19 @@ fun LanguageProvider.objectType(
if (type != null) {
return type
}

// 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 (per scope) for it.
return c.typeManager.registerType(type)
}

// Otherwise, we either need to create the type because of the generics or because we do not
// know the type yet.
var 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 (per scope) for it.
return c.typeManager.registerType(type)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
.appendSuper(super.toString())
.append("parameters", parameters)
.append("returnType", returnType)
.append("typeOrigin", typeOrigin)
.append("resolutionState", resolutionState)

Check warning on line 92 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt#L92

Added line #L92 was not covered by tests
.toString()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ open class NumericType(

init {
// Built-in types are always resolved
this.typeOrigin = Origin.RESOLVED
this.resolutionState = ResolutionState.RESOLVED
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
.appendSuper(super.toString())
.append("elementType", elementType)
.append("name", name)
.append("typeOrigin", typeOrigin)
.append("resolutionState", resolutionState)

Check warning on line 84 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.kt#L84

Added line #L84 was not covered by tests
.toString()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@ class StringType(

init {
// Built-in types are always resolved
this.typeOrigin = Origin.RESOLVED
this.resolutionState = ResolutionState.RESOLVED
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
var isPrimitive = false
protected set

open var typeOrigin: Origin? = null
open var resolutionState: ResolutionState? = null

/**
* This points to the [DeclaresType] node (most likely a [Declaration]), that declares this
Expand All @@ -87,12 +87,12 @@

constructor(typeName: String?) {
name = language.parseName(typeName ?: UNKNOWN_TYPE_STRING)
typeOrigin = Origin.UNRESOLVED
resolutionState = ResolutionState.UNRESOLVED
}

constructor(type: Type?) {
type?.name?.let { name = it.clone() }
typeOrigin = type?.typeOrigin
resolutionState = type?.resolutionState
}

constructor(typeName: CharSequence, language: Language<*>?) {
Expand All @@ -103,20 +103,33 @@
language.parseName(typeName)
}
this.language = language
typeOrigin = Origin.UNRESOLVED
resolutionState = ResolutionState.UNRESOLVED
}

constructor(fullTypeName: Name, language: Language<*>?) {
name = fullTypeName.clone()
typeOrigin = Origin.UNRESOLVED
resolutionState = ResolutionState.UNRESOLVED

Check warning on line 111 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt#L111

Added line #L111 was not covered by tests
this.language = language
}

/** Type Origin describes where the Type information came from */
enum class Origin {
/** Information about the resolution state of this type. */
enum class ResolutionState {
/**
* The type is fully resolved and if it is an [ObjectType], the property
* [ObjectType.recordDeclaration] will have the information about where the type was
* declared.
*/
RESOLVED,
DATAFLOW,
GUESSED,

/**
* GUESSED is not really used anymore, except in the java frontend, which needs a more or
* less complete type-rewrite. Once that is done, we can remove the GUESSED state.
*/
@Deprecated(message = "Use UNRESOLVED instead") GUESSED,

/**
* The type is not yet resolved. Therefore the type is only valid within the current scope.
*/
UNRESOLVED
}

Expand All @@ -129,11 +142,9 @@
* @param pointer Reason for the reference (array of pointer)
* @return Returns a reference to the current Type. E.g. when creating a pointer to an existing
* ObjectType
*
* TODO(oxisto) Ideally, we would make this function "internal", but there is a bug in the Go
* frontend, so that we still need this function :(
*/
abstract fun reference(pointer: PointerOrigin?): Type
// TODO(oxisto): Make this internal, but some tests still use it
/*internal*/ abstract fun reference(pointer: PointerOrigin?): Type

/**
* @return Dereferences the current Type by resolving the reference. E.g. when dereferencing a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class UnknownType private constructor() : Type() {
return "UNKNOWN"
}

override var typeOrigin: Origin? = null
override var resolutionState: ResolutionState? = null

companion object {
/** A map of [UnknownType] and their respective [Language]. */
Expand Down
Loading
Loading