From 5b7f0e2c86f3d7a10c45a91fe627a2ce09923891 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Fri, 20 Jun 2025 15:57:29 +0300 Subject: [PATCH] gRPC --- .github/workflows/ci.yml | 11 +- .gitignore | 5 +- build.gradle.kts | 2 +- buildSrc/build.gradle.kts | 4 + buildSrc/src/main/kotlin/Dependencies.kt | 82 +- buildSrc/src/main/kotlin/ProcessUtil.kt | 77 ++ jacodb-ets/.gitignore | 1 + jacodb-ets/build.gradle.kts | 60 +- .../kotlin/org/jacodb/ets/dto/ConvertToDto.kt | 66 ++ .../ets/dto/{Convert.kt => ConvertToEts.kt} | 45 +- .../kotlin/org/jacodb/ets/dto/EtsTypeToDto.kt | 173 +++ .../org/jacodb/ets/dto/EtsValueToDto.kt | 107 ++ .../main/kotlin/org/jacodb/ets/dto/Model.kt | 32 +- .../kotlin/org/jacodb/ets/dto/Serializers.kt | 7 +- .../main/kotlin/org/jacodb/ets/dto/Types.kt | 2 +- .../kotlin/org/jacodb/ets/grpc/LoadScene.kt | 57 + .../main/kotlin/org/jacodb/ets/grpc/Server.kt | 144 +++ .../main/kotlin/org/jacodb/ets/model/Class.kt | 8 +- .../main/kotlin/org/jacodb/ets/model/File.kt | 4 +- .../main/kotlin/org/jacodb/ets/model/Type.kt | 2 +- .../org/jacodb/ets/proto/ConvertToEts.kt | 1031 +++++++++++++++++ .../org/jacodb/ets/proto/ConvertToProto.kt | 163 +++ .../org/jacodb/ets/proto/EtsEntityToProto.kt | 43 + .../org/jacodb/ets/proto/EtsExprToProto.kt | 471 ++++++++ .../org/jacodb/ets/proto/EtsStmtToProto.kt | 87 ++ .../org/jacodb/ets/proto/EtsTypeToProto.kt | 197 ++++ .../org/jacodb/ets/proto/EtsValueToProto.kt | 130 +++ .../org/jacodb/ets/utils/BlockCfgBuilder.kt | 11 - .../org/jacodb/ets/utils/EtsFileToText.kt | 3 +- .../{EtsFileDtoToDot.kt => FileDtoToDot.kt} | 10 +- .../{EtsFileDtoToText.kt => FileDtoToText.kt} | 6 +- .../ets/utils/{LoadEtsFile.kt => Loaders.kt} | 113 +- .../org/jacodb/ets/utils/ProcessUtil.kt | 21 +- .../kotlin/org/jacodb/ets/utils/Resource.kt} | 2 +- .../kotlin/org/jacodb/ets/test/EtsFileTest.kt | 2 +- .../org/jacodb/ets/test/EtsFromJsonTest.kt | 96 +- .../kotlin/org/jacodb/ets/test/GrpcTest.kt | 156 +++ .../kotlin/org/jacodb/ets/test/WireTest.kt | 54 + .../org/jacodb/ets/test/utils/Assumptions.kt | 29 + .../org/jacodb/ets/test/utils/Entrypoints.kt | 25 +- .../src/test/resources/prepare_projects.sh | 14 + .../src/test/resources/prepare_repos.sh | 1 + .../org/jacodb/ets/test/utils/LoadEts.kt | 133 --- jacodb-ets/wire-client/build.gradle.kts | 14 + .../org/jacodb/ets/service/GrpcClient.kt | 64 + .../org/jacodb/ets/service/TestGreeter.kt | 28 + .../org/jacodb/ets/service/TestManager.kt | 29 + jacodb-ets/wire-protos/build.gradle.kts | 10 + .../wire-protos/src/main/proto/greeter.proto | 20 + .../wire-protos/src/main/proto/manager.proto | 15 + .../wire-protos/src/main/proto/model.proto | 461 ++++++++ jacodb-ets/wire-server/build.gradle.kts | 38 + .../kotlin/org/jacodb/ets/service/Greeter.kt | 53 + .../kotlin/org/jacodb/ets/service/Server.kt | 49 + jacodb-taint-configuration/build.gradle.kts | 1 - settings.gradle.kts | 33 +- 56 files changed, 4216 insertions(+), 286 deletions(-) create mode 100644 buildSrc/src/main/kotlin/ProcessUtil.kt create mode 100644 jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/ConvertToDto.kt rename jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/{Convert.kt => ConvertToEts.kt} (96%) create mode 100644 jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/EtsTypeToDto.kt create mode 100644 jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/EtsValueToDto.kt create mode 100644 jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/LoadScene.kt create mode 100644 jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/Server.kt create mode 100644 jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/ConvertToEts.kt create mode 100644 jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/ConvertToProto.kt create mode 100644 jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsEntityToProto.kt create mode 100644 jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsExprToProto.kt create mode 100644 jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsStmtToProto.kt create mode 100644 jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsTypeToProto.kt create mode 100644 jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsValueToProto.kt rename jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/{EtsFileDtoToDot.kt => FileDtoToDot.kt} (97%) rename jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/{EtsFileDtoToText.kt => FileDtoToText.kt} (95%) rename jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/{LoadEtsFile.kt => Loaders.kt} (62%) rename jacodb-ets/src/{testFixtures/kotlin/org/jacodb/ets/test/utils/Resources.kt => main/kotlin/org/jacodb/ets/utils/Resource.kt} (97%) create mode 100644 jacodb-ets/src/test/kotlin/org/jacodb/ets/test/GrpcTest.kt create mode 100644 jacodb-ets/src/test/kotlin/org/jacodb/ets/test/WireTest.kt create mode 100644 jacodb-ets/src/test/kotlin/org/jacodb/ets/test/utils/Assumptions.kt delete mode 100644 jacodb-ets/src/testFixtures/kotlin/org/jacodb/ets/test/utils/LoadEts.kt create mode 100644 jacodb-ets/wire-client/build.gradle.kts create mode 100644 jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/GrpcClient.kt create mode 100644 jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/TestGreeter.kt create mode 100644 jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/TestManager.kt create mode 100644 jacodb-ets/wire-protos/build.gradle.kts create mode 100644 jacodb-ets/wire-protos/src/main/proto/greeter.proto create mode 100644 jacodb-ets/wire-protos/src/main/proto/manager.proto create mode 100644 jacodb-ets/wire-protos/src/main/proto/model.proto create mode 100644 jacodb-ets/wire-server/build.gradle.kts create mode 100644 jacodb-ets/wire-server/src/main/kotlin/org/jacodb/ets/service/Greeter.kt create mode 100644 jacodb-ets/wire-server/src/main/kotlin/org/jacodb/ets/service/Server.kt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20c0eeb71..bc88eb3e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: cache-read-only: ${{ github.ref != 'refs/heads/develop' && github.ref != 'ref/heads/neo' }} - name: Build and run tests - run: ./gradlew --scan build -x :jacodb-ets:build + run: ./gradlew --scan build - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 @@ -122,11 +122,11 @@ jobs: - name: Set up ArkAnalyzer run: | - REPO_URL="https://gitcode.com/Lipen/arkanalyzer" + REPO_URL="https://gitcode.com/Lipen/arkanalyzer.git" DEST_DIR="arkanalyzer" MAX_RETRIES=10 - RETRY_DELAY=3 # Delay between retries in seconds - BRANCH="neo/2025-06-16" + RETRY_DELAY=3 # Delay between retries in seconds + BRANCH="neo/2025-06-20" for ((i=1; i<=MAX_RETRIES; i++)); do git clone --depth=1 --branch $BRANCH $REPO_URL $DEST_DIR && break @@ -147,6 +147,9 @@ jobs: npm install npm run build + - name: Enable ETS modules + run: echo "enableEts=true" >> local.properties + - name: Run ETS tests run: ./gradlew --scan :jacodb-ets:generateTestResources :jacodb-ets:test diff --git a/.gitignore b/.gitignore index fcd429928..a63f8ffe4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ .idea/ .gradle/ -build/ .kotlin/ +build/ +generated/ idea-community *.db -/generated/ +/local.properties diff --git a/build.gradle.kts b/build.gradle.kts index 54040e934..9748aed46 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -147,6 +147,7 @@ allprojects { license { include("**/*.kt") include("**/*.java") + exclude { it.file.startsWith(layout.buildDirectory.asFile.get()) } header(rootProject.file("docs/copyright/COPYRIGHT_HEADER.txt")) } } @@ -173,7 +174,6 @@ if (!repoUrl.isNullOrEmpty()) { project(":jacodb-storage"), project(":jacodb-approximations"), project(":jacodb-taint-configuration"), - project(":jacodb-ets"), ) ) { tasks { diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 876c922b2..ddfdcd6f8 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -5,3 +5,7 @@ plugins { repositories { mavenCentral() } + +dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") +} diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index a3c684d0f..8ce53dfce 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -6,12 +6,12 @@ object Versions { const val asm = "9.7.1" const val dokka = "1.9.20" // note: must be compatible with kotlin version const val gradle_download = "5.3.0" + const val gradle_node = "7.1.0" const val gradle_versions = "0.47.0" - - // hikaricp version compatible with Java 8 - const val hikaricp = "4.0.3" - + const val grpc = "1.72.0" + const val grpc_kotlin = "1.4.3" const val guava = "31.1-jre" + const val hikaricp = "4.0.3" // compatible with Java 8 const val javax_activation = "1.1" const val javax_mail = "1.4.7" const val javax_servlet_api = "2.5" @@ -24,23 +24,26 @@ object Versions { const val junit = "5.9.2" const val kotlin = "2.1.0" const val kotlin_logging = "1.8.3" + const val kotlin_metadata = kotlin const val kotlinx_benchmark = "0.4.6" const val kotlinx_cli = "0.3.5" const val kotlinx_collections_immutable = "0.3.5" const val kotlinx_coroutines = "1.6.4" - const val kotlin_metadata = kotlin const val kotlinx_serialization = "1.8.0" const val licenser = "0.6.1" + const val lmdb_java = "0.9.0" const val mockk = "1.13.3" + const val protobuf = "4.30.2" + const val rocks_db = "9.1.1" const val sarif4k = "0.5.0" const val shadow = "8.1.1" - const val slf4j = "1.7.36" + const val slf4j = "2.0.17" const val soot_utbot_fork = "4.4.0-FORK-2" const val sootup = "1.0.0" const val sqlite = "3.41.2.2" const val xodus = "2.0.1" - const val rocks_db = "9.1.1" - const val lmdb_java = "0.9.0" + const val wire = "5.3.1" + const val wire_grpc_server = "1.0.0-alpha04" // libs for tests only const val jgit_test_only_version = "5.9.0.202009080501-r" @@ -144,11 +147,6 @@ object Libs { ) // https://github.com/Kotlin/kotlinx.serialization - val kotlinx_serialization_core = dep( - group = "org.jetbrains.kotlinx", - name = "kotlinx-serialization-core", - version = Versions.kotlinx_serialization - ) val kotlinx_serialization_json = dep( group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", @@ -339,6 +337,52 @@ object Libs { name = "commons-compress", version = Versions.commons_compress_test_only_version ) + + // https://github.com/grpc/grpc-java + val grpc_api = dep( + group = "io.grpc", + name = "grpc-api", + version = Versions.grpc + ) + val grpc_protobuf = dep( + group = "io.grpc", + name = "grpc-protobuf", + version = Versions.grpc + ) + val grpc_services = dep( + group = "io.grpc", + name = "grpc-services", + version = Versions.grpc + ) + val grpc_netty_shaded = dep( + group = "io.grpc", + name = "grpc-netty-shaded", + version = Versions.grpc + ) + + // https://github.com/square/wire + val wire_runtime = dep( + group = "com.squareup.wire", + name = "wire-runtime", + version = Versions.wire + ) + val wire_grpc_client = dep( + group = "com.squareup.wire", + name = "wire-grpc-client", + version = Versions.wire + ) + + // https://github.com/square/wire-grpc-server + val wire_grpc_server = dep( + group = "com.squareup.wiregrpcserver", + name = "server", + version = Versions.wire_grpc_server + ) + val wire_grpc_server_generator = dep( + group = "com.squareup.wiregrpcserver", + name = "server-generator", + version = Versions.wire_grpc_server + ) } object Plugins { @@ -357,6 +401,12 @@ object Plugins { id = "de.undercouch.download" ) + // https://github.com/node-gradle/gradle-node-plugin + object GradleNode : ProjectPlugin( + version = Versions.gradle_node, + id = "com.github.node-gradle.node" + ) + // https://github.com/ben-manes/gradle-versions-plugin object GradleVersions : ProjectPlugin( version = Versions.gradle_versions, @@ -380,6 +430,12 @@ object Plugins { version = Versions.shadow, id = "com.github.johnrengelman.shadow" ) + + // https://github.com/square/wire + object Wire : ProjectPlugin( + version = Versions.wire, + id = "com.squareup.wire" + ) } fun PluginDependenciesSpec.id(plugin: Plugins.ProjectPlugin) { diff --git a/buildSrc/src/main/kotlin/ProcessUtil.kt b/buildSrc/src/main/kotlin/ProcessUtil.kt new file mode 100644 index 000000000..26fcf88e0 --- /dev/null +++ b/buildSrc/src/main/kotlin/ProcessUtil.kt @@ -0,0 +1,77 @@ +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import java.io.Reader +import java.util.concurrent.TimeUnit +import kotlin.time.Duration + +object ProcessUtil { + data class Result( + val exitCode: Int, + val stdout: String, + val stderr: String, + val isTimeout: Boolean, // true if the process was terminated due to timeout + ) + + fun run( + command: List, + input: Reader = "".reader(), + timeout: Duration? = null, + builder: ProcessBuilder.() -> Unit = {}, + ): Result { + val process = ProcessBuilder(command).apply(builder).start() + return communicate(process, input, timeout) + } + + private fun communicate( + process: Process, + input: Reader, + timeout: Duration? = null, + ): Result { + val stdout = StringBuilder() + val stderr = StringBuilder() + + val scope = CoroutineScope(Dispatchers.IO) + + // Handle process input + val stdinJob = scope.launch { + process.outputStream.bufferedWriter().use { writer -> + input.copyTo(writer) + } + } + + // Launch output capture coroutines + val stdoutJob = scope.launch { + process.inputStream.bufferedReader().useLines { lines -> + lines.forEach { stdout.appendLine(it) } + } + } + val stderrJob = scope.launch { + process.errorStream.bufferedReader().useLines { lines -> + lines.forEach { stderr.appendLine(it) } + } + } + + // Wait for completion + val isTimeout = if (timeout != null) { + !process.waitFor(timeout.inWholeNanoseconds, TimeUnit.NANOSECONDS) + } else { + process.waitFor() + false + } + + // Wait for all coroutines to finish + runBlocking { + joinAll(stdinJob, stdoutJob, stderrJob) + } + + return Result( + exitCode = process.exitValue(), + stdout = stdout.toString(), + stderr = stderr.toString(), + isTimeout = isTimeout, + ) + } +} diff --git a/jacodb-ets/.gitignore b/jacodb-ets/.gitignore index a90e952d6..24202e411 100644 --- a/jacodb-ets/.gitignore +++ b/jacodb-ets/.gitignore @@ -1,3 +1,4 @@ +arkanalyzer /src/test/resources/samples/etsir /src/test/resources/projects diff --git a/jacodb-ets/build.gradle.kts b/jacodb-ets/build.gradle.kts index 3349239e9..adaff0137 100644 --- a/jacodb-ets/build.gradle.kts +++ b/jacodb-ets/build.gradle.kts @@ -1,26 +1,40 @@ +import com.github.gradle.node.npm.task.NpmTask import java.io.FileNotFoundException +import kotlin.time.Duration.Companion.minutes plugins { kotlin("plugin.serialization") - `java-test-fixtures` + id(Plugins.GradleNode) } dependencies { api(project(":jacodb-api-common")) + api(project(":jacodb-ets:wire-client")) + api(project(":jacodb-ets:wire-server")) implementation(Libs.kotlin_logging) - implementation(Libs.slf4j_simple) implementation(Libs.kotlinx_serialization_json) implementation(Libs.kotlinx_coroutines_core) implementation(Libs.jdot) testImplementation(kotlin("test")) testImplementation(Libs.mockk) + testImplementation(Libs.slf4j_simple) testFixturesImplementation(Libs.kotlin_logging) testFixturesImplementation(Libs.junit_jupiter_api) } +node { + download = true + nodeProjectDir.set(file("arkanalyzer")) +} + +tasks.register("runArkAnalyzerServer") { + dependsOn(tasks.npmInstall) + args = listOf("run", "server") +} + // Example usage: // ``` // export ARKANALYZER_DIR=~/dev/arkanalyzer @@ -30,14 +44,14 @@ tasks.register("generateTestResources") { group = "build" description = "Generates test resources from TypeScript files using ArkAnalyzer." doLast { - println("Generating test resources using ArkAnalyzer...") + logger.lifecycle("Generating test resources using ArkAnalyzer...") val startTime = System.currentTimeMillis() val envVarName = "ARKANALYZER_DIR" val defaultArkAnalyzerDir = "arkanalyzer" val arkAnalyzerDir = rootDir.resolve(System.getenv(envVarName) ?: run { - println("Please, set $envVarName environment variable. Using default value: '$defaultArkAnalyzerDir'") + logger.lifecycle("Please, set $envVarName environment variable. Using default value: '$defaultArkAnalyzerDir'") defaultArkAnalyzerDir }) if (!arkAnalyzerDir.exists()) { @@ -48,7 +62,7 @@ tasks.register("generateTestResources") { "current dir is '${File("").absolutePath}'." ) } - println("Using ArkAnalyzer directory: '${arkAnalyzerDir.relativeTo(rootDir)}'") + logger.lifecycle("Using ArkAnalyzer directory: '${arkAnalyzerDir.relativeTo(rootDir)}'") val scriptSubPath = "src/save/serializeArkIR" val script = arkAnalyzerDir.resolve("out").resolve("$scriptSubPath.js") @@ -58,12 +72,12 @@ tasks.register("generateTestResources") { "Did you forget to execute 'npm run build' in the arkanalyzer project?" ) } - println("Using script: '${script.relativeTo(arkAnalyzerDir)}'") + logger.lifecycle("Using script: '${script.relativeTo(arkAnalyzerDir)}'") val resources = projectDir.resolve("src/test/resources") val inputDir = resources.resolve("samples/source") val outputDir = resources.resolve("samples/etsir/ast") - println("Generating test resources in '${outputDir.relativeTo(projectDir)}'...") + logger.lifecycle("Generating test resources in '${outputDir.relativeTo(projectDir)}'...") val cmd: List = listOf( "node", @@ -73,24 +87,26 @@ tasks.register("generateTestResources") { outputDir.relativeTo(resources).path, "-t", ) - println("Running: '${cmd.joinToString(" ")}'") - val process = ProcessBuilder(cmd).directory(resources).start() - val ok = process.waitFor(10, TimeUnit.MINUTES) - - val stdout = process.inputStream.bufferedReader().readText().trim() - if (stdout.isNotBlank()) { - println("[STDOUT]:\n--------\n$stdout\n--------") + logger.lifecycle("Running: ${cmd.joinToString(" ")}") + val result = ProcessUtil.run(cmd, timeout = 1.minutes) { + directory(resources) } - val stderr = process.errorStream.bufferedReader().readText().trim() - if (stderr.isNotBlank()) { - println("[STDERR]:\n--------\n$stderr\n--------") + if (result.stdout.isNotBlank()) { + logger.lifecycle("[STDOUT]:\n--------\n${result.stdout}--------") } - - if (!ok) { - println("Timeout!") - process.destroy() + if (result.stderr.isNotBlank()) { + logger.lifecycle("[STDERR]:\n--------\n${result.stderr}--------") + } + if (result.isTimeout) { + logger.warn("Timeout!") + } + if (result.exitCode != 0) { + logger.warn("Exit code: ${result.exitCode}") } - println("Done generating test resources in %.1fs".format((System.currentTimeMillis() - startTime) / 1000.0)) + logger.lifecycle( + "Done generating test resources in %.1fs" + .format((System.currentTimeMillis() - startTime) / 1000.0) + ) } } diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/ConvertToDto.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/ConvertToDto.kt new file mode 100644 index 000000000..68ace5325 --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/ConvertToDto.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * 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 org.jacodb.ets.dto + +import org.jacodb.ets.model.EtsClassSignature +import org.jacodb.ets.model.EtsFieldSignature +import org.jacodb.ets.model.EtsFileSignature +import org.jacodb.ets.model.EtsMethodParameter +import org.jacodb.ets.model.EtsMethodSignature +import org.jacodb.ets.model.EtsNamespaceSignature + +fun EtsFileSignature.toDto(): FileSignatureDto = + FileSignatureDto( + projectName = this.projectName, + fileName = this.fileName, + ) + +fun EtsNamespaceSignature.toDto(): NamespaceSignatureDto = + NamespaceSignatureDto( + name = this.name, + declaringFile = this.file.toDto(), + declaringNamespace = this.namespace?.toDto(), + ) + +fun EtsClassSignature.toDto(): ClassSignatureDto = + ClassSignatureDto( + name = this.name, + declaringFile = this.file.toDto(), + declaringNamespace = this.namespace?.toDto(), + ) + +fun EtsFieldSignature.toDto(): FieldSignatureDto = + FieldSignatureDto( + declaringClass = this.enclosingClass.toDto(), + name = this.name, + type = this.type.toDto(), + ) + +fun EtsMethodSignature.toDto(): MethodSignatureDto = + MethodSignatureDto( + declaringClass = this.enclosingClass.toDto(), + name = this.name, + parameters = this.parameters.map { it.toDto() }, + returnType = this.returnType.toDto(), + ) + +fun EtsMethodParameter.toDto(): MethodParameterDto = + MethodParameterDto( + name = this.name, + type = this.type.toDto(), + isOptional = this.isOptional, + ) diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Convert.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/ConvertToEts.kt similarity index 96% rename from jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Convert.kt rename to jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/ConvertToEts.kt index ddf297fc7..a15961c75 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Convert.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/ConvertToEts.kt @@ -102,6 +102,7 @@ import org.jacodb.ets.model.EtsRawType import org.jacodb.ets.model.EtsRemExpr import org.jacodb.ets.model.EtsReturnStmt import org.jacodb.ets.model.EtsRightShiftExpr +import org.jacodb.ets.model.EtsScene import org.jacodb.ets.model.EtsStaticCallExpr import org.jacodb.ets.model.EtsStaticFieldRef import org.jacodb.ets.model.EtsStmt @@ -126,6 +127,23 @@ import org.jacodb.ets.model.EtsUnsignedRightShiftExpr import org.jacodb.ets.model.EtsValue import org.jacodb.ets.model.EtsVoidType import org.jacodb.ets.model.EtsYieldExpr +import org.jacodb.ets.utils.CONSTRUCTOR_NAME + +/** + * Ad-hoc fix for constructor call. + * + * Replaces `x := x.constructor(...)` with `x.constructor(...)` call stmt. + */ +fun EtsAssignStmt.fixConstructorCall(): EtsStmt = + if (lhv is EtsLocal && + rhv is EtsInstanceCallExpr && + rhv.instance == lhv && + rhv.callee.name == CONSTRUCTOR_NAME + ) { + EtsCallStmt(location, rhv) + } else { + this + } class EtsMethodBuilder( signature: EtsMethodSignature, @@ -208,7 +226,7 @@ class EtsMethodBuilder( location = loc(), lhv = lhv, rhv = rhv, - ) + ).fixConstructorCall() } is CallStmtDto -> { @@ -443,18 +461,6 @@ class EtsMethodBuilder( fun ClassDto.toEtsClass(): EtsClass { val signature = signature.toEtsClassSignature() - val superClassSignature = superClassName?.takeIf { it != "" }?.let { name -> - EtsClassSignature( - name = name, - file = EtsFileSignature.UNKNOWN, - ) - } - val implementedInterfaces = implementedInterfaceNames.map { name -> - EtsClassSignature( - name = name, - file = EtsFileSignature.UNKNOWN, - ) - } val fields = fields.map { it.toEtsField() } val methods = methods.map { it.toEtsMethod() } val category = category.toEtsClassCategory() @@ -467,8 +473,8 @@ fun ClassDto.toEtsClass(): EtsClass { fields = fields, methods = methods, category = category, - superClass = superClassSignature, - implementedInterfaces = implementedInterfaces, + superClassName = superClassName, + implementedInterfaceNames = implementedInterfaceNames, typeParameters = typeParameters, modifiers = modifiers, decorators = decorators, @@ -687,7 +693,7 @@ fun NamespaceDto.toEtsNamespace(): EtsNamespace { ) } -fun EtsFileDto.toEtsFile(): EtsFile { +fun FileDto.toEtsFile(): EtsFile { val signature = signature.toEtsFileSignature() val classes = classes.map { it.toEtsClass() } val namespaces = namespaces.map { it.toEtsNamespace() } @@ -698,6 +704,13 @@ fun EtsFileDto.toEtsFile(): EtsFile { ) } +fun SceneDto.toEtsScene(): EtsScene { + return EtsScene( + projectFiles = files.map { it.toEtsFile() }, + sdkFiles = sdkFiles.map { it.toEtsFile() }, + ) +} + fun DecoratorDto.toEtsDecorator(): EtsDecorator { return EtsDecorator( name = kind, diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/EtsTypeToDto.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/EtsTypeToDto.kt new file mode 100644 index 000000000..4790f1575 --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/EtsTypeToDto.kt @@ -0,0 +1,173 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * 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 org.jacodb.ets.dto + +import org.jacodb.ets.model.EtsAliasType +import org.jacodb.ets.model.EtsAnyType +import org.jacodb.ets.model.EtsArrayType +import org.jacodb.ets.model.EtsBooleanType +import org.jacodb.ets.model.EtsClassType +import org.jacodb.ets.model.EtsEnumValueType +import org.jacodb.ets.model.EtsFunctionType +import org.jacodb.ets.model.EtsGenericType +import org.jacodb.ets.model.EtsIntersectionType +import org.jacodb.ets.model.EtsLiteralType +import org.jacodb.ets.model.EtsNeverType +import org.jacodb.ets.model.EtsNullType +import org.jacodb.ets.model.EtsNumberType +import org.jacodb.ets.model.EtsRawType +import org.jacodb.ets.model.EtsStringType +import org.jacodb.ets.model.EtsTupleType +import org.jacodb.ets.model.EtsType +import org.jacodb.ets.model.EtsUnclearRefType +import org.jacodb.ets.model.EtsUndefinedType +import org.jacodb.ets.model.EtsUnionType +import org.jacodb.ets.model.EtsUnknownType +import org.jacodb.ets.model.EtsVoidType + +fun EtsType.toDto(): TypeDto = accept(EtsTypeToDto) + +private object EtsTypeToDto : EtsType.Visitor { + override fun visit(type: EtsRawType): TypeDto { + // Note: the original (raw) type is lost! + return UnknownTypeDto + } + + override fun visit(type: EtsAnyType): TypeDto { + return AnyTypeDto + } + + override fun visit(type: EtsUnknownType): TypeDto { + return UnknownTypeDto + } + + override fun visit(type: EtsUnionType): TypeDto { + return UnionTypeDto(types = type.types.map { it.toDto() }) + } + + override fun visit(type: EtsIntersectionType): TypeDto { + return IntersectionTypeDto(types = type.types.map { it.toDto() }) + } + + override fun visit(type: EtsGenericType): TypeDto { + return GenericTypeDto( + name = type.typeName, + defaultType = type.defaultType?.toDto(), + constraint = type.constraint?.toDto(), + ) + } + + override fun visit(type: EtsAliasType): TypeDto { + return AliasTypeDto( + name = type.name, + originalType = type.originalType.toDto(), + signature = LocalSignatureDto( + type.signature.name, + type.signature.method.toDto(), + ), + ) + } + + override fun visit(type: EtsEnumValueType): TypeDto { + return EnumValueTypeDto( + signature = type.signature.toDto(), + constant = type.constant?.toDto(), + ) + } + + override fun visit(type: EtsBooleanType): TypeDto { + return BooleanTypeDto + } + + override fun visit(type: EtsNumberType): TypeDto { + return NumberTypeDto + } + + override fun visit(type: EtsStringType): TypeDto { + return StringTypeDto + } + + override fun visit(type: EtsNullType): TypeDto { + return NullTypeDto + } + + override fun visit(type: EtsUndefinedType): TypeDto { + return UndefinedTypeDto + } + + override fun visit(type: EtsVoidType): TypeDto { + return VoidTypeDto + } + + override fun visit(type: EtsNeverType): TypeDto { + return NeverTypeDto + } + + override fun visit(type: EtsLiteralType): TypeDto { + val literal = when { + type.literalTypeName.equals("true", ignoreCase = true) -> { + PrimitiveLiteralDto.BooleanLiteral(true) + } + + type.literalTypeName.equals("false", ignoreCase = true) -> { + PrimitiveLiteralDto.BooleanLiteral(false) + } + + else -> { + val x = type.literalTypeName.toDoubleOrNull() + if (x != null) { + PrimitiveLiteralDto.NumberLiteral(x) + } else { + PrimitiveLiteralDto.StringLiteral(type.literalTypeName) + } + } + } + return LiteralTypeDto(literal = literal) + } + + override fun visit(type: EtsClassType): TypeDto { + return ClassTypeDto( + signature = type.signature.toDto(), + typeParameters = type.typeParameters.map { it.toDto() }, + ) + } + + override fun visit(type: EtsUnclearRefType): TypeDto { + return UnclearReferenceTypeDto( + name = type.typeName, + typeParameters = type.typeParameters.map { it.toDto() }, + ) + } + + override fun visit(type: EtsArrayType): TypeDto { + return ArrayTypeDto( + elementType = type.elementType.toDto(), + dimensions = type.dimensions, + ) + } + + override fun visit(type: EtsTupleType): TypeDto { + return TupleTypeDto(types = type.types.map { it.toDto() }) + } + + override fun visit(type: EtsFunctionType): TypeDto { + return FunctionTypeDto( + signature = type.signature.toDto(), + typeParameters = type.typeParameters.map { it.toDto() }, + ) + } +} diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/EtsValueToDto.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/EtsValueToDto.kt new file mode 100644 index 000000000..756657516 --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/EtsValueToDto.kt @@ -0,0 +1,107 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * 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 org.jacodb.ets.dto + +import org.jacodb.ets.model.EtsArrayAccess +import org.jacodb.ets.model.EtsBooleanConstant +import org.jacodb.ets.model.EtsConstant +import org.jacodb.ets.model.EtsInstanceFieldRef +import org.jacodb.ets.model.EtsLocal +import org.jacodb.ets.model.EtsNullConstant +import org.jacodb.ets.model.EtsNumberConstant +import org.jacodb.ets.model.EtsParameterRef +import org.jacodb.ets.model.EtsStaticFieldRef +import org.jacodb.ets.model.EtsStringConstant +import org.jacodb.ets.model.EtsThis +import org.jacodb.ets.model.EtsUndefinedConstant +import org.jacodb.ets.model.EtsValue + +fun EtsValue.toDto(): ValueDto = accept(EtsValueToDto) + +fun EtsLocal.toDto(): LocalDto = LocalDto( + name = name, + type = type.toDto(), +) + +fun EtsConstant.toDto(): ConstantDto = ConstantDto( + value = toString(), + type = type.toDto(), +) + +private object EtsValueToDto : EtsValue.Visitor { + override fun visit(value: EtsLocal): LocalDto { + return value.toDto() + } + + override fun visit(value: EtsConstant): ValueDto { + return value.toDto() + } + + override fun visit(value: EtsStringConstant): ValueDto { + return value.toDto() + } + + override fun visit(value: EtsBooleanConstant): ValueDto { + return value.toDto() + } + + override fun visit(value: EtsNumberConstant): ValueDto { + return value.toDto() + } + + override fun visit(value: EtsNullConstant): ValueDto { + return value.toDto() + } + + override fun visit(value: EtsUndefinedConstant): ValueDto { + return value.toDto() + } + + override fun visit(value: EtsThis): ValueDto { + return ThisRefDto( + type = value.type.toDto(), + ) + } + + override fun visit(value: EtsParameterRef): ValueDto { + return ParameterRefDto( + index = value.index, + type = value.type.toDto(), + ) + } + + override fun visit(value: EtsArrayAccess): ValueDto { + return ArrayRefDto( + array = value.array.toDto(), + index = value.index.toDto(), + type = value.type.toDto(), + ) + } + + override fun visit(value: EtsInstanceFieldRef): ValueDto { + return InstanceFieldRefDto( + instance = value.instance.toDto(), + field = value.field.toDto(), + ) + } + + override fun visit(value: EtsStaticFieldRef): ValueDto { + return StaticFieldRefDto( + field = value.field.toDto(), + ) + } +} diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Model.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Model.kt index fd770528a..935b8efd9 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Model.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Model.kt @@ -19,12 +19,28 @@ package org.jacodb.ets.dto import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream import java.io.InputStream @Serializable -data class EtsFileDto( +data class SceneDto( + val files: List, + val sdkFiles: List = emptyList(), +) { + companion object { + fun loadFromJson(jsonString: String): FileDto { + return dtoJson.decodeFromString(jsonString) + } + + @OptIn(ExperimentalSerializationApi::class) + fun loadFromJson(stream: InputStream): FileDto { + return dtoJson.decodeFromStream(stream) + } + } +} + +@Serializable +data class FileDto( val signature: FileSignatureDto, val namespaces: List, val classes: List, @@ -32,17 +48,13 @@ data class EtsFileDto( val exportInfos: List, ) { companion object { - private val json = Json { - serializersModule = dtoModule - } - - fun loadFromJson(jsonString: String): EtsFileDto { - return json.decodeFromString(jsonString) + fun loadFromJson(jsonString: String): FileDto { + return dtoJson.decodeFromString(jsonString) } @OptIn(ExperimentalSerializationApi::class) - fun loadFromJson(stream: InputStream): EtsFileDto { - return json.decodeFromStream(stream) + fun loadFromJson(stream: InputStream): FileDto { + return dtoJson.decodeFromStream(stream) } } } diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Serializers.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Serializers.kt index a081a30e4..5ff37c2e6 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Serializers.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Serializers.kt @@ -23,6 +23,7 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonDecoder import kotlinx.serialization.json.JsonEncoder import kotlinx.serialization.json.JsonObject @@ -46,12 +47,16 @@ internal val typeModule = SerializersModule { polymorphicDefaultDeserializer(TypeDto::class) { RawTypeSerializer } } -internal val dtoModule = SerializersModule { +val dtoModule = SerializersModule { include(stmtModule) include(valueModule) include(typeModule) } +val dtoJson = Json { + serializersModule = dtoModule +} + object PrimitiveLiteralSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("PrimitiveLiteral", PrimitiveKind.STRING) diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Types.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Types.kt index bed8028f9..8139527c8 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Types.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Types.kt @@ -66,7 +66,7 @@ data class AliasTypeDto( data class EnumValueTypeDto( val signature: FieldSignatureDto, val constant: ConstantDto? = null, -): TypeDto +) : TypeDto @Serializable @SerialName("VoidType") diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/LoadScene.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/LoadScene.kt new file mode 100644 index 000000000..3e8009fc4 --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/LoadScene.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * 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 org.jacodb.ets.grpc + +import manager.GetSceneRequest +import manager.ManagerClient +import mu.KotlinLogging +import org.jacodb.ets.service.createGrpcClient +import java.nio.file.Path +import kotlin.io.path.pathString +import kotlin.time.DurationUnit +import kotlin.time.measureTimedValue +import model.Scene as ProtoScene + +private val logger = KotlinLogging.logger {} + +fun loadScene( + port: Int, + path: Path, + inferTypes: Boolean = false, +): ProtoScene { + logger.info { "Connecting to gRPC server on port $port..." } + val manager = createGrpcClient(port) + logger.info { "Requesting Scene for '$path'..." } + val (scene, timeLoad) = measureTimedValue { + val request = GetSceneRequest( + path = path.pathString, + infer_types = inferTypes, + ) + manager.GetScene().executeBlocking(request) + } + logger.info { + "Received Scene in %.1fs with ${ + scene.files.size + } files, ${ + scene.files.sumOf { f -> f.classes.size } + } classes, ${ + scene.files.sumOf { f -> f.classes.sumOf { cls -> cls.methods.size } } + } methods" + .format(timeLoad.toDouble(DurationUnit.SECONDS)) + } + return scene +} diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/Server.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/Server.kt new file mode 100644 index 000000000..bad759baa --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/Server.kt @@ -0,0 +1,144 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * 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 org.jacodb.ets.grpc + +import mu.KotlinLogging +import java.io.FileNotFoundException +import java.io.IOException +import java.net.Socket +import kotlin.concurrent.thread +import kotlin.io.path.Path +import kotlin.io.path.absolute +import kotlin.io.path.div +import kotlin.io.path.exists +import kotlin.io.path.pathString + +private val logger = KotlinLogging.logger {} + +class Server( + val process: Process, + val outputThread: Thread, + val errorThread: Thread, +) { + fun stop() { + logger.info { "Stopping ArkAnalyzer server..." } + process.destroy() + try { + process.waitFor() + } catch (e: InterruptedException) { + logger.error(e) { "Error while waiting for server process to finish" } + } + outputThread.join() + errorThread.join() + logger.info { "ArkAnalyzer server stopped" } + } +} + +private const val ENV_VAR_ARKANALYZER_PORT = "ARKANALYZER_PORT" + +private const val ENV_VAR_ARKANALYZER_DIR = "ARKANALYZER_DIR" +private const val DEFAULT_ARKANALYZER_DIR = "arkanalyzer" + +// Note: The script path is relative to the ARKANALYZER_DIR. Or use the absolute path. +private const val ENV_VAR_SERVER_SCRIPT_PATH = "SERVER_SCRIPT_PATH" +private const val DEFAULT_SERVER_SCRIPT_PATH = "out/src/rpc/grpc-server.js" + +private const val ENV_VAR_NODE_EXECUTABLE = "NODE_EXECUTABLE" +private const val DEFAULT_NODE_EXECUTABLE = "node" + +fun startArkAnalyzerServer(port: Int): Server { + logger.info { "Starting ArkAnalyzer server on port $port..." } + + val arkAnalyzerDir = Path(System.getenv(ENV_VAR_ARKANALYZER_DIR) ?: DEFAULT_ARKANALYZER_DIR) + if (!arkAnalyzerDir.exists()) { + throw FileNotFoundException( + "ArkAnalyzer directory does not exist: '${arkAnalyzerDir.absolute()}'. " + + "Did you forget to set the '$ENV_VAR_ARKANALYZER_DIR' environment variable? " + + "Current value is '${System.getenv(ENV_VAR_ARKANALYZER_DIR)}', " + + "current dir is '${Path("").toAbsolutePath()}'." + ) + } + logger.info { "Using ArkAnalyzer directory: $arkAnalyzerDir" } + + val scriptPath = System.getenv(ENV_VAR_SERVER_SCRIPT_PATH) ?: DEFAULT_SERVER_SCRIPT_PATH + val script = arkAnalyzerDir / scriptPath + if (!script.exists()) { + throw FileNotFoundException( + "Script file not found: '$script'. " + + "Did you forget to execute 'npm run build' in the ArkAnalyzer project?" + ) + } + logger.info { "Using server script: $script" } + + val node = System.getenv(ENV_VAR_NODE_EXECUTABLE) ?: DEFAULT_NODE_EXECUTABLE + logger.info { "Using Node.js executable: $node" } + + val process = ProcessBuilder(node, script.pathString) + .also { + val env = it.environment() + env[ENV_VAR_ARKANALYZER_PORT] = port.toString() + } + .start() + + // Capture process output (stdout) + val stdout = StringBuilder() + val outputThread = thread { + process.inputStream.bufferedReader().useLines { lines -> + lines.forEach { + logger.info { "[STDOUT] $it" } + stdout.appendLine(it) + } + } + } + + // Capture process error output (stderr) + val stderr = StringBuilder() + val errorThread = thread { + process.errorStream.bufferedReader().useLines { lines -> + lines.forEach { + logger.info { "[STDERR] $it" } + stderr.appendLine(it) + } + } + } + + // Wait for the server to start + waitForServerToStart(port) + + return Server(process, outputThread, errorThread) +} + +private fun waitForServerToStart(port: Int) { + logger.info { "Waiting for server to start on port $port..." } + val maxRetries = 100 + val retryDelay = 100L // in milliseconds + repeat(maxRetries) { + try { + Socket("localhost", port).use { socket -> + if (socket.isConnected) { + logger.info { "ArkAnalyzer server is ready on port $port" } + return@waitForServerToStart + } + } + } catch (_: IOException) { + // Server not ready yet, retry + } + Thread.sleep(retryDelay) + } + val time = maxRetries * retryDelay / 1000 + throw RuntimeException("ArkAnalyzer server did not start after $time s") +} diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Class.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Class.kt index 7ae302381..92021ec00 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Class.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Class.kt @@ -26,8 +26,8 @@ interface EtsClass : Base { val methods: List val ctor: EtsMethod val category: EtsClassCategory - val superClass: EtsClassSignature? - val implementedInterfaces: List + val superClassName: String? + val implementedInterfaceNames: List val declaringFile: EtsFile? val declaringNamespace: EtsNamespace? @@ -41,8 +41,8 @@ class EtsClassImpl( override val fields: List, override val methods: List, override val category: EtsClassCategory = EtsClassCategory.CLASS, - override val superClass: EtsClassSignature? = null, - override val implementedInterfaces: List = emptyList(), + override val superClassName: String? = null, + override val implementedInterfaceNames: List = emptyList(), override val typeParameters: List = emptyList(), override val modifiers: EtsModifiers = EtsModifiers.Companion.EMPTY, override val decorators: List = emptyList(), diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/File.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/File.kt index 818bfccde..f58b2cc1a 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/File.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/File.kt @@ -19,7 +19,9 @@ package org.jacodb.ets.model class EtsFile( val signature: EtsFileSignature, val classes: List, - val namespaces: List, + val namespaces: List = emptyList(), + // TODO: importInfos: List = emptyList(), + // TODO: exportInfos: List = emptyList(), ) { init { classes.forEach { (it as EtsClassImpl).declaringFile = this } diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Type.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Type.kt index 584a961ab..57a51859a 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Type.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Type.kt @@ -377,7 +377,7 @@ data class EtsFunctionType( data class EtsEnumValueType( val signature: EtsFieldSignature, val constant: EtsConstant? = null, -): EtsType { +) : EtsType { override val typeName: String get() = signature.name diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/ConvertToEts.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/ConvertToEts.kt new file mode 100644 index 000000000..86b2b0211 --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/ConvertToEts.kt @@ -0,0 +1,1031 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * 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 org.jacodb.ets.proto + +import org.jacodb.ets.model.BasicBlock +import org.jacodb.ets.model.EtsAddExpr +import org.jacodb.ets.model.EtsAliasType +import org.jacodb.ets.model.EtsAndExpr +import org.jacodb.ets.model.EtsAnyType +import org.jacodb.ets.model.EtsArrayAccess +import org.jacodb.ets.model.EtsArrayType +import org.jacodb.ets.model.EtsAssignStmt +import org.jacodb.ets.model.EtsAwaitExpr +import org.jacodb.ets.model.EtsBinaryExpr +import org.jacodb.ets.model.EtsBitAndExpr +import org.jacodb.ets.model.EtsBitNotExpr +import org.jacodb.ets.model.EtsBitOrExpr +import org.jacodb.ets.model.EtsBitXorExpr +import org.jacodb.ets.model.EtsBlockCfg +import org.jacodb.ets.model.EtsBooleanConstant +import org.jacodb.ets.model.EtsBooleanType +import org.jacodb.ets.model.EtsCallExpr +import org.jacodb.ets.model.EtsCallStmt +import org.jacodb.ets.model.EtsCastExpr +import org.jacodb.ets.model.EtsClass +import org.jacodb.ets.model.EtsClassCategory +import org.jacodb.ets.model.EtsClassImpl +import org.jacodb.ets.model.EtsClassSignature +import org.jacodb.ets.model.EtsClassType +import org.jacodb.ets.model.EtsConstant +import org.jacodb.ets.model.EtsDecorator +import org.jacodb.ets.model.EtsDeleteExpr +import org.jacodb.ets.model.EtsDivExpr +import org.jacodb.ets.model.EtsEntity +import org.jacodb.ets.model.EtsEqExpr +import org.jacodb.ets.model.EtsExpExpr +import org.jacodb.ets.model.EtsExpr +import org.jacodb.ets.model.EtsField +import org.jacodb.ets.model.EtsFieldImpl +import org.jacodb.ets.model.EtsFieldSignature +import org.jacodb.ets.model.EtsFile +import org.jacodb.ets.model.EtsFileSignature +import org.jacodb.ets.model.EtsFunctionType +import org.jacodb.ets.model.EtsGenericType +import org.jacodb.ets.model.EtsGtEqExpr +import org.jacodb.ets.model.EtsGtExpr +import org.jacodb.ets.model.EtsIfStmt +import org.jacodb.ets.model.EtsInExpr +import org.jacodb.ets.model.EtsInstanceCallExpr +import org.jacodb.ets.model.EtsInstanceFieldRef +import org.jacodb.ets.model.EtsInstanceOfExpr +import org.jacodb.ets.model.EtsIntersectionType +import org.jacodb.ets.model.EtsLValue +import org.jacodb.ets.model.EtsLeftShiftExpr +import org.jacodb.ets.model.EtsLiteralType +import org.jacodb.ets.model.EtsLocal +import org.jacodb.ets.model.EtsLocalSignature +import org.jacodb.ets.model.EtsLtEqExpr +import org.jacodb.ets.model.EtsLtExpr +import org.jacodb.ets.model.EtsMethod +import org.jacodb.ets.model.EtsMethodImpl +import org.jacodb.ets.model.EtsMethodParameter +import org.jacodb.ets.model.EtsMethodSignature +import org.jacodb.ets.model.EtsModifiers +import org.jacodb.ets.model.EtsMulExpr +import org.jacodb.ets.model.EtsNamespace +import org.jacodb.ets.model.EtsNamespaceSignature +import org.jacodb.ets.model.EtsNegExpr +import org.jacodb.ets.model.EtsNeverType +import org.jacodb.ets.model.EtsNewArrayExpr +import org.jacodb.ets.model.EtsNewExpr +import org.jacodb.ets.model.EtsNopStmt +import org.jacodb.ets.model.EtsNotEqExpr +import org.jacodb.ets.model.EtsNotExpr +import org.jacodb.ets.model.EtsNullConstant +import org.jacodb.ets.model.EtsNullType +import org.jacodb.ets.model.EtsNullishCoalescingExpr +import org.jacodb.ets.model.EtsNumberConstant +import org.jacodb.ets.model.EtsNumberType +import org.jacodb.ets.model.EtsOrExpr +import org.jacodb.ets.model.EtsParameterRef +import org.jacodb.ets.model.EtsPtrCallExpr +import org.jacodb.ets.model.EtsRawEntity +import org.jacodb.ets.model.EtsRawStmt +import org.jacodb.ets.model.EtsRawType +import org.jacodb.ets.model.EtsRef +import org.jacodb.ets.model.EtsRelationExpr +import org.jacodb.ets.model.EtsRemExpr +import org.jacodb.ets.model.EtsReturnStmt +import org.jacodb.ets.model.EtsRightShiftExpr +import org.jacodb.ets.model.EtsScene +import org.jacodb.ets.model.EtsStaticCallExpr +import org.jacodb.ets.model.EtsStaticFieldRef +import org.jacodb.ets.model.EtsStmt +import org.jacodb.ets.model.EtsStmtLocation +import org.jacodb.ets.model.EtsStrictEqExpr +import org.jacodb.ets.model.EtsStrictNotEqExpr +import org.jacodb.ets.model.EtsStringConstant +import org.jacodb.ets.model.EtsStringType +import org.jacodb.ets.model.EtsSubExpr +import org.jacodb.ets.model.EtsThis +import org.jacodb.ets.model.EtsThrowStmt +import org.jacodb.ets.model.EtsTupleType +import org.jacodb.ets.model.EtsType +import org.jacodb.ets.model.EtsTypeOfExpr +import org.jacodb.ets.model.EtsUnaryExpr +import org.jacodb.ets.model.EtsUnclearRefType +import org.jacodb.ets.model.EtsUndefinedConstant +import org.jacodb.ets.model.EtsUndefinedType +import org.jacodb.ets.model.EtsUnionType +import org.jacodb.ets.model.EtsUnknownType +import org.jacodb.ets.model.EtsUnsignedRightShiftExpr +import org.jacodb.ets.model.EtsValue +import org.jacodb.ets.model.EtsVoidType +import org.jacodb.ets.model.EtsYieldExpr +import model.BinaryExpr as ProtoBinaryExpr +import model.BinaryOperator as ProtoBinaryOperator +import model.BlockCfg as ProtoBlockCfg +import model.CallExpr as ProtoCallExpr +import model.Class as ProtoClass +import model.ClassSignature as ProtoClassSignature +import model.Decorator as ProtoDecorator +import model.Expr as ProtoExpr +import model.Field as ProtoField +import model.FieldSignature as ProtoFieldSignature +import model.File as ProtoFile +import model.FileSignature as ProtoFileSignature +import model.Local as ProtoLocal +import model.Method as ProtoMethod +import model.MethodParameter as ProtoMethodParameter +import model.MethodSignature as ProtoMethodSignature +import model.Namespace as ProtoNamespace +import model.NamespaceSignature as ProtoNamespaceSignature +import model.Ref as ProtoRef +import model.RelationExpr as ProtoRelationExpr +import model.RelationOperator as ProtoRelationOperator +import model.Scene as ProtoScene +import model.Stmt as ProtoStmt +import model.Type as ProtoType +import model.UnaryExpr as ProtoUnaryExpr +import model.UnaryOperator as ProtoUnaryOperator +import model.Value as ProtoValue + +// import model.ImportInfo as ProtoImportInfo +// import model.ExportInfo as ProtoExportInfo + +// region model + +fun ProtoScene.toEts(): EtsScene { + val files = files.map { it.toEts() } + return EtsScene(files) +} + +fun ProtoFile.toEts(): EtsFile { + val fileSignature = signature!!.toEts() + return EtsFile( + signature = fileSignature, + classes = classes.map { it.toEts(fileSignature) }, + namespaces = namespaces.map { it.toEts(fileSignature) }, + // TODO: importInfos + // TODO: exportInfos + ) +} + +fun ProtoNamespace.toEts( + fileSignature: EtsFileSignature, +): EtsNamespace { + val namespaceSignature = signature!!.toEts(fileSignature) + return EtsNamespace( + signature = namespaceSignature, + classes = classes.map { it.toEts(fileSignature) }, + namespaces = namespaces.map { it.toEts(fileSignature) }, + ) +} + +fun ProtoClass.toEts( + fileSignature: EtsFileSignature, +): EtsClass { + val classSignature = signature!!.toEts(fileSignature) + return EtsClassImpl( + signature = classSignature, + fields = fields.map { it.toEts(classSignature) }, + methods = methods.map { it.toEts(classSignature) }, + category = EtsClassCategory.entries[category], + superClassName = super_class_name.takeIf { it != "" }, + implementedInterfaceNames = implemented_interface_names, + typeParameters = type_parameters.map { it.toEts() }, + modifiers = EtsModifiers(modifiers), + decorators = decorators.map { it.toEts() }, + ) +} + +fun ProtoField.toEts( + classSignature: EtsClassSignature, +): EtsField { + return EtsFieldImpl( + signature = signature!!.toEts(classSignature), + modifiers = EtsModifiers(modifiers), + isOptional = is_optional, + isDefinitelyAssigned = is_definitely_assigned, + ) +} + +fun ProtoMethod.toEts( + classSignature: EtsClassSignature, +): EtsMethod { + val method = EtsMethodImpl( + signature = signature!!.toEts(classSignature), + typeParameters = type_parameters.map { it.toEts() }, + modifiers = EtsModifiers(modifiers), + decorators = decorators.map { it.toEts() }, + ) + val cfg = cfg + if (cfg != null) { + method._cfg = cfg.toEts(method) + } + return method +} + +fun ProtoDecorator.toEts(): EtsDecorator { + return EtsDecorator( + name = kind, + ) +} + +// TODO: import and export infos +// fun ProtoImportInfo.toEts(): EtsImportInfo +// fun ProtoExportInfo.toEts(): EtsExportInfo + +// endregion model + +// region signatures + +fun ProtoFileSignature.toEts(): EtsFileSignature { + return EtsFileSignature( + projectName = project_name, + fileName = file_name, + ) +} + +fun ProtoNamespaceSignature.toEts( + fileSignature: EtsFileSignature, +): EtsNamespaceSignature { + return EtsNamespaceSignature( + name = name, + file = fileSignature, + namespace = parent?.toEts(fileSignature) + ) +} + +fun ProtoClassSignature.toEts( + fileSignature: EtsFileSignature? = null, +): EtsClassSignature { + val file = fileSignature + ?: file_?.toEts() + ?: EtsFileSignature.UNKNOWN + return EtsClassSignature( + name = name, + file = file, + namespace = namespace?.toEts(file), + ) +} + +fun ProtoFieldSignature.toEts( + classSignature: EtsClassSignature? = null, +): EtsFieldSignature { + return EtsFieldSignature( + enclosingClass = classSignature + ?: enclosing_class?.toEts() + ?: EtsClassSignature.UNKNOWN, + name = name, + type = type!!.toEts(), + ) +} + +fun ProtoMethodSignature.toEts( + classSignature: EtsClassSignature? = null, +): EtsMethodSignature { + return EtsMethodSignature( + enclosingClass = classSignature + ?: enclosing_class?.toEts() + ?: EtsClassSignature.UNKNOWN, + name = name, + parameters = parameters.mapIndexed { i, p -> p.toEts(i) }, + returnType = returnType!!.toEts(), + ) +} + +fun ProtoMethodParameter.toEts(index: Int): EtsMethodParameter { + return EtsMethodParameter( + index = index, + name = name, + type = type!!.toEts(), + isOptional = isOptional, + isRest = isRest, + ) +} + +// endregion signatures + +// region types + +fun ProtoType.toEts(): EtsType { + return when { + raw_type != null -> { + EtsRawType( + kind = raw_type!!.kind, + // TODO: extra + ) + } + + any_type != null -> { + EtsAnyType + } + + unknown_type != null -> { + EtsUnknownType + } + + union_type != null -> { + EtsUnionType( + types = union_type!!.types.map { it.toEts() }, + ) + } + + intersection_type != null -> { + EtsIntersectionType( + types = intersection_type!!.types.map { it.toEts() }, + ) + } + + generic_type != null -> { + EtsGenericType( + typeName = generic_type!!.type_name, + constraint = generic_type!!.constraint?.toEts(), + defaultType = generic_type!!.default_type?.toEts(), + ) + } + + alias_type != null -> { + EtsAliasType( + name = alias_type!!.name, + originalType = alias_type!!.original_type!!.toEts(), + // TODO: signature + // signature = aliasType!!.signature!!.toEts(), + signature = EtsLocalSignature( + "", EtsMethodSignature( + EtsClassSignature.UNKNOWN, "", emptyList(), + EtsUnknownType + ) + ), + ) + } + + boolean_type != null -> { + EtsBooleanType + } + + number_type != null -> { + EtsNumberType + } + + string_type != null -> { + EtsStringType + } + + null_type != null -> { + EtsNullType + } + + undefined_type != null -> { + EtsUndefinedType + } + + void_type != null -> { + EtsVoidType + } + + never_type != null -> { + EtsNeverType + } + + literal_type != null -> { + EtsLiteralType( + literalTypeName = literal_type!!.literal_name, + ) + } + + class_type != null -> { + EtsClassType( + signature = class_type!!.signature!!.toEts(), + typeParameters = class_type!!.type_parameters.map { it.toEts() }, + ) + } + + unclear_ref_type != null -> { + EtsUnclearRefType( + name = unclear_ref_type!!.name, + typeParameters = unclear_ref_type!!.type_parameters.map { it.toEts() }, + ) + } + + array_type != null -> { + EtsArrayType( + elementType = array_type!!.element_type!!.toEts(), + dimensions = array_type!!.dimensions, + ) + } + + tuple_type != null -> { + EtsTupleType( + types = tuple_type!!.types.map { it.toEts() }, + ) + } + + function_type != null -> { + EtsFunctionType( + signature = function_type!!.signature!!.toEts(), + ) + } + + else -> { + error("Unsupported type: $this") + } + } +} + +// endregion types + +// region cfg + +fun ProtoBlockCfg.toEts(method: EtsMethod): EtsBlockCfg { + return CfgBuilder(method).build(this) +} + +internal class CfgBuilder( + val method: EtsMethod, +) { + private lateinit var currentStmts: MutableList + + private var freeTempLocal: Int = 0 + private fun newTempLocal(type: EtsType): EtsLocal { + return EtsLocal( + name = "_tmp${freeTempLocal++}", + type = type, + ) + } + + private fun loc(): EtsStmtLocation { + return EtsStmtLocation.stub(method) + } + + private var built: Boolean = false + + fun build(cfg: ProtoBlockCfg): EtsBlockCfg { + require(!built) { "Method has already been built" } + val etsCfg = cfg.toEts() + built = true + return etsCfg + } + + fun ProtoBlockCfg.toEts(): EtsBlockCfg { + if (blocks.isEmpty()) { + return EtsBlockCfg.EMPTY + } + return EtsBlockCfg( + blocks = blocks.map { block -> + currentStmts = mutableListOf() + for (stmt in block.statements) { + currentStmts += stmt.toEts() + } + if (currentStmts.isEmpty()) { + currentStmts += EtsNopStmt(loc()) + } + BasicBlock(block.id, currentStmts) + }, + // Note: in AA, successors for IF stmts are (false, true) branches, + // however in all our CFGs we use (true, false) order. + successors = blocks.associate { it.id to it.successors.asReversed() }, + ) + } + + private fun ensureLocal(value: EtsEntity): EtsLocal { + if (value is EtsLocal) { + return value + } + val local = newTempLocal(value.type) + currentStmts += EtsAssignStmt( + location = loc(), + lhv = local, + rhv = value, + ) + return local + } + + // region statements + + fun ProtoStmt.toEts(): EtsStmt = when { + raw_stmt != null -> { + EtsRawStmt( + location = loc(), + kind = raw_stmt!!.kind, + // TODO: handle .text + ) + } + + nop_stmt != null -> { + EtsNopStmt( + location = loc(), + ) + } + + assign_stmt != null -> { + EtsAssignStmt( + location = loc(), + lhv = assign_stmt!!.lhv!!.toEts() as EtsLValue, + rhv = assign_stmt!!.rhv!!.toEts(), + ) + } + + return_stmt != null -> { + EtsReturnStmt( + location = loc(), + returnValue = return_stmt!!.return_value?.let { + ensureLocal(it.toEts()) + }, + ) + } + + throw_stmt != null -> { + EtsThrowStmt( + location = loc(), + exception = ensureLocal(throw_stmt!!.exception!!.toEts()), + ) + } + + if_stmt != null -> { + EtsIfStmt( + location = loc(), + condition = ensureLocal(if_stmt!!.condition!!.toEts()), + ) + } + + call_stmt != null -> { + EtsCallStmt( + location = loc(), + expr = call_stmt!!.expr!!.toEts(), + ) + } + + else -> { + error("Unsupported statement: $this") + } + } + + // endregion statements + + // region values + + // TODO: extract each branch to `.toEts()` method + // For example, `rawValue != null -> rawValue!!.toEts()` + fun ProtoValue.toEts(): EtsEntity = when { + raw_value != null -> { + EtsRawEntity( + kind = raw_value!!.kind, + extra = mapOf("text" to raw_value!!.text), + ) + } + + local != null -> { + local!!.toEts() + } + + constant != null -> { + val type = constant!!.type!!.toEts() + when (type) { + EtsStringType -> EtsStringConstant(value = constant!!.value_) + EtsBooleanType -> EtsBooleanConstant(value = constant!!.value_.toBoolean()) + EtsNumberType -> EtsNumberConstant(value = constant!!.value_.toDouble()) + EtsNullType -> EtsNullConstant + EtsUndefinedType -> EtsUndefinedConstant + else -> object : EtsConstant { + val value: String = constant!!.value_ + override val type: EtsType = type + override fun toString(): String = value + override fun accept(visitor: EtsValue.Visitor): R { + return visitor.visit(this) + } + } + } + } + + expr != null -> { + expr!!.toEts() + } + + ref != null -> { + ref!!.toEts() + } + + else -> { + error("Unsupported value: $this") + } + } + + fun ProtoLocal.toEts(): EtsLocal { + return EtsLocal( + name = name, + type = type!!.toEts(), + ) + } + + // region expressions + + fun ProtoExpr.toEts(): EtsExpr = when { + new_expr != null -> { + EtsNewExpr( + type = new_expr!!.type!!.toEts(), + ) + } + + new_array_expr != null -> { + EtsNewArrayExpr( + elementType = new_array_expr!!.element_type!!.toEts(), + size = ensureLocal(new_array_expr!!.size!!.toEts()), + ) + } + + delete_expr != null -> { + EtsDeleteExpr( + arg = delete_expr!!.arg!!.toEts(), + ) + } + + await_expr != null -> { + EtsAwaitExpr( + arg = await_expr!!.arg!!.toEts(), + type = await_expr!!.type!!.toEts(), + ) + } + + yield_expr != null -> { + EtsYieldExpr( + arg = yield_expr!!.arg!!.toEts(), + type = yield_expr!!.type!!.toEts(), + ) + } + + type_of_expr != null -> { + EtsTypeOfExpr( + arg = type_of_expr!!.arg!!.toEts(), + ) + } + + instance_of_expr != null -> { + EtsInstanceOfExpr( + arg = instance_of_expr!!.arg!!.toEts(), + checkType = instance_of_expr!!.check_type!!.toEts(), + ) + } + + cast_expr != null -> { + EtsCastExpr( + arg = cast_expr!!.arg!!.toEts(), + type = cast_expr!!.type!!.toEts(), + ) + } + + unary_expr != null -> { + unary_expr!!.toEts() + } + + binary_expr != null -> { + binary_expr!!.toEts() + } + + relation_expr != null -> { + relation_expr!!.toEts() + } + + call_expr != null -> { + call_expr!!.toEts() + } + + else -> { + error("Unsupported expr: $this") + } + } + + fun ProtoUnaryExpr.toEts(): EtsUnaryExpr { + return when (op) { + ProtoUnaryOperator.NEG -> { + EtsNegExpr( + arg = arg!!.toEts(), + type = type!!.toEts(), + ) + } + + ProtoUnaryOperator.LOGICAL_NOT -> { + EtsNotExpr( + arg = arg!!.toEts(), + ) + } + + ProtoUnaryOperator.BITWISE_NOT -> { + EtsBitNotExpr( + arg = arg!!.toEts(), + type = type!!.toEts(), + ) + } + + else -> { + error("Unsupported unary operator: $op") + } + } + } + + fun ProtoBinaryExpr.toEts(): EtsBinaryExpr { + val left = left!!.toEts() + val right = right!!.toEts() + return when (op) { + ProtoBinaryOperator.ADDITION -> { + EtsAddExpr( + left = left, + right = right, + type = type!!.toEts(), + ) + } + + ProtoBinaryOperator.SUBTRACTION -> { + EtsSubExpr( + left = left, + right = right, + type = type!!.toEts(), + ) + } + + ProtoBinaryOperator.MULTIPLICATION -> { + EtsMulExpr( + left = left, + right = right, + type = type!!.toEts(), + ) + } + + ProtoBinaryOperator.DIVISION -> { + EtsDivExpr( + left = left, + right = right, + type = type!!.toEts(), + ) + } + + ProtoBinaryOperator.REMAINDER -> { + EtsRemExpr( + left = left, + right = right, + type = type!!.toEts(), + ) + } + + ProtoBinaryOperator.EXPONENTIATION -> { + EtsExpExpr( + left = left, + right = right, + type = type!!.toEts(), + ) + } + + ProtoBinaryOperator.LEFT_SHIFT -> { + EtsLeftShiftExpr( + left = left, + right = right, + type = type!!.toEts(), + ) + } + + ProtoBinaryOperator.RIGHT_SHIFT -> { + EtsRightShiftExpr( + left = left, + right = right, + type = type!!.toEts(), + ) + } + + ProtoBinaryOperator.UNSIGNED_RIGHT_SHIFT -> { + EtsUnsignedRightShiftExpr( + left = left, + right = right, + type = type!!.toEts(), + ) + } + + ProtoBinaryOperator.BITWISE_AND -> { + EtsBitAndExpr( + left = left, + right = right, + type = type!!.toEts(), + ) + } + + ProtoBinaryOperator.BITWISE_OR -> { + EtsBitOrExpr( + left = left, + right = right, + type = type!!.toEts(), + ) + } + + ProtoBinaryOperator.BITWISE_XOR -> { + EtsBitXorExpr( + left = left, + right = right, + type = type!!.toEts(), + ) + } + + ProtoBinaryOperator.LOGICAL_AND -> { + EtsAndExpr( + left = left, + right = right, + type = type!!.toEts(), + ) + } + + ProtoBinaryOperator.LOGICAL_OR -> { + EtsOrExpr( + left = left, + right = right, + type = type!!.toEts(), + ) + } + + ProtoBinaryOperator.NULLISH_COALESCING -> { + EtsNullishCoalescingExpr( + left = left, + right = right, + type = type!!.toEts(), + ) + } + + else -> { + error("Unsupported binary operator: $op") + } + } + } + + fun ProtoRelationExpr.toEts(): EtsRelationExpr { + val left = left!!.toEts() + val right = right!!.toEts() + return when (op) { + ProtoRelationOperator.EQ -> { + EtsEqExpr( + left = left, + right = right, + ) + } + + ProtoRelationOperator.NEQ -> { + EtsNotEqExpr( + left = left, + right = right, + ) + } + + ProtoRelationOperator.STRICT_EQ -> { + EtsStrictEqExpr( + left = left, + right = right, + ) + } + + ProtoRelationOperator.STRICT_NEQ -> { + EtsStrictNotEqExpr( + left = left, + right = right, + ) + } + + ProtoRelationOperator.LT -> { + EtsLtExpr( + left = left, + right = right, + ) + } + + ProtoRelationOperator.LTE -> { + EtsLtEqExpr( + left = left, + right = right, + ) + } + + ProtoRelationOperator.GT -> { + EtsGtExpr( + left = left, + right = right, + ) + } + + ProtoRelationOperator.GTE -> { + EtsGtEqExpr( + left = left, + right = right, + ) + } + + ProtoRelationOperator.IN -> { + EtsInExpr( + left = left, + right = right, + ) + } + + else -> { + error("Unsupported relation operator: $op") + } + } + } + + fun ProtoCallExpr.toEts(): EtsCallExpr { + val callee = callee!!.toEts() + val args = args.map { ensureLocal(it.toEts()) } + val type = type!!.toEts() + return when { + instance_call != null -> { + EtsInstanceCallExpr( + instance = instance_call!!.instance!!.toEts(), + callee = callee, + args = args, + type = type, + ) + } + + static_call != null -> { + EtsStaticCallExpr( + callee = callee, + args = args, + type = type, + ) + } + + ptr_call != null -> { + EtsPtrCallExpr( + ptr = ensureLocal(ptr_call!!.ptr!!.toEts()), + callee = callee, + args = args, + type = type, + ) + } + + else -> { + error("Unsupported call expr: $this") + } + } + } + + // endregion expressions + + // region references + + fun ProtoRef.toEts(): EtsRef = when { + this_ != null -> { + EtsThis( + type = this_!!.type!!.toEts(), + ) + } + + parameter != null -> { + EtsParameterRef( + index = parameter!!.index, + type = parameter!!.type!!.toEts(), + ) + } + + array_access != null -> { + EtsArrayAccess( + array = array_access!!.array!!.toEts(), + index = ensureLocal(array_access!!.index!!.toEts()), + type = array_access!!.type!!.toEts(), + ) + } + + field_ref != null -> { + when { + field_ref!!.instance != null -> { + EtsInstanceFieldRef( + instance = ensureLocal(field_ref!!.instance!!.instance!!.toEts()), + field = field_ref!!.instance!!.field_!!.toEts(), + type = field_ref!!.instance!!.type!!.toEts(), + ) + } + + field_ref!!.static != null -> { + EtsStaticFieldRef( + field = field_ref!!.static!!.field_!!.toEts(), + type = field_ref!!.static!!.type!!.toEts(), + ) + } + + else -> { + error("Unsupported field ref: $this") + } + } + } + + else -> { + error("Unsupported ref: $this") + } + } + + // endregion references + + // endregion value +} + +// endregion cfg diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/ConvertToProto.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/ConvertToProto.kt new file mode 100644 index 000000000..a133cc5bd --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/ConvertToProto.kt @@ -0,0 +1,163 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * 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 org.jacodb.ets.proto + +import org.jacodb.ets.model.EtsBlockCfg +import org.jacodb.ets.model.EtsClass +import org.jacodb.ets.model.EtsClassSignature +import org.jacodb.ets.model.EtsField +import org.jacodb.ets.model.EtsFieldImpl +import org.jacodb.ets.model.EtsFieldSignature +import org.jacodb.ets.model.EtsFile +import org.jacodb.ets.model.EtsFileSignature +import org.jacodb.ets.model.EtsMethod +import org.jacodb.ets.model.EtsMethodParameter +import org.jacodb.ets.model.EtsMethodSignature +import org.jacodb.ets.model.EtsModifiers +import org.jacodb.ets.model.EtsNamespace +import org.jacodb.ets.model.EtsNamespaceSignature +import org.jacodb.ets.model.EtsScene +import model.Block as ProtoBlock +import model.BlockCfg as ProtoBlockCfg +import model.Class as ProtoClass +import model.ClassSignature as ProtoClassSignature +import model.Field as ProtoField +import model.FieldSignature as ProtoFieldSignature +import model.File as ProtoFile +import model.FileSignature as ProtoFileSignature +import model.Method as ProtoMethod +import model.MethodParameter as ProtoMethodParameter +import model.MethodSignature as ProtoMethodSignature +import model.Namespace as ProtoNamespace +import model.NamespaceSignature as ProtoNamespaceSignature +import model.Scene as ProtoScene + +fun EtsScene.toProto(): ProtoScene { + return ProtoScene( + files = projectFiles.map { it.toProto() }, + sdkFiles = sdkFiles.map { it.toProto() }, + ) +} + +fun EtsFile.toProto(): ProtoFile { + return ProtoFile( + signature = signature.toProto(), + classes = classes.map { it.toProto() }, + namespaces = namespaces.map { it.toProto() }, + ) +} + +fun EtsNamespace.toProto(): ProtoNamespace { + return ProtoNamespace( + signature = signature.toProto(), + classes = classes.map { it.toProto() }, + namespaces = namespaces.map { it.toProto() }, + ) +} + +fun EtsClass.toProto(): ProtoClass { + return ProtoClass( + signature = signature.toProto(), + type_parameters = typeParameters.map { it.toProto() }, + fields = fields.map { it.toProto() }, + methods = methods.map { it.toProto() }, + ) +} + +fun EtsField.toProto(): ProtoField { + check(this is EtsFieldImpl) + return ProtoField( + signature = signature.toProto(), + modifiers = modifiers.toProto(), + is_optional = isOptional, + is_definitely_assigned = isDefinitelyAssigned, + ) +} + +fun EtsMethod.toProto(): ProtoMethod { + return ProtoMethod( + signature = signature.toProto(), + modifiers = modifiers.toProto(), + cfg = cfg.toProto(), + ) +} + +fun EtsFileSignature.toProto(): ProtoFileSignature { + return ProtoFileSignature( + project_name = projectName, + file_name = fileName, + ) +} + +fun EtsNamespaceSignature.toProto(): ProtoNamespaceSignature { + return ProtoNamespaceSignature( + name = name, + file_ = file.toProto(), + parent = namespace?.toProto(), + ) +} + +fun EtsClassSignature.toProto(): ProtoClassSignature { + return ProtoClassSignature( + name = name, + file_ = file.toProto(), + namespace = namespace?.toProto(), + ) +} + +fun EtsMethodSignature.toProto(): ProtoMethodSignature { + return ProtoMethodSignature( + name = name, + enclosing_class = enclosingClass.toProto(), + parameters = parameters.map { it.toProto() }, + returnType = returnType.toProto(), + ) +} + +fun EtsMethodParameter.toProto(): ProtoMethodParameter { + return ProtoMethodParameter( + name = name, + type = type.toProto(), + isOptional = isOptional, + isRest = isRest, + ) +} + +fun EtsFieldSignature.toProto(): ProtoFieldSignature { + return ProtoFieldSignature( + name = name, + enclosing_class = enclosingClass.toProto(), + type = type.toProto(), + ) +} + +fun EtsModifiers.toProto(): Int { + return mask +} + +fun EtsBlockCfg.toProto(): ProtoBlockCfg { + return ProtoBlockCfg( + blocks = blocks.map { block -> + ProtoBlock( + id = block.id, + statements = block.statements.map { stmt -> stmt.toProto() }, + successors = successors.getValue(block.id), + // Note: predecessors are omitted + ) + }, + ) +} diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsEntityToProto.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsEntityToProto.kt new file mode 100644 index 000000000..c2ec9418b --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsEntityToProto.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * 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 org.jacodb.ets.proto + +import org.jacodb.ets.model.EtsEntity +import org.jacodb.ets.model.EtsExpr +import org.jacodb.ets.model.EtsRawEntity +import org.jacodb.ets.model.EtsValue +import model.RawValue as ProtoRawValue +import model.Value as ProtoValue + +fun EtsEntity.toProto(): ProtoValue = accept(EtsEntityToProto) + +internal object EtsEntityToProto : + EtsEntity.Visitor, + EtsValue.Visitor.Default, + EtsExpr.Visitor.Default { + + override fun visit(value: EtsRawEntity): ProtoValue { + val rawValue = ProtoRawValue( + kind = value.kind, + type = value.type.toProto(), + ) + return ProtoValue(raw_value = rawValue) + } + + override fun defaultVisit(value: EtsValue): ProtoValue = value.toProto() + override fun defaultVisit(expr: EtsExpr): ProtoValue = expr.toProto() +} diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsExprToProto.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsExprToProto.kt new file mode 100644 index 000000000..978caad1a --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsExprToProto.kt @@ -0,0 +1,471 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * 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 org.jacodb.ets.proto + +import org.jacodb.ets.model.EtsAddExpr +import org.jacodb.ets.model.EtsAndExpr +import org.jacodb.ets.model.EtsAwaitExpr +import org.jacodb.ets.model.EtsBitAndExpr +import org.jacodb.ets.model.EtsBitNotExpr +import org.jacodb.ets.model.EtsBitOrExpr +import org.jacodb.ets.model.EtsBitXorExpr +import org.jacodb.ets.model.EtsCallExpr +import org.jacodb.ets.model.EtsCastExpr +import org.jacodb.ets.model.EtsDeleteExpr +import org.jacodb.ets.model.EtsDivExpr +import org.jacodb.ets.model.EtsEqExpr +import org.jacodb.ets.model.EtsExpExpr +import org.jacodb.ets.model.EtsExpr +import org.jacodb.ets.model.EtsGtEqExpr +import org.jacodb.ets.model.EtsGtExpr +import org.jacodb.ets.model.EtsInExpr +import org.jacodb.ets.model.EtsInstanceCallExpr +import org.jacodb.ets.model.EtsInstanceOfExpr +import org.jacodb.ets.model.EtsLeftShiftExpr +import org.jacodb.ets.model.EtsLtEqExpr +import org.jacodb.ets.model.EtsLtExpr +import org.jacodb.ets.model.EtsMulExpr +import org.jacodb.ets.model.EtsNegExpr +import org.jacodb.ets.model.EtsNewArrayExpr +import org.jacodb.ets.model.EtsNewExpr +import org.jacodb.ets.model.EtsNotEqExpr +import org.jacodb.ets.model.EtsNotExpr +import org.jacodb.ets.model.EtsNullishCoalescingExpr +import org.jacodb.ets.model.EtsOrExpr +import org.jacodb.ets.model.EtsPostDecExpr +import org.jacodb.ets.model.EtsPostIncExpr +import org.jacodb.ets.model.EtsPreDecExpr +import org.jacodb.ets.model.EtsPreIncExpr +import org.jacodb.ets.model.EtsPtrCallExpr +import org.jacodb.ets.model.EtsRemExpr +import org.jacodb.ets.model.EtsRightShiftExpr +import org.jacodb.ets.model.EtsStaticCallExpr +import org.jacodb.ets.model.EtsStrictEqExpr +import org.jacodb.ets.model.EtsStrictNotEqExpr +import org.jacodb.ets.model.EtsSubExpr +import org.jacodb.ets.model.EtsTypeOfExpr +import org.jacodb.ets.model.EtsUnaryPlusExpr +import org.jacodb.ets.model.EtsUnsignedRightShiftExpr +import org.jacodb.ets.model.EtsValue +import org.jacodb.ets.model.EtsVoidExpr +import org.jacodb.ets.model.EtsYieldExpr +import model.AwaitExpr as ProtoAwaitExpr +import model.BinaryExpr as ProtoBinaryExpr +import model.BinaryOperator as ProtoBinaryOperator +import model.CallExpr as ProtoCallExpr +import model.CastExpr as ProtoCastExpr +import model.DeleteExpr as ProtoDeleteExpr +import model.Expr as ProtoExpr +import model.InstanceCall as ProtoInstanceCall +import model.InstanceOfExpr as ProtoInstanceOfExpr +import model.NewArrayExpr as ProtoNewArrayExpr +import model.NewExpr as ProtoNewExpr +import model.PtrCall as ProtoPtrCall +import model.RelationExpr as ProtoRelationExpr +import model.RelationOperator as ProtoRelationOperator +import model.StaticCall as ProtoStaticCall +import model.TypeOfExpr as ProtoTypeOfExpr +import model.UnaryExpr as ProtoUnaryExpr +import model.UnaryOperator as ProtoUnaryOperator +import model.Value as ProtoValue +import model.YieldExpr as ProtoYieldExpr + +fun EtsExpr.toProto(): ProtoValue = accept(EtsExprToProto) + +fun EtsCallExpr.toProto(): ProtoCallExpr { + val callExpr = ProtoCallExpr( + callee = this.callee.toProto(), + args = this.args.map { (it as EtsValue).toProto() }, + type = this.type.toProto(), + ) + return when (this) { + is EtsInstanceCallExpr -> callExpr.copy(instance_call = ProtoInstanceCall(instance = this.instance.toProto())) + is EtsStaticCallExpr -> callExpr.copy(static_call = ProtoStaticCall()) + is EtsPtrCallExpr -> callExpr.copy(ptr_call = ProtoPtrCall(ptr = (this.ptr as EtsValue).toProto())) + else -> error("Unsupported call expression type: ${this::class.simpleName}") + } +} + +internal object EtsExprToProto : EtsExpr.Visitor { + override fun visit(expr: EtsNewExpr): ProtoValue { + val newExpr = ProtoNewExpr( + type = expr.type.toProto(), + ) + return ProtoValue(expr = ProtoExpr(new_expr = newExpr)) + } + + override fun visit(expr: EtsNewArrayExpr): ProtoValue { + val newArrayExpr = ProtoNewArrayExpr( + element_type = expr.elementType.toProto(), + size = expr.size.toProto(), + ) + return ProtoValue(expr = ProtoExpr(new_array_expr = newArrayExpr)) + } + + override fun visit(expr: EtsCastExpr): ProtoValue { + val castExpr = ProtoCastExpr( + arg = expr.arg.toProto(), + type = expr.type.toProto(), + ) + return ProtoValue(expr = ProtoExpr(cast_expr = castExpr)) + } + + override fun visit(expr: EtsInstanceOfExpr): ProtoValue { + val instanceOfExpr = ProtoInstanceOfExpr( + arg = expr.arg.toProto(), + check_type = expr.checkType.toProto(), + ) + return ProtoValue(expr = ProtoExpr(instance_of_expr = instanceOfExpr)) + } + + override fun visit(expr: EtsDeleteExpr): ProtoValue { + val deleteExpr = ProtoDeleteExpr( + arg = expr.arg.toProto(), + ) + return ProtoValue(expr = ProtoExpr(delete_expr = deleteExpr)) + } + + override fun visit(expr: EtsAwaitExpr): ProtoValue { + val awaitExpr = ProtoAwaitExpr( + arg = expr.arg.toProto(), + ) + return ProtoValue(expr = ProtoExpr(await_expr = awaitExpr)) + } + + override fun visit(expr: EtsYieldExpr): ProtoValue { + val yieldExpr = ProtoYieldExpr( + arg = expr.arg.toProto(), + ) + return ProtoValue(expr = ProtoExpr(yield_expr = yieldExpr)) + } + + override fun visit(expr: EtsTypeOfExpr): ProtoValue { + val typeOfExpr = ProtoTypeOfExpr( + arg = expr.arg.toProto(), + ) + return ProtoValue(expr = ProtoExpr(type_of_expr = typeOfExpr)) + } + + override fun visit(expr: EtsVoidExpr): ProtoValue { + TODO() + } + + override fun visit(expr: EtsNotExpr): ProtoValue { + val notExpr = ProtoUnaryExpr( + op = ProtoUnaryOperator.LOGICAL_NOT, + arg = expr.arg.toProto(), + type = expr.type.toProto() + ) + return ProtoValue(expr = ProtoExpr(unary_expr = notExpr)) + } + + override fun visit(expr: EtsBitNotExpr): ProtoValue { + val bitNotExpr = ProtoUnaryExpr( + op = ProtoUnaryOperator.BITWISE_NOT, + arg = expr.arg.toProto(), + type = expr.type.toProto() + ) + return ProtoValue(expr = ProtoExpr(unary_expr = bitNotExpr)) + } + + override fun visit(expr: EtsNegExpr): ProtoValue { + val negExpr = ProtoUnaryExpr( + op = ProtoUnaryOperator.NEG, + arg = expr.arg.toProto(), + type = expr.type.toProto() + ) + return ProtoValue(expr = ProtoExpr(unary_expr = negExpr)) + } + + override fun visit(expr: EtsUnaryPlusExpr): ProtoValue { + TODO() + } + + override fun visit(expr: EtsPreIncExpr): ProtoValue { + TODO() + } + + override fun visit(expr: EtsPreDecExpr): ProtoValue { + TODO() + } + + override fun visit(expr: EtsPostIncExpr): ProtoValue { + TODO() + } + + override fun visit(expr: EtsPostDecExpr): ProtoValue { + TODO() + } + + override fun visit(expr: EtsEqExpr): ProtoValue { + val eqExpr = ProtoRelationExpr( + op = ProtoRelationOperator.EQ, + left = expr.left.toProto(), + right = expr.right.toProto() + ) + return ProtoValue(expr = ProtoExpr(relation_expr = eqExpr)) + } + + override fun visit(expr: EtsNotEqExpr): ProtoValue { + val notEqExpr = ProtoRelationExpr( + op = ProtoRelationOperator.NEQ, + left = expr.left.toProto(), + right = expr.right.toProto() + ) + return ProtoValue(expr = ProtoExpr(relation_expr = notEqExpr)) + } + + override fun visit(expr: EtsStrictEqExpr): ProtoValue { + val strictEqExpr = ProtoRelationExpr( + op = ProtoRelationOperator.STRICT_EQ, + left = expr.left.toProto(), + right = expr.right.toProto() + ) + return ProtoValue(expr = ProtoExpr(relation_expr = strictEqExpr)) + } + + override fun visit(expr: EtsStrictNotEqExpr): ProtoValue { + val strictNotEqExpr = ProtoRelationExpr( + op = ProtoRelationOperator.STRICT_NEQ, + left = expr.left.toProto(), + right = expr.right.toProto() + ) + return ProtoValue(expr = ProtoExpr(relation_expr = strictNotEqExpr)) + } + + override fun visit(expr: EtsLtExpr): ProtoValue { + val ltExpr = ProtoRelationExpr( + op = ProtoRelationOperator.LT, + left = expr.left.toProto(), + right = expr.right.toProto() + ) + return ProtoValue(expr = ProtoExpr(relation_expr = ltExpr)) + } + + override fun visit(expr: EtsLtEqExpr): ProtoValue { + val ltEqExpr = ProtoRelationExpr( + op = ProtoRelationOperator.LTE, + left = expr.left.toProto(), + right = expr.right.toProto() + ) + return ProtoValue(expr = ProtoExpr(relation_expr = ltEqExpr)) + } + + override fun visit(expr: EtsGtExpr): ProtoValue { + val gtExpr = ProtoRelationExpr( + op = ProtoRelationOperator.GT, + left = expr.left.toProto(), + right = expr.right.toProto() + ) + return ProtoValue(expr = ProtoExpr(relation_expr = gtExpr)) + } + + override fun visit(expr: EtsGtEqExpr): ProtoValue { + val gtEqExpr = ProtoRelationExpr( + op = ProtoRelationOperator.GTE, + left = expr.left.toProto(), + right = expr.right.toProto() + ) + return ProtoValue(expr = ProtoExpr(relation_expr = gtEqExpr)) + } + + override fun visit(expr: EtsInExpr): ProtoValue { + val inExpr = ProtoRelationExpr( + op = ProtoRelationOperator.IN, + left = expr.left.toProto(), + right = expr.right.toProto() + ) + return ProtoValue(expr = ProtoExpr(relation_expr = inExpr)) + } + + override fun visit(expr: EtsAddExpr): ProtoValue { + val addExpr = ProtoBinaryExpr( + op = ProtoBinaryOperator.ADDITION, + left = expr.left.toProto(), + right = expr.right.toProto(), + type = expr.type.toProto() + ) + return ProtoValue(expr = ProtoExpr(binary_expr = addExpr)) + } + + override fun visit(expr: EtsSubExpr): ProtoValue { + val subExpr = ProtoBinaryExpr( + op = ProtoBinaryOperator.SUBTRACTION, + left = expr.left.toProto(), + right = expr.right.toProto(), + type = expr.type.toProto() + ) + return ProtoValue(expr = ProtoExpr(binary_expr = subExpr)) + } + + override fun visit(expr: EtsMulExpr): ProtoValue { + val mulExpr = ProtoBinaryExpr( + op = ProtoBinaryOperator.MULTIPLICATION, + left = expr.left.toProto(), + right = expr.right.toProto(), + type = expr.type.toProto() + ) + return ProtoValue(expr = ProtoExpr(binary_expr = mulExpr)) + } + + override fun visit(expr: EtsDivExpr): ProtoValue { + val divExpr = ProtoBinaryExpr( + op = ProtoBinaryOperator.DIVISION, + left = expr.left.toProto(), + right = expr.right.toProto(), + type = expr.type.toProto() + ) + return ProtoValue(expr = ProtoExpr(binary_expr = divExpr)) + } + + override fun visit(expr: EtsRemExpr): ProtoValue { + val remExpr = ProtoBinaryExpr( + op = ProtoBinaryOperator.REMAINDER, + left = expr.left.toProto(), + right = expr.right.toProto(), + type = expr.type.toProto() + ) + return ProtoValue(expr = ProtoExpr(binary_expr = remExpr)) + } + + override fun visit(expr: EtsExpExpr): ProtoValue { + val expExpr = ProtoBinaryExpr( + op = ProtoBinaryOperator.EXPONENTIATION, + left = expr.left.toProto(), + right = expr.right.toProto(), + type = expr.type.toProto() + ) + return ProtoValue(expr = ProtoExpr(binary_expr = expExpr)) + } + + override fun visit(expr: EtsBitAndExpr): ProtoValue { + val bitAndExpr = ProtoBinaryExpr( + op = ProtoBinaryOperator.BITWISE_AND, + left = expr.left.toProto(), + right = expr.right.toProto(), + type = expr.type.toProto() + ) + return ProtoValue(expr = ProtoExpr(binary_expr = bitAndExpr)) + } + + override fun visit(expr: EtsBitOrExpr): ProtoValue { + val bitOrExpr = ProtoBinaryExpr( + op = ProtoBinaryOperator.BITWISE_OR, + left = expr.left.toProto(), + right = expr.right.toProto(), + type = expr.type.toProto() + ) + return ProtoValue(expr = ProtoExpr(binary_expr = bitOrExpr)) + } + + override fun visit(expr: EtsBitXorExpr): ProtoValue { + val bitXorExpr = ProtoBinaryExpr( + op = ProtoBinaryOperator.BITWISE_XOR, + left = expr.left.toProto(), + right = expr.right.toProto(), + type = expr.type.toProto() + ) + return ProtoValue(expr = ProtoExpr(binary_expr = bitXorExpr)) + } + + override fun visit(expr: EtsLeftShiftExpr): ProtoValue { + val leftShiftExpr = ProtoBinaryExpr( + op = ProtoBinaryOperator.LEFT_SHIFT, + left = expr.left.toProto(), + right = expr.right.toProto(), + type = expr.type.toProto() + ) + return ProtoValue(expr = ProtoExpr(binary_expr = leftShiftExpr)) + } + + override fun visit(expr: EtsRightShiftExpr): ProtoValue { + val rightShiftExpr = ProtoBinaryExpr( + op = ProtoBinaryOperator.RIGHT_SHIFT, + left = expr.left.toProto(), + right = expr.right.toProto(), + type = expr.type.toProto() + ) + return ProtoValue(expr = ProtoExpr(binary_expr = rightShiftExpr)) + } + + override fun visit(expr: EtsUnsignedRightShiftExpr): ProtoValue { + val unsignedRightShiftExpr = ProtoBinaryExpr( + op = ProtoBinaryOperator.UNSIGNED_RIGHT_SHIFT, + left = expr.left.toProto(), + right = expr.right.toProto(), + type = expr.type.toProto() + ) + return ProtoValue(expr = ProtoExpr(binary_expr = unsignedRightShiftExpr)) + } + + override fun visit(expr: EtsAndExpr): ProtoValue { + val andExpr = ProtoBinaryExpr( + op = ProtoBinaryOperator.LOGICAL_AND, + left = expr.left.toProto(), + right = expr.right.toProto(), + type = expr.type.toProto() + ) + return ProtoValue(expr = ProtoExpr(binary_expr = andExpr)) + } + + override fun visit(expr: EtsOrExpr): ProtoValue { + val orExpr = ProtoBinaryExpr( + op = ProtoBinaryOperator.LOGICAL_OR, + left = expr.left.toProto(), + right = expr.right.toProto(), + type = expr.type.toProto() + ) + return ProtoValue(expr = ProtoExpr(binary_expr = orExpr)) + } + + override fun visit(expr: EtsNullishCoalescingExpr): ProtoValue { + val nullishCoalescingExpr = ProtoBinaryExpr( + op = ProtoBinaryOperator.NULLISH_COALESCING, + left = expr.left.toProto(), + right = expr.right.toProto(), + type = expr.type.toProto() + ) + return ProtoValue(expr = ProtoExpr(binary_expr = nullishCoalescingExpr)) + } + + override fun visit(expr: EtsInstanceCallExpr): ProtoValue { + val instanceCallExpr = ProtoCallExpr( + callee = expr.callee.toProto(), + args = expr.args.map { (it as EtsValue).toProto() }, + type = expr.type.toProto(), + instance_call = ProtoInstanceCall( + instance = expr.instance.toProto(), + ), + ) + return ProtoValue(expr = ProtoExpr(call_expr = instanceCallExpr)) + } + + override fun visit(expr: EtsStaticCallExpr): ProtoValue { + val staticCallExpr = ProtoCallExpr( + callee = expr.callee.toProto(), + args = expr.args.map { (it as EtsValue).toProto() }, + type = expr.type.toProto(), + static_call = ProtoStaticCall(), + ) + return ProtoValue(expr = ProtoExpr(call_expr = staticCallExpr)) + } + + override fun visit(expr: EtsPtrCallExpr): ProtoValue { + val ptrCallExpr = expr.toProto() + return ProtoValue(expr = ProtoExpr(call_expr = ptrCallExpr)) + } +} diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsStmtToProto.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsStmtToProto.kt new file mode 100644 index 000000000..b639b3e91 --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsStmtToProto.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * 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 org.jacodb.ets.proto + +import org.jacodb.ets.model.EtsAssignStmt +import org.jacodb.ets.model.EtsCallStmt +import org.jacodb.ets.model.EtsIfStmt +import org.jacodb.ets.model.EtsNopStmt +import org.jacodb.ets.model.EtsRawStmt +import org.jacodb.ets.model.EtsReturnStmt +import org.jacodb.ets.model.EtsStmt +import org.jacodb.ets.model.EtsThrowStmt +import org.jacodb.ets.model.EtsValue +import model.AssignStmt as ProtoAssignStmt +import model.CallStmt as ProtoCallStmt +import model.IfStmt as ProtoIfStmt +import model.NopStmt as ProtoNopStmt +import model.RawStmt as ProtoRawStmt +import model.ReturnStmt as ProtoReturnStmt +import model.Stmt as ProtoStmt +import model.ThrowStmt as ProtoThrowStmt + +fun EtsStmt.toProto(): ProtoStmt = accept(EtsStmtToProto) + +internal object EtsStmtToProto : EtsStmt.Visitor { + override fun visit(stmt: EtsRawStmt): ProtoStmt { + val rawStmt = ProtoRawStmt( + kind = stmt.kind, + ) + return ProtoStmt(raw_stmt = rawStmt) + } + + override fun visit(stmt: EtsNopStmt): ProtoStmt { + val nopStmt = ProtoNopStmt() + return ProtoStmt(nop_stmt = nopStmt) + } + + override fun visit(stmt: EtsAssignStmt): ProtoStmt { + val assignStmt = ProtoAssignStmt( + lhv = stmt.lhv.toProto(), + rhv = stmt.rhv.toProto(), + ) + return ProtoStmt(assign_stmt = assignStmt) + } + + override fun visit(stmt: EtsReturnStmt): ProtoStmt { + val returnStmt = ProtoReturnStmt( + return_value = stmt.returnValue?.toProto() + ) + return ProtoStmt(return_stmt = returnStmt) + } + + override fun visit(stmt: EtsThrowStmt): ProtoStmt { + val throwStmt = ProtoThrowStmt( + exception = (stmt.exception as EtsValue).toProto() + ) + return ProtoStmt(throw_stmt = throwStmt) + } + + override fun visit(stmt: EtsIfStmt): ProtoStmt { + val ifStmt = ProtoIfStmt( + condition = (stmt.condition as EtsValue).toProto() + ) + return ProtoStmt(if_stmt = ifStmt) + } + + override fun visit(stmt: EtsCallStmt): ProtoStmt { + val callStmt = ProtoCallStmt( + expr = stmt.expr.toProto() + ) + return ProtoStmt(call_stmt = callStmt) + } +} diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsTypeToProto.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsTypeToProto.kt new file mode 100644 index 000000000..fb70d3795 --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsTypeToProto.kt @@ -0,0 +1,197 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * 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 org.jacodb.ets.proto + +import org.jacodb.ets.model.EtsAliasType +import org.jacodb.ets.model.EtsAnyType +import org.jacodb.ets.model.EtsArrayType +import org.jacodb.ets.model.EtsBooleanType +import org.jacodb.ets.model.EtsClassType +import org.jacodb.ets.model.EtsEnumValueType +import org.jacodb.ets.model.EtsFunctionType +import org.jacodb.ets.model.EtsGenericType +import org.jacodb.ets.model.EtsIntersectionType +import org.jacodb.ets.model.EtsLiteralType +import org.jacodb.ets.model.EtsNeverType +import org.jacodb.ets.model.EtsNullType +import org.jacodb.ets.model.EtsNumberType +import org.jacodb.ets.model.EtsRawType +import org.jacodb.ets.model.EtsStringType +import org.jacodb.ets.model.EtsTupleType +import org.jacodb.ets.model.EtsType +import org.jacodb.ets.model.EtsUnclearRefType +import org.jacodb.ets.model.EtsUndefinedType +import org.jacodb.ets.model.EtsUnionType +import org.jacodb.ets.model.EtsUnknownType +import org.jacodb.ets.model.EtsVoidType +import model.AliasType as ProtoAliasType +import model.AnyType as ProtoAnyType +import model.ArrayType as ProtoArrayType +import model.BooleanType as ProtoBooleanType +import model.ClassType as ProtoClassType +import model.FunctionType as ProtoFunctionType +import model.GenericType as ProtoGenericType +import model.IntersectionType as ProtoIntersectionType +import model.LiteralType as ProtoLiteralType +import model.NeverType as ProtoNeverType +import model.NullType as ProtoNullType +import model.NumberType as ProtoNumberType +import model.StringType as ProtoStringType +import model.TupleType as ProtoTupleType +import model.Type as ProtoType +import model.UnclearRefType as ProtoUnclearRefType +import model.UndefinedType as ProtoUndefinedType +import model.UnionType as ProtoUnionType +import model.UnknownType as ProtoUnknownType +import model.VoidType as ProtoVoidType + +fun EtsType.toProto(): ProtoType = accept(EtsTypeToProto) + +internal object EtsTypeToProto : EtsType.Visitor { + override fun visit(type: EtsRawType): ProtoType { + // NOTE: !!! + val unknownType = ProtoUnknownType() + return ProtoType(unknown_type = unknownType) + } + + override fun visit(type: EtsAnyType): ProtoType { + val anyType = ProtoAnyType() + return ProtoType(any_type = anyType) + } + + override fun visit(type: EtsUnknownType): ProtoType { + val unknownType = ProtoUnknownType() + return ProtoType(unknown_type = unknownType) + } + + override fun visit(type: EtsUnionType): ProtoType { + val unionType = ProtoUnionType( + types = type.types.map { it.toProto() }, + ) + return ProtoType(union_type = unionType) + } + + override fun visit(type: EtsIntersectionType): ProtoType { + val intersectionType = ProtoIntersectionType( + types = type.types.map { it.toProto() }, + ) + return ProtoType(intersection_type = intersectionType) + } + + override fun visit(type: EtsGenericType): ProtoType { + val genericType = ProtoGenericType( + type_name = type.typeName, + default_type = type.defaultType?.toProto(), + constraint = type.constraint?.toProto() + ) + return ProtoType(generic_type = genericType) + } + + override fun visit(type: EtsAliasType): ProtoType { + val aliasType = ProtoAliasType( + name = type.name, + original_type = type.originalType.toProto(), + // TODO: local signature + ) + return ProtoType(alias_type = aliasType) + } + + override fun visit(type: EtsEnumValueType): ProtoType { + TODO("${type::class.java.simpleName} is not supported yet") + } + + override fun visit(type: EtsBooleanType): ProtoType { + val booleanType = ProtoBooleanType() + return ProtoType(boolean_type = booleanType) + } + + override fun visit(type: EtsNumberType): ProtoType { + val numberType = ProtoNumberType() + return ProtoType(number_type = numberType) + } + + override fun visit(type: EtsStringType): ProtoType { + val stringType = ProtoStringType() + return ProtoType(string_type = stringType) + } + + override fun visit(type: EtsNullType): ProtoType { + val nullType = ProtoNullType() + return ProtoType(null_type = nullType) + } + + override fun visit(type: EtsUndefinedType): ProtoType { + val undefinedType = ProtoUndefinedType() + return ProtoType(undefined_type = undefinedType) + } + + override fun visit(type: EtsVoidType): ProtoType { + val voidType = ProtoVoidType() + return ProtoType(void_type = voidType) + } + + override fun visit(type: EtsNeverType): ProtoType { + val neverType = ProtoNeverType() + return ProtoType(never_type = neverType) + } + + override fun visit(type: EtsLiteralType): ProtoType { + val literalType = ProtoLiteralType( + literal_name = type.literalTypeName, + ) + return ProtoType(literal_type = literalType) + } + + override fun visit(type: EtsClassType): ProtoType { + val classType = ProtoClassType( + signature = type.signature.toProto(), + type_parameters = type.typeParameters.map { it.toProto() }, + ) + return ProtoType(class_type = classType) + } + + override fun visit(type: EtsUnclearRefType): ProtoType { + val unclearRefType = ProtoUnclearRefType( + name = type.typeName, + type_parameters = type.typeParameters.map { it.toProto() }, + ) + return ProtoType(unclear_ref_type = unclearRefType) + } + + override fun visit(type: EtsArrayType): ProtoType { + val arrayType = ProtoArrayType( + element_type = type.elementType.toProto(), + dimensions = type.dimensions, + ) + return ProtoType(array_type = arrayType) + } + + override fun visit(type: EtsTupleType): ProtoType { + val tupleType = ProtoTupleType( + types = type.types.map { it.toProto() }, + ) + return ProtoType(tuple_type = tupleType) + } + + override fun visit(type: EtsFunctionType): ProtoType { + val functionType = ProtoFunctionType( + signature = type.signature.toProto(), + type_parameters = type.typeParameters.map { it.toProto() }, + ) + return ProtoType(function_type = functionType) + } +} diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsValueToProto.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsValueToProto.kt new file mode 100644 index 000000000..6fe35b113 --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsValueToProto.kt @@ -0,0 +1,130 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * 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. + */ + +@file:Suppress("LocalVariableName") + +package org.jacodb.ets.proto + +import org.jacodb.ets.model.EtsArrayAccess +import org.jacodb.ets.model.EtsBooleanConstant +import org.jacodb.ets.model.EtsConstant +import org.jacodb.ets.model.EtsInstanceFieldRef +import org.jacodb.ets.model.EtsLocal +import org.jacodb.ets.model.EtsNullConstant +import org.jacodb.ets.model.EtsNumberConstant +import org.jacodb.ets.model.EtsParameterRef +import org.jacodb.ets.model.EtsStaticFieldRef +import org.jacodb.ets.model.EtsStringConstant +import org.jacodb.ets.model.EtsThis +import org.jacodb.ets.model.EtsUndefinedConstant +import org.jacodb.ets.model.EtsValue +import model.ArrayAccess as ProtoArrayAccess +import model.Constant as ProtoConstant +import model.FieldRef as ProtoFieldRef +import model.InstanceFieldRef as ProtoInstanceFieldRef +import model.Local as ProtoLocal +import model.ParameterRef as ProtoParameterRef +import model.Ref as ProtoRef +import model.StaticFieldRef as ProtoStaticFieldRef +import model.This as ProtoThis +import model.Value as ProtoValue + +fun EtsValue.toProto(): ProtoValue = accept(EtsValueToProto) + +internal object EtsValueToProto : EtsValue.Visitor { + override fun visit(value: EtsLocal): ProtoValue { + val local = value.toProto() + return ProtoValue(local = local) + } + + private fun visitConstant(value: EtsConstant): ProtoValue { + val constant = value.toProto() + return ProtoValue(constant = constant) + } + + override fun visit(value: EtsConstant): ProtoValue { + return visitConstant(value) + } + + override fun visit(value: EtsStringConstant): ProtoValue { + return visitConstant(value) + } + + override fun visit(value: EtsBooleanConstant): ProtoValue { + return visitConstant(value) + } + + override fun visit(value: EtsNumberConstant): ProtoValue { + return visitConstant(value) + } + + override fun visit(value: EtsNullConstant): ProtoValue { + return visitConstant(value) + } + + override fun visit(value: EtsUndefinedConstant): ProtoValue { + return visitConstant(value) + } + + override fun visit(value: EtsThis): ProtoValue { + val this_ = ProtoThis(type = value.type.toProto()) + return ProtoValue(ref = ProtoRef(this_ = this_)) + } + + override fun visit(value: EtsParameterRef): ProtoValue { + val paramRef = ProtoParameterRef( + index = value.index, + type = value.type.toProto(), + ) + return ProtoValue(ref = ProtoRef(parameter = paramRef)) + } + + override fun visit(value: EtsArrayAccess): ProtoValue { + val arrayAccess = ProtoArrayAccess( + array = value.array.toProto(), + index = value.index.toProto(), + type = value.type.toProto(), + ) + return ProtoValue(ref = ProtoRef(array_access = arrayAccess)) + } + + override fun visit(value: EtsInstanceFieldRef): ProtoValue { + val instanceFieldRef = ProtoInstanceFieldRef( + instance = value.instance.toProto(), + field_ = value.field.toProto(), + type = value.type.toProto(), + ) + return ProtoValue(ref = ProtoRef(field_ref = ProtoFieldRef(instance = instanceFieldRef))) + } + + override fun visit(value: EtsStaticFieldRef): ProtoValue { + val staticFieldRef = ProtoStaticFieldRef( + field_ = value.field.toProto(), + type = value.type.toProto(), + ) + return ProtoValue(ref = ProtoRef(field_ref = ProtoFieldRef(static = staticFieldRef))) + } +} + +fun EtsLocal.toProto(): ProtoLocal = ProtoLocal( + name = this.name, + type = this.type.toProto(), +) + +fun EtsConstant.toProto(): ProtoConstant = ProtoConstant( + value_ = this.toString(), + type = this.type.toProto() +) diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/BlockCfgBuilder.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/BlockCfgBuilder.kt index d039a0c80..d4423d426 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/BlockCfgBuilder.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/BlockCfgBuilder.kt @@ -31,19 +31,11 @@ import org.jacodb.ets.dsl.Parameter import org.jacodb.ets.dsl.ThisRef import org.jacodb.ets.dsl.UnaryExpr import org.jacodb.ets.dsl.UnaryOperator -import org.jacodb.ets.dsl.add -import org.jacodb.ets.dsl.and -import org.jacodb.ets.dsl.const -import org.jacodb.ets.dsl.local -import org.jacodb.ets.dsl.param -import org.jacodb.ets.dsl.program -import org.jacodb.ets.dsl.toBlockCfg import org.jacodb.ets.model.BasicBlock import org.jacodb.ets.model.EtsAddExpr import org.jacodb.ets.model.EtsAndExpr import org.jacodb.ets.model.EtsAssignStmt import org.jacodb.ets.model.EtsBlockCfg -import org.jacodb.ets.model.EtsClassSignature import org.jacodb.ets.model.EtsDivExpr import org.jacodb.ets.model.EtsEntity import org.jacodb.ets.model.EtsEqExpr @@ -54,9 +46,6 @@ import org.jacodb.ets.model.EtsLocal import org.jacodb.ets.model.EtsLtEqExpr import org.jacodb.ets.model.EtsLtExpr import org.jacodb.ets.model.EtsMethod -import org.jacodb.ets.model.EtsMethodImpl -import org.jacodb.ets.model.EtsMethodParameter -import org.jacodb.ets.model.EtsMethodSignature import org.jacodb.ets.model.EtsMulExpr import org.jacodb.ets.model.EtsNegExpr import org.jacodb.ets.model.EtsNopStmt diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/EtsFileToText.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/EtsFileToText.kt index f15b4714a..b178871c4 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/EtsFileToText.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/EtsFileToText.kt @@ -26,7 +26,8 @@ fun EtsFile.toText(): String { lines += " typeParameters = ${clazz.typeParameters}" lines += " modifiers = ${clazz.modifiers}" lines += " decorators = ${clazz.decorators}" - lines += " superClass = '${clazz.superClass}'" + lines += " superClass = '${clazz.superClassName}'" + lines += " interfaces = ${clazz.implementedInterfaceNames}" lines += " fields: ${clazz.fields.size}" clazz.fields.forEach { field -> lines += " - FIELD '${field.signature}'" diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/EtsFileDtoToDot.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/FileDtoToDot.kt similarity index 97% rename from jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/EtsFileDtoToDot.kt rename to jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/FileDtoToDot.kt index f4f0a441a..7e61692a2 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/EtsFileDtoToDot.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/FileDtoToDot.kt @@ -18,7 +18,7 @@ package org.jacodb.ets.utils import org.jacodb.ets.dto.BasicBlockDto import org.jacodb.ets.dto.ClassDto -import org.jacodb.ets.dto.EtsFileDto +import org.jacodb.ets.dto.FileDto import org.jacodb.ets.dto.IfStmtDto import org.jacodb.ets.dto.MethodDto import org.jacodb.ets.dto.NopStmtDto @@ -28,7 +28,7 @@ import java.nio.file.Path import kotlin.io.path.createDirectories import kotlin.io.path.writeText -fun EtsFileDto.toDot(useLR: Boolean = false): String { +fun FileDto.toDot(useLR: Boolean = false): String { val lines: MutableList = mutableListOf() lines += "digraph {" if (useLR) { @@ -187,15 +187,15 @@ fun EtsFileDto.toDot(useLR: Boolean = false): String { return lines.joinToString("\n") } -fun EtsFileDto.dumpDot(path: Path) { +fun FileDto.dumpDot(path: Path) { path.parent?.createDirectories() path.writeText(toDot()) } -fun EtsFileDto.dumpDot(file: File) { +fun FileDto.dumpDot(file: File) { dumpDot(file.toPath()) } -fun EtsFileDto.dumpDot(path: String) { +fun FileDto.dumpDot(path: String) { dumpDot(File(path)) } diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/EtsFileDtoToText.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/FileDtoToText.kt similarity index 95% rename from jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/EtsFileDtoToText.kt rename to jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/FileDtoToText.kt index 61d293995..6ac5e1c34 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/EtsFileDtoToText.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/FileDtoToText.kt @@ -16,11 +16,11 @@ package org.jacodb.ets.utils -import org.jacodb.ets.dto.EtsFileDto +import org.jacodb.ets.dto.FileDto -fun EtsFileDto.toText(): String { +fun FileDto.toText(): String { val lines: MutableList = mutableListOf() - lines += "EtsFileDto '${signature}':" + lines += "FileDto '${signature}':" classes.forEach { clazz -> lines += "= CLASS '${clazz.signature}':" lines += " superClass = '${clazz.superClassName}'" diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/LoadEtsFile.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/Loaders.kt similarity index 62% rename from jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/LoadEtsFile.kt rename to jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/Loaders.kt index eb2402dea..9f3661738 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/LoadEtsFile.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/Loaders.kt @@ -17,7 +17,7 @@ package org.jacodb.ets.utils import mu.KotlinLogging -import org.jacodb.ets.dto.EtsFileDto +import org.jacodb.ets.dto.FileDto import org.jacodb.ets.dto.toEtsFile import org.jacodb.ets.model.EtsFile import org.jacodb.ets.model.EtsScene @@ -33,6 +33,7 @@ import kotlin.io.path.extension import kotlin.io.path.inputStream import kotlin.io.path.nameWithoutExtension import kotlin.io.path.pathString +import kotlin.io.path.relativeTo import kotlin.io.path.walk import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -81,6 +82,7 @@ fun generateEtsIR( createTempFile(projectPath.nameWithoutExtension, suffix = ".json") } + logger.info { "Generating IR for '$projectPath'..." } val cmd = listOfNotNull( node, script.pathString, @@ -121,8 +123,8 @@ fun loadEtsFileAutoConvert( useArkAnalyzerTypeInference = useArkAnalyzerTypeInference, ) irFilePath.inputStream().use { stream -> - val etsFileDto = EtsFileDto.loadFromJson(stream) - return etsFileDto.toEtsFile() + val fileDto = FileDto.loadFromJson(stream) + return fileDto.toEtsFile() } } @@ -164,9 +166,110 @@ private val walker = { dir: Path -> .filter { it.extension == "json" } .map { it.inputStream().use { stream -> - val etsFileDto = EtsFileDto.loadFromJson(stream) - etsFileDto.toEtsFile() + val fileDto = FileDto.loadFromJson(stream) + fileDto.toEtsFile() } } .toList() } + +/** + * Load an [FileDto] from a resource file. + * + * For example, `resources/ets/sample.json` can be loaded with: + * ``` + * val dto: FileDto = loadFileDtoFromResource("/ets/sample.json") + * ``` + */ +fun loadFileDtoFromResource(jsonPath: String): FileDto { + logger.debug { "Loading EtsIR from resource: '$jsonPath'" } + require(jsonPath.endsWith(".json")) { "File must have a '.json' extension: '$jsonPath'" } + getResourceStream(jsonPath).use { stream -> + return FileDto.loadFromJson(stream) + } +} + +/** + * Load an [EtsFile] from a resource file. + * + * For example, `resources/ets/sample.json` can be loaded with: + * ``` + * val file: EtsFile = loadEtsFileFromResource("/ets/sample.json") + * ``` + */ +fun loadEtsFileFromResource(jsonPath: String): EtsFile { + val fileDto = loadFileDtoFromResource(jsonPath) + return fileDto.toEtsFile() +} + +/** + * Load multiple [EtsFile]s from a resource directory. + * + * For example, all files in `resources/project/` can be loaded with: + * ``` + * val files: Sequence = loadMultipleEtsFilesFromResourceDirectory("/project") + * ``` + */ +fun loadMultipleEtsFilesFromResourceDirectory(dirPath: String): Sequence { + val rootPath = getResourcePath(dirPath) + return rootPath.walk().filter { it.extension == "json" }.map { path -> + loadEtsFileFromResource("$dirPath/${path.relativeTo(rootPath)}") + } +} + +fun loadMultipleEtsFilesFromMultipleResourceDirectories( + dirPaths: List, +): Sequence { + return dirPaths.asSequence().flatMap { loadMultipleEtsFilesFromResourceDirectory(it) } +} + +fun loadEtsProjectFromResources( + modules: List, + prefix: String, +): EtsScene { + logger.info { "Loading project with ${modules.size} modules $modules from '$prefix/'" } + val dirPaths = modules.map { "$prefix/$it" } + val files = loadMultipleEtsFilesFromMultipleResourceDirectories(dirPaths).toList() + logger.info { "Loaded ${files.size} files" } + return EtsScene(files, sdkFiles = emptyList()) +} + +/** + * Load an [FileDto] from a file. + * + * For example, `data/sample.json` can be loaded with: + * ``` + * val dto: FileDto = loadFileDto(Path("data/sample.json")) + * ``` + */ +fun loadFileDto(path: Path): FileDto { + require(path.extension == "json") { "File must have a '.json' extension: $path" } + path.inputStream().use { stream -> + return FileDto.loadFromJson(stream) + } +} + +/** + * Load an [EtsFile] from a file. + * + * For example, `data/sample.json` can be loaded with: + * ``` + * val file: EtsFile = loadEtsFile(Path("data/sample.json")) + * ``` + */ +fun loadEtsFile(path: Path): EtsFile { + val fileDto = loadFileDto(path) + return fileDto.toEtsFile() +} + +/** + * Load multiple [EtsFile]s from a directory. + * + * For example, all files in `data` can be loaded with: + * ``` + * val files: Sequence = loadMultipleEtsFilesFromDirectory(Path("data")) + * ``` + */ +fun loadMultipleEtsFilesFromDirectory(dirPath: Path): Sequence { + return dirPath.walk().filter { it.extension == "json" }.map { loadEtsFile(it) } +} diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/ProcessUtil.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/ProcessUtil.kt index 30f6d64d8..c5e272cbb 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/ProcessUtil.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/ProcessUtil.kt @@ -18,6 +18,7 @@ package org.jacodb.ets.utils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import mu.KotlinLogging @@ -37,20 +38,12 @@ object ProcessUtil { fun run( command: List, - input: String? = null, - timeout: Duration? = null, - ): Result { - val reader = input?.reader() ?: "".reader() - return run(command, reader, timeout) - } - - fun run( - command: List, - input: Reader, + input: Reader = "".reader(), timeout: Duration? = null, + builder: ProcessBuilder.() -> Unit = {}, ): Result { logger.debug { "Running command: $command" } - val process = ProcessBuilder(command).start() + val process = ProcessBuilder(command).apply(builder).start() return communicate(process, input, timeout) } @@ -90,10 +83,10 @@ object ProcessUtil { process.waitFor() false } + + // Wait for all coroutines to finish runBlocking { - stdinJob.join() - stdoutJob.join() - stderrJob.join() + joinAll(stdinJob, stdoutJob, stderrJob) } return Result( diff --git a/jacodb-ets/src/testFixtures/kotlin/org/jacodb/ets/test/utils/Resources.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/Resource.kt similarity index 97% rename from jacodb-ets/src/testFixtures/kotlin/org/jacodb/ets/test/utils/Resources.kt rename to jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/Resource.kt index f70b994e3..8b3ea07f2 100644 --- a/jacodb-ets/src/testFixtures/kotlin/org/jacodb/ets/test/utils/Resources.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/Resource.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jacodb.ets.test.utils +package org.jacodb.ets.utils import java.io.InputStream import java.nio.file.Path diff --git a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsFileTest.kt b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsFileTest.kt index 045c44336..9b17209df 100644 --- a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsFileTest.kt +++ b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsFileTest.kt @@ -24,9 +24,9 @@ import org.jacodb.ets.model.EtsNumberConstant import org.jacodb.ets.model.EtsReturnStmt import org.jacodb.ets.model.EtsStaticFieldRef import org.jacodb.ets.model.EtsThis -import org.jacodb.ets.test.utils.loadEtsFileFromResource import org.jacodb.ets.utils.INSTANCE_INIT_METHOD_NAME import org.jacodb.ets.utils.STATIC_INIT_METHOD_NAME +import org.jacodb.ets.utils.loadEtsFileFromResource import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs diff --git a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsFromJsonTest.kt b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsFromJsonTest.kt index 093d201ae..2d1fa7be1 100644 --- a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsFromJsonTest.kt +++ b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsFromJsonTest.kt @@ -51,14 +51,15 @@ import org.jacodb.ets.model.EtsReturnStmt import org.jacodb.ets.model.EtsScene import org.jacodb.ets.model.EtsStmtLocation import org.jacodb.ets.model.EtsUnknownType -import org.jacodb.ets.test.utils.getResourcePath -import org.jacodb.ets.test.utils.getResourcePathOrNull -import org.jacodb.ets.test.utils.loadEtsFileFromResource -import org.jacodb.ets.test.utils.loadEtsProjectFromResources import org.jacodb.ets.test.utils.testFactory import org.jacodb.ets.utils.DEFAULT_ARK_CLASS_NAME import org.jacodb.ets.utils.DEFAULT_ARK_METHOD_NAME +import org.jacodb.ets.utils.getResourcePath +import org.jacodb.ets.utils.getResourcePathOrNull import org.jacodb.ets.utils.loadEtsFileAutoConvert +import org.jacodb.ets.utils.loadEtsFileFromResource +import org.jacodb.ets.utils.loadEtsProjectAutoConvert +import org.jacodb.ets.utils.loadEtsProjectFromResources import org.junit.jupiter.api.Assumptions import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestFactory @@ -72,6 +73,9 @@ import kotlin.io.path.relativeTo import kotlin.io.path.walk import kotlin.test.assertEquals import kotlin.test.assertIs +import kotlin.test.assertTrue +import kotlin.time.DurationUnit +import kotlin.time.measureTimedValue private val logger = KotlinLogging.logger {} @@ -165,12 +169,11 @@ class EtsFromJsonTest { logger.warn { "No sample files found" } return@testFactory } - container("load ${availableFiles.size} files") { - for (path in availableFiles) { - test("load $path") { - val file = loadEtsFileFromResource("$prefix/etsir/ast/$path.json") - printFile(file, showStmts = true) - } + // container("load ${availableFiles.size} files") { + for (path in availableFiles) { + test("load $path") { + val file = loadEtsFileFromResource("$prefix/etsir/ast/$path.json") + printFile(file, showStmts = true) } } } @@ -197,13 +200,11 @@ class EtsFromJsonTest { logger.warn { "No sample files found" } return@testFactory } - container("auto-load ${availableFiles.size} files") { - for (path in availableFiles) { - test("load $path") { - val p = getResourcePath("$prefix/$path") - val file = loadEtsFileAutoConvert(p) - printFile(file, showStmts = true) - } + for (path in availableFiles) { + test("load $path") { + val p = getResourcePath("$prefix/$path") + val file = loadEtsFileAutoConvert(p) + printFile(file, showStmts = true) } } } @@ -218,6 +219,26 @@ class EtsFromJsonTest { printProject(project) } + @Test + fun testLoadEtsProjectAutoConvert() { + val res = "/projects/Photos/source" + Assumptions.assumeTrue(projectAvailable(res)) { "Project not available: $res" } + val path = getResourcePath(res) + val (scene, time) = measureTimedValue { + loadEtsProjectAutoConvert(path) + } + logger.info { + "Loaded project from '$res' with ${ + scene.projectFiles.size + } files, ${ + scene.projectAndSdkClasses.size + } classes, ${ + scene.projectAndSdkClasses.sumOf { it.methods.size } + } methods in %.1f seconds".format(time.toDouble(DurationUnit.SECONDS)) + } + // printProject(project) + } + @TestFactory fun testLoadAllAvailableEtsProjects() = testFactory { val base = getResourcePathOrNull("/projects") ?: run { @@ -240,11 +261,10 @@ class EtsFromJsonTest { logger.warn { "No projects found" } return@testFactory } - container("load ${availableProjectNames.size} projects") { - for (projectName in availableProjectNames) { - test("load $projectName") { - dynamicLoadEtsProject(projectName) - } + // container("load ${availableProjectNames.size} projects") { + for (projectName in availableProjectNames) { + test("load $projectName") { + dynamicLoadEtsProject(projectName) } } } @@ -270,6 +290,38 @@ class EtsFromJsonTest { printProject(project) } + @TestFactory + fun testLoadAllAvailableEtsProjectsAutoConvert() = testFactory { + val base = getResourcePathOrNull("/projects") ?: run { + logger.warn { "No projects directory found in resources" } + return@testFactory + } + val availableProjectNames = base.listDirectoryEntries() + .filter { it.isDirectory() } + .map { it.name } + .sorted() + logger.info { + buildString { + appendLine("Found ${availableProjectNames.size} projects") + for (name in availableProjectNames) { + appendLine(" - $name") + } + } + } + if (availableProjectNames.isEmpty()) { + logger.warn { "No projects found" } + return@testFactory + } + // container("load ${availableProjectNames.size} projects") { + for (projectName in availableProjectNames) { + test("load $projectName") { + val projectPath = getResourcePath("/projects/$projectName/source") + val project = loadEtsProjectAutoConvert(projectPath) + assertTrue(project.projectClasses.isNotEmpty()) + } + } + } + @Test fun testLoadValueFromJson() { val jsonString = """ diff --git a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/GrpcTest.kt b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/GrpcTest.kt new file mode 100644 index 000000000..e736917b1 --- /dev/null +++ b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/GrpcTest.kt @@ -0,0 +1,156 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * 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 org.jacodb.ets.test + +import greeter.GreeterClient +import greeter.HelloRequest +import mu.KotlinLogging +import org.jacodb.ets.grpc.Server +import org.jacodb.ets.grpc.loadScene +import org.jacodb.ets.grpc.startArkAnalyzerServer +import org.jacodb.ets.model.EtsScene +import org.jacodb.ets.proto.toEts +import org.jacodb.ets.proto.toProto +import org.jacodb.ets.service.createGrpcClient +import org.jacodb.ets.test.utils.assumeNotNull +import org.jacodb.ets.test.utils.testFactory +import org.jacodb.ets.utils.getResourcePath +import org.jacodb.ets.utils.getResourcePathOrNull +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.TestFactory +import java.nio.file.Path +import kotlin.io.path.isDirectory +import kotlin.io.path.listDirectoryEntries +import kotlin.io.path.name +import kotlin.test.Test +import kotlin.time.DurationUnit +import kotlin.time.measureTimedValue + +private val logger = KotlinLogging.logger {} + +class GrpcTest { + companion object { + private const val PORT = 50100 + + private lateinit var server: Server + + @BeforeAll + @JvmStatic + fun beforeAll() { + logger.info { "Setting up test environment..." } + server = startArkAnalyzerServer(PORT) + logger.info { "Done setting up test environment" } + } + + @AfterAll + @JvmStatic + fun afterAll() { + logger.info { "Shutting down test environment..." } + server.stop() + logger.info { "Test environment shut down" } + } + + fun getScene(path: Path): EtsScene { + val scene = loadScene(PORT, path) + logger.info { "Converting Scene from ProtoBuf to ETS..." } + val (etsScene, timeConvert) = measureTimedValue { + scene.toEts() + } + logger.info { + "Done converting Scene in %.1fs" + .format(timeConvert.toDouble(DurationUnit.SECONDS)) + } + return etsScene + } + } + + @Test + fun `test Greeter`() { + val greeter = createGrpcClient(PORT) + val name = "Kotlin" + val request = HelloRequest(name = name) + logger.info { "Sending $request" } + val response = greeter.SayHello().executeBlocking(request) + logger.info { "Received $response" } + assertTrue(response.message.isNotEmpty()) { "Response message should not be empty" } + assertTrue(response.message.contains(name)) { "Response message should contain the name '$name'" } + } + + @Test + fun `load example`() { + val res = "/samples/source/example.ts" + val path = getResourcePath(res) + val scene = getScene(path) + assertTrue(scene.projectClasses.isNotEmpty()) + } + + @Test + fun `load project`() { + val res = "/projects/Photos/source" + val path = getResourcePathOrNull(res) + assumeNotNull(path) { "Project not available: $res" } + val scene = getScene(path) + assertTrue(scene.projectClasses.isNotEmpty()) + } + + @TestFactory + fun `load all available project`() = testFactory { + val prefix = "/projects" + val base = getResourcePathOrNull(prefix) ?: run { + logger.warn { "No projects directory found in resources" } + return@testFactory + } + val availableProjects = base + .listDirectoryEntries() + .filter { it.isDirectory() } + .map { it.name } + .sorted() + logger.info { + buildString { + appendLine("Found ${availableProjects.size} projects") + for (path in availableProjects) { + appendLine(" - $path") + } + } + } + if (availableProjects.isEmpty()) { + logger.warn { "No projects found" } + return@testFactory + } + // container("load ${availableProjects.size} projects") { + for (projectName in availableProjects) { + test("load $projectName") { + val timeStart = System.currentTimeMillis() + + val p = getResourcePath("$prefix/$projectName/source") + val scene = getScene(p) + assertTrue { scene.projectClasses.isNotEmpty() } + + logger.info { "Converting loaded scene to ProtoBuf..." } + val proto = scene.toProto() + assertTrue { proto.files.isNotEmpty() } + + logger.info { + "Done processing project $projectName in %.1fs" + .format((System.currentTimeMillis() - timeStart) / 1000.0) + } + } + } + } +} diff --git a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/WireTest.kt b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/WireTest.kt new file mode 100644 index 000000000..a99ee7bb7 --- /dev/null +++ b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/WireTest.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * 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 org.jacodb.ets.test + +import greeter.GreeterClient +import greeter.HelloRequest +import mu.KotlinLogging +import org.jacodb.ets.service.GreeterService +import org.jacodb.ets.service.createGrpcClient +import org.jacodb.ets.service.grpcServer +import kotlin.test.Test + +private val logger = KotlinLogging.logger {} + +class WireTest { + companion object { + private const val PORT = 7777 + } + + @Test + fun `test Greeter`() { + val server = grpcServer(PORT) { + addService(GreeterService()) + } + server.start() + logger.info { "Server listening on port ${server.port}" } + + val client = createGrpcClient(PORT) + + val request = HelloRequest(name = "Kotlin") + logger.info { "Sending $request" } + val response = client.SayHello().executeBlocking(request) + logger.info { "Received $response" } + + logger.info { "Shutting down server..." } + server.shutdown() + server.awaitTermination() + logger.info { "Server shut down" } + } +} diff --git a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/utils/Assumptions.kt b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/utils/Assumptions.kt new file mode 100644 index 000000000..80c3c16a9 --- /dev/null +++ b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/utils/Assumptions.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * 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 org.jacodb.ets.test.utils + +import org.junit.jupiter.api.Assumptions +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +@OptIn(ExperimentalContracts::class) +fun assumeNotNull(value: Any?, messageSupplier: () -> String) { + contract { + returns() implies (value != null) + } + Assumptions.assumeTrue(value != null, messageSupplier) +} diff --git a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/utils/Entrypoints.kt b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/utils/Entrypoints.kt index 899e031ec..6c82d3891 100644 --- a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/utils/Entrypoints.kt +++ b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/utils/Entrypoints.kt @@ -16,10 +16,13 @@ package org.jacodb.ets.test.utils -import org.jacodb.ets.dto.EtsFileDto +import org.jacodb.ets.dto.FileDto import org.jacodb.ets.dto.toEtsFile import org.jacodb.ets.model.EtsFile import org.jacodb.ets.utils.dumpDot +import org.jacodb.ets.utils.getResourcePath +import org.jacodb.ets.utils.loadEtsFileFromResource +import org.jacodb.ets.utils.loadFileDtoFromResource import org.jacodb.ets.utils.render import org.jacodb.ets.utils.toText import kotlin.io.path.Path @@ -32,9 +35,9 @@ import kotlin.io.path.walk private val logger = mu.KotlinLogging.logger {} /** - * Visualize classes and methods in [EtsFileDto]. + * Visualize classes and methods in [FileDto]. */ -object DumpEtsFileDtoToDot { +object DumpFileDtoToDot { private const val NAME = "basic" private const val PATH = "/etsir/samples/$NAME.ts.json" private val DOT_DIR = Path("dot") @@ -42,12 +45,12 @@ object DumpEtsFileDtoToDot { @JvmStatic fun main(args: Array) { - val etsFileDto: EtsFileDto = loadEtsFileDtoFromResource(PATH) + val fileDto: FileDto = loadFileDtoFromResource(PATH) - val text = etsFileDto.toText() - logger.info { "Text representation of EtsFileDto:\n$text" } + val text = fileDto.toText() + logger.info { "Text representation of FileDto:\n$text" } - etsFileDto.dumpDot(DOT_DIR / DOT_PATH) + fileDto.dumpDot(DOT_DIR / DOT_PATH) render(DOT_DIR, DOT_PATH) } } @@ -74,7 +77,7 @@ object DumpEtsFileToDot { } /** - * Visualize classes and methods in [EtsFileDto] and [EtsFile] from directory. + * Visualize classes and methods in [FileDto] and [EtsFile] from directory. */ object DumpEtsFilesToDot { // private const val ETSIR = "/projects/applications_app_samples/etsir/ast/ArkTSDistributedCalc" @@ -97,14 +100,14 @@ object DumpEtsFilesToDot { .forEach { path -> logger.info { "Processing: $path" } - val etsFileDto = loadEtsFileDtoFromResource("$ETSIR/$path") + val fileDto = loadFileDtoFromResource("$ETSIR/$path") run { val dotPath = DOT_DIR / path.resolveSibling(path.nameWithoutExtension + ".dto.dot") - etsFileDto.dumpDot(dotPath) + fileDto.dumpDot(dotPath) render(DOT_DIR, dotPath.relativeTo(DOT_DIR)) } - val etsFile = etsFileDto.toEtsFile() + val etsFile = fileDto.toEtsFile() run { val dotPath = DOT_DIR / path.resolveSibling(path.nameWithoutExtension + ".dot") etsFile.dumpDot(dotPath) diff --git a/jacodb-ets/src/test/resources/prepare_projects.sh b/jacodb-ets/src/test/resources/prepare_projects.sh index 57c52ef82..c29256b95 100644 --- a/jacodb-ets/src/test/resources/prepare_projects.sh +++ b/jacodb-ets/src/test/resources/prepare_projects.sh @@ -279,6 +279,20 @@ function prepare_module() { prepare_module "component" "$REPO/features" ) +( + prepare_project_dir "Photos" + + REPO="../../repos/applications_photos" + check_repo $REPO + + prepare_module "common" "$REPO/common" + prepare_module "browser" "$REPO/feature/browser" + prepare_module "editor" "$REPO/feature/editor" + prepare_module "formAbility" "$REPO/feature/formAbility" + prepare_module "thirdselect" "$REPO/feature/thirdselect" + prepare_module "timeline" "$REPO/feature/timeline" +) + ( prepare_project_dir "PrintSpooler" diff --git a/jacodb-ets/src/test/resources/prepare_repos.sh b/jacodb-ets/src/test/resources/prepare_repos.sh index f8db30f07..bd768e191 100644 --- a/jacodb-ets/src/test/resources/prepare_repos.sh +++ b/jacodb-ets/src/test/resources/prepare_repos.sh @@ -31,6 +31,7 @@ prepare_repo https://gitcode.com/openharmony/applications_hap prepare_repo https://gitcode.com/openharmony/applications_launcher prepare_repo https://gitcode.com/openharmony/applications_mms prepare_repo https://gitcode.com/openharmony/applications_notes +prepare_repo https://gitcode.com/openharmony/applications_photos prepare_repo https://gitcode.com/openharmony/applications_print_spooler prepare_repo https://gitcode.com/openharmony/applications_screenlock prepare_repo https://gitcode.com/openharmony/applications_settings diff --git a/jacodb-ets/src/testFixtures/kotlin/org/jacodb/ets/test/utils/LoadEts.kt b/jacodb-ets/src/testFixtures/kotlin/org/jacodb/ets/test/utils/LoadEts.kt deleted file mode 100644 index 10b4cb2b7..000000000 --- a/jacodb-ets/src/testFixtures/kotlin/org/jacodb/ets/test/utils/LoadEts.kt +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * 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 org.jacodb.ets.test.utils - -import mu.KotlinLogging -import org.jacodb.ets.dto.EtsFileDto -import org.jacodb.ets.dto.toEtsFile -import org.jacodb.ets.model.EtsFile -import org.jacodb.ets.model.EtsScene -import java.nio.file.Path -import kotlin.io.path.extension -import kotlin.io.path.inputStream -import kotlin.io.path.relativeTo -import kotlin.io.path.walk - -private val logger = KotlinLogging.logger {} - -/** - * Load an [EtsFileDto] from a resource file. - * - * For example, `resources/ets/sample.json` can be loaded with: - * ``` - * val dto: EtsFileDto = loadEtsFileDtoFromResource("/ets/sample.json") - * ``` - */ -fun loadEtsFileDtoFromResource(jsonPath: String): EtsFileDto { - logger.debug { "Loading EtsIR from resource: '$jsonPath'" } - require(jsonPath.endsWith(".json")) { "File must have a '.json' extension: '$jsonPath'" } - getResourceStream(jsonPath).use { stream -> - return EtsFileDto.loadFromJson(stream) - } -} - -/** - * Load an [EtsFile] from a resource file. - * - * For example, `resources/ets/sample.json` can be loaded with: - * ``` - * val file: EtsFile = loadEtsFileFromResource("/ets/sample.json") - * ``` - */ -fun loadEtsFileFromResource(jsonPath: String): EtsFile { - val etsFileDto = loadEtsFileDtoFromResource(jsonPath) - return etsFileDto.toEtsFile() -} - -/** - * Load multiple [EtsFile]s from a resource directory. - * - * For example, all files in `resources/project/` can be loaded with: - * ``` - * val files: Sequence = loadMultipleEtsFilesFromResourceDirectory("/project") - * ``` - */ -fun loadMultipleEtsFilesFromResourceDirectory(dirPath: String): Sequence { - val rootPath = getResourcePath(dirPath) - return rootPath.walk().filter { it.extension == "json" }.map { path -> - loadEtsFileFromResource("$dirPath/${path.relativeTo(rootPath)}") - } -} - -fun loadMultipleEtsFilesFromMultipleResourceDirectories( - dirPaths: List, -): Sequence { - return dirPaths.asSequence().flatMap { loadMultipleEtsFilesFromResourceDirectory(it) } -} - -fun loadEtsProjectFromResources( - modules: List, - prefix: String, -): EtsScene { - logger.info { "Loading project with ${modules.size} modules $modules from '$prefix/'" } - val dirPaths = modules.map { "$prefix/$it" } - val files = loadMultipleEtsFilesFromMultipleResourceDirectories(dirPaths).toList() - logger.info { "Loaded ${files.size} files" } - return EtsScene(files, sdkFiles = emptyList()) -} - -//----------------------------------------------------------------------------- - -/** - * Load an [EtsFileDto] from a file. - * - * For example, `data/sample.json` can be loaded with: - * ``` - * val dto: EtsFileDto = loadEtsFileDto(Path("data/sample.json")) - * ``` - */ -fun loadEtsFileDto(path: Path): EtsFileDto { - require(path.extension == "json") { "File must have a '.json' extension: $path" } - path.inputStream().use { stream -> - return EtsFileDto.loadFromJson(stream) - } -} - -/** - * Load an [EtsFile] from a file. - * - * For example, `data/sample.json` can be loaded with: - * ``` - * val file: EtsFile = loadEtsFile(Path("data/sample.json")) - * ``` - */ -fun loadEtsFile(path: Path): EtsFile { - val etsFileDto = loadEtsFileDto(path) - return etsFileDto.toEtsFile() -} - -/** - * Load multiple [EtsFile]s from a directory. - * - * For example, all files in `data` can be loaded with: - * ``` - * val files: Sequence = loadMultipleEtsFilesFromDirectory(Path("data")) - * ``` - */ -fun loadMultipleEtsFilesFromDirectory(dirPath: Path): Sequence { - return dirPath.walk().filter { it.extension == "json" }.map { loadEtsFile(it) } -} diff --git a/jacodb-ets/wire-client/build.gradle.kts b/jacodb-ets/wire-client/build.gradle.kts new file mode 100644 index 000000000..60d998d86 --- /dev/null +++ b/jacodb-ets/wire-client/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + id(Plugins.Wire) +} + +dependencies { + protoSource(project(":jacodb-ets:wire-protos")) + api(Libs.wire_grpc_client) +} + +wire { + kotlin { + rpcRole = "client" + } +} diff --git a/jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/GrpcClient.kt b/jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/GrpcClient.kt new file mode 100644 index 000000000..8896988f0 --- /dev/null +++ b/jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/GrpcClient.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * 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 org.jacodb.ets.service + +import com.squareup.wire.GrpcClient +import com.squareup.wire.Service +import okhttp3.OkHttpClient +import okhttp3.Protocol + +const val DEFAULT_PORT = 7777 + +/** + * Creates a gRPC client with the specified port. + * + * ### Example: + * ```kotlin + * val client = grpcClient(7777) + * ``` + * + * ### Note: + * You probably want to use [createGrpcClient] instead, + * which is more convenient for creating specific clients. + * + * @param port The port on which the gRPC server is running. + * @return A configured [GrpcClient] instance. + */ +fun grpcClient(port: Int = DEFAULT_PORT): GrpcClient { + val okClient = OkHttpClient.Builder() + .protocols(listOf(Protocol.H2_PRIOR_KNOWLEDGE)) + .build() + return GrpcClient.Builder() + .client(okClient) + .baseUrl("http://0.0.0.0:$port") + .build() +} + +/** + * Creates a gRPC client for the specified service type. + * + * ### Example: + * ```kotlin + * val client = createGrpcClient(7777) + * ``` + * + * @param port The port on which the gRPC server is running. + * @return A configured instance of the specified service type [T]. + */ +inline fun createGrpcClient(port: Int = DEFAULT_PORT): T { + return grpcClient(port).create() +} diff --git a/jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/TestGreeter.kt b/jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/TestGreeter.kt new file mode 100644 index 000000000..8936d28a3 --- /dev/null +++ b/jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/TestGreeter.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * 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 org.jacodb.ets.service + +import greeter.GreeterClient +import greeter.HelloRequest + +fun main() { + val port = 50051 + val greeter = createGrpcClient(port) + val request = HelloRequest(name = "Kotlin") + val response = greeter.SayHello().executeBlocking(request) + println("Response: \"${response.message}\"") +} diff --git a/jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/TestManager.kt b/jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/TestManager.kt new file mode 100644 index 000000000..ab9a85745 --- /dev/null +++ b/jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/TestManager.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * 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 org.jacodb.ets.service + +import manager.GetSceneRequest +import manager.ManagerClient + +fun main() { + val port = 50051 + val manager = createGrpcClient(port) + val path = "TODO" + val request = GetSceneRequest(path) + val scene = manager.GetScene().executeBlocking(request) + println("scene = $scene") +} diff --git a/jacodb-ets/wire-protos/build.gradle.kts b/jacodb-ets/wire-protos/build.gradle.kts new file mode 100644 index 000000000..8637df70a --- /dev/null +++ b/jacodb-ets/wire-protos/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + id(Plugins.Wire) +} + +wire { + protoLibrary = true + kotlin { + rpcRole = "none" + } +} diff --git a/jacodb-ets/wire-protos/src/main/proto/greeter.proto b/jacodb-ets/wire-protos/src/main/proto/greeter.proto new file mode 100644 index 000000000..0bfca42c5 --- /dev/null +++ b/jacodb-ets/wire-protos/src/main/proto/greeter.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; +package greeter; + +option java_multiple_files = true; + +// The greeter service definition. +service Greeter { + // Sends a greeting + rpc SayHello(HelloRequest) returns (HelloReply); +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} diff --git a/jacodb-ets/wire-protos/src/main/proto/manager.proto b/jacodb-ets/wire-protos/src/main/proto/manager.proto new file mode 100644 index 000000000..afc7d66c8 --- /dev/null +++ b/jacodb-ets/wire-protos/src/main/proto/manager.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; +package manager; + +option java_multiple_files = true; + +import "model.proto"; + +service Manager { + rpc GetScene(GetSceneRequest) returns (model.Scene); +} + +message GetSceneRequest { + string path = 1; + optional bool infer_types = 2; +} diff --git a/jacodb-ets/wire-protos/src/main/proto/model.proto b/jacodb-ets/wire-protos/src/main/proto/model.proto new file mode 100644 index 000000000..9f9d98d6b --- /dev/null +++ b/jacodb-ets/wire-protos/src/main/proto/model.proto @@ -0,0 +1,461 @@ +syntax = "proto3"; +package model; + +option java_multiple_files = true; + +message Scene { + repeated File files = 1; + repeated File sdkFiles = 2; +} + +message File { + FileSignature signature = 1; + repeated Class classes = 2; + repeated Namespace namespaces = 3; + repeated ImportInfo importInfos = 5; + repeated ExportInfo exportInfos = 6; +} + +message Namespace { + NamespaceSignature signature = 1; + repeated Class classes = 2; + repeated Namespace namespaces = 3; +} + +message Class { + ClassSignature signature = 1; + int32 modifiers = 2; + repeated Decorator decorators = 3; + int32 category = 4; + repeated Type type_parameters = 5; + string super_class_name = 6; + repeated string implemented_interface_names = 7; + repeated Field fields = 8; + repeated Method methods = 9; +} + +message Field { + FieldSignature signature = 1; + int32 modifiers = 2; + repeated Decorator decorators = 3; + bool is_optional = 4; + bool is_definitely_assigned = 5; +} + +message Method { + MethodSignature signature = 1; + repeated Type type_parameters = 2; + int32 modifiers = 3; + repeated Decorator decorators = 4; + BlockCfg cfg = 5; +} + +message Decorator { + string kind = 1; +} + +message ImportInfo { + string import_clause_name = 1; + string import_type = 2; + optional string import_from = 3; + int32 modifiers = 4; + optional string name_before_as = 5; +} + +message ExportInfo { + string export_clause_name = 1; + int32 export_clause_type = 2; + optional string export_from = 3; + int32 modifiers = 4; + optional string name_before_as = 5; +} + +message FileSignature { + string project_name = 1; + string file_name = 2; +} + +message NamespaceSignature { + string name = 1; + FileSignature file = 2; + optional NamespaceSignature parent = 3; +} + +message ClassSignature { + string name = 1; + FileSignature file = 2; + optional NamespaceSignature namespace = 3; +} + +message FieldSignature { + ClassSignature enclosing_class = 1; + string name = 2; + Type type = 3; +} + +message MethodSignature { + ClassSignature enclosing_class = 1; + string name = 2; + repeated MethodParameter parameters = 3; + Type returnType = 4; +} + +message MethodParameter { + string name = 1; + Type type = 2; + bool isOptional = 3; + bool isRest = 4; +} + +message BlockCfg { + repeated Block blocks = 1; +} + +message Block { + int32 id = 1; + repeated Stmt statements = 2; + repeated int32 successors = 3; + repeated int32 predecessors = 4; +} + +message Type { + oneof kind { + RawType raw_type = 1; + AnyType any_type = 2; + UnknownType unknown_type = 3; + UnionType union_type = 4; + IntersectionType intersection_type = 5; + GenericType generic_type = 6; + AliasType alias_type = 7; + BooleanType boolean_type = 8; + NumberType number_type = 9; + StringType string_type = 10; + NullType null_type = 11; + UndefinedType undefined_type = 12; + VoidType void_type = 13; + NeverType never_type = 14; + LiteralType literal_type = 15; + ClassType class_type = 16; + UnclearRefType unclear_ref_type = 17; + ArrayType array_type = 18; + TupleType tuple_type = 19; + FunctionType function_type = 20; + } +} + +message RawType { + string kind = 1; + string text = 2; + // map extra = 2; + // map extra = 2; +} + +message AnyType {} +message UnknownType {} +message BooleanType {} +message NumberType {} +message StringType {} +message NullType {} +message UndefinedType {} +message VoidType {} +message NeverType {} + +message UnionType { + repeated Type types = 1; +} + +message IntersectionType { + repeated Type types = 1; +} + +message GenericType { + string type_name = 1; + optional Type constraint = 2; + optional Type default_type = 3; +} + +message AliasType { + string name = 1; + Type original_type = 2; + // TODO: LocalSignature signature = 3; +} + +message LiteralType { + string literal_name = 1; +} + +message ClassType { + ClassSignature signature = 1; + repeated Type type_parameters = 2; +} + +message UnclearRefType { + string name = 1; + repeated Type type_parameters = 2; +} + +message ArrayType { + Type element_type = 1; + int32 dimensions = 2; +} + +message TupleType { + repeated Type types = 1; +} + +message FunctionType { + MethodSignature signature = 1; + repeated Type type_parameters = 2; +} + +message Stmt { + oneof kind { + RawStmt raw_stmt = 1; + NopStmt nop_stmt = 2; + AssignStmt assign_stmt = 3; + ReturnStmt return_stmt = 4; + ThrowStmt throw_stmt = 5; + IfStmt if_stmt = 6; + CallStmt call_stmt = 7; + } +} + +message RawStmt { + string kind = 1; + string text = 2; +} + +message NopStmt {} + +message AssignStmt { + Value lhv = 1; + Value rhv = 2; +} + +message ReturnStmt { + optional Value return_value = 1; +} + +message ThrowStmt { + Value exception = 1; +} + +message IfStmt { + Value condition = 1; +} + +message CallStmt { + CallExpr expr = 1; +} + +message Value { + oneof kind { + RawValue raw_value = 1; + Local local = 2; + Constant constant = 3; + Expr expr = 4; + Ref ref = 5; + } +} + +message RawValue { + string kind = 1; + string text = 2; + Type type = 3; +} + +message Local { + string name = 1; + Type type = 2; +} + +message Constant { + string value = 1; + Type type = 2; +} + +message Expr { + oneof kind { + NewExpr new_expr = 1; + NewArrayExpr new_array_expr = 2; + DeleteExpr delete_expr = 3; + AwaitExpr await_expr = 4; + YieldExpr yield_expr = 5; + TypeOfExpr type_of_expr = 6; + InstanceOfExpr instance_of_expr = 7; + CastExpr cast_expr = 8; + UnaryExpr unary_expr = 9; + BinaryExpr binary_expr = 10; + RelationExpr relation_expr = 11; + CallExpr call_expr = 12; + } +} + +message NewExpr { + Type type = 1; +} + +message NewArrayExpr { + Type element_type = 1; + Value size = 2; +} + +message DeleteExpr { + Value arg = 1; +} + +message AwaitExpr { + Value arg = 1; + Type type = 2; +} + +message YieldExpr { + Value arg = 1; + Type type = 2; +} + +message TypeOfExpr { + Value arg = 1; +} + +message CastExpr { + Value arg = 1; + Type type = 2; +} + +message InstanceOfExpr { + Value arg = 1; + Type check_type = 2; +} + +message UnaryExpr { + UnaryOperator op = 1; + Value arg = 2; + Type type = 3; +} + +enum UnaryOperator { + UNARY_UNKNOWN = 0; + NEG = 1; // - + BITWISE_NOT = 2; // ~ + LOGICAL_NOT = 3; // ! +} + +message BinaryExpr { + BinaryOperator op = 1; + Value left = 2; + Value right = 3; + Type type = 4; +} + +enum BinaryOperator { + BINARY_UNKNOWN = 0; + + ADDITION = 1; // '+' + SUBTRACTION = 2; // '-' + MULTIPLICATION = 3; // '*' + DIVISION = 4; // '/' + REMAINDER = 5; // '%' + EXPONENTIATION = 6; // '**' + + LEFT_SHIFT = 7; // '<<' + RIGHT_SHIFT = 8; // '>>' + UNSIGNED_RIGHT_SHIFT = 9; // '>>>' + + BITWISE_AND = 10; // '&' + BITWISE_OR = 11; // '|' + BITWISE_XOR = 12; // '^' + + LOGICAL_AND = 13; // '&&' + LOGICAL_OR = 14; // '||' + + NULLISH_COALESCING = 15; // ?? +} + +// Relation Expressions +message RelationExpr { + RelationOperator op = 1; + Value left = 2; + Value right = 3; +} + +// Relation Operators +enum RelationOperator { + RELATION_UNKNOWN = 0; + EQ = 1; // '==' + NEQ = 2; // '!=' + STRICT_EQ = 3; // '===' + STRICT_NEQ = 4; // '!==' + LT = 5; // '<' + LTE = 6; // '<=' + GT = 7; // '>' + GTE = 8; // '>=' + IN = 9; // 'in' +} + +// Call Expressions +message CallExpr { + MethodSignature callee = 1; + repeated Value args = 2; + Type type = 3; + + oneof kind { + InstanceCall instance_call = 4; + StaticCall static_call = 5; + PtrCall ptr_call = 6; + } +} + +message InstanceCall { + Local instance = 1; +} + +// Note: class name is included in the callee's signature +message StaticCall {} + +message PtrCall { + Value ptr = 1; +} + +// References +message Ref { + oneof kind { + This this = 1; + ParameterRef parameter = 2; + ArrayAccess array_access = 3; + FieldRef field_ref = 4; + } +} + +message This { + Type type = 1; +} + +message ParameterRef { + int32 index = 1; + Type type = 2; +} + +message ArrayAccess { + Local array = 1; + Value index = 2; + Type type = 3; +} + +message FieldRef { + oneof kind { + InstanceFieldRef instance = 1; + StaticFieldRef static = 2; + } +} + +message InstanceFieldRef { + Local instance = 1; + FieldSignature field = 2; + Type type = 3; +} + +message StaticFieldRef { + FieldSignature field = 1; + Type type = 2; +} diff --git a/jacodb-ets/wire-server/build.gradle.kts b/jacodb-ets/wire-server/build.gradle.kts new file mode 100644 index 000000000..cdd73ada1 --- /dev/null +++ b/jacodb-ets/wire-server/build.gradle.kts @@ -0,0 +1,38 @@ +import com.squareup.wire.kotlin.grpcserver.GrpcServerSchemaHandler + +plugins { + id(Plugins.Wire) +} + +buildscript { + dependencies { + classpath(Libs.wire_grpc_server_generator) + } +} + +dependencies { + protoSource(project(":jacodb-ets:wire-protos")) + api(Libs.wire_grpc_server) + api(Libs.grpc_api) + implementation(Libs.grpc_protobuf) + implementation(Libs.grpc_services) // for ProtoReflectionService + implementation(Libs.kotlin_logging) + runtimeOnly(Libs.wire_runtime) + runtimeOnly(Libs.grpc_netty_shaded) +} + +wire { + custom { + schemaHandlerFactory = GrpcServerSchemaHandler.Factory() + options = mapOf( + "rpcCallStyle" to "blocking", + "singleMethodServices" to "false", + ) + exclusive = false + } + kotlin { + rpcRole = "server" + rpcCallStyle = "blocking" + singleMethodServices = false + } +} diff --git a/jacodb-ets/wire-server/src/main/kotlin/org/jacodb/ets/service/Greeter.kt b/jacodb-ets/wire-server/src/main/kotlin/org/jacodb/ets/service/Greeter.kt new file mode 100644 index 000000000..a2425a325 --- /dev/null +++ b/jacodb-ets/wire-server/src/main/kotlin/org/jacodb/ets/service/Greeter.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * 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 org.jacodb.ets.service + +import greeter.GreeterBlockingServer +import greeter.GreeterWireGrpc +import greeter.HelloReply +import greeter.HelloRequest +import io.grpc.stub.StreamObserver + +private val logger = mu.KotlinLogging.logger {} + +class GreeterImpl : GreeterBlockingServer { + override fun SayHello(request: HelloRequest): HelloReply { + return HelloReply(message = "Hello, ${request.name}!") + } +} + +class GreeterService : GreeterWireGrpc.GreeterImplBase() { + private val impl = GreeterImpl() + + override fun SayHello(request: HelloRequest, response: StreamObserver) { + logger.info { "Received $request" } + val reply = impl.SayHello(request) + logger.info { "Sending $reply" } + response.onNext(reply) + response.onCompleted() + } +} + +fun main() { + val port = 7777 + val server = grpcServer(port) { + addService(GreeterService()) + } + server.start() + println("Server listening on port ${server.port}") + server.awaitTermination() +} diff --git a/jacodb-ets/wire-server/src/main/kotlin/org/jacodb/ets/service/Server.kt b/jacodb-ets/wire-server/src/main/kotlin/org/jacodb/ets/service/Server.kt new file mode 100644 index 000000000..0637ee886 --- /dev/null +++ b/jacodb-ets/wire-server/src/main/kotlin/org/jacodb/ets/service/Server.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * 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 org.jacodb.ets.service + +import io.grpc.Server +import io.grpc.ServerBuilder +import io.grpc.protobuf.services.ProtoReflectionService + +/** + * Creates a gRPC server with the specified port and optional reflection service. + * + * ### Example: + * ```kotlin + * val server = grpcServer(7777) { + * addService(MyService()) + * } + * ``` + * + * @param port The port on which the server will listen. + * @param addReflection Whether to add the [ProtoReflectionService] for introspection (see [gRPC Reflection](https://grpc.io/docs/guides/reflection/)). + * @param setup A lambda to configure the server, where you can add services and other configurations. + * @return A configured [Server] instance. + */ +fun grpcServer( + port: Int, + addReflection: Boolean = true, + setup: ServerBuilder<*>.() -> Unit, + // Note: `setup` is not `= {}` by default because you probably want to add at least one service. +): Server = ServerBuilder + .forPort(port) + .apply(setup) + .apply { + if (addReflection) addService(@Suppress("DEPRECATION") ProtoReflectionService.newInstance()) + } + .build() diff --git a/jacodb-taint-configuration/build.gradle.kts b/jacodb-taint-configuration/build.gradle.kts index d46a5bd28..75d044802 100644 --- a/jacodb-taint-configuration/build.gradle.kts +++ b/jacodb-taint-configuration/build.gradle.kts @@ -11,7 +11,6 @@ dependencies { implementation(project(":jacodb-core")) implementation(testFixtures(project(":jacodb-core"))) - implementation(Libs.kotlinx_serialization_core) implementation(Libs.kotlinx_serialization_json) // for local tests only testImplementation(testFixtures(project(":jacodb-storage"))) diff --git a/settings.gradle.kts b/settings.gradle.kts index f2d99b2ca..7482ff5d1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,7 +1,7 @@ rootProject.name = "jacodb" plugins { - id("com.gradle.develocity") version("3.18.2") + id("com.gradle.develocity") version "3.18.2" id("org.danilopianini.gradle-pre-commit-git-hooks") version "1.1.11" } @@ -23,6 +23,36 @@ gitHooks { createHooks(true) } +val localProperties = java.util.Properties().apply { + if (file("local.properties").exists()) { + file("local.properties").reader().use(::load) + } +} + +val enableEts = run { + fun String.isTrue(): Boolean = this in listOf("true", "yes", "1") + // Note: we check for presence of the property first to maintain the following behavior: + // 1. If the property is set via command line (`-PenableEts=true`) + // or via `gradle.properties` file, it takes precedence. + // 2. If the property is not set, we check the `local.properties` file. + // 3. If neither is set, the default is `false`. + if (providers.gradleProperty("enableEts").isPresent) { + providers.gradleProperty("enableEts").get().isTrue() + } else { + localProperties.getProperty("enableEts", "false").isTrue() + } +} +if (enableEts) { + if (JavaVersion.current().isJava11Compatible) { + include("jacodb-ets") + include("jacodb-ets:wire-protos") + include("jacodb-ets:wire-client") + include("jacodb-ets:wire-server") + } else { + throw GradleException("jacodb-ets requires Java 11 or higher, but the current Java version is ${JavaVersion.current()}") + } +} + include("jacodb-api-common") include("jacodb-api-jvm") include("jacodb-api-storage") @@ -32,4 +62,3 @@ include("jacodb-examples") include("jacodb-benchmarks") include("jacodb-approximations") include("jacodb-taint-configuration") -include("jacodb-ets")