Skip to content

Commit e043b6b

Browse files
committed
Added Kotlin scripting support for queries
1 parent a031e04 commit e043b6b

File tree

9 files changed

+228
-0
lines changed

9 files changed

+228
-0
lines changed

buildSrc/src/main/kotlin/cpg.formatting-conventions.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ val headerWithHashes = """#
118118

119119
spotless {
120120
kotlin {
121+
targetExclude("**/*.query.kts")
121122
ktfmt().kotlinlangStyle()
122123
licenseHeader(headerWithStars).yearSeparator(" - ")
123124
}

codyze-core/build.gradle.kts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,20 @@ dependencies {
4343
api(libs.sarif4k)
4444
implementation(libs.clikt)
4545
implementation(projects.cpgCore)
46+
api(projects.cpgAnalysis)
47+
testImplementation(kotlin("test"))
48+
49+
// Script definition
50+
api("org.jetbrains.kotlin:kotlin-scripting-common")
51+
api("org.jetbrains.kotlin:kotlin-scripting-jvm")
52+
53+
// Scripting host
54+
api("org.jetbrains.kotlin:kotlin-scripting-jvm-host")
55+
56+
// We depend on the Python frontend for the integration tests, but the frontend is only available if enabled.
57+
// If it's not available, the integration tests fail (which is ok). But if we would directly reference the
58+
// project here, the build system would fail any task since it will not find a non-enabled project.
59+
findProject(":cpg-language-python")?.also {
60+
integrationTestImplementation(it)
61+
}
4662
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright (c) 2025, Fraunhofer AISEC. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* $$$$$$\ $$$$$$$\ $$$$$$\
17+
* $$ __$$\ $$ __$$\ $$ __$$\
18+
* $$ / \__|$$ | $$ |$$ / \__|
19+
* $$ | $$$$$$$ |$$ |$$$$\
20+
* $$ | $$ ____/ $$ |\_$$ |
21+
* $$ | $$\ $$ | $$ | $$ |
22+
* \$$$$$ |$$ | \$$$$$ |
23+
* \______/ \__| \______/
24+
*
25+
*/
26+
package codyze
27+
28+
import de.fraunhofer.aisec.codyze.evalQuery
29+
import de.fraunhofer.aisec.cpg.frontends.python.PythonLanguage
30+
import de.fraunhofer.aisec.cpg.graph.*
31+
import de.fraunhofer.aisec.cpg.query.QueryTree
32+
import de.fraunhofer.aisec.cpg.test.analyze
33+
import java.io.File
34+
import kotlin.io.path.Path
35+
import kotlin.script.experimental.api.ResultValue
36+
import kotlin.script.experimental.api.valueOrNull
37+
import kotlin.test.Test
38+
import kotlin.test.assertEquals
39+
import kotlin.test.assertNotNull
40+
41+
class QueryHostTest {
42+
@Test
43+
fun testQuery() {
44+
val topLevel = Path("src/integrationTest/resources")
45+
val result =
46+
analyze(listOf(topLevel.resolve("simple.py").toFile()), topLevel, true) {
47+
it.registerLanguage<PythonLanguage>()
48+
}
49+
val scriptResult = result.evalQuery(File("src/integrationTest/resources/simple.query.kts"))
50+
val evalResult = (scriptResult.valueOrNull()?.returnValue as? ResultValue.Value)?.value
51+
52+
val queryTree = evalResult as? QueryTree<*>
53+
assertNotNull(queryTree)
54+
assertEquals(true, queryTree.value)
55+
}
56+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
def foo() -> int:
2+
bar = 42
3+
return bar
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (c) 2025, Fraunhofer AISEC. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* $$$$$$\ $$$$$$$\ $$$$$$\
17+
* $$ __$$\ $$ __$$\ $$ __$$\
18+
* $$ / \__|$$ | $$ |$$ / \__|
19+
* $$ | $$$$$$$ |$$ |$$$$\
20+
* $$ | $$ ____/ $$ |\_$$ |
21+
* $$ | $$\ $$ | $$ | $$ |
22+
* \$$$$$ |$$ | \$$$$$ |
23+
* \______/ \__| \______/
24+
*
25+
*/
26+
import de.fraunhofer.aisec.cpg.graph.Node
27+
import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
28+
29+
val queryResult = result.allExtended<FunctionDeclaration>(mustSatisfy = { QueryTree(it.name.localName) eq "foo" })
30+
queryResult
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright (c) 2025, Fraunhofer AISEC. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* $$$$$$\ $$$$$$$\ $$$$$$\
17+
* $$ __$$\ $$ __$$\ $$ __$$\
18+
* $$ / \__|$$ | $$ |$$ / \__|
19+
* $$ | $$$$$$$ |$$ |$$$$\
20+
* $$ | $$ ____/ $$ |\_$$ |
21+
* $$ | $$\ $$ | $$ | $$ |
22+
* \$$$$$ |$$ | \$$$$$ |
23+
* \______/ \__| \______/
24+
*
25+
*/
26+
package de.fraunhofer.aisec.codyze
27+
28+
import de.fraunhofer.aisec.cpg.TranslationResult
29+
import java.io.File
30+
import kotlin.script.experimental.api.*
31+
import kotlin.script.experimental.host.toScriptSource
32+
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
33+
import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate
34+
import kotlin.script.experimental.jvmhost.createJvmEvaluationConfigurationFromTemplate
35+
36+
fun TranslationResult.evalQuery(scriptFile: File): ResultWithDiagnostics<EvaluationResult> {
37+
val evalCtx = QueryScriptContext(this)
38+
39+
val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<QueryScript>()
40+
val evaluationConfiguration =
41+
createJvmEvaluationConfigurationFromTemplate<QueryScript> { implicitReceivers(evalCtx) }
42+
val scriptResult =
43+
BasicJvmScriptingHost()
44+
.eval(scriptFile.toScriptSource(), compilationConfiguration, evaluationConfiguration)
45+
46+
println(scriptResult)
47+
48+
return scriptResult
49+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright (c) 2025, Fraunhofer AISEC. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* $$$$$$\ $$$$$$$\ $$$$$$\
17+
* $$ __$$\ $$ __$$\ $$ __$$\
18+
* $$ / \__|$$ | $$ |$$ / \__|
19+
* $$ | $$$$$$$ |$$ |$$$$\
20+
* $$ | $$ ____/ $$ |\_$$ |
21+
* $$ | $$\ $$ | $$ | $$ |
22+
* \$$$$$ |$$ | \$$$$$ |
23+
* \______/ \__| \______/
24+
*
25+
*/
26+
package de.fraunhofer.aisec.codyze
27+
28+
import de.fraunhofer.aisec.cpg.TranslationResult
29+
import kotlin.script.experimental.annotations.KotlinScript
30+
import kotlin.script.experimental.api.*
31+
import kotlin.script.experimental.jvm.dependenciesFromCurrentContext
32+
import kotlin.script.experimental.jvm.jvm
33+
34+
@KotlinScript(
35+
// File extension for the script type
36+
fileExtension = "query.kts",
37+
// Compilation configuration for the script type
38+
compilationConfiguration = QueryScriptConfiguration::class,
39+
)
40+
abstract class QueryScript
41+
42+
open class QueryScriptContext(val result: TranslationResult)
43+
44+
object QueryScriptConfiguration :
45+
ScriptCompilationConfiguration({
46+
baseClass(QueryScript::class)
47+
jvm { dependenciesFromCurrentContext(wholeClasspath = true) }
48+
implicitReceivers(QueryScriptContext::class)
49+
compilerOptions("-Xcontext-receivers", "-jvm-target=17")
50+
defaultImports.append(
51+
"de.fraunhofer.aisec.codyze.*",
52+
"de.fraunhofer.aisec.cpg.*",
53+
"de.fraunhofer.aisec.cpg.graph.*",
54+
"de.fraunhofer.aisec.cpg.query.*",
55+
)
56+
ide { acceptedLocations(ScriptAcceptedLocation.Everywhere) }
57+
}) {
58+
private fun readResolve(): Any = QueryScriptConfiguration
59+
}

codyze-core/src/main/resources/META-INF/kotlin/script/templates/de.fraunhofer.aisec.codyze.QueryScript.classname

Whitespace-only changes.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Configuration status="WARN">
2+
<Appenders>
3+
<Console name="STDOUT" target="SYSTEM_OUT">
4+
<PatternLayout pattern="%d{HH:mm:ss,SSS} %-5p %C{1} %m%n"/>
5+
<ThresholdFilter level="DEBUG"/>
6+
</Console>
7+
</Appenders>
8+
<Loggers>
9+
<Logger level="DEBUG" name="de.fraunhofer.aisec"/>
10+
<Root level="DEBUG">
11+
<AppenderRef ref="STDOUT"/>
12+
</Root>
13+
</Loggers>
14+
</Configuration>

0 commit comments

Comments
 (0)