-
Notifications
You must be signed in to change notification settings - Fork 62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
initial version of INI file parser #1858
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,3 +28,5 @@ cpg-neo4j @peckto | |
|
||
build.gradle.kts @oxisto | ||
.github @oxisto | ||
|
||
cpg-language-configfiles @maximiliankaul |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/* | ||
* Copyright (c) 2021, 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. | ||
* | ||
* $$$$$$\ $$$$$$$\ $$$$$$\ | ||
* $$ __$$\ $$ __$$\ $$ __$$\ | ||
* $$ / \__|$$ | $$ |$$ / \__| | ||
* $$ | $$$$$$$ |$$ |$$$$\ | ||
* $$ | $$ ____/ $$ |\_$$ | | ||
* $$ | $$\ $$ | $$ | $$ | | ||
* \$$$$$ |$$ | \$$$$$ | | ||
* \______/ \__| \______/ | ||
* | ||
*/ | ||
plugins { | ||
id("cpg.frontend-conventions") | ||
} | ||
|
||
publishing { | ||
publications { | ||
named<MavenPublication>("cpg-language-configfiles") { | ||
pom { | ||
artifactId = "cpg-language-configfiles" | ||
name.set("Code Property Graph - Configfiles Frontend") | ||
description.set("A configuration file (ini/yaml/toml/...) frontend for the CPG") | ||
} | ||
} | ||
} | ||
} | ||
|
||
dependencies { | ||
// ini4j for parsing ini files | ||
implementation(libs.ini4j) | ||
|
||
// to evaluate some test cases | ||
testImplementation(project(":cpg-analysis")) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
/* | ||
* 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.frontend.configfiles | ||
|
||
import de.fraunhofer.aisec.cpg.TranslationContext | ||
import de.fraunhofer.aisec.cpg.frontends.Language | ||
import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend | ||
import de.fraunhofer.aisec.cpg.frontends.TranslationException | ||
import de.fraunhofer.aisec.cpg.graph.* | ||
import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration | ||
import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration | ||
import de.fraunhofer.aisec.cpg.graph.types.Type | ||
import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation | ||
import java.io.File | ||
import java.io.FileInputStream | ||
import org.ini4j.Ini | ||
import org.ini4j.Profile | ||
|
||
/** | ||
* The INI file frontend. This frontend utilizes the [ini4j library](https://ini4j.sourceforge.net/) | ||
* to parse the config file. The result consists of | ||
* - a [TranslationUnitDeclaration] wrapping the entire result | ||
* - a [de.fraunhofer.aisec.cpg.graph.declarations.NamespaceDeclaration] wrapping the INI file and | ||
* thus preventing collisions with other symbols which might have the same name | ||
* - a [RecordDeclaration] per `Section` (a section refers to a block of INI values marked with a | ||
* line `[SectionName]`) | ||
* - a [de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration] per entry in a section. The | ||
* [de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration.name] matches the `entry`s `name` | ||
* field and the [de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration.initializer] is set | ||
* to a [Literal] with the corresponding `entry`s `value`. | ||
* | ||
* Note: | ||
* - the "ini4j" library does not provide any super type for all nodes. Thus, the frontend accepts | ||
* `Any` | ||
* - [typeOf] has to be implemented, but as there are no types always returns the builtin `string` | ||
* type | ||
* - [codeOf] has to accept `Any` (because of the limitations stated above) and simply returns | ||
* `.toString()` | ||
* - [locationOf] always returns `null` as the "ini4j" library does not provide any means of getting | ||
* a location given a node | ||
* - [setComment] TODO what's this? | ||
*/ | ||
class IniFilesFrontend(language: Language<IniFilesFrontend>, ctx: TranslationContext) : | ||
LanguageFrontend<Any, Any?>(language, ctx) { | ||
|
||
override fun parse(file: File): TranslationUnitDeclaration { | ||
val ini = Ini() | ||
try { | ||
ini.load(FileInputStream(file)) | ||
} catch (ex: Exception) { | ||
throw TranslationException("Parsing failed with exception: $ex") | ||
} | ||
val tud = newTranslationUnitDeclaration(name = file.name, rawNode = ini) | ||
scopeManager.resetToGlobal(tud) | ||
val nsd = newNamespaceDeclaration(name = file.name, rawNode = ini) | ||
scopeManager.addDeclaration(nsd) | ||
scopeManager.enterScope(nsd) | ||
|
||
ini.values.forEach { handleSection(it) } | ||
|
||
scopeManager.enterScope(nsd) | ||
return tud | ||
} | ||
|
||
/** | ||
* Translates a `Section` into a [RecordDeclaration] and handles all `entries` using | ||
* [handleEntry]. | ||
*/ | ||
private fun handleSection(section: Profile.Section) { | ||
val record = newRecordDeclaration(name = section.name, kind = "section", rawNode = section) | ||
scopeManager.addDeclaration(record) | ||
scopeManager.enterScope(record) | ||
section.entries.forEach { handleEntry(it) } | ||
scopeManager.leaveScope(record) | ||
} | ||
|
||
/** | ||
* Translates an `MutableEntry` to a new | ||
* [de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration] with the | ||
* [de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration.initializer] being set to the | ||
* `entry`s value. | ||
*/ | ||
private fun handleEntry(entry: MutableMap.MutableEntry<String?, String?>) { | ||
val field = | ||
newFieldDeclaration(name = entry.key, type = primitiveType("string"), rawNode = entry) | ||
.apply { initializer = newLiteral(value = entry.value, rawNode = entry) } | ||
scopeManager.addDeclaration(field) | ||
} | ||
|
||
override fun typeOf(type: Any?): Type { | ||
return primitiveType("string") | ||
} | ||
|
||
override fun codeOf(astNode: Any): String? { | ||
return astNode.toString() | ||
} | ||
|
||
override fun locationOf(astNode: Any): PhysicalLocation? { | ||
return null // currently, the line number / column cannot be accessed given an Ini object | ||
} | ||
|
||
override fun setComment(node: Node, astNode: Any) { | ||
TODO("Not yet implemented") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/* | ||
* 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.frontend.configfiles | ||
|
||
import de.fraunhofer.aisec.cpg.frontends.Language | ||
import de.fraunhofer.aisec.cpg.graph.types.StringType | ||
import de.fraunhofer.aisec.cpg.graph.types.Type | ||
import kotlin.reflect.KClass | ||
|
||
/** | ||
* A simple language representing classical [INI files](https://en.wikipedia.org/wiki/INI_file). As | ||
* there are conflicting definitions of an INI file, we go with: | ||
* - the file extension is `.ini` | ||
* - all entries live in a unique `section` | ||
* - all `key`s are unique per section | ||
* - the file is accepted by the [ini4j library](https://ini4j.sourceforge.net/) | ||
*/ | ||
class IniFilesLanguage : Language<IniFilesFrontend>() { | ||
override val fileExtensions = listOf("ini") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ".conf" extension, too |
||
override val namespaceDelimiter: String = "" // no such thing | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. path separator |
||
|
||
@Transient override val frontend: KClass<out IniFilesFrontend> = IniFilesFrontend::class | ||
override val builtInTypes: Map<String, Type> = | ||
mapOf("string" to StringType("string", language = this)) // everything is a string | ||
|
||
override val compoundAssignmentOperators: Set<String> = emptySet() // no such thing | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/* | ||
* 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.frontend.configfiles | ||
|
||
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.get | ||
import de.fraunhofer.aisec.cpg.graph.records | ||
import de.fraunhofer.aisec.cpg.test.BaseTest | ||
import de.fraunhofer.aisec.cpg.test.analyzeAndGetFirstTU | ||
import de.fraunhofer.aisec.cpg.test.assertLiteralValue | ||
import java.nio.file.Path | ||
import kotlin.test.Test | ||
import kotlin.test.assertEquals | ||
import kotlin.test.assertIs | ||
|
||
class IniFilesTest : BaseTest() { | ||
|
||
@Test | ||
fun gettingStartedWithINIConfigfiles() { | ||
val topLevel = Path.of("src", "test", "resources") | ||
val tu = | ||
analyzeAndGetFirstTU(listOf(topLevel.resolve("config.ini").toFile()), topLevel, true) { | ||
it.registerLanguage<IniFilesLanguage>() | ||
} | ||
assertIs<TranslationUnitDeclaration>(tu) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add namespace test |
||
assertEquals(2, tu.records.size, "Expected two records") | ||
|
||
val ownerRecord = tu.records["owner"] | ||
assertIs<RecordDeclaration>(ownerRecord) | ||
assertEquals(2, ownerRecord.fields.size, "Expected two fields") | ||
|
||
val nameField = ownerRecord.fields["name"] | ||
assertIs<FieldDeclaration>(nameField) | ||
assertLiteralValue("John Doe", nameField.initializer) | ||
|
||
val organizationField = ownerRecord.fields["organization"] | ||
assertIs<FieldDeclaration>(organizationField) | ||
assertLiteralValue("Acme Widgets Inc.", organizationField.initializer) | ||
|
||
val databaseRecord = tu.records["database"] | ||
assertIs<RecordDeclaration>(databaseRecord) | ||
assertEquals(3, databaseRecord.fields.size, "Expected three fields") | ||
|
||
val serverField = databaseRecord.fields["server"] | ||
assertIs<FieldDeclaration>(serverField) | ||
assertLiteralValue("192.0.2.62", serverField.initializer) | ||
|
||
val portField = databaseRecord.fields["port"] | ||
assertIs<FieldDeclaration>(portField) | ||
assertLiteralValue("143", portField.initializer) | ||
|
||
val fileField = databaseRecord.fields["file"] | ||
assertIs<FieldDeclaration>(fileField) | ||
assertLiteralValue("\"payroll.dat\"", fileField.initializer) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
; Wikipedia example https://en.wikipedia.org/wiki/INI_file | ||
|
||
; last modified 1 April 2001 by John Doe | ||
[owner] | ||
name = John Doe | ||
organization = Acme Widgets Inc. | ||
|
||
[database] | ||
; use IP address in case network name resolution is not working | ||
server = 192.0.2.62 | ||
port = 143 | ||
file = "payroll.dat" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return entire file first line number to last line number