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 da961cb624..e8aa4b8f22 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 @@ -29,17 +29,11 @@ import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.HasGlobalVariables import de.fraunhofer.aisec.cpg.frontends.HasImplicitReceiver import de.fraunhofer.aisec.cpg.frontends.HasStructs +import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.declarations.Declaration -import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.NamespaceDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.newFieldDeclaration -import de.fraunhofer.aisec.cpg.graph.scopes.GlobalScope import de.fraunhofer.aisec.cpg.graph.scopes.NameScope import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression @@ -129,39 +123,52 @@ internal fun TranslationContext.tryRecordInference( } /** - * Tries to infer a global variable from an unresolved [Reference]. This will return `null`, if + * Tries to infer a [VariableDeclaration] out of a [Reference]. This will return `null`, if * inference was not possible, or if it was turned off in the [InferenceConfiguration]. * - * Currently, this can only infer variables in the [GlobalScope], but not in a namespace. + * We mainly try to infer global variables and fields here, since these are possibly parts of the + * code we do not "see". We do not try to infer local variables, because we are under the assumption + * that even with incomplete code, we at least have the complete current function code. */ -internal fun TranslationContext.tryGlobalVariableInference(ref: Reference): Declaration? { - // For now, we only infer globals at the top-most global level, i.e., no globals in - // namespaces - if (ref.name.isQualified()) { +internal fun TranslationContext.tryVariableInference( + language: Language<*>?, + ref: Reference, +): Declaration? { + var currentRecordType = scopeManager.currentRecord?.toType() as? ObjectType + return if ( + language is HasImplicitReceiver && + !ref.name.isQualified() && + !ref.isStaticAccess && + currentRecordType != null + ) { + // This could potentially be a reference to a field with an implicit receiver call + tryFieldInference(ref, currentRecordType) + } else if (ref.name.isQualified()) { + // For now, we only infer globals at the top-most global level, i.e., no globals in + // namespaces val (scope, _) = scopeManager.extractScope(ref, null) when (scope) { is NameScope -> { log.warn( "We should infer a namespace variable ${ref.name} at this point, but this is not yet implemented." ) - return null + null } else -> { log.warn( "We should infer a variable ${ref.name} in ${scope}, but this is not yet implemented." ) - return null + null } } + } else if (ref.language is HasGlobalVariables) { + // We can try to infer a possible global variable (at top-level), if the language supports + // this + scopeManager.globalScope?.astNode?.startInference(this)?.inferVariableDeclaration(ref) + } else { + // Nothing to infer + null } - - if (ref.language !is HasGlobalVariables) { - return null - } - - // Forward this to our inference system. This will also check whether and how inference is - // configured. - return scopeManager.globalScope?.astNode?.startInference(this)?.inferVariableDeclaration(ref) } /** @@ -171,22 +178,20 @@ internal fun TranslationContext.tryGlobalVariableInference(ref: Reference): Decl */ internal fun TranslationContext.tryFieldInference( ref: Reference, - objectType: ObjectType + targetType: ObjectType ): ValueDeclaration? { // We only want to infer fields here, this can either happen if we have a reference with an // implicit receiver or if we have a scoped reference and the scope points to a record - val (scope, _) = scopeManager.extractScope(ref, null) + val (scope, _) = scopeManager.extractScope(ref) if (scope != null && scope !is RecordScope) { return null } - val name = ref.name - - var record = objectType.recordDeclaration + var record = targetType.recordDeclaration if (record == null) { // We access an unknown field of an unknown record. so we need to handle that along the // way as well - record = tryRecordInference(objectType, locationHint = ref, updateType = true) + record = tryRecordInference(targetType, locationHint = ref, updateType = true) } if (record == null) { @@ -198,7 +203,7 @@ internal fun TranslationContext.tryFieldInference( val declaration = ref.newFieldDeclaration( - name.localName, + ref.name.localName, // we will set the type later through the type inference observer record.unknownType(), 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 a576b9f742..30b4718970 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 @@ -44,7 +44,7 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import tryFieldInference import tryFunctionInference -import tryGlobalVariableInference +import tryVariableInference /** * Creates new connections between the place where a variable is declared and where it is used. @@ -161,34 +161,31 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { return target } - protected fun handleReference(currentClass: RecordDeclaration?, current: Node?) { - val language = current?.language - - if (current !is Reference || current is MemberExpression) return + protected fun handleReference(currentClass: RecordDeclaration?, ref: Reference) { + val language = ref.language // Ignore references to anonymous identifiers, if the language supports it (e.g., the _ // identifier in Go) if ( - language is HasAnonymousIdentifier && - current.name.localName == language.anonymousIdentifier + language is HasAnonymousIdentifier && ref.name.localName == language.anonymousIdentifier ) { return } // Ignore references to "super" if the language has super expressions, because they will be // handled separately in handleMemberExpression - if (language is HasSuperClasses && current.name.localName == language.superClassKeyword) { + if (language is HasSuperClasses && ref.name.localName == language.superClassKeyword) { return } // Find a list of candidate symbols. Currently, this is only used the in the "next-gen" call // resolution, but in future this will also be used in resolving regular references. - current.candidates = scopeManager.findSymbols(current.name, current.location).toSet() + ref.candidates = scopeManager.findSymbols(ref.name, ref.location).toSet() // Preparation for a future without legacy call resolving. Taking the first candidate is not // ideal since we are running into an issue with function pointers here (see workaround // below). - var wouldResolveTo = current.candidates.singleOrNull() + var wouldResolveTo = ref.candidates.singleOrNull() // For now, we need to ignore reference expressions that are directly embedded into call // expressions, because they are the "callee" property. In the future, we will use this @@ -199,7 +196,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { // of this call expression back to its original variable declaration. In the future, we want // to extend this particular code to resolve all callee references to their declarations, // i.e., their function definitions and get rid of the separate CallResolver. - if (current.resolutionHelper is CallExpression) { + if (ref.resolutionHelper is CallExpression) { // Peek into the declaration, and if it is only one declaration and a variable, we can // proceed normally, as we are running into the special case explained above. Otherwise, // we abort here (for now). @@ -213,7 +210,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { // percentage of references now. if (wouldResolveTo is FunctionDeclaration) { // We need to invoke the legacy resolver, just to be sure - var legacy = scopeManager.resolveReference(current) + var legacy = scopeManager.resolveReference(ref) // This is just for us to catch these differences in symbol resolving in the future. The // difference is pretty much only that the legacy system takes parameters of the @@ -230,45 +227,27 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { // Only consider resolving, if the language frontend did not specify a resolution. If we // already have populated the wouldResolveTo variable, we can re-use this instead of // resolving again - var refersTo = current.refersTo ?: wouldResolveTo + var refersTo = ref.refersTo ?: wouldResolveTo var recordDeclType: Type? = null if (currentClass != null) { recordDeclType = currentClass.toType() } - val helperType = current.resolutionHelper?.type + val helperType = ref.resolutionHelper?.type if (helperType is FunctionPointerType && refersTo == null) { - refersTo = resolveMethodFunctionPointer(current, helperType) + refersTo = resolveMethodFunctionPointer(ref, helperType) } // If we did not resolve the reference up to this point, we can try to infer the declaration if (refersTo == null) { - refersTo = - // Try to infer fields or namespace variables, if it makes sense - if ( - language is HasImplicitReceiver && - !current.name.isQualified() && - !current.isStaticAccess && - recordDeclType is ObjectType && - recordDeclType.recordDeclaration != null - ) { - ctx.tryFieldInference(current, recordDeclType) - } else { - // We can try to infer a possible global variable (either at top-level or - // namespace level), if the language supports this - ctx.tryGlobalVariableInference(current) - } + refersTo = ctx.tryVariableInference(language, ref) } if (refersTo != null) { - current.refersTo = refersTo + ref.refersTo = refersTo } else { - Util.warnWithFileLocation( - current, - log, - "Did not find a declaration for ${current.name}" - ) + Util.warnWithFileLocation(ref, log, "Did not find a declaration for ${ref.name}") } } @@ -353,6 +332,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { val record = type.recordDeclaration if (record != null) { + // TODO(oxisto): This should use symbols rather than the AST fields member = record.fields .filter { it.name.lastPartsMatch(reference.name) } @@ -367,15 +347,16 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { .map { it.definition } .firstOrNull() } + if (member == null && record is EnumDeclaration) { member = record.entries[reference.name.localName] } - if (member != null) { - return member + if (member == null) { + member = ctx.tryFieldInference(reference, containingClass) } - return ctx.tryFieldInference(reference, containingClass) + return member } protected fun handle(node: Node?, currClass: RecordDeclaration?) {