From 5c47ed54dbf5eac79c24db363a62b4be527fbab2 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Mon, 3 Mar 2025 13:16:34 +0100 Subject: [PATCH] Improvements to the configuration (#2091) --- .../cpg/concepts/ConfigurationPassTest.kt | 46 ++++- .../resources/python/conf/unused.ini | 2 + .../graph/concepts/config/Configuration.kt | 100 ++++++++--- .../concepts/config/ProvideConfigPass.kt | 165 ++++++++++++++++++ ...s.kt => IniFileConfigurationSourcePass.kt} | 100 +++++------ .../python/PythonStdLibConfigurationPass.kt | 11 +- 6 files changed, 338 insertions(+), 86 deletions(-) create mode 100644 cpg-concepts/src/integrationTest/resources/python/conf/unused.ini create mode 100644 cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/ProvideConfigPass.kt rename cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/ini/{IniFileConfigurationPass.kt => IniFileConfigurationSourcePass.kt} (52%) diff --git a/cpg-concepts/src/integrationTest/kotlin/de/fraunhofer/aisec/cpg/concepts/ConfigurationPassTest.kt b/cpg-concepts/src/integrationTest/kotlin/de/fraunhofer/aisec/cpg/concepts/ConfigurationPassTest.kt index 24aa1a247d2..2fef9577ae7 100644 --- a/cpg-concepts/src/integrationTest/kotlin/de/fraunhofer/aisec/cpg/concepts/ConfigurationPassTest.kt +++ b/cpg-concepts/src/integrationTest/kotlin/de/fraunhofer/aisec/cpg/concepts/ConfigurationPassTest.kt @@ -32,13 +32,15 @@ import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.concepts.config.Configuration import de.fraunhofer.aisec.cpg.graph.concepts.config.ConfigurationGroup import de.fraunhofer.aisec.cpg.graph.concepts.config.ConfigurationOption +import de.fraunhofer.aisec.cpg.graph.concepts.config.ConfigurationSource import de.fraunhofer.aisec.cpg.graph.concepts.config.LoadConfiguration import de.fraunhofer.aisec.cpg.graph.concepts.config.ReadConfigurationGroup import de.fraunhofer.aisec.cpg.graph.concepts.config.ReadConfigurationOption import de.fraunhofer.aisec.cpg.graph.concepts.config.RegisterConfigurationGroup import de.fraunhofer.aisec.cpg.graph.concepts.config.RegisterConfigurationOption import de.fraunhofer.aisec.cpg.graph.statements.expressions.SubscriptExpression -import de.fraunhofer.aisec.cpg.passes.concepts.config.ini.IniFileConfigurationPass +import de.fraunhofer.aisec.cpg.passes.concepts.config.ProvideConfigPass +import de.fraunhofer.aisec.cpg.passes.concepts.config.ini.IniFileConfigurationSourcePass import de.fraunhofer.aisec.cpg.passes.concepts.config.python.PythonStdLibConfigurationPass import de.fraunhofer.aisec.cpg.test.analyze import java.io.File @@ -46,6 +48,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertSame +import kotlin.test.assertTrue class ConfigurationPassTest { @Test @@ -55,8 +58,9 @@ class ConfigurationPassTest { analyze(listOf(), topLevel.toPath(), true) { it.registerLanguage() it.registerLanguage() - it.registerPass() + it.registerPass() it.registerPass() + it.registerPass() it.softwareComponents( mutableMapOf( "conf" to listOf(topLevel.resolve("conf")), @@ -76,6 +80,20 @@ class ConfigurationPassTest { val conf = result.conceptNodes.filterIsInstance().singleOrNull() assertNotNull(conf, "There should be a single configuration node") + assertEquals(11, conf.allOps.size, "There should be 11 overall ops in the configuration") + + val confSources = result.conceptNodes.filterIsInstance() + assertEquals(3, confSources.size, "There should be three configuration source nodes") + + confSources + .filter { it.name.toString() != "unused.ini" } + .forEach { + assertEquals( + 5, + it.allOps.size, + "There should be 5 overall ops in each configuration source (except for the unused one)", + ) + } val loadConfig = result.operationNodes.filterIsInstance().singleOrNull() assertNotNull(loadConfig) @@ -89,6 +107,11 @@ class ConfigurationPassTest { val sslGroup = groups["ssl"] assertNotNull(sslGroup) + assertEquals( + 2, + sslGroup.ops.size, + "There should be two ops in the ssl group (register and read)", + ) val readGroupOps = result.operationNodes.filterIsInstance() assertEquals( @@ -117,6 +140,17 @@ class ConfigurationPassTest { readOptionOps.associate { Pair(it.name.toString(), it.option) }, ) + readOptionOps + .map { it.underlyingNode } + .forEach { + // Prev DFG should include the option + assertNotNull(it) + assertTrue( + it.prevDFG.any { dfg -> dfg is ConfigurationOption }, + "Prev DFG of $it should include the option", + ) + } + val registerOptionOps = result.operationNodes.filterIsInstance() assertEquals( @@ -134,5 +168,13 @@ class ConfigurationPassTest { val sslEnabled = result.refs["ssl_enabled"] assertNotNull(sslEnabled) assertEquals(setOf("true", "false"), sslEnabled.evaluate(MultiValueEvaluator())) + + val unused = confSources["unused.ini"] + assertNotNull(unused) + assertEquals(0, unused.allOps.size, "There should be no ops in the unused configuration") + + val verifySSL = unused.groups["DEFAULT"]?.options["verify_ssl"] + assertNotNull(verifySSL) + assertEquals(setOf("true"), verifySSL.evaluate(MultiValueEvaluator())) } } diff --git a/cpg-concepts/src/integrationTest/resources/python/conf/unused.ini b/cpg-concepts/src/integrationTest/resources/python/conf/unused.ini new file mode 100644 index 00000000000..ebdc440150b --- /dev/null +++ b/cpg-concepts/src/integrationTest/resources/python/conf/unused.ini @@ -0,0 +1,2 @@ +[DEFAULT] +verify_ssl = true diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/config/Configuration.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/config/Configuration.kt index b4bc0d26257..cd3a604e6ec 100644 --- a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/config/Configuration.kt +++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/config/Configuration.kt @@ -30,6 +30,7 @@ import de.fraunhofer.aisec.cpg.graph.concepts.Concept import de.fraunhofer.aisec.cpg.graph.concepts.Operation import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression /** @@ -44,6 +45,17 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression */ class Configuration(underlyingNode: Node) : Concept(underlyingNode = underlyingNode) { var groups: MutableList = mutableListOf() + + /** + * The individual operations that target parts of the configuration are assigned to + * [Concept.ops] of their respective concept. For example a [ReadConfigurationGroup] is part of + * the [ConfigurationGroup]'s ops. This property returns all operations of all groups and + * options as well as the ones targeting the complete configuration. + */ + val allOps: Set + get() { + return ops + groups.flatMap { it.ops + it.options.flatMap { option -> option.ops } } + } } /** @@ -110,10 +122,9 @@ class LoadConfiguration( */ class ReadConfigurationGroup( underlyingNode: Node, - conf: Configuration, /** The config group that is being read with this operation. */ var group: ConfigurationGroup, -) : ConfigurationOperation(underlyingNode = underlyingNode, concept = conf) { +) : ConfigurationOperation(underlyingNode = underlyingNode, concept = group) { init { name = group.name } @@ -125,10 +136,9 @@ class ReadConfigurationGroup( */ class ReadConfigurationOption( underlyingNode: Node, - conf: Configuration, /** The config option that is being read with this operation. */ var option: ConfigurationOption, -) : ConfigurationOperation(underlyingNode = underlyingNode, concept = conf) { +) : ConfigurationOperation(underlyingNode = underlyingNode, concept = option) { init { name = option.name } @@ -146,10 +156,9 @@ class ReadConfigurationOption( */ class RegisterConfigurationGroup( underlyingNode: Node, - conf: Configuration, /** The config group that is being registered with this operation. */ var group: ConfigurationGroup, -) : ConfigurationOperation(underlyingNode = underlyingNode, concept = conf) { +) : ConfigurationOperation(underlyingNode = underlyingNode, concept = group) { init { name = group.name } @@ -167,19 +176,19 @@ class RegisterConfigurationGroup( */ class RegisterConfigurationOption( underlyingNode: Node, - conf: Configuration, /** The config option that is being registered with this operation. */ var option: ConfigurationOption, /** An optional default value of the option. */ var defaultValue: Node? = null, -) : ConfigurationOperation(underlyingNode = underlyingNode, concept = conf) { +) : ConfigurationOperation(underlyingNode = underlyingNode, concept = option) { init { name = option.name } } /** - * Represents an operation to provide a [Configuration], e.g., in the form of a configuration file. + * Represents an operation to provide a [Configuration], e.g., in the form of a configuration file + * (through a [ConfigurationSource]). * * When the configuration file is loaded, a [LoadConfiguration] operation would be found in the code * component (matching the configuration file's name in [LoadConfiguration.fileExpression]) and the @@ -187,36 +196,87 @@ class RegisterConfigurationOption( * * But also other sources of configuration could be represented by a [ProvideConfiguration] * operation, such as environment variables or command-line arguments. + * + * Note: The [ProvideConfiguration] operation is part of the [ConfigurationSource.ops] and not of + * the [Configuration.ops] as its an operation of the source, not the target. */ -class ProvideConfiguration(underlyingNode: Node, var conf: Configuration) : - ConfigurationOperation(underlyingNode = underlyingNode, concept = conf) +class ProvideConfiguration( + underlyingNode: Node, + var source: ConfigurationSource, + var conf: Configuration, +) : ConfigurationOperation(underlyingNode = underlyingNode, concept = source) /** - * Represents an operation to provide a [ConfigurationGroup]. For example, when loading an INI file - * with our INI file frontend, each section is presented as a [RecordDeclaration]. This record - * declaration would "provide" the configuration group. + * Represents an operation to provide a [ConfigurationGroup]. It connects a + * [ConfigurationGroupSource] with a [ConfigurationGroup]. */ class ProvideConfigurationGroup( underlyingNode: Node, - conf: Configuration, + var source: ConfigurationGroupSource, var group: ConfigurationGroup, -) : ConfigurationOperation(underlyingNode = underlyingNode, concept = conf) { +) : ConfigurationOperation(underlyingNode = underlyingNode, concept = source) { init { name = group.name } } /** - * Represents an operation to provide a [ConfigurationOption]. For example, when loading an INI file - * with our INI file frontend, each key-value pair is presented as a [FieldDeclaration]. This field - * declaration would "provide" the configuration option. + * Represents an operation to provide a [ConfigurationOption]. It connects a + * [ConfigurationOptionSource] with a [ConfigurationOption]. */ class ProvideConfigurationOption( underlyingNode: Node, + var source: ConfigurationOptionSource, var option: ConfigurationOption, var value: Node?, -) : ConfigurationOperation(underlyingNode = underlyingNode, concept = option.group.conf) { +) : ConfigurationOperation(underlyingNode = underlyingNode, concept = source) { init { name = option.name } } + +/** + * Represents a possible source for a configuration. For example, when loading an INI file with our + * INI file frontend, the whole file would be represented as a [TranslationUnitDeclaration]. This + * translation unit declaration would be the source of the configuration. + */ +class ConfigurationSource(underlyingNode: Node) : Concept(underlyingNode = underlyingNode) { + val groups: MutableList = mutableListOf() + + /** + * The individual operations that target parts of the configuration are assigned to + * [Concept.ops] of their respective concept. For example a [ReadConfigurationGroup] is part of + * the [ConfigurationGroup]'s ops. This property returns all operations of all groups and + * options as well as the ones targeting the complete configuration. + */ + val allOps: Set + get() { + return ops + groups.flatMap { it.ops + it.options.flatMap { option -> option.ops } } + } +} + +/** + * Represents a possible group source for a configuration group. For example, when loading an INI + * file with our INI file frontend, each section is presented as a [RecordDeclaration]. This record + * declaration would be the source of the configuration group. + */ +class ConfigurationGroupSource(underlyingNode: Node, conf: ConfigurationSource) : + Concept(underlyingNode = underlyingNode) { + val options: MutableList = mutableListOf() + + init { + conf.groups += this + } +} + +/** + * Represents a possible option source for a configuration option. For example, when loading an INI + * file with our INI file frontend, each key-value pair is presented as a [FieldDeclaration]. This + * field declaration would be the source to the configuration option. + */ +class ConfigurationOptionSource(underlyingNode: Node, var group: ConfigurationGroupSource) : + Concept(underlyingNode = underlyingNode) { + init { + group.options += this + } +} diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/ProvideConfigPass.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/ProvideConfigPass.kt new file mode 100644 index 00000000000..81703928a3b --- /dev/null +++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/ProvideConfigPass.kt @@ -0,0 +1,165 @@ +/* + * 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.passes.concepts.config + +import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.conceptNodes +import de.fraunhofer.aisec.cpg.graph.concepts.config.Configuration +import de.fraunhofer.aisec.cpg.graph.concepts.config.ConfigurationGroup +import de.fraunhofer.aisec.cpg.graph.concepts.config.ConfigurationGroupSource +import de.fraunhofer.aisec.cpg.graph.concepts.config.ConfigurationOperation +import de.fraunhofer.aisec.cpg.graph.concepts.config.ConfigurationOptionSource +import de.fraunhofer.aisec.cpg.graph.concepts.config.ConfigurationSource +import de.fraunhofer.aisec.cpg.graph.concepts.config.LoadConfiguration +import de.fraunhofer.aisec.cpg.graph.concepts.config.ProvideConfiguration +import de.fraunhofer.aisec.cpg.graph.concepts.config.ProvideConfigurationGroup +import de.fraunhofer.aisec.cpg.graph.concepts.config.ProvideConfigurationOption +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.operationNodes +import de.fraunhofer.aisec.cpg.graph.translationResult +import de.fraunhofer.aisec.cpg.helpers.Util +import de.fraunhofer.aisec.cpg.passes.concepts.ConceptPass +import de.fraunhofer.aisec.cpg.passes.concepts.config.python.stringValues + +/** + * This is a generic pass that is responsible for creating [ProvideConfiguration] nodes based on the + * configuration sources found in the graph. It connects a [ConfigurationSource] with a matching + * [Configuration]. + */ +class ProvideConfigPass(ctx: TranslationContext) : ConceptPass(ctx) { + override fun handleNode(node: Node, tu: TranslationUnitDeclaration) { + when (node) { + is TranslationUnitDeclaration -> handleTranslationUnit(node) + } + } + + private fun handleTranslationUnit( + tu: TranslationUnitDeclaration + ): List { + // Loop through all configuration sources + return tu.conceptNodes.filterIsInstance().flatMap { source -> + // Find all LoadConfigurationFile operations that match the INI file name + val loadConfigOps = + tu.translationResult + ?.operationNodes + ?.filterIsInstance() + ?.filter { + it.fileExpression.stringValues?.contains(source.name.toString()) == true + } + + // And create a ProvideConfiguration node for each of them + loadConfigOps?.flatMap { handleConfiguration(source, it.conf, tu, it) } ?: listOf() + } + } + + private fun handleConfiguration( + source: ConfigurationSource, + conf: Configuration, + tu: TranslationUnitDeclaration, + configuration: LoadConfiguration, + ): MutableList { + val ops = mutableListOf() + + // Loop through all groups and options and create ProvideConfigurationGroup and + // ProvideConfigurationOption nodes + ops += source.groups.mapNotNull { handleConfigurationGroup(conf, it) }.flatten() + + ops += + ProvideConfiguration(underlyingNode = tu, conf = configuration.conf, source = source) + .also { it.name = source.name } + + return ops + } + + private fun handleConfigurationGroup( + conf: Configuration, + source: ConfigurationGroupSource, + ): MutableList? { + val ops = mutableListOf() + + val sourceUnderlying = source.underlyingNode ?: return null + val group = conf.groups.singleOrNull { it.name.localName == source.name.localName } + if (group == null) { + Util.warnWithFileLocation( + sourceUnderlying, + log, + "Could not find configuration group for source {}", + source.name.localName, + ) + return null + } + + val op = + ProvideConfigurationGroup( + underlyingNode = sourceUnderlying, + group = group, + source = source, + ) + .also { it.name = source.name } + ops += op + + // Add an incoming DFG edge from the source + group.prevDFGEdges.add(source) + + // Continue with the options + ops += source.options.mapNotNull { handleConfigurationOption(group, it) } + + return ops + } + + private fun handleConfigurationOption( + group: ConfigurationGroup, + source: ConfigurationOptionSource, + ): ConfigurationOperation? { + val sourceUnderlying = source.underlyingNode ?: return null + val option = group.options.singleOrNull { it.name.localName == source.name.localName } + + if (option == null) { + Util.warnWithFileLocation( + sourceUnderlying, + log, + "Could not find configuration option for source {}", + sourceUnderlying.name.localName, + ) + return null + } + + val op = + ProvideConfigurationOption( + underlyingNode = sourceUnderlying, + option = option, + value = sourceUnderlying, + source = source, + ) + .also { it.name = source.name } + + // Add an incoming DFG edge from the source + option.prevDFGEdges.add(source) + + return op + } +} diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/ini/IniFileConfigurationPass.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/ini/IniFileConfigurationSourcePass.kt similarity index 52% rename from cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/ini/IniFileConfigurationPass.kt rename to cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/ini/IniFileConfigurationSourcePass.kt index 5e35d225eef..7aa61f7e4c5 100644 --- a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/ini/IniFileConfigurationPass.kt +++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/ini/IniFileConfigurationSourcePass.kt @@ -27,32 +27,25 @@ package de.fraunhofer.aisec.cpg.passes.concepts.config.ini import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.conceptNodes import de.fraunhofer.aisec.cpg.graph.concepts.config.* import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration -import de.fraunhofer.aisec.cpg.graph.operationNodes -import de.fraunhofer.aisec.cpg.graph.translationResult import de.fraunhofer.aisec.cpg.helpers.Util.warnWithFileLocation import de.fraunhofer.aisec.cpg.passes.ImportResolver import de.fraunhofer.aisec.cpg.passes.concepts.ConceptPass -import de.fraunhofer.aisec.cpg.passes.concepts.config.python.PythonStdLibConfigurationPass -import de.fraunhofer.aisec.cpg.passes.concepts.config.python.stringValues +import de.fraunhofer.aisec.cpg.passes.concepts.config.ProvideConfigPass import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn +import de.fraunhofer.aisec.cpg.passes.configuration.ExecuteBefore import kotlin.collections.singleOrNull /** - * This pass is responsible for creating [ConfigurationOperation] nodes based on the INI file - * frontend. - * - * First, it looks for [LoadConfiguration] operations that match the INI file name to retrieve a - * [Configuration] data structure, creating a [ProvideConfiguration] node. Then, it creates - * [ProvideConfigurationGroup] operations for each section in an INI file, and - * [ProvideConfigurationOption] operations for each option in a section. + * This pass is responsible for creating [ConfigurationSource] nodes based on the INI file frontend. */ @DependsOn(ImportResolver::class) -@DependsOn(PythonStdLibConfigurationPass::class, softDependency = true) -class IniFileConfigurationPass(ctx: TranslationContext) : ConceptPass(ctx) { +@ExecuteBefore(ProvideConfigPass::class) +class IniFileConfigurationSourcePass(ctx: TranslationContext) : ConceptPass(ctx) { override fun handleNode(node: Node, tu: TranslationUnitDeclaration) { // Since we cannot directly depend on the ini frontend, we have to check the language here // based on the node's language. @@ -67,82 +60,69 @@ class IniFileConfigurationPass(ctx: TranslationContext) : ConceptPass(ctx) { } } - private fun handleTranslationUnit(tu: TranslationUnitDeclaration): List? { - // Find all LoadConfigurationFile operations that match the INI file name - val loadConfigOps = - tu.translationResult?.operationNodes?.filterIsInstance()?.filter { - it.fileExpression.stringValues?.contains(tu.name.toString()) == true - } - - // And create a ProvideConfiguration node for each of them - return loadConfigOps?.map { ProvideConfiguration(underlyingNode = tu, conf = it.conf) } + private fun handleTranslationUnit(tu: TranslationUnitDeclaration): ConfigurationSource { + return ConfigurationSource(underlyingNode = tu).also { it.name = tu.name } } /** * Translates a [RecordDeclaration], which represents a section in an INI file, into a - * [ProvideConfigurationGroup] node. + * [ConfigurationGroupSource] node. */ private fun handleRecordDeclaration( record: RecordDeclaration, tu: TranslationUnitDeclaration, - ): List { - return tu.operationNodes.filterIsInstance().mapNotNull { - val group = it.conf.groups.singleOrNull { it.name.localName == record.name.localName } - if (group == null) { - warnWithFileLocation( - record, - log, - "Could not find configuration group {}", - record.name.localName, - ) - return@mapNotNull null - } + ): ConfigurationGroupSource? { + val conf = tu.conceptNodes.filterIsInstance().singleOrNull() + if (conf == null) { + warnWithFileLocation( + conf, + log, + "Could not find configuration for {}", + record.name.localName, + ) + return null + } - val op = - ProvideConfigurationGroup(underlyingNode = record, conf = it.conf, group = group) + val group = + ConfigurationGroupSource(underlyingNode = record, conf = conf).also { + it.name = record.name + } - // Add an incoming DFG edge to the group - group.prevDFGEdges.add(record) + // Add an incoming DFG edge from the record + group.prevDFGEdges.add(record) - op - } + return group } /** * Translates a [FieldDeclaration], which represents an option in an INI file, into a - * [ProvideConfigurationOption] node. + * [ConfigurationOptionSource] node. */ - private fun handleFieldDeclaration(field: FieldDeclaration): ProvideConfigurationOption? { - val option = + private fun handleFieldDeclaration(field: FieldDeclaration): ConfigurationOptionSource? { + val group = field.astParent - ?.operationNodes - ?.filterIsInstance() + ?.conceptNodes + ?.filterIsInstance() ?.singleOrNull() - ?.group - ?.options - ?.singleOrNull { it.name.localName == field.name.localName } - if (option == null) { + if (group == null) { warnWithFileLocation( field, log, - "Could not find configuration option {}", + "Could not find configuration group for {}", field.name.localName, ) return null } - val op = - ProvideConfigurationOption( - underlyingNode = field, - option = option, - value = field.initializer, - ) - .also { it.name = field.name } + val option = + ConfigurationOptionSource(underlyingNode = field, group = group).also { + it.name = field.name + } - // Add an incoming DFG edge to the option + // Add an incoming DFG edge from the field option.prevDFGEdges.add(field) - return op + return option } } diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/python/PythonStdLibConfigurationPass.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/python/PythonStdLibConfigurationPass.kt index d81bef8f1a9..e8dcef508dd 100644 --- a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/python/PythonStdLibConfigurationPass.kt +++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/python/PythonStdLibConfigurationPass.kt @@ -44,7 +44,9 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.SubscriptExpression import de.fraunhofer.aisec.cpg.helpers.Util.warnWithFileLocation import de.fraunhofer.aisec.cpg.passes.SymbolResolver import de.fraunhofer.aisec.cpg.passes.concepts.ConceptPass +import de.fraunhofer.aisec.cpg.passes.concepts.config.ProvideConfigPass import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn +import de.fraunhofer.aisec.cpg.passes.configuration.ExecuteBefore /** * This pass is responsible for creating [ConfigurationOperation] nodes based on the @@ -52,6 +54,7 @@ import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn * standard library. */ @DependsOn(SymbolResolver::class) +@ExecuteBefore(ProvideConfigPass::class) class PythonStdLibConfigurationPass(ctx: TranslationContext) : ConceptPass(ctx) { override fun handleNode(node: Node, tu: TranslationUnitDeclaration) { when (node) { @@ -145,11 +148,11 @@ class PythonStdLibConfigurationPass(ctx: TranslationContext) : ConceptPass(ctx) if (group == null) { // If it does not exist, we create it and implicitly add a registration operation group = ConfigurationGroup(sub, conf = conf).also { it.name = Name(name) }.implicit() - val op = RegisterConfigurationGroup(sub, conf = conf, group = group).implicit() + val op = RegisterConfigurationGroup(sub, group = group).implicit() ops += op } - val op = ReadConfigurationGroup(sub, conf = conf, group = group) + val op = ReadConfigurationGroup(sub, group = group) ops += op // Add an incoming DFG from the option group @@ -186,11 +189,11 @@ class PythonStdLibConfigurationPass(ctx: TranslationContext) : ConceptPass(ctx) ConfigurationOption(sub, group = group, key = sub) .also { it.name = group.name.fqn(name) } .implicit() - val op = RegisterConfigurationOption(sub, conf = group.conf, option = option).implicit() + val op = RegisterConfigurationOption(sub, option = option).implicit() ops += op } - val op = ReadConfigurationOption(sub, conf = group.conf, option = option) + val op = ReadConfigurationOption(sub, option = option) ops += op // Add an incoming DFG from the option