From f37e7233eebdea640a06ef7edeab46f811bd0c67 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Tue, 3 Jun 2025 16:30:22 +0300 Subject: [PATCH 1/3] Set scene for all files (#324) --- jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Scene.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Scene.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Scene.kt index 0f593308d..40d82b87a 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Scene.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Scene.kt @@ -22,6 +22,11 @@ class EtsScene( val projectFiles: List, val sdkFiles: List = emptyList(), ) : CommonProject { + init { + projectFiles.forEach { it.scene = this } + sdkFiles.forEach { it.scene = this } + } + val projectClasses: List get() = projectFiles.flatMap { it.allClasses } From 77b83e41272db42b5632f49e82acbd76188f5812 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Tue, 3 Jun 2025 16:31:42 +0300 Subject: [PATCH 2/3] Add CI job to publish all test results (#323) --- .github/workflows/ci.yml | 65 ++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 666add7d4..f68595a70 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,10 +11,6 @@ on: - neo workflow_dispatch: -permissions: - checks: write - pull-requests: write - jobs: ci-core: name: Run core tests on JDK ${{ matrix.jdk }} @@ -44,18 +40,18 @@ jobs: - name: Build and run tests run: ./gradlew --scan build -x :jacodb-ets:build - - name: Publish test results - uses: EnricoMi/publish-unit-test-result-action@v2 - if: (!cancelled()) - with: - files: "**/build/test-results/**/*.xml" - check_name: "Test results on JDK ${{ matrix.jdk }}" - - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} + - name: Upload Gradle test results + if: (!cancelled()) + uses: actions/upload-artifact@v4 + with: + name: gradle-test-results-${{ matrix.jdk }} + path: "**/build/test-results/**/*.xml" + - name: Upload Gradle reports if: (!cancelled()) uses: actions/upload-artifact@v4 @@ -90,12 +86,19 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} - - name: Publish test results - uses: EnricoMi/publish-unit-test-result-action@v2 + - name: Upload Gradle test results if: (!cancelled()) + uses: actions/upload-artifact@v4 + with: + name: gradle-test-results-lifecycle + path: "**/build/test-results/**/*.xml" + + - name: Upload Gradle reports + if: (!cancelled()) + uses: actions/upload-artifact@v4 with: - files: "**/build/test-results/**/*.xml" - check_name: "Lifecycle test results" + name: gradle-reports-lifecycle + path: '**/build/reports/' ci-ets: name: Run ETS tests @@ -147,9 +150,41 @@ jobs: - name: Run ETS tests run: ./gradlew --scan :jacodb-ets:generateTestResources :jacodb-ets:test + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload Gradle test results + if: (!cancelled()) + uses: actions/upload-artifact@v4 + with: + name: gradle-test-results-ets + path: "**/build/test-results/**/*.xml" + - name: Upload Gradle reports if: (!cancelled()) uses: actions/upload-artifact@v4 with: name: gradle-reports-ets path: '**/build/reports/' + + publish-test-results: + name: "Publish test results" + needs: [ ci-core, ci-lifecycle, ci-ets ] + if: (!cancelled()) + runs-on: ubuntu-latest + permissions: + checks: write + pull-requests: write + + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Publish test results + uses: EnricoMi/publish-unit-test-result-action@v2 + with: + files: "artifacts/gradle-test-results-*/**/*.xml" From eda991b8c1c861d2ebfa6f6136a9ec4532124d4f Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Tue, 3 Jun 2025 19:25:01 +0300 Subject: [PATCH 3/3] gRPC --- .github/workflows/ci.yml | 34 + build.gradle.kts | 1 + buildSrc/src/main/kotlin/Dependencies.kt | 134 +- buildSrc/src/main/kotlin/ProcessUtil.kt | 93 ++ jacodb-ets/.gitignore | 1 + jacodb-ets/build.gradle.kts | 115 +- jacodb-ets/grpc-client/build.gradle.kts | 8 + .../kotlin/org/jacodb/ets/grpc/Channel.kt | 33 + .../org/jacodb/ets/grpc/GreeterClient.kt | 53 + .../org/jacodb/ets/grpc/ManagerClient.kt | 41 + jacodb-ets/grpc-server/build.gradle.kts | 9 + .../org/jacodb/ets/grpc/GreeterServer.kt | 45 + .../kotlin/org/jacodb/ets/grpc/GrpcServer.kt | 59 + jacodb-ets/protos/build.gradle.kts | 48 + .../protos/src/main/proto/greeter.proto | 20 + .../protos/src/main/proto/manager.proto | 15 + jacodb-ets/protos/src/main/proto/model.proto | 461 +++++++ .../kotlin/org/jacodb/ets/dto/ConvertToDto.kt | 66 + .../ets/dto/{Convert.kt => ConvertToEts.kt} | 26 +- .../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 +- .../org/jacodb/ets/grpc/ConvertToEts.kt | 1110 +++++++++++++++++ .../org/jacodb/ets/grpc/ConvertToProto.kt | 128 ++ .../org/jacodb/ets/grpc/EtsTypeToProto.kt | 187 +++ .../org/jacodb/ets/grpc/EtsValueToProto.kt | 124 ++ .../kotlin/org/jacodb/ets/grpc/LoadScene.kt | 54 + .../org/jacodb/ets/grpc/TestManagerClient.kt | 41 + .../main/kotlin/org/jacodb/ets/model/Class.kt | 8 +- .../main/kotlin/org/jacodb/ets/model/File.kt | 4 +- .../org/jacodb/ets/proto/ConvertToEts.kt | 1018 +++++++++++++++ .../org/jacodb/ets/proto/ConvertToProto.kt | 66 + .../org/jacodb/ets/proto/EtsTypeToProto.kt | 157 +++ .../org/jacodb/ets/proto/EtsValueToProto.kt | 117 ++ .../org/jacodb/ets/proto/GreeterClient.kt | 72 ++ .../main/kotlin/org/jacodb/ets/proto/Model.kt | 994 +++++++++++++++ .../kotlin/org/jacodb/ets/proto/Schema.kt | 31 + .../main/kotlin/org/jacodb/ets/proto/Test.kt | 83 ++ .../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 +- .../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 | 105 ++ .../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 | 15 + .../org/jacodb/ets/service/ClientGreeter.kt | 43 + .../org/jacodb/ets/service/ClientManager.kt | 29 + .../kotlin/org/jacodb/ets/service/Utils.kt | 33 + jacodb-ets/wire-protos/build.gradle.kts | 14 + .../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 | 34 + .../kotlin/org/jacodb/ets/service/Greeter.kt | 53 + jacodb-taint-configuration/build.gradle.kts | 1 - settings.gradle.kts | 8 +- 65 files changed, 6799 insertions(+), 252 deletions(-) create mode 100644 buildSrc/src/main/kotlin/ProcessUtil.kt create mode 100644 jacodb-ets/grpc-client/build.gradle.kts create mode 100644 jacodb-ets/grpc-client/src/main/kotlin/org/jacodb/ets/grpc/Channel.kt create mode 100644 jacodb-ets/grpc-client/src/main/kotlin/org/jacodb/ets/grpc/GreeterClient.kt create mode 100644 jacodb-ets/grpc-client/src/main/kotlin/org/jacodb/ets/grpc/ManagerClient.kt create mode 100644 jacodb-ets/grpc-server/build.gradle.kts create mode 100644 jacodb-ets/grpc-server/src/main/kotlin/org/jacodb/ets/grpc/GreeterServer.kt create mode 100644 jacodb-ets/grpc-server/src/main/kotlin/org/jacodb/ets/grpc/GrpcServer.kt create mode 100644 jacodb-ets/protos/build.gradle.kts create mode 100644 jacodb-ets/protos/src/main/proto/greeter.proto create mode 100644 jacodb-ets/protos/src/main/proto/manager.proto create mode 100644 jacodb-ets/protos/src/main/proto/model.proto 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} (97%) 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/ConvertToEts.kt create mode 100644 jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/ConvertToProto.kt create mode 100644 jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/EtsTypeToProto.kt create mode 100644 jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/EtsValueToProto.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/TestManagerClient.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/EtsTypeToProto.kt create mode 100644 jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsValueToProto.kt create mode 100644 jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/GreeterClient.kt create mode 100644 jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/Model.kt create mode 100644 jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/Schema.kt create mode 100644 jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/Test.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/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/ClientGreeter.kt create mode 100644 jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/ClientManager.kt create mode 100644 jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/Utils.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 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f68595a70..219ebee09 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,6 +147,24 @@ jobs: npm install npm run build + - name: Start ArkAnalyzer server + run: | + nohup npm --prefix $ARKANALYZER_DIR run server:start > grpc-server.log 2>&1 & + PID=$! + echo $PID > grpc-server.pid + echo "Started gRPC server with PID $PID" + + - name: Wait for gRPC server to start + run: | + echo "Waiting for gRPC server to be ready..." + for i in {1..10}; do + nc -z localhost 50051 && echo "Server is up!" && exit 0 + sleep 1 + done + echo "Server failed to start" >&2 + cat grpc-server.log + exit 1 + - name: Run ETS tests run: ./gradlew --scan :jacodb-ets:generateTestResources :jacodb-ets:test @@ -169,6 +187,22 @@ jobs: name: gradle-reports-ets path: '**/build/reports/' + - name: Upload gRPC server logs + if: (!cancelled()) + uses: actions/upload-artifact@v4 + with: + name: grpc-server-log + path: grpc-server.log + + - name: Kill gRPC server + if: always() + run: | + if [ -f grpc-server.pid ]; then + PID=$(cat grpc-server.pid) + echo "Killing gRPC server with PID $PID" + kill $PID || echo "Process already terminated" + fi + publish-test-results: name: "Publish test results" needs: [ ci-core, ci-lifecycle, ci-ets ] diff --git a/build.gradle.kts b/build.gradle.kts index 54040e934..24136de3d 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")) } } diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index a3c684d0f..d9bda730c 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -6,12 +6,13 @@ 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_protobuf = "0.9.5" 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 +25,25 @@ 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 ktor = "3.1.3" 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" // 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", @@ -159,6 +157,11 @@ object Libs { name = "kotlinx-serialization-cbor", version = Versions.kotlinx_serialization ) + val kotlinx_serialization_protobuf = dep( + group = "org.jetbrains.kotlinx", + name = "kotlinx-serialization-protobuf", + version = Versions.kotlinx_serialization + ) // https://github.com/Kotlin/kotlinx-benchmark val kotlinx_benchmark_runtime = dep( @@ -339,6 +342,99 @@ object Libs { name = "commons-compress", version = Versions.commons_compress_test_only_version ) + + // https://protobuf.dev/ + val protobuf_protoc = dep( + group = "com.google.protobuf", + name = "protoc", + version = Versions.protobuf + ) + val protobuf_java = dep( + group = "com.google.protobuf", + name = "protobuf-java", + version = Versions.protobuf + ) + val protobuf_kotlin = dep( + group = "com.google.protobuf", + name = "protobuf-kotlin", + version = Versions.protobuf + ) + + // 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_protoc_gen = dep( + group = "io.grpc", + name = "protoc-gen-grpc-java", + version = Versions.grpc + ) + val grpc_netty_shaded = dep( + group = "io.grpc", + name = "grpc-netty-shaded", + version = Versions.grpc + ) + + // https://github.com/grpc/grpc-kotlin + val grpc_kotlin_stub = dep( + group = "io.grpc", + name = "grpc-kotlin-stub", + version = Versions.grpc_kotlin + ) + val grpc_protoc_gen_kotlin = dep( + group = "io.grpc", + name = "protoc-gen-grpc-kotlin", + version = Versions.grpc_kotlin + ) + + // https://github.com/ktorio/ktor + val ktor_client_core = dep( + group = "io.ktor", + name = "ktor-client-core", + version = Versions.ktor + ) + val ktor_client_cio = dep( + group = "io.ktor", + name = "ktor-client-cio", + version = Versions.ktor + ) + val ktor_client_content_negotiation = dep( + group = "io.ktor", + name = "ktor-client-content-negotiation", + version = Versions.ktor + ) + val ktor_server_core = dep( + group = "io.ktor", + name = "ktor-server-core", + version = Versions.ktor + ) + val ktor_server_netty = dep( + group = "io.ktor", + name = "ktor-server-netty", + version = Versions.ktor + ) + val ktor_server_content_negotiation = dep( + group = "io.ktor", + name = "ktor-server-content-negotiation", + version = Versions.ktor + ) + val ktor_serialization_kotlinx_json = dep( + group = "io.ktor", + name = "ktor-serialization-kotlinx-json", + version = Versions.ktor + ) + val ktor_serialization_kotlinx_protobuf = dep( + group = "io.ktor", + name = "ktor-serialization-kotlinx-protobuf", + version = Versions.ktor + ) } object Plugins { @@ -357,6 +453,18 @@ 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/google/protobuf-gradle-plugin + object GradleProtobuf : ProjectPlugin( + version = Versions.gradle_protobuf, + id = "com.google.protobuf" + ) + // https://github.com/ben-manes/gradle-versions-plugin object GradleVersions : ProjectPlugin( version = Versions.gradle_versions, diff --git a/buildSrc/src/main/kotlin/ProcessUtil.kt b/buildSrc/src/main/kotlin/ProcessUtil.kt new file mode 100644 index 000000000..a0e756126 --- /dev/null +++ b/buildSrc/src/main/kotlin/ProcessUtil.kt @@ -0,0 +1,93 @@ +import java.io.Reader +import java.time.Duration +import java.util.concurrent.TimeUnit +import kotlin.concurrent.thread + +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: String? = null, + timeout: Duration? = null, + builder: ProcessBuilder.() -> Unit = {}, + ): Result { + val reader = input?.reader() ?: "".reader() + return run(command, reader, timeout, builder) + } + + fun run( + command: List, + input: 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?, + ): Result { + // Handle process input (stdin) + val stdinThread = thread { + process.outputStream.bufferedWriter().use { writer -> + input.copyTo(writer) + writer.flush() + } + } + + // Capture process output (stdout) + val stdout = StringBuilder() + val stdoutThread = thread { + process.inputStream.bufferedReader().useLines { lines -> + lines.forEach { stdout.appendLine(it) } + } + } + + // Capture process error output (stderr) + val stderr = StringBuilder() + val stderrThread = thread { + process.errorStream.bufferedReader().useLines { lines -> + lines.forEach { stderr.appendLine(it) } + } + } + + // Start all threads + stdinThread.start() + stdoutThread.start() + stderrThread.start() + + // Wait for completion + val isTimeout = if (timeout != null) { + !process.waitFor(timeout.toMillis(), TimeUnit.MILLISECONDS) + } else { + process.waitFor() + false + } + + // If timeout occurred, destroy the process forcibly + if (isTimeout) { + process.destroyForcibly() + } + + // Wait for stream threads to finish + stdinThread.join() + stdoutThread.join() + stderrThread.join() + + return Result( + exitCode = if (isTimeout) -1 else 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..8aaad1c91 100644 --- a/jacodb-ets/build.gradle.kts +++ b/jacodb-ets/build.gradle.kts @@ -1,18 +1,31 @@ +import com.github.gradle.node.npm.task.NpmTask import java.io.FileNotFoundException +import java.io.IOException +import java.net.Socket plugins { kotlin("plugin.serialization") - `java-test-fixtures` + id(Plugins.GradleNode) } dependencies { api(project(":jacodb-api-common")) + api(project(":jacodb-ets:grpc-client")) + api(project(":jacodb-ets:grpc-server")) + 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_serialization_protobuf) implementation(Libs.kotlinx_coroutines_core) implementation(Libs.jdot) + implementation(Libs.ktor_client_core) + implementation(Libs.ktor_client_cio) + implementation(Libs.ktor_client_content_negotiation) + implementation(Libs.ktor_serialization_kotlinx_json) + implementation(Libs.ktor_serialization_kotlinx_protobuf) testImplementation(kotlin("test")) testImplementation(Libs.mockk) @@ -21,6 +34,83 @@ dependencies { testFixturesImplementation(Libs.junit_jupiter_api) } +node { + download = true + nodeProjectDir.set(file("arkanalyzer")) +} + +tasks.register("runArkAnalyzerServer") { + dependsOn(tasks.npmInstall) + args = listOf("run", "server") +} + +val port = 50051 +val serverPidFile = layout.buildDirectory.file("server.pid") +val serverLogFile = layout.buildDirectory.file("server.log") + +tasks.register("startArkAnalyzerServer") { + group = "arkanalyzer" + description = "Start the ArkAnalyzer server in background." + doLast { + val process = ProcessBuilder("npm", "run", "server") + .directory(file("arkanalyzer")) + .redirectOutput(serverLogFile.get().asFile) + .redirectErrorStream(true) + .apply { + val env = environment() + env["ARKANALYZER_PORT"] = port.toString() + } + .start() + + val pid = process.pid() + serverPidFile.get().asFile.writeText(pid.toString()) + println("ArkAnalyzer server started with PID $pid") + println("Logs are being written to: ${serverLogFile.get().asFile.absolutePath}") + + // Wait until the server is ready + val maxRetries = 10 + val retryDelay = 100L // in milliseconds + repeat(maxRetries) { + try { + Socket("localhost", port).use { socket -> + if (socket.isConnected) { + println("ArkAnalyzer server is ready on port $port") + return@doLast + } + } + } catch (_: IOException) { + // Server not ready yet, retry + } + Thread.sleep(retryDelay) + } + throw RuntimeException( + "ArkAnalyzer server did not start within the expected time. " + + "Check the logs at ${serverLogFile.get().asFile.absolutePath} for more details." + ) + } +} + +tasks.register("stopArkAnalyzerServer") { + group = "arkanalyzer" + description = "Stop the ArkAnalyzer server." + doLast { + if (!serverPidFile.get().asFile.exists()) { + println("ArkAnalyzer server is not running (PID file not found).") + return@doLast + } + val pid = serverPidFile.get().asFile.readText().trim().toLong() + println("Stopping ArkAnalyzer server with PID $pid") + try { + ProcessBuilder("kill", pid.toString()).start().waitFor() + println("ArkAnalyzer server with PID $pid stopped.") + } catch (e: Exception) { + println("Failed to stop ArkAnalyzer server with PID $pid: ${e.message}") + } finally { + serverPidFile.get().asFile.delete() + } + } +} + // Example usage: // ``` // export ARKANALYZER_DIR=~/dev/arkanalyzer @@ -74,21 +164,20 @@ tasks.register("generateTestResources") { "-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--------") + val result = ProcessUtil.run(cmd) { + directory(resources) } - val stderr = process.errorStream.bufferedReader().readText().trim() - if (stderr.isNotBlank()) { - println("[STDERR]:\n--------\n$stderr\n--------") + if (result.stdout.isNotBlank()) { + println("[STDOUT]:\n--------\n${result.stdout}\n--------") } - - if (!ok) { + if (result.stderr.isNotBlank()) { + println("[STDERR]:\n--------\n${result.stderr}\n--------") + } + if (result.isTimeout) { println("Timeout!") - process.destroy() + } + if (result.exitCode != 0) { + println("Exit code: ${result.exitCode}") } println("Done generating test resources in %.1fs".format((System.currentTimeMillis() - startTime) / 1000.0)) diff --git a/jacodb-ets/grpc-client/build.gradle.kts b/jacodb-ets/grpc-client/build.gradle.kts new file mode 100644 index 000000000..dc31b568c --- /dev/null +++ b/jacodb-ets/grpc-client/build.gradle.kts @@ -0,0 +1,8 @@ +dependencies { + api(project(":jacodb-ets:protos")) + + implementation(Libs.kotlin_logging) + implementation(Libs.slf4j_simple) + + testImplementation(kotlin("test")) +} diff --git a/jacodb-ets/grpc-client/src/main/kotlin/org/jacodb/ets/grpc/Channel.kt b/jacodb-ets/grpc-client/src/main/kotlin/org/jacodb/ets/grpc/Channel.kt new file mode 100644 index 000000000..d762a0076 --- /dev/null +++ b/jacodb-ets/grpc-client/src/main/kotlin/org/jacodb/ets/grpc/Channel.kt @@ -0,0 +1,33 @@ +/* + * 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 io.grpc.ManagedChannel +import io.grpc.ManagedChannelBuilder + +const val DEFAULT_PORT = 50051 + +fun grpcChannel( + host: String = "localhost", + port: Int = DEFAULT_PORT, + setup: ManagedChannelBuilder<*>.() -> Unit = {}, +): ManagedChannel { + return ManagedChannelBuilder.forAddress(host, port) + .usePlaintext() + .apply(setup) + .build() +} diff --git a/jacodb-ets/grpc-client/src/main/kotlin/org/jacodb/ets/grpc/GreeterClient.kt b/jacodb-ets/grpc-client/src/main/kotlin/org/jacodb/ets/grpc/GreeterClient.kt new file mode 100644 index 000000000..b5f00c22b --- /dev/null +++ b/jacodb-ets/grpc-client/src/main/kotlin/org/jacodb/ets/grpc/GreeterClient.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.grpc + +import greeter.GreeterGrpcKt +import greeter.helloRequest +import io.grpc.ManagedChannel +import mu.KotlinLogging +import java.io.Closeable +import java.util.concurrent.TimeUnit + +private val logger = KotlinLogging.logger {} + +class GreeterClient( + private val channel: ManagedChannel, +) : Closeable { + private val stub = GreeterGrpcKt.GreeterCoroutineStub(channel) + + suspend fun greet(name: String) { + val request = helloRequest { + this.name = name + } + logger.info { "Sending request: $request" } + val response = stub.sayHello(request) + logger.info { "Received response: $response" } + } + + override fun close() { + channel.shutdown().awaitTermination(5, TimeUnit.SECONDS) + } +} + +suspend fun main() { + val port = 50051 + val channel = grpcChannel(port = port) + val client = GreeterClient(channel) + val name = System.getProperty("user.name") + client.greet(name) +} diff --git a/jacodb-ets/grpc-client/src/main/kotlin/org/jacodb/ets/grpc/ManagerClient.kt b/jacodb-ets/grpc-client/src/main/kotlin/org/jacodb/ets/grpc/ManagerClient.kt new file mode 100644 index 000000000..44948a7a8 --- /dev/null +++ b/jacodb-ets/grpc-client/src/main/kotlin/org/jacodb/ets/grpc/ManagerClient.kt @@ -0,0 +1,41 @@ +/* + * 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 io.grpc.ManagedChannel +import manager.ManagerGrpcKt +import manager.getSceneRequest +import model.Scene +import java.io.Closeable +import java.util.concurrent.TimeUnit + +class ManagerClient( + private val channel: ManagedChannel, +) : AutoCloseable { + private val stub = ManagerGrpcKt.ManagerCoroutineStub(channel) + + suspend fun getScene(path: String): Scene { + val request = getSceneRequest { + this.path = path + } + return stub.getScene(request) + } + + override fun close() { + channel.shutdown().awaitTermination(5, TimeUnit.SECONDS) + } +} diff --git a/jacodb-ets/grpc-server/build.gradle.kts b/jacodb-ets/grpc-server/build.gradle.kts new file mode 100644 index 000000000..3e38ee57d --- /dev/null +++ b/jacodb-ets/grpc-server/build.gradle.kts @@ -0,0 +1,9 @@ +dependencies { + api(project(":jacodb-ets:protos")) + + implementation(Libs.kotlin_logging) + implementation(Libs.slf4j_simple) + runtimeOnly(Libs.grpc_netty_shaded) + + testImplementation(kotlin("test")) +} diff --git a/jacodb-ets/grpc-server/src/main/kotlin/org/jacodb/ets/grpc/GreeterServer.kt b/jacodb-ets/grpc-server/src/main/kotlin/org/jacodb/ets/grpc/GreeterServer.kt new file mode 100644 index 000000000..9907602c3 --- /dev/null +++ b/jacodb-ets/grpc-server/src/main/kotlin/org/jacodb/ets/grpc/GreeterServer.kt @@ -0,0 +1,45 @@ +/* + * 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 greeter.GreeterGrpcKt +import greeter.HelloReply +import greeter.HelloRequest +import greeter.helloReply +import mu.KotlinLogging + +private val logger = KotlinLogging.logger {} + +class GreeterService : GreeterGrpcKt.GreeterCoroutineImplBase() { + override suspend fun sayHello( + request: HelloRequest, + ): HelloReply { + logger.info { "Received: $request" } + return helloReply { + message = "Hello, ${request.name}" + } + } +} + +fun main() { + val port = 50051 + val server = GrpcServer(port) { + addService(GreeterService()) + } + server.start() + server.blockUntilShutdown() +} diff --git a/jacodb-ets/grpc-server/src/main/kotlin/org/jacodb/ets/grpc/GrpcServer.kt b/jacodb-ets/grpc-server/src/main/kotlin/org/jacodb/ets/grpc/GrpcServer.kt new file mode 100644 index 000000000..5e0418b62 --- /dev/null +++ b/jacodb-ets/grpc-server/src/main/kotlin/org/jacodb/ets/grpc/GrpcServer.kt @@ -0,0 +1,59 @@ +/* + * 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 io.grpc.Server +import io.grpc.ServerBuilder +import mu.KotlinLogging + +private val logger = KotlinLogging.logger {} + +class GrpcServer(private val server: Server) { + fun start() { + server.start() + logger.info { "Server listening on port ${server.port}" } + Runtime.getRuntime().addShutdownHook(Thread { + logger.info { "Shutting down gRPC server since JVM is shutting down" } + stop() + logger.info { "Server shut down" } + }) + } + + fun stop() { + server.shutdown() + } + + fun blockUntilShutdown() { + server.awaitTermination() + } + + companion object { + const val DEFAULT_PORT = 50051 + + // constructor + operator fun invoke( + port: Int = DEFAULT_PORT, + setup: ServerBuilder<*>.() -> Unit, + ): GrpcServer { + val server: Server = ServerBuilder + .forPort(port) + .apply(setup) + .build() + return GrpcServer(server) + } + } +} diff --git a/jacodb-ets/protos/build.gradle.kts b/jacodb-ets/protos/build.gradle.kts new file mode 100644 index 000000000..9f1d3442d --- /dev/null +++ b/jacodb-ets/protos/build.gradle.kts @@ -0,0 +1,48 @@ +import com.google.protobuf.gradle.id + +plugins { + id(Plugins.GradleProtobuf) +} + +dependencies { + implementation(Libs.kotlin_logging) + implementation(Libs.slf4j_simple) + + implementation(Libs.grpc_protobuf) + api(Libs.grpc_kotlin_stub) + api(Libs.protobuf_kotlin) + + testImplementation(kotlin("test")) +} + +protobuf { + protoc { + artifact = Libs.protobuf_protoc + } + plugins { + // Optional: an artifact spec for a protoc plugin, with "grpc" as + // the identifier, which can be referred to in the "plugins" + // container of the "generateProtoTasks" closure. + id("grpc") { + artifact = Libs.grpc_protoc_gen + } + id("grpc-kt") { + artifact = Libs.grpc_protoc_gen_kotlin + ":jdk8@jar" + } + } + generateProtoTasks { + ofSourceSet("main").forEach { + it.plugins { + // Apply the "grpc" plugin whose spec is defined above, without + // options. Note the braces cannot be omitted, otherwise the + // plugin will not be added. This is because of the implicit way + // NamedDomainObjectContainer binds the methods. + id("grpc") {} + id("grpc-kt") {} + } + it.builtins { + id("kotlin") {} + } + } + } +} diff --git a/jacodb-ets/protos/src/main/proto/greeter.proto b/jacodb-ets/protos/src/main/proto/greeter.proto new file mode 100644 index 000000000..0bfca42c5 --- /dev/null +++ b/jacodb-ets/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/protos/src/main/proto/manager.proto b/jacodb-ets/protos/src/main/proto/manager.proto new file mode 100644 index 000000000..afc7d66c8 --- /dev/null +++ b/jacodb-ets/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/protos/src/main/proto/model.proto b/jacodb-ets/protos/src/main/proto/model.proto new file mode 100644 index 000000000..a57ec3884 --- /dev/null +++ b/jacodb-ets/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 projectName = 1; + string fileName = 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/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 97% 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..7176fde37 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 @@ -443,18 +444,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 +456,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 +676,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 +687,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/grpc/ConvertToEts.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/ConvertToEts.kt new file mode 100644 index 000000000..0fc6b9745 --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/ConvertToEts.kt @@ -0,0 +1,1110 @@ +/* + * 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 model.BinaryOperator +import model.RelationOperator +import model.UnaryOperator +import model.aliasTypeOrNull +import model.anyTypeOrNull +import model.argOrNull +import model.arrayAccessOrNull +import model.arrayOrNull +import model.arrayTypeOrNull +import model.assignStmtOrNull +import model.awaitExprOrNull +import model.binaryExprOrNull +import model.booleanTypeOrNull +import model.callExprOrNull +import model.callStmtOrNull +import model.calleeOrNull +import model.castExprOrNull +import model.cfgOrNull +import model.checkTypeOrNull +import model.classTypeOrNull +import model.conditionOrNull +import model.constantOrNull +import model.constraintOrNull +import model.defaultTypeOrNull +import model.deleteExprOrNull +import model.elementTypeOrNull +import model.enclosingClassOrNull +import model.exceptionOrNull +import model.exprOrNull +import model.fieldOrNull +import model.fieldRefOrNull +import model.fileOrNull +import model.functionTypeOrNull +import model.genericTypeOrNull +import model.ifStmtOrNull +import model.indexOrNull +import model.instanceCallOrNull +import model.instanceOfExprOrNull +import model.instanceOrNull +import model.intersectionTypeOrNull +import model.leftOrNull +import model.lhvOrNull +import model.literalTypeOrNull +import model.localOrNull +import model.namespaceOrNull +import model.neverTypeOrNull +import model.newArrayExprOrNull +import model.newExprOrNull +import model.nopStmtOrNull +import model.nullTypeOrNull +import model.numberTypeOrNull +import model.originalTypeOrNull +import model.parameterOrNull +import model.parentOrNull +import model.ptrCallOrNull +import model.ptrOrNull +import model.rawStmtOrNull +import model.rawTypeOrNull +import model.rawValueOrNull +import model.refOrNull +import model.relationExprOrNull +import model.returnStmtOrNull +import model.returnValueOrNull +import model.rhvOrNull +import model.rightOrNull +import model.signatureOrNull +import model.sizeOrNull +import model.staticCallOrNull +import model.staticOrNull +import model.stringTypeOrNull +import model.thisOrNull +import model.throwStmtOrNull +import model.tupleTypeOrNull +import model.typeOfExprOrNull +import model.typeOrNull +import model.unaryExprOrNull +import model.unclearRefTypeOrNull +import model.undefinedTypeOrNull +import model.unionTypeOrNull +import model.unknownTypeOrNull +import model.voidTypeOrNull +import model.yieldExprOrNull +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.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.Scene as ProtoScene +import model.Stmt as ProtoStmt +import model.Type as ProtoType +import model.UnaryExpr as ProtoUnaryExpr +import model.Value as ProtoValue + +// import model.ImportInfo as ProtoImportInfo +// import model.ExportInfo as ProtoExportInfo + +// region model + +fun ProtoScene.toEts(): EtsScene { + val files = filesList.map { it.toEts() } + return EtsScene(files) +} + +fun ProtoFile.toEts(): EtsFile { + val fileSignature = signatureOrNull!!.toEts() + return EtsFile( + signature = fileSignature, + classes = classesList.map { it.toEts(fileSignature) }, + namespaces = namespacesList.map { it.toEts(fileSignature) }, + // TODO: importInfos + // TODO: exportInfos + ) +} + +fun ProtoNamespace.toEts( + fileSignature: EtsFileSignature, +): EtsNamespace { + val namespaceSignature = signatureOrNull!!.toEts(fileSignature) + return EtsNamespace( + signature = namespaceSignature, + classes = classesList.map { it.toEts(fileSignature) }, + namespaces = namespacesList.map { it.toEts(fileSignature) }, + ) +} + +fun ProtoClass.toEts( + fileSignature: EtsFileSignature, +): EtsClass { + val classSignature = signatureOrNull!!.toEts(fileSignature) + return EtsClassImpl( + signature = classSignature, + fields = fieldsList.map { it.toEts(classSignature) }, + methods = methodsList.map { it.toEts(classSignature) }, + category = EtsClassCategory.entries[category], + superClassName = superClassName.takeIf { it != "" }, + implementedInterfaceNames = implementedInterfaceNamesList, + typeParameters = typeParametersList.map { it.toEts() }, + modifiers = EtsModifiers(modifiers), + decorators = decoratorsList.map { it.toEts() }, + ) +} + +fun ProtoField.toEts( + classSignature: EtsClassSignature, +): EtsField { + return EtsFieldImpl( + signature = signature.toEts(classSignature), + modifiers = EtsModifiers(modifiers), + isOptional = isOptional, + isDefinitelyAssigned = isDefinitelyAssigned, + ) +} + +fun ProtoMethod.toEts( + classSignature: EtsClassSignature, +): EtsMethod { + val method = EtsMethodImpl( + signature = signatureOrNull!!.toEts(classSignature), + typeParameters = typeParametersList.map { it.toEts() }, + modifiers = EtsModifiers(modifiers), + decorators = decoratorsList.map { it.toEts() }, + ) + val cfg = cfgOrNull + 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 = projectName, + fileName = fileName, + ) +} + +fun ProtoNamespaceSignature.toEts( + fileSignature: EtsFileSignature, +): EtsNamespaceSignature { + return EtsNamespaceSignature( + name = name, + file = fileSignature, + namespace = parentOrNull?.toEts(fileSignature) + ) +} + +fun ProtoClassSignature.toEts( + fileSignature: EtsFileSignature? = null, +): EtsClassSignature { + val file = (fileSignature + ?: fileOrNull?.toEts() + ?: EtsFileSignature.UNKNOWN) + return EtsClassSignature( + name = name, + file = file, + namespace = namespaceOrNull?.toEts(file), + ) +} + +fun ProtoFieldSignature.toEts( + classSignature: EtsClassSignature? = null, +): EtsFieldSignature { + return EtsFieldSignature( + enclosingClass = classSignature + ?: enclosingClassOrNull?.toEts() + ?: EtsClassSignature.UNKNOWN, + name = name, + type = type.toEts(), + ) +} + +fun ProtoMethodSignature.toEts( + classSignature: EtsClassSignature? = null, +): EtsMethodSignature { + return EtsMethodSignature( + enclosingClass = classSignature + ?: enclosingClassOrNull?.toEts() + ?: EtsClassSignature.UNKNOWN, + name = name, + parameters = parametersList.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 { + rawTypeOrNull != null -> { + EtsRawType( + kind = rawTypeOrNull!!.kind, + // TODO: extra + ) + } + + anyTypeOrNull != null -> { + EtsAnyType + } + + unknownTypeOrNull != null -> { + EtsUnknownType + } + + unionTypeOrNull != null -> { + EtsUnionType( + types = unionTypeOrNull!!.typesList.map { it.toEts() }, + ) + } + + intersectionTypeOrNull != null -> { + EtsIntersectionType( + types = intersectionTypeOrNull!!.typesList.map { it.toEts() }, + ) + } + + genericTypeOrNull != null -> { + EtsGenericType( + typeName = genericTypeOrNull!!.typeName!!, + constraint = genericTypeOrNull!!.constraintOrNull?.toEts(), + defaultType = genericTypeOrNull!!.defaultTypeOrNull?.toEts(), + ) + } + + aliasTypeOrNull != null -> { + EtsAliasType( + name = aliasTypeOrNull!!.name, + originalType = aliasTypeOrNull!!.originalTypeOrNull!!.toEts(), + // TODO: signature + // signature = aliasTypeOrNull!!.signature!!.toEts(), + signature = EtsLocalSignature( + "", EtsMethodSignature( + EtsClassSignature.UNKNOWN, "", emptyList(), + EtsUnknownType + ) + ), + ) + } + + booleanTypeOrNull != null -> { + EtsBooleanType + } + + numberTypeOrNull != null -> { + EtsNumberType + } + + stringTypeOrNull != null -> { + EtsStringType + } + + nullTypeOrNull != null -> { + EtsNullType + } + + undefinedTypeOrNull != null -> { + EtsUndefinedType + } + + voidTypeOrNull != null -> { + EtsVoidType + } + + neverTypeOrNull != null -> { + EtsNeverType + } + + literalTypeOrNull != null -> { + EtsLiteralType( + literalTypeName = literalTypeOrNull!!.literalName!!, + ) + } + + classTypeOrNull != null -> { + EtsClassType( + signature = classTypeOrNull!!.signatureOrNull!!.toEts(), + typeParameters = classTypeOrNull!!.typeParametersList.map { it.toEts() }, + ) + } + + unclearRefTypeOrNull != null -> { + EtsUnclearRefType( + name = unclearRefTypeOrNull!!.name, + typeParameters = unclearRefTypeOrNull!!.typeParametersList.map { it.toEts() }, + ) + } + + arrayTypeOrNull != null -> { + EtsArrayType( + elementType = arrayTypeOrNull!!.elementTypeOrNull!!.toEts(), + dimensions = arrayTypeOrNull!!.dimensions, + ) + } + + tupleTypeOrNull != null -> { + EtsTupleType( + types = tupleTypeOrNull!!.typesList.map { it.toEts() }, + ) + } + + functionTypeOrNull != null -> { + EtsFunctionType( + signature = functionTypeOrNull!!.signatureOrNull!!.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 (blocksList.isEmpty()) { + return EtsBlockCfg.EMPTY + } + return EtsBlockCfg( + blocks = blocksList.map { block -> + currentStmts = mutableListOf() + for (stmt in block.statementsList) { + 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 = blocksList.associate { it.id to it.successorsList.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 { + rawStmtOrNull != null -> { + EtsRawStmt( + location = loc(), + kind = rawStmtOrNull!!.kind, + // TODO: handle .text + ) + } + + nopStmtOrNull != null -> { + EtsNopStmt( + location = loc(), + ) + } + + assignStmtOrNull != null -> { + EtsAssignStmt( + location = loc(), + lhv = assignStmtOrNull!!.lhvOrNull!!.toEts() as EtsLValue, + rhv = assignStmtOrNull!!.rhvOrNull!!.toEts(), + ) + } + + returnStmtOrNull != null -> { + EtsReturnStmt( + location = loc(), + returnValue = returnStmtOrNull!!.returnValueOrNull?.let { + ensureLocal(it.toEts()) + }, + ) + } + + throwStmtOrNull != null -> { + EtsThrowStmt( + location = loc(), + exception = ensureLocal(throwStmtOrNull!!.exceptionOrNull!!.toEts()), + ) + } + + ifStmtOrNull != null -> { + EtsIfStmt( + location = loc(), + condition = ensureLocal(ifStmtOrNull!!.conditionOrNull!!.toEts()), + ) + } + + callStmtOrNull != null -> { + EtsCallStmt( + location = loc(), + expr = callStmtOrNull!!.exprOrNull!!.toEts(), + ) + } + + else -> { + error("Unsupported statement: $this") + } + } + + // endregion statements + + // region values + + // TODO: extract each branch to `.toEts()` method + // For example, `rawValueOrNull != null -> rawValueOrNull!!.toEts()` + fun ProtoValue.toEts(): EtsEntity = when { + rawValueOrNull != null -> { + EtsRawEntity( + kind = rawValueOrNull!!.kind, + extra = mapOf("text" to rawValueOrNull!!.text), + ) + } + + localOrNull != null -> { + localOrNull!!.toEts() + } + + constantOrNull != null -> { + val type = constantOrNull!!.typeOrNull!!.toEts() + when (type) { + EtsStringType -> EtsStringConstant(value = constantOrNull!!.value) + EtsBooleanType -> EtsBooleanConstant(value = constantOrNull!!.value.toBoolean()) + EtsNumberType -> EtsNumberConstant(value = constantOrNull!!.value.toDouble()) + EtsNullType -> EtsNullConstant + EtsUndefinedType -> EtsUndefinedConstant + else -> object : EtsConstant { + val value: String = constantOrNull!!.value + override val type: EtsType = type + override fun toString(): String = value + override fun accept(visitor: EtsValue.Visitor): R { + return visitor.visit(this) + } + } + } + } + + exprOrNull != null -> { + exprOrNull!!.toEts() + } + + refOrNull != null -> { + refOrNull!!.toEts() + } + + else -> { + error("Unsupported value: $this") + } + } + + fun ProtoLocal.toEts(): EtsLocal { + return EtsLocal( + name = name, + type = typeOrNull!!.toEts(), + ) + } + + // region expressions + + fun ProtoExpr.toEts(): EtsExpr = when { + newExprOrNull != null -> { + EtsNewExpr( + type = newExprOrNull!!.typeOrNull!!.toEts(), + ) + } + + newArrayExprOrNull != null -> { + EtsNewArrayExpr( + elementType = newArrayExprOrNull!!.elementTypeOrNull!!.toEts(), + size = ensureLocal(newArrayExprOrNull!!.sizeOrNull!!.toEts()), + ) + } + + deleteExprOrNull != null -> { + EtsDeleteExpr( + arg = deleteExprOrNull!!.argOrNull!!.toEts(), + ) + } + + awaitExprOrNull != null -> { + EtsAwaitExpr( + arg = awaitExprOrNull!!.argOrNull!!.toEts(), + type = awaitExprOrNull!!.typeOrNull!!.toEts(), + ) + } + + yieldExprOrNull != null -> { + EtsYieldExpr( + arg = yieldExprOrNull!!.argOrNull!!.toEts(), + type = yieldExprOrNull!!.typeOrNull!!.toEts(), + ) + } + + typeOfExprOrNull != null -> { + EtsTypeOfExpr( + arg = typeOfExprOrNull!!.argOrNull!!.toEts(), + ) + } + + instanceOfExprOrNull != null -> { + EtsInstanceOfExpr( + arg = instanceOfExprOrNull!!.argOrNull!!.toEts(), + checkType = instanceOfExprOrNull!!.checkTypeOrNull!!.toEts(), + ) + } + + castExprOrNull != null -> { + EtsCastExpr( + arg = castExprOrNull!!.argOrNull!!.toEts(), + type = castExprOrNull!!.typeOrNull!!.toEts(), + ) + } + + unaryExprOrNull != null -> { + unaryExprOrNull!!.toEts() + } + + binaryExprOrNull != null -> { + binaryExprOrNull!!.toEts() + } + + relationExprOrNull != null -> { + relationExprOrNull!!.toEts() + } + + callExprOrNull != null -> { + callExprOrNull!!.toEts() + } + + else -> { + error("Unsupported expr: $this") + } + } + + fun ProtoUnaryExpr.toEts(): EtsUnaryExpr { + return when (op) { + UnaryOperator.NEG -> { + EtsNegExpr( + arg = argOrNull!!.toEts(), + type = typeOrNull!!.toEts(), + ) + } + + UnaryOperator.LOGICAL_NOT -> { + EtsNotExpr( + arg = argOrNull!!.toEts(), + ) + } + + UnaryOperator.BITWISE_NOT -> { + EtsBitNotExpr( + arg = argOrNull!!.toEts(), + type = typeOrNull!!.toEts(), + ) + } + + else -> { + error("Unsupported unary operator: $op") + } + } + } + + fun ProtoBinaryExpr.toEts(): EtsBinaryExpr { + val left = leftOrNull!!.toEts() + val right = rightOrNull!!.toEts() + return when (op) { + BinaryOperator.ADDITION -> { + EtsAddExpr( + left = left, + right = right, + type = typeOrNull!!.toEts(), + ) + } + + BinaryOperator.SUBTRACTION -> { + EtsSubExpr( + left = left, + right = right, + type = typeOrNull!!.toEts(), + ) + } + + BinaryOperator.MULTIPLICATION -> { + EtsMulExpr( + left = left, + right = right, + type = typeOrNull!!.toEts(), + ) + } + + BinaryOperator.DIVISION -> { + EtsDivExpr( + left = left, + right = right, + type = typeOrNull!!.toEts(), + ) + } + + BinaryOperator.REMAINDER -> { + EtsRemExpr( + left = left, + right = right, + type = typeOrNull!!.toEts(), + ) + } + + BinaryOperator.EXPONENTIATION -> { + EtsExpExpr( + left = left, + right = right, + type = typeOrNull!!.toEts(), + ) + } + + BinaryOperator.LEFT_SHIFT -> { + EtsLeftShiftExpr( + left = left, + right = right, + type = typeOrNull!!.toEts(), + ) + } + + BinaryOperator.RIGHT_SHIFT -> { + EtsRightShiftExpr( + left = left, + right = right, + type = typeOrNull!!.toEts(), + ) + } + + BinaryOperator.UNSIGNED_RIGHT_SHIFT -> { + EtsUnsignedRightShiftExpr( + left = left, + right = right, + type = typeOrNull!!.toEts(), + ) + } + + BinaryOperator.BITWISE_AND -> { + EtsBitAndExpr( + left = left, + right = right, + type = typeOrNull!!.toEts(), + ) + } + + BinaryOperator.BITWISE_OR -> { + EtsBitOrExpr( + left = left, + right = right, + type = typeOrNull!!.toEts(), + ) + } + + BinaryOperator.BITWISE_XOR -> { + EtsBitXorExpr( + left = left, + right = right, + type = typeOrNull!!.toEts(), + ) + } + + BinaryOperator.LOGICAL_AND -> { + EtsAndExpr( + left = left, + right = right, + type = typeOrNull!!.toEts(), + ) + } + + BinaryOperator.LOGICAL_OR -> { + EtsOrExpr( + left = left, + right = right, + type = typeOrNull!!.toEts(), + ) + } + + BinaryOperator.NULLISH_COALESCING -> { + EtsNullishCoalescingExpr( + left = left, + right = right, + type = typeOrNull!!.toEts(), + ) + } + + else -> { + error("Unsupported binary operator: $op") + } + } + } + + fun ProtoRelationExpr.toEts(): EtsRelationExpr { + val left = leftOrNull!!.toEts() + val right = rightOrNull!!.toEts() + return when (op) { + RelationOperator.EQ -> { + EtsEqExpr( + left = left, + right = right, + ) + } + + RelationOperator.NEQ -> { + EtsNotEqExpr( + left = left, + right = right, + ) + } + + RelationOperator.STRICT_EQ -> { + EtsStrictEqExpr( + left = left, + right = right, + ) + } + + RelationOperator.STRICT_NEQ -> { + EtsStrictNotEqExpr( + left = left, + right = right, + ) + } + + RelationOperator.LT -> { + EtsLtExpr( + left = left, + right = right, + ) + } + + RelationOperator.LTE -> { + EtsLtEqExpr( + left = left, + right = right, + ) + } + + RelationOperator.GT -> { + EtsGtExpr( + left = left, + right = right, + ) + } + + RelationOperator.GTE -> { + EtsGtEqExpr( + left = left, + right = right, + ) + } + + RelationOperator.IN -> { + EtsInExpr( + left = left, + right = right, + ) + } + + else -> { + error("Unsupported relation operator: $op") + } + } + } + + fun ProtoCallExpr.toEts(): EtsCallExpr { + val callee = calleeOrNull!!.toEts() + val args = argsList.map { ensureLocal(it.toEts()) } + val type = typeOrNull!!.toEts() + return when { + instanceCallOrNull != null -> { + EtsInstanceCallExpr( + instance = instanceCallOrNull!!.instanceOrNull!!.toEts(), + callee = callee, + args = args, + type = type, + ) + } + + staticCallOrNull != null -> { + EtsStaticCallExpr( + callee = callee, + args = args, + type = type, + ) + } + + ptrCallOrNull != null -> { + EtsPtrCallExpr( + ptr = ensureLocal(ptrCallOrNull!!.ptrOrNull!!.toEts()), + callee = callee, + args = args, + type = type, + ) + } + + else -> { + error("Unsupported call expr: $this") + } + } + } + + // endregion expressions + + // region references + + fun ProtoRef.toEts(): EtsRef = when { + thisOrNull != null -> { + EtsThis( + type = thisOrNull!!.typeOrNull!!.toEts(), + ) + } + + parameterOrNull != null -> { + EtsParameterRef( + index = parameterOrNull!!.index, + type = parameterOrNull!!.typeOrNull!!.toEts(), + ) + } + + arrayAccessOrNull != null -> { + EtsArrayAccess( + array = arrayAccessOrNull!!.arrayOrNull!!.toEts(), + index = ensureLocal(arrayAccessOrNull!!.indexOrNull!!.toEts()), + type = arrayAccessOrNull!!.typeOrNull!!.toEts(), + ) + } + + fieldRefOrNull != null -> { + when { + fieldRefOrNull!!.instanceOrNull != null -> { + EtsInstanceFieldRef( + instance = ensureLocal(fieldRefOrNull!!.instanceOrNull!!.instanceOrNull!!.toEts()), + field = fieldRefOrNull!!.instanceOrNull!!.fieldOrNull!!.toEts(), + type = fieldRefOrNull!!.instanceOrNull!!.typeOrNull!!.toEts(), + ) + } + + fieldRefOrNull!!.staticOrNull != null -> { + EtsStaticFieldRef( + field = fieldRefOrNull!!.staticOrNull!!.fieldOrNull!!.toEts(), + type = fieldRefOrNull!!.staticOrNull!!.typeOrNull!!.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/grpc/ConvertToProto.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/ConvertToProto.kt new file mode 100644 index 000000000..80e939ffe --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/ConvertToProto.kt @@ -0,0 +1,128 @@ +/* + * 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 model.classSignature +import model.class_ +import model.field +import model.fieldSignature +import model.file +import model.fileSignature +import model.method +import model.methodParameter +import model.methodSignature +import model.namespace +import model.namespaceSignature +import model.scene +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.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.EtsNamespace +import org.jacodb.ets.model.EtsNamespaceSignature +import org.jacodb.ets.model.EtsScene +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 = scene { + this.files += this@toProto.projectFiles.map { it.toProto() } +} + +fun EtsFile.toProto(): ProtoFile = file { + this.signature = this@toProto.signature.toProto() + this.classes += this@toProto.classes.map { it.toProto() } + this.namespaces += this@toProto.namespaces.map { it.toProto() } +} + +fun EtsNamespace.toProto(): ProtoNamespace = namespace { + this.signature = this@toProto.signature.toProto() + this.classes += this@toProto.classes.map { it.toProto() } + this.namespaces += this@toProto.namespaces.map { it.toProto() } +} + +fun EtsClass.toProto(): ProtoClass = class_ { + this.signature = this@toProto.signature.toProto() + this.typeParameters += this@toProto.typeParameters.map { it.toProto() } + this.fields += this@toProto.fields.map { it.toProto() } + this.methods += this@toProto.methods.map { it.toProto() } +} + +fun EtsField.toProto(): ProtoField = field { + this.signature = this@toProto.signature.toProto() +} + +fun EtsMethod.toProto(): ProtoMethod = method { + this.signature = this@toProto.signature.toProto() + this.typeParameters += this@toProto.typeParameters.map { it.toProto() } + this.cfg = this@toProto.cfg.toProto() +} + +fun EtsFileSignature.toProto(): ProtoFileSignature = fileSignature { + this.projectName = this@toProto.projectName + this.fileName = this@toProto.fileName +} + +fun EtsNamespaceSignature.toProto(): ProtoNamespaceSignature = namespaceSignature { + this.name = this@toProto.name + this.file = this@toProto.file.toProto() + this@toProto.namespace?.let { this.parent = it.toProto() } +} + +fun EtsClassSignature.toProto(): ProtoClassSignature = classSignature { + this.name = this@toProto.name + this.file = this@toProto.file.toProto() + // TODO: namespace +} + +fun EtsMethodSignature.toProto(): ProtoMethodSignature = methodSignature { + this.name = this@toProto.name + this.enclosingClass = this@toProto.enclosingClass.toProto() + this.parameters += this@toProto.parameters.map { it.toProto() } + this.returnType = this@toProto.returnType.toProto() +} + +fun EtsMethodParameter.toProto(): ProtoMethodParameter = methodParameter { + this.name = this@toProto.name + this.type = this@toProto.type.toProto() + this.isOptional = this@toProto.isOptional + this.isRest = this@toProto.isRest +} + +fun EtsFieldSignature.toProto(): ProtoFieldSignature = fieldSignature { + this.name = this@toProto.name + this.enclosingClass = this@toProto.enclosingClass.toProto() + this.type = this@toProto.type.toProto() +} + +fun EtsBlockCfg.toProto(): ProtoBlockCfg = TODO() diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/EtsTypeToProto.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/EtsTypeToProto.kt new file mode 100644 index 000000000..f4f3b9098 --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/EtsTypeToProto.kt @@ -0,0 +1,187 @@ +/* + * 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 model.aliasType +import model.anyType +import model.arrayType +import model.booleanType +import model.classType +import model.functionType +import model.genericType +import model.intersectionType +import model.literalType +import model.neverType +import model.nullType +import model.numberType +import model.stringType +import model.tupleType +import model.type +import model.unclearRefType +import model.undefinedType +import model.unionType +import model.unknownType +import model.voidType +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.Type as ProtoType +import model.TypeKt.Dsl as TypeDsl + +fun EtsType.toProto(): ProtoType = type { + accept(EtsTypeToProto(this)) +} + +private class EtsTypeToProto( + val dsl: TypeDsl, +) : EtsType.Visitor { + override fun visit(type: EtsRawType) { + // NOTE: !!! + dsl.unknownType = unknownType { } + } + + override fun visit(type: EtsAnyType) { + dsl.anyType = anyType { } + } + + override fun visit(type: EtsUnknownType) { + dsl.unknownType = unknownType { } + } + + override fun visit(type: EtsUnionType) { + dsl.unionType = unionType { + this.types += type.types.map { it.toProto() } + } + } + + override fun visit(type: EtsIntersectionType) { + dsl.intersectionType = intersectionType { + this.types += type.types.map { it.toProto() } + } + } + + override fun visit(type: EtsGenericType) { + dsl.genericType = genericType { + this.typeName = type.typeName + type.defaultType?.let { this.defaultType = it.toProto() } + type.constraint?.let { this.constraint = it.toProto() } + } + } + + override fun visit(type: EtsAliasType) { + dsl.aliasType = aliasType { + this.name = type.name + this.originalType = type.originalType.toProto() + // TODO: local signature? + // this.signature = localSignature { + // this.name = this@toProto.signature.name + // this.method = this@toProto.signature.method.toProto() + // } + } + } + + override fun visit(type: EtsEnumValueType) { + TODO("${type::class.java.simpleName} is not supported yet") + } + + override fun visit(type: EtsBooleanType) { + dsl.booleanType = booleanType { } + } + + override fun visit(type: EtsNumberType) { + dsl.numberType = numberType { } + } + + override fun visit(type: EtsStringType) { + dsl.stringType = stringType { } + } + + override fun visit(type: EtsNullType) { + dsl.nullType = nullType { } + } + + override fun visit(type: EtsUndefinedType) { + dsl.undefinedType = undefinedType { } + } + + override fun visit(type: EtsVoidType) { + dsl.voidType = voidType { } + } + + override fun visit(type: EtsNeverType) { + dsl.neverType = neverType { } + } + + override fun visit(type: EtsLiteralType) { + dsl.literalType = literalType { + this.literalName = type.literalTypeName + } + } + + override fun visit(type: EtsClassType) { + dsl.classType = classType { + this.signature = type.signature.toProto() + this.typeParameters += type.typeParameters.map { it.toProto() } + } + } + + override fun visit(type: EtsUnclearRefType) { + dsl.unclearRefType = unclearRefType { + this.name = type.typeName + this.typeParameters += type.typeParameters.map { it.toProto() } + } + } + + override fun visit(type: EtsArrayType) { + dsl.arrayType = arrayType { + this.elementType = type.elementType.toProto() + this.dimensions = type.dimensions + } + } + + override fun visit(type: EtsTupleType) { + dsl.tupleType = tupleType { + this.types += type.types.map { it.toProto() } + } + } + + override fun visit(type: EtsFunctionType) { + dsl.functionType = functionType { + this.signature = type.signature.toProto() + this.typeParameters += type.typeParameters.map { it.toProto() } + } + } +} diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/EtsValueToProto.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/EtsValueToProto.kt new file mode 100644 index 000000000..bec2fea23 --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/EtsValueToProto.kt @@ -0,0 +1,124 @@ +/* + * 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 model.arrayAccess +import model.constant +import model.local +import model.parameterRef +import model.ref +import model.this_ +import model.value +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.Local as ProtoLocal +import model.Value as ProtoValue +import model.ValueKt.Dsl as ValueDsl + +fun EtsValue.toProto(): ProtoValue = value { + accept(EtsValueToProto(this)) +} + +class EtsValueToProto( + val dsl: ValueDsl, +) : EtsValue.Visitor { + override fun visit(value: EtsLocal) { + dsl.local = value.toProto() + } + + private fun visitConstant(value: EtsConstant) { + dsl.constant = constant { + this.value = value.toString() + this.type = value.type.toProto() + } + } + + override fun visit(value: EtsConstant) { + visitConstant(value) + } + + override fun visit(value: EtsStringConstant) { + visitConstant(value) + } + + override fun visit(value: EtsBooleanConstant) { + visitConstant(value) + } + + override fun visit(value: EtsNumberConstant) { + visitConstant(value) + } + + override fun visit(value: EtsNullConstant) { + visitConstant(value) + } + + override fun visit(value: EtsUndefinedConstant) { + visitConstant(value) + } + + override fun visit(value: EtsThis) { + dsl.ref = ref { + this.this_ = this_ { + this.type = value.type.toProto() + } + } + } + + override fun visit(value: EtsParameterRef) { + dsl.ref = ref { + this.parameter = parameterRef { + this.index = value.index + this.type = value.type.toProto() + } + } + } + + override fun visit(value: EtsArrayAccess) { + dsl.ref = ref { + this.arrayAccess = arrayAccess { + this.array = value.array.toProto() + this.index = value.index.toProto() + this.type = value.type.toProto() + } + } + } + + override fun visit(value: EtsInstanceFieldRef) { + TODO() + } + + override fun visit(value: EtsStaticFieldRef) { + TODO() + } +} + +fun EtsLocal.toProto(): ProtoLocal = local { + this.name = this@toProto.name + this.type = this@toProto.type.toProto() +} 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..f5bd3381f --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/LoadScene.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.grpc + +import kotlinx.coroutines.runBlocking +import mu.KotlinLogging +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 {} + +const val DEFAULT_PORT = 50051 + +fun loadScene(path: Path, port: Int = DEFAULT_PORT): ProtoScene { + logger.info { "Connecting to gRPC server on port $port..." } + val channel = grpcChannel(port = port) { + maxInboundMessageSize(64 * 1024 * 1024) // 64 MiB + } + ManagerClient(channel).use { manager -> + logger.info { "Requesting Scene for '$path'..." } + val (scene, timeLoad) = measureTimedValue { + runBlocking { manager.getScene(path.pathString) } + } + + logger.info { + "Received Scene in %.1fs with ${ + scene.filesCount + } files, ${ + scene.filesList.sumOf { f -> f.classesCount } + } classes, ${ + scene.filesList.sumOf { f -> f.classesList.sumOf { cls -> cls.methodsCount } } + } methods" + .format(timeLoad.toDouble(DurationUnit.SECONDS)) + } + return scene + } +} diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/TestManagerClient.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/TestManagerClient.kt new file mode 100644 index 000000000..b272ddba7 --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/grpc/TestManagerClient.kt @@ -0,0 +1,41 @@ +/* + * 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 org.jacodb.ets.utils.getResourcePath +import kotlin.io.path.pathString + +suspend fun main() { + val port = 50051 + val channel = grpcChannel(port = port) { + maxInboundMessageSize(64 * 1024 * 1024) // 64 MiB + } + val client = GreeterClient(channel) + client.greet(System.getProperty("user.name")) + val manager = ManagerClient(channel) + val path = getResourcePath("/projects/Launcher/source") + val scene = manager.getScene(path.pathString) + println( + "Received Scene with ${ + scene.filesList.size + } files, ${ + scene.filesList.sumOf { f -> f.classesCount } + } classes, ${ + scene.filesList.sumOf { f -> f.classesList.sumOf { cls -> cls.methodsCount } } + } methods" + ) +} 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/proto/ConvertToEts.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/ConvertToEts.kt new file mode 100644 index 000000000..bd32c08b8 --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/ConvertToEts.kt @@ -0,0 +1,1018 @@ +/* + * 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.EtsClassImpl +import org.jacodb.ets.model.EtsClassSignature +import org.jacodb.ets.model.EtsClassType +import org.jacodb.ets.model.EtsConstant +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.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 + +class ProtoToEtsConverter { + fun convert(scene: ProtoScene): EtsScene { + return scene.toEts() + } + + // 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 = emptyList(), // TODO + ) + } + + 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) }, + // TODO: category + // TODO: typeParameters + // TODO: modifiers + // TODO: decorators + // TODO: other... + ) + } + + fun ProtoField.toEts( + classSignature: EtsClassSignature, + ): EtsField { + return EtsFieldImpl( + signature = signature.toEts(classSignature), + modifiers = EtsModifiers.EMPTY, // TODO + isOptional = false, // TODO + isDefinitelyAssigned = false, // TODO + ) + } + + fun ProtoMethod.toEts( + classSignature: EtsClassSignature, + ): EtsMethod { + val method = EtsMethodImpl( + signature = signature.toEts(classSignature), + typeParameters = typeParameters.map { it.toEts() }, + // TODO: modifiers + // TODO: decorators + ) + if (cfg != null) { + method._cfg = cfg.toEts(method) + } + return method + } + + // endregion model + + // region signatures + + fun ProtoFileSignature.toEts(): EtsFileSignature { + return EtsFileSignature( + projectName = projectName, + fileName = fileName, + ) + } + + fun ProtoClassSignature.toEts( + fileSignature: EtsFileSignature? = null, + ): EtsClassSignature { + return EtsClassSignature( + name = name, + file = fileSignature + ?: file?.toEts() + ?: EtsFileSignature.UNKNOWN, + // TODO: namespace + ) + } + + fun ProtoFieldSignature.toEts( + classSignature: EtsClassSignature? = null, + ): EtsFieldSignature { + return EtsFieldSignature( + enclosingClass = classSignature + ?: enclosingClass?.toEts() + ?: EtsClassSignature.UNKNOWN, + name = name, + type = type.toEts(), + ) + } + + fun ProtoMethodSignature.toEts( + classSignature: EtsClassSignature? = null, + ): EtsMethodSignature { + return EtsMethodSignature( + enclosingClass = classSignature + ?: enclosingClass?.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 = when (val type = kind.inner) { + // rawTypeOrNull != null -> { + is ProtoRawType -> { + EtsRawType( + kind = type.kind, + // TODO: extra + ) + } + + // anyTypeOrNull != null -> { + is ProtoAnyType -> { + EtsAnyType + } + + // unknownTypeOrNull != null -> { + is ProtoUnknownType -> { + EtsUnknownType + } + + // unionTypeOrNull != null -> { + is ProtoUnionType -> { + EtsUnionType( + types = type.types.map { it.toEts() }, + ) + } + + // intersectionTypeOrNull != null -> { + is ProtoIntersectionType -> { + EtsIntersectionType( + types = type.types.map { it.toEts() }, + ) + } + + // genericTypeOrNull != null -> { + is ProtoGenericType -> { + EtsGenericType( + typeName = type.typeName, + constraint = type.constraint?.toEts(), + defaultType = type.defaultType?.toEts(), + ) + } + + // aliasTypeOrNull != null -> { + is ProtoAliasType -> { + EtsAliasType( + name = type.name, + originalType = type.originalType.toEts(), + // TODO: signature + // signature = type.signature!!.toEts(), + signature = EtsLocalSignature( + "", EtsMethodSignature( + EtsClassSignature.UNKNOWN, "", emptyList(), + EtsUnknownType + ) + ), + ) + } + + // booleanTypeOrNull != null -> { + is ProtoBooleanType -> { + EtsBooleanType + } + + // numberTypeOrNull != null -> { + is ProtoNumberType -> { + EtsNumberType + } + + // stringTypeOrNull != null -> { + is ProtoStringType -> { + EtsStringType + } + + // nullTypeOrNull != null -> { + is ProtoNullType -> { + EtsNullType + } + + // undefinedTypeOrNull != null -> { + is ProtoUndefinedType -> { + EtsUndefinedType + } + + // voidTypeOrNull != null -> { + is ProtoVoidType -> { + EtsVoidType + } + + // neverTypeOrNull != null -> { + is ProtoNeverType -> { + EtsNeverType + } + + // literalTypeOrNull != null -> { + is ProtoLiteralType -> { + EtsLiteralType( + literalTypeName = type.literalName, + ) + } + + // classTypeOrNull != null -> { + is ProtoClassType -> { + EtsClassType( + signature = type.signature.toEts(), + typeParameters = type.typeParameters.map { it.toEts() }, + ) + } + + // unclearRefTypeOrNull != null -> { + is ProtoUnclearRefType -> { + EtsUnclearRefType( + name = type.name, + typeParameters = type.typeParameters.map { it.toEts() }, + ) + } + + // arrayTypeOrNull != null -> { + is ProtoArrayType -> { + EtsArrayType( + elementType = type.elementType.toEts(), + dimensions = type.dimensions, + ) + } + + // tupleTypeOrNull != null -> { + is ProtoTupleType -> { + EtsTupleType( + types = type.types.map { it.toEts() }, + ) + } + + // functionTypeOrNull != null -> { + is ProtoFunctionType -> { + EtsFunctionType( + signature = type.signature.toEts(), + ) + } + + else -> { + error("Unsupported type: $this") + } + } + + // endregion types + + // region cfg + + fun ProtoBlockCfg.toEts(method: EtsMethod): EtsBlockCfg { + return CfgBuilder(method).build(this) + } + + inner class CfgBuilder( + val method: EtsMethod, + ) { + private lateinit var currentStmts: MutableList + + private var freeTempLocal: Int = 0 + private fun newTempLocal(): EtsLocal { + return EtsLocal( + name = "_tmp${freeTempLocal++}", + // TODO: 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() + currentStmts += EtsAssignStmt( + location = loc(), + lhv = local, + rhv = value, + ) + return local + } + + // region statements + + fun ProtoStmt.toEts(): EtsStmt = when (val stmt = kind.inner) { + // rawStmtOrNull != null -> { + is ProtoRawStmt -> { + EtsRawStmt( + location = loc(), + kind = stmt.kind, + // TODO: handle .text + ) + } + + // nopStmtOrNull != null -> { + is ProtoNopStmt -> { + EtsNopStmt( + location = loc(), + ) + } + + // assignStmtOrNull != null -> { + is ProtoAssignStmt -> { + EtsAssignStmt( + location = loc(), + lhv = stmt.lhv.toEts() as EtsLValue, + rhv = stmt.rhv.toEts(), + ) + } + + // returnStmtOrNull != null -> { + is ProtoReturnStmt -> { + EtsReturnStmt( + location = loc(), + returnValue = stmt.returnValue?.let { + ensureLocal(it.toEts()) + }, + ) + } + + // throwStmtOrNull != null -> { + is ProtoThrowStmt -> { + EtsThrowStmt( + location = loc(), + exception = ensureLocal(stmt.exception.toEts()), + ) + } + + // ifStmtOrNull != null -> { + is ProtoIfStmt -> { + EtsIfStmt( + location = loc(), + condition = ensureLocal(stmt.condition.toEts()), + ) + } + + // callStmtOrNull != null -> { + is ProtoCallStmt -> { + EtsCallStmt( + location = loc(), + expr = stmt.expr.toEts(), + ) + } + + else -> { + error("Unsupported statement: $this") + } + } + + // endregion statements + + // region values + + // TODO: extract each branch to `.toEts()` method + // For example, `rawValueOrNull != null -> rawValue.toEts()` + fun ProtoValue.toEts(): EtsEntity = when (val value = kind.inner) { + // rawValueOrNull != null -> { + is ProtoRawValue -> { + EtsRawEntity( + kind = value.kind, + extra = mapOf("text" to value.text), + ) + } + + // localOrNull != null -> { + is ProtoLocal -> { + value.toEts() + } + + // constantOrNull != null -> { + is ProtoConstant -> { + val type = value.type.toEts() + when (type) { + EtsStringType -> EtsStringConstant(value = value.value) + EtsBooleanType -> EtsBooleanConstant(value = value.value.toBoolean()) + EtsNumberType -> EtsNumberConstant(value = value.value.toDouble()) + EtsNullType -> EtsNullConstant + EtsUndefinedType -> EtsUndefinedConstant + else -> object : EtsConstant { + val value: String = value.value + override val type: EtsType = type + override fun toString(): String = this.value + override fun accept(visitor: EtsValue.Visitor): R { + return visitor.visit(this) + } + } + } + } + + // exprOrNull != null -> { + is ProtoExpr -> { + value.toEts() + } + + // refOrNull != null -> { + is ProtoRef -> { + value.toEts() + } + + else -> { + error("Unsupported value: $this") + } + } + + fun ProtoLocal.toEts(): EtsLocal { + return EtsLocal( + name = name, + type = type.toEts(), + ) + } + + // region expressions + + fun ProtoExpr.toEts(): EtsExpr = when (val expr = kind.inner) { + // newExprOrNull != null -> { + is ProtoNewExpr -> { + EtsNewExpr( + type = expr.type.toEts(), + ) + } + + // newArrayExprOrNull != null -> { + is ProtoNewArrayExpr -> { + EtsNewArrayExpr( + elementType = expr.elementType.toEts(), + size = ensureLocal(expr.size.toEts()), + ) + } + + // deleteExprOrNull != null -> { + is ProtoDeleteExpr -> { + EtsDeleteExpr( + arg = expr.arg.toEts(), + ) + } + + // awaitExprOrNull != null -> { + is ProtoAwaitExpr -> { + EtsAwaitExpr( + arg = expr.arg.toEts(), + type = expr.type.toEts(), + ) + } + + // yieldExprOrNull != null -> { + is ProtoYieldExpr -> { + EtsYieldExpr( + arg = expr.arg.toEts(), + type = expr.type.toEts(), + ) + } + + // typeOfExprOrNull != null -> { + is ProtoTypeOfExpr -> { + EtsTypeOfExpr( + arg = expr.arg.toEts(), + ) + } + + // instanceOfExprOrNull != null -> { + is ProtoInstanceOfExpr -> { + EtsInstanceOfExpr( + arg = expr.arg.toEts(), + checkType = expr.checkType.toEts(), + ) + } + + // castExprOrNull != null -> { + is ProtoCastExpr -> { + EtsCastExpr( + arg = expr.arg.toEts(), + type = expr.type.toEts(), + ) + } + + // unaryExprOrNull != null -> { + is ProtoUnaryExpr -> { + expr.toEts() + } + + // binaryExprOrNull != null -> { + is ProtoBinaryExpr -> { + expr.toEts() + } + + // relationExprOrNull != null -> { + is ProtoRelationExpr -> { + expr.toEts() + } + + // callExprOrNull != null -> { + is ProtoCallExpr -> { + 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 (val call = kind.inner) { + // instanceCallOrNull != null -> { + is ProtoInstanceCall -> { + EtsInstanceCallExpr( + instance = call.instance.toEts(), + callee = callee, + args = args, + type = type, + ) + } + + // staticCallOrNull != null -> { + is ProtoStaticCall -> { + EtsStaticCallExpr( + callee = callee, + args = args, + type = type, + ) + } + + // ptrCallOrNull != null -> { + is ProtoPtrCall -> { + EtsPtrCallExpr( + ptr = ensureLocal(call.ptr.toEts()), + callee = callee, + args = args, + type = type, + ) + } + + else -> { + error("Unsupported call expr: $this") + } + } + } + + // endregion expressions + + // region references + + fun ProtoRef.toEts(): EtsRef = when (val ref = kind.inner) { + // thisOrNull != null -> { + is ProtoThis -> { + EtsThis( + type = ref.type.toEts(), + ) + } + + // parameterOrNull != null -> { + is ProtoParameterRef -> { + EtsParameterRef( + index = ref.index, + type = ref.type.toEts(), + ) + } + + // arrayAccessOrNull != null -> { + is ProtoArrayAccess -> { + EtsArrayAccess( + array = ref.array.toEts(), + index = ensureLocal(ref.index.toEts()), + type = ref.type.toEts(), + ) + } + + // fieldRefOrNull != null -> { + is ProtoFieldRef -> { + when (val fieldRef = ref.kind.inner) { + // ref.instanceOrNull != null -> { + is ProtoInstanceFieldRef -> { + EtsInstanceFieldRef( + instance = ensureLocal(fieldRef.instance.toEts()), + field = fieldRef.field.toEts(), + type = fieldRef.type.toEts(), + ) + } + + // fieldRef.staticOrNull != null -> { + is ProtoStaticFieldRef -> { + EtsStaticFieldRef( + field = fieldRef.field.toEts(), + type = fieldRef.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..47d958f27 --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/ConvertToProto.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.proto + +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.toProto(): ProtoFileSignature = + ProtoFileSignature( + projectName = this.projectName, + fileName = this.fileName, + ) + +fun EtsNamespaceSignature.toProto(): ProtoNamespaceSignature = + ProtoNamespaceSignature( + name = this.name, + file = this.file.toProto(), + parent = this.namespace?.toProto(), + ) + +fun EtsClassSignature.toProto(): ProtoClassSignature = + ProtoClassSignature( + name = this.name, + file = this.file.toProto(), + namespace = this.namespace?.toProto(), + ) + +fun EtsFieldSignature.toProto(): ProtoFieldSignature = + ProtoFieldSignature( + enclosingClass = this.enclosingClass.toProto(), + name = this.name, + type = this.type.toProto(), + ) + +fun EtsMethodSignature.toProto(): ProtoMethodSignature = + ProtoMethodSignature( + enclosingClass = this.enclosingClass.toProto(), + name = this.name, + parameters = this.parameters.map { it.toProto() }, + returnType = this.returnType.toProto(), + ) + +fun EtsMethodParameter.toProto(): ProtoMethodParameter = + ProtoMethodParameter( + name = this.name, + type = this.type.toProto(), + isOptional = this.isOptional, + ) 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..a213d3079 --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsTypeToProto.kt @@ -0,0 +1,157 @@ +/* + * 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 + +fun EtsType.toProto(): ProtoType { + val inner = accept(EtsTypeToProto) + return ProtoType(inner) +} + +private object EtsTypeToProto : EtsType.Visitor { + override fun visit(type: EtsRawType): IProtoType { + // Note: the original (raw) type is lost! + return ProtoUnknownType + } + + override fun visit(type: EtsAnyType): IProtoType { + return ProtoAnyType + } + + override fun visit(type: EtsUnknownType): IProtoType { + return ProtoUnknownType + } + + override fun visit(type: EtsUnionType): IProtoType { + return ProtoUnionType(types = type.types.map { it.toProto() }) + } + + override fun visit(type: EtsIntersectionType): IProtoType { + return ProtoIntersectionType(types = type.types.map { it.toProto() }) + } + + override fun visit(type: EtsGenericType): IProtoType { + return ProtoGenericType( + typeName = type.typeName, + defaultType = type.defaultType?.toProto(), + constraint = type.constraint?.toProto(), + ) + } + + override fun visit(type: EtsAliasType): IProtoType { + return ProtoAliasType( + name = type.name, + originalType = type.originalType.toProto(), + // TODO: signature + // signature = LocalSignatureDto( + // type.signature.name, + // type.signature.method.toProto(), + // ), + ) + } + + override fun visit(type: EtsEnumValueType): IProtoType { + TODO("${type::class.java.simpleName} is not supported yet") + } + + override fun visit(type: EtsBooleanType): IProtoType { + return ProtoBooleanType + } + + override fun visit(type: EtsNumberType): IProtoType { + return ProtoNumberType + } + + override fun visit(type: EtsStringType): IProtoType { + return ProtoStringType + } + + override fun visit(type: EtsNullType): IProtoType { + return ProtoNullType + } + + override fun visit(type: EtsUndefinedType): IProtoType { + return ProtoUndefinedType + } + + override fun visit(type: EtsVoidType): IProtoType { + return ProtoVoidType + } + + override fun visit(type: EtsNeverType): IProtoType { + return ProtoNeverType + } + + override fun visit(type: EtsLiteralType): IProtoType { + // TODO: consider more complex logic, see DTO + return ProtoLiteralType(literalName = type.literalTypeName) + } + + override fun visit(type: EtsClassType): IProtoType { + return ProtoClassType( + signature = type.signature.toProto(), + typeParameters = type.typeParameters.map { it.toProto() }, + ) + } + + override fun visit(type: EtsUnclearRefType): IProtoType { + return ProtoUnclearRefType( + name = type.typeName, + typeParameters = type.typeParameters.map { it.toProto() }, + ) + } + + override fun visit(type: EtsArrayType): IProtoType { + return ProtoArrayType( + elementType = type.elementType.toProto(), + dimensions = type.dimensions, + ) + } + + override fun visit(type: EtsTupleType): IProtoType { + return ProtoTupleType(types = type.types.map { it.toProto() }) + } + + override fun visit(type: EtsFunctionType): IProtoType { + return ProtoFunctionType( + signature = type.signature.toProto(), + typeParameters = type.typeParameters.map { it.toProto() }, + ) + } +} 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..23207f4d2 --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/EtsValueToProto.kt @@ -0,0 +1,117 @@ +/* + * 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.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.toProto(): ProtoValue { + val inner = accept(EtsValueToProto) + return ProtoValue(inner) +} + +private object EtsValueToProto : EtsValue.Visitor { + override fun visit(value: EtsLocal): IProtoValue { + return value.toProto() + } + + private fun visitConstant(value: EtsConstant): IProtoValue { + return ProtoConstant( + value = value.toString(), + type = value.type.toProto(), + ) + } + + override fun visit(value: EtsConstant): IProtoValue { + return visitConstant(value) + } + + override fun visit(value: EtsStringConstant): IProtoValue { + return visitConstant(value) + } + + override fun visit(value: EtsBooleanConstant): IProtoValue { + return visitConstant(value) + } + + override fun visit(value: EtsNumberConstant): IProtoValue { + return visitConstant(value) + } + + override fun visit(value: EtsNullConstant): IProtoValue { + return visitConstant(value) + } + + override fun visit(value: EtsUndefinedConstant): IProtoValue { + return visitConstant(value) + } + + override fun visit(value: EtsThis): IProtoValue { + return ProtoThis( + type = value.type.toProto(), + ) + } + + override fun visit(value: EtsParameterRef): IProtoValue { + return ProtoParameterRef( + index = value.index, + type = value.type.toProto(), + ) + } + + override fun visit(value: EtsArrayAccess): IProtoValue { + return ProtoArrayAccess( + array = value.array.toProto(), + index = value.index.toProto(), + type = value.type.toProto(), + ) + } + + override fun visit(value: EtsInstanceFieldRef): IProtoValue { + return ProtoInstanceFieldRef( + instance = value.instance.toProto(), + field = value.field.toProto(), + type = value.type.toProto(), + ) + } + + override fun visit(value: EtsStaticFieldRef): IProtoValue { + return ProtoStaticFieldRef( + field = value.field.toProto(), + type = value.type.toProto(), + ) + } +} + +// TODO: .toProto for EtsValue and EtsLocal have different return types +fun EtsLocal.toProto(): ProtoLocal { + return ProtoLocal( + name = name, + type = type.toProto(), + ) +} diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/GreeterClient.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/GreeterClient.kt new file mode 100644 index 000000000..de6de1ee9 --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/GreeterClient.kt @@ -0,0 +1,72 @@ +/* + * 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:OptIn(ExperimentalSerializationApi::class) + +package org.jacodb.ets.proto + +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.defaultRequest +import io.ktor.client.request.post +import io.ktor.client.request.setBody +import io.ktor.http.ContentType +import io.ktor.http.contentType +import io.ktor.http.path +import io.ktor.serialization.kotlinx.json.json +import io.ktor.serialization.kotlinx.protobuf.protobuf +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable + +@Serializable +data class HelloRequest(val name: String) + +@Serializable +data class HelloReply(val message: String) + +class GreeterClient(val port: Int = 7777) { + val client = HttpClient(CIO) { + install(ContentNegotiation) { + json() + protobuf() + } + defaultRequest { + url { + host = "localhost" + port = this@GreeterClient.port + path("hello") + contentType(ContentType.Application.Json) + } + } + } + + suspend fun sayHello(request: HelloRequest): HelloReply { + val response = client.post { + setBody(request) + } + return response.body() + } +} + +suspend fun main() { + val greeter = GreeterClient() + val name = System.getProperty("user.name") + val request = HelloRequest(name) + val reply = greeter.sayHello(request) + println(reply) +} diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/Model.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/Model.kt new file mode 100644 index 000000000..9705b183b --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/Model.kt @@ -0,0 +1,994 @@ +/* + * 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:OptIn(ExperimentalSerializationApi::class) + +package org.jacodb.ets.proto + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.protobuf.ProtoNumber +import kotlinx.serialization.protobuf.ProtoOneOf + +// region Model + +@Serializable +@SerialName("Scene") +data class ProtoScene( + @ProtoNumber(1) val files: List = emptyList(), + @ProtoNumber(2) val sdkFiles: List = emptyList(), +) + +@Serializable +@SerialName("File") +data class ProtoFile( + @ProtoNumber(1) val signature: ProtoFileSignature, + @ProtoNumber(2) val classes: List = emptyList(), + @ProtoNumber(3) val namespaces: List = emptyList(), +) + +@Serializable +@SerialName("FileSignature") +data class ProtoFileSignature( + @ProtoNumber(1) val projectName: String, + @ProtoNumber(2) val fileName: String, +) + +@Serializable +@SerialName("Namespace") +data class ProtoNamespace( + @ProtoNumber(1) val signature: ProtoNamespaceSignature, + @ProtoNumber(2) val classes: List = emptyList(), + @ProtoNumber(3) val namespaces: List = emptyList(), +) + +@Serializable +@SerialName("NamespaceSignature") +data class ProtoNamespaceSignature( + @ProtoNumber(1) val name: String, + @ProtoNumber(2) val file: ProtoFileSignature? = null, + @ProtoNumber(3) val parent: ProtoNamespaceSignature? = null, +) + +@Serializable +@SerialName("Class") +data class ProtoClass( + @ProtoNumber(1) val signature: ProtoClassSignature, + @ProtoNumber(2) val typeParameters: List = emptyList(), + @ProtoNumber(3) val fields: List = emptyList(), + @ProtoNumber(4) val methods: List = emptyList(), +) + +@Serializable +@SerialName("ClassSignature") +data class ProtoClassSignature( + @ProtoNumber(1) val name: String, + @ProtoNumber(2) val file: ProtoFileSignature? = null, + @ProtoNumber(3) val namespace: ProtoNamespaceSignature? = null, +) + +@Serializable +@SerialName("Field") +data class ProtoField( + @ProtoNumber(1) val signature: ProtoFieldSignature, + @ProtoNumber(2) val type: ProtoType, +) + +@Serializable +@SerialName("FieldSignature") +data class ProtoFieldSignature( + @ProtoNumber(1) val enclosingClass: ProtoClassSignature? = null, + @ProtoNumber(2) val name: String, + @ProtoNumber(3) val type: ProtoType, +) + +@Serializable +@SerialName("Method") +data class ProtoMethod( + @ProtoNumber(1) val signature: ProtoMethodSignature, + @ProtoNumber(2) val typeParameters: List = emptyList(), + @ProtoNumber(3) val cfg: ProtoBlockCfg? = null, +) + +@Serializable +@SerialName("MethodSignature") +data class ProtoMethodSignature( + @ProtoNumber(1) val enclosingClass: ProtoClassSignature? = null, + @ProtoNumber(2) val name: String, + @ProtoNumber(3) val parameters: List = emptyList(), + @ProtoNumber(4) val returnType: ProtoType, +) + +@Serializable +@SerialName("MethodParameter") +data class ProtoMethodParameter( + @ProtoNumber(1) val name: String, + @ProtoNumber(2) val type: ProtoType, + @ProtoNumber(3) val isOptional: Boolean = false, + @ProtoNumber(4) val isRest: Boolean = false, +) + +@Serializable +@SerialName("BlockCfg") +data class ProtoBlockCfg( + @ProtoNumber(1) val blocks: List = emptyList(), +) + +@Serializable +@SerialName("Block") +data class ProtoBlock( + @ProtoNumber(1) val id: Int, + @ProtoNumber(2) val statements: List = emptyList(), + @ProtoNumber(3) val predecessors: List = emptyList(), + @ProtoNumber(4) val successors: List = emptyList(), +) + +// endregion + +// region Type + +@Serializable +@SerialName("Type") +data class ProtoType( + @ProtoOneOf val kind: ProtoTypeKind, +) /* : IProtoType */ { + constructor(inner: IProtoType) : this(inner.kind()) +} + +sealed interface IProtoType + +fun IProtoType.kind(): ProtoTypeKind = when (this) { + // is ProtoType -> kind + is ProtoRawType -> ProtoTypeKind.RawType(this) + is ProtoAnyType -> ProtoTypeKind.AnyType(this) + is ProtoUnknownType -> ProtoTypeKind.UnknownType(this) + is ProtoUnionType -> ProtoTypeKind.UnionType(this) + is ProtoIntersectionType -> ProtoTypeKind.IntersectionType(this) + is ProtoGenericType -> ProtoTypeKind.GenericType(this) + is ProtoAliasType -> ProtoTypeKind.AliasType(this) + is ProtoBooleanType -> ProtoTypeKind.BooleanType(this) + is ProtoNumberType -> ProtoTypeKind.NumberType(this) + is ProtoStringType -> ProtoTypeKind.StringType(this) + is ProtoNullType -> ProtoTypeKind.NullType(this) + is ProtoUndefinedType -> ProtoTypeKind.UndefinedType(this) + is ProtoVoidType -> ProtoTypeKind.VoidType(this) + is ProtoNeverType -> ProtoTypeKind.NeverType(this) + is ProtoLiteralType -> ProtoTypeKind.LiteralType(this) + is ProtoClassType -> ProtoTypeKind.ClassType(this) + is ProtoUnclearRefType -> ProtoTypeKind.UnclearRefType(this) + is ProtoArrayType -> ProtoTypeKind.ArrayType(this) + is ProtoTupleType -> ProtoTypeKind.TupleType(this) + is ProtoFunctionType -> ProtoTypeKind.FunctionType(this) +} + +@Serializable +sealed interface ProtoTypeKind { + val inner: IProtoType + + @Serializable + data class RawType( + @ProtoNumber(1) override val inner: ProtoRawType, + ) : ProtoTypeKind + + @Serializable + data class AnyType( + @ProtoNumber(2) override val inner: ProtoAnyType, + ) : ProtoTypeKind + + @Serializable + data class UnknownType( + @ProtoNumber(3) override val inner: ProtoUnknownType, + ) : ProtoTypeKind + + @Serializable + data class BooleanType( + @ProtoNumber(4) override val inner: ProtoBooleanType, + ) : ProtoTypeKind + + @Serializable + data class NumberType( + @ProtoNumber(5) override val inner: ProtoNumberType, + ) : ProtoTypeKind + + @Serializable + data class StringType( + @ProtoNumber(6) override val inner: ProtoStringType, + ) : ProtoTypeKind + + @Serializable + data class NullType( + @ProtoNumber(7) override val inner: ProtoNullType, + ) : ProtoTypeKind + + @Serializable + data class UndefinedType( + @ProtoNumber(8) override val inner: ProtoUndefinedType, + ) : ProtoTypeKind + + @Serializable + data class VoidType( + @ProtoNumber(9) override val inner: ProtoVoidType, + ) : ProtoTypeKind + + @Serializable + data class NeverType( + @ProtoNumber(10) override val inner: ProtoNeverType, + ) : ProtoTypeKind + + @Serializable + data class UnionType( + @ProtoNumber(11) override val inner: ProtoUnionType, + ) : ProtoTypeKind + + @Serializable + data class IntersectionType( + @ProtoNumber(12) override val inner: ProtoIntersectionType, + ) : ProtoTypeKind + + @Serializable + data class GenericType( + @ProtoNumber(13) override val inner: ProtoGenericType, + ) : ProtoTypeKind + + @Serializable + data class AliasType( + @ProtoNumber(14) override val inner: ProtoAliasType, + ) : ProtoTypeKind + + @Serializable + data class LiteralType( + @ProtoNumber(15) override val inner: ProtoLiteralType, + ) : ProtoTypeKind + + @Serializable + data class ClassType( + @ProtoNumber(16) override val inner: ProtoClassType, + ) : ProtoTypeKind + + @Serializable + data class UnclearRefType( + @ProtoNumber(17) override val inner: ProtoUnclearRefType, + ) : ProtoTypeKind + + @Serializable + data class ArrayType( + @ProtoNumber(18) override val inner: ProtoArrayType, + ) : ProtoTypeKind + + @Serializable + data class TupleType( + @ProtoNumber(19) override val inner: ProtoTupleType, + ) : ProtoTypeKind + + @Serializable + data class FunctionType( + @ProtoNumber(20) override val inner: ProtoFunctionType, + ) : ProtoTypeKind +} + +@Serializable +@SerialName("RawType") +data class ProtoRawType( + @ProtoNumber(1) val kind: String, +) : IProtoType { + override fun toString(): String { + return "${this::class.java.simpleName}(kind=$kind)" + } +} + +@Serializable +@SerialName("AnyType") +object ProtoAnyType : IProtoType + +@Serializable +@SerialName("UnknownType") +object ProtoUnknownType : IProtoType + +@Serializable +@SerialName("BooleanType") +object ProtoBooleanType : IProtoType + +@Serializable +@SerialName("NumberType") +object ProtoNumberType : IProtoType + +@Serializable +@SerialName("StringType") +object ProtoStringType : IProtoType + +@Serializable +@SerialName("NullType") +object ProtoNullType : IProtoType + +@Serializable +@SerialName("UndefinedType") +object ProtoUndefinedType : IProtoType + +@Serializable +@SerialName("VoidType") +object ProtoVoidType : IProtoType + +@Serializable +@SerialName("NeverType") +object ProtoNeverType : IProtoType + +@Serializable +@SerialName("UnionType") +data class ProtoUnionType( + @ProtoNumber(1) val types: List = emptyList(), +) : IProtoType + +@Serializable +@SerialName("IntersectionType") +data class ProtoIntersectionType( + @ProtoNumber(1) val types: List = emptyList(), +) : IProtoType + +@Serializable +@SerialName("GenericType") +data class ProtoGenericType( + @ProtoNumber(1) val typeName: String, + @ProtoNumber(2) val constraint: ProtoType? = null, + @ProtoNumber(3) val defaultType: ProtoType? = null, +) : IProtoType + +@Serializable +@SerialName("AliasType") +data class ProtoAliasType( + @ProtoNumber(1) val name: String, + @ProtoNumber(2) val originalType: ProtoType, +) : IProtoType + +@Serializable +@SerialName("LiteralType") +data class ProtoLiteralType( + @ProtoNumber(1) val literalName: String, +) : IProtoType + +@Serializable +@SerialName("ClassType") +data class ProtoClassType( + @ProtoNumber(1) val signature: ProtoClassSignature, + @ProtoNumber(2) val typeParameters: List = emptyList(), +) : IProtoType + +@Serializable +@SerialName("UnclearRefType") +data class ProtoUnclearRefType( + @ProtoNumber(1) val name: String, + @ProtoNumber(2) val typeParameters: List = emptyList(), +) : IProtoType + +@Serializable +@SerialName("ArrayType") +data class ProtoArrayType( + @ProtoNumber(1) val elementType: ProtoType, + @ProtoNumber(2) val dimensions: Int = 1, +) : IProtoType + +@Serializable +@SerialName("TupleType") +data class ProtoTupleType( + @ProtoNumber(1) val types: List = emptyList(), +) : IProtoType + +@Serializable +@SerialName("FunctionType") +data class ProtoFunctionType( + @ProtoNumber(1) val signature: ProtoMethodSignature, + @ProtoNumber(2) val typeParameters: List = emptyList(), +) : IProtoType + +// endregion + +// region Stmt + +@Serializable +@SerialName("Stmt") +data class ProtoStmt( + @ProtoOneOf val kind: ProtoStmtKind, +) /* : IProtoStmt */ { + constructor(inner: IProtoStmt) : this(inner.kind()) +} + +sealed interface IProtoStmt + +fun IProtoStmt.kind(): ProtoStmtKind = when (this) { + // is ProtoStmt -> kind + is ProtoRawStmt -> ProtoStmtKind.RawStmt(this) + is ProtoNopStmt -> ProtoStmtKind.NopStmt(this) + is ProtoAssignStmt -> ProtoStmtKind.AssignStmt(this) + is ProtoReturnStmt -> ProtoStmtKind.ReturnStmt(this) + is ProtoThrowStmt -> ProtoStmtKind.ThrowStmt(this) + is ProtoIfStmt -> ProtoStmtKind.IfStmt(this) + is ProtoCallStmt -> ProtoStmtKind.CallStmt(this) +} + +@Serializable +sealed interface ProtoStmtKind { + val inner: IProtoStmt + + @Serializable + data class RawStmt( + @ProtoNumber(1) override val inner: ProtoRawStmt, + ) : ProtoStmtKind + + @Serializable + data class NopStmt( + @ProtoNumber(2) override val inner: ProtoNopStmt, + ) : ProtoStmtKind + + @Serializable + data class AssignStmt( + @ProtoNumber(3) override val inner: ProtoAssignStmt, + ) : ProtoStmtKind + + @Serializable + data class ReturnStmt( + @ProtoNumber(4) override val inner: ProtoReturnStmt, + ) : ProtoStmtKind + + @Serializable + data class ThrowStmt( + @ProtoNumber(5) override val inner: ProtoThrowStmt, + ) : ProtoStmtKind + + @Serializable + data class IfStmt( + @ProtoNumber(6) override val inner: ProtoIfStmt, + ) : ProtoStmtKind + + @Serializable + data class CallStmt( + @ProtoNumber(7) override val inner: ProtoCallStmt, + ) : ProtoStmtKind +} + +@Serializable +@SerialName("RawStmt") +data class ProtoRawStmt( + @ProtoNumber(1) val kind: String, + @ProtoNumber(2) val text: String, +) : IProtoStmt + +@Serializable +@SerialName("NopStmt") +object ProtoNopStmt : IProtoStmt + +@Serializable +@SerialName("AssignStmt") +data class ProtoAssignStmt( + @ProtoNumber(1) val lhv: ProtoValue, + @ProtoNumber(2) val rhv: ProtoValue, +) : IProtoStmt + +@Serializable +@SerialName("ReturnStmt") +data class ProtoReturnStmt( + @ProtoNumber(1) val returnValue: ProtoValue? = null, +) : IProtoStmt + +@Serializable +@SerialName("ThrowStmt") +data class ProtoThrowStmt( + @ProtoNumber(1) val exception: ProtoValue, +) : IProtoStmt + +@Serializable +@SerialName("IfStmt") +data class ProtoIfStmt( + @ProtoNumber(1) val condition: ProtoValue, +) : IProtoStmt + +@Serializable +@SerialName("CallStmt") +data class ProtoCallStmt( + @ProtoNumber(1) val expr: ProtoCallExpr, +) : IProtoStmt + +// endregion + +// region Value + +@Serializable +@SerialName("Value") +data class ProtoValue( + @ProtoOneOf val kind: ProtoValueKind, +) /* : IProtoValue */ { + constructor(inner: IProtoValue) : this(inner.kind()) +} + +sealed interface IProtoValue + +fun IProtoValue.kind(): ProtoValueKind = when (this) { + // is ProtoValue -> this.kind + is ProtoRawValue -> ProtoValueKind.Raw(this) + is ProtoLocal -> ProtoValueKind.Local(this) + is ProtoConstant -> ProtoValueKind.Constant(this) + is ProtoExpr -> ProtoValueKind.Expr(this) + is ProtoRef -> ProtoValueKind.Ref(this) + else -> error("Unknown value: $this") // unreachable +} + +@Serializable +sealed interface ProtoValueKind { + val inner: IProtoValue + + @Serializable + data class Raw( + @ProtoNumber(1) override val inner: ProtoRawValue, + ) : ProtoValueKind + + @Serializable + data class Local( + @ProtoNumber(2) override val inner: ProtoLocal, + ) : ProtoValueKind + + @Serializable + data class Constant( + @ProtoNumber(3) override val inner: ProtoConstant, + ) : ProtoValueKind + + @Serializable + data class Expr( + @ProtoNumber(4) override val inner: ProtoExpr, + ) : ProtoValueKind + + @Serializable + data class Ref( + @ProtoNumber(5) override val inner: ProtoRef, + ) : ProtoValueKind +} + +@Serializable +@SerialName("RawValue") +data class ProtoRawValue( + @ProtoNumber(1) val kind: String, + @ProtoNumber(2) val text: String, + @ProtoNumber(3) val type: ProtoType, +) : IProtoValue + +@Serializable +@SerialName("Local") +data class ProtoLocal( + @ProtoNumber(1) val name: String, + @ProtoNumber(2) val type: ProtoType, +) : IProtoValue + +@Serializable +@SerialName("Constant") +data class ProtoConstant( + @ProtoNumber(1) val value: String, + @ProtoNumber(2) val type: ProtoType, +) : IProtoValue + +@Serializable +@SerialName("Expr") +data class ProtoExpr( + @ProtoOneOf val kind: ProtoExprKind, +) : IProtoExpr + +sealed interface IProtoExpr : IProtoValue + +fun IProtoExpr.kind(): ProtoExprKind = when (this) { + is ProtoExpr -> this.kind + is ProtoNewExpr -> ProtoExprKind.NewExpr(this) + is ProtoNewArrayExpr -> ProtoExprKind.NewArrayExpr(this) + is ProtoDeleteExpr -> ProtoExprKind.DeleteExpr(this) + is ProtoAwaitExpr -> ProtoExprKind.AwaitExpr(this) + is ProtoYieldExpr -> ProtoExprKind.YieldExpr(this) + is ProtoTypeOfExpr -> ProtoExprKind.TypeOfExpr(this) + is ProtoCastExpr -> ProtoExprKind.CastExpr(this) + is ProtoUnaryExpr -> ProtoExprKind.UnaryExpr(this) + is ProtoBinaryExpr -> ProtoExprKind.BinaryExpr(this) + is ProtoRelationExpr -> ProtoExprKind.RelationExpr(this) + is ProtoCallExpr -> ProtoExprKind.CallExpr(this) + else -> error("Unknown expr: $this") +} + +@Serializable +sealed interface ProtoExprKind { + val inner: IProtoExpr + + @Serializable + data class NewExpr( + @ProtoNumber(1) override val inner: ProtoNewExpr, + ) : ProtoExprKind + + @Serializable + data class NewArrayExpr( + @ProtoNumber(2) override val inner: ProtoNewArrayExpr, + ) : ProtoExprKind + + @Serializable + data class DeleteExpr( + @ProtoNumber(3) override val inner: ProtoDeleteExpr, + ) : ProtoExprKind + + @Serializable + data class AwaitExpr( + @ProtoNumber(4) override val inner: ProtoAwaitExpr, + ) : ProtoExprKind + + @Serializable + data class YieldExpr( + @ProtoNumber(5) override val inner: ProtoYieldExpr, + ) : ProtoExprKind + + @Serializable + data class TypeOfExpr( + @ProtoNumber(6) override val inner: ProtoTypeOfExpr, + ) : ProtoExprKind + + @Serializable + data class CastExpr( + @ProtoNumber(7) override val inner: ProtoCastExpr, + ) : ProtoExprKind + + @Serializable + data class UnaryExpr( + @ProtoNumber(8) override val inner: ProtoUnaryExpr, + ) : ProtoExprKind + + @Serializable + data class BinaryExpr( + @ProtoNumber(9) override val inner: ProtoBinaryExpr, + ) : ProtoExprKind + + @Serializable + data class RelationExpr( + @ProtoNumber(10) override val inner: ProtoRelationExpr, + ) : ProtoExprKind + + @Serializable + data class CallExpr( + @ProtoNumber(11) override val inner: ProtoCallExpr, + ) : ProtoExprKind +} + +@Serializable +@SerialName("NewExpr") +data class ProtoNewExpr( + @ProtoNumber(1) val type: ProtoType, +) : IProtoExpr + +@Serializable +@SerialName("NewArrayExpr") +data class ProtoNewArrayExpr( + @ProtoNumber(1) val elementType: ProtoType, + @ProtoNumber(2) val size: ProtoValue, +) : IProtoExpr + +@Serializable +@SerialName("DeleteExpr") +data class ProtoDeleteExpr( + @ProtoNumber(1) val arg: ProtoValue, +) : IProtoExpr + +@Serializable +@SerialName("AwaitExpr") +data class ProtoAwaitExpr( + @ProtoNumber(1) val arg: ProtoValue, + @ProtoNumber(2) val type: ProtoType, +) : IProtoExpr + +@Serializable +@SerialName("YieldExpr") +data class ProtoYieldExpr( + @ProtoNumber(1) val arg: ProtoValue, + @ProtoNumber(2) val type: ProtoType, +) : IProtoExpr + +@Serializable +@SerialName("TypeOfExpr") +data class ProtoTypeOfExpr( + @ProtoNumber(1) val arg: ProtoValue, +) : IProtoExpr + +@Serializable +@SerialName("CastExpr") +data class ProtoCastExpr( + @ProtoNumber(1) val arg: ProtoValue, + @ProtoNumber(2) val type: ProtoType, +) : IProtoExpr + +@Serializable +@SerialName("InstanceOfExpr") +data class ProtoInstanceOfExpr( + @ProtoNumber(1) val arg: ProtoValue, + @ProtoNumber(2) val checkType: ProtoType, +) : IProtoExpr + +@Serializable +@SerialName("UnaryExpr") +data class ProtoUnaryExpr( + @ProtoNumber(1) val op: ProtoUnaryOperator, + @ProtoNumber(2) val arg: ProtoValue, + @ProtoNumber(3) val type: ProtoType, +) : IProtoExpr + +@SerialName("UnaryOperator") +enum class ProtoUnaryOperator { + @ProtoNumber(0) + UNARY_UNKNOWN, + + @ProtoNumber(1) + NEG, // - + + @ProtoNumber(2) + BITWISE_NOT, // ~ + + @ProtoNumber(3) + LOGICAL_NOT, // ! +} + +@Serializable +@SerialName("BinaryExpr") +data class ProtoBinaryExpr( + @ProtoNumber(1) val op: ProtoBinaryOperator, + @ProtoNumber(2) val left: ProtoValue, + @ProtoNumber(3) val right: ProtoValue, + @ProtoNumber(4) val type: ProtoType, +) : IProtoExpr + +@SerialName("BinaryOperator") +enum class ProtoBinaryOperator { + @ProtoNumber(0) + BINARY_UNKNOWN, + + @ProtoNumber(1) + ADDITION, // '+' + + @ProtoNumber(2) + SUBTRACTION, // '-' + + @ProtoNumber(3) + MULTIPLICATION, // '*' + + @ProtoNumber(4) + DIVISION, // '/' + + @ProtoNumber(5) + REMAINDER, // '%' + + @ProtoNumber(6) + EXPONENTIATION, // '**' + + @ProtoNumber(7) + LEFT_SHIFT, // '<<' + + @ProtoNumber(8) + RIGHT_SHIFT, // '>>' + + @ProtoNumber(9) + UNSIGNED_RIGHT_SHIFT, // '>>>' + + @ProtoNumber(10) + BITWISE_AND, // '&' + + @ProtoNumber(11) + BITWISE_OR, // '|' + + @ProtoNumber(12) + BITWISE_XOR, // '^' + + @ProtoNumber(13) + LOGICAL_AND, // '&&' + + @ProtoNumber(14) + LOGICAL_OR, // '||' + + @ProtoNumber(15) + NULLISH_COALESCING, // '??' +} + +@Serializable +@SerialName("RelationExpr") +data class ProtoRelationExpr( + @ProtoNumber(1) val op: ProtoRelationOperator, + @ProtoNumber(2) val left: ProtoValue, + @ProtoNumber(3) val right: ProtoValue, +) : IProtoExpr + +@SerialName("RelationOperator") +enum class ProtoRelationOperator { + @ProtoNumber(0) + RELATION_UNKNOWN, + + @ProtoNumber(1) + EQ, // '==' + + @ProtoNumber(2) + NEQ, // '!=' + + @ProtoNumber(3) + STRICT_EQ, // '===' + + @ProtoNumber(4) + STRICT_NEQ, // '!==' + + @ProtoNumber(5) + LT, // '<' + + @ProtoNumber(6) + LTE, // '<=' + + @ProtoNumber(7) + GT, // '>' + + @ProtoNumber(8) + GTE, // '>=' + + @ProtoNumber(9) + IN, // 'in' +} + +// region Call + +@Serializable +@SerialName("CallExpr") +data class ProtoCallExpr( + @ProtoNumber(1) val callee: ProtoMethodSignature, + @ProtoNumber(2) val args: List, + @ProtoNumber(3) val type: ProtoType, + @ProtoOneOf val kind: ProtoCallExprKind, +) : IProtoCallExpr + +sealed interface IProtoCallExpr : IProtoExpr + +fun IProtoCallExpr.kind(): ProtoCallExprKind = when (this) { + is ProtoInstanceCall -> ProtoCallExprKind.InstanceCall(this) + is ProtoStaticCall -> ProtoCallExprKind.StaticCall(this) + is ProtoPtrCall -> ProtoCallExprKind.PtrCall(this) + else -> error("Unknown call expr: $this") +} + +@Serializable +sealed interface ProtoCallExprKind { + val inner: IProtoCallExpr + + @Serializable + data class InstanceCall( + @ProtoNumber(4) override val inner: ProtoInstanceCall, + ) : ProtoCallExprKind + + @Serializable + data class StaticCall( + @ProtoNumber(5) override val inner: ProtoStaticCall, + ) : ProtoCallExprKind + + @Serializable + data class PtrCall( + @ProtoNumber(6) override val inner: ProtoPtrCall, + ) : ProtoCallExprKind +} + +@Serializable +@SerialName("InstanceCall") +data class ProtoInstanceCall( + @ProtoNumber(1) val instance: ProtoLocal, +) : IProtoCallExpr + +@Serializable +@SerialName("StaticCall") +object ProtoStaticCall : IProtoCallExpr + +@Serializable +@SerialName("PtrCall") +data class ProtoPtrCall( + @ProtoNumber(1) val ptr: ProtoValue, +) : IProtoCallExpr + +// endregion Call + +// region Ref + +@Serializable +@SerialName("Ref") +data class ProtoRef( + @ProtoOneOf val kind: ProtoRefKind, +) : IProtoRef + +sealed interface IProtoRef : IProtoValue + +@Serializable +sealed interface ProtoRefKind { + val inner: IProtoRef + + @Serializable + data class This( + @ProtoNumber(1) override val inner: ProtoThis, + ) : ProtoRefKind + + @Serializable + data class ParameterRef( + @ProtoNumber(2) override val inner: ProtoParameterRef, + ) : ProtoRefKind + + @Serializable + data class ArrayAccess( + @ProtoNumber(3) override val inner: ProtoArrayAccess, + ) : ProtoRefKind + + @Serializable + data class FieldRef( + @ProtoNumber(4) override val inner: ProtoFieldRef, + ) : ProtoRefKind +} + +@Serializable +@SerialName("This") +data class ProtoThis( + @ProtoNumber(1) val type: ProtoType, +) : IProtoRef + +@Serializable +@SerialName("ParameterRef") +data class ProtoParameterRef( + @ProtoNumber(1) val index: Int, + @ProtoNumber(2) val type: ProtoType, +) : IProtoRef + +@Serializable +@SerialName("ArrayAccess") +data class ProtoArrayAccess( + @ProtoNumber(1) val array: ProtoLocal, + @ProtoNumber(2) val index: ProtoValue, + @ProtoNumber(3) val type: ProtoType, +) : IProtoRef + +@Serializable +@SerialName("FieldRef") +data class ProtoFieldRef( + @ProtoOneOf val kind: ProtoFieldRefKind, +) : IProtoFieldRef + +sealed interface IProtoFieldRef : IProtoRef + +@Serializable +sealed interface ProtoFieldRefKind { + val inner: IProtoFieldRef + + @Serializable + data class InstanceFieldRef( + @ProtoNumber(1) override val inner: ProtoInstanceFieldRef, + ) : ProtoFieldRefKind + + @Serializable + data class StaticFieldRef( + @ProtoNumber(2) override val inner: ProtoStaticFieldRef, + ) : ProtoFieldRefKind +} + +@Serializable +@SerialName("InstanceFieldRef") +data class ProtoInstanceFieldRef( + @ProtoNumber(1) val instance: ProtoLocal, + @ProtoNumber(2) val field: ProtoFieldSignature, + @ProtoNumber(3) val type: ProtoType, +) : IProtoFieldRef + +@Serializable +@SerialName("StaticFieldRef") +data class ProtoStaticFieldRef( + @ProtoNumber(1) val field: ProtoFieldSignature, + @ProtoNumber(2) val type: ProtoType, +) : IProtoFieldRef + +// endregion Ref + +// endregion Value diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/Schema.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/Schema.kt new file mode 100644 index 000000000..5513701e4 --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/Schema.kt @@ -0,0 +1,31 @@ +/* + * 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:OptIn(ExperimentalSerializationApi::class) + +package org.jacodb.ets.proto + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.protobuf.schema.ProtoBufSchemaGenerator + +fun main() { + val descriptors = listOf(ProtoScene.serializer().descriptor) + val schemas = ProtoBufSchemaGenerator.generateSchemaText(descriptors) + .lineSequence() + .filterNot { it.contains("WARNING:") } + .joinToString("\n") + println(schemas) +} diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/Test.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/Test.kt new file mode 100644 index 000000000..43877d648 --- /dev/null +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/proto/Test.kt @@ -0,0 +1,83 @@ +/* + * 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 kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.encodeToHexString +import kotlinx.serialization.json.Json +import kotlinx.serialization.protobuf.ProtoBuf + +@OptIn(ExperimentalSerializationApi::class) +fun main() { + val json = Json { + prettyPrint = true + prettyPrintIndent = " ".repeat(2) + } + + val fileSignature = ProtoFileSignature( + projectName = "MyProject", + fileName = "MyFile", + ) + val scene = ProtoScene( + files = listOf( + ProtoFile( + signature = fileSignature, + classes = listOf( + ProtoClass( + signature = ProtoClassSignature( + name = "MyClass", + file = fileSignature, + ), + fields = listOf( + ProtoField( + signature = ProtoFieldSignature( + enclosingClass = ProtoClassSignature( + name = "MyClass", + file = fileSignature, + ), + name = "foo", + type = ProtoType(ProtoAnyType) + ), + type = ProtoType( + ProtoArrayType( + elementType = ProtoType(ProtoAnyType), + ) + ), + ), + ) + ) + ), + ) + ) + ) + + val s = ProtoBuf.encodeToHexString(scene) + println(s) + val s2 = json.encodeToString(scene) + println(s2) + + val etsScene = ProtoToEtsConverter().convert(scene) + println( + "EtsScene has ${ + etsScene.projectFiles.size + } files, ${ + etsScene.projectAndSdkClasses.size + } classes, ${ + etsScene.projectAndSdkClasses.sumOf { it.methods.size } + } methods" + ) +} 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/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..ec8e89d7a --- /dev/null +++ b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/GrpcTest.kt @@ -0,0 +1,105 @@ +/* + * 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 mu.KotlinLogging +import org.jacodb.ets.grpc.loadScene +import org.jacodb.ets.grpc.toEts +import org.jacodb.ets.model.EtsScene +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.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.test.assertTrue +import kotlin.time.DurationUnit +import kotlin.time.measureTimedValue + +private val logger = KotlinLogging.logger {} + +class GrpcTest { + companion object { + fun getScene(path: Path): EtsScene { + val scene = loadScene(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 `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 p = getResourcePath("$prefix/$projectName/source") + val scene = getScene(p) + assertTrue(scene.projectClasses.isNotEmpty()) + } + } + } +} 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..92d49433a --- /dev/null +++ b/jacodb-ets/wire-client/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("com.squareup.wire") version "5.3.1" +} + +dependencies { + protoSource(project(":jacodb-ets:wire-protos")) + runtimeOnly("com.squareup.wire:wire-runtime:5.3.1") + api("com.squareup.wire:wire-grpc-client:5.3.1") +} + +wire { + kotlin { + rpcRole = "client" + } +} diff --git a/jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/ClientGreeter.kt b/jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/ClientGreeter.kt new file mode 100644 index 000000000..6721bb187 --- /dev/null +++ b/jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/ClientGreeter.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.service + +import com.squareup.wire.GrpcMethod +import greeter2.HelloReply +import greeter2.HelloRequest + +fun main() { + val port = 50051 + // val greeter: GreeterClient = grpcClient(port).create() + // val request = HelloRequest(name = "Kotlin") + // val response = greeter.SayHello().executeBlocking(request) + // println("Response: \"${response.message}\"") + + // Below is the manual way to create a gRPC call. + // We use it here to make 'path' contain 'greeter' instead of 'greeter2', + val client = grpcClient(port) + val call = client.newCall( + GrpcMethod( + path = "/greeter.Greeter/SayHello", + requestAdapter = HelloRequest.ADAPTER, + responseAdapter = HelloReply.ADAPTER + ) + ) + val request = HelloRequest(name = "Kotlin") + val response: HelloReply = call.executeBlocking(request) + println("Response: \"${response.message}\"") +} diff --git a/jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/ClientManager.kt b/jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/ClientManager.kt new file mode 100644 index 000000000..db261b9f9 --- /dev/null +++ b/jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/ClientManager.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 manager2.ManagerClient +import manager2.GetSceneRequest + +fun main() { + val port = 50051 + val manager: ManagerClient = grpcClient(port).create() + val path = "TODO" + val request = GetSceneRequest(path) + val scene = manager.GetScene().executeBlocking(request) + println("scene = $scene") +} diff --git a/jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/Utils.kt b/jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/Utils.kt new file mode 100644 index 000000000..39877fc1f --- /dev/null +++ b/jacodb-ets/wire-client/src/main/kotlin/org/jacodb/ets/service/Utils.kt @@ -0,0 +1,33 @@ +/* + * 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 okhttp3.OkHttpClient +import okhttp3.Protocol + +const val DEFAULT_PORT = 7777 + +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() +} diff --git a/jacodb-ets/wire-protos/build.gradle.kts b/jacodb-ets/wire-protos/build.gradle.kts new file mode 100644 index 000000000..a3d81a6db --- /dev/null +++ b/jacodb-ets/wire-protos/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + id("com.squareup.wire") version "5.3.1" +} + +dependencies { + runtimeOnly("com.squareup.wire:wire-runtime:5.3.1") +} + +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..25b9ab50f --- /dev/null +++ b/jacodb-ets/wire-protos/src/main/proto/greeter.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; +package greeter2; + +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..3c4801697 --- /dev/null +++ b/jacodb-ets/wire-protos/src/main/proto/manager.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; +package manager2; + +option java_multiple_files = true; + +import "model.proto"; + +service Manager { + rpc GetScene(GetSceneRequest) returns (model2.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..80e4c7dac --- /dev/null +++ b/jacodb-ets/wire-protos/src/main/proto/model.proto @@ -0,0 +1,461 @@ +syntax = "proto3"; +package model2; + +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 projectName = 1; + string fileName = 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..ec93fdbc9 --- /dev/null +++ b/jacodb-ets/wire-server/build.gradle.kts @@ -0,0 +1,34 @@ +plugins { + id("com.squareup.wire") version "5.3.1" +} + +buildscript { + dependencies { + classpath("com.squareup.wiregrpcserver:server-generator:1.0.0-alpha04") + } +} + +dependencies { + protoSource(project(":jacodb-ets:wire-protos")) + runtimeOnly("com.squareup.wire:wire-runtime:5.3.1") + api("com.squareup.wiregrpcserver:server:1.0.0-alpha04") + api("io.grpc:grpc-services:1.72.0") + implementation("io.grpc:grpc-protobuf:1.72.0") + runtimeOnly("io.grpc:grpc-netty:1.72.0") +} + +wire { + custom { + schemaHandlerFactory = com.squareup.wire.kotlin.grpcserver.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..cd4b87438 --- /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 greeter2.GreeterBlockingServer +import greeter2.GreeterWireGrpc +import greeter2.HelloReply +import greeter2.HelloRequest +import io.grpc.ServerBuilder +import io.grpc.protobuf.services.ProtoReflectionService +import io.grpc.stub.StreamObserver + +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) { + println("Received $request") + response.onNext(impl.SayHello(request)) + response.onCompleted() + } +} + +fun main() { + val port = 7777 + val server = ServerBuilder + .forPort(port) + .addService(GreeterService()) + .addService(@Suppress("DEPRECATION") ProtoReflectionService.newInstance()) + .build() + server.start() + println("Server listening on port ${server.port}") + server.awaitTermination() +} 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..088105955 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" } @@ -33,3 +33,9 @@ include("jacodb-benchmarks") include("jacodb-approximations") include("jacodb-taint-configuration") include("jacodb-ets") +include("jacodb-ets:protos") +include("jacodb-ets:grpc-client") +include("jacodb-ets:grpc-server") +include("jacodb-ets:wire-protos") +include("jacodb-ets:wire-client") +include("jacodb-ets:wire-server")