diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/PointsToPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/PointsToPass.kt index 195b32b2623..369d0196533 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/PointsToPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/PointsToPass.kt @@ -587,18 +587,22 @@ open class PointsToPass(ctx: TranslationContext) : EOGStarterPass(ctx, orderDepe // Then the memoryValues if (key is HasMemoryValue && value.second.isNotEmpty()) { value.second.forEach { (v, properties) -> - var granularity = default() - var shortFS = false - properties.forEach { p -> - when (p) { - // String indicated a partial target - is String -> granularity = PartialDataflowGranularity(p) - // Boolean says if this is a shortFS or not - is Boolean -> shortFS = p - else -> TODO() + // We already added the parameters value in initializeParameters, so we skip + // that now + if (v !is Parameter) { + var granularity = default() + var shortFS = false + properties.forEach { p -> + when (p) { + // String indicated a partial target + is String -> granularity = PartialDataflowGranularity(p) + // Boolean says if this is a shortFS or not + is Boolean -> shortFS = p + else -> TODO() + } } + key.memoryValueEdges += Dataflow(v, key, granularity, shortFS) } - key.memoryValueEdges += Dataflow(v, key, granularity, shortFS) } } @@ -935,15 +939,9 @@ open class PointsToPass(ctx: TranslationContext) : EOGStarterPass(ctx, orderDepe mutableSetOf( NodeWithPropertiesKey( matchingDeclarations, - // Add the parameter - // index to indicate - // to the - // calculatePrevDFGs - // function that we - // need to replace - // the - // value of the call - // argument + // Add the parameter index to indicate to the + // calculatePrevDFGs function that we need to + // replace the value of the call argument equalLinkedHashSetOf( matchingDeclarations.argumentIndex ), @@ -1920,6 +1918,20 @@ open class PointsToPass(ctx: TranslationContext) : EOGStarterPass(ctx, orderDepe } private suspend fun calculateFunctionSummaries(invoke: Function): Function? { + fun addDummyFS() { + if (log.isTraceEnabled) { + log.trace("Creating dummy function summary") + } + val newValues = ConcurrentHashMap.newKeySet() + invoke.parameters.forEach { newValues.add(FSEntry(0, it, 1, "", isDummy = true)) } + val entries = identitySetOf() + if (invoke.returns.isNotEmpty()) entries.addAll(invoke.returns) else entries.add(invoke) + entries.forEach { entry -> invoke.functionSummary.put(entry, newValues) } + if (log.isTraceEnabled) { + log.trace("Finished creating dummy function summary") + } + } + if (invoke.functionSummary.isEmpty()) { if (invoke.hasBody()) { if (log.isTraceEnabled) { @@ -1970,28 +1982,13 @@ open class PointsToPass(ctx: TranslationContext) : EOGStarterPass(ctx, orderDepe log.error( "Cannot calculate functionSummary for ${invoke.name.localName} as it's recursively called. callChain: ${functionSummaryAnalysisChain.map{it.name.localName}}" ) - invoke.functionSummary.put( - newLiteral("dummy"), - ConcurrentHashMap.newKeySet(), - ) - return null + addDummyFS() } } else { // Add a dummy function summary so that we don't try this every time // In this dummy, all parameters point either to returns if we have any, or the // Function itself - if (log.isTraceEnabled) { - log.trace("Creating dummy function summary") - } - val newValues = ConcurrentHashMap.newKeySet() - invoke.parameters.forEach { newValues.add(FSEntry(0, it, 1, "")) } - val entries = identitySetOf() - if (invoke.returns.isNotEmpty()) entries.addAll(invoke.returns) - else entries.add(invoke) - entries.forEach { entry -> invoke.functionSummary.put(entry, newValues) } - if (log.isTraceEnabled) { - log.trace("Finished creating dummy function summary") - } + addDummyFS() } } return invoke @@ -3208,6 +3205,11 @@ open class PointsToPass(ctx: TranslationContext) : EOGStarterPass(ctx, orderDepe PowersetLattice.Element(), ), ) + // Additionally, directly add the value to the node. This is necessary on recursive + // calls, when we are not yet finished with acceptInternal of one function, but we + // already need to fetch the PMVs because we have a recursive call while handling + // the function + src.memoryValueEdges += Dataflow(pmv, src) } else { // Link the PMVs with each other so that we can find them. This is // especially important outside the respective function where we @@ -3249,6 +3251,10 @@ open class PointsToPass(ctx: TranslationContext) : EOGStarterPass(ctx, orderDepe PowersetLattice.Element(), ), ) + // Here again, directly add the value to the param to be able to access it directly + // instead of having to wait until acceptInternal is finished + param.memoryValueEdges += + Dataflow(pmv, param, granularity = PartialDataflowGranularity(pmvName)) } // Update the states diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/FollowXTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/FollowXTest.kt new file mode 100644 index 00000000000..4809f962bb5 --- /dev/null +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/FollowXTest.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2025, 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.graph.functions +import de.fraunhofer.aisec.cpg.graph.invoke +import de.fraunhofer.aisec.cpg.graph.variables +import de.fraunhofer.aisec.cpg.test.BaseTest +import de.fraunhofer.aisec.cpg.test.analyze +import java.io.File +import kotlin.test.Test +import org.junit.jupiter.api.assertNotNull + +class FollowXTest : BaseTest() { + @Test + fun testFollowX() { + val file = File("src/test/resources/complex_dfg.c") + val result = + analyze(listOf(file), file.parentFile.toPath(), true) { + it.registerLanguage() + } + assertNotNull(result) + + // functions + val mainFunc = result.functions("main").single() + + // Variables + val i = result.variables("i").single() + + // actual tests + // This will run forever (or very long), so it's commented out. + // TODO: Fix + /* val (paths, time) = + measureTimedValue { + i.followDFGEdgesUntilHit( + findAllPossiblePaths = true, + sensitivities = OnlyFullDFG + FieldSensitive + ContextSensitive, + predicate = { node -> (node as? Reference)?.name?.localName == "i" }, + ) + } + println("Path lookup took $time")*/ + } +} diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/PointsToPassTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/PointsToPassTest.kt index eb3a37cafba..bc5103fc4a6 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/PointsToPassTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/PointsToPassTest.kt @@ -36,6 +36,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.Variable import de.fraunhofer.aisec.cpg.graph.edges.flows.* import de.fraunhofer.aisec.cpg.graph.expressions.* import de.fraunhofer.aisec.cpg.helpers.toIdentitySet +import de.fraunhofer.aisec.cpg.test.analyze import de.fraunhofer.aisec.cpg.test.analyzeAndGetFirstTU import de.fraunhofer.aisec.cpg.test.assertLocalName import java.io.File @@ -5005,4 +5006,26 @@ class PointsToPassTest { ) } } + + @Test + fun testDerefPMVsOfRecursiveFunctions() { + val file = File("src/test/resources/complex_dfg.c") + val result = + analyze(listOf(file), file.parentFile.toPath(), true) { + it.registerLanguage() + } + assertNotNull(result) + + // Functions + val func2 = result.functions("func2").single { it.body != null } + val func3 = result.functions("func3").single { it.body != null } + + // Params and PMVs + val func2Param = func2.parameters.single() + val func2DerefPMV = func2Param.memoryValues.single { it.name.localName == "derefvalue" } + + // actual tests + assertEquals(2, func2DerefPMV.prevDFG.size) + assertContains(func2DerefPMV.prevFullDFG, func3.allChildren().single().input) + } } diff --git a/cpg-language-cxx/src/test/resources/complex_dfg.c b/cpg-language-cxx/src/test/resources/complex_dfg.c new file mode 100644 index 00000000000..45da7f0d040 --- /dev/null +++ b/cpg-language-cxx/src/test/resources/complex_dfg.c @@ -0,0 +1,30 @@ +#include + +int func1(int* p); +int func2(int* p); +int func3(int* p); + +int func3(int *p){ + printf("%d\n", *p); + (*p)++; + func2(p); +} + +int func2(int* p) { + for (int j=0; j<10; j++) { + func3(p); + } +} + +int func1(int* p) { + (*p)++; + func2(p); +} + +int main() { + int i=0; + int* p=&i; + func1(p); + printf("%d\n", i); + return 0; +}