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

Fixing placement of inferred record declarations #1515

Merged
merged 16 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -846,9 +846,9 @@ class ScopeManager : ScopeProvider {
* will also be used as a return value of this function. This can be useful, if you create
* objects, such as a [Node] inside this scope and want to return it to the calling function.
*/
fun <T : Any> withScope(scope: Scope?, init: () -> T): T {
fun <T : Any> withScope(scope: Scope?, init: (scope: Scope?) -> T): T {
val oldScope = jumpTo(scope)
val ret = init()
val ret = init(scope)
jumpTo(oldScope)

return ret
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ package de.fraunhofer.aisec.cpg.frontends

import de.fraunhofer.aisec.cpg.ScopeManager
import de.fraunhofer.aisec.cpg.TranslationContext
import de.fraunhofer.aisec.cpg.graph.Node
import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration
Expand Down Expand Up @@ -215,9 +214,7 @@ interface HasAnonymousIdentifier : LanguageTrait {
* [GlobalScope], i.e., not within a namespace, but directly contained in a
* [TranslationUnitDeclaration].
*/
interface HasGlobalVariables : LanguageTrait {
val globalVariableScopeClass: Class<out Node>
}
interface HasGlobalVariables : LanguageTrait

/**
* A language trait, that specifies that the language has so-called functional style casts, meaning
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ package de.fraunhofer.aisec.cpg.graph.scopes
import com.fasterxml.jackson.annotation.JsonBackReference
import de.fraunhofer.aisec.cpg.graph.Name
import de.fraunhofer.aisec.cpg.graph.Node
import de.fraunhofer.aisec.cpg.graph.Node.Companion.TO_STRING_STYLE
import de.fraunhofer.aisec.cpg.graph.statements.LabelStatement
import de.fraunhofer.aisec.cpg.helpers.neo4j.NameConverter
import org.apache.commons.lang3.builder.ToStringBuilder
import org.neo4j.ogm.annotation.GeneratedValue
import org.neo4j.ogm.annotation.Id
import org.neo4j.ogm.annotation.NodeEntity
Expand Down Expand Up @@ -112,4 +114,14 @@ abstract class Scope(

return scope
}

override fun toString(): String {
val builder = ToStringBuilder(this, TO_STRING_STYLE)

if (name?.isNotEmpty() == true) {
builder.append("name", name)
}

return builder.toString()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import de.fraunhofer.aisec.cpg.frontends.*
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.Scope
import de.fraunhofer.aisec.cpg.graph.scopes.StructureDeclarationScope
import de.fraunhofer.aisec.cpg.graph.statements.expressions.*
Expand Down Expand Up @@ -231,11 +232,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) {

// TODO: we need to do proper scoping (and merge it with the code above), but for now
// this just enables CXX static fields
if (
refersTo == null &&
language != null &&
language.namespaceDelimiter in current.name.toString()
) {
if (refersTo == null && language != null && current.name.isQualified()) {
recordDeclType = getEnclosingTypeOf(current)
val field = resolveMember(recordDeclType, current)
if (field != null) {
Expand Down Expand Up @@ -318,16 +315,24 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) {
* 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].
*/
private fun tryRecordInference(
type: Type,
): RecordDeclaration? {
private fun tryRecordInference(type: Type, locationHint: Node? = null): RecordDeclaration? {
val kind =
if (type.language is HasStructs) {
"struct"
} else {
"class"
}
val record = type.startInference(ctx)?.inferRecordDeclaration(type, currentTU, kind)
// Determine the scope where we want to start our inference
var (scope, _) = scopeManager.extractScope(type)

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

val record =
(scope?.astNode ?: currentTU)
.startInference(ctx)
?.inferRecordDeclaration(type, kind, locationHint)

// update the type's record
if (record != null) {
Expand Down Expand Up @@ -447,7 +452,35 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) {
if (member == null && record is EnumDeclaration) {
member = record.entries[reference.name.localName]
}
return member ?: handleUnknownField(containingClass, reference)

if (member != null) {
return member
}

// This is a little bit of a workaround, but at least this makes sure we are not inferring a
// record, where a namespace already exist
val (scope, _) = scopeManager.extractScope(reference, null)
KuechA marked this conversation as resolved.
Show resolved Hide resolved
return if (scope == null) {
handleUnknownField(containingClass, reference)
} else {
// Workaround needed for Java. If we already have a record scope, use the "old"
// inference function
when (scope) {
is RecordScope -> handleUnknownField(containingClass, reference)
is NameScope -> {
log.warn(
"We should infer a namespace variable ${reference.name} at this point, but this is not yet implemented."
)
null
}
else -> {
log.warn(
"We should infer a variable ${reference.name} in ${scope}, but this is not yet implemented."
)
null
}
}
}
}

// TODO(oxisto): Move to inference class
Expand All @@ -463,7 +496,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) {
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(base)
record = tryRecordInference(base, locationHint = ref)
}

if (record == null) {
Expand Down Expand Up @@ -800,11 +833,9 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) {
val root = it.root as? ObjectType
var record = root?.recordDeclaration
if (root != null && record == null) {
record =
it.startInference(ctx)
?.inferRecordDeclaration(root, currentTU, locationHint = call)
// update the record declaration
root.recordDeclaration = record
// We access an unknown method of an unknown record. so we need to handle that
// along the way as well
record = tryRecordInference(root, locationHint = call)
}
record
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class Inference internal constructor(val start: Node, override val ctx: Translat
debugWithFileLocation(
hint,
log,
"Inferred a new {} declaration {} with parameter types {}",
"Inferred a new {} declaration {} with parameter types {} in $it",
if (inferred is MethodDeclaration) "method" else "function",
inferred.name,
signature.map { it?.name }
Expand Down Expand Up @@ -174,7 +174,7 @@ class Inference internal constructor(val start: Node, override val ctx: Translat
* executing the commands in [init] (which needs to create an inferred node of [T]) as well as
* restoring the previous scope.
*/
private fun <T : Declaration> inferInScopeOf(start: Node, init: () -> T): T {
private fun <T : Declaration> inferInScopeOf(start: Node, init: (scope: Scope?) -> T): T {
return scopeManager.withScope(scopeManager.lookupScope(start), init)
}

Expand Down Expand Up @@ -359,14 +359,25 @@ class Inference internal constructor(val start: Node, override val ctx: Translat
*/
fun inferRecordDeclaration(
type: Type,
currentTU: TranslationUnitDeclaration,
kind: String = "class",
locationHint: Node? = null
): RecordDeclaration? {
if (!ctx.config.inferenceConfiguration.inferRecords) {
return null
}

// We assume that the start is either a record, a namespace or the translation unit
val record = start as? RecordDeclaration
val namespace = start as? NamespaceDeclaration
val tu = start as? TranslationUnitDeclaration

// If all are null, we have the wrong type
if (record == null && namespace == null && tu == null) {
throw UnsupportedOperationException(
"Starting inference with the wrong type of start node"
)
}

if (type !is ObjectType) {
errorWithFileLocation(
locationHint,
Expand All @@ -376,7 +387,7 @@ class Inference internal constructor(val start: Node, override val ctx: Translat
return null
}

return inferInScopeOf(currentTU) {
return inferInScopeOf(start) {
// This could be a class or a struct. We start with a class and may have to fine-tune
// this later.
val declaration = newRecordDeclaration(type.typeName, kind)
Expand All @@ -385,7 +396,7 @@ class Inference internal constructor(val start: Node, override val ctx: Translat
debugWithFileLocation(
locationHint,
log,
"Inferred a new record declaration ${declaration.name} (${declaration.kind})"
"Inferred a new record declaration ${declaration.name} (${declaration.kind}) in $it"
)

// Update the type
Expand Down Expand Up @@ -419,7 +430,7 @@ class Inference internal constructor(val start: Node, override val ctx: Translat
debugWithFileLocation(
hint,
log,
"Inferred a new variable declaration {} with type {}",
"Inferred a new variable declaration {} with type {} in $it",
inferred.name,
inferred.type
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,9 @@ class DFGFunctionSummariesTest {
val listType = t("test.List")
ctx?.let {
val recordDecl =
listType
.startInference(it)
this@translationUnit.startInference(it)
?.inferRecordDeclaration(
listType,
this@translationUnit
)
listType.recordDeclaration = recordDecl
recordDecl?.addSuperClass(objectType)
Expand All @@ -105,11 +103,9 @@ class DFGFunctionSummariesTest {
val specialListType = t("test.SpecialList")
ctx?.let {
val recordDecl =
specialListType
.startInference(it)
this@translationUnit.startInference(it)
?.inferRecordDeclaration(
specialListType,
this@translationUnit
)
specialListType.recordDeclaration = recordDecl
recordDecl?.addSuperClass(listType)
Expand All @@ -119,11 +115,9 @@ class DFGFunctionSummariesTest {
val verySpecialListType = t("test.VerySpecialList")
ctx?.let {
val recordDecl =
specialListType
.startInference(it)
this@translationUnit.startInference(it)
?.inferRecordDeclaration(
specialListType,
this@translationUnit
)
specialListType.recordDeclaration = recordDecl
recordDecl?.addSuperClass(listType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ package de.fraunhofer.aisec.cpg.frontends.cxx

import com.fasterxml.jackson.annotation.JsonIgnore
import de.fraunhofer.aisec.cpg.frontends.*
import de.fraunhofer.aisec.cpg.graph.Node
import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration
import de.fraunhofer.aisec.cpg.graph.types.*
import kotlin.reflect.KClass
import org.neo4j.ogm.annotation.Transient
Expand All @@ -53,9 +51,6 @@ open class CLanguage :

val unaryOperators = listOf("--", "++", "-", "+", "*", "&", "~")

override val globalVariableScopeClass: Class<out Node>
get() = TranslationUnitDeclaration::class.java

/**
* All operators which perform and assignment and an operation using lhs and rhs. See
* https://en.cppreference.com/w/c/language/operator_assignment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,22 @@ class CXXInferenceTest {

assertContains(tu.declarations, global)
}

@Test
fun testInferClassInNamespace() {
val file = File("src/test/resources/cxx/inference.cpp")
val tu =
TestUtils.analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) {
it.registerLanguage<CPPLanguage>()
it.loadIncludes(false)
it.addIncludesToGraph(false)
}
assertNotNull(tu)

val util = tu.namespaces["util"]
assertNotNull(util)

val someClass = util.records["SomeClass"]
assertNotNull(someClass)
}
}
10 changes: 9 additions & 1 deletion cpg-language-cxx/src/test/resources/cxx/inference.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
// You can use `clang++ -std=c++20 inference.cpp` to check, if it will compile.
#include "inference.h"

// To make it a little bit easier for our inference engine, we forward declare "constants" as a
// To make it a little bit easier for our inference engine, we forward declare "constants" and "util" as a
// namespace, because otherwise we would not know whether this is a class or a namespace.
namespace constants {};
namespace util {};

int doSomething() {
if(somethingGlobal == 4) {
Expand All @@ -17,5 +18,12 @@ int doSomething() {
}

int main() {
util::SomeClass* some = new util::SomeClass();
// this should trigger the inference
some->doSomething();

// repeat it to make sure we correctly resolve it now
some->doSomething();

doSomething();
}
7 changes: 7 additions & 0 deletions cpg-language-cxx/src/test/resources/cxx/inference.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,10 @@ int somethingGlobal = 0;
namespace constants {
double pi = std::numbers::pi;
}

namespace util {
class SomeClass {
public:
void doSomething() {};
};
}
Loading