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

Create one Type object per type reference #1773

Merged
merged 11 commits into from
Dec 2, 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
10 changes: 9 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,19 @@ jobs:
id: build
env:
VERSION: ${{ env.version }}
- name: Prepare report.xml for Codecov
run: |
# this is needed because codecov incorrectly reports lines that have no coverage information (good or bad) as a miss
# See https://github.com/codecov/feedback/issues/564 and https://github.com/Kotlin/kotlinx-kover/issues/699.
# Actually these lines should just not exist in the coverage XML file, since they are only structural elements, such
# as brackets.
cat cpg-all/build/reports/kover/report.xml | grep -v 'mi="0" ci="0" mb="0" cb="0"' > cpg-all/build/reports/kover/report-codecov.xml
rm cpg-all/build/reports/kover/report.xml
- name: Upload Code Coverage
uses: codecov/codecov-action@v5
with:
fail_ci_if_error: true
files: ./cpg-all/build/reports/kover/report.xml
files: ./cpg-all/build/reports/kover/report-codecov.xml
token: ${{ secrets.CODECOV_TOKEN }}
verbose: true
- name: Prepare test and coverage reports
Expand Down
25 changes: 4 additions & 21 deletions cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ 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>()

/**
* @param recordDeclaration that is instantiated by a template containing parameterizedtypes
Expand Down Expand Up @@ -200,26 +200,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 Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,8 @@ fun <T : Node, AstNode> T.codeAndLocationFromOtherRawNode(rawNode: AstNode?): T
* are between the child nodes.
*
* @param parentNode Used to extract the code for this node.
* @param newLineType The char(s) used to describe a new line, usually either "\n" or "\r\n". This
* is needed because the location block spanning the children usually comprises more than one
* @param lineBreakSequence The char(s) used to describe a new line, usually either "\n" or "\r\n".
* This is needed because the location block spanning the children usually comprises more than one
* line.
*/
context(CodeAndLocationProvider<AstNode>)
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 @@ -31,13 +31,10 @@
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import de.fraunhofer.aisec.cpg.IncompatibleSignature
import de.fraunhofer.aisec.cpg.ancestors
import de.fraunhofer.aisec.cpg.SignatureMatches
import de.fraunhofer.aisec.cpg.frontends.CastNotPossible
import de.fraunhofer.aisec.cpg.graph.ContextProvider
import de.fraunhofer.aisec.cpg.graph.LanguageProvider
import de.fraunhofer.aisec.cpg.graph.Node
import de.fraunhofer.aisec.cpg.graph.declarations.*
import de.fraunhofer.aisec.cpg.graph.objectType
import de.fraunhofer.aisec.cpg.graph.parseName
import de.fraunhofer.aisec.cpg.graph.types.Type
import de.fraunhofer.aisec.cpg.graph.unknownType
Expand Down Expand Up @@ -117,10 +114,10 @@
private fun findFunctionDeclarationEntry(functionDecl: FunctionDeclaration): List<DFGEntry>? {
if (functionToDFGEntryMap.isEmpty()) return null

val provider = functionDecl
val language = functionDecl.language
val languageName = language?.javaClass?.name
val methodName = functionDecl.name
val typeManager = functionDecl.ctx?.typeManager ?: return null

// The language and the method name have to match. If a signature is specified, it also has
// to match to the one of the FunctionDeclaration, null indicates that we accept everything.
Expand All @@ -131,7 +128,12 @@
// Split the name if we have a FQN
val entryMethodName = language.parseName(it.methodName)
val entryRecord =
entryMethodName.parent?.let { provider.lookupType(entryMethodName.parent) }
entryMethodName.parent?.let {
typeManager.lookupResolvedType(
entryMethodName.parent,
language = language
)
}
methodName.lastPartsMatch(
entryMethodName.localName
) && // The local name has to match
Expand All @@ -148,7 +150,10 @@
(it.signature == null ||
functionDecl.matchesSignature(
it.signature.map { signatureType ->
provider.lookupType(signatureType)
typeManager.lookupResolvedType(
signatureType,
language = language
) ?: functionDecl.unknownType()

Check warning on line 156 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/DFGFunctionSummaries.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/DFGFunctionSummaries.kt#L156

Added line #L156 was not covered by tests
}
) != IncompatibleSignature)
} else {
Expand Down Expand Up @@ -184,57 +189,50 @@
.map {
Pair(
language.parseName(it.methodName).parent?.let { it1 ->
functionDecl.objectType(it1)
},
typeManager.lookupResolvedType(it1, language = language)
} ?: language.unknownType(),

Check warning on line 193 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/DFGFunctionSummaries.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/DFGFunctionSummaries.kt#L193

Added line #L193 was not covered by tests
it
)
}
var mostPreciseClassEntries = mutableListOf<FunctionDeclarationEntry>()
var mostPreciseType = typeEntryList.first().first
var superTypes = mostPreciseType?.ancestors?.map { it.type } ?: setOf()
for (typeEntry in typeEntryList) {
if (typeEntry.first == mostPreciseType) {
mostPreciseClassEntries.add(typeEntry.second)
} else if (typeEntry.first in superTypes) {
mostPreciseClassEntries.clear()
mostPreciseClassEntries.add(typeEntry.second)
mostPreciseType = typeEntry.first
superTypes = mostPreciseType?.ancestors?.map { it.type } ?: setOf()
}
}
val maxSignature = mostPreciseClassEntries.mapNotNull { it.signature?.size }.max()
if (mostPreciseClassEntries.size > 1) {
mostPreciseClassEntries =
mostPreciseClassEntries
.filter { it.signature?.size == maxSignature }
.toMutableList()
}
// Filter parameter types. We start with parameter 0 and continue. Let's hope we remove
// some entries here.
var argIndex = 0
while (mostPreciseClassEntries.size > 1 && argIndex < maxSignature) {
mostPreciseType =
mostPreciseClassEntries.first().signature?.get(argIndex)?.let {
functionDecl.objectType(it)
}
superTypes = mostPreciseType?.ancestors?.map { it.type } ?: setOf()
val newMostPrecise = mutableListOf<FunctionDeclarationEntry>()
for (entry in mostPreciseClassEntries) {
val currentType =
entry.signature?.get(argIndex)?.let { functionDecl.objectType(it) }
if (currentType == mostPreciseType) {
newMostPrecise.add(entry)
} else if (currentType in superTypes) {
newMostPrecise.clear()
newMostPrecise.add(entry)
mostPreciseType = currentType
superTypes = mostPreciseType?.ancestors?.map { it.type } ?: setOf()
val uniqueTypes = typeEntryList.map { it.first }.distinct()
val targetType =
language.parseName(functionDecl.name).parent?.let { it1 ->
typeManager.lookupResolvedType(it1, language = language)
} ?: language.unknownType()

Check warning on line 201 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/DFGFunctionSummaries.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/DFGFunctionSummaries.kt#L201

Added line #L201 was not covered by tests

var mostPreciseType =
uniqueTypes
.map { Pair(it, language?.tryCast(targetType, it)) }
.sortedBy { it.second?.depthDistance }
.firstOrNull()
?.first

var mostPreciseClassEntries =
typeEntryList.filter { it.first == mostPreciseType }.map { it.second }

var signatureResults =
mostPreciseClassEntries
.map {
Pair(
it,
functionDecl.matchesSignature(
it.signature?.map {
typeManager.lookupResolvedType(it, language = language)
?: language.unknownType()
} ?: listOf()

Check warning on line 222 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/DFGFunctionSummaries.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/DFGFunctionSummaries.kt#L221-L222

Added lines #L221 - L222 were not covered by tests
)
)
}
}
argIndex++
mostPreciseClassEntries = newMostPrecise
}
functionToDFGEntryMap[mostPreciseClassEntries.first()]
.filter { it.second is SignatureMatches }
.associate { it }

val rankings = signatureResults.entries.map { Pair(it.value.ranking, it.key) }

// Find the best (lowest) rank and find functions with the specific rank
val bestRanking = rankings.minBy { it.first }.first
val list = rankings.filter { it.first == bestRanking }.map { it.second }

functionToDFGEntryMap[list.first()]
} else {
null
}
Expand Down Expand Up @@ -356,19 +354,4 @@

val log: Logger = LoggerFactory.getLogger(DFGFunctionSummaries::class.java)
}

fun ContextProvider.lookupType(fqn: CharSequence): Type {
// Try to look up the type from the specified FQN string
var type =
ctx?.typeManager?.lookupResolvedType(
fqn.toString(),
language = (this as? LanguageProvider)?.language
)
return if (type == null) {
log.warn("Could not find specified type $fqn. Using UnknownType")
this.unknownType()
} else {
type
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,46 +84,48 @@ class DFGFunctionSummariesTest {
.build {
translationResult {
translationUnit("DfgInferredCall.c") {
function("main", t("int")) {
body {
// We need three types with a type hierarchy.
val objectType = t("test.Object")
val listType = t("test.List")
ctx?.let {
val recordDecl =
[email protected](it)
?.inferRecordDeclaration(
listType,
)
listType.recordDeclaration = recordDecl
recordDecl?.addSuperClass(objectType)
listType.superTypes.add(objectType)
}
namespace("test") {
// We need three types with a type hierarchy.
val objectType = t("test.Object")
val listType = t("test.List")
ctx?.let {
val recordDecl =
startInference(it)
?.inferRecordDeclaration(
listType,
)
listType.recordDeclaration = recordDecl
recordDecl?.addSuperClass(objectType)
listType.superTypes.add(objectType)
}

val specialListType = t("test.SpecialList")
ctx?.let {
val recordDecl =
this@translationUnit.startInference(it)
?.inferRecordDeclaration(
specialListType,
)
specialListType.recordDeclaration = recordDecl
recordDecl?.addSuperClass(listType)
specialListType.superTypes.add(listType)
}
val specialListType = t("test.SpecialList")
ctx?.let {
val recordDecl =
startInference(it)
?.inferRecordDeclaration(
specialListType,
)
specialListType.recordDeclaration = recordDecl
recordDecl?.addSuperClass(listType)
specialListType.superTypes.add(listType)
}

val verySpecialListType = t("test.VerySpecialList")
ctx?.let {
val recordDecl =
[email protected](it)
?.inferRecordDeclaration(
specialListType,
)
specialListType.recordDeclaration = recordDecl
recordDecl?.addSuperClass(listType)
specialListType.superTypes.add(listType)
}
val verySpecialListType = t("test.VerySpecialList")
ctx?.let {
val recordDecl =
startInference(it)
?.inferRecordDeclaration(
verySpecialListType,
)
verySpecialListType.recordDeclaration = recordDecl
recordDecl?.addSuperClass(specialListType)
verySpecialListType.superTypes.add(listType)
}
}

function("main", t("int")) {
body {
memberCall("addAll", construct("test.VerySpecialList")) {
literal(1, t("int"))
construct("test.Object")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ fun assertUsageOfMemberAndBase(usingNode: Node?, usedBase: Node?, usedMember: De
}

fun assertFullName(fqn: String, node: Node?, message: String? = null) {
assertNotNull(node)
assertNotNull(node, message)
assertEquals(fqn, node.name.toString(), message)
}

Expand All @@ -317,8 +317,8 @@ 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 {
fun ContextProvider.assertResolvedType(fqn: String): Type {
var type =
ctx?.typeManager?.lookupResolvedType(fqn, generics, (this as? LanguageProvider)?.language)
ctx?.typeManager?.lookupResolvedType(fqn, language = (this as? LanguageProvider)?.language)
return assertNotNull(type)
}
Loading
Loading