diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/testcases/Query.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/testcases/Query.kt index 53283d4084..d1d43ea13e 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/testcases/Query.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/testcases/Query.kt @@ -25,6 +25,7 @@ */ package de.fraunhofer.aisec.cpg.testcases +import de.fraunhofer.aisec.cpg.InferenceConfiguration import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.frontends.TestLanguage import de.fraunhofer.aisec.cpg.graph.array @@ -99,6 +100,7 @@ class Query { .defaultPasses() .registerLanguage(TestLanguage(".")) .registerPass() + .inferenceConfiguration(InferenceConfiguration.builder().enabled(false).build()) .build() ) = testFrontend(config).build { @@ -158,7 +160,7 @@ class Query { memberCall("log", ref("logger")) { member("INFO", ref("Level", makeMagic = false)) literal("put ", t("string")) + - member("a", ref("a", makeMagic = false)) + + member("a", ref("sc", makeMagic = false)) + literal(" into highlyCriticalOperation()", t("string")) } returnStmt {} @@ -175,6 +177,9 @@ class Query { .defaultPasses() .registerLanguage(TestLanguage(".")) .registerPass() + .inferenceConfiguration( + InferenceConfiguration.builder().inferFunctions(false).build() + ) .build() ) = testFrontend(config).build { @@ -215,7 +220,7 @@ class Query { memberCall("log", ref("logger")) { member("INFO", ref("Level", makeMagic = false)) literal("put ", t("string")) + - member("a", ref("a", makeMagic = false)) + + member("a", ref("sc", makeMagic = false)) + literal(" into highlyCriticalOperation()", t("string")) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/InferenceConfiguration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/InferenceConfiguration.kt index b56b6f7df1..93b472b033 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/InferenceConfiguration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/InferenceConfiguration.kt @@ -34,11 +34,21 @@ import org.apache.commons.lang3.builder.ToStringStyle */ class InferenceConfiguration private constructor( - /** Enables smart guessing of cast vs. call expressions in the C/C++ language frontend */ + /** Enables or disables the inference system as a whole. */ + val enabled: Boolean, + + /** Enables smart guessing of cast vs. call expressions in the C/C++ language frontend. */ val guessCastExpressions: Boolean, - /** Enables the inference of record declarations */ + /** Enables the inference of record declarations. */ val inferRecords: Boolean, + + /** Enables the inference of function declarations. */ + val inferFunctions: Boolean, + + /** Enables the inference of variables, such as global variables. */ + val inferVariables: Boolean, + /** * Uses heuristics to add DFG edges for call expressions to unresolved functions (i.e., * functions not implemented in the given source code). @@ -46,20 +56,36 @@ private constructor( val inferDfgForUnresolvedSymbols: Boolean ) { class Builder( - var guessCastExpressions: Boolean = false, - var inferRecords: Boolean = false, - var inferDfgForUnresolvedCalls: Boolean = true + private var enabled: Boolean = true, + private var guessCastExpressions: Boolean = true, + private var inferRecords: Boolean = true, + private var inferFunctions: Boolean = true, + private var inferVariables: Boolean = true, + private var inferDfgForUnresolvedCalls: Boolean = true ) { fun guessCastExpressions(guess: Boolean) = apply { this.guessCastExpressions = guess } + fun enabled(infer: Boolean) = apply { this.enabled = infer } + fun inferRecords(infer: Boolean) = apply { this.inferRecords = infer } + fun inferFunctions(infer: Boolean) = apply { this.inferFunctions = infer } + + fun inferVariables(infer: Boolean) = apply { this.inferVariables = infer } + fun inferDfgForUnresolvedCalls(infer: Boolean) = apply { this.inferDfgForUnresolvedCalls = infer } fun build() = - InferenceConfiguration(guessCastExpressions, inferRecords, inferDfgForUnresolvedCalls) + InferenceConfiguration( + enabled, + guessCastExpressions, + inferRecords, + inferFunctions, + inferVariables, + inferDfgForUnresolvedCalls + ) } companion object { 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 e62bae1407..9736c93f0a 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 @@ -27,9 +27,11 @@ 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 +import de.fraunhofer.aisec.cpg.graph.scopes.GlobalScope import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression import de.fraunhofer.aisec.cpg.graph.types.Type @@ -42,7 +44,7 @@ import de.fraunhofer.aisec.cpg.passes.SymbolResolver * * Currently, this interface has no methods. However, in the future, this could be used to execute * language/frontend-specific code for the particular trait. This could help to fine-tune the - * [CallResolver] for specific languages. + * [SymbolResolver] for specific languages. */ interface LanguageTrait @@ -235,3 +237,12 @@ interface HasAnonymousIdentifier { val anonymousIdentifier: String get() = "_" } + +/** + * A language trait, that specifies that this language has global variables directly in the + * [GlobalScope], i.e,. not within a namespace, but directly contained in a + * [TranslationUnitDeclaration]. + */ +interface HasGlobalVariables { + val globalVariableScopeClass: Class +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/Util.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/Util.kt index cf60bf239c..e50cb7348b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/Util.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/Util.kt @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg.helpers import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation @@ -209,13 +210,13 @@ object Util { */ @Suppress("NOTHING_TO_INLINE") inline fun errorWithFileLocation( - node: Node, + node: Node?, log: Logger, format: String?, vararg arguments: Any? ) { log.error( - String.format("%s: %s", PhysicalLocation.locationLink(node.location), format), + String.format("%s: %s", PhysicalLocation.locationLink(node?.location), format), *arguments ) } @@ -344,13 +345,23 @@ object Util { } /** - * Establish dataflow from call arguments to the target [FunctionDeclaration] parameters + * Establish data-flow from a [CallExpression] arguments to the target [FunctionDeclaration] + * parameters. Additionally, if the call is a [MemberCallExpression], it establishes a data-flow + * from the [MemberCallExpression.base] towards the [MethodDeclaration.receiver]. * * @param target The call's target [FunctionDeclaration] - * @param arguments The call's arguments to be connected to the target's parameters + * @param call The [CallExpression] */ - fun attachCallParameters(target: FunctionDeclaration, arguments: List) { + fun attachCallParameters(target: FunctionDeclaration, call: CallExpression) { + // Add an incoming DFG edge from a member call's base to the method's receiver + if (target is MethodDeclaration && call is MemberCallExpression && !call.isStatic) { + target.receiver?.let { receiver -> call.base?.addNextDFG(receiver) } + } + + // Connect the arguments to parameters + val arguments = call.arguments target.parameterEdges.sortWith(Comparator.comparing { it.end.argumentIndex }) + var j = 0 while (j < arguments.size) { val parameters = target.parameters @@ -358,7 +369,6 @@ object Util { val param = parameters[j] if (param.isVariadic) { while (j < arguments.size) { - // map all the following arguments to this variadic param param.addPrevDFG(arguments[j]) j++ diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt index 0fb65f7f34..a5a01b6856 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt @@ -27,10 +27,7 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.TupleDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* @@ -155,7 +152,18 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * the function. */ protected fun handleFunctionDeclaration(node: FunctionDeclaration) { - node.allChildren().forEach { node.addPrevDFG(it) } + if (node.isInferred) { + // If the function is inferred, we connect all parameters to the function declaration. + // The condition should make sure that we don't add edges multiple times, i.e., we + // only handle the declaration exactly once. + node.addAllPrevDFG(node.parameters) + // If it's a method with a receiver, we connect that one too. + if (node is MethodDeclaration) { + node.receiver?.let { node.addPrevDFG(it) } + } + } else { + node.allChildren().forEach { node.addPrevDFG(it) } + } } /** @@ -398,12 +406,8 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { handleUnresolvedCalls(call, call) } else if (call.invokes.isNotEmpty()) { call.invokes.forEach { - if (it.isInferred && inferDfgForUnresolvedSymbols) { - handleUnresolvedCalls(call, it) - } else { - Util.attachCallParameters(it, call.arguments) - call.addPrevDFG(it) - } + Util.attachCallParameters(it, call) + call.addPrevDFG(it) } } } 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 26bb321345..af923a3644 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 @@ -130,7 +130,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { protected fun resolveMethodFunctionPointer( reference: Reference, type: FunctionPointerType - ): ValueDeclaration { + ): ValueDeclaration? { var target = scopeManager.resolveReference(reference) // If we didn't find anything, we create a new function or method declaration @@ -145,7 +145,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { target = (scope?.astNode ?: currentTU) .startInference(ctx) - .createInferredFunctionDeclaration( + ?.inferFunctionDeclaration( reference.name, null, false, @@ -236,6 +236,12 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { refersTo = field } } + + if (refersTo == null) { + // We can try to infer a possible global variable, if the language supports this + refersTo = tryGlobalVariableInference(current) + } + if (refersTo != null) { current.refersTo = refersTo } else { @@ -247,6 +253,49 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { } } + /** + * Tries to infer a global variable from an unresolved [Reference]. This will return `null`, if + * inference was not possible, or if it was turned off in the [InferenceConfiguration]. + */ + private fun tryGlobalVariableInference(ref: Reference): Declaration? { + if (ref.language !is HasGlobalVariables) { + return null + } + + // For now, we only infer globals at the top-most global level, i.e., no globals in + // namespaces + if (ref.name.isQualified()) { + return null + } + + // Forward this to our inference system. This will also check whether and how inference is + // configured. + return scopeManager.globalScope?.astNode?.startInference(ctx)?.inferVariableDeclaration(ref) + } + + /** + * 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? { + val kind = + if (type.language is HasStructs) { + "struct" + } else { + "class" + } + val record = type.startInference(ctx)?.inferRecordDeclaration(type, currentTU, kind) + + // update the type's record + if (record != null) { + type.recordDeclaration = record + } + + return record + } + /** * We get the type of the "scope" this node is in. (e.g. for a field, we drop the field's name * and have the class) @@ -368,21 +417,9 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { var record = base.recordDeclaration if (record == null) { - // No matching record in the map? If we should infer it, we do so, otherwise we stop. - if (!config.inferenceConfiguration.inferRecords) return null - - // We access an unknown field of an unknown record. so we need to handle that - val kind = - if (base.language is HasStructs) { - "struct" - } else { - "class" - } - record = base.startInference(ctx).inferRecordDeclaration(base, currentTU, kind) - // update the type's record - if (record != null) { - base.recordDeclaration = record - } + // We access an unknown field of an unknown record. so we need to handle that along the + // way as well + record = tryRecordInference(base) } if (record == null) { @@ -656,14 +693,16 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { .mapNotNull { val root = it.root as? ObjectType var record = root?.recordDeclaration - if (root != null && record == null && config.inferenceConfiguration.inferRecords) { - record = it.startInference(ctx).inferRecordDeclaration(it, currentTU) + if (root != null && record == null) { + record = + it.startInference(ctx) + ?.inferRecordDeclaration(it, currentTU, locationHint = call) // update the record declaration root.recordDeclaration = record } record } - .map { record -> record.inferMethod(call, ctx = ctx) } + .mapNotNull { record -> record.inferMethod(call, ctx = ctx) } } /** @@ -821,7 +860,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { protected fun getConstructorDeclaration( constructExpression: ConstructExpression, recordDeclaration: RecordDeclaration - ): ConstructorDeclaration { + ): ConstructorDeclaration? { val signature = constructExpression.signature var constructorCandidate = recordDeclaration.constructors.firstOrNull { it.hasSignature(signature) } @@ -842,15 +881,15 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { return constructorCandidate ?: recordDeclaration .startInference(ctx) - .createInferredConstructor(constructExpression.signature) + ?.createInferredConstructor(constructExpression.signature) } protected fun getConstructorDeclarationForExplicitInvocation( signature: List, recordDeclaration: RecordDeclaration - ): ConstructorDeclaration { + ): ConstructorDeclaration? { return recordDeclaration.constructors.firstOrNull { it.hasSignature(signature) } - ?: recordDeclaration.startInference(ctx).createInferredConstructor(signature) + ?: recordDeclaration.startInference(ctx)?.createInferredConstructor(signature) } companion object { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt index 7a05381321..b945fe6cc3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt @@ -25,7 +25,10 @@ */ package de.fraunhofer.aisec.cpg.passes.inference +import de.fraunhofer.aisec.cpg.InferenceConfiguration +import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.TypeManager import de.fraunhofer.aisec.cpg.frontends.HasClasses import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.graph.* @@ -33,9 +36,12 @@ import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.statements.expressions.TypeExpression import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.helpers.Util.debugWithFileLocation +import de.fraunhofer.aisec.cpg.helpers.Util.errorWithFileLocation import java.util.* import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -50,7 +56,7 @@ import org.slf4j.LoggerFactory * Since this class implements [IsInferredProvider], all nodes that are created using the node * builder functions, will automatically have [Node.isInferred] set to true. */ -class Inference(val start: Node, override val ctx: TranslationContext) : +class Inference internal constructor(val start: Node, override val ctx: TranslationContext) : LanguageProvider, ScopeProvider, IsInferredProvider, @@ -63,20 +69,24 @@ class Inference(val start: Node, override val ctx: TranslationContext) : override val isInferred: Boolean get() = true - val scopeManager = ctx.scopeManager - val typeManager = ctx.typeManager + val scopeManager: ScopeManager + val typeManager: TypeManager override val scope: Scope? get() = scopeManager.currentScope - fun createInferredFunctionDeclaration( + fun inferFunctionDeclaration( name: CharSequence?, code: String?, isStatic: Boolean, signature: List, returnType: Type?, hint: CallExpression? = null - ): FunctionDeclaration { + ): FunctionDeclaration? { + if (!ctx.config.inferenceConfiguration.inferFunctions) { + 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 @@ -107,6 +117,10 @@ class Inference(val start: Node, override val ctx: TranslationContext) : signature.map { it?.name } ) + // Create parameter declarations and receiver (only for methods). + if (inferred is MethodDeclaration) { + createInferredReceiver(inferred, record) + } createInferredParameters(inferred, signature) // Set the type and return type(s) @@ -117,11 +131,17 @@ class Inference(val start: Node, override val ctx: TranslationContext) : scopeManager.addDeclaration(inferred) // Some magic that adds it to static imports. Not sure if this really needed - if (record != null && isStatic) { record.staticImports.add(inferred) } + // Some more magic, that adds it to the AST. Note: this might not be 100 % compliant + // with the language, since in some languages the AST of a method declaration could be + // outside of a method, but this will do for now + if (record != null && inferred is MethodDeclaration) { + record.addMethod(inferred) + } + // "upgrade" our struct to a class, if it was inferred by us, since we are calling // methods on it. But only if the language supports classes in the first place. if ( @@ -158,6 +178,22 @@ class Inference(val start: Node, override val ctx: TranslationContext) : return scopeManager.withScope(scopeManager.lookupScope(start), init) } + /** + * This function creates a [VariableDeclaration], which acts as the + * [MethodDeclaration.receiver], in order to hold all data flows to the object instance of this + * particular [method]. + */ + private fun createInferredReceiver(method: MethodDeclaration, record: RecordDeclaration?) { + // We do not really know, how a receiver is called in a particular language, but we will + // probably not do anything wrong by calling it "this". + val receiver = newVariableDeclaration("this", record?.toType() ?: unknownType()) + method.receiver = receiver + } + + /** + * This function creates a [ParameterDeclaration] for each parameter in the [function]'s + * [signature]. + */ private fun createInferredParameters(function: FunctionDeclaration, signature: List) { // To save some unnecessary scopes, we only want to "enter" the function if it is necessary, // e.g., if we need to create parameters @@ -254,7 +290,7 @@ class Inference(val start: Node, override val ctx: TranslationContext) : * @param call * @return inferred FunctionTemplateDeclaration which can be invoked by the call */ - fun createInferredFunctionTemplate(call: CallExpression): FunctionTemplateDeclaration { + fun inferFunctionTemplate(call: CallExpression): FunctionTemplateDeclaration { // We assume that the start is either a record or the translation unit val record = start as? RecordDeclaration val tu = start as? TranslationUnitDeclaration @@ -290,18 +326,24 @@ class Inference(val start: Node, override val ctx: TranslationContext) : // Template Parameter val inferredTypeIdentifier = "T$typeCounter" val typeParamDeclaration = - inferred.startInference(ctx).inferTemplateParameter(inferredTypeIdentifier) + inferred.startInference(ctx)?.inferTemplateParameter(inferredTypeIdentifier) typeCounter++ - inferred.addParameter(typeParamDeclaration) + if (typeParamDeclaration != null) { + inferred.addParameter(typeParamDeclaration) + } } else if (node is Expression) { val inferredNonTypeIdentifier = "N$nonTypeCounter" val paramVariableDeclaration = node .startInference(ctx) - .inferNonTypeTemplateParameter(inferredNonTypeIdentifier) - node.addNextDFG(paramVariableDeclaration) + ?.inferNonTypeTemplateParameter(inferredNonTypeIdentifier) + if (paramVariableDeclaration != null) { + node.addNextDFG(paramVariableDeclaration) + } nonTypeCounter++ - inferred.addParameter(paramVariableDeclaration) + if (paramVariableDeclaration != null) { + inferred.addParameter(paramVariableDeclaration) + } } } return inferred @@ -310,36 +352,85 @@ class Inference(val start: Node, override val ctx: TranslationContext) : /** * Infers a record declaration for the given type. [type] is the object type representing a * record that we want to infer. The [kind] specifies if we create a class or a struct. + * + * Since [Type] does not contain a location, a separate node that contains a location can + * optionally be specified in [locationHint]. This could for example be a call expression that + * contained the reference to a class method. */ fun inferRecordDeclaration( type: Type, currentTU: TranslationUnitDeclaration, - kind: String = "class" + kind: String = "class", + locationHint: Node? = null ): RecordDeclaration? { + if (!ctx.config.inferenceConfiguration.inferRecords) { + return null + } + if (type !is ObjectType) { - log.error( + errorWithFileLocation( + locationHint, + log, "Trying to infer a record declaration of a non-object type. Not sure what to do? Should we change the type?" ) return null } - log.debug( + debugWithFileLocation( + locationHint, + log, "Encountered an unknown record type ${type.typeName} during a call. We are going to infer that record" ) - // 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) - declaration.isInferred = true + return inferInScopeOf(currentTU) { + // 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) + declaration.isInferred = true + + // Update the type + type.recordDeclaration = declaration + + scopeManager.addDeclaration(declaration) + declaration + } + } + + /** + * This infers a [VariableDeclaration] based on an unresolved [Reference], which is supplied as + * a [hint]. Currently, this is only used to infer global variables. In the future, we might + * also infer static variables in namespaces. + */ + fun inferVariableDeclaration(hint: Reference): VariableDeclaration? { + if (!ctx.config.inferenceConfiguration.inferVariables) { + return null + } + + return inferInScopeOf(start) { + // Build a new variable declaration from the reference. Maybe we are even lucky and the + // reference has a type -- some language frontends provide us one -- but most likely + // this type will be unknown. + val inferred = newVariableDeclaration(hint.name, hint.type) + + debugWithFileLocation( + hint, + log, + "Inferred a new variable declaration {} with type {}", + inferred.name, + inferred.type + ) + + // In any case, we will observe the type of our reference and update our new variable + // declaration accordingly. + hint.typeObservers += TypeInferenceObserver(inferred) - // update the type - type.recordDeclaration = declaration + // Add it to the scope + scopeManager.addDeclaration(inferred) - // add this record declaration to the current TU (this bypasses the scope manager) - currentTU.addDeclaration(declaration) - return declaration + inferred + } } - fun createInferredNamespaceDeclaration(name: Name, path: String?): NamespaceDeclaration { + fun inferNamespaceDeclaration(name: Name, path: String?): NamespaceDeclaration { // Here be dragons. Jump to the scope that the node defines directly, so that we can // delegate further operations to the scope manager. We also save the old scope so we can // restore it. @@ -413,6 +504,11 @@ class Inference(val start: Node, override val ctx: TranslationContext) : companion object { val log: Logger = LoggerFactory.getLogger(Inference::class.java) } + + init { + this.scopeManager = ctx.scopeManager + this.typeManager = ctx.typeManager + } } /** Provides information about the inference status of a node. */ @@ -425,17 +521,26 @@ interface IsImplicitProvider : MetadataProvider { val isImplicit: Boolean } -/** Returns a new [Inference] object starting from this node. */ -fun Node.startInference(ctx: TranslationContext) = Inference(this, ctx) +/** + * Returns a new [Inference] object starting from this node. This will check, whether inference is + * enabled at all (using [InferenceConfiguration.enabled]). Otherwise null, will be returned. + */ +fun Node.startInference(ctx: TranslationContext): Inference? { + if (!ctx.config.inferenceConfiguration.enabled) { + return null + } + + return Inference(this, ctx) +} /** Tries to infer a [FunctionDeclaration] from a [CallExpression]. */ fun TranslationUnitDeclaration.inferFunction( call: CallExpression, isStatic: Boolean = false, ctx: TranslationContext -): FunctionDeclaration { - return Inference(this, ctx) - .createInferredFunctionDeclaration( +): FunctionDeclaration? { + return startInference(ctx) + ?.inferFunctionDeclaration( call.name.localName, call.code, isStatic, @@ -451,9 +556,9 @@ fun NamespaceDeclaration.inferFunction( call: CallExpression, isStatic: Boolean = false, ctx: TranslationContext -): FunctionDeclaration { - return Inference(this, ctx) - .createInferredFunctionDeclaration( +): FunctionDeclaration? { + return startInference(ctx) + ?.inferFunctionDeclaration( call.name, call.code, isStatic, @@ -469,9 +574,9 @@ fun RecordDeclaration.inferMethod( call: CallExpression, isStatic: Boolean = false, ctx: TranslationContext -): MethodDeclaration { - return Inference(this, ctx) - .createInferredFunctionDeclaration( +): MethodDeclaration? { + return startInference(ctx) + ?.inferFunctionDeclaration( call.name.localName, call.code, isStatic, @@ -479,5 +584,5 @@ fun RecordDeclaration.inferMethod( // TODO: Is the call's type the return value's type? call.type, call - ) as MethodDeclaration + ) as? MethodDeclaration } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt index d68494784d..d4c28dcc67 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt @@ -37,12 +37,13 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertNotNull import kotlin.test.assertTrue class UnresolvedDFGPassTest { @Test fun testUnresolvedCalls() { - val result = getDfgUnresolvedCalls(true) + val result = getDfgUnresolvedCalls(true, false) // Flow from base to return value val firstCall = result.calls { it.name.localName == "get" }[0] @@ -69,7 +70,7 @@ class UnresolvedDFGPassTest { @Test fun testUnresolvedCallsNoInference() { - val result = getDfgUnresolvedCalls(false) + val result = getDfgUnresolvedCalls(false, false) // No flow from base to return value val firstCall = result.calls { it.name.localName == "get" }[0] @@ -87,9 +88,57 @@ class UnresolvedDFGPassTest { assertTrue(knownCall.prevDFG.firstOrNull() is MethodDeclaration) } + @Test + fun testUnresolvedCallsWithInference() { + // For calls with an inferred function declaration, we connect the arguments with the + // parameter declaration. + // The parameter declaration is connected to the function declaration which then flows to + // the lhs of the assignment. + val result = getDfgUnresolvedCalls(false, true) + + val osDecl = result.variables["os"] + assertNotNull(osDecl) + + // Flow from base to method declaration which then flows to the call + val firstCall = result.calls { it.name.localName == "get" }[0] + assertEquals(1, firstCall.prevDFG.size) + // Check if it's the "get" method. + val getMethod1 = firstCall.prevDFG.singleOrNull { it.name.localName == "get" } + assertNotNull(getMethod1) + assertEquals(1, getMethod1.prevDFG.size) + assertEquals( + osDecl, + (getMethod1.prevDFG.singleOrNull()?.prevDFG?.singleOrNull() as? Reference)?.refersTo + ) + + // Flow from base and argument to return value + val callWithParam = result.calls { it.name.localName == "get" }[1] + assertEquals(1, callWithParam.prevDFG.size) + // Check if it's the "get" method. + val getMethod2 = callWithParam.prevDFG.singleOrNull { it.name.localName == "get" } + assertNotNull(getMethod2) + assertEquals(2, getMethod2.prevDFG.size) + val callWithParamArgs = getMethod2.prevDFG.flatMap { it.prevDFG } + assertEquals( + osDecl, + callWithParamArgs.filterIsInstance().firstOrNull()?.refersTo + ) + assertEquals(4, callWithParamArgs.filterIsInstance>().firstOrNull()?.value) + + // No specific flows for resolved functions + // => Goes through the method declaration and then follows the instructions in the method's + // implementation + val knownCall = result.calls { it.name.localName == "knownFunction" }[0] + assertEquals(1, knownCall.prevDFG.size) + assertTrue(knownCall.prevDFG.firstOrNull() is MethodDeclaration) + } + companion object { - fun getDfgUnresolvedCalls(inferUnresolved: Boolean): TranslationResult { + fun getDfgUnresolvedCalls( + inferUnresolved: Boolean, + inferFunctions: Boolean + ): TranslationResult { val config = TranslationConfiguration.builder() .defaultPasses() @@ -97,6 +146,7 @@ class UnresolvedDFGPassTest { .inferenceConfiguration( InferenceConfiguration.builder() .inferDfgForUnresolvedCalls(inferUnresolved) + .inferFunctions(inferFunctions) .build() ) .build() diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt index 73a082bc45..a031c07d1c 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg.frontends.cxx import com.fasterxml.jackson.annotation.JsonIgnore import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.* +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 @@ -46,7 +47,8 @@ open class CLanguage : HasFunctionPointers, HasQualifier, HasElaboratedTypeSpecifier, - HasShortCircuitOperators { + HasShortCircuitOperators, + HasGlobalVariables { override val fileExtensions = listOf("c", "h") override val namespaceDelimiter = "::" @Transient override val frontend: KClass = CXXLanguageFrontend::class @@ -57,6 +59,9 @@ open class CLanguage : val unaryOperators = listOf("--", "++", "-", "+", "*", "&", "~") + override val globalVariableScopeClass: Class + 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 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 3a70eb3533..c65d684c43 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 @@ -316,7 +316,7 @@ open class CPPLanguage : // If we want to use an inferred functionTemplateDeclaration, this needs to be provided. // Otherwise, we could not resolve to a template and no modifications are made val functionTemplateDeclaration = - holder.startInference(ctx).createInferredFunctionTemplate(templateCall) + holder.startInference(ctx)?.inferFunctionTemplate(templateCall) templateCall.templateInstantiation = functionTemplateDeclaration val edges = templateCall.templateParameterEdges // Set instantiation propertyEdges @@ -327,6 +327,10 @@ open class CPPLanguage : ) } + if (functionTemplateDeclaration == null) { + return Pair(false, listOf()) + } + return Pair(true, functionTemplateDeclaration.realization) } diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt index 15d82d6389..c0b2886299 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt @@ -462,7 +462,17 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : return callExpression } - private fun handleIdExpression(ctx: IASTIdExpression): Reference { + private fun handleIdExpression(ctx: IASTIdExpression): Expression { + // In order to avoid many inferred/unresolved symbols, we want to make sure that we convert + // NULL into a proper null-literal, regardless whether headers are included or not. + if (ctx.name.toString() == "NULL") { + return if (language is CPPLanguage) { + newLiteral(null, objectType("std::nullptr_t"), rawNode = ctx) + } else { + newLiteral(0, unknownType(), rawNode = ctx) + } + } + // this expression could actually be a field / member expression, but somehow CDT only // recognizes them as a member expression if it has an explicit 'this' // TODO: handle this? convert the declared reference expression into a member expression? diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXInferenceTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXInferenceTest.kt new file mode 100644 index 0000000000..9283b1959b --- /dev/null +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXInferenceTest.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ +package de.fraunhofer.aisec.cpg.frontends.cxx + +import de.fraunhofer.aisec.cpg.TestUtils +import de.fraunhofer.aisec.cpg.graph.* +import java.io.File +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertNotNull + +class CXXInferenceTest { + @Test + fun testGlobals() { + val file = File("src/test/resources/cxx/inference.cpp") + val tu = + TestUtils.analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) { + it.registerLanguage() + it.loadIncludes(false) + it.addIncludesToGraph(false) + } + assertNotNull(tu) + + val global = tu.variables["somethingGlobal"] + assertNotNull(global) + + assertContains(tu.declarations, global) + } +} diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt index 2f9b001e77..e620405c78 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt @@ -265,24 +265,24 @@ internal class CXXLanguageFrontendTest : BaseTest() { @Throws(Exception::class) fun testFunctionDeclaration() { val file = File("src/test/resources/cxx/functiondecl.cpp") - val declaration = + val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) { it.registerLanguage() } - // should be seven function nodes - assertEquals(8, declaration.declarations.size) + // should be eight function nodes + assertEquals(8, tu.functions.size) - var method = declaration.getDeclarationAs(0, FunctionDeclaration::class.java) + var method = tu.getDeclarationAs(0, FunctionDeclaration::class.java) assertEquals("function0(int)void", method!!.signature) - method = declaration.getDeclarationAs(1, FunctionDeclaration::class.java) + method = tu.getDeclarationAs(1, FunctionDeclaration::class.java) assertEquals("function1(int, std::string, SomeType*, AnotherType&)int", method!!.signature) val args = method.parameters.map { it.name.localName } assertEquals(listOf("arg0", "arg1", "arg2", "arg3"), args) - method = declaration.getDeclarationAs(2, FunctionDeclaration::class.java) + method = tu.getDeclarationAs(2, FunctionDeclaration::class.java) assertEquals("function0(int)void", method!!.signature) var statements = (method.body as Block).statements @@ -294,7 +294,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertNotNull(statement) assertTrue(statement.isImplicit) - method = declaration.getDeclarationAs(3, FunctionDeclaration::class.java) + method = tu.getDeclarationAs(3, FunctionDeclaration::class.java) assertEquals("function2()void*", method!!.signature) statements = (method.body as Block).statements @@ -306,20 +306,20 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertNotNull(statement) assertFalse(statement.isImplicit) - method = declaration.getDeclarationAs(4, FunctionDeclaration::class.java) + method = tu.getDeclarationAs(4, FunctionDeclaration::class.java) assertNotNull(method) assertEquals("function3()UnknownType*", method.signature) - method = declaration.getDeclarationAs(5, FunctionDeclaration::class.java) + method = tu.getDeclarationAs(5, FunctionDeclaration::class.java) assertNotNull(method) assertEquals("function4(int)void", method.signature) - method = declaration.getDeclarationAs(6, FunctionDeclaration::class.java) + method = tu.getDeclarationAs(6, FunctionDeclaration::class.java) assertNotNull(method) assertEquals(0, method.parameters.size) assertEquals("function5()void", method.signature) - method = declaration.getDeclarationAs(7, FunctionDeclaration::class.java) + method = tu.getDeclarationAs(7, FunctionDeclaration::class.java) assertNotNull(method) assertEquals(1, method.parameters.size) @@ -512,7 +512,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { ) assertEquals(incompleteType().reference(POINTER), pointerWithAssign.type) assertLocalName("ptr2", pointerWithAssign) - assertLocalName("NULL", pointerWithAssign.initializer) + assertLiteralValue(null, pointerWithAssign.initializer) val classWithVariable = statements[6].declarations assertEquals(2, classWithVariable.size) 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 3a6c277e12..5d0d04813b 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 @@ -25,14 +25,11 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.BaseTest -import de.fraunhofer.aisec.cpg.TestUtils +import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.TestUtils.analyzeAndGetFirstTU import de.fraunhofer.aisec.cpg.TestUtils.findByName import de.fraunhofer.aisec.cpg.TestUtils.findByUniqueName import de.fraunhofer.aisec.cpg.TestUtils.findByUniquePredicate -import de.fraunhofer.aisec.cpg.TranslationResult -import de.fraunhofer.aisec.cpg.assertLocalName import de.fraunhofer.aisec.cpg.frontends.cxx.CPPLanguage import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.ConstructorDeclaration @@ -155,6 +152,9 @@ class CallResolverTest : BaseTest() { true ) { it.registerLanguage() + it.inferenceConfiguration( + InferenceConfiguration.builder().inferRecords(false).build() + ) } val tu = result.components.flatMap { it.translationUnits }.firstOrNull() assertNotNull(tu) diff --git a/cpg-language-cxx/src/test/resources/cxx/inference.cpp b/cpg-language-cxx/src/test/resources/cxx/inference.cpp new file mode 100644 index 0000000000..220c261399 --- /dev/null +++ b/cpg-language-cxx/src/test/resources/cxx/inference.cpp @@ -0,0 +1,21 @@ +// The headers are just there to make it compile with clang, but we will not parse headers. +// 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 +// namespace, because otherwise we would not know whether this is a class or a namespace. +namespace constants {}; + +int doSomething() { + if(somethingGlobal == 4) { + return 1; + } + + double a = constants::pi; + + return 0; +} + +int main() { + doSomething(); +} \ No newline at end of file diff --git a/cpg-language-cxx/src/test/resources/cxx/inference.h b/cpg-language-cxx/src/test/resources/cxx/inference.h new file mode 100644 index 0000000000..cb7d38c10d --- /dev/null +++ b/cpg-language-cxx/src/test/resources/cxx/inference.h @@ -0,0 +1,7 @@ +#include + +int somethingGlobal = 0; + +namespace constants { + double pi = std::numbers::pi; +} 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 1111bf3ca2..d2d9fd0633 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 @@ -394,7 +394,7 @@ class GoExtraPass(ctx: TranslationContext) : ComponentPass(ctx) { scopeManager.globalScope ?.astNode ?.startInference(ctx) - ?.createInferredNamespaceDeclaration(include.name, include.filename) + ?.inferNamespaceDeclaration(include.name, include.filename) } } diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt index e2b72c1a0d..8a1f35cea9 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.BaseTest +import de.fraunhofer.aisec.cpg.InferenceConfiguration import de.fraunhofer.aisec.cpg.TestUtils import de.fraunhofer.aisec.cpg.TestUtils.findByName import de.fraunhofer.aisec.cpg.TestUtils.findByUniqueName @@ -119,7 +120,12 @@ class CallResolverTest : BaseTest() { @Throws(Exception::class) fun testJava() { val result = - TestUtils.analyze("java", topLevel, true) { it.registerLanguage(JavaLanguage()) } + TestUtils.analyze("java", topLevel, true) { + it.registerLanguage(JavaLanguage()) + it.inferenceConfiguration( + InferenceConfiguration.builder().inferRecords(false).build() + ) + } val tu = result.components.flatMap { it.translationUnits }.firstOrNull() assertNotNull(tu) diff --git a/cpg-neo4j/src/test/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/ApplicationTest.kt b/cpg-neo4j/src/test/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/ApplicationTest.kt index 9faabb3585..949418253c 100644 --- a/cpg-neo4j/src/test/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/ApplicationTest.kt +++ b/cpg-neo4j/src/test/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/ApplicationTest.kt @@ -62,7 +62,7 @@ class ApplicationTest { fun testPush() { val (application, translationResult) = createTranslationResult() - assertEquals(31, translationResult.functions.size) + assertEquals(32, translationResult.functions.size) application.pushToNeo4j(translationResult) @@ -73,7 +73,7 @@ class ApplicationTest { val functions = session.loadAll(FunctionDeclaration::class.java) assertNotNull(functions) - assertEquals(31, functions.size) + assertEquals(32, functions.size) transaction.commit() } @@ -86,7 +86,7 @@ class ApplicationTest { fun testSerializeCpgViaOGM() { val (application, translationResult) = createTranslationResult() - assertEquals(31, translationResult.functions.size) + assertEquals(32, translationResult.functions.size) val (nodes, edges) = application.translateCPGToOGMBuilders(translationResult) val graph = application.buildJsonGraph(nodes, edges) @@ -118,7 +118,7 @@ class ApplicationTest { @Test fun testExportToJson() { val (application, translationResult) = createTranslationResult() - assertEquals(31, translationResult.functions.size) + assertEquals(32, translationResult.functions.size) val path = createTempFile().toFile() application.exportToJson(translationResult, path) assert(path.length() > 0)