Skip to content

Commit

Permalink
More inference
Browse files Browse the repository at this point in the history
  • Loading branch information
oxisto committed Nov 21, 2024
1 parent 90a1534 commit 2b7147c
Show file tree
Hide file tree
Showing 16 changed files with 396 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ private constructor(
/** Enables the inference of variables, such as global variables. */
val inferVariables: Boolean,

/**
* A very EXPERIMENTAL feature. If this is enabled, we will try to infer return types of
* functions based on the context of the call it originated out of. This is disabled by default.
*/
val inferReturnTypes: Boolean,

/**
* Uses heuristics to add DFG edges for call expressions to unresolved functions (i.e.,
* functions not implemented in the given source code).
Expand All @@ -61,6 +67,7 @@ private constructor(
private var inferRecords: Boolean = true,
private var inferFunctions: Boolean = true,
private var inferVariables: Boolean = true,
private var inferReturnTypes: Boolean = false,
private var inferDfgForUnresolvedCalls: Boolean = true
) {
fun enabled(infer: Boolean) = apply { this.enabled = infer }
Expand All @@ -73,6 +80,8 @@ private constructor(

fun inferVariables(infer: Boolean) = apply { this.inferVariables = infer }

fun inferReturnTypes(infer: Boolean) = apply { this.inferReturnTypes = infer }

fun inferDfgForUnresolvedCalls(infer: Boolean) = apply {
this.inferDfgForUnresolvedCalls = infer
}
Expand All @@ -84,6 +93,7 @@ private constructor(
inferRecords,
inferFunctions,
inferVariables,
inferReturnTypes,
inferDfgForUnresolvedCalls
)
}
Expand Down
28 changes: 1 addition & 27 deletions cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,6 @@ class ScopeManager : ScopeProvider {
val currentRecord: RecordDeclaration?
get() = this.firstScopeIsInstanceOrNull<RecordScope>()?.astNode as? RecordDeclaration

val currentTypedefs: Collection<TypedefDeclaration>
get() = this.getCurrentTypedefs(currentScope)

val currentNamespace: Name?
get() {
val namedScope = this.firstScopeIsInstanceOrNull<NameScope>()
Expand Down Expand Up @@ -237,7 +234,7 @@ class ScopeManager : ScopeProvider {
is Block -> BlockScope(nodeToScope)
is WhileStatement,
is DoStatement,
is AssertStatement -> LoopScope(nodeToScope as Statement)
is AssertStatement -> LoopScope(nodeToScope)
is ForStatement,
is ForEachStatement -> LoopScope(nodeToScope as Statement)
is SwitchStatement -> SwitchScope(nodeToScope)
Expand Down Expand Up @@ -508,29 +505,6 @@ class ScopeManager : ScopeProvider {
scope?.addTypedef(typedef)
}

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

val path = mutableListOf<ValueDeclarationScope>()
var current = searchScope

// We need to build a path from the current scope to the top most one
while (current != null) {
if (current is ValueDeclarationScope) {
path += current
}
current = current.parent
}

// And then follow the path in reverse. This ensures us that a local definition
// overwrites / shadows one that was there on a higher scope.
for (scope in path.reversed()) {
typedefs.putAll(scope.typedefs)
}

return typedefs.values
}

/**
* Resolves only references to Values in the current scope, static references to other visible
* records are not resolved over the ScopeManager.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,14 @@ interface HasAnonymousIdentifier : LanguageTrait {
*/
interface HasGlobalVariables : LanguageTrait

/**
* A language trait, that specifies that this language has global functions directly in the
* [GlobalScope], i.e., not within a namespace, but directly contained in a
* [TranslationUnitDeclaration]. For example, C++ has global functions, Java and Go do not (as every
* function is either in a class or a namespace).
*/
interface HasGlobalFunctions : LanguageTrait

/**
* A common super-class for all language traits that arise because they are an ambiguity of a
* function call, e.g., function-style casts. This means that we cannot differentiate between a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
*/
package de.fraunhofer.aisec.cpg.passes.inference

import de.fraunhofer.aisec.cpg.InferenceConfiguration
import de.fraunhofer.aisec.cpg.ScopeManager
import de.fraunhofer.aisec.cpg.TranslationContext
import de.fraunhofer.aisec.cpg.TypeManager
Expand All @@ -34,11 +33,13 @@ import de.fraunhofer.aisec.cpg.frontends.Language
import de.fraunhofer.aisec.cpg.graph.*
import de.fraunhofer.aisec.cpg.graph.declarations.*
import de.fraunhofer.aisec.cpg.graph.scopes.Scope
import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator
import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConstructExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference
import de.fraunhofer.aisec.cpg.graph.statements.expressions.TypeExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator
import de.fraunhofer.aisec.cpg.graph.types.*
import de.fraunhofer.aisec.cpg.helpers.Util.debugWithFileLocation
import de.fraunhofer.aisec.cpg.helpers.Util.errorWithFileLocation
Expand Down Expand Up @@ -80,7 +81,7 @@ class Inference internal constructor(val start: Node, override val ctx: Translat
code: String?,
isStatic: Boolean,
signature: List<Type?>,
returnType: Type?,
incomingReturnType: Type?,
hint: CallExpression? = null
): FunctionDeclaration? {
if (!ctx.config.inferenceConfiguration.inferFunctions) {
Expand Down Expand Up @@ -111,10 +112,11 @@ class Inference internal constructor(val start: Node, override val ctx: Translat
debugWithFileLocation(
hint,
log,
"Inferred a new {} declaration {} with parameter types {} in $it",
"Inferred a new {} declaration {} with parameter types {} and return types {} in $it",
if (inferred is MethodDeclaration) "method" else "function",
inferred.name,
signature.map { it?.name }
signature.map { it?.name },
inferred.returnTypes.map { it.name }
)

// Create parameter declarations and receiver (only for methods).
Expand All @@ -124,6 +126,15 @@ class Inference internal constructor(val start: Node, override val ctx: Translat
createInferredParameters(inferred, signature)

// Set the type and return type(s)
var returnType =
if (

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

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt#L130

Added line #L130 was not covered by tests
ctx.config.inferenceConfiguration.inferReturnTypes &&
incomingReturnType is UnknownType
) {
inferReturnType(hint)
} else {
incomingReturnType
}
returnType?.let { inferred.returnTypes = listOf(it) }
inferred.type = FunctionType.computeType(inferred)

Expand Down Expand Up @@ -528,6 +539,52 @@ class Inference internal constructor(val start: Node, override val ctx: Translat
this.scopeManager = ctx.scopeManager
this.typeManager = ctx.typeManager
}

fun inferReturnType(call: CallExpression?): Type {
if (call == null) {
return unknownType()

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

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt#L545

Added line #L545 was not covered by tests
}

// Try to find out, if the supplied hint is part of an assignment. If yes, we can use their
// type as the return type of the function
var targetType =
ctx.currentComponent.assignments.firstOrNull { it.value == call }?.target?.type
if (targetType != null && targetType !is UnknownType) {
return targetType
}

// Look for an "argument holder". These can be different kind of nodes
val holder =
ctx.currentComponent.allChildren<ArgumentHolder> { it.hasArgument(call) }.singleOrNull()
when (holder) {
is UnaryOperator -> {
// If it's a boolean operator, the return type is probably a boolean
if (holder.operatorCode == "!") {
return call.language?.builtInTypes?.values?.firstOrNull { it is BooleanType }
?: unknownType()

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

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt#L564

Added line #L564 was not covered by tests
}
// If it's a numeric operator, return the fist numeric type that we have
if (holder.operatorCode in listOf("+", "-", "++", "--")) {
return call.language?.builtInTypes?.values?.firstOrNull { it is NumericType }
?: unknownType()

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

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt#L569

Added line #L569 was not covered by tests
}
}
is ConstructExpression -> {
return holder.type
}
is BinaryOperator -> {
// If it is on the right side, it's probably the same as on the left-side (and
// vice-versa)
if (call == holder.rhs) {
return holder.lhs.type
} else if (call == holder.lhs) {
return holder.rhs.type

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

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt#L581

Added line #L581 was not covered by tests
}
}
}

return unknownType()
}
}

/** Provides information about the inference status of a node. */
Expand Down
Loading

0 comments on commit 2b7147c

Please sign in to comment.