Skip to content

Commit

Permalink
Create one Type object per type reference
Browse files Browse the repository at this point in the history
Fixes #1768
Fixes #1770
  • Loading branch information
oxisto committed Oct 4, 2024
1 parent c27b13d commit 4f5828a
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 115 deletions.
25 changes: 25 additions & 0 deletions cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.*
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
Expand Down Expand Up @@ -1009,6 +1010,30 @@ class ScopeManager : ScopeProvider {

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.
*
* It is important to know that the lookup needs to be unique, so if multiple declarations match
* this symbol, a warning is triggered and null is returned.
*/
fun lookupUniqueTypeSymbolByName(name: Name, startScope: Scope?): DeclaresType? {
var symbols =
lookupSymbolByName(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()
}
}

/**
Expand Down
50 changes: 15 additions & 35 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,11 +28,14 @@ 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.parseName
import de.fraunhofer.aisec.cpg.graph.scopes.Scope
import de.fraunhofer.aisec.cpg.graph.scopes.TemplateScope
import de.fraunhofer.aisec.cpg.graph.types.*
import de.fraunhofer.aisec.cpg.passes.TypeResolver
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import org.slf4j.Logger
Expand All @@ -54,8 +57,14 @@ class TypeManager {
MutableMap<TemplateDeclaration, MutableList<ParameterizedType>> =
ConcurrentHashMap()

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

/**
* A map of declared types by their name. Useful to check for the existence of a declared type
* by its fully qualified name across all scopes.
*/
@PopulatedByPass(TypeResolver::class) val declaredTypes = mutableMapOf<Name, Type>()

/**
* @param recordDeclaration that is instantiated by a template containing parameterizedtypes
Expand Down Expand Up @@ -197,26 +206,9 @@ class TypeManager {
}

if (t.isFirstOrderType) {
// 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
} else {
log.trace(
"Registering unique first order type {}{}",
t.name,
if ((t as? ObjectType)?.generics?.isNotEmpty() == true) {
" with generics ${t.generics.joinToString(",", "[", "]") { it.name.toString() }}"
} else {
""
}
)
}
synchronized(firstOrderTypes) { firstOrderTypes.add(t) }
} else if (t is SecondOrderType) {
if (!secondOrderTypes.add(t)) {
return secondOrderTypes.first { it == t && it is T } as T
} else {
log.trace("Registering unique second order type {}", t.name)
}
synchronized(secondOrderTypes) { secondOrderTypes.add(t) }
}

return t
Expand All @@ -237,25 +229,13 @@ class TypeManager {
* This function returns the first (there should be only one) [Type] with the given [fqn] that
* is [Type.Origin.RESOLVED].
*/
fun lookupResolvedType(
fqn: CharSequence,
generics: List<Type>? = null,
language: Language<*>? = null
): Type? {
fun lookupResolvedType(fqn: CharSequence, 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.root.name == fqn &&
if (generics != null) {
(it as? ObjectType)?.generics == generics
} else {
true
}
}
return declaredTypes[language.parseName(fqn)]
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,34 +113,16 @@ fun LanguageProvider.objectType(
"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
}
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 we know the type and can resolve it later
return c.typeManager.registerType(type)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,27 +85,17 @@ class ResolveCallExpressionAmbiguityPass(ctx: TranslationContext) : TranslationU
var callee = call.callee
val language = callee.language

// We can skip all callees that are not references
if (callee !is Reference) {
return
}

// Check, if this is cast is really a construct expression (if the language supports
// functional-constructs)
if (language is HasFunctionStyleConstruction) {
// Make sure, we do not accidentally "construct" primitive types
if (language.builtInTypes.contains(callee.name.toString()) == true) {
return
}

val fqn =
if (callee.name.parent == null) {
scopeManager.currentNamespace.fqn(
callee.name.localName,
delimiter = callee.name.delimiter
)
} else {
callee.name
}

// Check for our type. We are only interested in object types
val type = typeManager.lookupResolvedType(fqn)
if (type is ObjectType) {
var type = lookupPotentialTypeFromReference(callee)
if (type is ObjectType && !type.isPrimitive) {
walker.replaceCallWithConstruct(type, parent, call)
}
}
Expand All @@ -114,36 +104,33 @@ class ResolveCallExpressionAmbiguityPass(ctx: TranslationContext) : TranslationU
// cast expression. And this is only really necessary, if the function call has a single
// argument.
if (language is HasFunctionStyleCasts && call.arguments.size == 1) {
var pointer = false
// If the argument is a UnaryOperator, unwrap them
if (callee is UnaryOperator && callee.operatorCode == "*") {
pointer = true
callee = callee.input
// Check if it is type and replace the call
var type = lookupPotentialTypeFromReference(callee)
if (type != null) {
walker.replaceCallWithCast(type, parent, call, false)
}
}
}

// First, check if this is a built-in type
var builtInType = language.getSimpleTypeOf(callee.name)
if (builtInType != null) {
walker.replaceCallWithCast(builtInType, parent, call, false)
} else {
// If not, then this could still refer to an existing type. We need to make sure
// that we take the current namespace into account
val fqn =
if (callee.name.parent == null) {
scopeManager.currentNamespace.fqn(
callee.name.localName,
delimiter = callee.name.delimiter
)
} else {
callee.name
}

val type = typeManager.lookupResolvedType(fqn)
if (type != null) {
walker.replaceCallWithCast(type, parent, call, pointer)
}
}
/** This function checks whether our [Reference] refers to a [Type]. */
private fun lookupPotentialTypeFromReference(ref: Reference): Type? {
var name = ref.name
var scope = ref.scope

// First, check if it is a simple type
var type = ref.language?.getSimpleTypeOf(name)
if (type != null) {
return type
}

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

// Lastly, check if the reference contains a symbol that points to type (declaration)
return scopeManager.lookupUniqueTypeSymbolByName(name, scope)?.declaredType
}

override fun cleanup() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ fun <T : Any?> assertLiteralValue(expected: T, expr: Expression?, message: Strin
assertEquals(expected, assertIs<Literal<T>>(expr).value, message)
}

fun ContextProvider.assertResolvedType(fqn: String, generics: List<Type>? = null): Type {
var type = ctx?.typeManager?.lookupResolvedType(fqn, generics)
fun ContextProvider.assertResolvedType(fqn: String): Type {
var type = ctx?.typeManager?.lookupResolvedType(fqn)
return assertNotNull(type)
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,7 @@ internal class CXXLanguageFrontendTest : BaseTest() {
val decl = main
val ls = decl.variables["ls"]
assertNotNull(ls)
assertEquals(
assertResolvedType("std::vector", listOf(assertResolvedType("int"))),
ls.type
)
assertEquals(assertResolvedType("std::vector"), ls.type)
assertLocalName("ls", ls)

val forEachStatement = decl.forEachLoops.firstOrNull()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -519,8 +519,8 @@ class Application : Callable<Int> {

if (!noDefaultPasses) {
translationConfiguration.defaultPasses()
translationConfiguration.registerPass<ControlDependenceGraphPass>()
translationConfiguration.registerPass<ProgramDependenceGraphPass>()
// translationConfiguration.registerPass<ControlDependenceGraphPass>()
// translationConfiguration.registerPass<ProgramDependenceGraphPass>()
}
if (customPasses != "DEFAULT") {
val pieces = customPasses.split(",")
Expand Down

0 comments on commit 4f5828a

Please sign in to comment.