Skip to content

Commit

Permalink
Better support for language(s) in components and result (#1913)
Browse files Browse the repository at this point in the history
* Introducing an UnknownLanguage

* Calculating multi language in component and translation result

* Cleanup but test fails

* Fixed test, not sure why

* Fixed

* Added NoLanguage, code cleanup

* Small cleanup
  • Loading branch information
oxisto authored Dec 30, 2024
1 parent 9f0e957 commit 7705922
Show file tree
Hide file tree
Showing 42 changed files with 212 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ fun getFanciesFor(original: Node?, node: Node?): List<Pair<AttributedStyle, Regi
fancyWord("new", node, list, styles.keyword)

// check for primitive types
for (primitive in node.language?.primitiveTypeNames ?: listOf()) {
for (primitive in node.language.primitiveTypeNames) {
fancyWord(primitive, node, list, styles.keyword)
}

Expand Down Expand Up @@ -283,7 +283,7 @@ private fun fancyType(
node: HasType,
list: MutableList<Pair<AttributedStyle, Region>>
) {
val types = outer.language?.primitiveTypeNames?.toMutableSet() ?: mutableSetOf()
val types = outer.language.primitiveTypeNames.toMutableSet()
types += node.type.name.toString()

// check for primitive types
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,8 @@ class ScopeManager : ScopeProvider {
helper?.type is FunctionPointerType && it is FunctionDeclaration -> {
val fptrType = helper.type as FunctionPointerType
// TODO(oxisto): Support multiple return values
val returnType = it.returnTypes.firstOrNull() ?: IncompleteType()
val returnType =
it.returnTypes.firstOrNull() ?: IncompleteType(ref.language)
returnType == fptrType.returnType &&
it.matchesSignature(fptrType.parameters) !=
IncompatibleSignature
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
*/
package de.fraunhofer.aisec.cpg

import de.fraunhofer.aisec.cpg.frontends.Language
import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend
import de.fraunhofer.aisec.cpg.frontends.multiLanguage
import de.fraunhofer.aisec.cpg.graph.Component
import de.fraunhofer.aisec.cpg.graph.Name
import de.fraunhofer.aisec.cpg.graph.Node
Expand Down Expand Up @@ -173,6 +175,12 @@ class TranslationResult(
override val config: TranslationConfiguration
get() = finalCtx.config

override var language: Language<*>
get() {
return multiLanguage()
}
set(_) {}

companion object {
const val SOURCE_LOCATIONS_TO_FRONTEND = "sourceLocationsToFrontend"
const val APPLICATION_LOCAL_NAME = "application"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
*/
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.declarations.RecordDeclaration
Expand Down Expand Up @@ -183,7 +182,7 @@ class TypeManager {
fun createOrGetTypeParameter(
templateDeclaration: TemplateDeclaration,
typeName: String,
language: Language<*>?
language: Language<*>
): ParameterizedType {
var parameterizedType = getTypeParameter(templateDeclaration, typeName)
if (parameterizedType == null) {
Expand Down Expand Up @@ -271,7 +270,7 @@ internal fun Type.getAncestors(depth: Int): Set<Type.Ancestor> {
* Optionally, the nodes that hold the respective type can be supplied as [hint] and [targetHint].
*/
fun Type.tryCast(targetType: Type, hint: HasType? = null, targetHint: HasType? = null): CastResult {
return this.language?.tryCast(this, targetType, hint, targetHint) ?: CastNotPossible
return this.language.tryCast(this, targetType, hint, targetHint)
}

/**
Expand Down Expand Up @@ -351,7 +350,7 @@ val Collection<Type>.commonType: Type?
context(Pass<*>)
fun Reference.nameIsType(): Type? {
// First, check if it is a simple type
var type = language?.getSimpleTypeOf(name)
var type = language.getSimpleTypeOf(name)
if (type != null) {
return type
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize
import de.fraunhofer.aisec.cpg.*
import de.fraunhofer.aisec.cpg.graph.Name
import de.fraunhofer.aisec.cpg.graph.Node
import de.fraunhofer.aisec.cpg.graph.OverlayNode
import de.fraunhofer.aisec.cpg.graph.declarations.Declaration
import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
import de.fraunhofer.aisec.cpg.graph.edges.ast.TemplateArguments
Expand Down Expand Up @@ -78,11 +79,12 @@ data class ImplicitCast(override var depthDistance: Int) : CastResult(depthDista
* the [Node.language] property.
*/
abstract class Language<T : LanguageFrontend<*, *>> : Node() {

/** The file extensions without the dot */
abstract val fileExtensions: List<String>

/** The namespace delimiter used by the language. Often, this is "." */
abstract val namespaceDelimiter: String
open val namespaceDelimiter: String = "."

@get:JsonSerialize(using = KClassSerializer::class)
/** The class of the frontend which is used to parse files of this language. */
Expand Down Expand Up @@ -146,10 +148,8 @@ abstract class Language<T : LanguageFrontend<*, *>> : Node() {
}

init {
this.also { language ->
this.language = language
language::class.simpleName?.let { this.name = Name(it) }
}
this.language = this
this::class.simpleName?.let { this.name = Name(it) }
}

private fun arithmeticOpTypePropagation(lhs: Type, rhs: Type): Type {
Expand All @@ -158,8 +158,8 @@ abstract class Language<T : LanguageFrontend<*, *>> : Node() {
lhs !is FloatingPointType && lhs is NumericType && rhs is FloatingPointType -> rhs
lhs is FloatingPointType && rhs is FloatingPointType ||
lhs is IntegerType && rhs is IntegerType ->
// We take the one with the bigger bitwidth
if (((lhs as NumericType).bitWidth ?: 0) >= ((rhs as NumericType).bitWidth ?: 0)) {
// We take the one with the bigger bit-width
if ((lhs.bitWidth ?: 0) >= (rhs.bitWidth ?: 0)) {
lhs
} else {
rhs
Expand Down Expand Up @@ -239,8 +239,8 @@ abstract class Language<T : LanguageFrontend<*, *>> : Node() {

/**
* This function checks, if [type] can be cast into [targetType]. Note, this also takes the
* [WrapState] of the type into account, which means that pointer types of derived types will
* not match with a non-pointer type of its base type. But, if both are pointer types, they will
* "type" of the type into account, which means that pointer types of derived types will not
* match with a non-pointer type of its base type. But, if both are pointer types, they will
* match.
*
* Optionally, the nodes that hold the respective type can be supplied as [hint] and
Expand Down Expand Up @@ -400,3 +400,54 @@ internal class KClassSerializer : JsonSerializer<KClass<*>>() {
gen.writeString(value.qualifiedName)
}
}

/**
* Represents a language definition with no known implementation or specifics. The class is used as
* a placeholder or to handle cases where the language is not explicitly defined or supported.
*/
object UnknownLanguage : Language<Nothing>() {
override val fileExtensions: List<String>
get() = listOf()

override val frontend: KClass<out Nothing> = Nothing::class
override val builtInTypes: Map<String, Type> = mapOf()
override val compoundAssignmentOperators: Set<String> = setOf()
}

/**
* Represents a "language" that is not really a language. The class is used in cases where the
* language is not explicitly defined or supported, for example in an [OverlayNode].
*/
object NoLanguage : Language<Nothing>() {
override val fileExtensions = listOf<String>()
override val frontend: KClass<out Nothing> = Nothing::class
override val builtInTypes: Map<String, Type> = mapOf()
override val compoundAssignmentOperators: Set<String> = setOf()
}

/**
* Represents a composite language definition composed of multiple languages.
*
* @property languages A list of languages that are part of this composite language definition.
*/
class MultipleLanguages(val languages: Set<Language<*>>) : Language<Nothing>() {
override val fileExtensions = languages.flatMap { it.fileExtensions }
override val frontend: KClass<out Nothing> = Nothing::class
override val builtInTypes: Map<String, Type> = mapOf()
override val compoundAssignmentOperators: Set<String> = setOf()
}

/**
* Returns the single language of a node and its children. If the node has multiple children with
* different languages, it returns a [MultipleLanguages] object.
*/
fun Node.multiLanguage(): Language<*> {
val languages = astChildren.map { it.language }.toSet()
return if (languages.size == 1) {
languages.single()
} else if (languages.size > 1) {
MultipleLanguages(languages = languages)
} else {
UnknownLanguage
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
package de.fraunhofer.aisec.cpg.graph

import de.fraunhofer.aisec.cpg.PopulatedByPass
import de.fraunhofer.aisec.cpg.frontends.Language
import de.fraunhofer.aisec.cpg.frontends.multiLanguage
import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration
import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf
import de.fraunhofer.aisec.cpg.graph.edges.unwrapping
Expand Down Expand Up @@ -65,4 +67,10 @@ open class Component : Node() {

/** All outgoing interactions such as sending data to the network or some kind of IPC. */
val outgoingInteractions: MutableList<Node> = mutableListOf()

override var language: Language<*>
get() {
return multiLanguage()
}
set(_) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation
/** A simple interface that a node has [language]. */
interface HasLanguage {

var language: Language<*>?
var language: Language<*>
}

/** A simple interface that a node has [name] and [location]. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,9 @@ class Name(
* (such as a [Node], a [Language], a [LanguageFrontend] or a [Handler]) to parse a fully qualified
* name.
*/
fun LanguageProvider?.parseName(fqn: CharSequence) =
parseName(fqn, this?.language?.namespaceDelimiter ?: ".")
fun LanguageProvider.parseName(fqn: CharSequence): Name {
return parseName(fqn, this.language.namespaceDelimiter)
}

/** Tries to parse the given fully qualified name using the specified [delimiter] into a [Name]. */
internal fun parseName(fqn: CharSequence, delimiter: String, vararg splitDelimiters: String): Name {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import de.fraunhofer.aisec.cpg.TranslationContext
import de.fraunhofer.aisec.cpg.TypeManager
import de.fraunhofer.aisec.cpg.frontends.Handler
import de.fraunhofer.aisec.cpg.frontends.Language
import de.fraunhofer.aisec.cpg.frontends.UnknownLanguage
import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration
Expand Down Expand Up @@ -93,7 +94,7 @@ abstract class Node :
*/
@Relationship(value = "LANGUAGE", direction = Relationship.Direction.OUTGOING)
@JsonBackReference
override var language: Language<*>? = null
override var language: Language<*> = UnknownLanguage

/**
* The scope this node "lives" in / in which it is defined. This property is set in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ interface MetadataProvider
* each [Node], but also transformation steps, such as [Handler].
*/
interface LanguageProvider : MetadataProvider {
val language: Language<*>?
val language: Language<*>
}

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

import de.fraunhofer.aisec.cpg.frontends.NoLanguage
import de.fraunhofer.aisec.cpg.graph.edges.overlay.OverlaySingleEdge
import de.fraunhofer.aisec.cpg.graph.edges.unwrapping
import org.neo4j.ogm.annotation.Relationship
Expand All @@ -34,6 +35,11 @@ import org.neo4j.ogm.annotation.Relationship
* typically having shared edges to extend the original graph.
*/
abstract class OverlayNode() : Node() {

init {
this.language = NoLanguage
}

@Relationship(value = "OVERLAY", direction = Relationship.Direction.INCOMING)
/** All [OverlayNode]s nodes are connected to an original cpg [Node] by this. */
val underlyingNodeEdge: OverlaySingleEdge =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ fun LanguageProvider.autoType(): Type {
return AutoType(this.language)
}

fun MetadataProvider?.incompleteType(): Type {
return IncompleteType()
fun LanguageProvider.incompleteType(): Type {
return IncompleteType(this.language)
}

/** Returns a [PointerType] that describes an array reference to the current type. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ package de.fraunhofer.aisec.cpg.graph.declarations

import de.fraunhofer.aisec.cpg.PopulatedByPass
import de.fraunhofer.aisec.cpg.frontends.Language
import de.fraunhofer.aisec.cpg.frontends.UnknownLanguage
import de.fraunhofer.aisec.cpg.graph.*
import de.fraunhofer.aisec.cpg.graph.edges.flows.Usages
import de.fraunhofer.aisec.cpg.graph.edges.unwrapping
Expand All @@ -44,11 +45,11 @@ abstract class ValueDeclaration : Declaration(), HasType, HasAliases {

override val typeObservers: MutableSet<HasType.TypeObserver> = identitySetOf()

override var language: Language<*>? = null
override var language: Language<*> = UnknownLanguage
set(value) {
// We need to adjust an eventual unknown type, once we know the language
field = value
if (value != null && type is UnknownType) {
if (type is UnknownType) {
type = UnknownType.getUnknownType(value)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ class AssignExpression :
*/
val isCompoundAssignment: Boolean
get() {
return operatorCode in (language?.compoundAssignmentOperators ?: setOf())
return operatorCode in language.compoundAssignmentOperators
}

/**
Expand All @@ -120,7 +120,7 @@ class AssignExpression :
*/
val isSimpleAssignment: Boolean
get() {
return operatorCode in (language?.simpleAssignmentOperators ?: setOf())
return operatorCode in language.simpleAssignmentOperators
}

@Relationship("DECLARATIONS") var declarationEdges = astEdgesOf<VariableDeclaration>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ open class BinaryOperator :
set(value) {
field = value
if (
(operatorCode in (language?.compoundAssignmentOperators ?: setOf())) ||
(operatorCode == "=")
(operatorCode in language.compoundAssignmentOperators) ||
(operatorCode in language.simpleAssignmentOperators)
) {
throw TranslationException(
"Creating a BinaryOperator with an assignment operator code is not allowed. The class AssignExpression should be used instead."
"Creating a BinaryOperator with an assignment operator code is not allowed. The class AssignExpression must be used instead."
)
}
}
Expand All @@ -95,14 +95,8 @@ open class BinaryOperator :
this.type = newType
} else {
// Otherwise, we have a special language-specific function to deal with type propagation
val type = language?.propagateTypeOfBinaryOperation(this)
if (type != null) {
this.type = type
} else {
// If we don't know how to propagate the types of this particular binary operation,
// we just leave the type alone. We cannot take newType because it is just "half" of
// the operation (either from lhs or rhs) and would lead to very incorrect results.
}
val type = language.propagateTypeOfBinaryOperation(this)
this.type = type
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
package de.fraunhofer.aisec.cpg.graph.statements.expressions

import de.fraunhofer.aisec.cpg.frontends.Language
import de.fraunhofer.aisec.cpg.frontends.UnknownLanguage
import de.fraunhofer.aisec.cpg.graph.*
import de.fraunhofer.aisec.cpg.graph.statements.Statement
import de.fraunhofer.aisec.cpg.graph.types.*
Expand All @@ -49,11 +50,11 @@ import org.neo4j.ogm.annotation.Transient
abstract class Expression : Statement(), HasType {
@Transient override val typeObservers: MutableSet<HasType.TypeObserver> = identitySetOf()

override var language: Language<*>? = null
override var language: Language<*> = UnknownLanguage
set(value) {
// We need to adjust an eventual unknown type, once we know the language
field = value
if (value != null && type is UnknownType) {
if (type is UnknownType) {
type = UnknownType.getUnknownType(value)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import de.fraunhofer.aisec.cpg.graph.unknownType
*
* Note: This is intentionally a distinct type and not the [UnknownType].
*/
class AutoType(override var language: Language<*>?) : Type("auto", language) {
class AutoType(override var language: Language<*>) : Type("auto", language) {
override fun reference(pointer: PointerType.PointerOrigin?): Type {
return PointerType(this, pointer)
}
Expand Down
Loading

0 comments on commit 7705922

Please sign in to comment.