Skip to content

Commit cf783eb

Browse files
kotlintree on CPG demo (#2056)
--------- Co-authored-by: Andrey Shcheglov <[email protected]>
1 parent 69d6b41 commit cf783eb

File tree

13 files changed

+429
-44
lines changed

13 files changed

+429
-44
lines changed

save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/demo/cpg/CpgAdditionalParams.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import kotlinx.serialization.Serializable
66
/**
77
* Data class that represents all additional params required to run CpgDemo
88
*
9+
* @property engine CPG engine
910
* @property language language that the code for demo was written in
1011
*/
1112
@Serializable
1213
data class CpgAdditionalParams(
14+
val engine: CpgEngine,
1315
val language: Languages = Languages.KOTLIN,
1416
)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.saveourtool.save.demo.cpg
2+
3+
/**
4+
* Engines for CPG demo
5+
*
6+
* @property prettyName
7+
*/
8+
enum class CpgEngine(val prettyName: String) {
9+
/**
10+
* A default engine using [Fraunhofer-AISEC/cpg](https://github.com/Fraunhofer-AISEC/cpg)
11+
*/
12+
CPG("cpg (Fraunhofer-AISEC)"),
13+
14+
/**
15+
* A tree-sitter engine using [kotlintree](https://github.com/oxisto/kotlintree) as a binding
16+
*/
17+
TREE_SITTER("tree-sitter (kotlintree)"),
18+
;
19+
}

save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/demo/cpg/CpgResult.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ import kotlinx.serialization.Serializable
55

66
/**
77
* @property cpgGraph graph obtained from Cpg tool run
8+
* @property query query id SQL request to a NEO4
89
* @property logs execution logs
9-
* @property applicationName query id SQL request to a NEO4
1010
*/
1111
@Serializable
1212
@OptIn(ExperimentalSerializationApi::class)
1313
data class CpgResult(
1414
val cpgGraph: CpgGraph,
15-
val applicationName: String,
15+
val query: String,
1616
val logs: List<String>,
1717
) {
1818
companion object {

save-demo-cpg/build.gradle.kts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ repositories {
2020
}
2121
}
2222
mavenCentral()
23+
maven {
24+
name = "0x6675636b796f75676974687562/kotlintree"
25+
url = uri("https://maven.pkg.github.com/0x6675636b796f75676974687562/kotlintree")
26+
credentials {
27+
username = project.findProperty("gprUser") as String?
28+
?: System.getenv("GITHUB_ACTOR")
29+
password = project.findProperty("gprKey") as String?
30+
?: System.getenv("GITHUB_TOKEN")
31+
}
32+
}
2333
}
2434
val jepArchive by configurations.creating
2535

@@ -51,6 +61,7 @@ dependencies {
5161
runtimeOnly(fileTree("$buildDir/distros/jep-distro").apply {
5262
builtBy(resolveJep)
5363
})
64+
implementation("io.github.oxisto:kotlin-tree-jna:0.0.1")
5465
}
5566

5667
// This is a special hack for macOS and JEP, see: https://github.com/Fraunhofer-AISEC/cpg/pull/995/files

save-demo-cpg/src/main/kotlin/com/saveourtool/save/demo/cpg/controller/CpgController.kt

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.saveourtool.save.demo.cpg.*
55
import com.saveourtool.save.demo.cpg.config.ConfigProperties
66
import com.saveourtool.save.demo.cpg.repository.CpgRepository
77
import com.saveourtool.save.demo.cpg.service.CpgService
8+
import com.saveourtool.save.demo.cpg.service.TreeSitterService
89
import com.saveourtool.save.demo.cpg.utils.*
910
import com.saveourtool.save.utils.blockingToMono
1011
import com.saveourtool.save.utils.getLogger
@@ -32,6 +33,7 @@ const val FILE_NAME_SEPARATOR = "==="
3233
* @property configProperties
3334
* @property cpgService
3435
* @property cpgRepository
36+
* @property treeSitterService
3537
*/
3638
@ApiSwaggerSupport
3739
@Tags(
@@ -44,31 +46,56 @@ class CpgController(
4446
val configProperties: ConfigProperties,
4547
val cpgService: CpgService,
4648
val cpgRepository: CpgRepository,
49+
val treeSitterService: TreeSitterService,
4750
) {
4851
/**
4952
* @param request
5053
* @return result of uploading, it contains ID to request the result further
5154
*/
5255
@PostMapping("/upload-code")
53-
@Suppress("TooGenericExceptionCaught", "DoubleMutabilityForCollection")
5456
fun uploadCode(
5557
@RequestBody request: CpgRunRequest,
5658
): Mono<CpgResult> = blockingToMono {
59+
when (request.params.engine) {
60+
CpgEngine.CPG -> doUploadCode(
61+
request,
62+
cpgService::translate,
63+
cpgRepository::save
64+
) {
65+
cpgRepository.getGraph(it)
66+
}
67+
CpgEngine.TREE_SITTER -> doUploadCode(
68+
request,
69+
treeSitterService::translate,
70+
cpgRepository::save
71+
) {
72+
cpgRepository.getGraphForTreeSitter(it)
73+
}
74+
}
75+
}
76+
77+
@Suppress("TooGenericExceptionCaught")
78+
private fun <T> doUploadCode(
79+
@RequestBody request: CpgRunRequest,
80+
translateFunction: (Path) -> ResultWithLogs<T>,
81+
saveFunction: (T) -> Long,
82+
graphFunction: (Long) -> CpgGraph,
83+
): CpgResult {
5784
val tmpFolder = createTempDirectory(request.params.language.modeName)
58-
var logs: MutableList<String> = mutableListOf()
59-
try {
85+
val logs: MutableList<String> = mutableListOf()
86+
return try {
6087
createFiles(request, tmpFolder)
61-
val (result, logsFromLogback) = cpgService.translate(tmpFolder)
62-
logs = logsFromLogback.toMutableList()
88+
val (result, logsFromLogback) = translateFunction(tmpFolder)
89+
logs.addAll(logsFromLogback)
6390

6491
result
6592
.map {
66-
cpgRepository.save(it)
93+
saveFunction(it)
6794
}
68-
.map {
95+
.map { queryId ->
6996
CpgResult(
70-
cpgRepository.getGraph(it),
71-
"match (e: Component where e.name = \"${tmpFolder.fileName.name}\") return e;",
97+
graphFunction(queryId),
98+
CpgRepository.getQueryForNodes(queryId),
7299
logs,
73100
)
74101
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.saveourtool.save.demo.cpg.entity
2+
3+
import org.neo4j.ogm.typeconversion.CompositeAttributeConverter
4+
5+
/**
6+
* Location of node in tree-sitter
7+
*/
8+
class TreeSitterLocation {
9+
/**
10+
* file name of location
11+
*/
12+
var fileName: String = "N/A"
13+
14+
/**
15+
* start in bytes of location
16+
*/
17+
var startBytes: Int = 0
18+
19+
/**
20+
* end in bytes of location
21+
*/
22+
var endBytes: Int = 0
23+
24+
override fun toString(): String = "TreeSitterLocation(fileName='$fileName', startBytes=$startBytes, endBytes=$endBytes)"
25+
26+
companion object {
27+
/**
28+
* A converter for [TreeSitterLocation]
29+
*/
30+
class Converter : CompositeAttributeConverter<TreeSitterLocation> {
31+
override fun toGraphProperties(value: TreeSitterLocation?): Map<String, Any> = value?.let {
32+
mapOf(
33+
FILE_NAME to value.fileName,
34+
START_BYTES to value.startBytes,
35+
END_BYTES to value.endBytes,
36+
LOCATION to value.toString(),
37+
)
38+
}.orEmpty()
39+
40+
override fun toEntityAttribute(value: Map<String?, *>): TreeSitterLocation? = TreeSitterLocation().apply {
41+
fileName = value[FILE_NAME]?.toString() ?: return null
42+
startBytes = value[START_BYTES]?.toString()?.toInt() ?: return null
43+
endBytes = value[END_BYTES]?.toString()?.toInt() ?: return null
44+
}
45+
46+
companion object {
47+
const val END_BYTES = "endBytes"
48+
const val FILE_NAME = "file"
49+
const val LOCATION = "location"
50+
const val START_BYTES = "startBytes"
51+
}
52+
}
53+
}
54+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.saveourtool.save.demo.cpg.entity
2+
3+
import com.fasterxml.jackson.annotation.JsonBackReference
4+
import org.neo4j.ogm.annotation.GeneratedValue
5+
import org.neo4j.ogm.annotation.Id
6+
import org.neo4j.ogm.annotation.NodeEntity
7+
import org.neo4j.ogm.annotation.Relationship
8+
import org.neo4j.ogm.annotation.typeconversion.Convert
9+
10+
/**
11+
* Entity to store [io.github.oxisto.kotlintree.jvm.Node]
12+
*/
13+
@NodeEntity
14+
class TreeSitterNode {
15+
/**
16+
* ID which NEO4j generates
17+
*/
18+
@Id @GeneratedValue
19+
var id: Long? = null
20+
21+
/**
22+
* Previous node on one level horizontally
23+
*/
24+
@Relationship(value = "SIBLING", direction = Relationship.Direction.INCOMING)
25+
@JsonBackReference
26+
var prev: TreeSitterNode? = null
27+
28+
/**
29+
* Next node on one level horizontally
30+
*/
31+
@Relationship(value = "SIBLING", direction = Relationship.Direction.OUTGOING)
32+
@JsonBackReference
33+
var next: TreeSitterNode? = null
34+
35+
/**
36+
* Node on one level up
37+
*/
38+
@Relationship(value = "PARENT", direction = Relationship.Direction.INCOMING)
39+
@JsonBackReference
40+
var parent: TreeSitterNode? = null
41+
42+
/**
43+
* All node on one level down
44+
*/
45+
@Relationship(value = "PARENT", direction = Relationship.Direction.OUTGOING)
46+
@JsonBackReference
47+
@Suppress("DoubleMutabilityForCollection")
48+
var child: MutableList<TreeSitterNode> = mutableListOf()
49+
50+
/**
51+
* Location of this node
52+
*/
53+
@Convert(TreeSitterLocation.Companion.Converter::class)
54+
var location: TreeSitterLocation = TreeSitterLocation()
55+
56+
/**
57+
* Local name -- probably type of node
58+
*/
59+
var localName: String = "N/A"
60+
61+
/**
62+
* A code of this node in parsed AST tree
63+
*/
64+
var code: String = "N/A"
65+
66+
/**
67+
* @return [id] as not null with validating
68+
* @throws IllegalArgumentException when [id] is not set that means entity is not saved yet
69+
*/
70+
fun requiredId(): Long = requireNotNull(id) {
71+
"Entity is not saved yet: $this"
72+
}
73+
}

0 commit comments

Comments
 (0)