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 4c32bfa9af3..cf22dfe203c 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 @@ -553,8 +553,19 @@ open class SymbolResolver(ctx: TranslationContext) : EOGStarterPass(ctx) { ) for (missingParam in missingNewParams) { if (missingParam != null) { + // duplicate node to prevent changes through the reference + val node: AstNode = + when (missingParam) { + is Literal<*> -> { + missingParam.duplicate(true) + } + is TypeExpression -> { + missingParam.duplicate(true) + } + else -> missingParam + } constructExpression.addTemplateParameter( - missingParam, + node, Template.TemplateInitialization.DEFAULT, ) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt index acd0a9ef097..76037fbb8ce 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt @@ -28,8 +28,11 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.graph.AstNode import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.builder.div import de.fraunhofer.aisec.cpg.graph.declarations.* +import de.fraunhofer.aisec.cpg.graph.duplicate import de.fraunhofer.aisec.cpg.graph.expressions.Construction +import de.fraunhofer.aisec.cpg.graph.expressions.Literal import de.fraunhofer.aisec.cpg.graph.expressions.Reference import de.fraunhofer.aisec.cpg.graph.expressions.TypeExpression import de.fraunhofer.aisec.cpg.graph.objectType @@ -142,8 +145,19 @@ fun SymbolResolver.applyMissingParams( // If default is a previously defined template argument that has been explicitly // passed templateParametersExplicitInitialization[missingParam]?.let { + // duplicate node to prevent changes through the reference + val node: AstNode = + when (it) { + is Literal<*> -> { + it.duplicate(true) + } + is TypeExpression -> { + it.duplicate(true) + } + else -> it + } constructExpression.addTemplateParameter( - it, + node, Template.TemplateInitialization.DEFAULT, ) } @@ -161,8 +175,19 @@ fun SymbolResolver.applyMissingParams( } else if (missingParam in templateParameterRealDefaultInitialization) { // Add default of template parameter to construct declaration templateParameterRealDefaultInitialization[missingParam]?.let { + // duplicate node to prevent changes through the reference + val node: AstNode = + when (it) { + is Literal<*> -> { + it.duplicate(true) + } + is TypeExpression -> { + it.duplicate(true) + } + else -> it + } constructExpression.addTemplateParameter( - it, + node, Template.TemplateInitialization.DEFAULT, ) } diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXExtraPass.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXExtraPass.kt index 471f8256652..65c3e66db40 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXExtraPass.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXExtraPass.kt @@ -34,7 +34,9 @@ import de.fraunhofer.aisec.cpg.graph.declarations.Variable import de.fraunhofer.aisec.cpg.graph.expressions.BinaryOperator import de.fraunhofer.aisec.cpg.graph.expressions.Call import de.fraunhofer.aisec.cpg.graph.expressions.Construction +import de.fraunhofer.aisec.cpg.graph.expressions.Literal import de.fraunhofer.aisec.cpg.graph.expressions.Reference +import de.fraunhofer.aisec.cpg.graph.expressions.TypeExpression import de.fraunhofer.aisec.cpg.graph.expressions.UnaryOperator import de.fraunhofer.aisec.cpg.graph.scopes.GlobalScope import de.fraunhofer.aisec.cpg.graph.types.recordDeclaration @@ -244,7 +246,14 @@ class CXXExtraPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { // understand for (i in declaration.parameters.indices) { if (candidate.parameters[i].default != null) { - declaration.parameters[i].default = candidate.parameters[i].default + // duplicate node to prevent changes through the reference + val default = + when (val candidateDefault = candidate.parameters[i].default) { + is Literal<*> -> candidateDefault.duplicate(true) + is TypeExpression -> candidateDefault.duplicate(true) + else -> candidateDefault + } + declaration.parameters[i].default = default } } } diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/AstSanityTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/AstSanityTest.kt new file mode 100644 index 00000000000..fab89dbb73e --- /dev/null +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/AstSanityTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2026, 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.helpers.SubgraphWalker +import de.fraunhofer.aisec.cpg.processing.strategy.Strategy +import de.fraunhofer.aisec.cpg.test.analyze +import java.nio.file.Path +import java.util.stream.Stream +import kotlin.io.path.Path +import kotlin.io.path.walk +import kotlin.streams.asStream +import kotlin.test.assertNotNull +import kotlin.test.assertTrue +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource + +class AstSanityTest { + + /** Tests whether a node's AST parent is the same as the parent from AST traversal. */ + @ParameterizedTest + @MethodSource("getTestFilePaths") + fun testAstParentSanity(path: Path) { + val file = path.toFile() + val result = + analyze(listOf(file), file.parentFile.toPath(), true) { + it.registerLanguage() + it.registerLanguage() + } + assertNotNull(result) + + val walker = SubgraphWalker.IterativeGraphWalker(Strategy::AST_FORWARD) + walker.registerOnNodeVisit { node, parent -> + if (parent != null) { + assertTrue("$parent <> ${node.astParent}") { parent === node.astParent } + } + } + for (c in result.components) { + walker.iterate(c) + } + } + + companion object { + @JvmStatic + fun getTestFilePaths(): Stream { + return Path("src/test/resources/") + .walk() + .filter { it.toString().endsWith("c") || it.toString().endsWith(".cpp") } + .asStream() + } + } +}