Skip to content

Commit

Permalink
Even more inference cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
oxisto committed Oct 1, 2024
1 parent 42fd4f6 commit 66c1216
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}

/**
Expand All @@ -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) {
Expand All @@ -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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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).
Expand All @@ -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
Expand All @@ -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}")
}
}

Expand Down Expand Up @@ -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) }
Expand All @@ -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?) {
Expand Down

0 comments on commit 66c1216

Please sign in to comment.