diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt index 161e24788aa..0cd1ef78b09 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt @@ -596,8 +596,6 @@ class ScopeManager : ScopeProvider { * * @param ref * @return - * - * TODO: We should merge this function with [.resolveFunction] */ fun resolveReference(ref: Reference): ValueDeclaration? { val startScope = ref.scope @@ -614,7 +612,10 @@ class ScopeManager : ScopeProvider { return pair.second } - val (scope, name) = extractScope(ref, startScope) + var (scope, name) = extractScope(ref, startScope) + if (scope == null) { + scope = startScope + } // Try to resolve value declarations according to our criteria val decl = @@ -656,28 +657,6 @@ class ScopeManager : ScopeProvider { return decl } - /** - * Tries to resolve a function in a call expression. - * - * @param call the call expression - * @return a list of possible functions - */ - @JvmOverloads - fun resolveFunctionLegacy( - call: CallExpression, - startScope: Scope? = currentScope - ): List { - val (scope, name) = extractScope(call, startScope) - - val func = - resolve(scope) { - it.name.lastPartsMatch(name) && - it.matchesSignature(call.signature) != IncompatibleSignature - } - - return func - } - /** * This function tries to resolve a [CallExpression] into its matching [FunctionDeclaration] (or * multiple functions, if applicable). The result is returned in the form of a @@ -686,17 +665,25 @@ class ScopeManager : ScopeProvider { * * Note: The [CallExpression.callee] needs to be resolved first, otherwise the call resolution * fails. + * + * In order to migrate from our legacy resolution system, we already use this function for all + * resolving, but [legacyCandidates] can be used to specify the call's candidate functions based + * on the legacy system rather than the already resolved [CallExpression.callee]. */ - fun resolveCall(call: CallExpression, startScope: Scope? = currentScope): CallResolutionResult { + fun resolveCall( + call: CallExpression, + legacyCandidates: Set? = null + ): CallResolutionResult { val result = CallResolutionResult( call, + call.arguments, setOf(), setOf(), mapOf(), setOf(), CallResolutionResult.SuccessKind.UNRESOLVED, - startScope, + call.scope, ) val language = call.language @@ -709,17 +696,24 @@ class ScopeManager : ScopeProvider { // function val callee = call.callee as? Reference ?: return result - val (scope, _) = extractScope(callee, startScope) - result.actualStartScope = scope + // Set the start scope. This can either be the call's scope or a scope specified in an FQN + val (scope, _) = extractScope(callee, call.scope) + result.actualStartScope = scope ?: call.scope - // Retrieve a list of possible functions with a matching name - result.candidateFunctions = - callee.candidates.filterIsInstance().toSet() + // If we have pre-resolved candidates from our legacy system, we need to take these + if (legacyCandidates != null) { + result.candidateFunctions = legacyCandidates + } + // Otherwise, retrieve a list of possible functions with a matching name from our + // callee, which is already resolved. + else { + result.candidateFunctions = + callee.candidates.filterIsInstance().toSet() + } if (call.language !is HasFunctionOverloading) { // If the function does not allow function overloading, and we have multiple candidate - // symbols, the - // result is "problematic" + // symbols, the result is "problematic" if (result.candidateFunctions.size > 1) { result.success = CallResolutionResult.SuccessKind.PROBLEMATIC } @@ -735,8 +729,8 @@ class ScopeManager : ScopeProvider { it, it.matchesSignature( call.signature, + call.arguments, call.language is HasDefaultArguments, - call ) ) } @@ -761,7 +755,8 @@ class ScopeManager : ScopeProvider { } /** - * This function extracts a scope for the [Name] in node, e.g. if the name is fully qualified. + * This function extracts a scope for the [Name], e.g. if the name is fully qualified. `null` is + * returned, if no scope can be extracted. * * The pair returns the extracted scope and a name that is adjusted by possible import aliases. * The extracted scope is "responsible" for the name (e.g. declares the parent namespace) and @@ -776,7 +771,7 @@ class ScopeManager : ScopeProvider { * @param scope the current scope relevant for the name resolution, e.g. parent of node * @return a pair with the scope of node.name and the alias-adjusted name */ - fun extractScope(node: Node, scope: Scope? = currentScope): Pair { + fun extractScope(node: HasNameAndLocation, scope: Scope? = currentScope): Pair { return extractScope(node.name, node.location, scope) } @@ -803,7 +798,7 @@ class ScopeManager : ScopeProvider { scope: Scope? = currentScope, ): Pair { var n = name - var s = scope + var s: Scope? = null // First, we need to check, whether we have some kind of scoping. if (n.isQualified()) { @@ -910,12 +905,6 @@ class ScopeManager : ScopeProvider { return ret } - fun resolveFunctionStopScopeTraversalOnDefinition( - call: CallExpression - ): List { - return resolve(currentScope, true) { f -> f.name.lastPartsMatch(call.name) } - } - /** * Traverses the scope upwards and looks for declarations of type [T] which matches the * condition [predicate]. @@ -928,7 +917,7 @@ class ScopeManager : ScopeProvider { * @param predicate predicate the element must match to * @param */ - inline fun resolve( + internal inline fun resolve( searchScope: Scope?, stopIfFound: Boolean = false, noinline predicate: (T) -> Boolean @@ -936,7 +925,7 @@ class ScopeManager : ScopeProvider { return resolve(T::class.java, searchScope, stopIfFound, predicate) } - fun resolve( + internal fun resolve( klass: Class, searchScope: Scope?, stopIfFound: Boolean = false, @@ -1084,9 +1073,21 @@ class ScopeManager : ScopeProvider { predicate: ((Declaration) -> Boolean)? = null, ): List { val (scope, n) = extractScope(name, location, startScope) + + // We need to differentiate between a qualified and unqualified lookup. We have a qualified + // lookup, if the scope is not null. In this case we need to stay within the specified scope val list = - scope?.lookupSymbol(n.localName, predicate = predicate)?.toMutableList() - ?: mutableListOf() + // TODO(oxisto): extractScope does not return null in all cases, so we need to make sure + // that the returned scope is NOT our startscope + if (scope != null && scope is NameScope && scope != startScope) { + scope.lookupSymbol(n.localName, thisScopeOnly = true, predicate = predicate) + } + // Otherwise, we can look up the symbol alone (without any FQN) starting from the + // startScope + else { + startScope?.lookupSymbol(n.localName, predicate = predicate) + } + ?.toMutableList() ?: return listOf() // If we have both the definition and the declaration of a function declaration in our list, // we chose only the definition @@ -1132,8 +1133,8 @@ data class SignatureMatches(override val casts: List) : SignatureRes fun FunctionDeclaration.matchesSignature( signature: List, + arguments: List? = null, useDefaultArguments: Boolean = false, - call: CallExpression? = null, ): SignatureResult { val casts = mutableListOf() @@ -1155,7 +1156,7 @@ fun FunctionDeclaration.matchesSignature( // Check, if we can cast the arg into our target type; and if, yes, what is // the "distance" to the base type. We need this to narrow down the type during // resolving - val match = type.tryCast(param.type, call?.arguments?.getOrNull(i), param) + val match = type.tryCast(param.type, arguments?.getOrNull(i), param) if (match == CastNotPossible) { return IncompatibleSignature } @@ -1203,8 +1204,11 @@ fun FunctionDeclaration.matchesSignature( * of the call resolution. */ data class CallResolutionResult( - /** The original call expression. */ - val call: CallExpression, + /** The original expression that triggered the resolution. Most likely a [CallExpression]. */ + val call: Expression, + + /** The arguments that were supplied to the expression. */ + val arguments: List, /** * A set of candidate symbols we discovered based on the [CallExpression.callee] (using diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt index b09a518bff3..882559b2df7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt @@ -35,6 +35,7 @@ import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.graph.unknownType import java.io.File @@ -318,8 +319,9 @@ abstract class Language> : Node() { // We need to check, whether this language has special handling of templates. In this // case, we need to check, whether a template matches directly after we have no direct // matches - if (this is HasTemplates) { - result.call.templateParameterEdges = mutableListOf() + val call = result.call + if (this is HasTemplates && call is CallExpression) { + call.templateParameterEdges = mutableListOf() val (ok, candidates) = this.handleTemplateFunctionCalls( null, @@ -333,7 +335,7 @@ abstract class Language> : Node() { return Pair(candidates.toSet(), CallResolutionResult.SuccessKind.SUCCESSFUL) } - result.call.templateParameterEdges = null + call.templateParameterEdges = null } // If the list of viable functions is still empty at this point, the call is unresolved diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt index 0d8f8d02088..1e6f0e332a3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt @@ -34,7 +34,6 @@ import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.scopes.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* -import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.passes.* import kotlin.reflect.KClass @@ -85,29 +84,6 @@ interface HasTemplates : HasGenerics { */ interface HasDefaultArguments : LanguageTrait -/** - * A language trait that specifies that this language has a complex call resolution that we need to - * fine-tune in the language implementation. - */ -interface HasComplexCallResolution : LanguageTrait { - /** - * A function that can be used to fine-tune resolution of a method [call]. - * - * Note: The function itself should NOT set the [CallExpression.invokes] but rather return a - * list of possible candidates. - * - * @return a list of [FunctionDeclaration] candidates. - */ - fun refineMethodCallResolution( - curClass: RecordDeclaration?, - possibleContainingTypes: Set, - call: CallExpression, - ctx: TranslationContext, - currentTU: TranslationUnitDeclaration, - callResolver: SymbolResolver - ): List -} - /** A language trait that specifies if the language supports function pointers. */ interface HasFunctionPointers : LanguageTrait @@ -134,12 +110,12 @@ interface HasClasses : LanguageTrait interface HasSuperClasses : LanguageTrait { /** * Determines which keyword is used to access functions, etc. of the superclass of an object - * (often "super). + * (often `super`). */ val superClassKeyword: String - fun handleSuperCall( - callee: MemberExpression, + fun handleSuperExpression( + me: MemberExpression, curClass: RecordDeclaration, scopeManager: ScopeManager, ): Boolean diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/Scope.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/Scope.kt index 9507bbabb6c..6194f544951 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/Scope.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/Scope.kt @@ -105,9 +105,21 @@ abstract class Scope( /** * Looks up a list of [Declaration] nodes for the specified [symbol]. Optionally, [predicate] * can be used for additional filtering. + * + * By default, the lookup algorithm will go to the [Scope.parent] if no match was found in the + * current scope. This behaviour can be turned off with [thisScopeOnly]. This is useful for + * qualified lookups, where we want to stay in our lookup-scope. + * + * @param symbol the symbol to lookup + * @param thisScopeOnly whether we should stay in the current scope for lookup or traverse to + * its parents if no match was found. + * @param replaceImports whether any symbols pointing to [ImportDeclaration.importedSymbols] or + * wildcards should be replaced with their actual nodes + * @param predicate An optional predicate which should be used in the lookup. */ fun lookupSymbol( symbol: Symbol, + thisScopeOnly: Boolean = false, replaceImports: Boolean = true, predicate: ((Declaration) -> Boolean)? = null ): List { @@ -141,8 +153,12 @@ abstract class Scope( break } - // If we do not have a hit, we can go up one scope - scope = scope.parent + // If we do not have a hit, we can go up one scope, unless thisScopeOnly is set to true + if (!thisScopeOnly) { + scope = scope.parent + } else { + break + } } return list ?: listOf() 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 1edce6cb034..a8f1fc5b415 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 @@ -32,7 +32,7 @@ import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.scopes.NameScope import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope -import de.fraunhofer.aisec.cpg.graph.scopes.StructureDeclarationScope +import de.fraunhofer.aisec.cpg.graph.scopes.Symbol import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.ScopedWalker @@ -142,7 +142,6 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { if (target == null) { // Determine the scope where we want to start our inference var (scope, _) = scopeManager.extractScope(reference) - if (scope !is NameScope) { scope = null } @@ -292,9 +291,25 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { } } - protected fun handleMemberExpression(curClass: RecordDeclaration?, current: Node?) { - if (current !is MemberExpression) { - return + protected fun handleMemberExpression(curClass: RecordDeclaration?, current: MemberExpression) { + // Some locals for easier smart casting + var base = current.base + var language = current.language + + // We need to adjust certain types of the base in case of a "super" expression, and we + // delegate this to the language. If that is successful, we can continue with regular + // resolving. + if ( + language is HasSuperClasses && + curClass != null && + base is Reference && + base.name.endsWith(language.superClassKeyword) + ) { + language.handleSuperExpression( + current, + curClass, + scopeManager, + ) } // For legacy reasons, method and field resolving is split between the VariableUsageResolver @@ -307,57 +322,15 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { return } - var baseTarget: Declaration? = null - if (current.base is Reference) { - val base = current.base as Reference - if ( - current.language is HasSuperClasses && - base.name.toString() == (current.language as HasSuperClasses).superClassKeyword - ) { - if (curClass != null && curClass.superClasses.isNotEmpty()) { - val superType = curClass.superClasses[0] - val superRecord = superType.recordDeclaration - if (superRecord == null) { - log.error( - "Could not find referring super type ${superType.typeName} for ${curClass.name} in the record map. Will set the super type to java.lang.Object" - ) - // TODO: Should be more generic! - base.type = current.objectType(Any::class.java.name) - } else { - // We need to connect this super reference to the receiver of this - // method - val func = scopeManager.currentFunction - if (func is MethodDeclaration) { - baseTarget = func.receiver - } - if (baseTarget != null) { - base.refersTo = baseTarget - // Explicitly set the type of the call's base to the super type - base.type = superType - (base.refersTo as? HasType)?.type = superType - // And set the assigned subtypes, to ensure, that really only our - // super type is in there - base.assignedTypes = mutableSetOf(superType) - (base.refersTo as? ValueDeclaration)?.assignedTypes = - mutableSetOf(superType) - } - } - } else { - // no explicit super type -> java.lang.Object - // TODO: Should be more generic - val objectType = current.objectType(Any::class.java.name) - base.type = objectType - } - } else { - // The base should have been resolved by now. Maybe we have some other clue about - // this base from the type system, so we can set the declaration accordingly. - if (base.refersTo == null) { - base.refersTo = base.type.recordDeclaration - } + if (base is Reference) { + // The base should have been resolved by now. Maybe we have some other clue about + // this base from the type system, so we can set the declaration accordingly. + if (base.refersTo == null) { + base.refersTo = base.type.recordDeclaration } } - val baseType = current.base.type.root + val baseType = base.type.root current.refersTo = resolveMember(baseType, current) } @@ -472,48 +445,79 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { is MemberExpression -> handleMemberExpression(currClass, node) is Reference -> handleReference(currClass, node) is ConstructExpression -> handleConstructExpression(node) - is CallExpression -> handleCallExpression(scopeManager.currentRecord, node) + is CallExpression -> handleCallExpression(node) } } - protected fun handleCallExpression(curClass: RecordDeclaration?, call: CallExpression) { + protected fun handleCallExpression(call: CallExpression) { + // Some local variables for easier smart casting + val callee = call.callee + val language = call.language + // Dynamic function invokes (such as function pointers) are handled by extra pass, so we are - // not resolving them here. In this case, our callee refers to a variable rather than a - // function. + // not resolving them here. + // + // We have a dynamic invoke in two cases: + // a) our calleee is not a reference + // b) our reference refers to a variable rather than a function if ( - (call.callee as? Reference)?.refersTo is VariableDeclaration || - (call.callee as? Reference)?.refersTo is ParameterDeclaration + callee !is Reference || + callee.refersTo is VariableDeclaration || + callee.refersTo is ParameterDeclaration ) { return } + // If this is a template call and our language supports templates, we need to directly + // handle this with the template system. This will also take care of inference and + // everything. This will stay in this way until we completely redesign the template system. + if (call.instantiatesTemplate() && language is HasTemplates) { + val (ok, candidates) = + language.handleTemplateFunctionCalls( + scopeManager.currentRecord, + call, + true, + ctx, + currentTU, + false + ) + if (ok) { + call.invokes = candidates + return + } + } + // We are moving towards a new approach to call resolution. However, we cannot use this for // all nodes yet, so we need to use legacy resolution for some + val isSpecialCXXCase = + call.language.isCPP && scopeManager.currentRecord != null && !callee.name.isQualified() val useLegacyResolution = when { - call.language.isCPP && curClass != null -> true + isSpecialCXXCase -> true call is MemberCallExpression -> true - call is ConstructExpression -> true - call.instantiatesTemplate() && call.language is HasTemplates -> true - call.callee !is Reference -> true else -> { false } } + var legacyCandidates = + if (useLegacyResolution) { + resolveCalleeByName(callee.name.localName, call) + .filterIsInstance() + .toSet() + } else { + null + } - if (!useLegacyResolution) { - handleCallExpression(call, curClass) - } else { - handleCallExpressionLegacy(call, curClass) + // There seems to be one more special case and that is a regular function within a record. + // This could either be a member call with an omitted "this" or a regular call. The problem + // is that the legacy system can now only resolve member calls but not regular calls + // (anymore). So if we have this special case and the legacy system does not return any + // candidates, we need to switch to the new system. + if (isSpecialCXXCase && legacyCandidates?.isEmpty() == true) { + legacyCandidates = null } - } - - /** - * This function tries to resolve the call expression according to the [CallExpression.callee]. - */ - private fun handleCallExpression(call: CallExpression, curClass: RecordDeclaration?) { - val result = scopeManager.resolveCall(call) + val result = scopeManager.resolveCall(call, legacyCandidates = legacyCandidates) when (result.success) { PROBLEMATIC -> { log.error( @@ -531,224 +535,38 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { call.invokes = result.bestViable.toList() } UNRESOLVED -> { - // 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 - } - call.invokes = listOfNotNull(func) + call.invokes = tryFunctionInference(call, result) } } // We also set the callee's refersTo - (call.callee as? Reference)?.refersTo = call.invokes.firstOrNull() - } - - private fun handleCallExpressionLegacy( - call: CallExpression, - curClass: RecordDeclaration?, - ) { - // At this point, we decide what to do based on the callee property - val callee = call.callee - - // With one exception. If the language supports templates and if this is a template call, we - // delegate it to the language. In the future, we definitely want to do this in a smarter - // way - var candidates = - if (call.instantiatesTemplate() && call.language is HasTemplates) { - val (_, candidates) = - (call.language as HasTemplates).handleTemplateFunctionCalls( - curClass, - call, - true, - ctx, - currentTU, - false - ) - - candidates - } else { - resolveCalleeLegacy(callee, curClass, call) ?: return - } - - // If we do not have any candidates at this point, we will infer one. - if (candidates.isEmpty()) { - // We need to see, whether we have any suitable base (e.g. a class) or not; but only if - // the call itself is not already scoped (e.g. to a namespace) - val (suitableBases, bestGuess) = - if (callee is MemberExpression || callee?.name?.isQualified() == false) { - getPossibleContainingTypes(call) - } else { - Pair(setOf(), null) - } - - candidates = - if (suitableBases.isEmpty()) { - // This is not really the most ideal place, but for now this will do. While this - // is definitely a function, it could still be a function inside a namespace. In - // this case, we want to start inference in that particular namespace and not in - // the TU. It is also a little bit redundant, since ScopeManager.resolveFunction - // (which gets called before) already extracts the scope, but this information - // gets lost. - val (scope, _) = scopeManager.extractScope(call, scopeManager.globalScope) - - // We have two possible start points, a namespace declaration or a translation - // unit. Nothing else is allowed (for now) - 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) - } - } - - // Set the INVOKES edge to our candidates - call.invokes = candidates - - // Additionally, also set the REFERS_TO of the callee. In the future, we might make more - // resolution decisions based on the callee itself. Unfortunately we can only set one here, - // so we will take the first one - if (callee is Reference) { - callee.refersTo = candidates.firstOrNull() - } + callee.refersTo = call.invokes.firstOrNull() } - /** - * Resolves [call] to a list of [FunctionDeclaration] nodes, based on the - * [CallExpression.callee] property. - * - * In case a resolution is not possible, `null` can be returned. - */ - protected fun resolveCalleeLegacy( - callee: Expression?, - curClass: RecordDeclaration?, - call: CallExpression - ): List? { - return when (callee) { - is MemberExpression -> resolveMemberCallee(callee, curClass, call) - is Reference -> resolveReferenceCallee(callee, curClass, call) - null -> { - Util.warnWithFileLocation( - call, - log, - "Call expression without callee, maybe because of a parsing error" - ) - null - } - else -> { - Util.errorWithFileLocation( - call, - log, - "Could not resolve callee of unsupported type ${callee.javaClass}" - ) - null - } - } - } - - /** - * Resolves a [CallExpression.callee] of type [Reference] to a possible list of - * [FunctionDeclaration] nodes. - */ - protected fun resolveReferenceCallee( - callee: Reference, - curClass: RecordDeclaration?, - call: CallExpression - ): List { - return if (curClass == null || callee.name.isQualified()) { - // We can already forward this to the nextGen resolver. Not quite sure why we ended up - // here in the first place - val result = ctx.scopeManager.resolveCall(call) - result.bestViable.toList() - } else { - resolveCalleeByName(callee.name.localName, curClass, call) - } - } + protected fun resolveCalleeByName(localName: String, call: CallExpression): Set { + val (possibleContainingTypes, _) = getPossibleContainingTypes(call) - /** - * Resolves a [CallExpression.callee] of type [MemberExpression] to a possible list of - * [FunctionDeclaration] nodes. - */ - fun resolveMemberCallee( - callee: MemberExpression, - curClass: RecordDeclaration?, - call: CallExpression - ): List { - // We need to adjust certain types of the base in case of a super call and we delegate this. - // If that is successful, we can continue with regular resolving. - if ( - curClass != null && - callee.base is Reference && - isSuperclassReference(callee.base as Reference) - ) { - (callee.language as? HasSuperClasses)?.handleSuperCall( - callee, - curClass, - scopeManager, + var invocationCandidates = mutableSetOf() + val records = possibleContainingTypes.mapNotNull { it.root.recordDeclaration }.toSet() + for (record in records) { + invocationCandidates.addAll( + ctx.scopeManager.findSymbols(record.name.fqn(call.name.localName)) ) } - return resolveCalleeByName(callee.name.localName, curClass, call) - } - - protected fun resolveCalleeByName( - localName: String, - curClass: RecordDeclaration?, - call: CallExpression - ): List { - - val (possibleContainingTypes, _) = getPossibleContainingTypes(call) - - // Find function targets. If our languages has a complex call resolution, we need to take - // this into account - var invocationCandidates = - if (call.language is HasComplexCallResolution) { - (call.language as HasComplexCallResolution) - .refineMethodCallResolution( - curClass, - possibleContainingTypes, - call, - ctx, - currentTU, - this - ) - .toMutableList() - } else { - scopeManager.resolveFunctionLegacy(call).toMutableList() - } // Find invokes by supertypes - if ( - invocationCandidates.isEmpty() && - localName.isNotEmpty() && - (!call.language.isCPP || shouldSearchForInvokesInParent(call)) - ) { + if (invocationCandidates.isEmpty() && localName.isNotEmpty()) { val records = possibleContainingTypes.mapNotNull { it.root.recordDeclaration }.toSet() invocationCandidates = - getInvocationCandidatesFromParents(localName, call, records).toMutableList() + getInvocationCandidatesFromParents(localName, call, records).toMutableSet() } // Add overridden invokes invocationCandidates.addAll( invocationCandidates + .filterIsInstance() .map { getOverridingCandidates(possibleContainingTypes, it) } .flatten() - .toMutableList() ) return invocationCandidates @@ -784,17 +602,6 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { return records.mapNotNull { record -> record.inferMethod(call, ctx = ctx) } } - /** - * In C++ search we don't search in the parent if there is a potential candidate with matching - * name - * - * @param call - * @return true if we should stop searching parent, false otherwise - */ - protected fun shouldSearchForInvokesInParent(call: CallExpression): Boolean { - return scopeManager.resolveFunctionStopScopeTraversalOnDefinition(call).isEmpty() - } - protected fun handleConstructExpression(constructExpression: ConstructExpression) { if (constructExpression.instantiates != null && constructExpression.constructor != null) return @@ -864,77 +671,23 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { return Pair(possibleTypes, bestGuess) } - fun getInvocationCandidatesFromRecord( - recordDeclaration: RecordDeclaration?, - name: String, - call: CallExpression - ): List { - if (recordDeclaration == null) { - return listOf() - } - - // We should not directly access the "methods" property of the record declaration, - // because depending on the programming language, this only may hold methods that are - // declared directly within the original type declaration, but not ones that are - // declared "outside" (e.g, like it is possible in Go and C++). Instead, we should - // retrieve the scope of the record and look for appropriate declarations. - val scope = scopeManager.lookupScope(recordDeclaration) as? StructureDeclarationScope - - val candidateFunctions = - scope - ?.lookupSymbol(name) - ?.filterIsInstance() - ?.toSet() ?: setOf() - - // The following code is unfortunately largely a copy/paste from the new resolveCall - // function; but resolveCall is not yet completely ready to resolve methods, therefore, we - // need to have this duplicate code here, to at least use the new features here. - val signatureResults = - candidateFunctions - .map { - Pair( - it, - it.matchesSignature( - call.signature, - call.language is HasDefaultArguments, - call - ) - ) - } - .filter { it.second is SignatureMatches } - .associate { it } - val viableFunctions = signatureResults.keys - val result = - CallResolutionResult( - call, - candidateFunctions, - viableFunctions, - signatureResults, - setOf(), - UNRESOLVED, - call.scope - ) - val pair = call.language?.bestViableResolution(result) - - return pair?.first?.toList() ?: listOf() - } - protected fun getInvocationCandidatesFromParents( - name: String, + name: Symbol, call: CallExpression, possibleTypes: Set - ): List { + ): List { val workingPossibleTypes = mutableSetOf(*possibleTypes.toTypedArray()) return if (possibleTypes.isEmpty()) { listOf() } else { val firstLevelCandidates = - possibleTypes.map { getInvocationCandidatesFromRecord(it, name, call) }.flatten() + possibleTypes.map { scopeManager.findSymbols(it.name.fqn(name)) }.flatten() // C++ does not allow overloading at different hierarchy levels. If we find a // FunctionDeclaration with the same name as the function in the CallExpression we have // to stop the search in the parent even if the FunctionDeclaration does not match with // the signature of the CallExpression + // TODO: move this to refineMethodResolution of CXXLanguage if (call.language.isCPP) { // TODO: Needs a special trait? workingPossibleTypes.removeIf { recordDeclaration -> !shouldContinueSearchInParent(recordDeclaration, name) @@ -981,8 +734,8 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { recordDeclaration.constructors.firstOrNull { it.matchesSignature( signature, + constructExpression.arguments, constructExpression.language is HasDefaultArguments, - constructExpression ) != IncompatibleSignature } @@ -992,6 +745,42 @@ 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; but only if + // the call itself is not already scoped (e.g. to a namespace) + 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) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt index 47eef6eb9d5..a73658f4217 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt @@ -100,7 +100,7 @@ internal class ScopeManagerTest : BaseTest() { // resolve symbol val call = frontend.newCallExpression(frontend.newReference("A::func1"), "A::func1", false) - val func = final.resolveFunctionLegacy(call).firstOrNull() + val func = final.findSymbols(call.callee!!.name).firstOrNull() assertEquals(func1, func) } diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt index 5a538470cd9..9ee1f64b389 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt @@ -48,7 +48,6 @@ open class CPPLanguage : CLanguage(), HasDefaultArguments, HasTemplates, - HasComplexCallResolution, HasStructs, HasClasses, HasUnknownType, @@ -161,43 +160,6 @@ open class CPPLanguage : "__int128" to IntegerType("__int128", 128, this, NumericType.Modifier.SIGNED), ) - /** - * @param call - * @return FunctionDeclarations that are invocation candidates for the MethodCall call using C++ - * resolution techniques - */ - override fun refineMethodCallResolution( - curClass: RecordDeclaration?, - possibleContainingTypes: Set, - call: CallExpression, - ctx: TranslationContext, - currentTU: TranslationUnitDeclaration, - callResolver: SymbolResolver - ): List { - var invocationCandidates = mutableListOf() - val records = possibleContainingTypes.mapNotNull { it.root.recordDeclaration }.toSet() - for (record in records) { - invocationCandidates.addAll( - callResolver.getInvocationCandidatesFromRecord(record, call.name.localName, call) - ) - } - if (invocationCandidates.isEmpty()) { - // This could be a regular function call that somehow ends up here because of weird - // complexity of the old call resolver - val result = ctx.scopeManager.resolveCall(call) - invocationCandidates.addAll(result.bestViable) - } - - // Make sure, that our invocation candidates for member call expressions are really METHODS, - // otherwise this will lead to false positives. This is a hotfix until we rework the call - // resolver completely. - if (call is MemberCallExpression) { - invocationCandidates = - invocationCandidates.filterIsInstance().toMutableList() - } - return invocationCandidates - } - override fun tryCast( type: Type, targetType: Type, diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXAmbiguitiesTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXAmbiguitiesTest.kt index 76f6187a5e9..aadb414edc8 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXAmbiguitiesTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXAmbiguitiesTest.kt @@ -57,8 +57,10 @@ class CXXAmbiguitiesTest { } assertNotNull(tu) - // make sure we still have only one declaration in the file (the record) - assertEquals(1, tu.declarations.size) + // we have 3 (record) declarations in our TU now. 1 of the original MyClass and two because + // CDT thinks that "call" is the return type and "crazy" the type of the parameter. We infer + // record declarations for all types, so we end up with 3 declarations here. + assertEquals(3, tu.declarations.size) val myClass = tu.records["MyClass"] assertNotNull(myClass) diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt index 520b59f9e85..abd331f2d9e 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt @@ -717,6 +717,7 @@ class CallResolverTest : BaseTest() { true ) { it.registerLanguage() + it.inferenceConfiguration(InferenceConfiguration.builder().enabled(false).build()) } val calls = result.calls @@ -727,8 +728,7 @@ class CallResolverTest : BaseTest() { } false } - assertEquals(1, calcCall.invokes.size) - assertFalse(calcCall.invokes[0].isInferred) + assertEquals(0, calcCall.invokes.size) } @Test diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt index 1683c1df8f4..1cebeb5eba0 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt @@ -407,10 +407,6 @@ class GoExtraPass(ctx: TranslationContext) : ComponentPass(ctx) { it is NamespaceDeclaration && it.path == import.importURL } - scopeManager.resolve(scopeManager.globalScope, true) { - it.name == import.name && it.path == import.importURL - } - // If not, we can infer a namespace declaration, so we can bundle all inferred function // declarations in there if (namespace.isEmpty()) { diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt index db622510d6f..43d32ca7667 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt @@ -45,7 +45,8 @@ open class JavaLanguage : HasGenerics, HasQualifier, HasUnknownType, - HasShortCircuitOperators { + HasShortCircuitOperators, + HasFunctionOverloading { override val fileExtensions = listOf("java") override val namespaceDelimiter = "." @Transient override val frontend: KClass = JavaLanguageFrontend::class @@ -108,11 +109,11 @@ open class JavaLanguage : } else super.propagateTypeOfBinaryOperation(operation) } - override fun handleSuperCall( + override fun handleSuperExpression( callee: MemberExpression, curClass: RecordDeclaration, scopeManager: ScopeManager, - ) = JavaCallResolverHelper.handleSuperCall(callee, curClass, scopeManager) + ) = JavaCallResolverHelper.handleSuperExpression(callee, curClass, scopeManager) override val startCharacter = '<' override val endCharacter = '>' diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaCallResolverHelper.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaCallResolverHelper.kt index 25d313b0487..388e86c1b71 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaCallResolverHelper.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaCallResolverHelper.kt @@ -29,7 +29,6 @@ import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguage import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration -import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.types.HasType @@ -42,18 +41,18 @@ class JavaCallResolverHelper { companion object { /** - * Handle calls in the form of `super.call()` or `ClassName.super.call()`, conforming to - * JLS13 §15.12.1. + * Handle expressions in the form of `super.property` or `ClassName.super.call()`, + * conforming to JLS13 §15.12.1. * * This function basically sets the correct type of the [Reference] containing the "super" - * keyword. Afterwards, we can use the regular [SymbolResolver.resolveMemberCallee] to - * resolve the [MemberCallExpression]. + * keyword. Afterward, we can use the regular [SymbolResolver.resolveCalleeByName] to + * resolve the [MemberExpression]. * - * @param callee The callee of the call expression that needs to be adjusted + * @param me The member expression that needs to be adjusted * @param curClass The class containing the call */ - fun handleSuperCall( - callee: MemberExpression, + fun handleSuperExpression( + me: MemberExpression, curClass: RecordDeclaration, scopeManager: ScopeManager ): Boolean { @@ -61,7 +60,7 @@ class JavaCallResolverHelper { // still need to connect the super reference to the receiver of this method. val func = scopeManager.currentFunction if (func is MethodDeclaration) { - (callee.base as Reference?)?.refersTo = func.receiver + (me.base as Reference?)?.refersTo = func.receiver } // In the next step we can "cast" the base to the correct type, by setting the base @@ -69,37 +68,36 @@ class JavaCallResolverHelper { // In case the reference is just called "super", this is a direct superclass, either // defined explicitly or java.lang.Object by default - if (callee.base.name.toString() == JavaLanguage().superClassKeyword) { + if (me.base.name.toString() == JavaLanguage().superClassKeyword) { if (curClass.superClasses.isNotEmpty()) { target = curClass.superClasses[0].root.recordDeclaration } else { Util.warnWithFileLocation( - callee, + me, LOGGER, "super call without direct superclass! Expected java.lang.Object to be present at least!" ) } } else { // BaseName.super.call(), might either be in order to specify an enclosing class or - // an - // interface that is implemented - target = handleSpecificSupertype(callee, curClass) + // an interface that is implemented + target = handleSpecificSupertype(me, curClass) } if (target != null) { val superType = target.toType() // Explicitly set the type of the call's base to the super type, basically "casting" // the "this" object to the super class - callee.base.type = superType + me.base.type = superType - val refersTo = (callee.base as? Reference)?.refersTo + val refersTo = (me.base as? Reference)?.refersTo if (refersTo is HasType) { refersTo.type = superType refersTo.assignedTypes = mutableSetOf(superType) } // Make sure that really only our super class is in the list of assigned types - callee.base.assignedTypes = mutableSetOf(superType) + me.base.assignedTypes = mutableSetOf(superType) return true } diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/DeclarationHandler.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/DeclarationHandler.kt index 948340673c7..a74b96976ec 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/DeclarationHandler.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/DeclarationHandler.kt @@ -194,12 +194,7 @@ class DeclarationHandler(lang: LLVMIRLanguageFrontend) : } // try to see, if the struct already exists as a record declaration - var record = - frontend.scopeManager - .resolve(frontend.scopeManager.globalScope, true) { - it.name.toString() == name - } - .firstOrNull() + var record = frontend.scopeManager.getRecordForName(Name(name)) // if yes, return it if (record != null) { diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt index 194c5cda295..6376fc98ab9 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt @@ -481,12 +481,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : var record = (baseType as? ObjectType)?.recordDeclaration if (record == null) { - record = - frontend.scopeManager - .resolve(frontend.scopeManager.globalScope, true) { - it.name == baseType.name - } - .firstOrNull() + record = frontend.scopeManager.getRecordForName(baseType.name) if (record != null) { (baseType as? ObjectType)?.recordDeclaration = record } diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt index 4ac3b40a64b..35e3163ac2b 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt @@ -31,7 +31,6 @@ import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.frontends.TranslationException import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.Declaration -import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference @@ -246,11 +245,7 @@ class LLVMIRLanguageFrontend(language: Language, ctx: Tr /** Determines if a struct with [name] exists in the scope. */ fun isKnownStructTypeName(name: String): Boolean { - return this.scopeManager - .resolve(this.scopeManager.globalScope, true) { - it.name.toString() == name - } - .isNotEmpty() + return this.scopeManager.getRecordForName(Name(name)) != null } fun getOperandValueAtIndex(instr: LLVMValueRef, idx: Int): Expression { diff --git a/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/RubyLanguage.kt b/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/RubyLanguage.kt index 93969276d99..6c401300b6a 100644 --- a/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/RubyLanguage.kt +++ b/cpg-language-ruby/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ruby/RubyLanguage.kt @@ -75,8 +75,8 @@ class RubyLanguage() : "^=" // Bitwise XOR assignment ) - override fun handleSuperCall( - callee: MemberExpression, + override fun handleSuperExpression( + me: MemberExpression, curClass: RecordDeclaration, scopeManager: ScopeManager ): Boolean {