[go: nahoru, domu]

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

Improvements to function and record inference #1586

Open
wants to merge 2 commits into
base: has-argument
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
More inference
  • Loading branch information
oxisto committed Jul 3, 2024
commit 493284554ad0b7a2ebe672794fe82968f3adc4b6
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,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am somewhat satisfied with this feature, so we could also enable this by default


/**
* 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
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,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 @@ -236,7 +233,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 @@ -567,29 +564,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 @@ -217,6 +217,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 language trait, that specifies that the language has so-called functional style casts, meaning
* that they look like regular call expressions. Since we can therefore not distinguish between a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ import de.fraunhofer.aisec.cpg.helpers.Util
import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn
import de.fraunhofer.aisec.cpg.passes.inference.Inference.TypeInferenceObserver
import de.fraunhofer.aisec.cpg.passes.inference.inferFunction
import de.fraunhofer.aisec.cpg.passes.inference.inferMethod
import de.fraunhofer.aisec.cpg.passes.inference.startInference
import de.fraunhofer.aisec.cpg.passes.inference.tryMethodInference
import de.fraunhofer.aisec.cpg.passes.inference.tryRecordInference
import de.fraunhofer.aisec.cpg.processing.strategy.Strategy
import org.slf4j.Logger
import org.slf4j.LoggerFactory
Expand Down Expand Up @@ -473,6 +474,11 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) {
is Reference -> handleReference(currClass, node)
is ConstructExpression -> handleConstructExpression(node)
is CallExpression -> handleCallExpression(scopeManager.currentRecord, node)

// the following function are more for type-resolving, but they are placed inside the
// symbol resolver, because this allows us to have the necessary location of nodes where
// we infer from
is RecordDeclaration -> handleRecordDeclaration(node)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to check, I think this is not necessary anymore, this should already be handled by the type resolver now.

}
}

Expand Down Expand Up @@ -614,7 +620,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) {

listOfNotNull(func)
} else {
createMethodDummies(suitableBases, bestGuess, call)
ctx.tryMethodInference(suitableBases, bestGuess, call)
}
}

Expand Down Expand Up @@ -754,34 +760,29 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) {
return invocationCandidates
}

/**
* Creates an inferred element for each RecordDeclaration
*
* @param possibleContainingTypes
* @param call
*/
protected fun createMethodDummies(
possibleContainingTypes: Set<Type>,
bestGuess: Type?,
call: CallExpression
): List<FunctionDeclaration> {
var records =
possibleContainingTypes.mapNotNull {
val root = it.root as? ObjectType
root?.recordDeclaration
}
fun handleRecordDeclaration(record: RecordDeclaration) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above, I think this is not needed anymore

var superTypeDeclarations = mutableSetOf<RecordDeclaration>()

// We access an unknown method of an unknown record. so we need to handle that
// along the way as well. We prefer the base type
if (records.isEmpty()) {
records =
listOfNotNull(
ctx.tryRecordInference(bestGuess?.root ?: unknownType(), locationHint = call)
)
// If we have not resolved our super types yet, we can probably infer them
for (type in record.superTypes) {
if (type.typeOrigin == Type.Origin.UNRESOLVED) {
var superRecord = ctx.tryRecordInference(type, locationHint = record)
type.declaredFrom = superRecord
type.recordDeclaration = superRecord
type.typeOrigin = Type.Origin.RESOLVED

if (superRecord != null) {
superTypeDeclarations += superRecord
}

record.superTypeDeclarations = superTypeDeclarations
}
}
records = records.distinct()

return records.mapNotNull { record -> record.inferMethod(call, ctx = ctx) }
// Also make sure to resolve sub-records
for (sub in record.declarations.filterIsInstance<RecordDeclaration>()) {
handleRecordDeclaration(sub)
}
}

/**
Expand Down Expand Up @@ -1016,68 +1017,3 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) {
}
}
}

fun TranslationContext.tryNamespaceInference(
name: Name,
locationHint: Node?
): NamespaceDeclaration? {
return scopeManager.globalScope
?.astNode
?.startInference(this)
?.inferNamespaceDeclaration(name, null, locationHint)
}

/**
* Tries to infer a [RecordDeclaration] from an unresolved [Type]. This will return `null`, if
* inference was not possible, or if it was turned off in the [InferenceConfiguration].
*/
fun TranslationContext.tryRecordInference(
type: Type,
locationHint: Node? = null
): RecordDeclaration? {
val kind =
if (type.language is HasStructs) {
"struct"
} else {
"class"
}
// Determine the scope where we want to start our inference
var (scope, _) = scopeManager.extractScope(type)

if (scope !is NameScope) {
scope = null
}

var holder = scope?.astNode

// If we could not find a scope, but we have an FQN, we can try to infer a namespace (or a
// parent record)
var parentName = type.name.parent
if (scope == null && parentName != null) {
// At this point, we need to check whether we have any type reference to our parent
// name. If we have (e.g. it is used in a function parameter, variable, etc.), then we
// have a high chance that this is actually a parent record and not a namespace
var parentType = typeManager.typeExists(parentName)
holder =
if (parentType != null) {
tryRecordInference(parentType, locationHint = locationHint)
} else {
tryNamespaceInference(parentName, locationHint = locationHint)
}
}

val record =
(holder ?: this.scopeManager.globalScope?.astNode)
?.startInference(this)
?.inferRecordDeclaration(type, kind, locationHint)

// update the type's record. Because types are only unique per scope, we potentially need to
// update multiple type nodes, i.e., all type nodes whose FQN match the inferred record
if (record != null) {
typeManager.firstOrderTypes
.filter { it.name == record.name }
.forEach { it.recordDeclaration = record }
}

return record
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import de.fraunhofer.aisec.cpg.graph.types.Type
import de.fraunhofer.aisec.cpg.graph.types.recordDeclaration
import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker
import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn
import de.fraunhofer.aisec.cpg.passes.inference.tryRecordInference

/**
* The purpose of this [Pass] is to establish a relationship between [Type] nodes (more specifically
Expand Down
Loading
Loading