From 42fd4f65d633902ee667a4dd4efc586b9b33dadc Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Tue, 1 Oct 2024 23:40:04 +0200 Subject: [PATCH] Even more clean up and migration of inference stuff --- .../aisec/cpg/passes/InferenceHelpers.kt | 47 +++++- .../aisec/cpg/passes/SymbolResolver.kt | 137 ++++-------------- 2 files changed, 71 insertions(+), 113 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/InferenceHelpers.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/InferenceHelpers.kt index 3d6773a68d..da961cb624 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/InferenceHelpers.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/InferenceHelpers.kt @@ -52,8 +52,10 @@ import de.fraunhofer.aisec.cpg.graph.unknownType import de.fraunhofer.aisec.cpg.passes.Pass.Companion.log import de.fraunhofer.aisec.cpg.passes.SymbolResolver import de.fraunhofer.aisec.cpg.passes.TypeResolver +import de.fraunhofer.aisec.cpg.passes.getPossibleContainingTypes 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 kotlin.collections.forEach @@ -214,8 +216,7 @@ internal fun TranslationContext.tryFieldInference( return declaration } - -fun TranslationContext.tryFunctionInference( +internal fun TranslationContext.tryFunctionInference( call: CallExpression, result: CallResolutionResult, ): List { @@ -247,12 +248,46 @@ fun TranslationContext.tryFunctionInference( } val func = when (val start = scope?.astNode) { - is TranslationUnitDeclaration -> start.inferFunction(call, ctx = ctx) - is NamespaceDeclaration -> start.inferFunction(call, ctx = ctx) + is TranslationUnitDeclaration -> start.inferFunction(call, ctx = this) + is NamespaceDeclaration -> start.inferFunction(call, ctx = this) else -> null } listOfNotNull(func) } else { - createMethodDummies(suitableBases, bestGuess, call) + tryMethodInference(call, suitableBases, bestGuess) + } +} + +/** + * Creates an inferred element for each RecordDeclaration + * + * @param call + * @param possibleContainingTypes + */ +internal fun TranslationContext.tryMethodInference( + call: CallExpression, + possibleContainingTypes: Set, + bestGuess: Type?, +): List { + var records = + possibleContainingTypes.mapNotNull { + val root = it.root as? ObjectType + root?.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( + tryRecordInference( + bestGuess?.root ?: call.unknownType(), + locationHint = call, + updateType = true + ) + ) } -} \ No newline at end of file + records = records.distinct() + + return records.mapNotNull { record -> record.inferMethod(call, ctx = this) } +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt index 6cdd1b42e7..a576b9f742 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt @@ -38,15 +38,13 @@ import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.ScopedWalker import de.fraunhofer.aisec.cpg.helpers.Util import de.fraunhofer.aisec.cpg.helpers.replace import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn -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.processing.strategy.Strategy import org.slf4j.Logger import org.slf4j.LoggerFactory import tryFieldInference +import tryFunctionInference import tryGlobalVariableInference -import tryRecordInference /** * Creates new connections between the place where a variable is declared and where it is used. @@ -445,7 +443,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { // callee var candidates = if (useLegacyResolution) { - val (possibleTypes, _) = getPossibleContainingTypes(call) + val (possibleTypes, _) = ctx.getPossibleContainingTypes(call) resolveMemberByName(callee.name.localName, possibleTypes).toSet() } else { callee.candidates @@ -478,7 +476,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { call.invokes = result.bestViable.toMutableList() } UNRESOLVED -> { - call.invokes = tryFunctionInference(call, result).toMutableList() + call.invokes = ctx.tryFunctionInference(call, result).toMutableList() } } @@ -592,40 +590,6 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { return candidates } - /** - * Creates an inferred element for each RecordDeclaration - * - * @param possibleContainingTypes - * @param call - */ - protected fun createMethodDummies( - possibleContainingTypes: Set, - bestGuess: Type?, - call: CallExpression - ): List { - var records = - possibleContainingTypes.mapNotNull { - val root = it.root as? ObjectType - root?.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, - updateType = true - ) - ) - } - records = records.distinct() - - return records.mapNotNull { record -> record.inferMethod(call, ctx = ctx) } - } - protected fun handleConstructExpression(constructExpression: ConstructExpression) { if (constructExpression.instantiates != null && constructExpression.constructor != null) return @@ -707,32 +671,6 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { return resolveWithArguments(candidates, op.operatorArguments, op as Expression) } - /** - * Returns a set of types in which the callee of our [call] could reside in. More concretely, it - * returns a [Pair], where the first element is the set of types and the second is our best - * guess. - */ - protected fun getPossibleContainingTypes(call: CallExpression): Pair, Type?> { - val possibleTypes = mutableSetOf() - var bestGuess: Type? = null - if (call is MemberCallExpression) { - call.base?.let { base -> - bestGuess = base.type - possibleTypes.add(base.type) - possibleTypes.addAll(base.assignedTypes) - } - } else { - // This could be a C++ member call with an implicit this (which we do not create), so - // let's add the current class to the possible list - scopeManager.currentRecord?.toType()?.let { - bestGuess = it - possibleTypes.add(it) - } - } - - return Pair(possibleTypes, bestGuess) - } - protected fun getInvocationCandidatesFromParents( name: Symbol, possibleTypes: Set, @@ -806,48 +744,6 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { ?.createInferredConstructor(constructExpression.signature) } - fun tryFunctionInference( - call: CallExpression, - result: CallResolutionResult, - ): List { - // We need to see, whether we have any suitable base (e.g. a class) or not; There are two - // main cases - // a) we have a member expression -> easy - // b) we have a call expression -> not so easy. This could be a member call with an implicit - // this (in which case we want to explore the base type). But that is only possible if - // the callee is not qualified, because otherwise we are in a static call like - // MyClass::doSomething() or in a namespace call (in case we do not want to explore the - // base type here yet). This will change in a future PR. - val (suitableBases, bestGuess) = - if (call.callee is MemberExpression || !call.callee.name.isQualified()) { - getPossibleContainingTypes(call) - } else { - Pair(setOf(), null) - } - - return if (suitableBases.isEmpty()) { - // Resolution has provided no result, we can forward this to the inference system, - // if we want. While this is definitely a function, it could still be a function - // inside a namespace. We therefore have two possible start points, a namespace - // declaration or a translation unit. Nothing else is allowed (fow now). We can - // re-use the information in the ResolutionResult, since this already contains the - // actual start scope (e.g. in case the callee has an FQN). - var scope = result.actualStartScope - if (scope !is NameScope) { - scope = scopeManager.globalScope - } - val func = - when (val start = scope?.astNode) { - is TranslationUnitDeclaration -> start.inferFunction(call, ctx = ctx) - is NamespaceDeclaration -> start.inferFunction(call, ctx = ctx) - else -> null - } - listOfNotNull(func) - } else { - createMethodDummies(suitableBases, bestGuess, call) - } - } - companion object { val LOGGER: Logger = LoggerFactory.getLogger(SymbolResolver::class.java) @@ -872,3 +768,30 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { } } } + +/** + * Returns a set of types in which the callee of our [call] could reside in. More concretely, it + * returns a [Pair], where the first element is the set of types and the second is our best guess. + */ +internal fun TranslationContext.getPossibleContainingTypes( + call: CallExpression +): Pair, Type?> { + val possibleTypes = mutableSetOf() + var bestGuess: Type? = null + if (call is MemberCallExpression) { + call.base?.let { base -> + bestGuess = base.type + possibleTypes.add(base.type) + possibleTypes.addAll(base.assignedTypes) + } + } else if (call.language is HasImplicitReceiver) { + // This could be a member call with an implicit receiver, so let's add the current class + // to the possible list + scopeManager.currentRecord?.toType()?.let { + bestGuess = it + possibleTypes.add(it) + } + } + + return Pair(possibleTypes, bestGuess) +}