diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..0eade37292 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,17 @@ +--- +name: Bug report +about: Create a report to help improve Vineflower +title: '' +labels: bug +assignees: '' + +--- + +## Vineflower version +Replace this text with the version of Vineflower that you are using. + +## Describe the bug +Replace this text with a clear and concise description of what the bug is. + +## Additional information +Replace this text with any stacktraces, screenshots, example code, or class files (Only if you have the rights to distribute them!) here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000..7436991ae2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea to improve Vineflower +title: '' +labels: enhancement +assignees: '' + +--- + +## Is your feature request related to a problem? Please describe. +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +## Describe the solution you'd like +A clear and concise description of what you want to happen. + +## Additional context +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 91f7a99b66..12233a8ebb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,28 +4,29 @@ jobs: build: strategy: matrix: - java: [8, 11, 17] - runs-on: ubuntu-20.04 + java: [11, 17, 19] + runs-on: ubuntu-22.04 container: - image: openjdk:${{ matrix.java }}-jdk + image: eclipse-temurin:${{ matrix.java }} options: --user root steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - uses: gradle/wrapper-validation-action@v1 - - run: ./gradlew build --stacktrace + - name: Build with Gradle + run: | + chmod +x gradlew + ./gradlew build --stacktrace - name: Archive test results if: "${{ always() }}" - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: "test-results-java${{ matrix.java }}" path: | build/reports/ - */build/reports/ + plugins/*/build/reports/ build/test-results/**/*.xml - - name: Codecov - uses: codecov/codecov-action@v3.1.0 publish-test-results: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: [build] permissions: issues: write @@ -34,7 +35,7 @@ jobs: if: success() || failure() steps: - name: Download Artifacts - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: path: artifacts - name: Publish Test Results diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b9ed2e8926..96bc26e675 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,12 +6,12 @@ on: - '*' jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 container: - image: openjdk:15-jdk + image: eclipse-temurin:17 options: --user root steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - uses: gradle/wrapper-validation-action@v1 - run: ./gradlew build publish publishToSonatype closeAndReleaseSonatypeStagingRepository -x test --stacktrace env: diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index ab0e5ef41c..dc314b8373 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -3,12 +3,12 @@ on: [push, pull_request] jobs: publish: if: ${{ github.repository_owner == 'Vineflower' && contains(github.ref_name, 'develop') }} - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 container: - image: openjdk:15-jdk + image: eclipse-temurin:17 options: --user root steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - uses: gradle/wrapper-validation-action@v1 - run: ./gradlew publish --stacktrace env: @@ -16,4 +16,4 @@ jobs: SIGNING_KEY: ${{ secrets.SIGNING_KEY }} SIGNING_KEY_PASSPHRASE: ${{ secrets.SIGNING_KEY_PASSPHRASE }} SONATYPE_USER: ${{ secrets.SONATYPE_USER }} - SONATYPE_PASS: ${{ secrets.SONATYPE_PASS }} + SONATYPE_PASS: ${{ secrets.SONATYPE_PASS }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3e9e4d9267..fee87f98ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,25 @@ -/.idea/ +## Gradle /.gradle/ /buildSrc/.gradle/ /build/ /buildSrc/build/ + +## IDEA +/.idea/ /out/ + +## Eclipse +.settings/ +.classpath +.project +bin + +## VSCode +.vscode + +## Common /testData/classes/java*/ /testData/classes/jasm/ /testData/classes/groovy/ /testData/classes/kt/ -/testData/classes/scala/ -bin \ No newline at end of file +/testData/classes/scala/ \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a10b091658..3acab47653 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,8 +5,10 @@ Welcome! Thank you for taking an interest in contributing to Vineflower. * The [ARCHITECTURE.md](./ARCHITECTURE.md) file in the repository contains technical information on how Vineflower is structured. Reading that will greatly help with familiarizing yourself with the codebase and with PR development. * It's highly recommended to socialize your changes first through our [social platforms](https://github.com/Vineflower) before making a pull request! * Check out the issue tracker and roadmap! You can find good things to contribute there. +* Make sure your pull request is based off of the latest development branch in the repository. The main branch is a stable snapshot, representing the latest released version of the decompiler. ## Good things to contribute +* Fixes to bugs present in the decompiler. * Improvements to the readability of the decompiled code. * New feature support for the decompiler. * Optimization, to make it run faster and use fewer resources. @@ -17,7 +19,7 @@ Welcome! Thank you for taking an interest in contributing to Vineflower. * More technical issues are usually found in the [Test class](https://github.com/Vineflower/vineflower/blob/master/test/org/jetbrains/java/decompiler/SingleClassesTest.java) where individual wrong tests are described with their errors. ## Code Style and guidelines -While the code inherited from FernFlower varies with its style and syntax, it's expected that pull requests stick to our established code style. +While the code inherited from Fernflower varies with its style and syntax, it's expected that pull requests stick to our established code style. * We use UpperCamelCase for class names, camelCase for method, variable, local variable, and field names, and UPPER_SNAKE_CASE for static final fields. Please always qualify instance fields with a `this.` qualifier. * Please try to keep pull requests small and self-contained! It makes reviewing and maintaining the patch much easier. * Statements should always have braces around them, even if they are only 1 line in their length. @@ -30,12 +32,12 @@ While the code inherited from FernFlower varies with its style and syntax, it's * When contributing, you should add new tests that cover the area of code that you are targeting. Having more tests makes the decompiler more robust, so it's always appreciated. ## Resources -FernFlower is a very complex and involved bit of software, and there's a lot going on in a rather questionable structure. Naturally, understanding it all is a challenge so various resources are provided here to aid with the process. +Fernflower is a very complex and involved bit of software, and there's a lot going on in a rather questionable structure. Naturally, understanding it all is a challenge so various resources are provided here to aid with the process. * Knowing how java bytecode works is essential. You can find the detailed description of all the opcodes [here](https://docs.oracle.com/javase/specs/jvms/se16/html/jvms-6.html#jvms-6.5) or a simple list of them [here.](https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings) -* Graph theory comes up frequently in the statement analysis portion of FernFlower. It's useful knowing about [Basic Blocks](https://en.wikipedia.org/wiki/Basic_block), [Control Flow Graphs](https://en.wikipedia.org/wiki/Control-flow_graph), and [Dominators.](https://en.wikipedia.org/wiki/Dominator_(graph_theory)) +* Graph theory comes up frequently in the statement analysis portion of Fernflower. It's useful knowing about [Basic Blocks](https://en.wikipedia.org/wiki/Basic_block), [Control Flow Graphs](https://en.wikipedia.org/wiki/Control-flow_graph), and [Dominators.](https://en.wikipedia.org/wiki/Dominator_(graph_theory)) * [Static Single Assignment Form](https://en.wikipedia.org/wiki/Static_single_assignment_form) is also used widely to track variables and their versions within the decompilation stages. * The [Java Language Specification](https://docs.oracle.com/javase/specs/jls/se16/html/), while dense, is a good source of information regarding language features. -* The [ARCHITECTURE.md](./ARCHITECTURE.md) file in the repository contains important information about how FernFlower is structured. +* The [ARCHITECTURE.md](./ARCHITECTURE.md) file in the repository contains important information about how Fernflower is structured. * The [social platforms](https://github.com/Vineflower) contains many people who have worked with the code before, so any remaining questions are best asked there. ## License diff --git a/build.gradle b/build.gradle index acccc80281..07d2958634 100644 --- a/build.gradle +++ b/build.gradle @@ -1,29 +1,62 @@ // Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -import org.jetbrains.java.decompiler.build.JasmCompile +import org.vineflower.build.JasmCompile +import org.vineflower.build.TestDataRuntimesProvider plugins { id 'jacoco' + id 'com.github.johnrengelman.shadow' version '8.1.1' id 'org.jetbrains.kotlin.jvm' version '1.6.21' id("io.github.gradle-nexus.publish-plugin") version '1.3.0' } apply plugin: 'jacoco' -apply plugin: 'java' +apply plugin: 'java-test-fixtures' apply plugin: 'groovy' apply plugin: 'scala' apply plugin: 'maven-publish' apply plugin: 'signing' -compileJava { - sourceCompatibility '11' - targetCompatibility '11' +allprojects { + apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'java' + ext.isArm = System.getProperty('os.arch') == 'aarch64' + + group = 'org.vineflower' + + compileJava { + sourceCompatibility = '11' + targetCompatibility = '11' + } + + java.toolchain { + languageVersion = JavaLanguageVersion.of(11) + } + + repositories { + mavenCentral() + } + + dependencies { + implementation 'org.jetbrains:annotations:24.0.0' + testImplementation("org.junit.jupiter:junit-jupiter-api:${project.junit_bom}") + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${project.junit_bom}") + } + + test { + useJUnitPlatform() + } + + tasks.withType(JavaCompile).configureEach { + it.options.encoding = "UTF-8" + } } group = 'org.vineflower' archivesBaseName = 'vineflower' -version = '1.9.3' +version = '1.10.0' def ENV = System.getenv() version = version + (ENV.GITHUB_ACTIONS ? "" : "+local") @@ -32,6 +65,7 @@ version = version + (ENV.STATUS == "snapshot" ? "-SNAPSHOT" : "") sourceSets { main.java.srcDirs 'src' test.java.srcDirs 'test' + testFixtures.java.srcDirs 'testFixtures' testDataGroovy.groovy.srcDirs files("testData/src/groovy/") testDataKotlin.kotlin.srcDirs files("testData/src/kt/") testDataScala.scala.srcDirs files("testData/src/scala") @@ -40,16 +74,21 @@ sourceSets { repositories { mavenCentral() } dependencies { - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0' - testImplementation 'org.junit.jupiter:junit-jupiter-params:5.9.0' - testImplementation 'org.hamcrest:hamcrest:2.2' + testImplementation("org.junit.jupiter:junit-jupiter-api:${project.junit_bom}") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${project.junit_bom}") + testImplementation("org.junit.jupiter:junit-jupiter-params:${project.junit_bom}") + + testImplementation("org.hamcrest:hamcrest:${project.hamcrest}") + + testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:${project.junit_bom}") + testFixturesImplementation("org.hamcrest:hamcrest:${project.hamcrest}") - testDataGroovyImplementation 'org.codehaus.groovy:groovy:3.0.8' - testDataKotlinImplementation platform('org.jetbrains.kotlin:kotlin-bom') - testDataKotlinImplementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' - testDataScalaImplementation 'org.scala-lang:scala3-library_3:3.1.3' - testRuntimeOnly 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + testDataGroovyImplementation("org.codehaus.groovy:groovy:${project.groovy}") + testDataKotlinImplementation platform("org.jetbrains.kotlin:kotlin-bom") + testDataKotlinImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + + testDataScalaImplementation("org.scala-lang:scala3-library_3:${project.scala_library}") + testRuntimeOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk8" } @@ -63,10 +102,6 @@ jacocoTestReport { } } -java.toolchain { - languageVersion = JavaLanguageVersion.of(11) -} - tasks.withType(JavaCompile) { options.deprecation = true } @@ -82,9 +117,19 @@ void createJavaTestDataSet(int version, String suffix = "", List compile } tasks.getByName("compileTestDataJava${version}${suffix}Java") { destinationDirectory = file("testData/classes/java${version}${suffix.toLowerCase()}") - javaCompiler = javaToolchains.compilerFor { - languageVersion = JavaLanguageVersion.of(version) + if (project.isArm && version > 8 && version < 11) { + // On ARM systems, a more limited set of JVM versions are available + // We'll accept the `--release` flag so development is at least somewhat possible + javaCompiler = javaToolchains.compilerFor { + languageVersion = JavaLanguageVersion.of(11) + } + options.release = version + } else { + javaCompiler = javaToolchains.compilerFor { + languageVersion = JavaLanguageVersion.of(version) + } } + options.compilerArgs = compilerArgs } testDataClasses.dependsOn("testDataJava${version}${suffix}Classes") @@ -92,13 +137,14 @@ void createJavaTestDataSet(int version, String suffix = "", List compile def testJavaRuntimes = [:] -[8, 9, 11, 16, 17].forEach { version -> +[8, 9, 11, 16, 17, 21].forEach { version -> + def runtimeVersion = isArm && version > 8 && version < 11 ? 11 : version createJavaTestDataSet(version) testJavaRuntimes[version] = javaToolchains.launcherFor { - languageVersion = JavaLanguageVersion.of(version) + languageVersion = JavaLanguageVersion.of(runtimeVersion) } } -[16, 17, 19].forEach { version -> createJavaTestDataSet(version, "Preview", ["--enable-preview"]) } +[16, 17, 19, 21].forEach { version -> createJavaTestDataSet(version, "Preview", ["--enable-preview"]) } [8, 16].forEach { version -> createJavaTestDataSet(version, "NoDebug", ["-g:none"])} task compileTestDataJasm(type: JasmCompile) { @@ -127,29 +173,9 @@ compileTestDataScalaScala { } testDataClasses.dependsOn(testDataScalaClasses) -class TestDataRuntimesProvider implements CommandLineArgumentProvider { - @Nested - final MapProperty launchers - - @Inject - TestDataRuntimesProvider(final ObjectFactory objects) { - this.launchers = objects.mapProperty(Integer, JavaLauncher) - } - - @Override - Iterable asArguments() { - def result = [] - this.launchers.get().each { k, v -> - result << "-Djava.${k}.home=${v.metadata.installationPath.asFile.absolutePath}" - } - return result - } -} - test { maxHeapSize = "512M" - - useJUnitPlatform() + systemProperty "DOT_EXPORT_DIR", System.getProperty("DOT_EXPORT_DIR", null) systemProperty "DOT_ERROR_EXPORT_DIR", System.getProperty("DOT_ERROR_EXPORT_DIR", null) systemProperty "VALIDATE_DECOMPILED_CODE", System.getProperty("VALIDATE_DECOMPILED_CODE", "false") @@ -162,6 +188,9 @@ test { } jar { + archiveClassifier = "slim" + from sourceSets.main.output + manifest { attributes ( 'Main-Class': 'org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler', @@ -171,8 +200,21 @@ jar { } } +// If testFixtures can't be inferred from the build path, it'll need to be manually specified to be able to find +// the test fixtures class. Put it in a unique place to mark it as such. +testFixturesJar { + destinationDirectory = file("$rootDir/build/extralibs") +} + +task sourceJar(type:Jar) { + archiveClassifier = "sources" + from sourceSets.main.allSource +} + tasks.withType(Javadoc) { failOnError false + include 'org/jetbrains/java/decompiler/api/**' + include 'org/jetbrains/java/decompiler/main/extern/**' options.addStringOption('Xdoclint:none', '-quiet') options.addStringOption('encoding', 'UTF-8') options.addStringOption('charSet', 'UTF-8') @@ -188,10 +230,32 @@ tasks.withType(Jar) { preserveFileTimestamps = false } -tasks.withType(JavaCompile).configureEach { - it.options.encoding = "UTF-8" +def allJar = tasks.register('allJar', Jar) {allJar -> + archiveClassifier.set('') + from sourceSets.main.output + + manifest { + attributes ( + 'Main-Class': 'org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler', + 'Implementation-Name': "Vineflower", + 'Implementation-Version': project.version + ) + } + + subprojects.each { + // Check if gradle.properties has 'does_shadow=true', and use the shadowJar configuration if so + Task buildTask = it.tasks.named("true".equals(it.does_shadow) ? 'shadowJar' : 'jar').get() + // Make sure the task is defined as a dependency + dependsOn buildTask + // Relocate the jar into META-INF/plugins + allJar.from(buildTask.outputs) { + into 'META-INF/plugins/' + } + } } +build.dependsOn(allJar) + publishing { publications { mavenJava(MavenPublication) { @@ -224,7 +288,12 @@ publishing { } } - from components.java + // VF-only jar + artifact jar + // Jar with included plugins + artifact allJar + artifact sourcesJar + artifact javadocJar } } } @@ -249,4 +318,4 @@ signing { useInMemoryPgpKeys(signingKey, signingPassword) sign publishing.publications.mavenJava } -} +} \ No newline at end of file diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 6305977abd..2563db1e8c 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -1,4 +1,7 @@ -apply plugin: 'java' +plugins { + id 'java' + id 'groovy-gradle-plugin' +} repositories { mavenCentral() diff --git a/buildSrc/src/main/groovy/org/vineflower/build/TestDataRuntimesProvider.groovy b/buildSrc/src/main/groovy/org/vineflower/build/TestDataRuntimesProvider.groovy new file mode 100644 index 0000000000..b4ba2685e1 --- /dev/null +++ b/buildSrc/src/main/groovy/org/vineflower/build/TestDataRuntimesProvider.groovy @@ -0,0 +1,28 @@ +package org.vineflower.build + +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.MapProperty +import org.gradle.api.tasks.Nested +import org.gradle.jvm.toolchain.JavaLauncher +import org.gradle.process.CommandLineArgumentProvider + +import javax.inject.Inject + +public class TestDataRuntimesProvider implements CommandLineArgumentProvider { + @Nested + final MapProperty launchers + + @Inject + public TestDataRuntimesProvider(final ObjectFactory objects) { + this.launchers = objects.mapProperty(Integer, JavaLauncher) + } + + @Override + Iterable asArguments() { + def result = [] + this.launchers.get().each { k, v -> + result << "-Djava.${k}.home=${v.metadata.installationPath.asFile.absolutePath}" + } + return result + } +} \ No newline at end of file diff --git a/buildSrc/src/main/java/org/jetbrains/java/decompiler/build/JasmCompile.java b/buildSrc/src/main/java/org/vineflower/build/JasmCompile.java similarity index 92% rename from buildSrc/src/main/java/org/jetbrains/java/decompiler/build/JasmCompile.java rename to buildSrc/src/main/java/org/vineflower/build/JasmCompile.java index 5149fa6a35..ed37840b66 100644 --- a/buildSrc/src/main/java/org/jetbrains/java/decompiler/build/JasmCompile.java +++ b/buildSrc/src/main/java/org/vineflower/build/JasmCompile.java @@ -1,4 +1,4 @@ -package org.jetbrains.java.decompiler.build; +package org.vineflower.build; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.model.ReplacedBy; @@ -7,12 +7,9 @@ import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.SourceTask; import org.gradle.api.tasks.TaskAction; -import org.gradle.api.tasks.compile.CompileOptions; -import org.gradle.work.InputChanges; import org.openjdk.asmtools.jasm.Main; import java.io.File; -import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; diff --git a/buildSrc/src/main/java/org/jetbrains/java/decompiler/build/JasmCompileOptions.java b/buildSrc/src/main/java/org/vineflower/build/JasmCompileOptions.java similarity index 90% rename from buildSrc/src/main/java/org/jetbrains/java/decompiler/build/JasmCompileOptions.java rename to buildSrc/src/main/java/org/vineflower/build/JasmCompileOptions.java index e24e843448..f15bfea83e 100644 --- a/buildSrc/src/main/java/org/jetbrains/java/decompiler/build/JasmCompileOptions.java +++ b/buildSrc/src/main/java/org/vineflower/build/JasmCompileOptions.java @@ -1,4 +1,4 @@ -package org.jetbrains.java.decompiler.build; +package org.vineflower.build; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.compile.AbstractOptions; diff --git a/gradle.properties b/gradle.properties index e5d9a95bf5..c7e5b6bd4d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,7 @@ kotlin.stdlib.default.dependency = false + +# Dependencies for Gradle +groovy=3.0.9 +hamcrest=2.2 +junit_bom=5.9.2 +scala_library=3.2.2 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2a..7f93135c49 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2e6e5897b5..1af9e0930b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index c53aefaa5f..0adc8e1a53 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -32,10 +32,10 @@ # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +131,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,6 +198,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in @@ -205,6 +214,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index 107acd32c4..93e3f59f13 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/intellij.java.decompiler.engine.iml b/intellij.java.decompiler.engine.iml deleted file mode 100644 index 8daf525178..0000000000 --- a/intellij.java.decompiler.engine.iml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/plugins/idea-not-null/.gitignore b/plugins/idea-not-null/.gitignore new file mode 100644 index 0000000000..d310fd352d --- /dev/null +++ b/plugins/idea-not-null/.gitignore @@ -0,0 +1,3 @@ +/build/ +/out/ +bin \ No newline at end of file diff --git a/plugins/idea-not-null/build.gradle b/plugins/idea-not-null/build.gradle new file mode 100644 index 0000000000..4db1e4e6cd --- /dev/null +++ b/plugins/idea-not-null/build.gradle @@ -0,0 +1,6 @@ +archivesBaseName = 'vineflower-idea-not-null' + +dependencies { + implementation project(":") + testImplementation testFixtures(project(":")) +} \ No newline at end of file diff --git a/plugins/idea-not-null/gradle.properties b/plugins/idea-not-null/gradle.properties new file mode 100644 index 0000000000..0d22531f6d --- /dev/null +++ b/plugins/idea-not-null/gradle.properties @@ -0,0 +1 @@ +does_shadow=false \ No newline at end of file diff --git a/plugins/idea-not-null/src/main/java/org/vineflower/ideanotnull/IdeaNotNullOptions.java b/plugins/idea-not-null/src/main/java/org/vineflower/ideanotnull/IdeaNotNullOptions.java new file mode 100644 index 0000000000..1fd13b2fd3 --- /dev/null +++ b/plugins/idea-not-null/src/main/java/org/vineflower/ideanotnull/IdeaNotNullOptions.java @@ -0,0 +1,16 @@ +package org.vineflower.ideanotnull; + +import org.jetbrains.java.decompiler.api.plugin.PluginOptions; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; + +public interface IdeaNotNullOptions { + @IFernflowerPreferences.Name("Resugar Intellij IDEA @NotNull") + @IFernflowerPreferences.Description("Resugar Intellij IDEA's code generated by @NotNull annotations.") + @IFernflowerPreferences.ShortName("inn") + @IFernflowerPreferences.Type(IFernflowerPreferences.Type.BOOLEAN) + String IDEA_NOT_NULL_ANNOTATION = "resugar-idea-notnull"; + + static void addDefaults(PluginOptions.AddDefaults cons) { + cons.addDefault(IDEA_NOT_NULL_ANNOTATION, "1"); + } +} diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/IdeaNotNullHelper.java b/plugins/idea-not-null/src/main/java/org/vineflower/ideanotnull/IdeaNotNullPass.java similarity index 92% rename from src/org/jetbrains/java/decompiler/modules/decompiler/IdeaNotNullHelper.java rename to plugins/idea-not-null/src/main/java/org/vineflower/ideanotnull/IdeaNotNullPass.java index 7ef3e7a5c8..64f7cc8c86 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/IdeaNotNullHelper.java +++ b/plugins/idea-not-null/src/main/java/org/vineflower/ideanotnull/IdeaNotNullPass.java @@ -1,10 +1,14 @@ -// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -package org.jetbrains.java.decompiler.modules.decompiler; +package org.vineflower.ideanotnull; +import org.jetbrains.java.decompiler.api.plugin.pass.Pass; +import org.jetbrains.java.decompiler.api.plugin.pass.PassContext; import org.jetbrains.java.decompiler.code.CodeConstants; import org.jetbrains.java.decompiler.code.cfg.BasicBlock; import org.jetbrains.java.decompiler.main.DecompilerContext; import org.jetbrains.java.decompiler.main.collectors.CounterContainer; +import org.jetbrains.java.decompiler.modules.decompiler.SequenceHelper; +import org.jetbrains.java.decompiler.modules.decompiler.StatEdge; +import org.jetbrains.java.decompiler.modules.decompiler.ValidationHelper; import org.jetbrains.java.decompiler.modules.decompiler.exps.*; import org.jetbrains.java.decompiler.modules.decompiler.exps.FunctionExprent.FunctionType; import org.jetbrains.java.decompiler.modules.decompiler.stats.*; @@ -12,14 +16,23 @@ import org.jetbrains.java.decompiler.struct.attr.StructAnnotationAttribute; import org.jetbrains.java.decompiler.struct.attr.StructAnnotationParameterAttribute; import org.jetbrains.java.decompiler.struct.attr.StructGeneralAttribute; +import org.jetbrains.java.decompiler.struct.gen.CodeType; import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; import java.util.ArrayList; import java.util.List; -public final class IdeaNotNullHelper { - +public class IdeaNotNullPass implements Pass{ + + public boolean run(PassContext ctx){ + if (!DecompilerContext.getOption(IdeaNotNullOptions.IDEA_NOT_NULL_ANNOTATION)) { + return false; + } + return removeHardcodedChecks(ctx.getRoot(), ctx.getMethod()); + } + + // moved from IdeaNotNullHelper public static boolean removeHardcodedChecks(RootStatement root, StructMethod mt) { boolean checks_removed = false; @@ -72,7 +85,7 @@ private static boolean findAndRemoveParameterCheck(Statement stat, StructMethod Exprent second_param = func.getLstOperands().get(1); if (second_param instanceof ConstExprent && - second_param.getExprType().type == CodeConstants.TYPE_NULL) { // TODO: reversed parameter order + second_param.getExprType().type == CodeType.NULL) { // TODO: reversed parameter order if (first_param instanceof VarExprent && ((InvocationExprent)ifbranch.getExprents().get(0)).getName().equals("$$$reportNull$$$0")) { VarExprent var = (VarExprent)first_param; @@ -222,7 +235,7 @@ private static boolean removeReturnCheck(Statement stat, StructMethod mt) { Statement elsebranch = ifparent.getElsestat(); if (second_param instanceof ConstExprent && - second_param.getExprType().type == CodeConstants.TYPE_NULL) { // TODO: reversed parameter order + second_param.getExprType().type == CodeType.NULL) { // TODO: reversed parameter order //if(first_param instanceof VarExprent && ((VarExprent)first_param).getIndex() == var_value.getIndex()) { if (first_param.equals(exprent_value)) { // TODO: check for absence of side effects like method invocations etc. if (ifbranch instanceof BasicBlockStatement && @@ -286,7 +299,7 @@ else if (parent != null && Statement ifbranch = ifstat.getIfstat(); if (second_param instanceof ConstExprent && - second_param.getExprType().type == CodeConstants.TYPE_NULL) { // TODO: reversed parameter order + second_param.getExprType().type == CodeType.NULL) { // TODO: reversed parameter order if (first_param.equals(exprent_value)) { // TODO: check for absence of side effects like method invocations etc. if (ifbranch instanceof BasicBlockStatement && ifbranch.getExprents().size() == 1 && diff --git a/plugins/idea-not-null/src/main/java/org/vineflower/ideanotnull/IdeaNotNullPlugin.java b/plugins/idea-not-null/src/main/java/org/vineflower/ideanotnull/IdeaNotNullPlugin.java new file mode 100644 index 0000000000..5bc9b8f086 --- /dev/null +++ b/plugins/idea-not-null/src/main/java/org/vineflower/ideanotnull/IdeaNotNullPlugin.java @@ -0,0 +1,31 @@ +package org.vineflower.ideanotnull; + +import org.jetbrains.annotations.Nullable; +import org.jetbrains.java.decompiler.api.plugin.Plugin; +import org.jetbrains.java.decompiler.api.java.JavaPassLocation; +import org.jetbrains.java.decompiler.api.java.JavaPassRegistrar; +import org.jetbrains.java.decompiler.api.plugin.PluginOptions; +import org.jetbrains.java.decompiler.api.plugin.pass.NamedPass; +import org.jetbrains.java.decompiler.util.Pair; + +public class IdeaNotNullPlugin implements Plugin { + + public String id() { + return "IdeaNotNull"; + } + + @Override + public String description() { + return "Decompiles code inserted by Intellij IDEA's @NotNull annotation."; + } + + @Override + public void registerJavaPasses(JavaPassRegistrar registrar) { + registrar.register(JavaPassLocation.MAIN_LOOP, new NamedPass("IdeaNotNull", new IdeaNotNullPass())); + } + + @Override + public @Nullable PluginOptions getPluginOptions() { + return () -> Pair.of(IdeaNotNullOptions.class, IdeaNotNullOptions::addDefaults); + } +} diff --git a/plugins/idea-not-null/src/main/resources/META-INF/services/org.jetbrains.java.decompiler.api.plugin.Plugin b/plugins/idea-not-null/src/main/resources/META-INF/services/org.jetbrains.java.decompiler.api.plugin.Plugin new file mode 100644 index 0000000000..9b1e901ede --- /dev/null +++ b/plugins/idea-not-null/src/main/resources/META-INF/services/org.jetbrains.java.decompiler.api.plugin.Plugin @@ -0,0 +1 @@ +org.vineflower.ideanotnull.IdeaNotNullPlugin \ No newline at end of file diff --git a/plugins/idea-not-null/src/test/java/org/vineflower/ideanotnull/InnClassesTest.java b/plugins/idea-not-null/src/test/java/org/vineflower/ideanotnull/InnClassesTest.java new file mode 100644 index 0000000000..cef4bf35fb --- /dev/null +++ b/plugins/idea-not-null/src/test/java/org/vineflower/ideanotnull/InnClassesTest.java @@ -0,0 +1,26 @@ +package org.vineflower.ideanotnull; + +import org.jetbrains.java.decompiler.SingleClassesTestBase; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; + +import static org.jetbrains.java.decompiler.SingleClassesTestBase.TestDefinition.Version.CUSTOM; + +public class InnClassesTest extends SingleClassesTestBase { + + protected void registerAll() { + registerSet("With Idea Not Null", this::registerInn, + IFernflowerPreferences.BYTECODE_SOURCE_MAPPING, "1", + IFernflowerPreferences.DUMP_ORIGINAL_LINES, "1", + IFernflowerPreferences.DUMP_EXCEPTION_ON_ERROR, "0", + IFernflowerPreferences.IGNORE_INVALID_BYTECODE, "1", + IFernflowerPreferences.VERIFY_ANONYMOUS_CLASSES, "1", + IFernflowerPreferences.INCLUDE_ENTIRE_CLASSPATH, "0", + IFernflowerPreferences.TERNARY_CONDITIONS, "1", + IFernflowerPreferences.FORCE_JSR_INLINE, "1" + ); + } + + private void registerInn() { + registerRaw(CUSTOM, "TestIdeaNotNull"); + } +} diff --git a/plugins/idea-not-null/testData/classes/custom/TestIdeaNotNull.class b/plugins/idea-not-null/testData/classes/custom/TestIdeaNotNull.class new file mode 100644 index 0000000000..ca21087385 Binary files /dev/null and b/plugins/idea-not-null/testData/classes/custom/TestIdeaNotNull.class differ diff --git a/plugins/idea-not-null/testData/results/TestIdeaNotNull.dec b/plugins/idea-not-null/testData/results/TestIdeaNotNull.dec new file mode 100644 index 0000000000..02010fc8e0 --- /dev/null +++ b/plugins/idea-not-null/testData/results/TestIdeaNotNull.dec @@ -0,0 +1,245 @@ +package pkg; + +import java.util.Random; +import org.jetbrains.annotations.NotNull; + +public class TestIdeaNotNull { + @NotNull + public String test() { + String s = "";// 10 + int i = new Random().nextInt(12);// 11 + if (i == 0) {// 13 + return s;// 14 + } else { + for (int j = 0; j < i; j++) {// 17 + s = s + i;// 18 + } + + return s;// 21 + } + } + + public void test1(@NotNull String s) { + System.out.println(s);// 25 + }// 26 + + public void test2(@NotNull String s, @NotNull Integer i) { + System.out.println(s);// 29 + System.out.println(i);// 31 + }// 32 + + public void test3(@NotNull String s, @NotNull Integer i, @NotNull Float f) { + System.out.println(s);// 35 + System.out.println(i);// 37 + if (i == 0) {// 39 + System.out.println(f);// 40 + } + }// 42 + + @NotNull + public String test4() { + String s = "";// 46 + int i = new Random().nextInt(12);// 47 + + for (int j = 0; j < i; j++) {// 49 + s = s + i;// 50 + } + + throw new RuntimeException(s);// 53 + } + + @NotNull + public String test5(@NotNull Integer i) { + String s = "";// 58 + if (i == 0) {// 60 + return s;// 61 + } else { + for (int j = 0; j < i; j++) {// 64 + s = s + i;// 65 + } + + return s;// 68 + } + } +} + +class 'pkg/TestIdeaNotNull' { + method 'test ()Ljava/lang/String;' { + 0 8 + 1 8 + 2 8 + a 9 + b 9 + c 9 + d 9 + e 9 + f 9 + 10 10 + 11 10 + 14 11 + 1d 11 + 1e 13 + 1f 13 + 20 13 + 21 13 + 22 13 + 2c 14 + 30 14 + 34 14 + 35 14 + 36 14 + 37 14 + 38 13 + 39 13 + 3a 13 + 3e 17 + 47 17 + } + + method 'test1 (Ljava/lang/String;)V' { + 8 22 + 9 22 + a 22 + b 22 + c 22 + d 22 + e 22 + f 23 + } + + method 'test2 (Ljava/lang/String;Ljava/lang/Integer;)V' { + 10 26 + 11 26 + 12 26 + 13 26 + 14 26 + 15 26 + 16 26 + 17 27 + 18 27 + 19 27 + 1a 27 + 1b 27 + 1c 27 + 1d 27 + 1e 28 + } + + method 'test3 (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Float;)V' { + 1a 31 + 1b 31 + 1c 31 + 1d 31 + 1e 31 + 1f 31 + 20 31 + 21 32 + 22 32 + 23 32 + 24 32 + 25 32 + 26 32 + 27 32 + 28 33 + 29 33 + 2a 33 + 2b 33 + 2c 33 + 2f 34 + 30 34 + 31 34 + 32 34 + 33 34 + 36 36 + } + + method 'test4 ()Ljava/lang/String;' { + 0 40 + 1 40 + 2 40 + a 41 + b 41 + c 41 + d 41 + e 41 + f 41 + 10 43 + 11 43 + 12 43 + 13 43 + 14 43 + 1e 44 + 22 44 + 26 44 + 27 44 + 28 44 + 29 44 + 2a 43 + 2b 43 + 2c 43 + 34 47 + 38 47 + } + + method 'test5 (Ljava/lang/Integer;)Ljava/lang/String;' { + 9 52 + a 52 + b 52 + c 53 + d 53 + e 53 + f 53 + 10 53 + 13 54 + 1d 54 + 1e 56 + 1f 56 + 20 56 + 21 56 + 22 56 + 23 56 + 24 56 + 25 56 + 2f 57 + 33 57 + 37 57 + 38 57 + 39 57 + 3a 57 + 3b 56 + 3c 56 + 3d 56 + 41 60 + 4b 60 + } +} + +Lines mapping: +10 <-> 9 +11 <-> 10 +13 <-> 11 +14 <-> 12 +17 <-> 14 +18 <-> 15 +21 <-> 18 +25 <-> 23 +26 <-> 24 +29 <-> 27 +31 <-> 28 +32 <-> 29 +35 <-> 32 +37 <-> 33 +39 <-> 34 +40 <-> 35 +42 <-> 37 +46 <-> 41 +47 <-> 42 +49 <-> 44 +50 <-> 45 +53 <-> 48 +58 <-> 53 +60 <-> 54 +61 <-> 55 +64 <-> 57 +65 <-> 58 +68 <-> 61 diff --git a/plugins/kotlin/.gitignore b/plugins/kotlin/.gitignore new file mode 100644 index 0000000000..7dc657844a --- /dev/null +++ b/plugins/kotlin/.gitignore @@ -0,0 +1,4 @@ +/build/ +/out/ +/testData/classes/kt/ +bin \ No newline at end of file diff --git a/plugins/kotlin/build.gradle b/plugins/kotlin/build.gradle new file mode 100644 index 0000000000..d906f1f5f1 --- /dev/null +++ b/plugins/kotlin/build.gradle @@ -0,0 +1,57 @@ +plugins { + id 'java' + id 'org.jetbrains.kotlin.jvm' +} + +apply plugin: 'com.github.johnrengelman.shadow' +apply plugin: 'java' +apply plugin: 'maven-publish' + +group = 'org.vineflower' +version = "0.1.0" +archivesBaseName = 'vineflower-kotlin' + +sourceSets { + testDataKotlin.kotlin.srcDirs files("testData/src/kt/") +} + +dependencies { + shadow project(":") + testImplementation testFixtures(project(":")) + implementation fileTree(dir: 'libs', include: ['*.jar']) + + testDataKotlinImplementation platform('org.jetbrains.kotlin:kotlin-bom') + testDataKotlinImplementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + testRuntimeOnly 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' +} + +task testDataClasses { + group = 'build' +} +testClasses.dependsOn(testDataClasses) + +compileTestDataKotlinKotlin { + destinationDirectory = file("testData/classes/kt") +} +testDataClasses.dependsOn(testDataKotlinClasses) + +jar { + enabled = false +} + +shadowJar { + archiveClassifier.set('') +} + +build.dependsOn(shadowJar) + +// TODO: re-enable after we're publishing for this! +//publishing { +// publications { +// mavenJava(MavenPublication) { +// artifactId = "vineflower-kotlin" +// from components.java +// artifact(shadowJar) +// } +// } +//} \ No newline at end of file diff --git a/plugins/kotlin/gradle.properties b/plugins/kotlin/gradle.properties new file mode 100644 index 0000000000..1dcb5305f3 --- /dev/null +++ b/plugins/kotlin/gradle.properties @@ -0,0 +1 @@ +does_shadow=true \ No newline at end of file diff --git a/plugins/kotlin/libs/metadata.jar b/plugins/kotlin/libs/metadata.jar new file mode 100644 index 0000000000..ddfe66f5b8 Binary files /dev/null and b/plugins/kotlin/libs/metadata.jar differ diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinChooser.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinChooser.java new file mode 100644 index 0000000000..673b498158 --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinChooser.java @@ -0,0 +1,146 @@ +package org.vineflower.kotlin; + +import kotlinx.metadata.internal.metadata.ProtoBuf; +import kotlinx.metadata.internal.metadata.jvm.JvmProtoBuf; +import kotlinx.metadata.internal.protobuf.ExtensionRegistryLite; +import org.jetbrains.java.decompiler.api.plugin.LanguageChooser; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.modules.decompiler.exps.AnnotationExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.ConstExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.NewExprent; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.attr.StructAnnotationAttribute; +import org.jetbrains.java.decompiler.struct.attr.StructGeneralAttribute; +import org.jetbrains.java.decompiler.util.Key; +import org.vineflower.kotlin.metadata.BitEncoding; +import org.vineflower.kotlin.metadata.MetadataNameResolver; + +import java.io.ByteArrayInputStream; +import java.util.Objects; + +public class KotlinChooser implements LanguageChooser { + private static final Key[] ANNOTATION_ATTRIBUTES = { + StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS, StructGeneralAttribute.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS + }; + + private static final ExtensionRegistryLite EXTENSIONS = ExtensionRegistryLite.newInstance(); + static { + JvmProtoBuf.registerAllExtensions(EXTENSIONS); + } + + @Override + public boolean isLanguage(StructClass cl) { + if (!DecompilerContext.getOption(KotlinOptions.DECOMPILE_KOTLIN)) { + return false; + } + + // Try to find @Metadata() + + for (Key key : ANNOTATION_ATTRIBUTES) { + if (cl.hasAttribute(key)) { + StructAnnotationAttribute attr = cl.getAttribute((Key) key); + for (AnnotationExprent anno : attr.getAnnotations()) { + if (anno.getClassName().equals("kotlin/Metadata")) { + setContextVariables(cl); + return true; + } + } + } + } + + DecompilerContext.setProperty(KotlinDecompilationContext.CURRENT_TYPE, null); + return false; + } + + public static void setContextVariables(StructClass cl) { + AnnotationExprent anno = null; + + loop: + for (Key key : ANNOTATION_ATTRIBUTES) { + if (cl.hasAttribute(key)) { + StructAnnotationAttribute attr = cl.getAttribute((Key) key); + for (AnnotationExprent a : attr.getAnnotations()) { + if (a.getClassName().equals("kotlin/Metadata")) { + anno = a; + break loop; + } + } + } + } + + try { + int kIndex = anno.getParNames().indexOf("k"); + int d1Index = anno.getParNames().indexOf("d1"); + int d2Index = anno.getParNames().indexOf("d2"); + + if (kIndex == -1) { + DecompilerContext.getLogger().writeMessage("No k attribute in class metadata for class " + cl.qualifiedName, IFernflowerLogger.Severity.WARN); + DecompilerContext.setProperty(KotlinDecompilationContext.CURRENT_TYPE, null); + return; + } + + if (d1Index == -1) { + DecompilerContext.getLogger().writeMessage("No d1 attribute in class metadata for class " + cl.qualifiedName, IFernflowerLogger.Severity.WARN); + DecompilerContext.setProperty(KotlinDecompilationContext.CURRENT_TYPE, null); + return; + } + + if (d2Index == -1) { + DecompilerContext.getLogger().writeMessage("No d2 attribute in class metadata for class " + cl.qualifiedName, IFernflowerLogger.Severity.WARN); + DecompilerContext.setProperty(KotlinDecompilationContext.CURRENT_TYPE, null); + return; + } + + int k = (int) ((ConstExprent) anno.getParValues().get(kIndex)).getValue(); + Exprent d1 = anno.getParValues().get(d1Index); + Exprent d2 = anno.getParValues().get(d2Index); + + String[] data1 = getDataFromExpr((NewExprent) d1); + + String[] data2 = getDataFromExpr((NewExprent) d2); + + byte[] buf = BitEncoding.decodeBytes(data1); + + ByteArrayInputStream input = new ByteArrayInputStream(buf); + JvmProtoBuf.StringTableTypes types = JvmProtoBuf.StringTableTypes.parseDelimitedFrom(input, EXTENSIONS); + + DecompilerContext.setProperty(KotlinDecompilationContext.NAME_RESOLVER, new MetadataNameResolver(types, data2)); + + if (k == 1) { // Class file + ProtoBuf.Class pcl = ProtoBuf.Class.parseFrom(input, EXTENSIONS); + + DecompilerContext.setProperty(KotlinDecompilationContext.CURRENT_TYPE, KotlinDecompilationContext.KotlinType.CLASS); + DecompilerContext.setProperty(KotlinDecompilationContext.CURRENT_CLASS, pcl); + + } else if (k == 2) { // File facade + ProtoBuf.Package pcl = ProtoBuf.Package.parseFrom(input, EXTENSIONS); + DecompilerContext.setProperty(KotlinDecompilationContext.CURRENT_TYPE, KotlinDecompilationContext.KotlinType.FILE); + DecompilerContext.setProperty(KotlinDecompilationContext.FILE_PACKAGE, pcl); + } else if (k == 3) { // Synthetic class + ProtoBuf.Function func = ProtoBuf.Function.parseFrom(input, EXTENSIONS); + DecompilerContext.setProperty(KotlinDecompilationContext.CURRENT_TYPE, KotlinDecompilationContext.KotlinType.SYNTHETIC_CLASS); + DecompilerContext.setProperty(KotlinDecompilationContext.SYNTHETIC_CLASS, func); + } else if (k == 5) { // Multi-file facade + ProtoBuf.Package pcl = ProtoBuf.Package.parseFrom(input, EXTENSIONS); + DecompilerContext.setProperty(KotlinDecompilationContext.CURRENT_TYPE, KotlinDecompilationContext.KotlinType.MULTIFILE_CLASS); + DecompilerContext.setProperty(KotlinDecompilationContext.MULTIFILE_PACKAGE, pcl); + } else { + DecompilerContext.setProperty(KotlinDecompilationContext.CURRENT_TYPE, null); + } + } catch (Exception e) { + DecompilerContext.getLogger().writeMessage("Failed to parse metadata for class " + cl.qualifiedName, IFernflowerLogger.Severity.WARN, e); + DecompilerContext.setProperty(KotlinDecompilationContext.CURRENT_TYPE, null); + } + } + + private static String[] getDataFromExpr(NewExprent d2) { + return d2.getLstArrayElements() + .stream() + .map(ConstExprent.class::cast) + .map(ConstExprent::getValue) + .map(String.class::cast) + .toArray(String[]::new); + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinDecompilationContext.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinDecompilationContext.java new file mode 100644 index 0000000000..ebe8d99e06 --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinDecompilationContext.java @@ -0,0 +1,47 @@ +package org.vineflower.kotlin; + +import kotlinx.metadata.internal.metadata.ProtoBuf; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.util.Key; +import org.vineflower.kotlin.metadata.MetadataNameResolver; + +public class KotlinDecompilationContext { + public enum KotlinType { + CLASS, + FILE, + SYNTHETIC_CLASS, + MULTIFILE_CLASS, + } + + public static final Key CURRENT_TYPE = Key.of("KT_CURRENT_TYPE"); + public static final Key CURRENT_CLASS = Key.of("KT_CURRENT_CLASS"); + public static final Key FILE_PACKAGE = Key.of("KT_FILE_PACKAGE"); + public static final Key SYNTHETIC_CLASS = Key.of("KT_SYNTHETIC_CLASS"); + public static final Key MULTIFILE_PACKAGE = Key.of("KT_MULTIFILE_PACKAGE"); + + public static final Key NAME_RESOLVER = Key.of("KT_NAME_RESOLVER"); + + public static ProtoBuf.Class getCurrentClass() { + return getCurrentType() == KotlinType.CLASS ? DecompilerContext.getContextProperty(CURRENT_CLASS) : null; + } + + public static ProtoBuf.Package getFilePackage() { + return getCurrentType() == KotlinType.FILE ? DecompilerContext.getContextProperty(FILE_PACKAGE) : null; + } + + public static ProtoBuf.Function getSyntheticClass() { + return getCurrentType() == KotlinType.SYNTHETIC_CLASS ? DecompilerContext.getContextProperty(SYNTHETIC_CLASS) : null; + } + + public static ProtoBuf.Package getMultifilePackage() { + return getCurrentType() == KotlinType.MULTIFILE_CLASS ? DecompilerContext.getContextProperty(MULTIFILE_PACKAGE) : null; + } + + public static KotlinType getCurrentType() { + return DecompilerContext.getContextProperty(CURRENT_TYPE); + } + + public static MetadataNameResolver getNameResolver() { + return DecompilerContext.getContextProperty(NAME_RESOLVER); + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinImportCollector.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinImportCollector.java new file mode 100644 index 0000000000..f8a20b6b43 --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinImportCollector.java @@ -0,0 +1,74 @@ +package org.vineflower.kotlin; + +import org.jetbrains.java.decompiler.main.ClassesProcessor; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.collectors.ImportCollector; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor; +import org.jetbrains.java.decompiler.struct.StructContext; +import org.jetbrains.java.decompiler.util.TextBuffer; +import org.vineflower.kotlin.util.KTypes; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class KotlinImportCollector extends ImportCollector { + public static final String[] AUTO_KOTLIN_IMPORTS = { + "annotation", + "collections", + "comparisons", + "io", + "jvm", + "ranges", + "sequences", + "text", + }; + + public KotlinImportCollector(ImportCollector parent) { + super(parent); + + // Any class that Kotlin "overrides" requires explicit non-imported references + for (String className : KTypes.KOTLIN_TO_JAVA_LANG.keySet()) { + String simpleName = className.substring(className.lastIndexOf('/') + 1); + String packageName = className.substring(0, className.lastIndexOf('/')).replace('/', '.'); + if (!mapSimpleNames.containsKey(simpleName)) { + mapSimpleNames.put(simpleName, packageName); + } + } + + for (String className : KTypes.KOTLIN_TO_JAVA_UTIL.keySet()) { + String simpleName = className.substring(className.lastIndexOf('/') + 1); + String packageName = className.substring(0, className.lastIndexOf('/')).replace('/', '.'); + if (!mapSimpleNames.containsKey(simpleName)) { + mapSimpleNames.put(simpleName, packageName); + } + } + } + + @Override + protected boolean keepImport(Map.Entry ent) { + if (!super.keepImport(ent)) return false; + if (ent.getValue().equals("kotlin")) return false; + for (String autoImport : AUTO_KOTLIN_IMPORTS) { + if (ent.getValue().equals("kotlin." + autoImport)) return false; + } + return true; + } + + @Override + public void writeImports(TextBuffer buffer, boolean addSeparator) { + if (DecompilerContext.getOption(IFernflowerPreferences.REMOVE_IMPORTS)) { + return; + } + + List imports = packImports(); + for (String imp : imports) { + buffer.append("import ").append(imp).appendLineSeparator(); + } + if (addSeparator && !imports.isEmpty()) { + buffer.appendLineSeparator(); + } + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinOptions.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinOptions.java new file mode 100644 index 0000000000..e1f9154a1b --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinOptions.java @@ -0,0 +1,24 @@ +package org.vineflower.kotlin; + +import org.jetbrains.java.decompiler.api.plugin.PluginOptions; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences.*; + +import java.util.Map; + +public interface KotlinOptions { + @Name("Show public visibility") + @Description("If a construct is public, show the public keyword") + @Type(Type.BOOLEAN) + String SHOW_PUBLIC_VISIBILITY = "kt-show-public"; + + @Name("Decompile Kotlin") + @Description("Decompile Kotlin classes as Kotlin instead of Java") + @Type(Type.BOOLEAN) + String DECOMPILE_KOTLIN = "kt-decompile-kotlin"; + + static void addDefaults(PluginOptions.AddDefaults cons) { + cons.addDefault(SHOW_PUBLIC_VISIBILITY, "1"); + cons.addDefault(DECOMPILE_KOTLIN, "1"); + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinPlugin.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinPlugin.java new file mode 100644 index 0000000000..7e79998ae4 --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinPlugin.java @@ -0,0 +1,88 @@ +package org.vineflower.kotlin; + +import org.jetbrains.annotations.Nullable; +import org.jetbrains.java.decompiler.api.plugin.Plugin; +import org.jetbrains.java.decompiler.api.plugin.LanguageSpec; +import org.jetbrains.java.decompiler.api.plugin.PluginOptions; +import org.jetbrains.java.decompiler.api.plugin.pass.LoopingPassBuilder; +import org.jetbrains.java.decompiler.api.plugin.pass.MainPassBuilder; +import org.jetbrains.java.decompiler.api.plugin.pass.Pass; +import org.jetbrains.java.decompiler.api.plugin.pass.WrappedPass; +import org.jetbrains.java.decompiler.modules.decompiler.*; +import org.jetbrains.java.decompiler.modules.decompiler.decompose.DomHelper; +import org.jetbrains.java.decompiler.util.Pair; +import org.vineflower.kotlin.pass.*; + +public class KotlinPlugin implements Plugin { + private static final StackVarsProcessor.StackSimplifyOptions INLINE_ALL_VARS = new StackVarsProcessor.StackSimplifyOptions() + .inlineRegularVars(); + + private static final SecondaryFunctionsHelper.IdentifySecondaryOptions FORCE_TERNARY_SIMPLIFY = new SecondaryFunctionsHelper.IdentifySecondaryOptions() + .forceTernarySimplification(); + + @Override + public String id() { + return "Kotlin"; + } + + @Override + public String description() { + return "Detects and decompiles Kotlin class files."; + } + + @Override + public @Nullable PluginOptions getPluginOptions() { + return () -> Pair.of(KotlinOptions.class, KotlinOptions::addDefaults); + } + + @Override + public LanguageSpec getLanguageSpec() { + return new LanguageSpec("kotlin", new KotlinChooser(), new DomHelper(), new KotlinWriter(), makePass()); + } + + private static Pass makePass() { + return new MainPassBuilder() + .addPass("Finally", new JavaFinallyPass()) + .addPass("RemoveSynchronized", ctx -> DomHelper.removeSynchronizedHandler(ctx.getRoot())) + .addPass("CondenseSequences", WrappedPass.of(ctx -> SequenceHelper.condenseSequences(ctx.getRoot()))) + .addPass("ClearStatements", WrappedPass.of(ctx -> ClearStructHelper.clearStatements(ctx.getRoot()))) + .addPass("ProcessExpr", WrappedPass.of( + ctx -> new ExprProcessor(ctx.getMethodDescriptor(), ctx.getVarProc()).processStatement(ctx.getRoot(), ctx.getEnclosingClass())) + ) + .addPass("CondenseSequences_1", WrappedPass.of(ctx -> SequenceHelper.condenseSequences(ctx.getRoot()))) + .addPass("StackVars", new StackVarInitialPass()) + .addPass("InlineIfPPMM", ctx -> PPandMMHelper.inlinePPIandMMIIf(ctx.getRoot())) + .addPass("MainLoop", + new LoopingPassBuilder("Main") + .addFallthroughPass("ResetEdges", WrappedPass.of(ctx -> LabelHelper.cleanUpEdges(ctx.getRoot()))) + .addFallthroughPass("MergeLoop", + new LoopingPassBuilder("Merge") + .addLoopingPass("EliminateLoops", ctx -> EliminateLoopsHelper.eliminateLoops(ctx.getRoot(), ctx.getEnclosingClass())) + .addFallthroughPass("EnhanceLoops", new KMergePass()) + .addLoopingPass("ExtractLoops", ctx -> LoopExtractHelper.extractLoops(ctx.getRoot())) + .addLoopingPass("MergeAllIfs", ctx -> IfHelper.mergeAllIfs(ctx.getRoot())) + .build() + ) + .addFallthroughPass("SimplifyStack", WrappedPass.of(ctx -> StackVarsProcessor.simplifyStackVars(ctx.getRoot(), ctx.getMethod(), ctx.getEnclosingClass()))) + .addFallthroughPass("EliminateDead", new EliminateDeadVarsPass()) + .addFallthroughPass("VarVersions", WrappedPass.of(ctx -> ctx.getVarProc().setVarVersions(ctx.getRoot()))) + .addFallthroughPass("IdentifyLabels", WrappedPass.of(ctx -> LabelHelper.identifyLabels(ctx.getRoot()))) + .addLoopingPass("InlineSingleBlocks", ctx -> InlineSingleBlockHelper.inlineSingleBlocks(ctx.getRoot())) + .addLoopingPass("MakeDoWhile", ctx -> MergeHelper.makeDoWhileLoops(ctx.getRoot())) + .addLoopingPass("CondenseDo", ctx -> MergeHelper.condenseInfiniteLoopsWithReturn(ctx.getRoot())) + .addLoopingPass("CondenseExits", ctx -> ExitHelper.condenseExits(ctx.getRoot())) + .build() + ) + .addPass("SimplifyStack", WrappedPass.of(ctx -> StackVarsProcessor.simplifyStackVars(ctx.getRoot(), ctx.getMethod(), ctx.getEnclosingClass(), INLINE_ALL_VARS))) + .addPass("AdjustReturnType", ctx -> ExitHelper.adjustReturnType(ctx.getRoot(), ctx.getMethodDescriptor())) + .addPass("RedundantReturns", ctx -> ExitHelper.removeRedundantReturns(ctx.getRoot())) + .addPass("IdentifySecondary", ctx -> SecondaryFunctionsHelper.identifySecondaryFunctions(ctx.getRoot(), ctx.getVarProc(), FORCE_TERNARY_SIMPLIFY)) + .addPass("SetVarDefinitions", WrappedPass.of(ctx -> ctx.getVarProc().setVarDefinitions(ctx.getRoot()))) + .addPass("ReplaceExprs", new ReplaceExprentsPass()) + // TODO: preference for this pass + .addPass("ResugarMethods", new ResugarKotlinMethodsPass()) + .addPass("ReplaceContinue", ctx -> LabelHelper.replaceContinueWithBreak(ctx.getRoot())) + + .build(); + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinWriter.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinWriter.java new file mode 100644 index 0000000000..e5558e0f0e --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinWriter.java @@ -0,0 +1,1775 @@ +// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.vineflower.kotlin; + +import kotlinx.metadata.internal.metadata.ProtoBuf; +import net.fabricmc.fernflower.api.IFabricJavadocProvider; +import org.jetbrains.java.decompiler.api.plugin.StatementWriter; +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.code.Instruction; +import org.jetbrains.java.decompiler.code.InstructionSequence; +import org.jetbrains.java.decompiler.main.*; +import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; +import org.jetbrains.java.decompiler.main.collectors.ImportCollector; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.main.rels.ClassWrapper; +import org.jetbrains.java.decompiler.main.rels.MethodWrapper; +import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor; +import org.jetbrains.java.decompiler.modules.decompiler.exps.*; +import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPair; +import org.jetbrains.java.decompiler.modules.renamer.PoolInterceptor; +import org.jetbrains.java.decompiler.struct.*; +import org.jetbrains.java.decompiler.struct.attr.*; +import org.jetbrains.java.decompiler.struct.consts.ConstantPool; +import org.jetbrains.java.decompiler.struct.consts.LinkConstant; +import org.jetbrains.java.decompiler.struct.consts.PooledConstant; +import org.jetbrains.java.decompiler.struct.consts.PrimitiveConstant; +import org.jetbrains.java.decompiler.struct.gen.FieldDescriptor; +import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.struct.gen.generics.GenericClassDescriptor; +import org.jetbrains.java.decompiler.struct.gen.generics.GenericFieldDescriptor; +import org.jetbrains.java.decompiler.struct.gen.generics.GenericMethodDescriptor; +import org.jetbrains.java.decompiler.util.*; +import org.jetbrains.java.decompiler.util.collections.VBStyleCollection; +import org.vineflower.kotlin.expr.KAnnotationExprent; +import org.vineflower.kotlin.metadata.MetadataNameResolver; +import org.vineflower.kotlin.struct.*; +import org.vineflower.kotlin.util.KTypes; +import org.vineflower.kotlin.util.KUtils; +import org.vineflower.kotlin.util.ProtobufFlags; + +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; + +public class KotlinWriter implements StatementWriter { + private static final Set ERROR_DUMP_STOP_POINTS = new HashSet<>(Arrays.asList( + "Fernflower.decompileContext", + "MethodProcessor.codeToJava", + "KotlinWriter.writeMethod" + )); + private static final String NOT_NULL_ANN_NAME = "org/jetbrains/annotations/NotNull"; + private static final String NULLABLE_ANN_NAME = "org/jetbrains/annotations/Nullable"; + private static final String REPEATABLE_ANN_NAME = "java/lang/annotation/Repeatable"; + private static final Set KT_HARD_KEYWORDS = new HashSet<>(Arrays.asList( + "as", + "break", + "class", + "continue", + "do", + "else", + "false", + "for", + "fun", + "if", + "in", + "interface", + "is", + "null", + "object", + "package", + "return", + "super", + "this", + "throw", + "true", + "try", + "typealias", + "typeof", // Reserved for future use + "val", + "var", + "when", + "while" + )); + + private final PoolInterceptor interceptor; + private final IFabricJavadocProvider javadocProvider; + + public KotlinWriter() { + interceptor = DecompilerContext.getPoolInterceptor(); + javadocProvider = (IFabricJavadocProvider) DecompilerContext.getProperty(IFabricJavadocProvider.PROPERTY_NAME); + } + + private boolean invokeProcessors(TextBuffer buffer, ClassNode node) { + ClassWrapper wrapper = node.getWrapper(); + if (wrapper == null) { + buffer.append("/* $VF: Couldn't be decompiled. Class " + node.classStruct.qualifiedName + " wasn't processed yet! */"); + List lines = new ArrayList<>(); + lines.addAll(KotlinWriter.getErrorComment()); + for (String line : lines) { + buffer.append("//"); + if (!line.isEmpty()) buffer.append(' ').append(line); + buffer.appendLineSeparator(); + } + return false; // Doesn't make sense! how is this null? referencing an anonymous class in another object? + } + StructClass cl = wrapper.getClassStruct(); + + try { + InitializerProcessor.extractInitializers(wrapper); + InitializerProcessor.hideInitalizers(wrapper); + + if (node.type == ClassNode.Type.ROOT && + cl.getVersion().has14ClassReferences() && + DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_CLASS_1_4)) { + ClassReference14Processor.processClassReferences(node); + } + + if (cl.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM)) { + EnumProcessor.clearEnum(wrapper); + } + + if (DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ASSERTIONS)) { + AssertProcessor.buildAssertions(node); + } + } catch (Throwable t) { + DecompilerContext.getLogger().writeMessage("Class " + node.simpleName + " couldn't be written.", + IFernflowerLogger.Severity.WARN, + t); + buffer.append("// $VF: Couldn't be decompiled"); + buffer.appendLineSeparator(); + if (DecompilerContext.getOption(IFernflowerPreferences.DUMP_EXCEPTION_ON_ERROR)) { + List lines = new ArrayList<>(); + lines.addAll(KotlinWriter.getErrorComment()); + collectErrorLines(t, lines); + for (String line : lines) { + buffer.append("//"); + if (!line.isEmpty()) buffer.append(' ').append(line); + buffer.appendLineSeparator(); + } + } + + return false; + } + + return true; + } + + public void writeClassHeader(StructClass cl, TextBuffer buffer, ImportCollector importCollector) { + if (KotlinDecompilationContext.getCurrentType() == KotlinDecompilationContext.KotlinType.FILE) { + for (Key key : ANNOTATION_ATTRIBUTES) { + StructAnnotationAttribute attr = cl.getAttribute((Key) key); + if (attr != null) { + for (AnnotationExprent expr : attr.getAnnotations()) { + if (expr.getClassName().equals("kotlin/Metadata")) { + continue; + } + + KAnnotationExprent kAnnotationExprent = new KAnnotationExprent(expr, KAnnotationExprent.UseSiteTarget.FILE); + buffer.append(kAnnotationExprent.toJava(0)).appendLineSeparator().appendLineSeparator(); + } + } + } + } + + int index = cl.qualifiedName.lastIndexOf('/'); + if (index >= 0) { + buffer.append("package "); + + String[] packageParts = cl.qualifiedName.substring(0, index).split("/"); + for (int i = 0; i < packageParts.length; i++) { + if (i > 0) buffer.append('.'); + buffer.append(toValidKotlinIdentifier(packageParts[i])); + } + + buffer.appendLineSeparator().appendLineSeparator(); + } + + KotlinImportCollector kotlinImportCollector = new KotlinImportCollector(importCollector); + kotlinImportCollector.writeImports(buffer, true); + + MetadataNameResolver nameResolver = KotlinDecompilationContext.getNameResolver(); + if (KotlinDecompilationContext.getCurrentType() == KotlinDecompilationContext.KotlinType.CLASS) { + if (KotlinDecompilationContext.getCurrentClass().getTypeAliasCount() > 0) { + List typeAliases = KotlinDecompilationContext.getCurrentClass().getTypeAliasList(); + for (ProtoBuf.TypeAlias typeAlias : typeAliases) { + buffer.append("typealias "); + buffer.append(toValidKotlinIdentifier(nameResolver.resolve(typeAlias.getName()))); + buffer.append(" = "); + buffer.append(toValidKotlinIdentifier(nameResolver.resolve(typeAlias.getUnderlyingType().getClassName()))); + buffer.appendLineSeparator(); + } + + buffer.appendLineSeparator(); + } + } + } + + public void writeClass(ClassNode node, TextBuffer buffer, int indent) { + ClassNode outerNode = DecompilerContext.getContextProperty(DecompilerContext.CURRENT_CLASS_NODE); + DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_NODE, node); + DecompilerContext.setImportCollector(new KotlinImportCollector(DecompilerContext.getImportCollector())); + + try { + // last minute processing + boolean ok = invokeProcessors(buffer, node); + + if (!ok) { + return; + } + + ClassWrapper wrapper = node.getWrapper(); + StructClass cl = wrapper.getClassStruct(); + ConstantPool pool = cl.getPool(); + + KotlinChooser.setContextVariables(cl); + + DecompilerContext.getLogger().startWriteClass(cl.qualifiedName); + + KProperty.Data propertyData = KProperty.parse(node); + Map functions = KFunction.parse(node); + KConstructor.Data constructorData = KConstructor.parse(node); + + if (DecompilerContext.getOption(IFernflowerPreferences.SOURCE_FILE_COMMENTS)) { + StructSourceFileAttribute sourceFileAttr = node.classStruct + .getAttribute(StructGeneralAttribute.ATTRIBUTE_SOURCE_FILE); + + if (sourceFileAttr != null) { + String sourceFile = sourceFileAttr.getSourceFile(pool); + + buffer + .appendIndent(indent) + .append("// $VF: Compiled from " + sourceFile) + .appendLineSeparator(); + } + } + + if (cl.hasModifier(CodeConstants.ACC_ANNOTATION)) { + // Kotlin's annotation classes are treated quite differently from other classes + writeAnnotationDefinition(node, buffer, indent, propertyData, functions, constructorData); + return; + } + + if (KotlinDecompilationContext.getCurrentType() == KotlinDecompilationContext.KotlinType.FILE) { + writeKotlinFile(node, buffer, indent, propertyData, functions); // no constructors in top level file + return; + } + + // write class definition + writeClassDefinition(node, buffer, indent, constructorData); + + boolean hasContent = false; + boolean enumFields = false; + + List components = cl.getRecordComponents(); + + // FIXME: fields don't have line mappings + // fields + + // Find the last field marked as an enum + int maxEnumIdx = 0; + for (int i = 0; i < cl.getFields().size(); i++) { + StructField fd = cl.getFields().get(i); + boolean isEnum = fd.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM); + if (isEnum) { + maxEnumIdx = i; + } + } + + List deferredEnumFields = new ArrayList<>(); + + // Find any regular fields mixed in with the enum fields + // This is invalid but allowed in bytecode. + for (int i = 0; i < cl.getFields().size(); i++) { + StructField fd = cl.getFields().get(i); + boolean isEnum = fd.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM); + if (i < maxEnumIdx && !isEnum) { + deferredEnumFields.add(fd); + } + } + + for (StructField fd : cl.getFields()) { + boolean hide = fd.isSynthetic() && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) || + wrapper.getHiddenMembers().contains(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor())) || deferredEnumFields.contains(fd); + if (hide) continue; + + if (components != null && fd.getAccessFlags() == (CodeConstants.ACC_FINAL | CodeConstants.ACC_PRIVATE) && + components.stream().anyMatch(c -> c.getName().equals(fd.getName()) && c.getDescriptor().equals(fd.getDescriptor()))) { + // Record component field: skip it + continue; + } + + boolean isEnum = fd.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM); + if (isEnum) { + if (enumFields) { + buffer.append(',').appendLineSeparator(); + } + enumFields = true; + } else if (enumFields) { + buffer.append(';'); + buffer.appendLineSeparator(); + buffer.appendLineSeparator(); + enumFields = false; + + // If the fields after are non enum, readd the fields found scattered throughout the enum + for (StructField fd2 : deferredEnumFields) { + TextBuffer fieldBuffer = new TextBuffer(); + writeField(wrapper, cl, fd2, fieldBuffer, indent + 1); + fieldBuffer.clearUnassignedBytecodeMappingData(); + buffer.append(fieldBuffer); + } + } + + if (propertyData == null || !propertyData.associatedFields.contains(fd.getName())) { + TextBuffer fieldBuffer = new TextBuffer(); + writeField(wrapper, cl, fd, fieldBuffer, indent + 1); + fieldBuffer.clearUnassignedBytecodeMappingData(); + buffer.append(fieldBuffer); + + hasContent = true; + } + } + + if (enumFields) { + buffer.append(';').appendLineSeparator(); + + // If we end with enum fields, readd the fields found mixed in + for (StructField fd2 : deferredEnumFields) { + TextBuffer fieldBuffer = new TextBuffer(); + writeField(wrapper, cl, fd2, fieldBuffer, indent + 1); + fieldBuffer.clearUnassignedBytecodeMappingData(); + buffer.append(fieldBuffer); + } + } + + if (propertyData != null && !propertyData.properties.isEmpty()) { + if (hasContent) { + buffer.appendLineSeparator(); + } + + for (KProperty prop : propertyData.properties) { + buffer.append(prop.stringify(indent + 1)); + } + + hasContent = true; + } + + // methods + VBStyleCollection methods = cl.getMethods(); + for (int i = 0; i < methods.size(); i++) { + StructMethod mt = methods.get(i); + boolean hide = mt.isSynthetic() && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) || + mt.hasModifier(CodeConstants.ACC_BRIDGE) && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_BRIDGE) || + wrapper.getHiddenMembers().contains(InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor())) || + mt.getName().equals("") && mt.getDescriptor().equals("(Lkotlin/jvm/internal/DefaultConstructorMarker;)V") || + propertyData != null && propertyData.associatedMethods.contains(InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor())); + if (hide) continue; + + KFunction function = functions.get(mt); + if (function != null) { + if (hasContent) { + buffer.appendLineSeparator(); + } + hasContent = true; + buffer.append(function.stringify(indent + 1)); + continue; + } + + if (constructorData != null) { + KConstructor constructor = constructorData.constructors.get(mt); + if (constructor != null) { + if (hasContent) { + buffer.appendLineSeparator(); + } + hasContent |= constructor.stringify(buffer, indent + 1); + continue; + } + } + + TextBuffer methodBuffer = new TextBuffer(); + boolean methodSkipped = !writeMethod(node, mt, i, methodBuffer, indent + 1); + if (!methodSkipped) { + if (hasContent) { + buffer.appendLineSeparator(); + } + hasContent = true; + buffer.append(methodBuffer); + } + } + + // member classes + for (ClassNode inner : node.nested) { + if (inner.type == ClassNode.Type.MEMBER) { + StructClass innerCl = inner.classStruct; + boolean isSynthetic = (inner.access & CodeConstants.ACC_SYNTHETIC) != 0 || innerCl.isSynthetic(); + boolean hide = isSynthetic && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) || + wrapper.getHiddenMembers().contains(innerCl.qualifiedName); + if (hide) continue; + + if (hasContent) { + buffer.appendLineSeparator(); + } + writeClass(inner, buffer, indent + 1); + + hasContent = true; + } + } + + buffer.appendIndent(indent).append('}'); + + if (node.type != ClassNode.Type.ANONYMOUS) { + buffer.appendLineSeparator(); + } + } finally { + DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_NODE, outerNode); + DecompilerContext.getLogger().endWriteClass(); + } + } + + private void writeKotlinFile(ClassNode node, TextBuffer buffer, int indent, KProperty.Data propertyData, Map functions) { + ClassWrapper wrapper = node.getWrapper(); + StructClass cl = wrapper.getClassStruct(); + + for (KProperty property : propertyData.properties) { + buffer.append(property.stringify(indent)); + } + + for (StructField fd : cl.getFields()) { + if (propertyData.associatedFields.contains(fd.getName())) continue; + + TextBuffer fieldBuffer = new TextBuffer(); + writeField(wrapper, cl, fd, fieldBuffer, indent); + fieldBuffer.clearUnassignedBytecodeMappingData(); + buffer.append(fieldBuffer); + } + + if (!cl.getFields().isEmpty()) { + buffer.appendLineSeparator(); + } + + for (int i = 0; i < cl.getMethods().size(); i++) { + StructMethod mt = cl.getMethods().get(i); + if (functions.containsKey(mt)) { + buffer.append(functions.get(mt).stringify(indent)); + buffer.appendLineSeparator(); + continue; + } + + String key = InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor()); + if (mt.getName().equals("") || propertyData.associatedMethods.contains(key)) continue; + + TextBuffer methodBuffer = new TextBuffer(); + writeMethod(node, mt, i, methodBuffer, indent); + methodBuffer.clearUnassignedBytecodeMappingData(); + buffer.append(methodBuffer, node.simpleName, mt.getName() + " " + mt.getDescriptor()); + buffer.appendLineSeparator(); + } + + for (ClassNode inner : node.nested) { + if (inner.type == ClassNode.Type.MEMBER) { + writeClass(inner, buffer, indent); + } + } + } + + private void writeAnnotationDefinition(ClassNode node, TextBuffer buffer, int indent, KProperty.Data propertyData, Map functions, KConstructor.Data constructorData) { + ClassWrapper wrapper = node.getWrapper(); + StructClass cl = wrapper.getClassStruct(); + + StructAnnotationAttribute runtimeAnnotations = cl.getAttribute(StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS); + AnnotationExprent repeatableAnnotation = runtimeAnnotations.getAnnotations().stream() + .filter(a -> REPEATABLE_ANN_NAME.equals(a.getClassName())) + .findFirst() + .orElse(null); + + String repeatableContainer = null; + + if (repeatableAnnotation != null) { + repeatableContainer = ((ConstExprent) repeatableAnnotation.getParValues().get(0)).getValue().toString(); + runtimeAnnotations.getAnnotations().remove(repeatableAnnotation); + } + + appendAnnotations(buffer, indent, cl, -1); + appendJvmAnnotations(buffer, indent, cl, true, cl.getPool(), TypeAnnotation.CLASS_TYPE_PARAMETER); + + buffer.appendIndent(indent); + appendModifiers(buffer, cl.getAccessFlags(), CLASS_ALLOWED, true, CLASS_EXCLUDED); + + VarType classType = new VarType(cl.qualifiedName, true); + + buffer.append("annotation class ") + .append(KTypes.getKotlinType(classType, false)) + .append("(") + .appendLineSeparator(); + + List nonParameterProperties = new ArrayList<>(propertyData.properties); + + boolean first = true; + for (KParameter param : constructorData.primary.parameters) { + if (!first) { + buffer.append(",").appendLineSeparator(); + } + first = false; + buffer.appendIndent(indent + 1) + .append("val ") + .append(KotlinWriter.toValidKotlinIdentifier(param.name)) + .append(": ") + .append(param.type.stringify(indent + 1)); + + // Because Kotlin really doesn't like making this easy for us, defaults are still passed directly via attributes + KProperty prop = propertyData.properties.stream() + .filter(p -> p.name.equals(param.name)) + .findFirst() + .orElseThrow(); + + nonParameterProperties.remove(prop); + + KPropertyAccessor getter = prop.getter; + if (getter != null) { + StructMethod mt = getter.underlyingMethod.methodStruct; + StructAnnDefaultAttribute paramAttr = mt.getAttribute(StructGeneralAttribute.ATTRIBUTE_ANNOTATION_DEFAULT); + if (paramAttr != null) { + Exprent kExpr = KUtils.replaceExprent(paramAttr.getDefaultValue()); + Exprent expr = kExpr != null ? kExpr : paramAttr.getDefaultValue(); + buffer.append(" = ").append(expr.toJava()); + } + } + } + + buffer.appendLineSeparator() + .appendIndent(indent) + .append(")"); + + boolean appended = false; + + TextBuffer innerBuffer = new TextBuffer(); + + for (KProperty prop : nonParameterProperties) { + innerBuffer.append(prop.stringify(indent + 1)); + appended = true; + } + + first = true; + for (KFunction function : functions.values()) { + if (!first || appended) { + innerBuffer.appendLineSeparator(); + } + first = false; + + innerBuffer.append(function.stringify(indent + 1)); + appended = true; + } + + first = true; + for (ClassNode inner : node.nested) { + if (inner.type == ClassNode.Type.MEMBER) { + if (inner.classStruct.qualifiedName.equals(repeatableContainer)) { + // Skip the container class + continue; + } + + if (!first || appended) { + innerBuffer.appendLineSeparator(); + } + first = false; + + writeClass(inner, innerBuffer, indent + 1); + appended = true; + } + } + + if (appended) { + buffer.append(" {") + .appendLineSeparator() + .append(innerBuffer) + .appendIndent(indent) + .append("}"); + } + + buffer.appendLineSeparator(); + } + + private static boolean isGenerated(int flags) { + return (flags & (CodeConstants.ACC_SYNTHETIC | CodeConstants.ACC_MANDATED)) != 0; + } + + private void writeClassDefinition(ClassNode node, TextBuffer buffer, int indent, KConstructor.Data constructorData) { + if (node.type == ClassNode.Type.ANONYMOUS) { + buffer.append(" {").appendLineSeparator(); + return; + } + + ClassWrapper wrapper = node.getWrapper(); + StructClass cl = wrapper.getClassStruct(); + + int flags = node.type == ClassNode.Type.ROOT ? cl.getAccessFlags() : node.access; + boolean isDeprecated = cl.hasAttribute(StructGeneralAttribute.ATTRIBUTE_DEPRECATED); + boolean isSynthetic = (flags & CodeConstants.ACC_SYNTHETIC) != 0 || cl.hasAttribute(StructGeneralAttribute.ATTRIBUTE_SYNTHETIC); + boolean isEnum = DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM) && (flags & CodeConstants.ACC_ENUM) != 0; + boolean isInterface = (flags & CodeConstants.ACC_INTERFACE) != 0; + boolean isAnnotation = (flags & CodeConstants.ACC_ANNOTATION) != 0; + boolean isModuleInfo = (flags & CodeConstants.ACC_MODULE) != 0 && cl.hasAttribute(StructGeneralAttribute.ATTRIBUTE_MODULE); + StructPermittedSubclassesAttribute permittedSubClassesAttr = cl.getAttribute(StructGeneralAttribute.ATTRIBUTE_PERMITTED_SUBCLASSES); + List permittedSubClasses = permittedSubClassesAttr != null ? permittedSubClassesAttr.getClasses() : Collections.emptyList(); + boolean isSealed = permittedSubClassesAttr != null && !permittedSubClasses.isEmpty(); + boolean isFinal = (flags & CodeConstants.ACC_FINAL) != 0; + boolean isNonSealed = !isSealed && !isFinal && cl.getVersion().hasSealedClasses() && isSuperClassSealed(cl); + + if (isDeprecated) { + if (!containsDeprecatedAnnotation(cl)) { + appendDeprecation(buffer, indent); + } + } + + if (interceptor != null) { + String oldName = interceptor.getOldName(cl.qualifiedName); + appendRenameComment(buffer, oldName, MType.CLASS, indent); + } + + if (javadocProvider != null) { + appendJavadoc(buffer, javadocProvider.getClassDoc(cl), indent); + } + + appendAnnotations(buffer, indent, cl, -1); + appendJvmAnnotations(buffer, indent, cl, isInterface, cl.getPool(), TypeAnnotation.CLASS_TYPE_PARAMETER); + + + ProtoBuf.Class proto = KotlinDecompilationContext.getCurrentClass(); + ProtobufFlags.Class kotlinFlags; + if (proto != null) { + kotlinFlags = new ProtobufFlags.Class(proto.getFlags()); + } else { + appendComment(buffer, "Class flags could not be determined", indent); + kotlinFlags = new ProtobufFlags.Class(0); + } + + buffer.appendIndent(indent); + + if (kotlinFlags.visibility != ProtoBuf.Visibility.PUBLIC) { + buffer.append(ProtobufFlags.toString(kotlinFlags.visibility)).append(' '); + } + + if (kotlinFlags.isExpect) { + buffer.append("expect "); + } + + if ((!isInterface && kotlinFlags.modality != ProtoBuf.Modality.FINAL) || kotlinFlags.modality == ProtoBuf.Modality.SEALED) { + buffer.append(ProtobufFlags.toString(kotlinFlags.modality)).append(' '); + } + + if (kotlinFlags.isExternal) { + buffer.append("external "); + } + if (kotlinFlags.isInner) { + buffer.append("inner "); + } + if (kotlinFlags.isFun) { + buffer.append("fun "); + } + if (kotlinFlags.isInline) { + buffer.append("inline "); + } + if (kotlinFlags.isData) { + buffer.append("data "); + } + + if (isEnum) { + buffer.append("enum class "); + } else if (isInterface) { + buffer.append("interface "); + } else if (isAnnotation) { + buffer.append("annotation class"); + } else if (kotlinFlags.kind == ProtoBuf.Class.Kind.OBJECT) { + buffer.append("object "); + } else if (kotlinFlags.kind == ProtoBuf.Class.Kind.COMPANION_OBJECT) { + buffer.append("companion object "); + } else { + buffer.append("class "); + } + + buffer.append(toValidKotlinIdentifier(node.simpleName)); + + GenericClassDescriptor descriptor = cl.getSignature(); + if (descriptor != null && !descriptor.fparameters.isEmpty()) { + appendTypeParameters(buffer, descriptor.fparameters, descriptor.fbounds); + } + + buffer.pushNewlineGroup(indent, 1); + + boolean appendedColon = false; + if (!isEnum && !isInterface && cl.superClass != null) { + if (constructorData != null && constructorData.primary != null && constructorData.primary.writePrimaryConstructor(buffer, indent)) { + appendedColon = true; + } else { + VarType supertype = new VarType(cl.superClass.getString(), true); + if (!VarType.VARTYPE_OBJECT.equals(supertype)) { + buffer.appendPossibleNewline(" "); + buffer.append(": "); + buffer.append(ExprProcessor.getCastTypeName(descriptor == null ? supertype : descriptor.superclass)); + appendedColon = true; + } + } + } + + if (!isAnnotation) { + int[] interfaces = cl.getInterfaces(); + if (interfaces.length > 0) { + for (int i = 0; i < interfaces.length; i++) { + if (!appendedColon) { + buffer.append(" :"); + appendedColon = true; + } else { + buffer.append(","); + } + buffer.appendPossibleNewline(" "); + + if (descriptor == null || descriptor.superinterfaces.size() > i) { + buffer.append(ExprProcessor.getCastTypeName(descriptor == null ? new VarType(cl.getInterface(i), true) : descriptor.superinterfaces.get(i))); + } + } + } + } + + buffer.popNewlineGroup(); + + buffer.append(" {").appendLineSeparator(); + } + + private static boolean isSuperClassSealed(StructClass cl) { + if (cl.superClass != null) { + StructClass superClass = DecompilerContext.getStructContext().getClass((String) cl.superClass.value); + if (superClass != null && superClass.hasAttribute(StructGeneralAttribute.ATTRIBUTE_PERMITTED_SUBCLASSES)) { + return true; + } + } + for (String iface : cl.getInterfaceNames()) { + StructClass ifaceClass = DecompilerContext.getStructContext().getClass(iface); + if (ifaceClass != null && ifaceClass.hasAttribute(StructGeneralAttribute.ATTRIBUTE_PERMITTED_SUBCLASSES)) { + return true; + } + } + return false; + } + + public void writeField(ClassWrapper wrapper, StructClass cl, StructField fd, TextBuffer buffer, int indent) { + boolean isInterface = cl.hasModifier(CodeConstants.ACC_INTERFACE); + boolean isDeprecated = fd.hasAttribute(StructGeneralAttribute.ATTRIBUTE_DEPRECATED); + boolean isEnum = fd.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM); + + if (isDeprecated) { + if (!containsDeprecatedAnnotation(fd)) { + appendDeprecation(buffer, indent); + } + } + + String name = fd.getName(); + if (interceptor != null) { + String newName = interceptor.getName(cl.qualifiedName + " " + fd.getName() + " " + fd.getDescriptor()); + + if (newName != null) { + name = newName.split(" ")[1]; + } + } + + if (interceptor != null) { + String oldName = interceptor.getOldName(cl.qualifiedName + " " + name + " " + fd.getDescriptor()); + appendRenameComment(buffer, oldName, MType.FIELD, indent); + } + + if (javadocProvider != null) { + appendJavadoc(buffer, javadocProvider.getFieldDoc(cl, fd), indent); + } + appendAnnotations(buffer, indent, fd, TypeAnnotation.FIELD); + appendJvmAnnotations(buffer, indent, fd, isInterface, cl.getPool(), TypeAnnotation.FIELD); + + buffer.appendIndent(indent); + + if (!fd.hasModifier(CodeConstants.ACC_FINAL) && !fd.hasModifier(CodeConstants.ACC_STATIC) && !fd.hasModifier(CodeConstants.ACC_PRIVATE)) { + buffer.append("open "); + } + + if (!isEnum) { + appendModifiers(buffer, fd.getAccessFlags(), FIELD_ALLOWED, isInterface, FIELD_EXCLUDED); + } + + Map.Entry fieldTypeData = getFieldTypeData(fd); + VarType fieldType = fieldTypeData.getKey(); + GenericFieldDescriptor descriptor = fieldTypeData.getValue(); + + if (!isEnum) { + buffer.append(ExprProcessor.getCastTypeName(descriptor == null ? fieldType : descriptor.type)); + buffer.append(' '); + } + + buffer.append(name); + + Exprent initializer; + if (fd.hasModifier(CodeConstants.ACC_STATIC)) { + initializer = wrapper.getStaticFieldInitializers().getWithKey(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor())); + } else { + initializer = wrapper.getDynamicFieldInitializers().getWithKey(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor())); + } + + if (initializer != null) { + if (isEnum && initializer instanceof NewExprent) { + NewExprent expr = (NewExprent) initializer; + expr.setEnumConst(true); + buffer.append(expr.toJava(indent)); + } else { + buffer.append(" = "); + + if (initializer instanceof ConstExprent) { + ((ConstExprent) initializer).adjustConstType(fieldType); + } + + // FIXME: special case field initializer. Can map to more than one method (constructor) and bytecode instruction. + ExprProcessor.getCastedExprent(initializer, descriptor == null ? fieldType : descriptor.type, buffer, indent, false); + } + } else if (fd.hasModifier(CodeConstants.ACC_FINAL) && fd.hasModifier(CodeConstants.ACC_STATIC)) { + StructConstantValueAttribute attr = fd.getAttribute(StructGeneralAttribute.ATTRIBUTE_CONSTANT_VALUE); + if (attr != null) { + PrimitiveConstant constant = cl.getPool().getPrimitiveConstant(attr.getIndex()); + buffer.append(" = "); + buffer.append(new ConstExprent(fieldType, constant.value, null).toJava(indent)); + } + } + + if (!isEnum) { + buffer.append(";").appendLineSeparator(); + } + } + + public static String toValidKotlinIdentifier(String name) { + if (name == null || name.isEmpty()) return name; + + if (KT_HARD_KEYWORDS.contains(name)) { + return "`" + name + "`"; + } + + boolean requiresBackticks = !Character.isJavaIdentifierStart(name.charAt(0)) || name.charAt(0) == '$'; + for (int i = 1; i < name.length(); i++) { + if (!Character.isJavaIdentifierPart(name.charAt(i)) || name.charAt(i) == '$') { + requiresBackticks = true; + break; + } + } + boolean needsComment = false; + if (name.contains("`")) { + name = name.replace("`", "_"); + needsComment = true; + } + + if (requiresBackticks) { + name = "`" + name + "`"; + } + return name + (needsComment ? " /* $VF was: " + name + " */" : ""); + } + + public boolean writeMethod(ClassNode node, StructMethod mt, int methodIndex, TextBuffer buffer, int indent) { + ClassWrapper wrapper = node.getWrapper(); + StructClass cl = wrapper.getClassStruct(); + // Get method by index, this keeps duplicate methods (with the same key) separate + MethodWrapper methodWrapper = wrapper.getMethodWrapper(methodIndex); + + boolean hideMethod = false; + + MethodWrapper outerWrapper = (MethodWrapper) DecompilerContext.getContextProperty(DecompilerContext.CURRENT_METHOD_WRAPPER); + DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, methodWrapper); + + try { + boolean isInterface = cl.hasModifier(CodeConstants.ACC_INTERFACE); + boolean isAnnotation = cl.hasModifier(CodeConstants.ACC_ANNOTATION); + boolean isEnum = cl.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM); + boolean isDeprecated = mt.hasAttribute(StructGeneralAttribute.ATTRIBUTE_DEPRECATED); + boolean clInit = false, init = false, dInit = false; + + MethodDescriptor md = MethodDescriptor.parseDescriptor(mt, node); + + int flags = mt.getAccessFlags(); + if ((flags & CodeConstants.ACC_NATIVE) != 0) { + flags &= ~CodeConstants.ACC_STRICT; // compiler bug: a strictfp class sets all methods to strictfp + } + if (CodeConstants.CLINIT_NAME.equals(mt.getName())) { + flags &= CodeConstants.ACC_STATIC; // ignore all modifiers except 'static' in a static initializer + } + + if (isDeprecated) { + if (!containsDeprecatedAnnotation(mt)) { + appendDeprecation(buffer, indent); + } + } + + String name = mt.getName(); + if (interceptor != null) { + String newName = interceptor.getName(cl.qualifiedName + " " + mt.getName() + " " + mt.getDescriptor()); + + if (newName != null) { + name = newName.split(" ")[1]; + } + } + + if (interceptor != null) { + String oldName = interceptor.getOldName(cl.qualifiedName + " " + name + " " + mt.getDescriptor()); + appendRenameComment(buffer, oldName, MType.METHOD, indent); + } + + boolean isBridge = (flags & CodeConstants.ACC_BRIDGE) != 0; + if (isBridge) { + appendComment(buffer, "bridge method", indent); + } + + if (DecompilerContext.getOption(IFernflowerPreferences.DECOMPILER_COMMENTS) && methodWrapper.addErrorComment || methodWrapper.commentLines != null) { + if (methodWrapper.addErrorComment) { + for (String s : KotlinWriter.getErrorComment()) { + methodWrapper.addComment(s); + } + } + + for (String s : methodWrapper.commentLines) { + buffer.appendIndent(indent).append("// " + s).appendLineSeparator(); + } + } + + if (javadocProvider != null) { + appendJavadoc(buffer, javadocProvider.getMethodDoc(cl, mt), indent); + } + + appendAnnotations(buffer, indent, mt, TypeAnnotation.METHOD_RETURN_TYPE); + + appendJvmAnnotations(buffer, indent, mt, isInterface, cl.getPool(), TypeAnnotation.METHOD_RETURN_TYPE); + + buffer.appendIndent(indent); + + if (CodeConstants.INIT_NAME.equals(name)) { + if (node.type == ClassNode.Type.ANONYMOUS) { + name = ""; + dInit = true; + } else { + name = node.simpleName; + init = true; + } + } else if (CodeConstants.CLINIT_NAME.equals(name)) { + name = ""; + clInit = true; + } + + boolean didOverride = false; + if (!CodeConstants.INIT_NAME.equals(mt.getName()) && !CodeConstants.CLINIT_NAME.equals(mt.getName()) && !mt.hasModifier(CodeConstants.ACC_STATIC) && !mt.hasModifier(CodeConstants.ACC_PRIVATE)) { + // Search superclasses for methods that match the name and descriptor of this one. + // Make sure not to search the current class otherwise it will return the current method itself! + // TODO: record overrides + boolean isOverride = searchForMethod(cl, mt.getName(), md, false); + if (isOverride) { + buffer.append("override "); + didOverride = true; + if (mt.hasModifier(CodeConstants.ACC_ABSTRACT)) { + buffer.append("abstract "); + } + if (mt.hasModifier(CodeConstants.ACC_FINAL)) { + buffer.append("final "); + } + } + } + + if (!didOverride && !mt.hasModifier(CodeConstants.ACC_FINAL) && !mt.hasModifier(CodeConstants.ACC_PRIVATE) && !mt.hasModifier(CodeConstants.ACC_STATIC) && !isInterface && !isAnnotation && !isEnum && !cl.hasModifier(CodeConstants.ACC_FINAL)) { + buffer.append(mt.hasModifier(CodeConstants.ACC_ABSTRACT) ? "abstract " : "open "); + } + + if (!dInit) { + buffer.append("fun "); + } + + GenericMethodDescriptor descriptor = mt.getSignature(); + boolean throwsExceptions = false; + int paramCount = 0; + + if (!clInit && !dInit) { + boolean thisVar = !mt.hasModifier(CodeConstants.ACC_STATIC); + + int index = isEnum && init ? 3 : thisVar ? 1 : 0; + int start = isEnum && init ? 2 : 0; + + if (descriptor != null && !descriptor.typeParameters.isEmpty()) { + appendTypeParameters(buffer, descriptor.typeParameters, descriptor.typeParameterBounds); + buffer.append(' '); + } + + // TODO: More robust checks for extension functions + String varprocName = methodWrapper.varproc.getVarName(new VarVersionPair(index, 0)); + boolean extension = varprocName != null && varprocName.startsWith("$this$"); + + if (extension) { + VarType paramType = descriptor != null && descriptor.parameterTypes.size() > 0 ? descriptor.parameterTypes.get(0) : md.params[0]; + String typeName = KTypes.getKotlinType(paramType); + boolean isNullable = processParameterAnnotations(buffer, mt, 0); + + if (KTypes.isFunctionType(paramType)) { + // Extension functions always need parentheses for function types as the receiver + typeName = "(" + typeName + ")"; + } + + buffer.append(typeName); + + if (isNullable) { + buffer.append('?'); + } + + buffer.append("."); + + paramCount++; + start++; + index += paramType.stackSize; + } + + buffer.append(toValidKotlinIdentifier(name)); + buffer.append('('); + + List mask = methodWrapper.synthParameters; + + int lastVisibleParameterIndex = -1; + for (int i = 0; i < md.params.length; i++) { + if (mask == null || mask.get(i) == null) { + lastVisibleParameterIndex = i; + } + } + if (lastVisibleParameterIndex != -1) { + buffer.pushNewlineGroup(indent, 1); + buffer.appendPossibleNewline(); + } + + List methodParameters = null; + if (DecompilerContext.getOption(IFernflowerPreferences.USE_METHOD_PARAMETERS)) { + StructMethodParametersAttribute attr = mt.getAttribute(StructGeneralAttribute.ATTRIBUTE_METHOD_PARAMETERS); + if (attr != null) { + methodParameters = attr.getEntries(); + } + } + + boolean hasDescriptor = descriptor != null; + //mask should now have the Outer.this in it... so this *shouldn't* be nessasary. + //if (init && !isEnum && ((node.access & CodeConstants.ACC_STATIC) == 0) && node.type == ClassNode.CLASS_MEMBER) + // index++; + + boolean first = true; + buffer.pushNewlineGroup(indent, 0); + for (int i = start; i < md.params.length; i++) { + VarType parameterType = hasDescriptor && paramCount < descriptor.parameterTypes.size() ? descriptor.parameterTypes.get(paramCount) : md.params[i]; + if (mask == null || mask.get(i) == null) { + if (!first) { + buffer.append(","); + buffer.appendPossibleNewline(" "); + } + first = false; + + // @PAnn vararg? pName: pTy + boolean nullable = processParameterAnnotations(buffer, mt, paramCount); + + boolean isVarArg = i == lastVisibleParameterIndex && mt.hasModifier(CodeConstants.ACC_VARARGS) && parameterType.arrayDim > 0; + if (isVarArg) { + buffer.append("vararg "); + } + + String parameterName; + if (methodParameters != null && i < methodParameters.size()) { + parameterName = methodParameters.get(i).myName; + } else { + parameterName = methodWrapper.varproc.getVarName(new VarVersionPair(index, 0)); + } + + if ((flags & (CodeConstants.ACC_ABSTRACT | CodeConstants.ACC_NATIVE)) != 0) { + String newParameterName = methodWrapper.methodStruct.getVariableNamer().renameAbstractParameter(parameterName, index); + parameterName = !newParameterName.equals(parameterName) ? newParameterName : DecompilerContext.getStructContext().renameAbstractParameter(methodWrapper.methodStruct.getClassQualifiedName(), mt.getName(), mt.getDescriptor(), index - (((flags & CodeConstants.ACC_STATIC) == 0) ? 1 : 0), parameterName); + + } + + parameterName = toValidKotlinIdentifier(parameterName); + + buffer.append(parameterName == null ? "param" + index : parameterName); // null iff decompiled with errors + buffer.append(": "); + + if (methodParameters != null && i < methodParameters.size()) { + appendModifiers(buffer, methodParameters.get(i).myAccessFlags, CodeConstants.ACC_FINAL, isInterface, 0); + } else if (methodWrapper.varproc.getVarFinal(new VarVersionPair(index, 0)) == VarTypeProcessor.FinalType.EXPLICIT_FINAL) { + buffer.append("final "); + } + + String typeName; + if (isVarArg) { + parameterType = parameterType.decreaseArrayDim(); + } + typeName = KTypes.getKotlinType(parameterType); + + if (KTypes.isFunctionType(parameterType) && nullable) { + typeName = "(" + typeName + ")"; + } + + buffer.append(typeName); + + if (nullable) { + buffer.append("?"); + } + + paramCount++; + } + + index += parameterType.stackSize; + } + buffer.popNewlineGroup(); + + if (lastVisibleParameterIndex != -1) { + buffer.appendPossibleNewline("", true); + buffer.popNewlineGroup(); + } + buffer.append(')'); + + VarType retType = descriptor == null ? md.ret : descriptor.returnType; + if (!init && !retType.isSuperset(VarType.VARTYPE_VOID)) { + buffer.append(": "); + String ret = KTypes.getKotlinType(retType); + if (KTypes.isFunctionType(retType) && isNullable(mt)) { + ret = "(" + ret + ")"; + } + buffer.append(ret); + if (isNullable(mt)) { + buffer.append("?"); + } + } + } + + if ((flags & (CodeConstants.ACC_ABSTRACT | CodeConstants.ACC_NATIVE)) != 0) { // native or abstract method (explicit or interface) + buffer.appendLineSeparator(); + } else { + if (!clInit && !dInit) { + buffer.append(' '); + } + + boolean hideIfInit = (clInit || dInit || hideConstructor(node, init, throwsExceptions, paramCount, flags)); + + hideMethod = !writeMethodBody(node, methodWrapper, buffer, indent, hideIfInit); + } + } finally { + DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, outerWrapper); + } + + // save total lines + // TODO: optimize + //tracer.setCurrentSourceLine(buffer.countLines(start_index_method)); + + return !hideMethod; + } + + public static boolean writeMethodBody(ClassNode node, MethodWrapper methodWrapper, TextBuffer buffer, int indent, boolean hideIfInit) { + boolean hideMethod = true; + StructClass cl = node.classStruct; + StructMethod mt = methodWrapper.methodStruct; + + buffer.append('{').appendLineSeparator(); + + RootStatement root = methodWrapper.root; + + if (root != null && methodWrapper.decompileError == null) { // check for existence + try { + // Avoid generating imports for ObjectMethods during root.toJava(...) + if (RecordHelper.isHiddenRecordMethod(cl, mt, root)) { + hideMethod = true; + } else { + TextBuffer code = root.toJava(indent + 1); + code.addBytecodeMapping(root.getDummyExit().bytecode); + hideMethod = code.length() == 0 && hideIfInit; + buffer.append(code, cl.qualifiedName, InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor())); + } + } catch (Throwable t) { + String message = "Method " + mt.getName() + " " + mt.getDescriptor() + " in class " + node.classStruct.qualifiedName + " couldn't be written."; + DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN, t); + methodWrapper.decompileError = t; + } + } + + if (methodWrapper.decompileError != null) { + dumpError(buffer, methodWrapper, indent + 1); + } + buffer.appendIndent(indent).append('}').appendLineSeparator(); + return !hideMethod; + } + + public static void dumpError(TextBuffer buffer, MethodWrapper wrapper, int indent) { + List lines = new ArrayList<>(); + lines.add("$VF: Couldn't be decompiled"); + boolean exceptions = DecompilerContext.getOption(IFernflowerPreferences.DUMP_EXCEPTION_ON_ERROR); + boolean bytecode = DecompilerContext.getOption(IFernflowerPreferences.DUMP_BYTECODE_ON_ERROR); + if (exceptions) { + lines.addAll(KotlinWriter.getErrorComment()); + collectErrorLines(wrapper.decompileError, lines); + if (bytecode) { + lines.add(""); + } + } + if (bytecode) { + try { + lines.add("Bytecode:"); + collectBytecode(wrapper, lines); + } catch (Exception e) { + lines.add("Error collecting bytecode:"); + collectErrorLines(e, lines); + } finally { + wrapper.methodStruct.releaseResources(); + } + } + for (String line : lines) { + buffer.appendIndent(indent); + buffer.append("//"); + if (!line.isEmpty()) buffer.append(' ').append(line); + buffer.appendLineSeparator(); + } + } + + public static void collectErrorLines(Throwable error, List lines) { + StackTraceElement[] stack = error.getStackTrace(); + List filteredStack = new ArrayList<>(); + boolean hasSeenOwnClass = false; + for (StackTraceElement e : stack) { + String className = e.getClassName(); + boolean isOwnClass = className.startsWith("org.jetbrains.java.decompiler"); + if (isOwnClass) { + hasSeenOwnClass = true; + } else if (hasSeenOwnClass) { + break; + } + filteredStack.add(e); + if (isOwnClass) { + String simpleName = className.substring(className.lastIndexOf('.') + 1); + if (ERROR_DUMP_STOP_POINTS.contains(simpleName + "." + e.getMethodName())) { + break; + } + } + } + if (filteredStack.isEmpty()) return; + lines.add(error.toString()); + for (StackTraceElement e : filteredStack) { + lines.add(" at " + e); + } + Throwable cause = error.getCause(); + if (cause != null) { + List causeLines = new ArrayList<>(); + collectErrorLines(cause, causeLines); + if (!causeLines.isEmpty()) { + lines.add("Caused by: " + causeLines.get(0)); + lines.addAll(causeLines.subList(1, causeLines.size())); + } + } + } + + private static void collectBytecode(MethodWrapper wrapper, List lines) throws IOException { + ClassNode classNode = (ClassNode) DecompilerContext.getContextProperty(DecompilerContext.CURRENT_CLASS_NODE); + StructMethod method = wrapper.methodStruct; + InstructionSequence instructions = method.getInstructionSequence(); + if (instructions == null) { + method.expandData(classNode.classStruct); + instructions = method.getInstructionSequence(); + } + int lastOffset = instructions.getOffset(instructions.length() - 1); + int digits = 8 - Integer.numberOfLeadingZeros(lastOffset) / 4; + ConstantPool pool = classNode.classStruct.getPool(); + StructBootstrapMethodsAttribute bootstrap = classNode.classStruct.getAttribute(StructGeneralAttribute.ATTRIBUTE_BOOTSTRAP_METHODS); + + for (int idx = 0; idx < instructions.length(); idx++) { + int offset = instructions.getOffset(idx); + Instruction instr = instructions.getInstr(idx); + StringBuilder sb = new StringBuilder(); + String offHex = Integer.toHexString(offset); + for (int i = offHex.length(); i < digits; i++) sb.append('0'); + sb.append(offHex).append(": "); + if (instr.wide) { + sb.append("wide "); + } + sb.append(TextUtil.getInstructionName(instr.opcode)); + switch (instr.group) { + case CodeConstants.GROUP_INVOCATION: { + sb.append(' '); + if (instr.opcode == CodeConstants.opc_invokedynamic && bootstrap != null) { + appendBootstrapCall(sb, pool.getLinkConstant(instr.operand(0)), bootstrap); + } else { + appendConstant(sb, pool.getConstant(instr.operand(0))); + } + for (int i = 1; i < instr.operandsCount(); i++) { + sb.append(' ').append(instr.operand(i)); + } + break; + } + case CodeConstants.GROUP_FIELDACCESS: { + sb.append(' '); + appendConstant(sb, pool.getConstant(instr.operand(0))); + break; + } + case CodeConstants.GROUP_JUMP: { + sb.append(' '); + int dest = offset + instr.operand(0); + String destHex = Integer.toHexString(dest); + for (int i = destHex.length(); i < digits; i++) sb.append('0'); + sb.append(destHex); + break; + } + default: { + switch (instr.opcode) { + case CodeConstants.opc_new: + case CodeConstants.opc_checkcast: + case CodeConstants.opc_instanceof: + case CodeConstants.opc_ldc: + case CodeConstants.opc_ldc_w: + case CodeConstants.opc_ldc2_w: { + sb.append(' '); + PooledConstant constant = pool.getConstant(instr.operand(0)); + if (constant.type == CodeConstants.CONSTANT_Dynamic && bootstrap != null) { + appendBootstrapCall(sb, (LinkConstant) constant, bootstrap); + } else { + appendConstant(sb, constant); + } + break; + } + default: { + for (int i = 0; i < instr.operandsCount(); i++) { + sb.append(' ').append(instr.operand(i)); + } + } + } + } + } + lines.add(sb.toString()); + } + } + + private static void appendBootstrapCall(StringBuilder sb, LinkConstant target, StructBootstrapMethodsAttribute bootstrap) { + sb.append(target.elementname).append(' ').append(target.descriptor); + + LinkConstant bsm = bootstrap.getMethodReference(target.index1); + List bsmArgs = bootstrap.getMethodArguments(target.index1); + + sb.append(" bsm="); + appendConstant(sb, bsm); + sb.append(" args=[ "); + boolean first = true; + for (PooledConstant arg : bsmArgs) { + if (!first) sb.append(", "); + first = false; + appendConstant(sb, arg); + } + sb.append(" ]"); + } + + private static void appendConstant(StringBuilder sb, PooledConstant constant) { + if (constant == null) { + sb.append(""); + return; + } + if (constant instanceof PrimitiveConstant) { + PrimitiveConstant prim = ((PrimitiveConstant) constant); + Object value = prim.value; + String stringValue = String.valueOf(value); + if (prim.type == CodeConstants.CONSTANT_Class) { + sb.append(stringValue); + } else if (prim.type == CodeConstants.CONSTANT_String) { + sb.append('"').append(ConstExprent.convertStringToJava(stringValue, false)).append('"'); + } else { + sb.append(stringValue); + } + } else if (constant instanceof LinkConstant) { + LinkConstant linkConstant = (LinkConstant) constant; + sb.append(linkConstant.classname).append('.').append(linkConstant.elementname).append(' ').append(linkConstant.descriptor); + } + } + + public static boolean hideConstructor(ClassNode node, boolean init, boolean throwsExceptions, int paramCount, int methodAccessFlags) { + if (!init || throwsExceptions || paramCount > 0 || !DecompilerContext.getOption(IFernflowerPreferences.HIDE_DEFAULT_CONSTRUCTOR)) { + return false; + } + + ClassWrapper wrapper = node.getWrapper(); + StructClass cl = wrapper.getClassStruct(); + + int classAccessFlags = node.type == ClassNode.Type.ROOT ? cl.getAccessFlags() : node.access; + boolean isEnum = cl.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM); + + // default constructor requires same accessibility flags. Exception: enum constructor which is always private + if (!isEnum && ((classAccessFlags & ACCESSIBILITY_FLAGS) != (methodAccessFlags & ACCESSIBILITY_FLAGS))) { + return false; + } + + int count = 0; + for (StructMethod mt : cl.getMethods()) { + if (CodeConstants.INIT_NAME.equals(mt.getName())) { + if (++count > 1) { + return false; + } + } + } + + return true; + } + + private static Map.Entry getFieldTypeData(StructField fd) { + VarType fieldType = new VarType(fd.getDescriptor(), false); + + GenericFieldDescriptor descriptor = fd.getSignature(); + return new AbstractMap.SimpleImmutableEntry<>(fieldType, descriptor); + } + + private static boolean containsDeprecatedAnnotation(StructMember mb) { + for (Key key : ANNOTATION_ATTRIBUTES) { + StructAnnotationAttribute attribute = (StructAnnotationAttribute) mb.getAttribute((Key) key); + if (attribute != null) { + for (AnnotationExprent annotation : attribute.getAnnotations()) { + if (annotation.getClassName().equals("java/lang/Deprecated")) { + return true; + } + } + } + } + + return false; + } + + private static void appendDeprecation(TextBuffer buffer, int indent) { + buffer.appendIndent(indent).append("/** @deprecated */").appendLineSeparator(); + } + + private enum MType {CLASS, FIELD, METHOD} + + private static void appendRenameComment(TextBuffer buffer, String oldName, MType type, int indent) { + if (oldName == null) return; + + buffer.appendIndent(indent); + buffer.append("// $VF: renamed from: "); + + switch (type) { + case CLASS: + buffer.append(ExprProcessor.buildJavaClassName(oldName)); + break; + + case FIELD: + String[] fParts = oldName.split(" "); + FieldDescriptor fd = FieldDescriptor.parseDescriptor(fParts[2]); + buffer.append(fParts[1]); + buffer.append(' '); + buffer.append(getTypePrintOut(fd.type)); + break; + + default: + String[] mParts = oldName.split(" "); + MethodDescriptor md = MethodDescriptor.parseDescriptor(mParts[2]); + buffer.append(mParts[1]); + buffer.append(" ("); + boolean first = true; + for (VarType paramType : md.params) { + if (!first) { + buffer.append(", "); + } + first = false; + buffer.append(getTypePrintOut(paramType)); + } + buffer.append(") "); + buffer.append(getTypePrintOut(md.ret)); + } + + buffer.appendLineSeparator(); + } + + private static String getTypePrintOut(VarType type) { + String typeText = ExprProcessor.getCastTypeName(type, false); + if (ExprProcessor.UNDEFINED_TYPE_STRING.equals(typeText) && + DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) { + typeText = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT, false); + } + return typeText; + } + + public static List getErrorComment() { + return Arrays.stream(((String) DecompilerContext.getProperty(IFernflowerPreferences.ERROR_MESSAGE)).split("\n")).filter(s -> !s.isEmpty()).collect(Collectors.toList()); + } + + private static void appendComment(TextBuffer buffer, String comment, int indent) { + buffer.appendIndent(indent).append("// $VF: ").append(comment).appendLineSeparator(); + } + + private static void appendInlineComment(TextBuffer buffer, String comment) { + buffer.append("/* $VF: ").append(comment).append(" */"); + } + + private static void appendJavadoc(TextBuffer buffer, String javaDoc, int indent) { + if (javaDoc == null) return; + buffer.appendIndent(indent).append("/**").appendLineSeparator(); + for (String s : javaDoc.split("\n")) { + buffer.appendIndent(indent).append(" * ").append(s).appendLineSeparator(); + } + buffer.appendIndent(indent).append(" */").appendLineSeparator(); + } + + static final Key[] ANNOTATION_ATTRIBUTES = { + StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS, StructGeneralAttribute.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS}; + static final Key[] PARAMETER_ANNOTATION_ATTRIBUTES = { + StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, StructGeneralAttribute.ATTRIBUTE_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS}; + static final Key[] TYPE_ANNOTATION_ATTRIBUTES = { + StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_TYPE_ANNOTATIONS, StructGeneralAttribute.ATTRIBUTE_RUNTIME_INVISIBLE_TYPE_ANNOTATIONS}; + + public static void appendAnnotations(TextBuffer buffer, int indent, StructMember mb, int targetType) { + Set filter = new HashSet<>(); + + for (Key key : ANNOTATION_ATTRIBUTES) { + StructAnnotationAttribute attribute = mb.getAttribute((Key) key); + if (attribute != null) { + for (AnnotationExprent annotation : attribute.getAnnotations()) { + if (annotation.getClassName().equals("kotlin/Metadata") + || annotation.getClassName().equals(NOT_NULL_ANN_NAME) + || annotation.getClassName().equals(NULLABLE_ANN_NAME) + || annotation.getClassName().equals("kotlin/jvm/JvmStatic")) { + continue; + } + + KAnnotationExprent kAnnotation = new KAnnotationExprent(annotation); + + String text = kAnnotation.toJava(indent).convertToStringAndAllowDataDiscard(); + filter.add(text); + buffer.append(text); + if (indent < 0) { + buffer.append(' '); + } else { + buffer.appendLineSeparator(); + } + } + } + } + + appendTypeAnnotations(buffer, indent, mb, targetType, -1, filter); + } + + public static void appendJvmAnnotations(TextBuffer buffer, int indent, StructMember mb, boolean isInterface, ConstantPool pool, int targetType) { + switch (targetType) { + case TypeAnnotation.METHOD_RETURN_TYPE: + if (isInterface && !mb.hasModifier(CodeConstants.ACC_ABSTRACT)) { + buffer.appendIndent(indent).append("@JvmDefault").appendLineSeparator(); + } + if (mb.hasModifier(CodeConstants.ACC_SYNCHRONIZED)) { + buffer.appendIndent(indent).append("@Synchronized").appendLineSeparator(); + } + if (mb.hasAttribute(StructGeneralAttribute.ATTRIBUTE_EXCEPTIONS)) { + StructExceptionsAttribute attrib = mb.getAttribute(StructGeneralAttribute.ATTRIBUTE_EXCEPTIONS); + buffer.appendIndent(indent).append("@Throws("); + buffer.pushNewlineGroup(indent, 1); + boolean first = true; + for (int i : attrib.getThrowsExceptions()) { + if (!first) { + buffer.append(",").appendPossibleNewline(" "); + } + first = false; + String name = pool.getPrimitiveConstant(i).getString(); + buffer.append(name).append("::class"); + } + buffer.popNewlineGroup(); + buffer.append(")").appendLineSeparator(); + } + break; + case TypeAnnotation.FIELD: + // TODO: use site targets + if (mb.hasModifier(CodeConstants.ACC_TRANSIENT)) { + buffer.appendIndent(indent).append("@Transient").appendLineSeparator(); + } + if (mb.hasModifier(CodeConstants.ACC_VOLATILE)) { + buffer.appendIndent(indent).append("@Volatile").appendLineSeparator(); + } + break; + case TypeAnnotation.CLASS_TYPE_PARAMETER: + if (mb.hasAttribute(StructRecordAttribute.ATTRIBUTE_RECORD)) { + buffer.appendIndent(indent).append("@JvmRecord").appendLineSeparator(); + } + } + + if (mb.hasModifier(CodeConstants.ACC_STATIC) && targetType != TypeAnnotation.CLASS_TYPE_PARAMETER && KotlinDecompilationContext.getCurrentType() != KotlinDecompilationContext.KotlinType.FILE) { + buffer.appendIndent(indent).append("@JvmStatic").appendLineSeparator(); + } + if (mb.hasModifier(CodeConstants.ACC_STRICT)) { + buffer.appendIndent(indent).append("@Strictfp").appendLineSeparator(); + } + if (mb.hasModifier(CodeConstants.ACC_SYNTHETIC)) { + buffer.appendIndent(indent).append("@JvmSynthetic").appendLineSeparator(); + } + } + + static boolean isNullable(StructMember mb) { + for (Key key : ANNOTATION_ATTRIBUTES) { + StructAnnotationAttribute attribute = (StructAnnotationAttribute) mb.getAttribute((Key) key); + if (attribute != null) { + return attribute.getAnnotations().stream().anyMatch(annotation -> annotation.getClassName().equals(NULLABLE_ANN_NAME)); + } + } + return false; + } + + // Returns true if a method with the given name and descriptor matches in the inheritance tree of the superclass. + public static boolean searchForMethod(StructClass cl, String name, MethodDescriptor md, boolean search) { + // Didn't find the class or the library containing the class wasn't loaded, can't search + if (cl == null) { + return false; + } + + VBStyleCollection methods = cl.getMethods(); + + if (search) { + // If we're allowed to search, iterate through the methods and try to find matches + for (StructMethod method : methods) { + // Match against name, descriptor, and whether or not the found method is static. + // TODO: We are not handling generics or superclass parameters and return types + if (md.equals(MethodDescriptor.parseDescriptor(method.getDescriptor())) && name.equals(method.getName()) && !method.hasModifier(CodeConstants.ACC_STATIC)) { + return true; + } + } + } + + // If we have a superclass that's not Object, search that as well + if (cl.superClass != null) { + StructClass superClass = DecompilerContext.getStructContext().getClass((String) cl.superClass.value); + + boolean foundInSuperClass = searchForMethod(superClass, name, md, true); + + if (foundInSuperClass) { + return true; + } + } + + // Search all of the interfaces implemented by this class for the method + for (String ifaceName : cl.getInterfaceNames()) { + StructClass iface = DecompilerContext.getStructContext().getClass(ifaceName); + + boolean foundInIface = searchForMethod(iface, name, md, true); + + if (foundInIface) { + return true; + } + } + + // We didn't manage to find anything, return + return false; + } + + public static boolean processParameterAnnotations(TextBuffer buffer, StructMethod mt, int param) { + Set filter = new HashSet<>(); + boolean ret = false; + + for (Key key : PARAMETER_ANNOTATION_ATTRIBUTES) { + StructAnnotationParameterAttribute attribute = (StructAnnotationParameterAttribute) mt.getAttribute((Key) key); + if (attribute != null) { + List> annotations = attribute.getParamAnnotations(); + if (param < annotations.size()) { + for (AnnotationExprent annotation : annotations.get(param)) { + if (annotation.getClassName().equals(NOT_NULL_ANN_NAME)) { + continue; + } else if (annotation.getClassName().equals(NULLABLE_ANN_NAME)) { + ret = true; + continue; + } + String text = annotation.toJava(-1).convertToStringAndAllowDataDiscard(); + filter.add(text); + buffer.append(text).append(' '); + } + } + } + } + + appendTypeAnnotations(buffer, -1, mt, TypeAnnotation.METHOD_PARAMETER, param, filter); + return ret; + } + + private static void appendTypeAnnotations(TextBuffer buffer, int indent, StructMember mb, int targetType, int index, Set filter) { + for (Key key : TYPE_ANNOTATION_ATTRIBUTES) { + StructTypeAnnotationAttribute attribute = (StructTypeAnnotationAttribute) mb.getAttribute((Key) key); + if (attribute != null) { + for (TypeAnnotation annotation : attribute.getAnnotations()) { + if (annotation.isTopLevel() && annotation.getTargetType() == targetType && (index < 0 || annotation.getIndex() == index)) { + String text = annotation.getAnnotation().toJava(indent).convertToStringAndAllowDataDiscard(); + if (!filter.contains(text)) { + buffer.append(text); + if (indent < 0) { + buffer.append(' '); + } else { + buffer.appendLineSeparator(); + } + } + } + } + } + } + } + + private static final Map MODIFIERS; + + static { + MODIFIERS = new LinkedHashMap<>(); + MODIFIERS.put(CodeConstants.ACC_PUBLIC, "public"); + MODIFIERS.put(CodeConstants.ACC_PROTECTED, "protected"); + MODIFIERS.put(CodeConstants.ACC_PRIVATE, "private"); + MODIFIERS.put(CodeConstants.ACC_ABSTRACT, "abstract"); +// MODIFIERS.put(CodeConstants.ACC_STATIC, "static"); +// MODIFIERS.put(CodeConstants.ACC_FINAL, "final"); +// MODIFIERS.put(CodeConstants.ACC_STRICT, "strictfp"); +// MODIFIERS.put(CodeConstants.ACC_TRANSIENT, "transient"); +// MODIFIERS.put(CodeConstants.ACC_VOLATILE, "volatile"); +// MODIFIERS.put(CodeConstants.ACC_SYNCHRONIZED, "synchronized"); + MODIFIERS.put(CodeConstants.ACC_NATIVE, "native"); + } + + private static final int CLASS_ALLOWED = + CodeConstants.ACC_PROTECTED | CodeConstants.ACC_PRIVATE | CodeConstants.ACC_ABSTRACT | + CodeConstants.ACC_STATIC | CodeConstants.ACC_STRICT; + private static final int FIELD_ALLOWED = + CodeConstants.ACC_PUBLIC | CodeConstants.ACC_PROTECTED | CodeConstants.ACC_PRIVATE | CodeConstants.ACC_STATIC | + CodeConstants.ACC_FINAL | CodeConstants.ACC_TRANSIENT | CodeConstants.ACC_VOLATILE; + private static final int METHOD_ALLOWED = + CodeConstants.ACC_PUBLIC | CodeConstants.ACC_PROTECTED | CodeConstants.ACC_PRIVATE | CodeConstants.ACC_ABSTRACT | + CodeConstants.ACC_STATIC | CodeConstants.ACC_FINAL | CodeConstants.ACC_SYNCHRONIZED | CodeConstants.ACC_NATIVE | + CodeConstants.ACC_STRICT; + + private static final int CLASS_EXCLUDED = CodeConstants.ACC_ABSTRACT | CodeConstants.ACC_STATIC; + private static final int FIELD_EXCLUDED = CodeConstants.ACC_PUBLIC | CodeConstants.ACC_STATIC | CodeConstants.ACC_FINAL; + private static final int METHOD_EXCLUDED = CodeConstants.ACC_PUBLIC | CodeConstants.ACC_ABSTRACT; + + private static final int ACCESSIBILITY_FLAGS = CodeConstants.ACC_PUBLIC | CodeConstants.ACC_PROTECTED | CodeConstants.ACC_PRIVATE; + + private static void appendModifiers(TextBuffer buffer, int flags, int allowed, boolean isInterface, int excluded) { + flags &= allowed; + if (!isInterface) excluded = 0; + for (int modifier : MODIFIERS.keySet()) { + if ((flags & modifier) == modifier && (modifier & excluded) == 0) { + buffer.append(MODIFIERS.get(modifier)).append(' '); + } + } + } + + public static String getModifiers(int flags) { + return MODIFIERS.entrySet().stream().filter(e -> (e.getKey() & flags) != 0).map(Map.Entry::getValue).collect(Collectors.joining(" ")); + } + + public static void appendTypeParameters(TextBuffer buffer, List parameters, List> bounds) { + buffer.append('<'); + + for (int i = 0; i < parameters.size(); i++) { + if (i > 0) { + buffer.append(", "); + } + + buffer.append(parameters.get(i)); + + List parameterBounds = bounds.get(i); + if (parameterBounds.size() > 1 || !"java/lang/Object".equals(parameterBounds.get(0).value)) { + buffer.append(" extends "); + buffer.append(ExprProcessor.getCastTypeName(parameterBounds.get(0))); + for (int j = 1; j < parameterBounds.size(); j++) { + buffer.append(" & "); + buffer.append(ExprProcessor.getCastTypeName(parameterBounds.get(j))); + } + } + } + + buffer.append('>'); + } + + private static void appendFQClassNames(TextBuffer buffer, List names) { + for (int i = 0; i < names.size(); i++) { + String name = names.get(i); + buffer.appendIndent(2).append(name); + if (i < names.size() - 1) { + buffer.append(',').appendLineSeparator(); + } + } + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KAnnotationExprent.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KAnnotationExprent.java new file mode 100644 index 0000000000..c7432ca7b8 --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KAnnotationExprent.java @@ -0,0 +1,142 @@ +package org.vineflower.kotlin.expr; + +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.modules.decompiler.exps.*; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.TextBuffer; +import org.vineflower.kotlin.util.KTypes; +import org.vineflower.kotlin.util.KUtils; + +public class KAnnotationExprent extends AnnotationExprent implements KExprent { + public enum UseSiteTarget { + FILE, + PROPERTY, + FIELD, + GET, + SET, + RECEIVER, + PARAM, + SETPARAM, + DELEGATE, + } + + private final UseSiteTarget useSiteTarget; + + public KAnnotationExprent(AnnotationExprent expr) { + this(expr, null); + } + + public KAnnotationExprent(AnnotationExprent expr, UseSiteTarget useSiteTarget) { + super(expr.getClassName(), expr.getParNames(), KUtils.replaceExprents(expr.getParValues())); + bytecode = expr.bytecode; + this.useSiteTarget = useSiteTarget; + } + + @Override + public TextBuffer toJava(int indent) { + TextBuffer buffer = new TextBuffer(); + buffer.addBytecodeMapping(bytecode); + + buffer.appendIndent(indent).append('@'); + + if (useSiteTarget != null) { + buffer.append(useSiteTarget.name().toLowerCase()).append(':'); + } + + VarType type = new VarType(getClassName(), true); + buffer.append(KTypes.getKotlinType(type)); + + switch (getAnnotationType()) { + case SINGLE_ELEMENT: + buffer.append('(').appendPossibleNewline(); + writeAnnotationValue(getParValues().get(0), buffer); + buffer.append(')'); + break; + case NORMAL: + buffer.append('(').appendPossibleNewline(); + boolean first = true; + for (int i = 0; i < getParValues().size(); i++) { + if (first) { + first = false; + } else { + buffer.append(",").appendPossibleNewline(" "); + } + buffer + .append(getParNames().get(i)) + .append(" = "); + writeAnnotationValue(getParValues().get(i), buffer); + } + buffer.append(')'); + break; + } + + return buffer; + } + + public static void writeAnnotationValue(Exprent expr, TextBuffer buffer) { + if (expr instanceof FieldExprent) { + // Enum value + FieldExprent fieldExprent = (FieldExprent) expr; + VarType type = new VarType(fieldExprent.getClassname(), true); + DecompilerContext.getImportCollector().getShortName(fieldExprent.getClassname(), true); + + buffer + .append(KTypes.getKotlinType(type)) + .append(".") + .append(fieldExprent.getName()); + } else if (expr instanceof NewExprent) { + // Array value + NewExprent newExprent = (NewExprent) expr; + buffer.append('['); + boolean first = true; + for (Exprent exprent : newExprent.getLstArrayElements()) { + if (first) { + first = false; + } else { + buffer.append(", "); + } + writeAnnotationValue(exprent, buffer); + } + buffer.append(']'); + } else if (expr instanceof AnnotationExprent) { + AnnotationExprent annotationExprent = (AnnotationExprent) expr; + buffer.append('@'); + VarType type = new VarType(annotationExprent.getClassName(), true); + buffer.append(KTypes.getKotlinType(type)); + + switch (annotationExprent.getAnnotationType()) { + case SINGLE_ELEMENT: + buffer.append('('); + writeAnnotationValue(annotationExprent.getParValues().get(0), buffer); + buffer.append(')'); + break; + case NORMAL: + buffer.append('('); + boolean first = true; + for (int i = 0; i < annotationExprent.getParValues().size(); i++) { + if (first) { + first = false; + } else { + buffer.append(", "); + } + buffer + .append(annotationExprent.getParNames().get(i)) + .append(" = "); + writeAnnotationValue(annotationExprent.getParValues().get(i), buffer); + } + buffer.append(')'); + break; + } + } else if (expr instanceof ConstExprent) { + ConstExprent constExprent = (ConstExprent) expr; + if (constExprent.getConstType().equals(VarType.VARTYPE_CLASS)) { + VarType type = new VarType((String) constExprent.getValue(), true); + DecompilerContext.getImportCollector().getShortName((String) constExprent.getValue(), true); + buffer.append(KTypes.getKotlinType(type)) + .append("::class"); + } else { + buffer.append(constExprent.toJava(0)); + } + } + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KConstExprent.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KConstExprent.java new file mode 100644 index 0000000000..42d38585ac --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KConstExprent.java @@ -0,0 +1,35 @@ +package org.vineflower.kotlin.expr; + +import org.jetbrains.java.decompiler.modules.decompiler.exps.ConstExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.ExprUtil; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.TextBuffer; + +public class KConstExprent extends ConstExprent implements KExprent { + public KConstExprent(ConstExprent exprent) { + super(exprent.getConstType(), exprent.getValue(), exprent.isBoolPermitted(), exprent.bytecode); + } + + @Override + public TextBuffer toJava(int indent) { + if (!getConstType().equals(VarType.VARTYPE_CLASS)) { + return super.toJava(indent); + } + + TextBuffer buf = new TextBuffer(); + buf.addBytecodeMapping(bytecode); + + if (getValue() == null) { + //TODO figure out why this happens here instead of elsewhere + return buf.append("Class<*>"); + } + + String value = getValue().toString(); + VarType type = new VarType(value, !value.startsWith("[")); + buf.appendCastTypeName(type).append("::class.java"); + if (ExprUtil.PRIMITIVE_TYPES.containsKey(value)) { + buf.append("ObjectType"); // Primitive boxes require javaObjectType + } + return buf; + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KExprent.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KExprent.java new file mode 100644 index 0000000000..898d3d5cb5 --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KExprent.java @@ -0,0 +1,7 @@ +package org.vineflower.kotlin.expr; + +/** + * Marker interface for Kotlin exprents. + */ +public interface KExprent { +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KFieldExprent.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KFieldExprent.java new file mode 100644 index 0000000000..412729b3b7 --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KFieldExprent.java @@ -0,0 +1,25 @@ +package org.vineflower.kotlin.expr; + +import org.jetbrains.java.decompiler.modules.decompiler.exps.ExprUtil; +import org.jetbrains.java.decompiler.modules.decompiler.exps.FieldExprent; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.TextBuffer; +import org.vineflower.kotlin.util.KTypes; + +public class KFieldExprent extends FieldExprent implements KExprent { + public KFieldExprent(FieldExprent field) { + super(field.getName(), field.getClassname(), field.isStatic(), field.getInstance(), field.getDescriptor(), field.bytecode); + } + + @Override + public TextBuffer toJava(int indent) { + if (getName().equals("TYPE") && ExprUtil.PRIMITIVE_TYPES.containsKey(getClassname())) { + TextBuffer buf = new TextBuffer(); + VarType type = new VarType(getClassname(), true); + buf.append(KTypes.getKotlinType(type)); + buf.append("::class.javaPrimitiveType"); + return buf; + } + return super.toJava(indent); + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KFunctionExprent.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KFunctionExprent.java new file mode 100644 index 0000000000..b6f8553d6e --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KFunctionExprent.java @@ -0,0 +1,327 @@ +package org.vineflower.kotlin.expr; + +import org.jetbrains.java.decompiler.modules.decompiler.exps.*; +import org.jetbrains.java.decompiler.modules.decompiler.vars.CheckTypesResult; +import org.jetbrains.java.decompiler.struct.gen.CodeType; +import org.jetbrains.java.decompiler.struct.gen.TypeFamily; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.TextBuffer; +import org.jetbrains.java.decompiler.util.Typed; +import org.vineflower.kotlin.util.KTypes; +import org.vineflower.kotlin.util.KUtils; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; + +public class KFunctionExprent extends FunctionExprent implements KExprent { + private KFunctionType kType = KFunctionType.NONE; + + public enum KFunctionType implements Typed { + NONE, + + EQUALS3, + IF_NULL, + GET_KCLASS, + } + + public KFunctionExprent(KFunctionType funcType, List operands, BitSet bytecodeOffsets) { + this(FunctionType.OTHER, operands, bytecodeOffsets); + + this.kType = funcType; + } + + public KFunctionExprent(FunctionType funcType, List operands, BitSet bytecodeOffsets) { + super(funcType, new ArrayList<>(KUtils.replaceExprents(operands)), bytecodeOffsets); + } + + public KFunctionExprent(FunctionExprent func) { + this(func, KFunctionType.NONE, func.getExprType()); + } + + private KFunctionExprent(FunctionExprent func, KFunctionType kType, VarType exprType) { + super(func.getFuncType(), new ArrayList<>(KUtils.replaceExprents(func.getLstOperands())), func.bytecode); + + this.kType = kType; + setImplicitType(exprType); + setNeedsCast(func.doesCast()); + + if (getFuncType() == FunctionType.EQ) { + // If one (or both) sides is null, Kotlin uses == instead of === for strict equality + Exprent left = (Exprent) getAllExprents().get(0); + Exprent right = (Exprent) getAllExprents().get(1); + + if (left.getExprType() != VarType.VARTYPE_NULL && right.getExprType() != VarType.VARTYPE_NULL) { + setFuncType(KFunctionType.EQUALS3); + } + } + } + + @Override + public TextBuffer toJava(int indent) { + + TextBuffer buf = new TextBuffer(); + buf.addBytecodeMapping(this.bytecode); + List lstOperands = getLstOperands(); + + optimizeType(); + + switch(getFuncType()) { + case OTHER: + switch (kType) { + case EQUALS3: + buf.append(wrapOperandString(lstOperands.get(0), true, indent)) + .append(" === ") + .append(wrapOperandString(lstOperands.get(1), true, indent)); + return buf; + case IF_NULL: + buf.append(wrapOperandString(lstOperands.get(0), true, indent)) + .append(" ?: ") + .append(wrapOperandString(lstOperands.get(1), true, indent)); + return buf; + case GET_KCLASS: + Exprent operand = lstOperands.get(0); + if (operand instanceof VarExprent) { + VarExprent varExprent = ((VarExprent) operand); + if (!varExprent.getVarType().equals(VarType.VARTYPE_CLASS)) { + throw new IllegalArgumentException("Variable accessing KClass is not a Class"); + } + return buf.append(varExprent.toJava()).append(".kotlin"); + } else if (operand instanceof ConstExprent) { + ConstExprent constExprent = (ConstExprent) operand; + String value = constExprent.getValue().toString(); + VarType type = new VarType(value, !value.startsWith("[")); + buf.append(KTypes.getKotlinType(type)); + } else { + FieldExprent fieldExprent = (FieldExprent) operand; + String primitiveType = fieldExprent.getClassname(); + VarType type = new VarType(primitiveType, true); + buf.append(KTypes.getKotlinType(type)); + } + return buf.append("::class"); + } + + throw new IllegalStateException("Unknown function type: " + kType); + case TERNARY: + Exprent condition = lstOperands.get(0); + Exprent ifTrue = lstOperands.get(1); + Exprent ifFalse = lstOperands.get(2); + + if ( + condition instanceof KFunctionExprent && ((KFunctionExprent) condition).getFuncType() == FunctionType.INSTANCEOF + && ifTrue instanceof KFunctionExprent && ((KFunctionExprent) ifTrue).getFuncType() == FunctionType.CAST + && ifFalse.getExprType() == VarType.VARTYPE_NULL + ) { + // Safe cast + KFunctionExprent cast = (KFunctionExprent) ifTrue; + buf.append(cast.getLstOperands().get(0).toJava(indent)); + buf.append(" as? "); + buf.append(cast.getLstOperands().get(1).toJava(indent)); + return buf; + } + + buf.pushNewlineGroup(indent, 1); + buf.append("if ("); + buf.append(wrapOperandString(condition, true, indent)) + .append(")") + .appendPossibleNewline(" ") + .append(wrapOperandString(ifTrue, true, indent)) + .appendPossibleNewline(" ") + .append("else") + .appendPossibleNewline(" ") + .append(wrapOperandString(ifFalse, true, indent)); + buf.popNewlineGroup(); + + return buf; + case INSTANCEOF: + buf.append(wrapOperandString(lstOperands.get(0), true, indent)) + .append(" is ") + .append(wrapOperandString(lstOperands.get(1), true, indent)); + + return buf; + case BOOL_NOT: + // Special cases for `is` and `!is` + // TODO: do the same for `in` and `!in` + if (lstOperands.get(0) instanceof KFunctionExprent) { + KFunctionExprent func = (KFunctionExprent) lstOperands.get(0); + if (func.getFuncType() == FunctionExprent.FunctionType.INSTANCEOF) { + buf.append(wrapOperandString(func.getLstOperands().get(0), true, indent)) + .append(" !is ") + .append(wrapOperandString(func.getLstOperands().get(1), true, indent)); + return buf; + } + } + break; + case CAST: + if (!doesCast()) { + return buf.append(lstOperands.get(0).toJava(indent)); + } + buf.append(wrapOperandString(lstOperands.get(0), true, indent)).append(" as ").append(lstOperands.get(1).toJava(indent)); + return buf; + case BIT_NOT: + buf.append(wrapOperandString(lstOperands.get(0), true, indent)); + return buf.append(".inv()"); + case AND: // Bitwise AND + buf.append(wrapOperandString(lstOperands.get(0), true, indent)).append(" and ") + .append(wrapOperandString(lstOperands.get(1), true, indent)); + return buf; + case OR: + buf.append(wrapOperandString(lstOperands.get(0), true, indent)).append(" or ") + .append(wrapOperandString(lstOperands.get(1), true, indent)); + return buf; + case XOR: + buf.append(wrapOperandString(lstOperands.get(0), true, indent)).append(" xor ") + .append(wrapOperandString(lstOperands.get(1), true, indent)); + return buf; + case SHL: + buf.append(wrapOperandString(lstOperands.get(0), true, indent)).append(" shl ") + .append(wrapOperandString(lstOperands.get(1), true, indent)); + return buf; + case SHR: + buf.append(wrapOperandString(lstOperands.get(0), true, indent)).append(" shr ") + .append(wrapOperandString(lstOperands.get(1), true, indent)); + return buf; + case USHR: + buf.append(wrapOperandString(lstOperands.get(0), true, indent)).append(" ushr ") + .append(wrapOperandString(lstOperands.get(1), true, indent)); + return buf; + } + + return buf.append(super.toJava(indent)); + } + + @Override + public VarType getExprType() { + switch (kType) { + case EQUALS3: + return VarType.VARTYPE_BOOLEAN; + case IF_NULL: + Exprent param1 = getLstOperands().get(0); + Exprent param2 = getLstOperands().get(1); + VarType supertype = VarType.getCommonSupertype(param1.getExprType(), param2.getExprType()); + + if (supertype != null) { + return supertype; + } else { + // TODO: Needs a better default! + return VarType.VARTYPE_OBJECT; + } + case GET_KCLASS: + return VarType.VARTYPE_CLASS; + } + + return super.getExprType(); + } + + @Override + public CheckTypesResult checkExprTypeBounds() { + CheckTypesResult result = new CheckTypesResult(); + + Exprent param1 = getLstOperands().get(0); + VarType type1 = param1.getExprType(); + Exprent param2 = null; + VarType type2 = null; + + if (getLstOperands().size() > 1) { + param2 = getLstOperands().get(1); + type2 = param2.getExprType(); + } + + switch (kType) { + case IF_NULL: + VarType supertype = getExprType(); + result.addMinTypeExprent(param1, VarType.getMinTypeInFamily(supertype.typeFamily)); + result.addMinTypeExprent(param2, VarType.getMinTypeInFamily(supertype.typeFamily)); + break; + case EQUALS3: { + if (type1.type == CodeType.BOOLEAN) { + if (type2.isStrictSuperset(type1)) { + result.addMinTypeExprent(param1, VarType.VARTYPE_BYTECHAR); + } + else { // both are booleans + boolean param1_false_boolean = (param1 instanceof ConstExprent && !((ConstExprent)param1).hasBooleanValue()); + boolean param2_false_boolean = (param2 instanceof ConstExprent && !((ConstExprent)param2).hasBooleanValue()); + + if (param1_false_boolean || param2_false_boolean) { + result.addMinTypeExprent(param1, VarType.VARTYPE_BYTECHAR); + result.addMinTypeExprent(param2, VarType.VARTYPE_BYTECHAR); + } + } + } + else if (type2.type == CodeType.BOOLEAN) { + if (type1.isStrictSuperset(type2)) { + result.addMinTypeExprent(param2, VarType.VARTYPE_BYTECHAR); + } + } + } + } + + return super.checkExprTypeBounds(); + } + + private void optimizeType() { + if (getAnyFunctionType() == KFunctionType.EQUALS3) { + Exprent l = getLstOperands().get(0); + Exprent r = getLstOperands().get(1); + + if (l.getExprType().typeFamily != TypeFamily.OBJECT || r.getExprType().typeFamily != TypeFamily.OBJECT) { + setFuncType(FunctionType.EQ); + } + } + } + + public Typed getAnyFunctionType() { + FunctionType funcType = getFuncType(); + + if (funcType == FunctionType.OTHER) { + if (kType == KFunctionType.NONE) { + throw new IllegalStateException("No function type at all set!"); + } + + return kType; + } + + return funcType; + } + + @Override + public void setFuncType(FunctionType funcType) { + // Forward to the implementation below + setFuncType((Typed) funcType); + } + + public void setFuncType(Typed typed) { + if (typed instanceof FunctionType) { + // Set only regular func type and remove kotlin type + super.setFuncType((FunctionType) typed); + kType = KFunctionType.NONE; + } else if (typed instanceof KFunctionType) { + // Set only kotlin func type and remove regular type + super.setFuncType(FunctionType.OTHER); + kType = (KFunctionType) typed; + } else { + throw new IllegalArgumentException("Unknown function type: " + typed); + } + } + + @Override + public int getPrecedence() { + switch (kType) { + case EQUALS3: + return 6; + case IF_NULL: + return 11; + case GET_KCLASS: + return 1; + case NONE: + break; + } + + return super.getPrecedence(); + } + + @Override + public Exprent copy() { + return new KFunctionExprent((FunctionExprent) super.copy(), kType, getExprType()); + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KInvocationExprent.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KInvocationExprent.java new file mode 100644 index 0000000000..342509e396 --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KInvocationExprent.java @@ -0,0 +1,25 @@ +package org.vineflower.kotlin.expr; + +import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.InvocationExprent; + +public class KInvocationExprent extends InvocationExprent implements KExprent { + private boolean shadowStaticBase = false; + + public KInvocationExprent(InvocationExprent expr) { + super(expr); + } + + public boolean isShadowStaticBase() { + return shadowStaticBase; + } + + public void setShadowStaticBase(boolean shadowStaticBase) { + this.shadowStaticBase = shadowStaticBase; + } + + @Override + public Exprent copy() { + return new KInvocationExprent((InvocationExprent) super.copy()); + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KVarExprent.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KVarExprent.java new file mode 100644 index 0000000000..b6afd16e6e --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KVarExprent.java @@ -0,0 +1,66 @@ +package org.vineflower.kotlin.expr; + +import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.VarExprent; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarProcessor; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.TextBuffer; +import org.vineflower.kotlin.KotlinWriter; +import org.vineflower.kotlin.util.KTypes; + +import java.util.BitSet; + +public class KVarExprent extends VarExprent implements KExprent { + public KVarExprent(int index, VarType varType, VarProcessor processor, BitSet bytecode) { + super(index, varType, processor, bytecode); + } + + public KVarExprent(VarExprent ex) { + this(ex.getIndex(), ex.getVarType(), ex.getProcessor(), ex.bytecode); + this.setStack(ex.isStack()); + this.setClassDef(ex.isClassDef()); + this.setVersion(ex.getVersion()); + // FIXME: breaks tests by replacing name with "" +// this.setLVT(ex.getLVT()); + this.setEffectivelyFinal(ex.isEffectivelyFinal()); + this.setDefinition(ex.isDefinition()); + } + + @Override + public String getName() { + String name = super.getName(); + + if (name.startsWith("this@") || name.equals("this")) { + return name; + } + + return KotlinWriter.toValidKotlinIdentifier(name); + } + + @Override + public TextBuffer toJava(int indent) { + TextBuffer buffer = new TextBuffer(); + + buffer.addBytecodeMapping(bytecode); + + boolean definition = isDefinition(); + if (definition) { + // TODO: inference of var/val + buffer.append("var "); + } + + buffer.append(getName()); + + if (definition) { + buffer.append(": "); + buffer.append(KTypes.getKotlinType(getDefinitionVarType())); + } + + return buffer; + } + + @Override + public Exprent copy() { + return new KVarExprent((VarExprent) super.copy()); + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/metadata/BitEncoding.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/metadata/BitEncoding.java new file mode 100644 index 0000000000..8ab5757f7f --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/metadata/BitEncoding.java @@ -0,0 +1,138 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.vineflower.kotlin.metadata; + +public class BitEncoding { + + private static final char _8TO7_MODE_MARKER = (char) -1; + + private BitEncoding() { + } + + + private static void addModuloByte( byte[] data, int increment) { + for (int i = 0, n = data.length; i < n; i++) { + data[i] = (byte) ((data[i] + increment) & 0x7f); + } + } + + public static byte[] decodeBytes( String[] data) { + if (data.length > 0 && !data[0].isEmpty()) { + char possibleMarker = data[0].charAt(0); + if (possibleMarker == (char) 0) { + return stringsToBytes(dropMarker(data)); + } + if (possibleMarker == _8TO7_MODE_MARKER) { + data = dropMarker(data); + } + } + + byte[] bytes = combineStringArrayIntoBytes(data); + // Adding 0x7f modulo max byte value is equivalent to subtracting 1 the same modulo, which is inverse to what happens in encodeBytes + addModuloByte(bytes, 0x7f); + return decode7to8(bytes); + } + + private static byte[] stringsToBytes(String[] data) { + int size = 0; + for (String s : data) { + size += s.length(); + } + + byte[] bytes = new byte[size]; + + int offset = 0; + for (String s : data) { + for (int i = 0; i < s.length(); i++) { + bytes[offset++] = (byte) s.charAt(i); + } + } + + return bytes; + } + + + private static String[] dropMarker( String[] data) { + // Clone because the clients should be able to use the passed array for their own purposes. + // This is cheap because the size of the array is 1 or 2 almost always. + String[] result = data.clone(); + result[0] = result[0].substring(1); + return result; + } + + /** + * Combines the array of strings resulted from encodeBytes() into one long byte array + */ + + private static byte[] combineStringArrayIntoBytes( String[] data) { + int resultLength = 0; + for (String s : data) { + assert s.length() <= 65535 : "String is too long: " + s.length(); + resultLength += s.length(); + } + + byte[] result = new byte[resultLength]; + int p = 0; + for (String s : data) { + for (int i = 0, n = s.length(); i < n; i++) { + result[p++] = (byte) s.charAt(i); + } + } + + return result; + } + + /** + * Decodes the byte array resulted from encode8to7(). + * + * Each byte of the input array has at most 7 valuable bits of information. So the decoding is equivalent to the following: least + * significant 7 bits of all input bytes are combined into one long bit string. This bit string is then split into groups of 8 bits, + * each of which forms a byte in the output. If there are any leftovers, they are ignored, since they were added just as a padding and + * do not comprise a full byte. + * + * Suppose the following encoded byte array is given (bits are numbered the same way as in encode8to7() doc): + * + * 01234567 01234567 01234567 01234567 + * + * The output of the following form would be produced: + * + * 01234560 12345601 23456012 + * + * Note how all most significant bits and leftovers are dropped, since they don't contain any useful information + */ + + private static byte[] decode7to8( byte[] data) { + // floor(7 * data.length / 8) + int resultLength = 7 * data.length / 8; + + byte[] result = new byte[resultLength]; + + // We maintain a pointer to an input bit in the same fashion as in encode8to7(): it's represented as two numbers: index of the + // current byte in the input and index of the bit in the byte + int byteIndex = 0; + int bit = 0; + + // A resulting byte is comprised of 8 bits, starting from the current bit. Since each input byte only "contains 7 bytes", a + // resulting byte always consists of two parts: several most significant bits of the current byte and several least significant bits + // of the next byte + for (int i = 0; i < resultLength; i++) { + int firstPart = (data[byteIndex] & 0xff) >>> bit; + byteIndex++; + int secondPart = (data[byteIndex] & ((1 << (bit + 1)) - 1)) << 7 - bit; + result[i] = (byte) (firstPart + secondPart); + + if (bit == 6) { + byteIndex++; + bit = 0; + } + else { + bit++; + } + } + + return result; + } +} \ No newline at end of file diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/metadata/MetadataNameResolver.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/metadata/MetadataNameResolver.java new file mode 100644 index 0000000000..9108149bc5 --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/metadata/MetadataNameResolver.java @@ -0,0 +1,115 @@ +package org.vineflower.kotlin.metadata; + +import kotlinx.metadata.internal.metadata.jvm.JvmProtoBuf; + +import java.util.*; + +public class MetadataNameResolver { + private final JvmProtoBuf.StringTableTypes types; + private final String[] strings; + private final List records; + private static final Map PREDEFINED = buildPredefined(); + + public MetadataNameResolver(JvmProtoBuf.StringTableTypes types, String[] strings) { + this.types = types; + this.strings = strings; + this.records = new ArrayList<>(); + + for (JvmProtoBuf.StringTableTypes.Record record : types.getRecordList()) { + for (int i = 0; i < record.getRange(); i++) { + records.add(record); + } + } + } + + public String resolve(int idx) { + JvmProtoBuf.StringTableTypes.Record record = this.records.get(idx); + + String string; + if (record.hasString()) { + string = record.getString(); + } else if (record.hasPredefinedIndex() && PREDEFINED.containsKey(record.getPredefinedIndex())) { + string = PREDEFINED.get(record.getPredefinedIndex()); + } else { + string = this.strings[idx]; + } + + if (record.getSubstringIndexCount() >= 2) { + List l = record.getReplaceCharList(); + int begin = l.get(0); + int end = l.get(1); + + if (0 <= begin && begin <= end && end <= string.length()) { + string = string.substring(begin, end); + } + } + + if (record.getReplaceCharCount() >= 2) { + List l = record.getReplaceCharList(); + int from = l.get(0); + int to = l.get(1); + + if (0 <= from && from <= to && to <= 65535) { + string = string.replace((char) from, (char) to); + } + } + + var operation = record.getOperation() == null ? JvmProtoBuf.StringTableTypes.Record.Operation.NONE : record.getOperation(); + switch (operation) { + case INTERNAL_TO_CLASS_ID: + string = string.replace('$', '.'); + break; + case DESC_TO_CLASS_ID: + if (string.length() >= 2) { + string = string.substring(1, string.length() - 1); + } + + string = string.replace('$', '.'); + break; + } + + return string; + } + + private static Map buildPredefined() { + List strings = List.of( + "kotlin/Any", + "kotlin/Nothing", + "kotlin/Unit", + "kotlin/Throwable", + "kotlin/Number", + + "kotlin/Byte", "kotlin/Double", "kotlin/Float", "kotlin/Int", + "kotlin/Long", "kotlin/Short", "kotlin/Boolean", "kotlin/Char", + + "kotlin/CharSequence", + "kotlin/String", + "kotlin/Comparable", + "kotlin/Enum", + + "kotlin/Array", + "kotlin/ByteArray", "kotlin/DoubleArray", "kotlin/FloatArray", "kotlin/IntArray", + "kotlin/LongArray", "kotlin/ShortArray", "kotlin/BooleanArray", "kotlin/CharArray", + + "kotlin/Cloneable", + "kotlin/Annotation", + + "kotlin/collections/Iterable", "kotlin/collections/MutableIterable", + "kotlin/collections/Collection", "kotlin/collections/MutableCollection", + "kotlin/collections/List", "kotlin/collections/MutableList", + "kotlin/collections/Set", "kotlin/collections/MutableSet", + "kotlin/collections/Map", "kotlin/collections/MutableMap", + "kotlin/collections/Map.Entry", "kotlin/collections/MutableMap.MutableEntry", + + "kotlin/collections/Iterator", "kotlin/collections/MutableIterator", + "kotlin/collections/ListIterator", "kotlin/collections/MutableListIterator" + ); + + Map res = new HashMap<>(); + for (int i = 0; i < strings.size(); i++) { + res.put(i, strings.get(i)); + } + + return res; + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/pass/EliminateDeadVarsPass.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/pass/EliminateDeadVarsPass.java new file mode 100644 index 0000000000..824badcbf9 --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/pass/EliminateDeadVarsPass.java @@ -0,0 +1,86 @@ +package org.vineflower.kotlin.pass; + +import org.jetbrains.java.decompiler.api.plugin.pass.Pass; +import org.jetbrains.java.decompiler.api.plugin.pass.PassContext; +import org.jetbrains.java.decompiler.modules.decompiler.StackVarsProcessor; +import org.jetbrains.java.decompiler.modules.decompiler.exps.*; +import org.jetbrains.java.decompiler.modules.decompiler.flow.DirectGraph; +import org.jetbrains.java.decompiler.modules.decompiler.flow.DirectNode; +import org.jetbrains.java.decompiler.modules.decompiler.sforms.SSAUConstructorSparseEx; +import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPair; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionsGraph; +import org.jetbrains.java.decompiler.struct.StructMethod; + +import java.util.List; + +public class EliminateDeadVarsPass implements Pass { + + @Override + public boolean run(PassContext ctx) { + return eliminate(ctx.getRoot(), ctx.getMethod()); + } + + private static boolean eliminate(RootStatement root, StructMethod mt) { + SSAUConstructorSparseEx ssau = new SSAUConstructorSparseEx(); + ssau.splitVariables(root, mt); + + // TODO: preference for this, as it can be a destructive action + DirectGraph digraph = ssau.getDirectGraph(); + VarVersionsGraph ssu = ssau.getSsuVersions(); + + boolean changedAny = false; + boolean changed; + do { + changed = false; + + for (DirectNode nd : digraph.nodes) { + List exprents = nd.exprents; + + for (int i = 0; i < exprents.size(); i++) { + Exprent ex = exprents.get(i); + + if (ex instanceof AssignmentExprent) { + AssignmentExprent aex = (AssignmentExprent) ex; + Exprent left = aex.getLeft(); + Exprent right = aex.getRight(); + + if (left instanceof VarExprent) { + VarExprent var = (VarExprent) left; + + VarVersionPair vvp = var.getVarVersionPair(); + if (isPureToReplace(right)) { + if (!ssu.nodes.getWithKey(vvp).hasAnySuccessors()) { + exprents.remove(i); + changed = true; + changedAny = true; + +// System.out.println(mt + ": Removed " + var + " = " + right); + i--; + } + } + } + } + } + } + } while (changed); + + StackVarsProcessor.setVersionsToNull(root); + + return changedAny; + } + + private static boolean isPureToReplace(Exprent expr) { + if (expr instanceof ConstExprent) { + return true; + } + + if (expr instanceof FieldExprent) { + FieldExprent field = (FieldExprent) expr; + + return field.isStatic() && field.getClassname().equals("kotlin/Unit"); + } + + return false; + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/pass/JavaFinallyPass.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/pass/JavaFinallyPass.java new file mode 100644 index 0000000000..f0a5ddbf48 --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/pass/JavaFinallyPass.java @@ -0,0 +1,39 @@ +package org.vineflower.kotlin.pass; + +import org.jetbrains.java.decompiler.api.plugin.pass.Pass; +import org.jetbrains.java.decompiler.api.plugin.pass.PassContext; +import org.jetbrains.java.decompiler.code.cfg.ControlFlowGraph; +import org.jetbrains.java.decompiler.modules.decompiler.FinallyProcessor; +import org.jetbrains.java.decompiler.modules.decompiler.decompose.DomHelper; +import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarProcessor; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; + +public class JavaFinallyPass implements Pass { + @Override + public boolean run(PassContext ctx) { + RootStatement root = ctx.getRoot(); + StructMethod mt = ctx.getMethod(); + StructClass cl = ctx.getEnclosingClass(); + VarProcessor varProc = ctx.getVarProc(); + ControlFlowGraph graph = ctx.getGraph(); + MethodDescriptor md = ctx.getMethodDescriptor(); + + FinallyProcessor fProc = new FinallyProcessor(mt, md, varProc); + + boolean res = false; + int iteration = 0; + while (fProc.iterateGraph(cl, mt, root, graph)) { + RootStatement oldRoot = root; + + root = DomHelper.parseGraph(graph, mt, ++iteration); + root.addComments(oldRoot); + + res = true; + } + + return res; + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/pass/KMergePass.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/pass/KMergePass.java new file mode 100644 index 0000000000..7e11eb64bd --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/pass/KMergePass.java @@ -0,0 +1,301 @@ +package org.vineflower.kotlin.pass; + +import org.jetbrains.java.decompiler.api.plugin.pass.Pass; +import org.jetbrains.java.decompiler.api.plugin.pass.PassContext; +import org.jetbrains.java.decompiler.modules.decompiler.MergeHelper; +import org.jetbrains.java.decompiler.modules.decompiler.SequenceHelper; +import org.jetbrains.java.decompiler.modules.decompiler.StatEdge; +import org.jetbrains.java.decompiler.modules.decompiler.exps.*; +import org.jetbrains.java.decompiler.modules.decompiler.stats.DoStatement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.SequenceStatement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.Statement; +import org.jetbrains.java.decompiler.modules.decompiler.vars.CheckTypesResult; +import org.jetbrains.java.decompiler.struct.gen.VarType; + +import java.util.ArrayList; + +public class KMergePass extends MergeHelper implements Pass { + @Override + public boolean run(PassContext ctx) { + RootStatement root = ctx.getRoot(); + + while (enhanceLoopsRec(root)) /**/; + SequenceHelper.condenseSequences(root); + + return false; + } + + private static boolean enhanceLoopsRec(Statement stat) { + boolean res = false; + + for (Statement st : new ArrayList<>(stat.getStats())) { + if (st.getExprents() == null) { + res |= enhanceLoopsRec(st); + } + } + + if (stat instanceof DoStatement) { + res |= enhanceLoop((DoStatement)stat); + } + + return res; + } + + private static boolean enhanceLoop(DoStatement stat) { + DoStatement.Type oldloop = stat.getLooptype(); + + switch (oldloop) { + case INFINITE: + + // identify a while loop + if (matchWhile(stat)) { + if (!matchForEach(stat)) { + matchFor(stat); + } + } + else { + // identify a do{}while loop + //matchDoWhile(stat); + } + + break; + case WHILE: + if (!matchForEach(stat)) { + matchFor(stat); + } + } + + return (stat.getLooptype() != oldloop); + } + + protected static boolean matchForEach(DoStatement stat) { + AssignmentExprent firstDoExprent = null; + AssignmentExprent[] initExprents = new AssignmentExprent[3]; + Statement firstData = null, preData = null, lastData = null; + Exprent lastExprent = null; + + // search for an initializing exprent + Statement current = stat; + while (true) { + Statement parent = current.getParent(); + if (parent == null) { + break; + } + + if (parent instanceof SequenceStatement) { + if (current == parent.getFirst()) { + current = parent; + } + else { + preData = current.getNeighbours(StatEdge.TYPE_REGULAR, Statement.EdgeDirection.BACKWARD).get(0); + preData = getLastDirectData(preData); + if (preData != null && !preData.getExprents().isEmpty()) { + int size = preData.getExprents().size(); + for (int x = 0; x < initExprents.length; x++) { + if (size > x) { + Exprent exprent = preData.getExprents().get(size - 1 - x); + if (exprent instanceof AssignmentExprent) { + initExprents[x] = (AssignmentExprent)exprent; + } + } + } + } + break; + } + } + else { + break; + } + } + + firstData = getFirstDirectData(stat.getFirst()); + if (firstData != null && firstData.getExprents().get(0) instanceof AssignmentExprent) { + firstDoExprent = (AssignmentExprent)firstData.getExprents().get(0); + } + lastData = getLastDirectData(stat.getFirst()); + if (lastData != null && !lastData.getExprents().isEmpty()) { + lastExprent = lastData.getExprents().get(lastData.getExprents().size() - 1); + } + + if (stat.getLooptype() == DoStatement.Type.WHILE && initExprents[0] != null && firstDoExprent != null) { + if (isIteratorCall(initExprents[0].getRight())) { + + InvocationExprent invc = (InvocationExprent)getUncast((initExprents[0]).getRight()); + if (invc.getClassname().contains("java/util/stream")) { + return false; + } + + if (!isHasNextCall(drillNots(stat.getConditionExprent())) || + !(firstDoExprent instanceof AssignmentExprent)) { + return false; + } + + AssignmentExprent ass = firstDoExprent; + if ((!isNextCall(ass.getRight()) && !isNextUnboxing(ass.getRight())) || !(ass.getLeft() instanceof VarExprent)) { + return false; + } + + InvocationExprent next = (InvocationExprent)getUncast(ass.getRight()); + if (isNextUnboxing(next)) + next = (InvocationExprent)getUncast(next.getInstance()); + InvocationExprent hnext = (InvocationExprent)getUncast(drillNots(stat.getConditionExprent())); + if (!(next.getInstance() instanceof VarExprent) || + !(hnext.getInstance() instanceof VarExprent) || + ((VarExprent)initExprents[0].getLeft()).isVarReferenced(stat, (VarExprent)next.getInstance(), (VarExprent)hnext.getInstance())) { + return false; + } + + // Casted foreach + Exprent right = initExprents[0].getRight(); + if (right instanceof FunctionExprent) { + FunctionExprent fRight = (FunctionExprent) right; + if (fRight.getFuncType() == FunctionExprent.FunctionType.CAST) { + right = fRight.getLstOperands().get(0); + } + + if (right instanceof InvocationExprent) { + return false; + } + } + + // Make sure this variable isn't used before + if (isVarUsedBefore((VarExprent) ass.getLeft(), stat)) { + return false; + } + + InvocationExprent holder = (InvocationExprent)right; + + initExprents[0].getBytecodeRange(holder.getInstance().bytecode); + holder.getBytecodeRange(holder.getInstance().bytecode); + firstDoExprent.getBytecodeRange(ass.getLeft().bytecode); + ass.getRight().getBytecodeRange(ass.getLeft().bytecode); + if (stat.getIncExprent() != null) { + stat.getIncExprent().getBytecodeRange(holder.getInstance().bytecode); + } + if (stat.getInitExprent() != null) { + stat.getInitExprent().getBytecodeRange(ass.getLeft().bytecode); + } + + stat.setLooptype(DoStatement.Type.FOR_EACH); + stat.setInitExprent(ass.getLeft()); + stat.setIncExprent(holder.getInstance()); + preData.getExprents().remove(initExprents[0]); + firstData.getExprents().remove(firstDoExprent); + + if (initExprents[1] != null && initExprents[1].getLeft() instanceof VarExprent && + holder.getInstance() instanceof VarExprent) { + VarExprent copy = (VarExprent)initExprents[1].getLeft(); + VarExprent inc = (VarExprent)holder.getInstance(); + if (copy.getIndex() == inc.getIndex() && copy.getVersion() == inc.getVersion() && + !inc.isVarReferenced(stat.getTopParent(), copy) && !isNextCall(initExprents[1].getRight())) { + preData.getExprents().remove(initExprents[1]); + initExprents[1].getBytecodeRange(initExprents[1].getRight().bytecode); + stat.getIncExprent().getBytecodeRange(initExprents[1].getRight().bytecode); + stat.setIncExprent(initExprents[1].getRight()); + } + } + + // Type of assignment- store in var for type calculation + CheckTypesResult typeRes = ass.checkExprTypeBounds(); + if (typeRes != null && !typeRes.getLstMinTypeExprents().isEmpty()) { + VarType boundType = typeRes.getLstMinTypeExprents().get(0).type; + VarExprent var = (VarExprent) ass.getLeft(); + var.setBoundType(boundType); + } + + return true; + } else if (initExprents[1] != null) { + if (!(firstDoExprent.getRight() instanceof ArrayExprent) || !(firstDoExprent.getLeft() instanceof VarExprent)) { + return false; + } + + if (!(lastExprent instanceof FunctionExprent)) { + return false; + } + + // Kotlin: Inverted indices 0 and 1 as kotlinc inverts the cases + + if (!(initExprents[1].getRight() instanceof ConstExprent) || + !(initExprents[0].getRight() instanceof FunctionExprent) || + !(stat.getConditionExprent() instanceof FunctionExprent)) { + return false; + } + + //FunctionExprent funcCond = (FunctionExprent)drillNots(stat.getConditionExprent()); //TODO: Verify this is counter < copy.length + FunctionExprent funcRight = (FunctionExprent)initExprents[0].getRight(); + FunctionExprent funcInc = (FunctionExprent)lastExprent; + ArrayExprent arr = (ArrayExprent)firstDoExprent.getRight(); + FunctionExprent.FunctionType incType = funcInc.getFuncType(); + + if (funcRight.getFuncType() != FunctionExprent.FunctionType.ARRAY_LENGTH || + (incType != FunctionExprent.FunctionType.PPI && incType != FunctionExprent.FunctionType.IPP) || + !(arr.getIndex() instanceof VarExprent) || + !(arr.getArray() instanceof VarExprent)) { + return false; + } + + VarExprent index = (VarExprent)arr.getIndex(); + VarExprent array = (VarExprent)arr.getArray(); + Exprent countExpr = funcInc.getLstOperands().get(0); + + // Foreach over multi dimensional array initializers can cause this to not be a var exprent + if (countExpr instanceof VarExprent) { + VarExprent counter = (VarExprent) countExpr; + + if (counter.getIndex() != index.getIndex() || + counter.getVersion() != index.getVersion()) { + return false; + } + + if (counter.isVarReferenced(stat.getFirst(), index)) { + return false; + } + } + + // Make sure this variable isn't used before + if (isVarUsedBefore((VarExprent) firstDoExprent.getLeft(), stat)) { + return false; + } + + // Add bytecode offsets + funcRight.getLstOperands().get(0).addBytecodeOffsets(initExprents[0].bytecode); + funcRight.getLstOperands().get(0).addBytecodeOffsets(initExprents[1].bytecode); + funcRight.getLstOperands().get(0).addBytecodeOffsets(lastExprent.bytecode); + firstDoExprent.getLeft().addBytecodeOffsets(firstDoExprent.bytecode); + firstDoExprent.getLeft().addBytecodeOffsets(initExprents[0].bytecode); + + stat.setLooptype(DoStatement.Type.FOR_EACH); + stat.setInitExprent(firstDoExprent.getLeft()); + stat.setIncExprent(funcRight.getLstOperands().get(0)); + preData.getExprents().remove(initExprents[1]); + preData.getExprents().remove(initExprents[0]); + firstData.getExprents().remove(firstDoExprent); + lastData.getExprents().remove(lastExprent); + + if (initExprents[2] != null && initExprents[2].getLeft() instanceof VarExprent) { + VarExprent copy = (VarExprent)initExprents[2].getLeft(); + + if (copy.getIndex() == array.getIndex() && copy.getVersion() == array.getVersion()) { + preData.getExprents().remove(initExprents[2]); + initExprents[2].getRight().addBytecodeOffsets(initExprents[2].bytecode); + initExprents[2].getRight().addBytecodeOffsets(stat.getIncExprent().bytecode); + stat.setIncExprent(initExprents[2].getRight()); + } + } + + // Type of assignment- store in var for type calculation + CheckTypesResult typeRes = firstDoExprent.checkExprTypeBounds(); + if (typeRes != null && !typeRes.getLstMinTypeExprents().isEmpty()) { + VarType boundType = typeRes.getLstMinTypeExprents().get(0).type; + VarExprent var = (VarExprent) firstDoExprent.getLeft(); + var.setBoundType(boundType); + } + + return true; + } + } + + return false; + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/pass/ReplaceExprentsPass.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/pass/ReplaceExprentsPass.java new file mode 100644 index 0000000000..285131846a --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/pass/ReplaceExprentsPass.java @@ -0,0 +1,78 @@ +package org.vineflower.kotlin.pass; + +import org.jetbrains.java.decompiler.api.plugin.pass.Pass; +import org.jetbrains.java.decompiler.api.plugin.pass.PassContext; +import org.jetbrains.java.decompiler.modules.decompiler.exps.*; +import org.jetbrains.java.decompiler.modules.decompiler.stats.BasicBlockStatement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.IfStatement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.Statement; +import org.vineflower.kotlin.util.KUtils; + +import java.util.List; + +public class ReplaceExprentsPass implements Pass { + @Override + public boolean run(PassContext ctx) { + return replace(ctx.getRoot()); + } + + private static boolean replace(Statement stat) { + boolean res = false; + + for (Statement st : stat.getStats()) { + res |= replace(st); + } + + List exprs = List.of(); + if (stat instanceof BasicBlockStatement) { + exprs = stat.getExprents(); + } else if (stat instanceof IfStatement) { + exprs = ((IfStatement)stat).getHeadexprentList(); + } + + if (exprs.size() > 0) { + for(int i = 0; i < exprs.size(); i++){ + Exprent expr = exprs.get(i); + Exprent map = KUtils.replaceExprent(expr); + + if (map != null) { + exprs.set(i, map); + res = true; + } + } + + for (Exprent ex : exprs) { + res |= replace(ex); + } + } + + for (int i = 0; i < stat.getVarDefinitions().size(); i++) { + Exprent expr = stat.getVarDefinitions().get(i); + + Exprent map = KUtils.replaceExprent(expr); + if (map != null) { + stat.getVarDefinitions().set(i, map); + + res = true; + } + } + + return res; + } + + private static boolean replace(Exprent expr) { + boolean res = false; + + for (Exprent ex : expr.getAllExprents()) { + res |= replace(ex); + Exprent map = KUtils.replaceExprent(ex); + + if (map != null) { + expr.replaceExprent(ex, map); + res = true; + } + } + + return res; + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/pass/ResugarKotlinMethodsPass.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/pass/ResugarKotlinMethodsPass.java new file mode 100644 index 0000000000..804600793e --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/pass/ResugarKotlinMethodsPass.java @@ -0,0 +1,149 @@ +package org.vineflower.kotlin.pass; + +import org.jetbrains.java.decompiler.api.plugin.pass.Pass; +import org.jetbrains.java.decompiler.api.plugin.pass.PassContext; +import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.FunctionExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.InvocationExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.VarExprent; +import org.jetbrains.java.decompiler.modules.decompiler.flow.DirectGraph; +import org.jetbrains.java.decompiler.modules.decompiler.flow.DirectNode; +import org.jetbrains.java.decompiler.modules.decompiler.flow.FlattenStatementsHelper; +import org.jetbrains.java.decompiler.struct.match.MatchEngine; +import org.vineflower.kotlin.expr.KFunctionExprent; + +import java.util.List; + +public class ResugarKotlinMethodsPass implements Pass { + @Override + public boolean run(PassContext ctx) { + boolean res = false; + + DirectGraph digraph = FlattenStatementsHelper.build(ctx.getRoot()); + + for (DirectNode nd : digraph.nodes) { + List exprs = nd.exprents; + for (Exprent ex : exprs) { + res |= resugarExprs(ex); + } + + for (int i = 0; i < exprs.size(); i++) { + Exprent expr = exprs.get(i); + + ResugarRes exprRes = resugarExpr(expr); + if (exprRes.remove) { + exprs.remove(i); + i--; + res = true; + } else if (exprRes.expr != null) { + exprs.set(i, exprRes.expr); + res = true; + } + } + } + + return res; + } + + private static boolean resugarExprs(Exprent expr) { + boolean res = false; + + for (Exprent ex : expr.getAllExprents()) { + res |= resugarExprs(ex); + + Exprent map = resugarExpr(ex).expr; + + if (map != null) { + expr.replaceExprent(ex, map); + res = true; + } + } + + return res; + } + + + private static final MatchEngine[] NONNULL_INTRINSICS = { + new MatchEngine( + "exprent type:invocation invclass:kotlin/jvm/internal/Intrinsics signature:checkNotNullParameter(Ljava/lang/Object;Ljava/lang/String;)V" + ), + new MatchEngine( + "exprent type:invocation invclass:kotlin/jvm/internal/Intrinsics signature:checkNotNullExpressionValue(Ljava/lang/Object;Ljava/lang/String;)V" + ), + new MatchEngine( + "exprent type:invocation invclass:kotlin/jvm/internal/Intrinsics signature:checkNotNull(Ljava/lang/Object;Ljava/lang/String;)V" + ), + new MatchEngine( + "exprent type:invocation invclass:kotlin/jvm/internal/Intrinsics signature:checkNotNull(Ljava/lang/Object;)V" + ) + }; + + // Intrinsics.areEqual($lhs$, $rhs$) + private static final MatchEngine EQUAL_INTRINSIC = new MatchEngine( + "exprent type:invocation invclass:kotlin/jvm/internal/Intrinsics name:areEqual parameter:0:$lhs$ parameter:1:$rhs$" + ); + + // ($x$ != null) ? $x$ : $y$ + private static final MatchEngine TERNARY_NULL_CHECK = new MatchEngine( + "exprent type:function functype:ternary", + " exprent position:1 ret:$x1$", + " exprent position:2 ret:$y$", + " exprent position:0 type:function functype:neq", + " exprent ret:$x$", + " exprent type:constant consttype:null" + ); + + // Reflection.getOrCreateKotlinClass($class$) + private static final MatchEngine GET_KCLASS = new MatchEngine( + "exprent type:invocation invclass:kotlin/jvm/internal/Reflection name:getOrCreateKotlinClass parameter:0:$class$" + ); + + private static class ResugarRes { + public final Exprent expr; + public final boolean remove; + + public ResugarRes(Exprent expr) { + this(expr, false); + } + + public ResugarRes(Exprent expr, boolean remove) { + this.expr = expr; + this.remove = remove; + } + } + + private static ResugarRes resugarExpr(Exprent ex) { + for (MatchEngine engine : NONNULL_INTRINSICS) { + if (engine.match(ex)) { + return new ResugarRes(null, true); + } + } + + if (EQUAL_INTRINSIC.match(ex)) { + return new ResugarRes(new KFunctionExprent(FunctionExprent.FunctionType.EQ, List.of( + (Exprent) EQUAL_INTRINSIC.getVariableValue("$lhs$"), (Exprent) EQUAL_INTRINSIC.getVariableValue("$rhs$") + ), null), false); + } + + if (TERNARY_NULL_CHECK.match(ex)) { + Exprent innerVal = (Exprent)TERNARY_NULL_CHECK.getVariableValue("$x1$"); + if (innerVal instanceof InvocationExprent && ((InvocationExprent)innerVal).isUnboxingCall()) { + innerVal = ((InvocationExprent)innerVal).getInstance(); + } + + if (innerVal instanceof VarExprent && TERNARY_NULL_CHECK.getVariableValue("$x$").equals(innerVal)) { + return new ResugarRes(new KFunctionExprent(KFunctionExprent.KFunctionType.IF_NULL, List.of( + (Exprent) TERNARY_NULL_CHECK.getVariableValue("$x$"), (Exprent) TERNARY_NULL_CHECK.getVariableValue("$y$") + ), null), false); + } + } + + if (GET_KCLASS.match(ex)) { + return new ResugarRes(new KFunctionExprent(KFunctionExprent.KFunctionType.GET_KCLASS, List.of( + (Exprent) GET_KCLASS.getVariableValue("$class$") + ), null), false); + } + + return new ResugarRes(null); + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/pass/StackVarInitialPass.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/pass/StackVarInitialPass.java new file mode 100644 index 0000000000..95b567ae5d --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/pass/StackVarInitialPass.java @@ -0,0 +1,29 @@ +package org.vineflower.kotlin.pass; + +import org.jetbrains.java.decompiler.api.plugin.pass.Pass; +import org.jetbrains.java.decompiler.api.plugin.pass.PassContext; +import org.jetbrains.java.decompiler.modules.decompiler.PPandMMHelper; +import org.jetbrains.java.decompiler.modules.decompiler.StackVarsProcessor; +import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarProcessor; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructMethod; + +public class StackVarInitialPass implements Pass { + @Override + public boolean run(PassContext ctx) { + RootStatement root = ctx.getRoot(); + StructMethod mt = ctx.getMethod(); + StructClass cl = ctx.getEnclosingClass(); + VarProcessor varProc = ctx.getVarProc(); + + do { + + StackVarsProcessor.simplifyStackVars(root, mt, cl); + + varProc.setVarVersions(root); + } while (new PPandMMHelper(varProc).findPPandMM(root)); + + return true; + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KConstructor.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KConstructor.java new file mode 100644 index 0000000000..6143c53eb9 --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KConstructor.java @@ -0,0 +1,294 @@ +package org.vineflower.kotlin.struct; + +import kotlinx.metadata.internal.metadata.ProtoBuf; +import kotlinx.metadata.internal.metadata.jvm.JvmProtoBuf; +import org.jetbrains.java.decompiler.main.ClassesProcessor; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.collectors.ImportCollector; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.rels.ClassWrapper; +import org.jetbrains.java.decompiler.main.rels.MethodWrapper; +import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.InvocationExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.TypeAnnotation; +import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.InterpreterUtil; +import org.jetbrains.java.decompiler.util.TextBuffer; +import org.vineflower.kotlin.KotlinDecompilationContext; +import org.vineflower.kotlin.KotlinOptions; +import org.vineflower.kotlin.KotlinWriter; +import org.vineflower.kotlin.metadata.MetadataNameResolver; +import org.vineflower.kotlin.util.KUtils; +import org.vineflower.kotlin.util.ProtobufFlags; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class KConstructor { + private static final VarType DEFAULT_CONSTRUCTOR_MARKER = new VarType("kotlin/jvm/internal/DefaultConstructorMarker", true); + + public final ProtobufFlags.Constructor flags; + public final KParameter[] parameters; + public final boolean isPrimary; + + public final MethodWrapper method; + private final ClassesProcessor.ClassNode node; + + private KConstructor( + KParameter[] parameters, + ProtobufFlags.Constructor flags, + MethodWrapper method, + boolean isPrimary, + ClassesProcessor.ClassNode node) { + this.parameters = parameters; + this.flags = flags; + this.method = method; + this.isPrimary = isPrimary; + this.node = node; + } + + public static Data parse(ClassesProcessor.ClassNode node) { + MetadataNameResolver resolver = KotlinDecompilationContext.getNameResolver(); + ClassWrapper wrapper = node.getWrapper(); + StructClass struct = wrapper.getClassStruct(); + + KotlinDecompilationContext.KotlinType type = KotlinDecompilationContext.getCurrentType(); + if (type != KotlinDecompilationContext.KotlinType.CLASS) return null; + + ProtobufFlags.Class classFlags = new ProtobufFlags.Class(KotlinDecompilationContext.getCurrentClass().getFlags()); + if (classFlags.modality == ProtoBuf.Modality.ABSTRACT) return null; + + List protoConstructors = KotlinDecompilationContext.getCurrentClass().getConstructorList(); + if (protoConstructors.isEmpty()) return null; + + Map constructors = new HashMap<>(); + KConstructor primary = null; + + for (ProtoBuf.Constructor constructor : protoConstructors) { + KParameter[] parameters = new KParameter[constructor.getValueParameterCount()]; + for (int i = 0; i < parameters.length; i++) { + ProtoBuf.ValueParameter protoParameter = constructor.getValueParameter(i); + parameters[i] = new KParameter( + new ProtobufFlags.ValueParameter(protoParameter.getFlags()), + resolver.resolve(protoParameter.getName()), + KType.from(protoParameter.getType(), resolver), + KType.from(protoParameter.getVarargElementType(), resolver), + protoParameter.getTypeId() + ); + } + + ProtobufFlags.Constructor flags = new ProtobufFlags.Constructor(constructor.getFlags()); + + JvmProtoBuf.JvmMethodSignature signature = constructor.getExtension(JvmProtoBuf.constructorSignature); + String desc = resolver.resolve(signature.getDesc()); + MethodWrapper method = wrapper.getMethodWrapper("", desc); + if (method == null) { + if (classFlags.kind == ProtoBuf.Class.Kind.ANNOTATION_CLASS) { + // Annotation classes are very odd and don't actually have a constructor under the hood + KConstructor kConstructor = new KConstructor(parameters, flags, null, false, node); + return new Data(null, kConstructor); + } + + DecompilerContext.getLogger().writeMessage("Method " + desc + " not found in " + struct.qualifiedName, IFernflowerLogger.Severity.WARN); + continue; + } + + boolean isPrimary = !flags.isSecondary; + + KConstructor kConstructor = new KConstructor(parameters, flags, method, isPrimary, node); + constructors.put(method.methodStruct, kConstructor); + + if (isPrimary) { + primary = kConstructor; + } + } + + return new Data(constructors, primary); + } + + public boolean stringify(TextBuffer buffer, int indent) { + if (KotlinWriter.hideConstructor(node, true, false, parameters.length, method.methodStruct.getAccessFlags())) { + return false; + } + + TextBuffer buf = new TextBuffer(); + RootStatement root = method.root; + + if (!isPrimary) { + if (flags.hasAnnotations) { + KotlinWriter.appendAnnotations(buf, indent, method.methodStruct, TypeAnnotation.METHOD_RETURN_TYPE); + KotlinWriter.appendJvmAnnotations(buf, indent, method.methodStruct, false, method.classStruct.getPool(), TypeAnnotation.METHOD_RETURN_TYPE); + } + + buf.appendIndent(indent); + + if (flags.visibility != ProtoBuf.Visibility.PUBLIC || DecompilerContext.getOption(KotlinOptions.SHOW_PUBLIC_VISIBILITY)) { + KUtils.appendVisibility(buf, flags.visibility); + } + + buf.append("constructor"); + + buf.append("(").pushNewlineGroup(indent, 1); + + boolean first = true; + for (KParameter parameter : parameters) { + if (!first) { + buf.append(",").appendPossibleNewline(" "); + } + + first = false; + + parameter.stringify(indent + 1, buf); + } + + buf.appendPossibleNewline("", true).popNewlineGroup(); + + String methodDescriptor = method.methodStruct.getName() + method.methodStruct.getDescriptor(); + String containingClass = node.classStruct.qualifiedName; + + List exprents = method.getOrBuildGraph().first.exprents; + if (exprents.isEmpty()) { + DecompilerContext.getLogger().writeMessage("Unexpected empty constructor body in " + containingClass + " " + methodDescriptor, IFernflowerLogger.Severity.WARN); + return true; + } + + buf.append(") "); + + Exprent firstExpr = exprents.get(0); + if (!(firstExpr instanceof InvocationExprent)) { + // no detected super / this constructor call (something isn't right) + DecompilerContext.getLogger().writeMessage("Unexpected missing super/this constructor call in " + containingClass + " " + methodDescriptor, IFernflowerLogger.Severity.WARN); + } else { + buf.append(": "); + + InvocationExprent invocation = (InvocationExprent) firstExpr; + buf.append(invocation.toJava(indent + 1), node.classStruct.qualifiedName, InterpreterUtil.makeUniqueKey(method.methodStruct.getName(), method.methodStruct.getDescriptor())); + + method.getOrBuildGraph().first.exprents.remove(0); + } + } + + if (method.getOrBuildGraph().first.exprents.isEmpty()) { + // There is no extra body so all done! + if (isPrimary) return false; // avoid extra empty line + + buffer.append(buf); + return true; + } + + if (isPrimary) { + buf.appendIndent(indent).append("init"); + } + + buf.append(" {").appendLineSeparator(); + + TextBuffer body = root.toJava(indent + 1); + body.addBytecodeMapping(root.getDummyExit().bytecode); + + StructMethod mt = method.methodStruct; + buf.append(body, node.classStruct.qualifiedName, InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor())); + + buf.appendIndent(indent).append("}").appendLineSeparator(); + + buffer.append(buf); + return true; + } + + public boolean writePrimaryConstructor(TextBuffer buffer, int indent) { + if (!isPrimary) return false; + + TextBuffer buf = new TextBuffer(); + boolean appended = false; + + if (flags.hasAnnotations) { + buf.append(" "); + // -1 for indent indicates inline + KotlinWriter.appendAnnotations(buf, -1, method.methodStruct, TypeAnnotation.METHOD_RETURN_TYPE); + KotlinWriter.appendJvmAnnotations(buf, -1, method.methodStruct, false, method.classStruct.getPool(), TypeAnnotation.METHOD_RETURN_TYPE); + appended = true; + } + + // For cleanliness, public primary constructors are not forced public by the config option + if (flags.visibility != ProtoBuf.Visibility.PUBLIC || (appended && DecompilerContext.getOption(KotlinOptions.SHOW_PUBLIC_VISIBILITY))) { + buf.append(" "); + KUtils.appendVisibility(buf, flags.visibility); + appended = true; + } + + if (appended) { + buf.append("constructor"); + } + + if (parameters.length > 0 || appended) { + buf.append("(").pushNewlineGroup(indent, 1); + + boolean first = true; + for (KParameter parameter : parameters) { + if (!first) { + buf.append(",").appendPossibleNewline(" "); + } + + first = false; + + parameter.stringify(indent + 1, buf); + } + + buf.appendPossibleNewline("", true).popNewlineGroup().append(")"); + } + + RootStatement root = method.root; + if (method.getOrBuildGraph().first.exprents.isEmpty()) { + // No ability to declare super constructor call + buffer.append(buf); + return false; + } + + Exprent firstExpr = method.getOrBuildGraph().first.exprents.get(0); + if (!(firstExpr instanceof InvocationExprent) || !((InvocationExprent) firstExpr).getName().equals("")) { + // no detected super constructor call + buffer.append(buf); + return false; +// throw new IllegalStateException("First expression of constructor is not InvocationExprent"); + } + + InvocationExprent invocation = (InvocationExprent) firstExpr; + if (invocation.getClassname().equals("java/lang/Object")) { + // No need to declare super constructor call + buffer.append(buf); + return false; + } + + ImportCollector imports = DecompilerContext.getImportCollector(); + String superClass = imports.getShortName(invocation.getClassname().replace('/', '.')); + buf.append(" : "); + + // replace "super" with the actual class name + buf.append(superClass).append('('); + + KUtils.removeArguments(invocation, DEFAULT_CONSTRUCTOR_MARKER); + + buf.append(invocation.appendParamList(indent + 1)); + buf.append(")"); + + buf.addBytecodeMapping(invocation.bytecode); + + method.getOrBuildGraph().first.exprents.remove(0); + + buffer.append(buf, node.classStruct.qualifiedName, InterpreterUtil.makeUniqueKey(method.methodStruct.getName(), method.methodStruct.getDescriptor())); + return true; + } + + public static class Data { + public final Map constructors; + public final KConstructor primary; + + public Data(Map constructors, KConstructor primary) { + this.constructors = constructors; + this.primary = primary; + } + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KContract.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KContract.java new file mode 100644 index 0000000000..813361b7aa --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KContract.java @@ -0,0 +1,247 @@ +package org.vineflower.kotlin.struct; + +import kotlinx.metadata.internal.metadata.ProtoBuf; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.util.TextBuffer; +import org.vineflower.kotlin.KotlinWriter; +import org.vineflower.kotlin.metadata.MetadataNameResolver; +import org.vineflower.kotlin.util.ProtobufFlags; + +import java.util.List; +import java.util.stream.Collectors; + +public class KContract { + private static final String INVOCATION_KIND = "kotlin.contracts.InvocationKind"; + @NotNull + public final List effects; + + private KContract(@NotNull List effects) { + this.effects = effects; + } + + public static KContract from(ProtoBuf.Contract proto, List params, MetadataNameResolver nameResolver) { + return new KContract(proto.getEffectList().stream().map(it -> KEffect.from(it, params, nameResolver)).collect(Collectors.toList())); + } + + public TextBuffer stringify(int indent) { + TextBuffer buf = new TextBuffer(); + buf.appendIndent(indent).append("contract {").appendLineSeparator(); + for (KEffect effect : effects) { + effect.stringify(buf, indent + 1); + } + buf.appendIndent(indent).append("}").appendLineSeparator(); + return buf; + } + + public static class KEffect { + @Nullable + public final ProtoBuf.Effect.EffectType type; + @NotNull + public final List expressions; + @Nullable + public final KExpression conditionalConclusion; + @Nullable + public final ProtoBuf.Effect.InvocationKind kind; + + private KEffect( + @Nullable ProtoBuf.Effect.EffectType type, + @NotNull List expressions, + @Nullable KExpression conditionalConclusion, + @Nullable ProtoBuf.Effect.InvocationKind kind) { + this.expressions = expressions; + this.type = type; + this.conditionalConclusion = conditionalConclusion; + this.kind = kind; + } + + static KEffect from(ProtoBuf.Effect proto, List params, MetadataNameResolver nameResolver) { + ProtoBuf.Effect.EffectType type = proto.hasEffectType() ? proto.getEffectType() : null; + List expressions = proto.getEffectConstructorArgumentList().stream().map(it -> KExpression.from(it, params, nameResolver)).collect(Collectors.toList()); + KExpression conditionalConclusion = proto.hasConclusionOfConditionalEffect() ? KExpression.from(proto.getConclusionOfConditionalEffect(), params, nameResolver) : null; + ProtoBuf.Effect.InvocationKind kind = proto.hasKind() ? proto.getKind() : null; + return new KEffect(type, expressions, conditionalConclusion, kind); + } + + public void stringify(TextBuffer buf, int indent) { + if (type == null) return; + + buf.appendIndent(indent); + + switch (type) { + case RETURNS_NOT_NULL: + buf.append("returnsNotNull()"); + if (conditionalConclusion != null) { + buf.append(" implies ("); + conditionalConclusion.stringify(buf, indent, false); + buf.append(')'); + } + break; + case CALLS: + buf.append("callsInPlace("); + KExpression func = expressions.get(0); + String name = func.valueParameterReference.name; + buf.append(KotlinWriter.toValidKotlinIdentifier(name)); + if (kind != null) { + buf.append(", ") + .append(DecompilerContext.getImportCollector().getShortName(INVOCATION_KIND)) + .append(".") + .append(kind.name()); + } + buf.append(")"); + break; + case RETURNS_CONSTANT: + buf.append("returns("); + if (!expressions.isEmpty()) { + KExpression expr = expressions.get(0); + expr.stringify(buf, indent, false); + } + buf.append(")"); + if (conditionalConclusion != null) { + buf.append(" implies ("); + conditionalConclusion.stringify(buf, indent, false); + buf.append(')'); + } + break; + } + buf.appendLineSeparator(); + } + } + + public static class KExpression { + // Placeholder type for receiver type + private static final KParameter THIS_TYPE = new KParameter(new ProtobufFlags.ValueParameter(0), "this", KType.NOTHING, null, 0); + + @NotNull + public final ProtobufFlags.Expression flags; + @Nullable + public final KParameter valueParameterReference; + @Nullable + public final ProtoBuf.Expression.ConstantValue constantValue; + @Nullable + public final KType instanceofType; // isInstanceType + @NotNull + public final List andArguments; + @NotNull + public final List orArguments; + + private KExpression( + @NotNull ProtobufFlags.Expression flags, + @Nullable KParameter valueParameterReference, + @Nullable ProtoBuf.Expression.ConstantValue constantValue, + @Nullable KType instanceofType, + @NotNull List andArguments, + @NotNull List orArguments) { + this.flags = flags; + this.valueParameterReference = valueParameterReference; + this.constantValue = constantValue; + this.instanceofType = instanceofType; + this.andArguments = andArguments; + this.orArguments = orArguments; + } + + static KExpression from(ProtoBuf.Expression proto, List params, MetadataNameResolver nameResolver) { + ProtobufFlags.Expression flags = new ProtobufFlags.Expression(proto.getFlags()); + KParameter valueParameterReference = null; + if (proto.hasValueParameterReference()) { + int index = proto.getValueParameterReference(); + valueParameterReference = index == 0 ? THIS_TYPE : params.get(index - 1); + } + + ProtoBuf.Expression.ConstantValue constantValue = proto.hasConstantValue() ? proto.getConstantValue() : null; + KType instanceofType = null; + if (proto.hasIsInstanceType()) { + instanceofType = KType.from(proto.getIsInstanceType(), nameResolver); + } else if (proto.hasIsInstanceTypeId()) { + instanceofType = KType.from(proto.getIsInstanceTypeId(), nameResolver); + } + List andArguments = proto.getAndArgumentList().stream().map(it -> from(it, params, nameResolver)).collect(Collectors.toList()); + List orArguments = proto.getOrArgumentList().stream().map(it -> from(it, params, nameResolver)).collect(Collectors.toList()); + return new KExpression(flags, valueParameterReference, constantValue, instanceofType, andArguments, orArguments); + } + + public void stringify(TextBuffer buf, int indent, boolean partOfOr) { + if (!andArguments.isEmpty() && (!orArguments.isEmpty() || partOfOr)) { + // all `&&` predicates must be evaluated before any `||` predicates + buf.append('('); + } + + boolean appended = true; + + String paramName = null; + if (valueParameterReference == THIS_TYPE) { + paramName = "this"; + } else if (valueParameterReference != null) { + paramName = KotlinWriter.toValidKotlinIdentifier(valueParameterReference.name); + } + + if (instanceofType != null) { + buf.append(paramName) + .append(' ') + .append(flags.isNegated ? "!is" : "is") + .append(' ') + .append(instanceofType.stringify(indent)); + } else if (flags.isNullPredicate) { + buf.append(paramName) + .append(' ') + .append(flags.isNegated ? "!=" : "==") + .append(' ') + .append("null"); + } else if (constantValue != null) { + if (valueParameterReference != null && valueParameterReference.type.isNullable) { + buf.append(paramName) + .append(' ') + .append(flags.isNegated ? "!=" : "==") + .append(' ') + .append(constantValue.name().toLowerCase()); + } else { + String output = valueParameterReference != null && "kotlin/Boolean".equals(valueParameterReference.type.kotlinType) + ? paramName + : constantValue.name().toLowerCase(); + + if (flags.isNegated) { + buf.append('!'); + } + + buf.append(output); + } + } else if (valueParameterReference != null) { + if (!valueParameterReference.type.kotlinType.equals("kotlin/Boolean")) { + //TODO figure out why this happens + } + if (flags.isNegated) { + buf.append('!'); + } + buf.append(paramName); + } else { + appended = false; + } + + if (!andArguments.isEmpty()) { + for (KExpression andArgument : andArguments) { + if (appended) { + buf.append(" && "); + } + appended = true; + + andArgument.stringify(buf, indent, false); + } + } + + if (!orArguments.isEmpty()) { + if (!andArguments.isEmpty() || partOfOr) { + buf.append(')'); + } + for (KExpression orArgument : orArguments) { + if (appended) { + buf.append(" || "); + } + appended = true; + + orArgument.stringify(buf, indent, true); + } + } + } + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KFunction.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KFunction.java new file mode 100644 index 0000000000..dd481661b9 --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KFunction.java @@ -0,0 +1,372 @@ +package org.vineflower.kotlin.struct; + +import kotlinx.metadata.internal.metadata.ProtoBuf; +import kotlinx.metadata.internal.metadata.jvm.JvmProtoBuf; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.java.decompiler.main.ClassesProcessor; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.rels.ClassWrapper; +import org.jetbrains.java.decompiler.main.rels.MethodWrapper; +import org.jetbrains.java.decompiler.modules.decompiler.exps.TypeAnnotation; +import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.InterpreterUtil; +import org.jetbrains.java.decompiler.util.TextBuffer; +import org.vineflower.kotlin.KotlinDecompilationContext; +import org.vineflower.kotlin.KotlinOptions; +import org.vineflower.kotlin.KotlinWriter; +import org.vineflower.kotlin.metadata.MetadataNameResolver; +import org.vineflower.kotlin.util.KUtils; +import org.vineflower.kotlin.util.ProtobufFlags; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class KFunction { + public final String name; + public final ProtobufFlags.Function flags; + public final List contextReceiverTypes; + public final KParameter[] parameters; + public final List typeParameters; + public final KType returnType; + + public final MethodWrapper method; + + @Nullable + public final KType receiverType; + + @Nullable + public final KContract contract; + + public final boolean knownOverride; + + private final ClassesProcessor.ClassNode node; + + private KFunction( + String name, + KParameter[] parameters, + List typeParameters, + KType returnType, + ProtobufFlags.Function flags, + List contextReceiverTypes, MethodWrapper method, + @Nullable KType receiverType, + @Nullable KContract contract, + boolean knownOverride, + ClassesProcessor.ClassNode node) { + this.name = name; + this.parameters = parameters; + this.typeParameters = typeParameters; + this.returnType = returnType; + this.flags = flags; + this.contextReceiverTypes = contextReceiverTypes; + this.method = method; + this.receiverType = receiverType; + this.contract = contract; + this.knownOverride = knownOverride; + this.node = node; + } + + public static Map parse(ClassesProcessor.ClassNode node) { + MetadataNameResolver resolver = KotlinDecompilationContext.getNameResolver(); + ClassWrapper wrapper = node.getWrapper(); + StructClass struct = wrapper.getClassStruct(); + + List protoFunctions; + + KotlinDecompilationContext.KotlinType type = KotlinDecompilationContext.getCurrentType(); + if (type == null) return Map.of(); + + switch (type) { + case CLASS: + protoFunctions = KotlinDecompilationContext.getCurrentClass().getFunctionList(); + break; + case FILE: + protoFunctions = KotlinDecompilationContext.getFilePackage().getFunctionList(); + break; + case MULTIFILE_CLASS: + protoFunctions = KotlinDecompilationContext.getMultifilePackage().getFunctionList(); + break; + case SYNTHETIC_CLASS: + // indicating lambdas and such + protoFunctions = Collections.singletonList(KotlinDecompilationContext.getSyntheticClass()); + break; + default: + throw new IllegalStateException("Unexpected value: " + type); + } + + Map functions = new HashMap<>(protoFunctions.size(), 1f); + + for (ProtoBuf.Function function : protoFunctions) { + JvmProtoBuf.JvmMethodSignature jvmData = function.getExtension(JvmProtoBuf.methodSignature); + + ProtobufFlags.Function flags = new ProtobufFlags.Function(function.getFlags()); + + String name = resolver.resolve(function.getName()); + + KParameter[] parameters = new KParameter[function.getValueParameterCount()]; + for (int i = 0; i < parameters.length; i++) { + ProtoBuf.ValueParameter parameter = function.getValueParameter(i); + ProtobufFlags.ValueParameter paramFlags = new ProtobufFlags.ValueParameter(parameter.getFlags()); + String paramName = resolver.resolve(parameter.getName()); + KType paramType = KType.from(parameter.getType(), resolver); + KType varargType = parameter.hasVarargElementType() ? KType.from(parameter.getVarargElementType(), resolver) : null; + int typeId = parameter.getTypeId(); + parameters[i] = new KParameter(paramFlags, paramName, paramType, varargType, typeId); + } + + KType receiverType = null; + if (function.hasReceiverType()) { + receiverType = KType.from(function.getReceiverType(), resolver); + } + + KType returnType = KType.from(function.getReturnType(), resolver); + + MethodWrapper method = null; + + String lookupName = jvmData.hasName() ? resolver.resolve(jvmData.getName()) : name; + if (jvmData.hasDesc()) { + method = wrapper.getMethodWrapper(lookupName, resolver.resolve(jvmData.getDesc())); + } + + if (method == null) { + StringBuilder desc = new StringBuilder("("); + if (receiverType != null) { + desc.append(receiverType); + } + + for (KParameter parameter : parameters) { + desc.append(parameter.type); + } + + int endOfParams = desc.length(); + desc.append(")").append(returnType); + + method = wrapper.getMethodWrapper(lookupName, desc.toString()); + + if (method == null) { + throw new IllegalStateException("Couldn't find method " + name + " " + desc + " in class " + struct.qualifiedName); + } + } + + List typeParameters = function.getTypeParameterList().stream() + .map(typeParameter -> KTypeParameter.from(typeParameter, resolver)) + .collect(Collectors.toList()); + + List contextReceiverTypes = function.getContextReceiverTypeList().stream() + .map(ctxType -> KType.from(ctxType, resolver)) + .collect(Collectors.toList()); + + boolean knownOverride = flags.visibility != ProtoBuf.Visibility.PRIVATE + && flags.visibility != ProtoBuf.Visibility.PRIVATE_TO_THIS + && flags.visibility != ProtoBuf.Visibility.LOCAL + && KotlinWriter.searchForMethod(struct, method.methodStruct.getName(), method.desc(), false); + + KContract contract = function.hasContract() ? KContract.from(function.getContract(), List.of(parameters), resolver) : null; + + functions.put(method.methodStruct, new KFunction(name, parameters, typeParameters, returnType, flags, contextReceiverTypes, method, receiverType, contract, knownOverride, node)); + } + + return functions; + } + + public TextBuffer stringify(int indent) { + TextBuffer buf = new TextBuffer(); + KotlinWriter.appendAnnotations(buf, indent, method.methodStruct, TypeAnnotation.METHOD_RETURN_TYPE); + KotlinWriter.appendJvmAnnotations(buf, indent, method.methodStruct, false, method.classStruct.getPool(), TypeAnnotation.METHOD_RETURN_TYPE); + + buf.appendIndent(indent); + + if (!contextReceiverTypes.isEmpty()) { + buf.append("context("); + boolean first = true; + for (KType contextReceiverType : contextReceiverTypes) { + if (!first) { + buf.append(", "); + } + + buf.append(contextReceiverType.stringify(indent + 1)); + first = false; + } + buf.append(")").appendLineSeparator().appendIndent(indent); + } + + if (flags.visibility != ProtoBuf.Visibility.PUBLIC || DecompilerContext.getOption(KotlinOptions.SHOW_PUBLIC_VISIBILITY)) { + KUtils.appendVisibility(buf, flags.visibility); + } + + if (flags.isExpect) { + buf.append("expect "); + } + + if (flags.modality != ProtoBuf.Modality.FINAL) { + if (!knownOverride || flags.modality != ProtoBuf.Modality.OPEN) { + buf.append(flags.modality.name().toLowerCase()) + .append(' '); + } + } + + if (flags.isExternal) { + buf.append("external "); + } + + if (knownOverride) { + buf.append("override "); + } + + if (flags.isTailrec) { + buf.append("tailrec "); + } + + if (flags.isSuspend) { + buf.append("suspend "); + } + + if (flags.isInline) { + buf.append("inline "); + } + + if (flags.isInfix) { + buf.append("infix "); + } + + if (flags.isOperator) { + buf.append("operator "); + } + + buf.append("fun "); + + List complexTypeParams = typeParameters.stream() + .filter(typeParameter -> typeParameter.upperBounds.size() > 1) + .collect(Collectors.toList()); + + Map typeParamsById = typeParameters.stream() + .collect(Collectors.toMap(typeParameter -> typeParameter.id, Function.identity())); + + if (!typeParameters.isEmpty()) { + buf.append('<'); + + for (int i = 0; i < typeParameters.size(); i++) { + KTypeParameter typeParameter = typeParameters.get(i); + + if (typeParameter.reified) { + buf.append("reified "); + } + + buf.append(KotlinWriter.toValidKotlinIdentifier(typeParameter.name)); + + if (typeParameter.upperBounds.size() == 1) { + buf.append(" : ").append(typeParameter.upperBounds.get(0).stringify(indent + 1)); + } + + if (i < typeParameters.size() - 1) { + buf.append(", "); + } + } + + buf.append("> "); + } + + if (receiverType != null) { + // Function types need parentheses around the receiver type, but that happens in KType.stringify only if it's nullable + // so we need to wrap in the case of non-nullable function types + if (!receiverType.isNullable && receiverType.kotlinType.startsWith("kotlin/Function")) { + buf.append("("); + } + buf.append(receiverType.stringify(indent + 1)); + if (!receiverType.isNullable && receiverType.kotlinType.startsWith("kotlin/Function")) { + buf.append(")"); + } + + buf.append("."); + } + + buf.append(KotlinWriter.toValidKotlinIdentifier(name)) + .append('(') + .pushNewlineGroup(indent, 1) + .appendPossibleNewline(""); + + boolean first = true; + for (KParameter parameter : parameters) { + if (!first) { + buf.append(",").appendPossibleNewline(" "); + } + + first = false; + + parameter.stringify(indent + 1, buf); + } + + buf.appendPossibleNewline("", true) + .popNewlineGroup() + .append(')'); + + if (returnType != null && returnType.type != VarType.VARTYPE_VOID.type) { + buf.append(": ") + .append(returnType.stringify(indent + 1)); + } + + if (complexTypeParams.isEmpty()) { + buf.append(' '); + } else { + buf.pushNewlineGroup(indent, 1) + .appendPossibleNewline(" ") + .append("where "); + + first = true; + for (KTypeParameter typeParameter : complexTypeParams) { + for (KType upperBound : typeParameter.upperBounds) { + if (!first) { + buf.appendPossibleNewline(",").appendPossibleNewline(" "); + } + + buf.append(KotlinWriter.toValidKotlinIdentifier(typeParameter.name)) + .append(" : ") + .append(upperBound.stringify(indent + 1)); + + first = false; + } + } + + buf.appendPossibleNewline(" ", true) + .popNewlineGroup(); + } + + buf.append('{').appendLineSeparator(); + + if (contract != null) { + buf.append(contract.stringify(indent + 1)); + } + + RootStatement root = method.root; + if (root != null && method.decompileError == null) { + try { + TextBuffer body = root.toJava(indent + 1); + body.addBytecodeMapping(root.getDummyExit().bytecode); + if (body.length() != 0 && contract != null) { + buf.appendLineSeparator(); + } + + buf.append(body, node.classStruct.qualifiedName, InterpreterUtil.makeUniqueKey(method.methodStruct.getName(), method.methodStruct.getDescriptor())); + } catch (Throwable t) { + String message = "Method " + method.methodStruct.getName() + " " + method.desc() + " in class " + node.classStruct.qualifiedName + " couldn't be written."; + DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN, t); + method.decompileError = t; + } + } + + if (method.decompileError != null) { + KotlinWriter.dumpError(buf, method, indent + 1); + } + + buf.appendIndent(indent).append('}').appendLineSeparator(); + + return buf; + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KParameter.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KParameter.java new file mode 100644 index 0000000000..cd4eb4af9b --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KParameter.java @@ -0,0 +1,43 @@ +package org.vineflower.kotlin.struct; + +import org.jetbrains.java.decompiler.util.TextBuffer; +import org.vineflower.kotlin.KotlinWriter; +import org.vineflower.kotlin.util.ProtobufFlags; + +public class KParameter { + public final ProtobufFlags.ValueParameter flags; + public final String name; + public final KType type; + public final KType varargType; + public final int typeId; + + KParameter(ProtobufFlags.ValueParameter flags, String name, KType type, KType varargType, int typeId) { + this.flags = flags; + this.name = name; + this.type = type; + this.varargType = varargType; + this.typeId = typeId; + } + + public void stringify(int indent, TextBuffer buf) { + if (flags.isCrossinline) { + buf.append("crossinline "); + } + + if (flags.isNoinline) { + buf.append("noinline "); + } + + // Vararg types are a bit odd to say the least + boolean isVararg = varargType != null && type.kotlinType.equals("kotlin/Array"); + + if (isVararg) { + buf.append("vararg "); + } + + buf.append(KotlinWriter.toValidKotlinIdentifier(name)).append(": "); + + KType type = isVararg ? varargType : this.type; + buf.append(type.stringify(indent + 1)); + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KProperty.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KProperty.java new file mode 100644 index 0000000000..aed36a8be6 --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KProperty.java @@ -0,0 +1,372 @@ +package org.vineflower.kotlin.struct; + +import kotlinx.metadata.internal.metadata.ProtoBuf; +import kotlinx.metadata.internal.metadata.jvm.JvmProtoBuf; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.main.ClassesProcessor; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.rels.ClassWrapper; +import org.jetbrains.java.decompiler.main.rels.MethodWrapper; +import org.jetbrains.java.decompiler.modules.decompiler.exps.ConstExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructField; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.attr.StructConstantValueAttribute; +import org.jetbrains.java.decompiler.struct.attr.StructGeneralAttribute; +import org.jetbrains.java.decompiler.struct.consts.PrimitiveConstant; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.InterpreterUtil; +import org.jetbrains.java.decompiler.util.TextBuffer; +import org.jetbrains.java.decompiler.util.collections.VBStyleCollection; +import org.vineflower.kotlin.KotlinDecompilationContext; +import org.vineflower.kotlin.KotlinOptions; +import org.vineflower.kotlin.KotlinWriter; +import org.vineflower.kotlin.metadata.MetadataNameResolver; +import org.vineflower.kotlin.util.KTypes; +import org.vineflower.kotlin.util.KUtils; +import org.vineflower.kotlin.util.ProtobufFlags; + +import java.util.*; + +public final class KProperty { + public final String name; + public final KType type; + + public final ProtobufFlags.Property flags; + + @Nullable + public final KPropertyAccessor getter; + + @Nullable + public final KPropertyAccessor setter; + + @Nullable + public final String setterParamName; + + @Nullable + public final StructField underlyingField; + + @Nullable + public final Exprent initializer; + + private final ClassesProcessor.ClassNode node; + + private KProperty( + String name, + KType type, + ProtobufFlags.Property flags, + KPropertyAccessor getter, + KPropertyAccessor setter, + @Nullable String setterParamName, StructField underlyingField, + Exprent initializer, + ClassesProcessor.ClassNode node) { + this.name = name; + this.type = type; + this.flags = flags; + this.getter = getter; + this.setter = setter; + this.setterParamName = setterParamName; + this.underlyingField = underlyingField; + this.initializer = initializer; + this.node = node; + } + + public TextBuffer stringify(int indent) { + TextBuffer buf = new TextBuffer(); + + buf.appendIndent(indent); + + // Modifiers in the order that Kotlin's coding conventions specify + KUtils.appendVisibility(buf, flags.visibility); + + if (flags.isExpect) { + buf.append("expect "); + } + + if (Objects.requireNonNull(flags.modality) == ProtoBuf.Modality.FINAL) { + buf.append(flags.isConst ? "const " : "final "); + } else if (!node.classStruct.hasModifier(CodeConstants.ACC_INTERFACE) || flags.modality != ProtoBuf.Modality.ABSTRACT) { + buf.append(flags.modality.name().toLowerCase()) + .append(' '); + } + + if (flags.isExternal) { + buf.append("external "); + } + + if (flags.isLateinit) { + buf.append("lateinit "); + } + + buf.append(flags.isVar ? "var " : "val ") + .append(KotlinWriter.toValidKotlinIdentifier(name)) + .append(": ") + .append(type.stringify(indent)); + + if (initializer != null) { + TextBuffer initializerBuf = initializer.toJava(indent); + initializerBuf.clearUnassignedBytecodeMappingData(); + if (flags.isDelegated) { + buf.append(" by ") + .append(initializerBuf); + } else { + buf.append(" =") + .pushNewlineGroup(indent, 1) + .appendPossibleNewline(" ") + .append(initializerBuf) + .popNewlineGroup(); + } + } + + // Custom getters and setters, and possible modifier differences + if (getter != null && getter.flags.isNotDefault) { + buf.pushNewlineGroup(indent, 1) + .appendLineSeparator() + .appendIndent(indent + 1); + + KUtils.appendVisibility(buf, getter.flags.visibility); + + buf.append(getter.flags.modality.name().toLowerCase()) + .append(' '); + + if (getter.flags.isExternal) { + buf.append("external "); + } + + if (getter.flags.isInline) { + buf.append("inline "); + } + + buf.append("get() "); + + KotlinWriter.writeMethodBody(node, getter.underlyingMethod, buf, indent + 1, false); + + buf.popNewlineGroup(); + } else if (getter != null && getter.flags.isExternal) { + buf.appendLineSeparator() + .appendIndent(indent + 1) + .append("external get"); + } + + if (setter != null && setter.flags.isNotDefault) { + buf.pushNewlineGroup(indent, 1) + .appendLineSeparator() + .appendIndent(indent + 1); + + KUtils.appendVisibility(buf, getter.flags.visibility); + + buf.append(setter.flags.modality.name().toLowerCase()) + .append(' '); + + if (setter.flags.isExternal) { + buf.append("external "); + } + + if (setter.flags.isInline) { + buf.append("inline "); + } + + buf.append("set(") + .append(setterParamName) + .append(") "); + + KotlinWriter.writeMethodBody(node, setter.underlyingMethod, buf, indent + 1, false); + + buf.popNewlineGroup(); + } else if (setter != null && (setter.flags.isExternal || setter.flags.visibility != flags.visibility || setter.flags.modality != flags.modality)) { + buf.appendLineSeparator().appendIndent(indent + 1); + + if (setter.flags.visibility != flags.visibility) { + KUtils.appendVisibility(buf, setter.flags.visibility); + } + + if (setter.flags.modality != flags.modality) { + buf.append(setter.flags.modality.name().toLowerCase()) + .append(' '); + } + + if (setter.flags.isExternal) { + buf.append("external "); + } + + buf.append("set"); + } else if (setter == null && flags.isVar && flags.visibility != ProtoBuf.Visibility.PRIVATE) { // Special case: no setter is generated if it's a var with a private setter + buf.appendLineSeparator() + .appendIndent(indent + 1) + .append("private set"); + } + + buf.appendLineSeparator(); + + return buf; + } + + private static void appendVisibility(TextBuffer buf, ProtoBuf.Visibility visibility) { + switch (visibility) { + case LOCAL: + buf.append("// QF: local property") + .appendLineSeparator() + .append("internal "); + break; + case PRIVATE_TO_THIS: + buf.append("private "); + break; + case PUBLIC: + if (DecompilerContext.getOption(KotlinOptions.SHOW_PUBLIC_VISIBILITY)) { + buf.append("public "); + } + break; + default: + buf.append(visibility.name().toLowerCase()) + .append(' '); + } + } + + public static @Nullable Data parse(ClassesProcessor.ClassNode node) { + MetadataNameResolver nameResolver = KotlinDecompilationContext.getNameResolver(); + ClassWrapper wrapper = node.getWrapper(); + StructClass structClass = wrapper.getClassStruct(); + + List protoProperties; + + KotlinDecompilationContext.KotlinType currentType = KotlinDecompilationContext.getCurrentType(); + if (currentType == null) return null; + + switch (currentType) { + case CLASS: + protoProperties = KotlinDecompilationContext.getCurrentClass().getPropertyList(); + break; + case FILE: + protoProperties = KotlinDecompilationContext.getFilePackage().getPropertyList(); + break; + case MULTIFILE_CLASS: + protoProperties = KotlinDecompilationContext.getMultifilePackage().getPropertyList(); + break; + case SYNTHETIC_CLASS: + return null; // No property information in synthetic classes + default: + throw new IllegalStateException("Unexpected value: " + KotlinDecompilationContext.getCurrentType()); + } + + List properties = new ArrayList<>(); + Set associatedFields = new HashSet<>(); + Set associatedMethods = new HashSet<>(); + + for (ProtoBuf.Property property : protoProperties) { + JvmProtoBuf.JvmPropertySignature jvmProp = property.getExtension(JvmProtoBuf.propertySignature); + + ProtobufFlags.Property flags = new ProtobufFlags.Property(property.getFlags()); + + String name = nameResolver.resolve(property.getName()); + + String propDesc = null; + KType type = null; + if (property.hasReturnType()) { + type = KType.from(property.getReturnType(), nameResolver); + propDesc = KTypes.getJavaSignature(type.kotlinType, property.getReturnType().getNullable()); + } + + // Delegates create a hidden field containing the created delegate, so reference that instead + Exprent delegateExprent = null; + if (flags.isDelegated) { + String delegateFieldName = nameResolver.resolve(jvmProp.getField().getName()); + String delegateDesc = nameResolver.resolve(jvmProp.getField().getDesc()); + StructField delegateField = structClass.getField(delegateFieldName, delegateDesc); + if (delegateField != null) { + associatedFields.add(delegateFieldName); + String key = InterpreterUtil.makeUniqueKey(delegateFieldName, delegateDesc); + if (delegateField.hasModifier(CodeConstants.ACC_STATIC)) { + delegateExprent = wrapper.getStaticFieldInitializers().getWithKey(key); + } else { + delegateExprent = wrapper.getDynamicFieldInitializers().getWithKey(key); + } + } + } + + KPropertyAccessor getter = null; + if (flags.hasGetter) { + String methodName = nameResolver.resolve(jvmProp.getGetter().getName()); + String desc = nameResolver.resolve(jvmProp.getGetter().getDesc()); + StructMethod method = structClass.getMethod(methodName, desc); + if (method != null) { + MethodWrapper methodWrapper = wrapper.getMethodWrapper(methodName, desc); + getter = new KPropertyAccessor(new ProtobufFlags.PropertyAccessor(property.getGetterFlags()), methodWrapper); + associatedMethods.add(InterpreterUtil.makeUniqueKey(methodName, desc)); + + if (propDesc == null) { + propDesc = method.getDescriptor().substring(method.getDescriptor().indexOf(')') + 1); + } + } + } + + KPropertyAccessor setter = null; + String setterParamName = null; + if (flags.hasSetter) { + String methodName = nameResolver.resolve(jvmProp.getSetter().getName()); + String desc = nameResolver.resolve(jvmProp.getSetter().getDesc()); + StructMethod method = structClass.getMethod(methodName, desc); + if (method != null) { + MethodWrapper methodWrapper = wrapper.getMethodWrapper(methodName, desc); + setter = new KPropertyAccessor(new ProtobufFlags.PropertyAccessor(property.getSetterFlags()), methodWrapper); + associatedMethods.add(InterpreterUtil.makeUniqueKey(methodName, desc)); + setterParamName = nameResolver.resolve(property.getSetterValueParameter().getName()); + } + } + + StructField field = null; + if (propDesc != null) { + field = structClass.getField(name, propDesc); + if (field != null) { + associatedFields.add(name); + } + } else { + VBStyleCollection fields = structClass.getFields(); + for (StructField f : fields) { + if (f.getName().equals(name)) { + field = f; + propDesc = f.getDescriptor(); + associatedFields.add(name); + break; + } + } + } + + VarType varType = propDesc != null ? new VarType(propDesc) : VarType.VARTYPE_OBJECT; + + String key = InterpreterUtil.makeUniqueKey(name, varType.toString()); + Exprent initializer; + + if (delegateExprent != null) { + initializer = delegateExprent; + } else if (field == null) { + initializer = null; + } else if (field.hasAttribute(StructGeneralAttribute.ATTRIBUTE_CONSTANT_VALUE)) { + StructConstantValueAttribute attr = field.getAttribute(StructGeneralAttribute.ATTRIBUTE_CONSTANT_VALUE); + PrimitiveConstant constant = structClass.getPool().getPrimitiveConstant(attr.getIndex()); + initializer = new ConstExprent(varType, constant.value, null); + } else if (field.hasModifier(CodeConstants.ACC_STATIC)) { + initializer = wrapper.getStaticFieldInitializers().getWithKey(key); + } else { + initializer = wrapper.getDynamicFieldInitializers().getWithKey(key); + } + + properties.add(new KProperty(name, type, flags, getter, setter, setterParamName, field, initializer, node)); + } + + return new Data(properties, associatedFields, associatedMethods); + } + + public static class Data { + public final List properties; + public final Set associatedFields; + public final Set associatedMethods; + + public Data(List properties, Set associatedFields, Set associatedMethods) { + this.properties = properties; + this.associatedFields = associatedFields; + this.associatedMethods = associatedMethods; + } + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KPropertyAccessor.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KPropertyAccessor.java new file mode 100644 index 0000000000..d6dd8510dc --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KPropertyAccessor.java @@ -0,0 +1,15 @@ +package org.vineflower.kotlin.struct; + +import org.jetbrains.java.decompiler.main.rels.MethodWrapper; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.vineflower.kotlin.util.ProtobufFlags; + +public class KPropertyAccessor { + public final ProtobufFlags.PropertyAccessor flags; + public final MethodWrapper underlyingMethod; + + public KPropertyAccessor(ProtobufFlags.PropertyAccessor flags, MethodWrapper underlyingMethod) { + this.flags = flags; + this.underlyingMethod = underlyingMethod; + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KType.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KType.java new file mode 100644 index 0000000000..5d6f4728bb --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KType.java @@ -0,0 +1,194 @@ +package org.vineflower.kotlin.struct; + +import kotlinx.metadata.internal.metadata.ProtoBuf; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.TextBuffer; +import org.vineflower.kotlin.KotlinDecompilationContext; +import org.vineflower.kotlin.metadata.MetadataNameResolver; +import org.vineflower.kotlin.util.KTypes; + +import java.util.Objects; + +public class KType extends VarType { + public static final KType UNIT = new KType(VARTYPE_VOID, "kotlin/Unit", false, null, null, null); + public static final KType NOTHING = new KType(new VarType("java/lang/Void", true), "kotlin/Nothing", false, null, null, null); + + public final String kotlinType; + public final boolean isNullable; + public final TypeArgument @Nullable [] typeArguments; + + @Nullable + public final String typeParameterName; + + @Nullable + public final String typeAliasName; + + private KType( + VarType type, + String kotlinType, + boolean isNullable, + TypeArgument @Nullable [] typeArguments, + @Nullable String typeParameterName, + @Nullable String typeAliasName) { + super(type.type, type.arrayDim, type.value, type.typeFamily, type.stackSize); + this.kotlinType = kotlinType; + this.isNullable = isNullable; + this.typeArguments = typeArguments; + this.typeParameterName = typeParameterName; + this.typeAliasName = typeAliasName; + } + + public static KType from(ProtoBuf.Type type, MetadataNameResolver nameResolver) { + String kotlinType = type.hasClassName() ? nameResolver.resolve(type.getClassName()) : "kotlin/Any"; + boolean isNullable = type.getNullable(); + + // short-circuit for `Unit` + if ("kotlin/Unit".equals(kotlinType) && !isNullable) { + return UNIT; + } + + // similar short-circuit for `Nothing` + if ("kotlin/Nothing".equals(kotlinType) && !isNullable) { + return NOTHING; + } + + TypeArgument[] typeArguments = null; + if (type.getArgumentCount() > 0) { + typeArguments = new TypeArgument[type.getArgumentCount()]; + for (int i = 0; i < type.getArgumentCount(); i++) { + ProtoBuf.Type.Argument argument = type.getArgument(i); + typeArguments[i] = new TypeArgument(from(argument.getType(), nameResolver), argument.getProjection()); + } + } + + String jvmDesc = KTypes.getJavaSignature(kotlinType, isNullable); + if ("kotlin/Array".equals(kotlinType)) { + TypeArgument arrayType = Objects.requireNonNull(typeArguments)[0]; + if (arrayType.typeProjection == ProtoBuf.Type.Argument.Projection.IN) { + jvmDesc = "[Ljava/lang/Object;"; // `in` variance is erased to `Object` (to allow any supertype to be passed in) + } else { + jvmDesc = "[" + arrayType.type.toString(); + } + } + VarType varType = new VarType(jvmDesc); + + String typeParameterName = type.hasTypeParameterName() ? nameResolver.resolve(type.getTypeParameterName()) : null; + String typeAliasName = type.hasTypeAliasName() ? nameResolver.resolve(type.getTypeAliasName()) : null; + + return new KType(varType, kotlinType, isNullable, typeArguments, typeParameterName, typeAliasName); + } + + public static KType from(int tableIndex, MetadataNameResolver resolver) { + ProtoBuf.TypeTable table; + switch (KotlinDecompilationContext.getCurrentType()) { + case CLASS: + table = KotlinDecompilationContext.getCurrentClass().getTypeTable(); + break; + case SYNTHETIC_CLASS: + table = KotlinDecompilationContext.getSyntheticClass().getTypeTable(); + break; + case FILE: + table = KotlinDecompilationContext.getFilePackage().getTypeTable(); + break; + case MULTIFILE_CLASS: + table = KotlinDecompilationContext.getMultifilePackage().getTypeTable(); + break; + default: + throw new IllegalStateException("No decompilation context found"); + } + + return from(table.getType(tableIndex), resolver); + } + + // stringify is for the decompiler output + public TextBuffer stringify(int indent) { + TextBuffer buf = new TextBuffer(); + + if (typeParameterName != null) { + buf.append(typeParameterName); + if (isNullable) { + buf.append("?"); + } + + return buf; + } + + if (!kotlinType.startsWith("kotlin/Function")) { // Non-functions are essentially equivalent to Java generic types + String type = kotlinType.replace('/', '.'); + buf.append(DecompilerContext.getImportCollector().getShortName(type)); + + if (typeArguments != null) { + buf.append("<"); + buf.pushNewlineGroup(indent, 1); + buf.appendPossibleNewline(); + boolean first = true; + for (TypeArgument typeArgument : typeArguments) { + if (!first) { + buf.append(",") + .appendPossibleNewline(" "); + } + buf.append(typeArgument.stringify(indent + 1)); + first = false; + } + buf.appendPossibleNewline("", true); + buf.append(">"); + buf.popNewlineGroup(); + + } + + if (isNullable) { + buf.append("?"); + } + } else { // Metadata-defined function types always have all param types listed instead of defaulting to `FunctionN` + buf.append(isNullable ? "((" : "("); + buf.pushNewlineGroup(indent, 1); + buf.appendPossibleNewline(); + for (int i = 0; i < typeArguments.length - 1; i++) { + if (i != 0) { + buf.append(",").appendPossibleNewline(" "); + } + buf.append(typeArguments[i].stringify(indent + 1)); + } + buf.appendPossibleNewline("", true); + buf.popNewlineGroup(); + buf.append(") -> "); + buf.append(typeArguments[typeArguments.length - 1].stringify(indent + 1)); + if (isNullable) { + buf.append(")?"); + } + } + + return buf; + } + + public static class TypeArgument { + public final KType type; + public final ProtoBuf.Type.Argument.Projection typeProjection; + + public TypeArgument(KType type, ProtoBuf.Type.Argument.Projection typeProjection) { + this.type = type; + this.typeProjection = typeProjection; + } + + public TextBuffer stringify(int indent) { + TextBuffer buf = new TextBuffer(); + switch (typeProjection) { + case IN: + buf.append("in "); + break; + case OUT: + buf.append("out "); + break; + case STAR: + buf.append("*"); + return buf; + case INV: + break; + } + buf.append(type.stringify(indent)); + return buf; + } + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KTypeParameter.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KTypeParameter.java new file mode 100644 index 0000000000..e802a58088 --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/struct/KTypeParameter.java @@ -0,0 +1,38 @@ +package org.vineflower.kotlin.struct; + +import kotlinx.metadata.internal.metadata.ProtoBuf; +import org.vineflower.kotlin.metadata.MetadataNameResolver; + +import java.util.List; +import java.util.stream.Collectors; + +public class KTypeParameter { + public final boolean reified; + public final ProtoBuf.TypeParameter.Variance variance; + public final List upperBounds; + public final String name; + public final int id; + + private KTypeParameter( + boolean reified, + ProtoBuf.TypeParameter.Variance variance, + List upperBounds, + String name, + int id) { + this.reified = reified; + this.variance = variance; + this.upperBounds = upperBounds; + this.name = name; + this.id = id; + } + + public static KTypeParameter from(ProtoBuf.TypeParameter proto, MetadataNameResolver resolver) { + return new KTypeParameter( + proto.getReified(), + proto.getVariance(), + proto.getUpperBoundList().stream().map(type -> KType.from(type, resolver)).collect(Collectors.toList()), + resolver.resolve(proto.getName()), + proto.getId() + ); + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/util/KTypes.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/util/KTypes.java new file mode 100644 index 0000000000..8d698c3cfd --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/util/KTypes.java @@ -0,0 +1,286 @@ +package org.vineflower.kotlin.util; + +import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.struct.gen.generics.GenericType; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class KTypes { + private static final int MAX_KOTLIN_FUNCTION_ARITY = 22; + + private static final Pattern GENERICS_PATTERN = Pattern.compile("(.+)<(.+)>"); + private static final Map JAVA_CLASS_TRANSLATIONS = Map.of( + "java/lang/Integer", "Int", + "java/lang/Object", "Any" + ); + + private static final Map JAVA_GENERIC_TRANSLATIONS = Map.of( + "java/util/Map", "MutableMap", + "java/util/HashMap", "HashMap", + "java/util/List", "MutableList", + "java/util/ArrayList", "ArrayList", + "java/util/Set", "MutableSet", + "java/util/HashSet", "HashSet", + "java/util/Collection", "MutableCollection", + "java/util/Iterator", "MutableIterator", + "java/util/Map$Entry", "MutableMap.MutableEntry", + "java/lang/Iterable", "MutableIterable" + ); + + public static final Map KOTLIN_TO_JAVA_LANG = Map.ofEntries( + Map.entry("kotlin/Any", "java/lang/Object"), + Map.entry("kotlin/Boolean", "java/lang/Boolean"), + Map.entry("kotlin/Byte", "java/lang/Byte"), + Map.entry("kotlin/Char", "java/lang/Character"), + Map.entry("kotlin/CharSequence", "java/lang/CharSequence"), + Map.entry("kotlin/Comparable", "java/lang/Comparable"), + Map.entry("kotlin/Double", "java/lang/Double"), + Map.entry("kotlin/Enum", "java/lang/Enum"), + Map.entry("kotlin/Float", "java/lang/Float"), + Map.entry("kotlin/Int", "java/lang/Integer"), + Map.entry("kotlin/Long", "java/lang/Long"), + Map.entry("kotlin/Nothing", "java/lang/Void"), + Map.entry("kotlin/Number", "java/lang/Number"), + Map.entry("kotlin/Short", "java/lang/Short"), + Map.entry("kotlin/String", "java/lang/String"), + Map.entry("kotlin/Throwable", "java/lang/Throwable"), + Map.entry("kotlin/collections/MutableIterable", "java/lang/Iterable"), + Map.entry("kotlin/collections/Iterable", "java/lang/Iterable") + ); + + public static final Map KOTLIN_TO_JAVA_UTIL = Map.ofEntries( + Map.entry("kotlin/collections/MutableCollection", "java/util/Collection"), + Map.entry("kotlin/collections/MutableMap", "java/util/Map"), + Map.entry("kotlin/collections/MutableList", "java/util/List"), + Map.entry("kotlin/collections/MutableSet", "java/util/Set"), + Map.entry("kotlin/collections/MutableIterator", "java/util/Iterator"), + Map.entry("kotlin/collections/MutableListIterator", "java/util/ListIterator"), + Map.entry("kotlin/collections/MutableMap$MutableEntry", "java/util/Map$Entry"), + Map.entry("kotlin/collections/Collection", "java/util/Collection"), + Map.entry("kotlin/collections/Map", "java/util/Map"), + Map.entry("kotlin/collections/List", "java/util/List"), + Map.entry("kotlin/collections/ListIterator", "java/util/ListIterator"), + Map.entry("kotlin/collections/Set", "java/util/Set"), + Map.entry("kotlin/collections/Iterator", "java/util/Iterator"), + Map.entry("kotlin/collections/Map$Entry", "java/util/Map$Entry") + ); + + private static final Map KOTLIN_PRIMITIVE_TYPES = Map.of( + "kotlin/Int", "I", + "kotlin/Long", "J", + "kotlin/Short", "S", + "kotlin/Byte", "B", + "kotlin/Boolean", "Z", + "kotlin/Char", "C", + "kotlin/Float", "F", + "kotlin/Double", "D", + "kotlin/Unit", "V" + ); + + public static String getJavaSignature(String kotlinType, boolean isNullable) { + if (kotlinType.startsWith("L") && kotlinType.endsWith(";")) { + kotlinType = kotlinType.substring(1, kotlinType.length() - 1); + } + + // Kotlin uses . instead of $ when referring to inner classes in metadata but keeps $ in the bytecode + if (kotlinType.contains(".")) { + kotlinType = kotlinType.replace('.', '$'); + } + + if (kotlinType.startsWith("kotlin/")) { + if (KOTLIN_PRIMITIVE_TYPES.containsKey(kotlinType) && !isNullable) { + return KOTLIN_PRIMITIVE_TYPES.get(kotlinType); + } else if (kotlinType.endsWith("$Companion") && KOTLIN_PRIMITIVE_TYPES.containsKey(kotlinType.replace("$Companion", ""))) { + return "Lkotlin/jvm/internal/" + kotlinType.split("/")[1].split("\\$")[0] + "CompanionObject;"; + } else if (kotlinType.endsWith("Array") && KOTLIN_PRIMITIVE_TYPES.containsKey(kotlinType.replace("Array", ""))) { + return "[" + KOTLIN_PRIMITIVE_TYPES.get(kotlinType.replace("Array", "")); + } else if (KOTLIN_TO_JAVA_LANG.containsKey(kotlinType)) { + return "L" + KOTLIN_TO_JAVA_LANG.get(kotlinType) + ";"; + } else if (KOTLIN_TO_JAVA_UTIL.containsKey(kotlinType)) { + return "L" + KOTLIN_TO_JAVA_UTIL.get(kotlinType) + ";"; + } else if (kotlinType.startsWith("kotlin/Function")) { + try { + if (Integer.parseInt(kotlinType.substring("kotlin/Function".length())) > MAX_KOTLIN_FUNCTION_ARITY) { + return "Lkotlin/jvm/functions/FunctionN;"; + } + return "Lkotlin/jvm/functions" + kotlinType.substring("kotlin".length()) + ";"; + } catch (NumberFormatException e) { + // Not a function type with arity + } + } + } + return "L" + kotlinType + ";"; + } + + public static String getKotlinType(VarType type) { + return getKotlinType(type, true); + } + + public static String getKotlinType(VarType type, boolean includeOuterClasses) { + if (type == null) { + //TODO: prevent passing null + return "*"; + } + + String typeStr; + if (isFunctionType(type)) { + typeStr = functionTypeToKotlin(type); + } else if (JAVA_CLASS_TRANSLATIONS.containsKey(type.value)) { + typeStr = JAVA_CLASS_TRANSLATIONS.get(type.value); + } else if (JAVA_GENERIC_TRANSLATIONS.containsKey(type.value) && type.isGeneric()) { + GenericType genericType = ((GenericType) type); + List arguments = genericType.getArguments(); + String genericName = JAVA_GENERIC_TRANSLATIONS.get(type.value); + if (arguments.isEmpty()) { + typeStr = genericName; + } else { + StringBuilder builder = new StringBuilder(genericName); + builder.append("<"); + for (int i = 0; i < arguments.size(); i++) { + if (i != 0) { + builder.append(", "); + } + builder.append(getKotlinType(arguments.get(i))); + } + builder.append(">"); + typeStr = builder.toString(); + } + } else { + String s = ExprProcessor.getCastTypeName(type); + if (ExprProcessor.UNDEFINED_TYPE_STRING.equals(s)) { + s = "Any"; + } + // When going this path, arrays already get captured so no need to fall through to the array handling + if (!includeOuterClasses) { + s = s.substring(s.lastIndexOf('.') + 1); + } + + return mapJavaTypeToKotlin(s); + } + return "Array<".repeat(type.arrayDim) + typeStr + ">".repeat(type.arrayDim); + } + + private static String mapJavaTypeToKotlin(String type) { + if (type.endsWith("[]")) { + String baseType = type.substring(0, type.length() - 2); + switch (baseType) { + case "boolean": + case "byte": + case "char": + case "short": + case "int": + case "long": + case "float": + case "double": + return mapJavaTypeToKotlin(baseType) + "Array"; + default: + return "Array<" + mapJavaTypeToKotlin(baseType) + ">"; + } + } + + switch (type) { + case "boolean": + return "Boolean"; + case "byte": + return "Byte"; + case "char": + return "Char"; + case "short": + return "Short"; + case "int": + return "Int"; + case "long": + return "Long"; + case "float": + return "Float"; + case "double": + return "Double"; + case "void": + return "Unit"; + + default: + String[] parts = type.split("\\."); + StringBuilder sb = new StringBuilder(); + boolean appendDot = false; + for (String part : parts) { + Matcher matcher = GENERICS_PATTERN.matcher(part); + if (matcher.matches()) { + sb.append(mapJavaTypeToKotlin(matcher.group(1))); + sb.append("<"); + boolean appendComma = false; + for (String generic : matcher.group(2).split(",")) { + if (appendComma) { + sb.append(", "); + } + sb.append(mapJavaTypeToKotlin(generic.trim())); + appendComma = true; + } + sb.append(">"); + } else { + if (appendDot) { + sb.append("."); + } + sb.append(part); + appendDot = true; + } + } + return sb.toString(); + } + } + + public static boolean isFunctionType(VarType type) { + return type.value.startsWith("kotlin/jvm/functions/Function"); + } + + private static String functionTypeToKotlin(VarType type) { + if (!isFunctionType(type)) { + throw new IllegalArgumentException("Not a function type: " + type); + } + + String paramCount = type.value.substring("kotlin/jvm/functions/Function".length()); + if (paramCount.equals("N") || !(type instanceof GenericType)) { + // TODO: support FunctionN properly + String typeStr = ExprProcessor.getCastTypeName(type); + if (ExprProcessor.UNDEFINED_TYPE_STRING.equals(typeStr)) { + return "Any"; + } + return mapJavaTypeToKotlin(typeStr); + } + + GenericType genericType = (GenericType) type; + List params = genericType.getArguments(); + + int paramCountInt = Integer.parseInt(paramCount); + StringBuilder sb = new StringBuilder(); + sb.append("("); + for (int i = 0; i < paramCountInt; i++) { + if (i > 0) { + sb.append(", "); + } + + VarType param = params.get(i); + String paramStr = getKotlinType(param); + if (isFunctionType(param)) { + paramStr = "(" + paramStr + ")"; + } + sb.append(paramStr); + // TODO: check for nullability in types + sb.append("?"); + } + + sb.append(") -> "); + VarType returnType = params.get(paramCountInt); + String returnStr = getKotlinType(returnType); + if (isFunctionType(returnType)) { + returnStr = "(" + returnStr + ")"; + } + sb.append(returnStr); + + return sb.toString(); + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/util/KUtils.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/util/KUtils.java new file mode 100644 index 0000000000..ca0879d7d2 --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/util/KUtils.java @@ -0,0 +1,110 @@ +package org.vineflower.kotlin.util; + +import kotlinx.metadata.internal.metadata.ProtoBuf; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.modules.decompiler.exps.*; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.TextBuffer; +import org.vineflower.kotlin.KotlinOptions; +import org.vineflower.kotlin.expr.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class KUtils { + public static List replaceExprents(List exprs) { + List res = new ArrayList<>(); + + for (Exprent expr : exprs) { + Exprent map = replaceExprent(expr); + res.add(map != null ? map : expr); + } + + return res; + } + + public static Exprent replaceExprent(Exprent ex) { + if (ex instanceof KExprent) { + return ex; + } + + if (ex instanceof FunctionExprent) { + return new KFunctionExprent((FunctionExprent) ex); + } else if (ex instanceof VarExprent) { + return new KVarExprent((VarExprent) ex); + } else if (ex instanceof InvocationExprent) { + return new KInvocationExprent((InvocationExprent) ex); + } else if (ex instanceof ConstExprent) { + return new KConstExprent((ConstExprent) ex); + } else if (ex instanceof FieldExprent) { + return new KFieldExprent((FieldExprent) ex); + } else if (ex instanceof AnnotationExprent) { + return new KAnnotationExprent((AnnotationExprent) ex); + } + + return null; + } + + public static void appendVisibility(TextBuffer buf, ProtoBuf.Visibility visibility) { + switch (visibility) { + case LOCAL: + buf.append("// QF: local property") + .appendLineSeparator() + .append("internal "); + break; + case PRIVATE_TO_THIS: + buf.append("private "); + break; + case PUBLIC: + if (DecompilerContext.getOption(KotlinOptions.SHOW_PUBLIC_VISIBILITY)) { + buf.append("public "); + } + break; + default: + buf.append(visibility.name().toLowerCase()) + .append(' '); + } + } + + public static void removeArguments(InvocationExprent expr, VarType toRemove) { + removeArguments(expr, toRemove, null); + } + + public static void removeArguments(InvocationExprent expr, VarType toRemove, VarType replaceReturnType) { + if (expr.getLstParameters().isEmpty()) { + return; + } + + if (expr.getLstParameters().size() == 1) { + VarType argType = expr.getDescriptor().params[0]; + if (argType.equals(toRemove)) { + expr.getLstParameters().clear(); + } + return; + } + + VarType[] params = expr.getDescriptor().params; + List lst = expr.getLstParameters(); + for (int i = 0; i < params.length; i++) { + VarType argType = params[i]; + if (argType.equals(toRemove)) { + lst.set(i, null); + } + } + lst.removeIf(Objects::isNull); + + String newDesc = expr.getStringDescriptor() + .replace(toRemove.toString(), ""); + + if (newDesc.endsWith(")")) { + if (replaceReturnType == null) { + throw new IllegalStateException("Invalid descriptor: " + newDesc); + } + + newDesc += replaceReturnType.toString(); + } + + expr.setStringDescriptor(newDesc); + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/util/ProtobufFlags.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/util/ProtobufFlags.java new file mode 100644 index 0000000000..8a32ad4edf --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/util/ProtobufFlags.java @@ -0,0 +1,251 @@ +package org.vineflower.kotlin.util; + +import kotlinx.metadata.internal.metadata.ProtoBuf; +import kotlinx.metadata.internal.metadata.deserialization.Flags; + +public interface ProtobufFlags { + //TODO: hasNonStableParameterNames + //TODO: Update everything to use Flags class + + int HAS_ANNOTATIONS = 0x0001; + + int VISIBILITY_MASK = 0x000E; + int MODALITY_MASK = 0x0030; + int KIND_MASK = 0x01C0; + + //region: Constructor + int CTOR_SECONDARY = 0x0010; + + class Constructor { + public final boolean hasAnnotations; + public final ProtoBuf.Visibility visibility; + public final boolean isSecondary; +// public final boolean hasNonStableParameterNames; + + public Constructor(int flags) { + hasAnnotations = (flags & HAS_ANNOTATIONS) != 0; + visibility = ProtoBuf.Visibility.valueOf((flags & VISIBILITY_MASK) >> 1); + isSecondary = (flags & CTOR_SECONDARY) != 0; +// hasNonStableParameterNames = (flags & FUN_HAS_NON_STABLE_PARAMETER_NAMES) != 0; + } + } + //endregion + + //region: Function + int FUN_IS_OPERATOR = 0x0100; + int FUN_IS_INFIX = 0x0200; + int FUN_IS_INLINE = 0x0400; + int FUN_IS_TAILREC = 0x0800; + int FUN_IS_EXTERNAL = 0x1000; + int FUN_IS_SUSPEND = 0x2000; + int FUN_IS_EXPECT = 0x4000; + + class Function { + public final boolean hasAnnotations; + public final ProtoBuf.Visibility visibility; + public final ProtoBuf.Modality modality; + public final ProtoBuf.MemberKind kind; + public final boolean isOperator; + public final boolean isInfix; + public final boolean isInline; + public final boolean isTailrec; + public final boolean isExternal; + public final boolean isSuspend; + public final boolean isExpect; +// public final boolean hasNonStableParameterNames; + + public Function(int flags) { + hasAnnotations = (flags & HAS_ANNOTATIONS) != 0; + visibility = ProtoBuf.Visibility.valueOf((flags & VISIBILITY_MASK) >> 1); + modality = ProtoBuf.Modality.valueOf((flags & MODALITY_MASK) >> 4); + kind = ProtoBuf.MemberKind.valueOf((flags & KIND_MASK) >> 6); + isOperator = (flags & FUN_IS_OPERATOR) != 0; + isInfix = (flags & FUN_IS_INFIX) != 0; + isInline = (flags & FUN_IS_INLINE) != 0; + isTailrec = (flags & FUN_IS_TAILREC) != 0; + isExternal = (flags & FUN_IS_EXTERNAL) != 0; + isSuspend = (flags & FUN_IS_SUSPEND) != 0; + isExpect = (flags & FUN_IS_EXPECT) != 0; +// hasNonStableParameterNames = (flags & FUN_HAS_NON_STABLE_PARAMETER_NAMES) != 0; + } + } + //endregion + + //region: Property + int PROP_IS_VAR = 0x0100; + int PROP_HAS_GETTER = 0x0200; + int PROP_HAS_SETTER = 0x0400; + int PROP_IS_CONST = 0x0800; + int PROP_IS_LATEINIT = 0x1000; + int PROP_HAS_CONSTANT = 0x2000; + int PROP_IS_EXTERNAL = 0x4000; + int PROP_IS_DELEGATED = 0x8000; + int PROP_IS_EXPECT = 0x10000; + + class Property { + public final boolean hasAnnotations; + public final ProtoBuf.Visibility visibility; + public final ProtoBuf.Modality modality; + public final ProtoBuf.MemberKind kind; + public final boolean isVar; + public final boolean hasGetter; + public final boolean hasSetter; + public final boolean isConst; + public final boolean isLateinit; + public final boolean hasConstant; + public final boolean isExternal; + public final boolean isDelegated; + public final boolean isExpect; + + public Property(int flags) { + hasAnnotations = (flags & HAS_ANNOTATIONS) != 0; + visibility = ProtoBuf.Visibility.valueOf((flags & VISIBILITY_MASK) >> 1); + modality = ProtoBuf.Modality.valueOf((flags & MODALITY_MASK) >> 4); + kind = ProtoBuf.MemberKind.valueOf((flags & KIND_MASK) >> 6); + isVar = (flags & PROP_IS_VAR) != 0; + hasGetter = (flags & PROP_HAS_GETTER) != 0; + hasSetter = (flags & PROP_HAS_SETTER) != 0; + isConst = (flags & PROP_IS_CONST) != 0; + isLateinit = (flags & PROP_IS_LATEINIT) != 0; + hasConstant = (flags & PROP_HAS_CONSTANT) != 0; + isExternal = (flags & PROP_IS_EXTERNAL) != 0; + isDelegated = (flags & PROP_IS_DELEGATED) != 0; + isExpect = (flags & PROP_IS_EXPECT) != 0; + } + } + //endregion + + //region: Property getter/setter + int PROP_ACCESSOR_IS_NOT_DEFAULT = 0x0040; + int PROP_ACCESSOR_IS_EXTERNAL = 0x0080; + int PROP_ACCESSOR_IS_INLINE = 0x0100; + + class PropertyAccessor { + public final boolean hasAnnotations; + public final ProtoBuf.Visibility visibility; + public final ProtoBuf.Modality modality; + public final boolean isNotDefault; + public final boolean isExternal; + public final boolean isInline; + + public PropertyAccessor(int flags) { + hasAnnotations = (flags & HAS_ANNOTATIONS) != 0; + visibility = ProtoBuf.Visibility.valueOf((flags & VISIBILITY_MASK) >> 1); + modality = ProtoBuf.Modality.valueOf((flags & MODALITY_MASK) >> 4); + isNotDefault = (flags & PROP_ACCESSOR_IS_NOT_DEFAULT) != 0; + isExternal = (flags & PROP_ACCESSOR_IS_EXTERNAL) != 0; + isInline = (flags & PROP_ACCESSOR_IS_INLINE) != 0; + } + } + //endregion + + //region: Value parameter + int VAL_PARAM_DECLARES_DEFAULT = 0x0002; + int VAL_PARAM_IS_CROSSINLINE = 0x0004; + int VAL_PARAM_IS_NOINLINE = 0x0008; + + class ValueParameter { + public final boolean hasAnnotations; + public final boolean declaresDefault; + public final boolean isCrossinline; + public final boolean isNoinline; + + public ValueParameter(int flags) { + hasAnnotations = (flags & HAS_ANNOTATIONS) != 0; + declaresDefault = (flags & VAL_PARAM_DECLARES_DEFAULT) != 0; + isCrossinline = (flags & VAL_PARAM_IS_CROSSINLINE) != 0; + isNoinline = (flags & VAL_PARAM_IS_NOINLINE) != 0; + } + } + //endregion + + //region: Typealias + class TypeAlias { + public final boolean hasAnnotations; + public final ProtoBuf.Visibility visibility; + + public TypeAlias(int flags) { + hasAnnotations = (flags & HAS_ANNOTATIONS) != 0; + visibility = ProtoBuf.Visibility.valueOf((flags & VISIBILITY_MASK) >> 1); + } + } + //endregion + + //region: Class + int CLASS_IS_INNER = 0x0200; + int CLASS_IS_DATA = 0x0400; + int CLASS_IS_EXTERNAL = 0x0800; + int CLASS_IS_EXPECT = 0x1000; + int CLASS_IS_INLINE = 0x2000; + int CLASS_IS_FUN = 0x4000; + int CLASS_HAS_ENUM_ENTRIES = 0x8000; + + class Class { + public final boolean hasAnnotations; + public final ProtoBuf.Visibility visibility; + public final ProtoBuf.Modality modality; + public final ProtoBuf.Class.Kind kind; + public final boolean isInner; + public final boolean isData; + public final boolean isExternal; + public final boolean isExpect; + public final boolean isInline; + public final boolean isFun; + public final boolean hasEnumEntries; + + public Class(int flags) { + hasAnnotations = (flags & HAS_ANNOTATIONS) != 0; + visibility = ProtoBuf.Visibility.valueOf((flags & VISIBILITY_MASK) >> 1); + modality = ProtoBuf.Modality.valueOf((flags & MODALITY_MASK) >> 4); + kind = ProtoBuf.Class.Kind.valueOf((flags & KIND_MASK) >> 6); + isInner = (flags & CLASS_IS_INNER) != 0; + isData = (flags & CLASS_IS_DATA) != 0; + isExternal = (flags & CLASS_IS_EXTERNAL) != 0; + isExpect = (flags & CLASS_IS_EXPECT) != 0; + isInline = (flags & CLASS_IS_INLINE) != 0; + isFun = (flags & CLASS_IS_FUN) != 0; + hasEnumEntries = (flags & CLASS_HAS_ENUM_ENTRIES) != 0; + } + } + //endregion + + //region: Contract + class Expression { + public final boolean isNegated; + public final boolean isNullPredicate; + + public Expression(int flags) { + isNegated = Flags.IS_NEGATED.get(flags); + isNullPredicate = Flags.IS_NULL_CHECK_PREDICATE.get(flags); + } + } + //endregion + + static String toString(ProtoBuf.Visibility visibility) { + switch (visibility) { + case PRIVATE: + case PRIVATE_TO_THIS: + return "private"; + case PROTECTED: + return "protected"; + case INTERNAL: + return "internal"; + default: + return "public"; + } + } + + static String toString(ProtoBuf.Modality modality) { + switch (modality) { + case FINAL: + return "final"; + case OPEN: + return "open"; + case ABSTRACT: + return "abstract"; + case SEALED: + return "sealed"; + } + throw new IllegalStateException("Unknown modality: " + modality); + } +} diff --git a/plugins/kotlin/src/main/resources/META-INF/services/org.jetbrains.java.decompiler.api.plugin.Plugin b/plugins/kotlin/src/main/resources/META-INF/services/org.jetbrains.java.decompiler.api.plugin.Plugin new file mode 100644 index 0000000000..6a3a374077 --- /dev/null +++ b/plugins/kotlin/src/main/resources/META-INF/services/org.jetbrains.java.decompiler.api.plugin.Plugin @@ -0,0 +1 @@ +org.vineflower.kotlin.KotlinPlugin \ No newline at end of file diff --git a/plugins/kotlin/src/test/java/org/vineflower/kotlin/KotlinTests.java b/plugins/kotlin/src/test/java/org/vineflower/kotlin/KotlinTests.java new file mode 100644 index 0000000000..cb6d438fa3 --- /dev/null +++ b/plugins/kotlin/src/test/java/org/vineflower/kotlin/KotlinTests.java @@ -0,0 +1,89 @@ +package org.vineflower.kotlin; + +import org.jetbrains.java.decompiler.DecompilerTestFixture; +import org.jetbrains.java.decompiler.SingleClassesTestBase; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; + +import java.nio.file.Path; + +import static org.jetbrains.java.decompiler.SingleClassesTestBase.TestDefinition.Version.KOTLIN; + +public class KotlinTests extends SingleClassesTestBase { + + protected Path getClassFile(DecompilerTestFixture fixture, TestDefinition.Version version, String name) { + Path reg = fixture.getTestDataDir().resolve("classes/" + version.directory + "/" + name + ".class"); + Path kt = fixture.getTestDataDir().resolve("classes/" + version.directory + "/" + name + "Kt.class"); + + return reg.toFile().exists() ? reg : kt; + } + + protected void registerAll() { + registerSet("Entire Classpath", this::registerKotlinTests, + IFernflowerPreferences.BYTECODE_SOURCE_MAPPING, "1", + IFernflowerPreferences.DUMP_ORIGINAL_LINES, "1", + IFernflowerPreferences.DUMP_EXCEPTION_ON_ERROR, "0", + IFernflowerPreferences.IGNORE_INVALID_BYTECODE, "1", + IFernflowerPreferences.REMOVE_SYNTHETIC, "0", + IFernflowerPreferences.VERIFY_ANONYMOUS_CLASSES, "1" + ); + } + + private void registerKotlinTests() { + register(KOTLIN, "TestKt"); + register(KOTLIN, "TestParams"); + register(KOTLIN, "TestInfixFun"); + register(KOTLIN, "TestFunVarargs"); + register(KOTLIN, "TestSmartCasts"); + register(KOTLIN, "TestTailrecFunctions"); + register(KOTLIN, "TestTryCatchExpressions"); + register(KOTLIN, "TestTryFinallyExpressions"); + register(KOTLIN, "TestVars"); + // TODO: handle lambdas + register(KOTLIN, "TestNonInlineLambda"); + register(KOTLIN, "TestNothingReturns"); + // TODO: Generics containing nullability (like List) lose the nullability + register(KOTLIN, "TestNullable"); + register(KOTLIN, "TestExtensionFun"); + register(KOTLIN, "TestClassDec"); + register(KOTLIN, "TestAnyType"); + // TODO: gets condensed into ternaries + register(KOTLIN, "TestWhen"); + register(KOTLIN, "TestWhenBoolean"); + register(KOTLIN, "TestForRange"); + register(KOTLIN, "TestIfRange"); + register(KOTLIN, "TestComparison"); + register(KOTLIN, "TestNullableOperator"); + // TODO: x = x needs to not have the type attached + register(KOTLIN, "TestShadowParam"); + register(KOTLIN, "TestWhenControlFlow"); + register(KOTLIN, "TestLabeledJumps"); + register(KOTLIN, "TestFuncRef"); + // TODO: top-level constructs are wrongly put into a class and turned into instance methods + register(KOTLIN, "TestTopLevelKt"); + // TODO: lists, sets, etc. should be considered to be their Kotlin type, not their Java type + register(KOTLIN, "TestConvertedK2JOps"); + // TODO: Does not decompile to data class + register(KOTLIN, "TestDataClass"); + // TODO: Does not decompile to destructuring declaration + register(KOTLIN, "TestDestructors"); + // TODO: Type projections are not Kotlin equivalents + register(KOTLIN, "TestGenerics"); + // TODO: Nothing, function types + register(KOTLIN, "TestKotlinTypes"); + // TODO: Does not decompile to object literal + // FIXME: @JvmStatic function fails to decompile at all + register(KOTLIN, "TestObject"); + // TODO: sealed becomes "open abstract" + register(KOTLIN, "TestSealedHierarchy"); + // TODO: Annotation classes decompile entirely incorrectly + register(KOTLIN, "TestAnnotations"); + register(KOTLIN, "TestBitwiseFunctions"); + register(KOTLIN, "TestCompileTimeErrors"); + register(KOTLIN, "TestPoorNames"); + register(KOTLIN, "TestSafeCasts"); + register(KOTLIN, "TestSynchronized"); + register(KOTLIN, "TestReflection"); + register(KOTLIN, "TestConstructors"); + register(KOTLIN, "TestContracts"); + } +} diff --git a/plugins/kotlin/testData/results/pkg/TestAnnotations.dec b/plugins/kotlin/testData/results/pkg/TestAnnotations.dec new file mode 100644 index 0000000000..9fea29d521 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestAnnotations.dec @@ -0,0 +1,40 @@ +package pkg + +import java.lang.annotation.Retention +import java.lang.annotation.RetentionPolicy + +class TestAnnotations { + @TestAnnotations.TestAnnotation(first = "test", second = 1) + public fun test() { + }// 16 + + @TestAnnotations.RepeatableAnnotation.Container([@TestAnnotations.RepeatableAnnotation("test"), @TestAnnotations.RepeatableAnnotation("test2")]) + public fun test2() { + }// 21 + + @Repeatable + @Retention(RetentionPolicy.RUNTIME) + annotation class RepeatableAnnotation( + val value: String + ) + + @Retention(RetentionPolicy.RUNTIME) + annotation class TestAnnotation( + val first: String, + val second: Int + ) +} + +class 'pkg/TestAnnotations' { + method 'test ()V' { + 0 8 + } + + method 'test2 ()V' { + 0 12 + } +} + +Lines mapping: +16 <-> 9 +21 <-> 13 diff --git a/plugins/kotlin/testData/results/pkg/TestAnyType.dec b/plugins/kotlin/testData/results/pkg/TestAnyType.dec new file mode 100644 index 0000000000..40e0f17498 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestAnyType.dec @@ -0,0 +1,45 @@ +package pkg + +class TestAnyType { + public fun test(param: Any): Int { + if (param is java.lang.String) {// 5 + return (param as java.lang.String).length();// 6 + } else { + System.out.println(param);// 9 + return 0;// 11 + } + } +} + +class 'pkg/TestAnyType' { + method 'test (Ljava/lang/Object;)I' { + 6 4 + 7 4 + 8 4 + 9 4 + a 4 + d 5 + e 5 + f 5 + 10 5 + 11 5 + 12 5 + 13 5 + 14 5 + 15 7 + 16 7 + 17 7 + 18 7 + 19 7 + 1a 7 + 1b 7 + 1c 8 + 1d 8 + } +} + +Lines mapping: +5 <-> 5 +6 <-> 6 +9 <-> 8 +11 <-> 9 diff --git a/plugins/kotlin/testData/results/pkg/TestBitwiseFunctions.dec b/plugins/kotlin/testData/results/pkg/TestBitwiseFunctions.dec new file mode 100644 index 0000000000..ecec103da9 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestBitwiseFunctions.dec @@ -0,0 +1,79 @@ +package pkg + +class TestBitwiseFunctions { + public fun and(a: Int, b: Int): Int { + return a and b;// 5 + } + + public fun or(a: Int, b: Int): Int { + return a or b;// 9 + } + + public fun xor(a: Int, b: Int): Int { + return a xor b;// 13 + } + + public fun shl(a: Int, b: Int): Int { + return a shl b;// 17 + } + + public fun shr(a: Int, b: Int): Int { + return a shr b;// 21 + } + + public fun ushr(a: Int, b: Int): Int { + return a ushr b;// 25 + } +} + +class 'pkg/TestBitwiseFunctions' { + method 'and (II)I' { + 0 4 + 1 4 + 2 4 + 3 4 + } + + method 'or (II)I' { + 0 8 + 1 8 + 2 8 + 3 8 + } + + method 'xor (II)I' { + 0 12 + 1 12 + 2 12 + 3 12 + } + + method 'shl (II)I' { + 0 16 + 1 16 + 2 16 + 3 16 + } + + method 'shr (II)I' { + 0 20 + 1 20 + 2 20 + 3 20 + } + + method 'ushr (II)I' { + 0 24 + 1 24 + 2 24 + 3 24 + } +} + +Lines mapping: +5 <-> 5 +9 <-> 9 +13 <-> 13 +17 <-> 17 +21 <-> 21 +25 <-> 25 diff --git a/plugins/kotlin/testData/results/pkg/TestClassDec.dec b/plugins/kotlin/testData/results/pkg/TestClassDec.dec new file mode 100644 index 0000000000..05fc5b7a6b --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestClassDec.dec @@ -0,0 +1,148 @@ +package pkg + +class TestClassDec { + public fun pkg.TestClassDec.Vec2iVal.dot(v: pkg.TestClassDec.Vec2iVal): Int { + return `$this$dot`.getX() * v.getX() + `$this$dot`.getY() * v.getY();// 11 + } + + public fun test() { + new TestClassDec.EmptyDec(); + var vec: TestClassDec.Vec2iVal = new TestClassDec.Vec2iVal(1, 2);// 16 + var vec1: TestClassDec.Vec2iVal = new TestClassDec.Vec2iVal(2, 4);// 17 + System.out.println(vec.getX());// 19 + System.out.println(this.dot(vec, vec1));// 20 + }// 21 + + class EmptyDec { + } + + class Vec2i(x: Int, y: Int) { + } + + class Vec2iVal(x: Int, y: Int) { + public final val x: Int + public final val y: Int + + init { + this.x = x;// 7 + this.y = y; + } + } + + class Vec2iVar(x: Int, y: Int) { + public final var x: Int + internal set + public final var y: Int + internal set + + init { + this.x = x;// 6 + this.y = y; + } + } +} + +class 'pkg/TestClassDec' { + method 'dot (Lpkg/TestClassDec$Vec2iVal;Lpkg/TestClassDec$Vec2iVal;)I' { + c 4 + d 4 + e 4 + f 4 + 10 4 + 11 4 + 12 4 + 13 4 + 14 4 + 15 4 + 16 4 + 17 4 + 18 4 + 19 4 + 1a 4 + 1b 4 + 1c 4 + 1d 4 + 1e 4 + 1f 4 + } + + method 'test ()V' { + c 9 + d 9 + 11 9 + 16 10 + 17 10 + 1b 10 + 1c 11 + 1d 11 + 1e 11 + 1f 11 + 22 11 + 23 11 + 24 11 + 25 11 + 26 11 + 27 11 + 28 11 + 29 11 + 2a 12 + 2b 12 + 2c 12 + 2d 12 + 2e 12 + 2f 12 + 32 12 + 33 12 + 34 12 + 35 12 + 36 12 + 37 12 + 38 12 + 39 12 + 3a 13 + } +} + +class 'pkg/TestClassDec$Vec2iVal' { + method ' (II)V' { + 4 26 + 5 26 + 6 26 + 7 26 + 8 26 + 9 27 + a 27 + b 27 + c 27 + d 27 + e 28 + } +} + +class 'pkg/TestClassDec$Vec2iVar' { + method ' (II)V' { + 4 38 + 5 38 + 6 38 + 7 38 + 8 38 + 9 39 + a 39 + b 39 + c 39 + d 39 + e 40 + } +} + +Lines mapping: +6 <-> 39 +7 <-> 27 +11 <-> 5 +16 <-> 10 +17 <-> 11 +19 <-> 12 +20 <-> 13 +21 <-> 14 +Not mapped: +15 diff --git a/plugins/kotlin/testData/results/pkg/TestComparison.dec b/plugins/kotlin/testData/results/pkg/TestComparison.dec new file mode 100644 index 0000000000..deae0a7218 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestComparison.dec @@ -0,0 +1,75 @@ +package pkg + +class TestComparison { + public fun test2(a: Any, b: Any): Boolean { + return a == b;// 5 + } + + public fun test3(a: Any, b: Any): Boolean { + return a === b;// 9 + } + + public fun testNull2(a: Any?): Boolean { + return a == null;// 13 + } + + public fun testNull3(a: Any?): Boolean { + return a == null;// 17 + } + + public fun testNullDouble2(a: Any?, b: Any?): Boolean { + return a == b;// 21 + } + + public fun testNullDouble3(a: Any?, b: Any?): Boolean { + return a === b;// 25 + } +} + +class 'pkg/TestComparison' { + method 'test2 (Ljava/lang/Object;Ljava/lang/Object;)Z' { + c 4 + d 4 + 11 4 + } + + method 'test3 (Ljava/lang/Object;Ljava/lang/Object;)Z' { + c 8 + d 8 + e 8 + 16 8 + } + + method 'testNull2 (Ljava/lang/Object;)Z' { + 0 12 + 1 12 + 9 12 + } + + method 'testNull3 (Ljava/lang/Object;)Z' { + 0 16 + 1 16 + 9 16 + } + + method 'testNullDouble2 (Ljava/lang/Object;Ljava/lang/Object;)Z' { + 0 20 + 1 20 + 5 20 + } + + method 'testNullDouble3 (Ljava/lang/Object;Ljava/lang/Object;)Z' { + 0 24 + 1 24 + 2 24 + a 24 + } +} + +Lines mapping: +5 <-> 5 +9 <-> 9 +13 <-> 13 +17 <-> 17 +21 <-> 21 +25 <-> 25 diff --git a/plugins/kotlin/testData/results/pkg/TestCompileTimeErrors.dec b/plugins/kotlin/testData/results/pkg/TestCompileTimeErrors.dec new file mode 100644 index 0000000000..b39d3dee40 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestCompileTimeErrors.dec @@ -0,0 +1,80 @@ +package pkg + +import org.jetbrains.annotations.Nullable + +class TestCompileTimeErrors { + public fun test(i: I): O where O : I, O : pkg.TestCompileTimeErrors.Test { + throw new NotImplementedError(null, 1, null);// 10 + } + + public fun test2(i: Int?): pkg.TestCompileTimeErrors.Test? { + return if (i == null) null else new TestCompileTimeErrors.Test(i) { + @Nullable + private final Integer testValue; + + { + this.testValue = `$i`; + }// 14 + + @Nullable + public Integer getTestValue() { + return this.testValue;// 16 + } + + /** @deprecated */ + // $VF: synthetic method + public static void getTestValue$annotations() { + } + }; + } + + interface Test { + public val testValue: Int + } +} + +class 'pkg/TestCompileTimeErrors' { + method 'test (Ljava/lang/Object;)Ljava/lang/Object;' { + 4 6 + 5 6 + 6 6 + a 6 + } + + method 'test2 (Ljava/lang/Integer;)Lpkg/TestCompileTimeErrors$Test;' { + 0 10 + 1 10 + 4 10 + c 10 + 10 10 + 13 10 + } +} + +class 'pkg/TestCompileTimeErrors$test2$1' { + method ' (Ljava/lang/Integer;)V' { + 4 15 + 5 15 + 6 15 + 7 15 + 8 15 + 9 16 + } + + method 'getTestValue ()Ljava/lang/Integer;' { + 0 20 + 1 20 + 2 20 + 3 20 + 4 20 + } + + method 'getTestValue$annotations ()V' { + 0 26 + } +} + +Lines mapping: +10 <-> 7 +14 <-> 17 +16 <-> 21 diff --git a/plugins/kotlin/testData/results/pkg/TestConstructors.dec b/plugins/kotlin/testData/results/pkg/TestConstructors.dec new file mode 100644 index 0000000000..734acf7b17 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestConstructors.dec @@ -0,0 +1,60 @@ +package pkg + +class TestConstructors private constructor() { + public constructor(a: Int) : this() {// 4 + System.out.println("a = " + a);// 5 + }// 6 + + public constructor(a: Int, b: Int) : this(a) {// 8 + System.out.println("b = " + b);// 9 + }// 10 +} + +class 'pkg/TestConstructors' { + method ' (I)V' { + 1 3 + 2 3 + 3 3 + 4 4 + 5 4 + 6 4 + 7 4 + 8 4 + 9 4 + a 4 + b 4 + c 4 + e 4 + f 4 + 10 4 + 11 5 + } + + method ' (II)V' { + 1 7 + 2 7 + 3 7 + 4 7 + 5 8 + 6 8 + 7 8 + 8 8 + 9 8 + a 8 + b 8 + c 8 + d 8 + f 8 + 10 8 + 11 8 + 12 9 + } +} + +Lines mapping: +4 <-> 4 +5 <-> 5 +6 <-> 6 +8 <-> 8 +9 <-> 9 +10 <-> 10 diff --git a/plugins/kotlin/testData/results/pkg/TestContracts.dec b/plugins/kotlin/testData/results/pkg/TestContracts.dec new file mode 100644 index 0000000000..4025e8953b --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestContracts.dec @@ -0,0 +1,232 @@ +package pkg + +import kotlin.contracts.InvocationKind + +class TestContracts { + public fun testSimpleContract(x: Int?): Int { + contract { + returns() implies (x != null) + } + + if (x == null) {// 13 + throw new IllegalStateException("x is null".toString()); + } else { + return x;// 14 + } + } + + public fun testBooleanContract(a: Boolean, b: Boolean): Boolean? { + contract { + returns(true) implies (!a && !b) + returns(null) implies (a && b) + returns(false) implies ((a && !b || (!a && b) + } + + return if (a && b) null else a || b;// 24 + } + + public fun testTypeContract(x: Any?): Int { + contract { + returns() implies (x is Int) + } + + if (x !is Integer) {// 31 + throw new IllegalStateException("x is not Int".toString()); + } else { + return (x as java.lang.Number).intValue();// 32 + } + } + + public fun testFunctionalContract(f: () -> Int): Int { + contract { + callsInPlace(f, InvocationKind.EXACTLY_ONCE) + } + + return (f.invoke() as java.lang.Number).intValue();// 39 + } + + public fun testFunctionalContract2(f: () -> Int, b: Boolean): Int { + contract { + callsInPlace(f, InvocationKind.AT_MOST_ONCE) + } + + return if (b) (f.invoke() as java.lang.Number).intValue() else 0;// 46 + } + + public fun testFunctionalContract3(f: () -> Int, i: Int): Int { + contract { + callsInPlace(f) + } + + var var3: java.lang.Iterable = (new IntRange(0, i)) as java.lang.Iterable; + var var4: Int = 0; + var var5: java.util.Iterator = var3.iterator(); + + while (var5.hasNext()) { + var var6: Int = (var5 as IntIterator).nextInt(); + var4 += (f.invoke() as java.lang.Number).intValue(); + } + + return var4;// 53 + } +} + +class 'pkg/TestContracts' { + method 'testSimpleContract (Ljava/lang/Integer;)I' { + 1 10 + 2 10 + 9 11 + a 11 + b 11 + c 11 + d 11 + 11 11 + 12 13 + 13 13 + 14 13 + 15 13 + 16 13 + } + + method 'testBooleanContract (ZZ)Ljava/lang/Boolean;' { + 1 24 + 2 24 + 5 24 + 6 24 + 9 24 + d 24 + e 24 + 11 24 + 12 24 + 1a 24 + 1d 24 + } + + method 'testTypeContract (Ljava/lang/Object;)I' { + 1 32 + 5 32 + c 33 + d 33 + e 33 + f 33 + 10 33 + 14 33 + 15 35 + 16 35 + 17 35 + 18 35 + 19 35 + 1a 35 + 1b 35 + 1c 35 + } + + method 'testFunctionalContract (Lkotlin/jvm/functions/Function0;)I' { + 7 44 + 8 44 + 9 44 + a 44 + b 44 + c 44 + d 44 + e 44 + f 44 + 10 44 + 11 44 + 12 44 + 13 44 + } + + method 'testFunctionalContract2 (Lkotlin/jvm/functions/Function0;Z)I' { + 7 52 + 8 52 + b 52 + c 52 + d 52 + e 52 + f 52 + 10 52 + 11 52 + 12 52 + 13 52 + 14 52 + 15 52 + 16 52 + 1a 52 + 1b 52 + } + + method 'testFunctionalContract3 (Lkotlin/jvm/functions/Function0;I)I' { + b 60 + c 60 + 10 60 + 11 60 + 12 60 + 13 60 + 14 61 + 15 61 + 16 61 + 17 62 + 18 62 + 19 62 + 1a 62 + 1b 62 + 1c 62 + 1d 62 + 1e 62 + 1f 64 + 20 64 + 21 64 + 22 64 + 23 64 + 24 64 + 25 64 + 29 65 + 2a 65 + 2b 65 + 2c 65 + 2d 65 + 2e 65 + 2f 65 + 30 65 + 31 65 + 32 65 + 3e 66 + 3f 66 + 40 66 + 41 66 + 42 66 + 43 66 + 44 66 + 45 66 + 46 66 + 47 66 + 48 66 + 49 66 + 4e 66 + 4f 66 + 51 66 + 52 66 + 56 69 + 57 69 + 58 69 + } +} + +Lines mapping: +13 <-> 11 +14 <-> 14 +24 <-> 25 +31 <-> 33 +32 <-> 36 +39 <-> 45 +46 <-> 53 +53 <-> 70 +Not mapped: +10 +18 +28 +36 +43 +50 +57 diff --git a/plugins/kotlin/testData/results/pkg/TestConvertedK2JOps.dec b/plugins/kotlin/testData/results/pkg/TestConvertedK2JOps.dec new file mode 100644 index 0000000000..8e0855be92 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestConvertedK2JOps.dec @@ -0,0 +1,33 @@ +package pkg + +class TestConvertedK2JOps { + public final val any: Any = new Object() + public final val list: List = CollectionsKt.listOf(new java.lang.String[]{"a", "b", "c"}) + public final val map: Map = MapsKt.mapOf(new Pair[]{TuplesKt.to("a", "b"), TuplesKt.to("c", "d")}) + public final val set: Set = SetsKt.setOf(new java.lang.String[]{"a", "b", "c"}) + + + public fun codeConstructs() { + System.out.println("Hello, world!");// 10 + }// 12 +} + +class 'pkg/TestConvertedK2JOps' { + method 'codeConstructs ()V' { + 0 10 + 1 10 + 2 10 + 3 10 + 4 10 + 6 10 + 7 10 + 8 10 + c 11 + } +} + +Lines mapping: +10 <-> 11 +12 <-> 12 +Not mapped: +11 diff --git a/plugins/kotlin/testData/results/pkg/TestDataClass.dec b/plugins/kotlin/testData/results/pkg/TestDataClass.dec new file mode 100644 index 0000000000..29e6c123f9 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestDataClass.dec @@ -0,0 +1,362 @@ +package pkg + +data class TestDataClass(dataClassVal: Regex, variableWithVeryLongName: Int, requestLineWrapsIfTheParamListIsTooLong: List, nullability: String?) { + public final val dataClassVal: Regex + public final val nullability: String? + public final val requestLineWrapsIfTheParamListIsTooLong: List + public final val variableWithVeryLongName: Int + + init { + this.dataClassVal = dataClassVal;// 4 + this.variableWithVeryLongName = variableWithVeryLongName;// 5 + this.requestLineWrapsIfTheParamListIsTooLong = requestLineWrapsIfTheParamListIsTooLong;// 6 + this.nullability = nullability;// 7 + }// 3 + + public operator fun component1(): Regex { + return this.dataClassVal; + } + + public operator fun component2(): Int { + return this.variableWithVeryLongName; + } + + public operator fun component3(): List { + return this.requestLineWrapsIfTheParamListIsTooLong; + } + + public operator fun component4(): String? { + return this.nullability; + } + + public fun copy(dataClassVal: Regex, variableWithVeryLongName: Int, requestLineWrapsIfTheParamListIsTooLong: List, nullability: String?): TestDataClass { + return new TestDataClass(dataClassVal, variableWithVeryLongName, requestLineWrapsIfTheParamListIsTooLong, nullability); + } + + @JvmStatic + @JvmSynthetic + fun `copy$default`(var0: TestDataClass, var1: Regex, var2: Int, var3: java.util.List, var4: java.lang.String, var5: Int, var6: Any): TestDataClass { + if ((var5 and 1) != 0) { + var1 = var0.dataClassVal; + } + + if ((var5 and 2) != 0) { + var2 = var0.variableWithVeryLongName; + } + + if ((var5 and 4) != 0) { + var3 = var0.requestLineWrapsIfTheParamListIsTooLong; + } + + if ((var5 and 8) != 0) { + var4 = var0.nullability; + } + + return var0.copy(var1, var2, var3, var4); + } + + public open fun toString(): String { + return "TestDataClass(dataClassVal=" + + this.dataClassVal + + ", variableWithVeryLongName=" + + this.variableWithVeryLongName + + ", requestLineWrapsIfTheParamListIsTooLong=" + + this.requestLineWrapsIfTheParamListIsTooLong + + ", nullability=" + + this.nullability + + ")"; + } + + public open fun hashCode(): Int { + return ( + (this.dataClassVal.hashCode() * 31 + Integer.hashCode(this.variableWithVeryLongName)) * 31 + + this.requestLineWrapsIfTheParamListIsTooLong.hashCode() + ) + * 31 + + (if (this.nullability == null) 0 else this.nullability.hashCode()); + } + + public open operator fun equals(other: Any?): Boolean { + if (this === other) { + return true; + } else if (other !is TestDataClass) { + return false; + } else { + var var2: TestDataClass = other as TestDataClass; + if (!(this.dataClassVal == (other as TestDataClass).dataClassVal)) { + return false; + } else if (this.variableWithVeryLongName != var2.variableWithVeryLongName) { + return false; + } else if (!(this.requestLineWrapsIfTheParamListIsTooLong == var2.requestLineWrapsIfTheParamListIsTooLong)) { + return false; + } else { + return this.nullability == var2.nullability; + } + } + } +} + +class 'pkg/TestDataClass' { + method ' (Lkotlin/text/Regex;ILjava/util/List;Ljava/lang/String;)V' { + 10 9 + 11 9 + 12 9 + 13 9 + 14 9 + 15 10 + 16 10 + 17 10 + 18 10 + 19 10 + 1a 11 + 1b 11 + 1c 11 + 1d 11 + 1e 11 + 1f 12 + 20 12 + 21 12 + 22 12 + 23 12 + 24 12 + 25 13 + } + + method 'component1 ()Lkotlin/text/Regex;' { + 0 16 + 1 16 + 2 16 + 3 16 + 4 16 + } + + method 'component2 ()I' { + 0 20 + 1 20 + 2 20 + 3 20 + 4 20 + } + + method 'component3 ()Ljava/util/List;' { + 0 24 + 1 24 + 2 24 + 3 24 + 4 24 + } + + method 'component4 ()Ljava/lang/String;' { + 0 28 + 1 28 + 2 28 + 3 28 + 4 28 + } + + method 'copy (Lkotlin/text/Regex;ILjava/util/List;Ljava/lang/String;)Lpkg/TestDataClass;' { + 10 32 + 11 32 + 12 32 + 13 32 + 14 32 + 18 32 + } + + method 'copy$default (Lpkg/TestDataClass;Lkotlin/text/Regex;ILjava/util/List;Ljava/lang/String;ILjava/lang/Object;)Lpkg/TestDataClass;' { + 0 38 + 1 38 + 2 38 + 3 38 + 4 38 + 7 39 + 8 39 + 9 39 + a 39 + b 39 + c 42 + d 42 + e 42 + f 42 + 10 42 + 13 43 + 14 43 + 15 43 + 16 43 + 17 43 + 18 46 + 19 46 + 1a 46 + 1b 46 + 1c 46 + 1f 47 + 20 47 + 21 47 + 22 47 + 23 47 + 24 50 + 25 50 + 26 50 + 27 50 + 28 50 + 29 50 + 2c 51 + 2d 51 + 2e 51 + 2f 51 + 30 51 + 31 51 + 32 54 + 33 54 + 34 54 + 35 54 + 36 54 + 37 54 + 38 54 + 39 54 + 3a 54 + 3b 54 + } + + method 'toString ()Ljava/lang/String;' { + 0 59 + 1 59 + 2 59 + 3 59 + 4 61 + 5 61 + 6 61 + 7 61 + 8 63 + 9 63 + a 63 + b 63 + c 65 + d 65 + e 65 + f 65 + 10 58 + 11 58 + 12 58 + 13 58 + 14 58 + 15 58 + } + + method 'hashCode ()I' { + 0 71 + 1 71 + 2 71 + 3 71 + 4 71 + 5 71 + 6 71 + 8 71 + 9 71 + a 71 + b 71 + c 71 + d 71 + e 71 + f 71 + 10 71 + 11 71 + 12 71 + 13 71 + 15 71 + 16 71 + 17 71 + 18 71 + 19 72 + 1a 72 + 1b 72 + 1c 72 + 1d 72 + 1e 72 + 1f 72 + 20 71 + 22 71 + 23 74 + 24 74 + 25 70 + 26 75 + 27 75 + 28 75 + 29 75 + 2a 75 + 2d 75 + 31 75 + 32 75 + 33 75 + 34 75 + 35 75 + 38 70 + 3b 70 + } + + method 'equals (Ljava/lang/Object;)Z' { + 0 79 + 1 79 + 2 79 + 5 80 + 6 80 + 7 81 + b 81 + e 82 + f 82 + 10 84 + 11 84 + 12 84 + 13 84 + 14 84 + 15 85 + 16 85 + 17 85 + 18 85 + 19 85 + 1a 85 + 1b 85 + 1c 85 + 20 85 + 23 86 + 24 86 + 25 87 + 26 87 + 27 87 + 28 87 + 29 87 + 2a 87 + 2b 87 + 2c 87 + 2d 87 + 30 88 + 31 88 + 32 89 + 33 89 + 34 89 + 35 89 + 36 89 + 37 89 + 38 89 + 39 89 + 3d 89 + 40 90 + 41 90 + 42 92 + 43 92 + 44 92 + 45 92 + 46 92 + 47 92 + 48 92 + 49 92 + 4d 92 + } +} + +Lines mapping: +3 <-> 14 +4 <-> 10 +5 <-> 11 +6 <-> 12 +7 <-> 13 diff --git a/plugins/kotlin/testData/results/pkg/TestDestructors.dec b/plugins/kotlin/testData/results/pkg/TestDestructors.dec new file mode 100644 index 0000000000..cf9eb96cff --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestDestructors.dec @@ -0,0 +1,908 @@ +package pkg + +import kotlin.jvm.functions.Function0 + +class TestDestructors { + public fun destructDataClasses(x: Pair, y: Triple) { + System.out.println(x.component1() as java.lang.String + " " + x.component2() as Integer);// 8 9 + System.out.println(y.component1() as java.lang.Number + " " + y.component2() as java.lang.Boolean + " " + y.component3() as java.lang.String);// 11 12 + }// 13 + + public fun destructDataClassesSpecial(x: Pair, y: Triple, Nothing?, Unit>) { + System.out.println((x.component1() as java.lang.Number).intValue() + " " + x.component2() as java.lang.String);// 19 20 + var c: java.util.List = y.component1() as java.util.List;// 22 + var d: Void = y.component2() as Void; + y.component3(); + System.out.println(c + " " + d + " " + Unit.INSTANCE);// 23 + }// 24 + + public fun destructDataClassesSkip(x: Triple, y: Triple) { + System.out.println(x.component2() as Integer);// 30 31 + System.out.println(y.component1() as java.lang.Number + " " + y.component3() as java.lang.String);// 33 34 + }// 35 + + public fun destructorImpossible(x: Pair): String { + var a: java.lang.String = x.component1() as java.lang.String;// 38 + x.component2(); + throw new KotlinNothingValueException(); + } + + public fun destructExtensionFunction(x: Int) { + System.out.println("" + this.component1(x) + this.component2(x) + this.component3(x));// 44 45 + }// 46 + + public inline fun destructInlineLambda(x: () -> Int) { + System.out + .println( + "" + + this.component1((x.invoke() as java.lang.Number).intValue())// 49 50 85 + + this.component2((x.invoke() as java.lang.Number).intValue())// 86 + + this.component3((x.invoke() as java.lang.Number).intValue())// 87 + ); + }// 51 + + public fun callDestructInlineLambda() { + System.out.println("" + this.component1(123) + this.component2(123) + this.component3(123));// 54 88 89 90 91 92 + }// 55 + + public fun callDestructInlineLambdaWithControlFlow(x: Int) { + if (100 <= x && x < 1000) { + var `a$iv`: Int = this.component1(x);// 95 + if (100 <= x && x < 1000) {// 96 + var `b$iv`: Int = this.component2(x); + if (100 <= x && x < 1000) {// 58 97 + System.out.println("" + `a$iv` + `b$iv` + this.component3(x));// 94 98 + } + } + } + }// 99 + + public fun destructInlineLambdaNoInline(x: () -> Int) { + System.out + .println( + "" + + this.component1((x.invoke() as java.lang.Number).intValue())// 62 63 100 + + this.component2((x.invoke() as java.lang.Number).intValue())// 101 + + this.component3((x.invoke() as java.lang.Number).intValue())// 102 + ); + }// 64 + + public fun destructLambdaInline(x: Int) { + var var2: Function0 = new (x);// 67 + System.out + .println( + "" + + this.component1((var2.invoke() as java.lang.Number).intValue())// 68 103 + + this.component2((var2.invoke() as java.lang.Number).intValue())// 104 + + this.component3((var2.invoke() as java.lang.Number).intValue())// 105 + ); + }// 69 + + public operator fun Int.component1(): Int { + return java.lang.String.valueOf(`$this$component1`).charAt(0) - 48;// 77 + } + + public operator fun Int.component2(): Int { + return java.lang.String.valueOf(`$this$component2`).charAt(1) - 48;// 78 + } + + public operator fun Int.component3(): Int { + return java.lang.String.valueOf(`$this$component3`).charAt(2) - 48;// 79 + } + + public inline operator fun (() -> Int).component1(): Int { + return this.component1((`$this$component1`.invoke() as java.lang.Number).intValue());// 81 + } + + public inline operator fun (() -> Int).component2(): Int { + return this.component2((`$this$component2`.invoke() as java.lang.Number).intValue());// 82 + } + + public inline operator fun (() -> Int).component3(): Int { + return this.component3((`$this$component3`.invoke() as java.lang.Number).intValue());// 83 + } +} + +class 'pkg/TestDestructors' { + method 'destructDataClasses (Lkotlin/Pair;Lkotlin/Triple;)V' { + c 6 + d 6 + e 6 + f 6 + 10 6 + 11 6 + 12 6 + 14 6 + 15 6 + 16 6 + 17 6 + 18 6 + 19 6 + 1a 6 + 1d 6 + 1e 6 + 1f 6 + 20 6 + 21 6 + 22 6 + 23 6 + 24 6 + 25 6 + 26 6 + 27 6 + 29 6 + 2a 6 + 2b 6 + 2c 7 + 2d 7 + 2e 7 + 2f 7 + 30 7 + 31 7 + 32 7 + 35 7 + 36 7 + 37 7 + 38 7 + 39 7 + 3a 7 + 3b 7 + 3e 7 + 3f 7 + 40 7 + 41 7 + 42 7 + 43 7 + 44 7 + 47 7 + 48 7 + 49 7 + 4a 7 + 4b 7 + 4c 7 + 4d 7 + 4e 7 + 4f 7 + 50 7 + 51 7 + 52 7 + 53 7 + 54 7 + 56 7 + 57 7 + 58 7 + 59 8 + } + + method 'destructDataClassesSpecial (Lkotlin/Pair;Lkotlin/Triple;)V' { + c 11 + d 11 + e 11 + f 11 + 10 11 + 11 11 + 12 11 + 13 11 + 14 11 + 15 11 + 17 11 + 18 11 + 19 11 + 1a 11 + 1b 11 + 1c 11 + 1d 11 + 20 11 + 21 11 + 22 11 + 23 11 + 24 11 + 25 11 + 26 11 + 27 11 + 28 11 + 29 11 + 2a 11 + 2c 11 + 2d 11 + 2e 11 + 2f 12 + 30 12 + 31 12 + 32 12 + 33 12 + 34 12 + 35 12 + 36 12 + 37 12 + 38 13 + 39 13 + 3a 13 + 3b 13 + 3c 13 + 3d 13 + 3e 13 + 3f 13 + 40 13 + 41 14 + 42 14 + 43 14 + 44 14 + 46 15 + 47 15 + 48 15 + 4b 15 + 4c 15 + 4d 15 + 4e 15 + 4f 15 + 50 15 + 51 15 + 52 15 + 53 15 + 54 15 + 55 15 + 56 15 + 57 15 + 58 15 + 5a 15 + 5b 15 + 5c 15 + 5d 16 + } + + method 'destructDataClassesSkip (Lkotlin/Triple;Lkotlin/Triple;)V' { + c 19 + d 19 + e 19 + f 19 + 10 19 + 11 19 + 12 19 + 14 19 + 15 19 + 16 19 + 17 19 + 18 19 + 19 19 + 1a 19 + 1b 20 + 1c 20 + 1d 20 + 1e 20 + 1f 20 + 20 20 + 21 20 + 24 20 + 25 20 + 26 20 + 27 20 + 28 20 + 29 20 + 2a 20 + 2d 20 + 2e 20 + 2f 20 + 30 20 + 31 20 + 32 20 + 33 20 + 34 20 + 35 20 + 36 20 + 37 20 + 38 20 + 3a 20 + 3b 20 + 3c 20 + 3d 21 + } + + method 'destructorImpossible (Lkotlin/Pair;)Ljava/lang/String;' { + 6 24 + 7 24 + 8 24 + 9 24 + a 24 + b 24 + c 24 + d 24 + e 25 + f 25 + 10 25 + 11 25 + 1a 26 + } + + method 'destructExtensionFunction (I)V' { + 0 30 + 1 30 + 2 30 + 3 30 + 4 30 + 6 30 + 7 30 + 8 30 + 9 30 + a 30 + c 30 + d 30 + e 30 + f 30 + 10 30 + 13 30 + 14 30 + 15 30 + 16 30 + 17 30 + 18 30 + 19 30 + 1a 30 + 1b 30 + 1c 30 + 1d 30 + 1e 30 + 20 30 + 21 30 + 22 30 + 23 31 + } + + method 'destructInlineLambda (Lkotlin/jvm/functions/Function0;)V' { + 8 37 + b 37 + 11 37 + 12 37 + 13 37 + 14 37 + 15 37 + 16 37 + 17 37 + 18 37 + 19 37 + 1a 37 + 1b 37 + 1c 37 + 1d 37 + 1e 37 + 1f 37 + 20 37 + 21 37 + 22 37 + 24 38 + 27 38 + 2d 38 + 2e 38 + 2f 38 + 30 38 + 31 38 + 32 38 + 33 38 + 34 38 + 35 38 + 36 38 + 37 38 + 38 38 + 39 38 + 3a 38 + 3b 38 + 3c 38 + 3d 38 + 3e 38 + 41 39 + 44 39 + 4a 39 + 4b 39 + 4c 39 + 4d 39 + 4e 39 + 4f 39 + 50 39 + 51 39 + 52 39 + 53 39 + 54 39 + 55 39 + 56 39 + 57 39 + 58 39 + 59 39 + 5a 39 + 5b 39 + 5e 37 + 5f 38 + 60 38 + 61 39 + 62 39 + 63 36 + 64 36 + 65 36 + 66 36 + 67 36 + 68 34 + 69 34 + 6a 34 + 6c 35 + 6d 35 + 6e 35 + 6f 41 + } + + method 'callDestructInlineLambda ()V' { + 0 44 + 4 44 + 9 44 + f 44 + 10 44 + 13 44 + 14 44 + 15 44 + 16 44 + 17 44 + 18 44 + 19 44 + 1c 44 + 22 44 + 23 44 + 29 44 + 2a 44 + 2d 44 + 2e 44 + 2f 44 + 30 44 + 31 44 + 32 44 + 33 44 + 35 44 + 3b 44 + 3c 44 + 42 44 + 43 44 + 46 44 + 47 44 + 48 44 + 49 44 + 4a 44 + 4b 44 + 4c 44 + 4f 44 + 50 44 + 51 44 + 52 44 + 53 44 + 54 44 + 55 44 + 56 44 + 57 44 + 58 44 + 59 44 + 5a 44 + 5b 44 + 5d 44 + 5e 44 + 5f 44 + 61 45 + } + + method 'callDestructInlineLambdaWithControlFlow (I)V' { + 0 49 + 4 49 + a 49 + b 49 + 11 48 + 12 48 + 13 48 + 14 48 + 17 48 + 18 48 + 19 48 + 1a 48 + 1b 48 + 27 48 + 2a 49 + 2e 57 + 2f 49 + 30 49 + 31 49 + 32 49 + 33 49 + 34 49 + 35 49 + 36 49 + 37 49 + 38 51 + 39 51 + 3e 51 + 3f 51 + 45 50 + 46 50 + 47 50 + 48 50 + 49 50 + 4d 50 + 4e 50 + 4f 50 + 50 50 + 51 50 + 5d 50 + 60 51 + 64 57 + 65 51 + 66 51 + 67 51 + 68 51 + 69 51 + 6a 51 + 6b 51 + 6c 51 + 6d 51 + 6e 53 + 6f 53 + 74 53 + 75 53 + 7b 52 + 7c 52 + 7d 52 + 7e 52 + 7f 52 + 83 52 + 84 52 + 85 52 + 86 52 + 87 52 + 93 52 + 96 53 + 9a 57 + 9b 53 + 9c 53 + 9d 53 + 9e 53 + 9f 53 + a0 53 + a4 53 + a5 53 + a6 53 + a7 53 + a8 53 + a9 53 + aa 53 + ab 53 + ac 53 + ad 53 + ae 53 + af 53 + b0 53 + b4 53 + b8 57 + } + + method 'destructInlineLambdaNoInline (Lkotlin/jvm/functions/Function0;)V' { + 6 63 + 8 63 + e 63 + f 63 + 10 63 + 11 63 + 12 63 + 13 63 + 14 63 + 15 63 + 16 63 + 17 63 + 18 63 + 19 63 + 1a 63 + 1b 63 + 1c 63 + 1d 63 + 1e 63 + 20 64 + 23 64 + 29 64 + 2a 64 + 2b 64 + 2c 64 + 2d 64 + 2e 64 + 2f 64 + 30 64 + 31 64 + 32 64 + 33 64 + 34 64 + 35 64 + 36 64 + 37 64 + 38 64 + 39 64 + 3a 64 + 3c 65 + 3f 65 + 45 65 + 46 65 + 47 65 + 48 65 + 49 65 + 4a 65 + 4b 65 + 4c 65 + 4d 65 + 4e 65 + 4f 65 + 50 65 + 51 65 + 52 65 + 53 65 + 54 65 + 55 65 + 56 65 + 59 63 + 5a 64 + 5b 65 + 5c 65 + 5d 62 + 5e 62 + 5f 62 + 60 62 + 61 62 + 62 60 + 63 60 + 64 60 + 66 61 + 67 61 + 68 61 + 69 67 + } + + method 'destructLambdaInline (I)V' { + 4 70 + 8 70 + 9 70 + a 70 + b 70 + c 74 + f 74 + 15 74 + 16 74 + 17 74 + 18 74 + 19 74 + 1a 74 + 1b 74 + 1c 74 + 1d 74 + 1e 74 + 1f 74 + 20 74 + 21 74 + 22 74 + 23 74 + 24 74 + 25 74 + 26 74 + 28 75 + 2b 75 + 31 75 + 32 75 + 33 75 + 34 75 + 35 75 + 36 75 + 37 75 + 38 75 + 39 75 + 3a 75 + 3b 75 + 3c 75 + 3d 75 + 3e 75 + 3f 75 + 40 75 + 41 75 + 42 75 + 45 76 + 48 76 + 4e 76 + 4f 76 + 50 76 + 51 76 + 52 76 + 53 76 + 54 76 + 55 76 + 56 76 + 57 76 + 58 76 + 59 76 + 5a 76 + 5b 76 + 5c 76 + 5d 76 + 5e 76 + 5f 76 + 62 74 + 63 75 + 64 75 + 65 76 + 66 76 + 67 73 + 68 73 + 69 73 + 6a 73 + 6b 73 + 6c 71 + 6d 71 + 6e 71 + 70 72 + 71 72 + 72 72 + 73 78 + } + + method 'component1 (I)I' { + 0 81 + 1 81 + 2 81 + 3 81 + 4 81 + 5 81 + 6 81 + 7 81 + 8 81 + 9 81 + a 81 + b 81 + } + + method 'component2 (I)I' { + 0 85 + 1 85 + 2 85 + 3 85 + 4 85 + 5 85 + 6 85 + 7 85 + 8 85 + 9 85 + a 85 + b 85 + } + + method 'component3 (I)I' { + 0 89 + 1 89 + 2 89 + 3 89 + 4 89 + 5 89 + 6 89 + 7 89 + 8 89 + 9 89 + a 89 + b 89 + } + + method 'component1 (Lkotlin/jvm/functions/Function0;)I' { + 8 93 + 9 93 + a 93 + b 93 + c 93 + d 93 + e 93 + f 93 + 10 93 + 11 93 + 12 93 + 13 93 + 14 93 + 15 93 + 16 93 + 17 93 + 18 93 + } + + method 'component2 (Lkotlin/jvm/functions/Function0;)I' { + 8 97 + 9 97 + a 97 + b 97 + c 97 + d 97 + e 97 + f 97 + 10 97 + 11 97 + 12 97 + 13 97 + 14 97 + 15 97 + 16 97 + 17 97 + 18 97 + } + + method 'component3 (Lkotlin/jvm/functions/Function0;)I' { + 8 101 + 9 101 + a 101 + b 101 + c 101 + d 101 + e 101 + f 101 + 10 101 + 11 101 + 12 101 + 13 101 + 14 101 + 15 101 + 16 101 + 17 101 + 18 101 + } +} + +Lines mapping: +8 <-> 7 +9 <-> 7 +11 <-> 8 +12 <-> 8 +13 <-> 9 +19 <-> 12 +20 <-> 12 +22 <-> 13 +23 <-> 16 +24 <-> 17 +30 <-> 20 +31 <-> 20 +33 <-> 21 +34 <-> 21 +35 <-> 22 +38 <-> 25 +44 <-> 31 +45 <-> 31 +46 <-> 32 +49 <-> 38 +50 <-> 38 +51 <-> 42 +54 <-> 45 +55 <-> 46 +58 <-> 53 +62 <-> 64 +63 <-> 64 +64 <-> 68 +67 <-> 71 +68 <-> 75 +69 <-> 79 +77 <-> 82 +78 <-> 86 +79 <-> 90 +81 <-> 94 +82 <-> 98 +83 <-> 102 +85 <-> 38 +86 <-> 39 +87 <-> 40 +88 <-> 45 +89 <-> 45 +90 <-> 45 +91 <-> 45 +92 <-> 45 +94 <-> 54 +95 <-> 50 +96 <-> 51 +97 <-> 53 +98 <-> 54 +99 <-> 58 +100 <-> 64 +101 <-> 65 +102 <-> 66 +103 <-> 75 +104 <-> 76 +105 <-> 77 +Not mapped: +59 +93 diff --git a/plugins/kotlin/testData/results/pkg/TestExtensionFun.dec b/plugins/kotlin/testData/results/pkg/TestExtensionFun.dec new file mode 100644 index 0000000000..1439805c84 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestExtensionFun.dec @@ -0,0 +1,47 @@ +package pkg + +class TestExtensionFun { + public fun CharSequence.repeat2(n: Int): String { + return StringsKt.repeat(`$this$repeat2`, n);// 5 + } + + public fun test() { + System.out.println(this.repeat2("Bye " as java.lang.CharSequence, 2));// 9 + }// 10 +} + +class 'pkg/TestExtensionFun' { + method 'repeat2 (Ljava/lang/CharSequence;I)Ljava/lang/String;' { + 6 4 + 7 4 + 8 4 + 9 4 + a 4 + b 4 + } + + method 'test ()V' { + 0 8 + 1 8 + 2 8 + 3 8 + 4 8 + 5 8 + 6 8 + 7 8 + 8 8 + 9 8 + a 8 + b 8 + c 8 + e 8 + f 8 + 10 8 + 11 9 + } +} + +Lines mapping: +5 <-> 5 +9 <-> 9 +10 <-> 10 diff --git a/plugins/kotlin/testData/results/pkg/TestForRange.dec b/plugins/kotlin/testData/results/pkg/TestForRange.dec new file mode 100644 index 0000000000..c09d5857d9 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestForRange.dec @@ -0,0 +1,669 @@ +package pkg + +import kotlin.internal.ProgressionUtilKt + +class TestForRange { + public fun testInt() { + for (int i = 1; i < 11; i++) {// 5 + System.out.println(i);// 6 + } + }// 8 + + public fun testChar() { + for (char c = 'a'; c < '{'; c++) {// 11 + System.out.println(c);// 12 + } + }// 14 + + public fun testIntStep() { + var i: Int = 1; + var var2: Int = ProgressionUtilKt.getProgressionLastElement(1, 10, 2); + if (1 <= var2) { + while (true) { + System.out.println(i);// 18 + if (i == var2) {// 17 + break; + } + + i += 2; + } + } + }// 20 + + public fun testIntStepX(x: Int) { + if (x <= 0) { + throw new IllegalArgumentException("Step must be positive, was: " + x + "."); + } else { + var i: Int = 1; + var var3: Int = ProgressionUtilKt.getProgressionLastElement(1, 100, x); + if (1 <= var3) { + while (true) { + System.out.println(i);// 24 + if (i == var3) {// 23 + break; + } + + i += x; + } + } + } + }// 26 + + public fun testIntDownTo() { + for (int i = 10; 0 < i; i--) {// 29 + System.out.println(i);// 30 + } + }// 32 + + public fun testIntDownToStep() { + var i: Int = 10; + var var2: Int = ProgressionUtilKt.getProgressionLastElement(10, 1, -2); + if (var2 <= 10) { + while (true) { + System.out.println(i);// 36 + if (i == var2) {// 35 + break; + } + + i -= 2; + } + } + }// 38 + + public fun testIntDownToStepX(x: Int) { + if (x <= 0) { + throw new IllegalArgumentException("Step must be positive, was: " + x + "."); + } else { + var var2: Int = -x; + var i: Int = 100; + var var4: Int = ProgressionUtilKt.getProgressionLastElement(100, 1, var2); + if (var4 <= 100) { + while (true) { + System.out.println(i);// 42 + if (i == var4) {// 41 + break; + } + + i += var2; + } + } + } + }// 44 + + public fun testUntil() { + for (int i = 1; i < 10; i++) {// 47 + System.out.println(i);// 48 + } + }// 50 + + public fun testUntilStep() { + var var1: IntProgression = RangesKt.step(RangesKt.until(1, 100) as IntProgression, 2); + var i: Int = var1.getFirst(); + var var3: Int = var1.getLast(); + var var4: Int = var1.getStep(); + if (var4 > 0 && i <= var3 || var4 < 0 && var3 <= i) { + while (true) { + System.out.println(i);// 54 + if (i == var3) {// 53 + break; + } + + i += var4; + } + } + }// 56 + + public fun testUntilStepX(x: Int) { + var var2: IntProgression = RangesKt.step(RangesKt.until(1, 100) as IntProgression, x); + var i: Int = var2.getFirst(); + var var4: Int = var2.getLast(); + var var5: Int = var2.getStep(); + if (var5 > 0 && i <= var4 || var5 < 0 && var4 <= i) { + while (true) { + System.out.println(i);// 60 + if (i == var4) {// 59 + break; + } + + i += var5; + } + } + }// 62 + + public fun testIntY(x: Int, y: Int) { + var i: Int = x; + if (x <= y) { + while (true) { + System.out.println(i);// 66 + if (i == y) {// 65 + break; + } + + i++; + } + } + }// 68 + + public fun testIntYStep(x: Int, y: Int) { + var i: Int = x; + var var4: Int = ProgressionUtilKt.getProgressionLastElement(x, y, 2); + if (x <= var4) { + while (true) { + System.out.println(i);// 72 + if (i == var4) {// 71 + break; + } + + i += 2; + } + } + }// 74 + + public fun testIntYStepX(x: Int, y: Int, z: Int) { + if (z <= 0) { + throw new IllegalArgumentException("Step must be positive, was: " + z + "."); + } else { + var i: Int = x; + var var5: Int = ProgressionUtilKt.getProgressionLastElement(x, y, z); + if (x <= var5) { + while (true) { + System.out.println(i);// 78 + if (i == var5) {// 77 + break; + } + + i += z; + } + } + } + }// 80 +} + +class 'pkg/TestForRange' { + method 'testInt ()V' { + 0 6 + 1 6 + 2 6 + 3 6 + 4 6 + 5 6 + 8 7 + 9 7 + a 7 + b 7 + c 7 + d 7 + e 7 + f 6 + 10 6 + 11 6 + 15 9 + } + + method 'testChar ()V' { + 0 12 + 1 12 + 2 12 + 3 12 + 4 12 + 5 12 + 6 12 + 9 13 + a 13 + b 13 + c 13 + d 13 + e 13 + f 13 + 10 12 + 12 12 + 18 15 + } + + method 'testIntStep ()V' { + 0 18 + 1 18 + 2 19 + 3 19 + 4 19 + 5 19 + 6 19 + 7 19 + 8 19 + 9 19 + a 20 + b 20 + c 20 + f 22 + 10 22 + 11 22 + 12 22 + 13 22 + 14 22 + 15 22 + 16 23 + 17 23 + 18 23 + 1b 27 + 1c 27 + 1d 27 + 21 30 + } + + method 'testIntStepX (I)V' { + 0 33 + 1 33 + 8 34 + 9 34 + a 34 + b 34 + c 34 + d 34 + 11 34 + 12 36 + 13 36 + 14 37 + 15 37 + 16 37 + 17 37 + 18 37 + 19 37 + 1a 37 + 1b 37 + 1c 38 + 1d 38 + 1e 38 + 21 40 + 22 40 + 23 40 + 24 40 + 25 40 + 26 40 + 27 40 + 28 41 + 29 41 + 2a 41 + 2e 45 + 30 45 + 34 49 + } + + method 'testIntDownTo ()V' { + 0 52 + 1 52 + 2 52 + 3 52 + 4 52 + 5 52 + 8 53 + 9 53 + a 53 + b 53 + c 53 + d 53 + e 53 + f 52 + 10 52 + 11 52 + 15 55 + } + + method 'testIntDownToStep ()V' { + 0 58 + 1 58 + 2 58 + 3 59 + 4 59 + 5 59 + 6 59 + 7 59 + 8 59 + 9 59 + a 59 + b 59 + c 60 + d 60 + e 60 + 11 62 + 12 62 + 13 62 + 14 62 + 15 62 + 16 62 + 17 62 + 18 63 + 19 63 + 1a 63 + 1d 67 + 1e 67 + 1f 67 + 23 70 + } + + method 'testIntDownToStepX (I)V' { + 0 73 + 1 73 + 8 74 + 9 74 + a 74 + b 74 + c 74 + d 74 + 11 74 + 12 76 + 13 76 + 14 76 + 15 77 + 16 77 + 17 77 + 18 78 + 19 78 + 1a 78 + 1b 78 + 1c 78 + 1d 78 + 1e 78 + 1f 78 + 20 78 + 21 79 + 22 79 + 23 79 + 24 79 + 27 81 + 28 81 + 29 81 + 2a 81 + 2b 81 + 2c 81 + 2d 81 + 2e 82 + 2f 82 + 30 82 + 31 82 + 35 86 + 37 86 + 3b 90 + } + + method 'testUntil ()V' { + 0 93 + 1 93 + 2 93 + 3 93 + 4 93 + 5 93 + 8 94 + 9 94 + a 94 + b 94 + c 94 + d 94 + e 94 + f 93 + 10 93 + 11 93 + 15 96 + } + + method 'testUntilStep ()V' { + 0 99 + 1 99 + 2 99 + 3 99 + 4 99 + 5 99 + 6 99 + 7 99 + 8 99 + 9 99 + a 99 + b 99 + c 99 + d 99 + e 100 + f 100 + 10 100 + 11 100 + 12 100 + 13 101 + 14 101 + 15 101 + 16 101 + 17 101 + 18 102 + 19 102 + 1a 102 + 1b 102 + 1c 102 + 1d 102 + 1e 103 + 1f 103 + 20 103 + 23 103 + 24 103 + 25 103 + 28 103 + 29 103 + 2a 103 + 2d 103 + 2e 103 + 2f 103 + 32 105 + 33 105 + 34 105 + 35 105 + 36 105 + 37 105 + 38 105 + 39 106 + 3a 106 + 3b 106 + 3f 110 + 40 110 + 42 110 + 46 113 + } + + method 'testUntilStepX (I)V' { + 0 116 + 1 116 + 2 116 + 3 116 + 4 116 + 5 116 + 6 116 + 7 116 + 8 116 + 9 116 + a 116 + b 116 + c 116 + d 116 + e 117 + f 117 + 10 117 + 11 117 + 12 117 + 13 118 + 14 118 + 15 118 + 16 118 + 17 118 + 18 118 + 19 119 + 1a 119 + 1b 119 + 1c 119 + 1d 119 + 1e 119 + 1f 120 + 20 120 + 21 120 + 24 120 + 25 120 + 26 120 + 27 120 + 2a 120 + 2b 120 + 2c 120 + 2f 120 + 30 120 + 31 120 + 32 120 + 35 122 + 36 122 + 37 122 + 38 122 + 39 122 + 3a 122 + 3b 122 + 3c 123 + 3d 123 + 3e 123 + 3f 123 + 43 127 + 44 127 + 46 127 + 4a 130 + } + + method 'testIntY (II)V' { + 0 133 + 1 133 + 2 134 + 3 134 + 4 134 + 7 136 + 8 136 + 9 136 + a 136 + b 136 + c 136 + d 136 + e 137 + f 137 + 10 137 + 13 141 + 14 141 + 15 141 + 19 144 + } + + method 'testIntYStep (II)V' { + 0 147 + 1 147 + 2 148 + 3 148 + 4 148 + 5 148 + 6 148 + 7 148 + 8 148 + 9 148 + a 149 + b 149 + c 149 + d 149 + 10 151 + 11 151 + 12 151 + 13 151 + 14 151 + 15 151 + 16 151 + 17 152 + 18 152 + 19 152 + 1a 152 + 1d 156 + 1e 156 + 1f 156 + 23 159 + } + + method 'testIntYStepX (III)V' { + 0 162 + 1 162 + 8 163 + 9 163 + a 163 + b 163 + c 163 + d 163 + 11 163 + 12 165 + 13 165 + 14 165 + 15 166 + 16 166 + 17 166 + 18 166 + 19 166 + 1a 166 + 1b 166 + 1c 166 + 1d 167 + 1e 167 + 1f 167 + 20 167 + 21 167 + 24 169 + 25 169 + 26 169 + 27 169 + 28 169 + 29 169 + 2a 169 + 2b 169 + 2c 170 + 2d 170 + 2e 170 + 2f 170 + 30 170 + 35 174 + 37 174 + 38 174 + 3c 178 + } +} + +Lines mapping: +5 <-> 7 +6 <-> 8 +8 <-> 10 +11 <-> 13 +12 <-> 14 +14 <-> 16 +17 <-> 24 +18 <-> 23 +20 <-> 31 +23 <-> 42 +24 <-> 41 +26 <-> 50 +29 <-> 53 +30 <-> 54 +32 <-> 56 +35 <-> 64 +36 <-> 63 +38 <-> 71 +41 <-> 83 +42 <-> 82 +44 <-> 91 +47 <-> 94 +48 <-> 95 +50 <-> 97 +53 <-> 107 +54 <-> 106 +56 <-> 114 +59 <-> 124 +60 <-> 123 +62 <-> 131 +65 <-> 138 +66 <-> 137 +68 <-> 145 +71 <-> 153 +72 <-> 152 +74 <-> 160 +77 <-> 171 +78 <-> 170 +80 <-> 179 diff --git a/plugins/kotlin/testData/results/pkg/TestFunVarargs.dec b/plugins/kotlin/testData/results/pkg/TestFunVarargs.dec new file mode 100644 index 0000000000..84b88115fd --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestFunVarargs.dec @@ -0,0 +1,123 @@ +package pkg + +import java.util.Arrays + +class TestFunVarargs { + public fun printAll(vararg messages: String) { + for (java.lang.String m : messages) {// 5 + System.out.println(m); + } + }// 6 + + public fun printAllArray(messages: Array) { + for (java.lang.String m : messages) {// 9 + System.out.println(m); + } + }// 10 + + public fun log(vararg entries: String) { + this.printAll(Arrays.copyOf(entries, entries.length) as java.lang.String[]);// 13 + this.printAllArray(entries);// 14 + }// 15 + + public fun test() { + this.log("a", "b", "c");// 18 + }// 19 + + public fun nestedArrays(e0: Array, e1: Array, e2: Array>>) { + }// 23 +} + +class 'pkg/TestFunVarargs' { + method 'printAll ([Ljava/lang/String;)V' { + 7 6 + 8 6 + a 6 + 13 6 + 14 6 + 15 7 + 16 7 + 17 7 + 18 7 + 19 7 + 1a 7 + 1b 7 + 1c 7 + 1d 6 + 1e 6 + 1f 6 + 23 9 + } + + method 'printAllArray ([Ljava/lang/String;)V' { + 7 12 + 8 12 + a 12 + 13 12 + 14 12 + 15 13 + 16 13 + 17 13 + 18 13 + 19 13 + 1a 13 + 1b 13 + 1c 13 + 1d 12 + 1e 12 + 1f 12 + 23 15 + } + + method 'log ([Ljava/lang/String;)V' { + 6 18 + 7 18 + 8 18 + 9 18 + a 18 + b 18 + c 18 + d 18 + e 18 + f 18 + 10 18 + 11 18 + 12 18 + 13 19 + 14 19 + 15 19 + 16 19 + 17 19 + 18 20 + } + + method 'test ()V' { + 0 23 + 8 23 + 9 23 + d 23 + e 23 + 12 23 + 13 23 + 16 23 + 17 23 + 18 23 + 19 24 + } + + method 'nestedArrays ([Ljava/lang/String;[Ljava/lang/Object;[[[Ljava/lang/String;)V' { + 12 27 + } +} + +Lines mapping: +5 <-> 7 +6 <-> 10 +9 <-> 13 +10 <-> 16 +13 <-> 19 +14 <-> 20 +15 <-> 21 +18 <-> 24 +19 <-> 25 +23 <-> 28 diff --git a/plugins/kotlin/testData/results/pkg/TestFuncRef.dec b/plugins/kotlin/testData/results/pkg/TestFuncRef.dec new file mode 100644 index 0000000000..b9060c2077 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestFuncRef.dec @@ -0,0 +1,68 @@ +package pkg + +public fun accept(f: (Int) -> String) { + System.out.println(f.invoke(5)); +}// 3 + +public fun function(r: Int): String { + return StringsKt.repeat("OK" as java.lang.CharSequence, r);// 6 +} + +public fun test() { + accept(.INSTANCE);// 10 +}// 11 + + +class 'pkg/TestFuncRefKt' { + method 'accept (Lkotlin/jvm/functions/Function1;)V' { + 6 3 + 7 3 + 8 3 + 9 3 + a 3 + b 3 + c 3 + d 3 + e 3 + f 3 + 10 3 + 11 3 + 12 3 + 14 3 + 15 3 + 16 3 + 17 4 + } + + method 'function (I)Ljava/lang/String;' { + 0 7 + 1 7 + 2 7 + 3 7 + 4 7 + 5 7 + 6 7 + 7 7 + 8 7 + 9 7 + } + + method 'test ()V' { + 0 11 + 1 11 + 2 11 + 3 11 + 4 11 + 5 11 + 6 11 + 7 11 + 8 11 + 9 12 + } +} + +Lines mapping: +3 <-> 5 +6 <-> 8 +10 <-> 12 +11 <-> 13 diff --git a/plugins/kotlin/testData/results/pkg/TestGenerics.dec b/plugins/kotlin/testData/results/pkg/TestGenerics.dec new file mode 100644 index 0000000000..7a8687b667 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestGenerics.dec @@ -0,0 +1,51 @@ +package pkg + +class TestGenerics { + public fun genericFun(v: T): T { + return (T)v;// 5 + } + + public fun nullableGeneric(v: T): T? { + return null;// 9 + } + + public fun subType(v: TestGenerics) { + }// 13 + + public fun superType(v: TestGenerics) { + }// 16 + + public fun any(v: TestGenerics<*>) { + }// 19 +} + +class 'pkg/TestGenerics' { + method 'genericFun (Ljava/lang/Object;)Ljava/lang/Object;' { + 0 4 + 1 4 + } + + method 'nullableGeneric (Ljava/lang/Object;)Ljava/lang/Object;' { + 0 8 + 1 8 + } + + method 'subType (Lpkg/TestGenerics;)V' { + 6 12 + } + + method 'superType (Lpkg/TestGenerics;)V' { + 6 15 + } + + method 'any (Lpkg/TestGenerics;)V' { + 6 18 + } +} + +Lines mapping: +5 <-> 5 +9 <-> 9 +13 <-> 13 +16 <-> 16 +19 <-> 19 diff --git a/plugins/kotlin/testData/results/pkg/TestIfRange.dec b/plugins/kotlin/testData/results/pkg/TestIfRange.dec new file mode 100644 index 0000000000..b1b89609e5 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestIfRange.dec @@ -0,0 +1,381 @@ +package pkg + +class TestIfRange { + public fun testInt(x: Int) { + if (1 <= x && x < 11) {// 5 + System.out.println(x);// 6 + } + }// 8 + + public fun testChar(x: Char) { + if ('a' <= x && x < '{') {// 11 + System.out.println(x);// 12 + } + }// 14 + + public fun testInvertedInt(x: Int) { + if (1 > x || x >= 11) {// 17 + System.out.println(x);// 18 + } + }// 20 + + public fun testIntStep(x: Int) { + if (CollectionsKt.contains(RangesKt.step((new IntRange(1, 100)) as IntProgression, 2) as java.lang.Iterable, x)) {// 23 + System.out.println(x);// 24 + } + }// 26 + + public fun testIntStepY(x: Int, y: Int) { + if (CollectionsKt.contains(RangesKt.step((new IntRange(1, 100)) as IntProgression, y) as java.lang.Iterable, x)) {// 28 + System.out.println(x);// 29 + } + }// 31 + + public fun testIntY(x: Int, y: Int) { + if (1 <= x && x <= y) {// 34 + System.out.println(x);// 35 + } + }// 37 + + public fun testIntDownTo(x: Int) { + if (CollectionsKt.contains(RangesKt.downTo(10, 1) as java.lang.Iterable, x)) {// 40 + System.out.println(x);// 41 + } + }// 43 + + public fun testIntDownToStep(x: Int) { + if (CollectionsKt.contains(RangesKt.step(RangesKt.downTo(10, 1), 2) as java.lang.Iterable, x)) {// 46 + System.out.println(x);// 47 + } + }// 49 + + public fun testIntUntil(x: Int) { + if (1 <= x && x < 10) {// 52 + System.out.println(x);// 53 + } + }// 55 + + public fun testIntUntilStep(x: Int) { + if (CollectionsKt.contains(RangesKt.step(RangesKt.until(1, 100) as IntProgression, 2) as java.lang.Iterable, x)) {// 58 + System.out.println(x);// 59 + } + }// 61 + + public fun testIntUntilY(x: Int, y: Int) { + if (1 <= x && x < y) {// 64 + System.out.println(x);// 65 + } + }// 67 + + public fun testIntUntilSelf(x: Int) { + if (1 <= x && x < x) {// 70 + System.out.println(x);// 71 + } + }// 73 +} + +class 'pkg/TestIfRange' { + method 'testInt (I)V' { + 0 4 + 1 4 + 2 4 + 5 4 + 6 4 + 7 4 + 8 4 + 14 4 + 17 5 + 18 5 + 19 5 + 1a 5 + 1b 5 + 1e 7 + } + + method 'testChar (C)V' { + 0 10 + 1 10 + 2 10 + 3 10 + 6 10 + 7 10 + 8 10 + 9 10 + 15 10 + 18 11 + 19 11 + 1a 11 + 1b 11 + 1c 11 + 1f 13 + } + + method 'testInvertedInt (I)V' { + 0 16 + 1 16 + 2 16 + 5 16 + 6 16 + 7 16 + 8 16 + 14 16 + 17 17 + 18 17 + 19 17 + 1a 17 + 1b 17 + 1e 19 + } + + method 'testIntStep (I)V' { + 4 22 + 5 22 + 6 22 + a 22 + b 22 + c 22 + d 22 + e 22 + f 22 + 10 22 + 11 22 + 12 22 + 13 22 + 14 22 + 15 22 + 16 22 + 17 22 + 18 22 + 19 22 + 1a 22 + 1b 22 + 1e 23 + 1f 23 + 20 23 + 21 23 + 22 23 + 25 25 + } + + method 'testIntStepY (II)V' { + 4 28 + 5 28 + 6 28 + a 28 + b 28 + c 28 + d 28 + e 28 + f 28 + 10 28 + 11 28 + 12 28 + 13 28 + 14 28 + 15 28 + 16 28 + 17 28 + 18 28 + 19 28 + 1a 28 + 1b 28 + 1e 29 + 1f 29 + 20 29 + 21 29 + 22 29 + 25 31 + } + + method 'testIntY (II)V' { + 0 34 + 1 34 + 2 34 + 5 34 + 6 34 + 7 34 + 13 34 + 16 35 + 17 35 + 18 35 + 19 35 + 1a 35 + 1d 37 + } + + method 'testIntDownTo (I)V' { + 0 40 + 1 40 + 2 40 + 3 40 + 4 40 + 5 40 + 6 40 + 7 40 + 8 40 + 9 40 + a 40 + b 40 + c 40 + d 40 + e 40 + f 40 + 10 40 + 13 41 + 14 41 + 15 41 + 16 41 + 17 41 + 1a 43 + } + + method 'testIntDownToStep (I)V' { + 0 46 + 1 46 + 2 46 + 3 46 + 4 46 + 5 46 + 6 46 + 7 46 + 8 46 + 9 46 + a 46 + b 46 + c 46 + d 46 + e 46 + f 46 + 10 46 + 11 46 + 12 46 + 13 46 + 14 46 + 17 47 + 18 47 + 19 47 + 1a 47 + 1b 47 + 1e 49 + } + + method 'testIntUntil (I)V' { + 0 52 + 1 52 + 2 52 + 5 52 + 6 52 + 7 52 + 8 52 + 14 52 + 17 53 + 18 53 + 19 53 + 1a 53 + 1b 53 + 1e 55 + } + + method 'testIntUntilStep (I)V' { + 0 58 + 1 58 + 2 58 + 3 58 + 4 58 + 5 58 + 6 58 + 7 58 + 8 58 + 9 58 + a 58 + b 58 + c 58 + d 58 + e 58 + f 58 + 10 58 + 11 58 + 12 58 + 13 58 + 14 58 + 15 58 + 16 58 + 17 58 + 1a 59 + 1b 59 + 1c 59 + 1d 59 + 1e 59 + 21 61 + } + + method 'testIntUntilY (II)V' { + 0 64 + 1 64 + 2 64 + 5 64 + 6 64 + 7 64 + 13 64 + 16 65 + 17 65 + 18 65 + 19 65 + 1a 65 + 1d 67 + } + + method 'testIntUntilSelf (I)V' { + 0 70 + 1 70 + 2 70 + 5 70 + 6 70 + 7 70 + 13 70 + 16 71 + 17 71 + 18 71 + 19 71 + 1a 71 + 1d 73 + } +} + +Lines mapping: +5 <-> 5 +6 <-> 6 +8 <-> 8 +11 <-> 11 +12 <-> 12 +14 <-> 14 +17 <-> 17 +18 <-> 18 +20 <-> 20 +23 <-> 23 +24 <-> 24 +26 <-> 26 +28 <-> 29 +29 <-> 30 +31 <-> 32 +34 <-> 35 +35 <-> 36 +37 <-> 38 +40 <-> 41 +41 <-> 42 +43 <-> 44 +46 <-> 47 +47 <-> 48 +49 <-> 50 +52 <-> 53 +53 <-> 54 +55 <-> 56 +58 <-> 59 +59 <-> 60 +61 <-> 62 +64 <-> 65 +65 <-> 66 +67 <-> 68 +70 <-> 71 +71 <-> 72 +73 <-> 74 diff --git a/plugins/kotlin/testData/results/pkg/TestInfixFun.dec b/plugins/kotlin/testData/results/pkg/TestInfixFun.dec new file mode 100644 index 0000000000..abfe471ee7 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestInfixFun.dec @@ -0,0 +1,129 @@ +package pkg + +class TestInfixFun { + public fun test() { + System.out.println(test$times(2, "Bye "));// 7 + }// 8 + + public infix fun Int.mult(str: String): String { + return StringsKt.repeat(str as java.lang.CharSequence, `$this$mult`);// 10 + } + + public fun testOuter() { + System.out.println(this.mult(2, "Bye "));// 14 + }// 15 + + public fun testDuplicate() { + System.out.println(testDuplicate$mult(2, "Bye "));// 20 + }// 21 + + @JvmStatic + fun Int.`test$times`(str: java.lang.String): java.lang.String { + return StringsKt.repeat(str as java.lang.CharSequence, `$this$test_u24times`);// 5 + } + + @JvmStatic + fun Int.`testDuplicate$mult`(str: java.lang.String): java.lang.String { + return StringsKt.repeat(str as java.lang.CharSequence, `$this$testDuplicate_u24mult` + 1);// 18 + } +} + +class 'pkg/TestInfixFun' { + method 'test ()V' { + 0 4 + 1 4 + 2 4 + 3 4 + 4 4 + 5 4 + 6 4 + 7 4 + 8 4 + a 4 + b 4 + c 4 + d 5 + } + + method 'mult (ILjava/lang/String;)Ljava/lang/String;' { + 6 8 + 7 8 + 8 8 + 9 8 + a 8 + b 8 + c 8 + d 8 + e 8 + } + + method 'testOuter ()V' { + 0 12 + 1 12 + 2 12 + 3 12 + 4 12 + 5 12 + 6 12 + 7 12 + 8 12 + 9 12 + b 12 + c 12 + d 12 + e 13 + } + + method 'testDuplicate ()V' { + 0 16 + 1 16 + 2 16 + 3 16 + 4 16 + 5 16 + 6 16 + 7 16 + 8 16 + a 16 + b 16 + c 16 + d 17 + } + + method 'test$times (ILjava/lang/String;)Ljava/lang/String;' { + 0 21 + 1 21 + 2 21 + 3 21 + 4 21 + 5 21 + 6 21 + 7 21 + 8 21 + } + + method 'testDuplicate$mult (ILjava/lang/String;)Ljava/lang/String;' { + 0 26 + 1 26 + 2 26 + 3 26 + 4 26 + 5 26 + 6 26 + 7 26 + 8 26 + 9 26 + a 26 + } +} + +Lines mapping: +5 <-> 22 +7 <-> 5 +8 <-> 6 +10 <-> 9 +14 <-> 13 +15 <-> 14 +18 <-> 27 +20 <-> 17 +21 <-> 18 diff --git a/plugins/kotlin/testData/results/pkg/TestKotlinTypes.dec b/plugins/kotlin/testData/results/pkg/TestKotlinTypes.dec new file mode 100644 index 0000000000..4f22fe26c0 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestKotlinTypes.dec @@ -0,0 +1,21 @@ +package pkg + +import kotlin.jvm.functions.Function1 + +class TestKotlinTypes { + public final val consumer: (Int) -> Unit = .INSTANCE as Function1 + + + public fun throwAlways(): Nothing { + throw new Exception();// 5 + } +} + +class 'pkg/TestKotlinTypes' { + method 'throwAlways ()Ljava/lang/Void;' { + 7 9 + } +} + +Lines mapping: +5 <-> 10 diff --git a/plugins/kotlin/testData/results/pkg/TestKt.dec b/plugins/kotlin/testData/results/pkg/TestKt.dec new file mode 100644 index 0000000000..df4e6f6630 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestKt.dec @@ -0,0 +1,25 @@ +package pkg + +class TestKt { + public fun test() { + System.out.println("Hello, world!");// 5 + }// 6 +} + +class 'pkg/TestKt' { + method 'test ()V' { + 0 4 + 1 4 + 2 4 + 3 4 + 4 4 + 6 4 + 7 4 + 8 4 + 9 5 + } +} + +Lines mapping: +5 <-> 5 +6 <-> 6 diff --git a/plugins/kotlin/testData/results/pkg/TestLabeledJumps.dec b/plugins/kotlin/testData/results/pkg/TestLabeledJumps.dec new file mode 100644 index 0000000000..b6d221b59c --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestLabeledJumps.dec @@ -0,0 +1,170 @@ +package pkg + +class TestLabeledJumps { + public fun testContinue(tester: (Int) -> Boolean) { + label24: + for (int i = 1; i < 101; i++) {// 5 + for (int j = 1; j < 101; j++) {// 6 + if (tester.invoke(j) as java.lang.Boolean) {// 7 + continue label24; + } + + System.out.println(j + " " + i);// 11 + } + + System.out.println("loop");// 14 + } + }// 16 + + public fun testBreak(tester: (Int) -> Boolean) { + label22: + for (int i = 1; i < 101; i++) {// 19 + for (int j = 1; j < 101; j++) {// 20 + if (tester.invoke(j) as java.lang.Boolean) {// 21 + break label22; + } + + System.out.println(j + " " + i);// 25 + } + } + + System.out.println("end");// 29 + }// 30 +} + +class 'pkg/TestLabeledJumps' { + method 'testContinue (Lkotlin/jvm/functions/Function1;)V' { + 6 5 + 7 5 + 8 5 + 9 5 + a 5 + b 5 + e 6 + f 6 + 10 6 + 11 6 + 12 6 + 13 6 + 16 7 + 17 7 + 18 7 + 19 7 + 1a 7 + 1b 7 + 1c 7 + 1d 7 + 1e 7 + 1f 7 + 20 7 + 21 7 + 22 7 + 23 7 + 24 7 + 25 7 + 26 7 + 2c 11 + 2d 11 + 2e 11 + 2f 11 + 30 11 + 31 11 + 32 11 + 33 11 + 34 11 + 35 11 + 37 11 + 38 11 + 39 11 + 3a 6 + 3b 6 + 3c 6 + 40 14 + 41 14 + 42 14 + 43 14 + 44 14 + 46 14 + 49 5 + 4a 5 + 4b 5 + 4f 16 + } + + method 'testBreak (Lkotlin/jvm/functions/Function1;)V' { + 6 20 + 7 20 + 8 20 + 9 20 + a 20 + b 20 + e 21 + f 21 + 10 21 + 11 21 + 12 21 + 13 21 + 16 22 + 17 22 + 18 22 + 19 22 + 1a 22 + 1b 22 + 1c 22 + 1d 22 + 1e 22 + 1f 22 + 20 22 + 21 22 + 22 22 + 23 22 + 24 22 + 25 22 + 26 22 + 2c 26 + 2d 26 + 2e 26 + 2f 26 + 30 26 + 31 26 + 32 26 + 33 26 + 34 26 + 35 26 + 37 26 + 38 26 + 39 26 + 3a 21 + 3b 21 + 3c 21 + 40 20 + 41 20 + 42 20 + 46 30 + 47 30 + 48 30 + 49 30 + 4a 30 + 4c 30 + 4d 30 + 4e 30 + 4f 31 + } +} + +Lines mapping: +5 <-> 6 +6 <-> 7 +7 <-> 8 +11 <-> 12 +14 <-> 15 +16 <-> 17 +19 <-> 21 +20 <-> 22 +21 <-> 23 +25 <-> 27 +29 <-> 31 +30 <-> 32 +Not mapped: +8 +22 diff --git a/plugins/kotlin/testData/results/pkg/TestNonInlineLambda.dec b/plugins/kotlin/testData/results/pkg/TestNonInlineLambda.dec new file mode 100644 index 0000000000..5854635b22 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestNonInlineLambda.dec @@ -0,0 +1,601 @@ +package pkg + +import kotlin.jvm.internal.Ref.IntRef +import kotlin.jvm.internal.Ref.ObjectRef + +open class TestNonInlineLambda { + public final var intField: Int + internal set + private final var privateIntField: Int + private final var privateStringField: String = "" + public final var stringField: String = "" + internal set + + + public fun testCaptureInt(x: Int) { + this.execute(new (x));// 7 8 + }// 11 + + public fun testCaptureObject(x: String) { + this.execute(new (x));// 14 15 + }// 18 + + public fun testCaptureIntIterationValue(x: Iterable) { + var var2: java.util.Iterator = x.iterator();// 21 + + while (var2.hasNext()) { + this.execute(new ((var2.next() as java.lang.Number).intValue()));// 22 + } + }// 26 + + public fun testCaptureObjectIterationValue(x: Iterable) { + for (java.lang.String i : x) {// 29 + this.execute(new (i));// 30 + } + }// 34 + + public fun testCaptureMutableInt(x: Int) { + var y: IntRef = new IntRef();// 37 + y.element = x; + this.execute(new (y));// 38 + var var3: Int = y.element++;// 41 + this.execute(new (y));// 42 + y.element *= 500;// 45 + this.execute(new (y));// 46 + y.element = 100;// 49 + this.execute(new (y));// 50 + y.element += x;// 53 + this.execute(new (y));// 54 + }// 57 + + public fun testCaptureMutableObject(x: String) { + var y: ObjectRef = new ObjectRef();// 60 + y.element = x; + this.execute(new (y));// 61 + y.element = y.element + "!!";// 64 + this.execute(new (y));// 65 + y.element = "" + y.element + y.element + y.element;// 68 + this.execute(new (y));// 69 + y.element = "Hello: ";// 72 + this.execute(new (y));// 73 + y.element = y.element + x;// 76 + this.execute(new (y));// 77 + }// 80 + + public fun testCaptureAndMutateInt(x: Int) { + var y: IntRef = new IntRef();// 83 + this.execute(new (y));// 84 + y.element = 5 + x;// 89 + this.execute(new (y));// 90 + }// 95 + + public fun testCaptureAndMutateString(x: String) { + var y: ObjectRef = new ObjectRef();// 98 + y.element = ""; + this.execute(new (y));// 99 + y.element = "Hello: " + x;// 105 + this.execute(new (y));// 106 + }// 112 + + public fun testCapturePublicMutableIntField() { + this.execute(new (this));// 117 + }// 118 + + public fun testCapturePublicMutableStringField() { + this.execute(new (this));// 123 + }// 124 + + public fun testCapturePrivateMutableIntField() { + this.execute(new (this));// 129 + }// 130 + + public fun testCapturePrivateMutableStringField() { + this.execute(new (this));// 135 + }// 136 + + public open fun execute(block: () -> Unit) { + }// 141 + + @JvmStatic + @JvmSynthetic + fun `access$getPrivateIntField$p`(`$this`: TestNonInlineLambda): Int { + return `$this`.privateIntField; + } + + @JvmStatic + @JvmSynthetic + fun `access$setPrivateIntField$p`(`$this`: TestNonInlineLambda, var1: Int) { + `$this`.privateIntField = var1; + } + + @JvmStatic + @JvmSynthetic + fun `access$setPrivateStringField$p`(`$this`: TestNonInlineLambda, var1: java.lang.String) { + `$this`.privateStringField = var1; + } + + @JvmStatic + @JvmSynthetic + fun `access$getPrivateStringField$p`(`$this`: TestNonInlineLambda): java.lang.String { + return `$this`.privateStringField;// 3 + } +} + +class 'pkg/TestNonInlineLambda' { + method 'testCaptureInt (I)V' { + 0 15 + 2 15 + 7 15 + b 15 + c 15 + d 15 + e 15 + f 15 + 10 15 + 11 16 + } + + method 'testCaptureObject (Ljava/lang/String;)V' { + 6 19 + 8 19 + d 19 + 11 19 + 12 19 + 13 19 + 14 19 + 15 19 + 16 19 + 17 20 + } + + method 'testCaptureIntIterationValue (Ljava/lang/Iterable;)V' { + 6 23 + 7 23 + 8 23 + 9 23 + a 23 + b 23 + c 23 + d 25 + e 25 + f 25 + 10 25 + 11 25 + 12 25 + 16 26 + 17 26 + 18 26 + 19 26 + 1a 26 + 1b 26 + 1c 26 + 1d 26 + 1e 26 + 1f 26 + 20 26 + 21 26 + 23 26 + 28 26 + 2c 26 + 2d 26 + 2e 26 + 2f 26 + 30 26 + 31 26 + 35 28 + } + + method 'testCaptureObjectIterationValue (Ljava/lang/Iterable;)V' { + 6 31 + 7 31 + 8 31 + 9 31 + a 31 + b 31 + c 31 + 16 31 + 17 31 + 18 31 + 19 31 + 1a 31 + 1b 31 + 1c 31 + 1d 31 + 1e 31 + 1f 31 + 20 32 + 25 32 + 29 32 + 2a 32 + 2b 32 + 2c 32 + 2d 32 + 2e 32 + 32 34 + } + + method 'testCaptureMutableInt (I)V' { + 7 37 + 8 38 + 9 38 + a 38 + b 38 + c 38 + d 39 + 12 39 + 16 39 + 17 39 + 18 39 + 19 39 + 1a 39 + 1b 39 + 1c 40 + 1d 40 + 1e 40 + 1f 40 + 20 40 + 24 40 + 28 41 + 2d 41 + 31 41 + 32 41 + 33 41 + 34 41 + 35 41 + 36 41 + 37 42 + 3c 42 + 3d 42 + 3e 42 + 40 42 + 41 42 + 42 42 + 43 43 + 48 43 + 4c 43 + 4d 43 + 4e 43 + 4f 43 + 50 43 + 51 43 + 52 44 + 53 44 + 54 44 + 55 44 + 56 44 + 57 44 + 58 45 + 5d 45 + 61 45 + 62 45 + 63 45 + 64 45 + 65 45 + 66 45 + 67 46 + 6c 46 + 6e 46 + 6f 46 + 70 46 + 71 47 + 76 47 + 7a 47 + 7b 47 + 7c 47 + 7d 47 + 7e 47 + 7f 47 + 80 48 + } + + method 'testCaptureMutableObject (Ljava/lang/String;)V' { + d 51 + e 52 + f 52 + 10 52 + 11 52 + 12 52 + 13 53 + 18 53 + 1c 53 + 1d 53 + 1e 53 + 1f 53 + 20 53 + 21 53 + 22 54 + 23 54 + 24 54 + 25 54 + 26 54 + 27 54 + 28 54 + 29 54 + 2a 54 + 2b 54 + 2c 54 + 2d 54 + 2e 54 + 2f 55 + 34 55 + 38 55 + 39 55 + 3a 55 + 3b 55 + 3c 55 + 3d 55 + 3e 56 + 3f 56 + 40 56 + 41 56 + 42 56 + 43 56 + 44 56 + 45 56 + 46 56 + 47 56 + 48 56 + 49 56 + 4a 56 + 4b 56 + 4c 56 + 4d 56 + 4e 56 + 4f 56 + 50 56 + 51 56 + 52 56 + 53 57 + 58 57 + 5c 57 + 5d 57 + 5e 57 + 5f 57 + 60 57 + 61 57 + 62 58 + 63 58 + 64 58 + 65 58 + 66 58 + 67 58 + 68 59 + 6d 59 + 71 59 + 72 59 + 73 59 + 74 59 + 75 59 + 76 59 + 77 60 + 78 60 + 79 60 + 7a 60 + 7b 60 + 7c 60 + 7d 60 + 7e 60 + 7f 60 + 80 60 + 81 60 + 82 60 + 83 60 + 84 60 + 85 61 + 8a 61 + 8e 61 + 8f 61 + 90 61 + 91 61 + 92 61 + 93 61 + 94 62 + } + + method 'testCaptureAndMutateInt (I)V' { + 7 65 + 8 66 + d 66 + 11 66 + 12 66 + 13 66 + 14 66 + 15 66 + 16 66 + 17 67 + 18 67 + 19 67 + 1a 67 + 1b 67 + 1c 67 + 1d 67 + 1e 68 + 23 68 + 27 68 + 28 68 + 29 68 + 2a 68 + 2b 68 + 2c 68 + 2d 69 + } + + method 'testCaptureAndMutateString (Ljava/lang/String;)V' { + d 72 + e 73 + f 73 + 10 73 + 11 73 + 12 73 + 13 73 + 14 74 + 19 74 + 1d 74 + 1e 74 + 1f 74 + 20 74 + 21 74 + 22 74 + 23 75 + 24 75 + 25 75 + 26 75 + 27 75 + 28 75 + 29 75 + 2a 75 + 2b 75 + 2c 75 + 2d 76 + 32 76 + 36 76 + 37 76 + 38 76 + 39 76 + 3a 76 + 3b 76 + 3c 77 + } + + method 'testCapturePublicMutableIntField ()V' { + 0 80 + 5 80 + 9 80 + a 80 + b 80 + c 80 + d 80 + e 80 + f 81 + } + + method 'testCapturePublicMutableStringField ()V' { + 0 84 + 5 84 + 9 84 + a 84 + b 84 + c 84 + d 84 + e 84 + f 85 + } + + method 'testCapturePrivateMutableIntField ()V' { + 0 88 + 5 88 + 9 88 + a 88 + b 88 + c 88 + d 88 + e 88 + f 89 + } + + method 'testCapturePrivateMutableStringField ()V' { + 0 92 + 5 92 + 9 92 + a 92 + b 92 + c 92 + d 92 + e 92 + f 93 + } + + method 'execute (Lkotlin/jvm/functions/Function0;)V' { + 6 96 + } + + method 'access$getPrivateIntField$p (Lpkg/TestNonInlineLambda;)I' { + 0 101 + 1 101 + 2 101 + 3 101 + 4 101 + } + + method 'access$setPrivateIntField$p (Lpkg/TestNonInlineLambda;I)V' { + 0 107 + 1 107 + 2 107 + 3 107 + 4 107 + 5 108 + } + + method 'access$setPrivateStringField$p (Lpkg/TestNonInlineLambda;Ljava/lang/String;)V' { + 0 113 + 1 113 + 2 113 + 3 113 + 4 113 + 5 114 + } + + method 'access$getPrivateStringField$p (Lpkg/TestNonInlineLambda;)Ljava/lang/String;' { + 0 119 + 1 119 + 2 119 + 3 119 + 4 119 + } +} + +Lines mapping: +3 <-> 120 +7 <-> 16 +8 <-> 16 +11 <-> 17 +14 <-> 20 +15 <-> 20 +18 <-> 21 +21 <-> 24 +22 <-> 27 +26 <-> 29 +29 <-> 32 +30 <-> 33 +34 <-> 35 +37 <-> 38 +38 <-> 40 +41 <-> 41 +42 <-> 42 +45 <-> 43 +46 <-> 44 +49 <-> 45 +50 <-> 46 +53 <-> 47 +54 <-> 48 +57 <-> 49 +60 <-> 52 +61 <-> 54 +64 <-> 55 +65 <-> 56 +68 <-> 57 +69 <-> 58 +72 <-> 59 +73 <-> 60 +76 <-> 61 +77 <-> 62 +80 <-> 63 +83 <-> 66 +84 <-> 67 +89 <-> 68 +90 <-> 69 +95 <-> 70 +98 <-> 73 +99 <-> 75 +105 <-> 76 +106 <-> 77 +112 <-> 78 +117 <-> 81 +118 <-> 82 +123 <-> 85 +124 <-> 86 +129 <-> 89 +130 <-> 90 +135 <-> 93 +136 <-> 94 +141 <-> 97 diff --git a/plugins/kotlin/testData/results/pkg/TestNothingReturns.dec b/plugins/kotlin/testData/results/pkg/TestNothingReturns.dec new file mode 100644 index 0000000000..26e29647a7 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestNothingReturns.dec @@ -0,0 +1,145 @@ +package pkg + +class TestNothingReturns { + public fun loop(): Nothing { + while (true) { + System.out.println("loop");// 6 + } + } + + public fun test1(): Nothing { + this.loop();// 11 + throw new KotlinNothingValueException(); + } + + public fun test2(): Long { + this.test1();// 15 + throw new KotlinNothingValueException(); + } + + public fun test3(i: Int): Int { + if (i == 0) {// 19 + this.loop();// 20 + throw new KotlinNothingValueException(); + } else { + return this.test3(i - 1) + 1;// 23 + } + } + + public fun test4() { + this.loop();// 27 + throw new KotlinNothingValueException(); + } + + public fun test5(s: String): String { + StringsKt.repeat(s as java.lang.CharSequence, 5);// 32 + this.loop(); + throw new KotlinNothingValueException(); + } + + public fun test6(s: String?): String { + if (s == null) {// 36 + this.loop(); + throw new KotlinNothingValueException(); + } else { + return s; + } + } +} + +class 'pkg/TestNothingReturns' { + method 'loop ()Ljava/lang/Void;' { + 1 5 + 2 5 + 3 5 + 4 5 + 5 5 + 7 5 + 8 5 + 9 5 + } + + method 'test1 ()Ljava/lang/Void;' { + 0 10 + 1 10 + 2 10 + 3 10 + c 11 + } + + method 'test2 ()J' { + 0 15 + 1 15 + 2 15 + 3 15 + c 16 + } + + method 'test3 (I)I' { + 0 20 + 1 20 + 4 21 + 5 21 + 6 21 + 7 21 + 10 22 + 11 24 + 12 24 + 13 24 + 14 24 + 15 24 + 16 24 + 17 24 + 18 24 + 19 24 + 1a 24 + } + + method 'test4 ()V' { + 0 29 + 1 29 + 2 29 + 3 29 + c 30 + } + + method 'test5 (Ljava/lang/String;)Ljava/lang/String;' { + 6 34 + 7 34 + 8 34 + 9 34 + a 34 + b 34 + c 34 + d 34 + e 35 + f 35 + 10 35 + 11 35 + 1a 36 + } + + method 'test6 (Ljava/lang/String;)Ljava/lang/String;' { + 0 40 + 2 40 + 6 41 + 7 41 + 8 41 + 9 41 + 12 42 + 13 44 + } +} + +Lines mapping: +6 <-> 6 +11 <-> 11 +15 <-> 16 +19 <-> 21 +20 <-> 22 +23 <-> 25 +27 <-> 30 +32 <-> 35 +36 <-> 41 +Not mapped: +5 diff --git a/plugins/kotlin/testData/results/pkg/TestNullable.dec b/plugins/kotlin/testData/results/pkg/TestNullable.dec new file mode 100644 index 0000000000..5cda97427b --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestNullable.dec @@ -0,0 +1,35 @@ +package pkg + +class TestNullable { + public fun nullableParams(v: String, vn: String?) { + }// 6 + + public fun nullableReturn(): String? { + return null;// 9 + } + + public fun nullableGenerics(v: List): List? { + return v;// 13 + } +} + +class 'pkg/TestNullable' { + method 'nullableParams (Ljava/lang/String;Ljava/lang/String;)V' { + 6 4 + } + + method 'nullableReturn ()Ljava/lang/String;' { + 0 7 + 1 7 + } + + method 'nullableGenerics (Ljava/util/List;)Ljava/util/List;' { + 6 11 + 7 11 + } +} + +Lines mapping: +6 <-> 5 +9 <-> 8 +13 <-> 12 diff --git a/plugins/kotlin/testData/results/pkg/TestNullableOperator.dec b/plugins/kotlin/testData/results/pkg/TestNullableOperator.dec new file mode 100644 index 0000000000..a8636ad41c --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestNullableOperator.dec @@ -0,0 +1,209 @@ +package pkg + +class TestNullableOperator { + public fun test(x: Int?): Int { + return x ?: 0;// 5 + } + + public fun test2(x: String?): String { + var var10000: java.lang.String = x;// 9 + if (x == null) { + var10000 = "default"; + } + + return var10000; + } + + public fun test2_1(x: Any?): Any { + var var10000: Any = x;// 13 + if (x == null) { + var10000 = "default"; + } + + return var10000; + } + + public fun test2_2(x: Any?): Any { + var var10000: Any = x;// 17 + if (x == null) { + var10000 = "default"; + } + + return var10000; + } + + public fun test3(x: Int?): Int { + if (x != null) {// 21 + return x; + } else { + throw new Exception(); + } + } + + public fun test4(x: Exception?) { + if (x != null) {// 25 + x.printStackTrace(); + } + }// 26 + + public fun test5(x: Exception?) { + var var10000: Unit; + if (x != null) {// 29 + x.printStackTrace(); + var10000 = Unit.INSTANCE; + } else { + var10000 = null; + } + + if (var10000 == null) { + throw new Exception(); + } + }// 30 + + public fun test6(x: Int?): Int { + if (x != null) {// 33 + var y: Int = x; + System.out.println(y);// 35 + return y;// 37 + } else { + return 0; + } + } + + public fun test6_1(x: Int?) { + if (x != null) {// 41 + System.out.println(x);// 43 + } + } +} + +class 'pkg/TestNullableOperator' { + method 'test (Ljava/lang/Integer;)I' { + 0 4 + c 4 + d 4 + } + + method 'test2 (Ljava/lang/String;)Ljava/lang/String;' { + 0 8 + 2 9 + 6 10 + 8 13 + } + + method 'test2_1 (Ljava/lang/Object;)Ljava/lang/Object;' { + 0 17 + 2 18 + 6 19 + 8 22 + } + + method 'test2_2 (Ljava/lang/Object;)Ljava/lang/Object;' { + 0 26 + 2 27 + 6 28 + 8 31 + } + + method 'test3 (Ljava/lang/Integer;)I' { + 0 35 + 2 35 + 5 36 + 6 36 + 7 36 + 8 36 + 9 36 + a 36 + b 36 + c 36 + d 36 + e 36 + f 36 + 10 36 + 11 36 + 12 36 + 13 36 + } + + method 'test4 (Ljava/lang/Exception;)V' { + 0 43 + 2 43 + 5 44 + 6 44 + 7 44 + c 46 + } + + method 'test5 (Ljava/lang/Exception;)V' { + 0 50 + 2 50 + 5 51 + 6 51 + 7 51 + 8 52 + 9 52 + a 52 + f 54 + 10 57 + 1a 58 + 1b 60 + } + + method 'test6 (Ljava/lang/Integer;)I' { + 0 63 + 2 63 + 5 64 + 6 64 + 7 64 + 8 64 + 9 64 + a 64 + b 64 + c 64 + d 64 + e 65 + f 65 + 10 65 + 11 65 + 12 65 + 13 66 + 14 66 + 15 66 + 16 66 + } + + method 'test6_1 (Ljava/lang/Integer;)V' { + 0 73 + 2 73 + 5 74 + 6 74 + 7 74 + c 76 + d 74 + e 74 + f 74 + 10 74 + 11 74 + 12 76 + 13 76 + 14 76 + } +} + +Lines mapping: +5 <-> 5 +9 <-> 9 +13 <-> 18 +17 <-> 27 +21 <-> 36 +25 <-> 44 +26 <-> 47 +29 <-> 51 +30 <-> 61 +33 <-> 64 +35 <-> 66 +37 <-> 67 +41 <-> 74 +43 <-> 75 +Not mapped: +44 diff --git a/plugins/kotlin/testData/results/pkg/TestObject.dec b/plugins/kotlin/testData/results/pkg/TestObject.dec new file mode 100644 index 0000000000..3e7e6e42c7 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestObject.dec @@ -0,0 +1,45 @@ +package pkg + +object TestObject private constructor() { + @JvmStatic + public TestObject INSTANCE = new TestObject(); + + public const val objectConstVal: Int = 926 + public final val objectVal: Regex = new Regex("") + private final var objectVar: Int = 42 + + + public fun objectFun() { + objectVar += -1;// 5 + }// 6 + + @JvmStatic + public fun objectJvmStaticFun() { + var var0: Int = objectVar++;// 16 + }// 17 +} + +class 'pkg/TestObject' { + method 'objectFun ()V' { + 5 12 + 7 12 + 8 12 + 9 12 + a 13 + } + + method 'objectJvmStaticFun ()V' { + 4 17 + 5 17 + 6 17 + 7 17 + a 17 + e 18 + } +} + +Lines mapping: +5 <-> 13 +6 <-> 14 +16 <-> 18 +17 <-> 19 diff --git a/plugins/kotlin/testData/results/pkg/TestParams.dec b/plugins/kotlin/testData/results/pkg/TestParams.dec new file mode 100644 index 0000000000..d35e235e6b --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestParams.dec @@ -0,0 +1,145 @@ +package pkg + +class TestParams { + public fun printMessageUnit(message: String) { + System.out.println(message);// 5 + }// 6 + + public fun printMessageVoid(message: String) { + System.out.println(message);// 9 + }// 10 + + public fun multiply(x: Int, y: Int): Int { + return x * y;// 12 + } + + public fun multiplyBraces(x: Int, y: Int): Int { + return x * y;// 15 + } + + public fun printMessageWithPrefix(message: String, prefix: String) { + System.out.println("[" + prefix + "] " + message);// 19 + }// 20 + + @JvmStatic + @JvmSynthetic + fun `printMessageWithPrefix$default`(var0: TestParams, var1: java.lang.String, var2: java.lang.String, var3: Int, var4: Any) { + if ((var3 and 2) != 0) {// 18 + var2 = "Info"; + } + + var0.printMessageWithPrefix(var1, var2); + } + + public fun callPrintMessage() { + printMessageWithPrefix$default(this, "Test", null, 2, null);// 23 + this.printMessageWithPrefix("Test", "Debug");// 24 + }// 25 +} + +class 'pkg/TestParams' { + method 'printMessageUnit (Ljava/lang/String;)V' { + 6 4 + 7 4 + 8 4 + 9 4 + a 4 + b 4 + c 4 + d 5 + } + + method 'printMessageVoid (Ljava/lang/String;)V' { + 6 8 + 7 8 + 8 8 + 9 8 + a 8 + b 8 + c 8 + d 9 + } + + method 'multiply (II)I' { + 0 12 + 1 12 + 2 12 + 3 12 + } + + method 'multiplyBraces (II)I' { + 0 16 + 1 16 + 2 16 + 3 16 + } + + method 'printMessageWithPrefix (Ljava/lang/String;Ljava/lang/String;)V' { + c 20 + d 20 + e 20 + f 20 + 10 20 + 11 20 + 12 20 + 13 20 + 14 20 + 15 20 + 17 20 + 18 20 + 19 20 + 1a 21 + } + + method 'printMessageWithPrefix$default (Lpkg/TestParams;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)V' { + 0 26 + 1 26 + 2 26 + 3 26 + 6 27 + 7 27 + 8 27 + 9 30 + a 30 + b 30 + c 30 + d 30 + e 30 + f 31 + } + + method 'callPrintMessage ()V' { + 0 34 + 1 34 + 2 34 + 3 34 + 4 34 + 5 34 + 6 34 + 7 34 + 8 34 + 9 35 + a 35 + b 35 + c 35 + d 35 + e 35 + f 35 + 10 35 + 11 36 + } +} + +Lines mapping: +5 <-> 5 +6 <-> 6 +9 <-> 9 +10 <-> 10 +12 <-> 13 +15 <-> 17 +18 <-> 27 +19 <-> 21 +20 <-> 22 +23 <-> 35 +24 <-> 36 +25 <-> 37 diff --git a/plugins/kotlin/testData/results/pkg/TestPoorNames.dec b/plugins/kotlin/testData/results/pkg/TestPoorNames.dec new file mode 100644 index 0000000000..2af7540612 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestPoorNames.dec @@ -0,0 +1,73 @@ +package pkg + +class TestPoorNames { + public final val `Dangerous property name?!`: String = "test" + public final val `Property with spaces`: Int = 42 + + + public fun `Function with spaces`() { + }// 5 + + public fun `Dangerous function name?!`() { + }// 8 + + public fun `functionWith$Dollar`() { + }// 14 + + public fun functionWithParameters(`Parameter with spaces`: Int, `Dangerous parameter name?!`: String) { + }// 17 + + public fun test() { + new TestPoorNames.Class with spaces(); + this.Dangerous function name?!();// 23 + this.functionWithParameters(42, "test");// 24 + }// 25 + + class `Class with spaces` { + } +} + +class 'pkg/TestPoorNames' { + method 'Function with spaces ()V' { + 0 8 + } + + method 'Dangerous function name?! ()V' { + 0 11 + } + + method 'functionWith$Dollar ()V' { + 0 14 + } + + method 'functionWithParameters (ILjava/lang/String;)V' { + 6 17 + } + + method 'test ()V' { + 8 21 + 9 21 + a 21 + b 21 + c 22 + d 22 + e 22 + f 22 + 10 22 + 11 22 + 12 22 + 13 22 + 14 23 + } +} + +Lines mapping: +5 <-> 9 +8 <-> 12 +14 <-> 15 +17 <-> 18 +23 <-> 22 +24 <-> 23 +25 <-> 24 +Not mapped: +22 diff --git a/plugins/kotlin/testData/results/pkg/TestReflection.dec b/plugins/kotlin/testData/results/pkg/TestReflection.dec new file mode 100644 index 0000000000..9126567a5b --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestReflection.dec @@ -0,0 +1,133 @@ +package pkg + +import kotlin.jvm.functions.Function1 +import kotlin.reflect.KFunction + +class TestReflection { + public fun testClassReference() { + System.out.println(TestReflection::class);// 5 + System.out.println(TestReflection::class.java);// 6 + }// 7 + + public fun testPrimitiveWrapper() { + System.out.println(Int::class);// 10 + System.out.println(Integer::class.javaObjectType);// 11 + }// 12 + + public fun testPrimitiveType() { + System.out.println(Int::class.javaPrimitiveType);// 15 + }// 16 + + public fun testInferredPrimitive() { + System.out.println(Int::class.javaPrimitiveType);// 19 + }// 20 + + public fun testFunctionReference() { + var f: KFunction = .INSTANCE as KFunction;// 23 + System.out.println(.INSTANCE as KFunction);// 24 + (f as Function1).invoke(new TestReflection());// 25 + }// 26 +} + +class 'pkg/TestReflection' { + method 'testClassReference ()V' { + 6 7 + 7 7 + 8 7 + a 7 + b 7 + c 7 + d 8 + e 8 + 10 8 + 11 8 + 12 8 + 13 8 + 14 8 + 15 8 + 16 8 + 17 9 + } + + method 'testPrimitiveWrapper ()V' { + 7 12 + 8 12 + 9 12 + b 12 + c 12 + d 12 + e 13 + f 13 + 11 13 + 12 13 + 13 13 + 14 13 + 15 13 + 16 13 + 17 13 + 18 14 + } + + method 'testPrimitiveType ()V' { + 4 17 + 5 17 + 6 17 + 8 17 + 9 17 + a 17 + b 18 + } + + method 'testInferredPrimitive ()V' { + 4 21 + 5 21 + 6 21 + 8 21 + 9 21 + a 21 + b 22 + } + + method 'testFunctionReference ()V' { + 0 25 + 1 25 + 2 25 + 3 25 + 4 25 + 5 25 + 6 25 + 7 26 + 8 26 + 9 26 + a 26 + b 26 + c 26 + d 26 + e 27 + f 27 + 10 27 + 11 27 + 19 27 + 1a 27 + 1b 27 + 1c 27 + 1d 27 + 1f 28 + } +} + +Lines mapping: +5 <-> 8 +6 <-> 9 +7 <-> 10 +10 <-> 13 +11 <-> 14 +12 <-> 15 +15 <-> 18 +16 <-> 19 +19 <-> 22 +20 <-> 23 +23 <-> 26 +24 <-> 27 +25 <-> 28 +26 <-> 29 diff --git a/plugins/kotlin/testData/results/pkg/TestSafeCasts.dec b/plugins/kotlin/testData/results/pkg/TestSafeCasts.dec new file mode 100644 index 0000000000..f8429bc7fe --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestSafeCasts.dec @@ -0,0 +1,268 @@ +package pkg + +class TestSafeCasts { + public fun test(obj: Any): Boolean { + var t: Int = obj as? Integer;// 5 + if ((obj as? Integer) != null) {// 7 + if (t == 1) { + return true; + } + } + + return false; + } + + public fun testTestBefore(obj: Any): Boolean? { + if (obj !is Integer) {// 11 + return null;// 12 + } else { + var t: Int = obj as? Integer;// 15 + if ((obj as? Integer) != null) {// 17 + if (t == 1) { + return true; + } + } + + return false; + } + } + + public fun testHardIncompatible(obj: Int): Boolean { + return (obj as? java.lang.String) == "1";// 21 23 + } + + public fun testSmartCastIncompatible(obj: Any): Boolean { + return obj is Integer && (obj as? java.lang.String) == "1";// 27 31 33 + } + + public fun testCastNonNullToNullable(obj: Any): Boolean { + var t: Int = obj as? Integer;// 37 + if ((obj as? Integer) != null) {// 39 + if (t == 1) { + return true; + } + } + + return false; + } + + public fun testBeforeNonNullToNullable(obj: Any): Boolean? { + if (obj !is Integer) {// 43 + return null;// 44 + } else { + var t: Int = obj as? Integer;// 47 + if ((obj as? Integer) != null) {// 49 + if (t == 1) { + return true; + } + } + + return false; + } + } + + public fun testCastNullableToNullable(obj: Any?): Boolean { + var t: Int = obj as? Integer;// 53 + if ((obj as? Integer) != null) {// 55 + if (t == 1) { + return true; + } + } + + return false; + } + + public fun testBeforeNullableToNullable(obj: Any?): Boolean? { + if (obj != null && obj !is Integer) {// 59 + return null;// 60 + } else { + var t: Int = obj as? Integer;// 63 + if ((obj as? Integer) != null) {// 65 + if (t == 1) { + return true; + } + } + + return false; + } + } +} + +class 'pkg/TestSafeCasts' { + method 'test (Ljava/lang/Object;)Z' { + a 4 + d 4 + 15 4 + 16 5 + 17 6 + 1a 5 + 21 6 + 22 6 + 23 6 + 24 6 + 25 6 + 28 7 + 2c 11 + 2d 7 + } + + method 'testTestBefore (Ljava/lang/Object;)Ljava/lang/Boolean;' { + 6 15 + a 15 + d 16 + e 16 + 13 18 + 16 18 + 1e 18 + 1f 19 + 20 20 + 23 19 + 2a 20 + 2b 20 + 2c 20 + 2d 20 + 2e 20 + 31 21 + 35 25 + 36 21 + 37 21 + 38 21 + 39 21 + } + + method 'testHardIncompatible (I)Z' { + 7 30 + a 30 + b 30 + c 30 + d 30 + 16 30 + 17 30 + 18 30 + 1c 30 + } + + method 'testSmartCastIncompatible (Ljava/lang/Object;)Z' { + 6 34 + 7 34 + 8 34 + 9 34 + a 34 + 13 34 + 16 34 + 1f 34 + 20 34 + 21 34 + } + + method 'testCastNonNullToNullable (Ljava/lang/Object;)Z' { + a 38 + d 38 + 15 38 + 16 39 + 17 40 + 1a 39 + 21 40 + 22 40 + 23 40 + 24 40 + 25 40 + 28 41 + 2c 45 + 2d 41 + } + + method 'testBeforeNonNullToNullable (Ljava/lang/Object;)Ljava/lang/Boolean;' { + 6 49 + a 49 + d 50 + e 50 + 13 52 + 16 52 + 1e 52 + 1f 53 + 20 54 + 23 53 + 2a 54 + 2b 54 + 2c 54 + 2d 54 + 2e 54 + 31 55 + 35 59 + 36 55 + 37 55 + 38 55 + 39 55 + } + + method 'testCastNullableToNullable (Ljava/lang/Object;)Z' { + 4 64 + 7 64 + f 64 + 10 65 + 11 66 + 14 65 + 1b 66 + 1c 66 + 1d 66 + 1e 66 + 1f 66 + 22 67 + 26 71 + 27 67 + } + + method 'testBeforeNullableToNullable (Ljava/lang/Object;)Ljava/lang/Boolean;' { + 0 75 + 2 75 + a 75 + d 75 + 10 76 + 11 76 + 16 78 + 19 78 + 21 78 + 22 79 + 23 80 + 26 79 + 2d 80 + 2e 80 + 2f 80 + 30 80 + 31 80 + 34 81 + 38 85 + 39 81 + 3a 81 + 3b 81 + 3c 81 + } +} + +Lines mapping: +5 <-> 5 +7 <-> 6 +11 <-> 16 +12 <-> 17 +15 <-> 19 +17 <-> 20 +21 <-> 31 +23 <-> 31 +27 <-> 35 +31 <-> 35 +33 <-> 35 +37 <-> 39 +39 <-> 40 +43 <-> 50 +44 <-> 51 +47 <-> 53 +49 <-> 54 +53 <-> 65 +55 <-> 66 +59 <-> 76 +60 <-> 77 +63 <-> 79 +65 <-> 80 +Not mapped: +28 diff --git a/plugins/kotlin/testData/results/pkg/TestSealedHierarchy.dec b/plugins/kotlin/testData/results/pkg/TestSealedHierarchy.dec new file mode 100644 index 0000000000..2e488973aa --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestSealedHierarchy.dec @@ -0,0 +1,43 @@ +package pkg + +sealed class TestSealedHierarchy protected constructor() { + class TestClass(x: Int) : TestSealedHierarchy() {// 5 + public final val x: Int + + init { + this.x = x; + } + } + + object TestObject private constructor() : TestSealedHierarchy() {// 4 + @JvmStatic + public TestSealedHierarchy.TestObject INSTANCE = new TestSealedHierarchy.TestObject(); + + } +} + +class 'pkg/TestSealedHierarchy$TestClass' { + method ' (I)V' { + 2 3 + 3 3 + 4 3 + 5 7 + 6 7 + 7 7 + 8 7 + 9 7 + a 8 + } +} + +class 'pkg/TestSealedHierarchy$TestObject' { + method ' ()V' { + 2 11 + 3 11 + 4 11 + } +} + +Lines mapping: +4 <-> 12 +5 <-> 4 diff --git a/plugins/kotlin/testData/results/pkg/TestShadowParam.dec b/plugins/kotlin/testData/results/pkg/TestShadowParam.dec new file mode 100644 index 0000000000..ac3433aacf --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestShadowParam.dec @@ -0,0 +1,43 @@ +package pkg + +class TestShadowParam { + public fun test(x: Int) { + var xx: Int = x - 1;// 5 6 + System.out.println(x - 1);// 7 + if (xx < 0) {// 8 + System.out.println(xx);// 9 + } + }// 11 +} + +class 'pkg/TestShadowParam' { + method 'test (I)V' { + 0 4 + 2 4 + 3 4 + 4 4 + 5 5 + 6 5 + 7 5 + 8 5 + 9 5 + a 5 + b 5 + c 6 + d 6 + 10 7 + 11 7 + 12 7 + 13 7 + 14 7 + 17 9 + } +} + +Lines mapping: +5 <-> 5 +6 <-> 5 +7 <-> 6 +8 <-> 7 +9 <-> 8 +11 <-> 10 diff --git a/plugins/kotlin/testData/results/pkg/TestSmartCasts.dec b/plugins/kotlin/testData/results/pkg/TestSmartCasts.dec new file mode 100644 index 0000000000..0d58d4ecce --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestSmartCasts.dec @@ -0,0 +1,545 @@ +package pkg + +class TestSmartCasts { + public fun testWhen(o: Any?): String { + if (o is java.lang.String) {// 19 20 + return o as java.lang.String;// 21 + } else { + if (o is TestSmartCasts.A.B) {// 24 + System.out.println("B: " + o); + } else { + if (o !is TestSmartCasts.A.C) {// 25 + if (o is Pair) {// 26 + return "<" + this.testWhen((o as Pair).getFirst()) + ", " + this.testWhen((o as Pair).getSecond()) + ">"; + } + + if (o == null) {// 27 + return "null"; + } + + return "else: " + o;// 28 + } + + System.out.println("C: " + o); + } + + if (o == null) {// 31 + throw new NullPointerException("null cannot be cast to non-null type pkg.TestSmartCasts.A"); + } else { + return (o as TestSmartCasts.A).test(); + } + } + } + + public fun testIf(a: Any?): String { + return if (a !is TestSmartCasts.A.B && a !is TestSmartCasts.A.C) "else: " + a else (a as TestSmartCasts.A).test();// 35 36 39 + } + + public fun testIf2(a: Any?): String { + if (a is TestSmartCasts.A) {// 43 + if (a is TestSmartCasts.A.B || a is TestSmartCasts.A.C) {// 44 + System.out.println((a as TestSmartCasts.A).test());// 45 + } + + if (a is TestSmartCasts.A.B) {// 48 + if (a is TestSmartCasts.A.C) {// 49 + System.out.println((a as TestSmartCasts.A.B).testB());// 50 + System.out.println((a as TestSmartCasts.A.C).testC());// 51 + } + + if (a is TestSmartCasts.A.C && (a as TestSmartCasts.A.C).testC() == "C" || a is TestSmartCasts.A.B) {// 54 + System.out.println((a as TestSmartCasts.A.B).testB());// 55 + } + } + } + + return "else: " + a;// 60 + } + + public fun testCast(a: Any?) { + System.out.println(a);// 64 + if (a == null) {// 65 + throw new NullPointerException("null cannot be cast to non-null type kotlin.String"); + } else { + System.out.println("hello");// 66 + System.out.println(a);// 67 + (a as java.lang.String).charAt(0);// 68 + System.out.println((a as java.lang.String).charAt(0));// 69 + } + }// 70 + + public fun testSealedIf(a: pkg.TestSmartCasts.A): String { + if (a is TestSmartCasts.A.B) {// 73 + return (a as TestSmartCasts.A.B).testB();// 74 + } else { + return if (a is TestSmartCasts.A.C) (a as TestSmartCasts.A.C).testC() else a.test();// 75 76 78 + } + } + + public fun testDoubleType(t: List): String { + return if (t is TestSmartCasts.X) (t as TestSmartCasts.X).woo(t as java.lang.Iterable) else t.get(0) as java.lang.String;// 83 84 87 + } + + sealed class A protected constructor() { + public fun test(): String { + return "";// 15 + } + + class B : TestSmartCasts.A() {// 8 + public fun testB(): String { + return "B";// 9 + } + } + + class C : TestSmartCasts.A() {// 11 + public fun testC(): String { + return "C";// 12 + } + } + } + + interface X { + public open fun Iterable<*>.woo(): String { + } + + // $VF: Class flags could not be determined + internal class DefaultImpls { + @JvmStatic + fun woo(var0: TestSmartCasts.X, receiver: MutableIterable<*>): java.lang.String { + return "A";// 5 + } + } + } +} + +class 'pkg/TestSmartCasts' { + method 'testWhen (Ljava/lang/Object;)Ljava/lang/String;' { + 0 4 + 2 4 + 3 4 + 4 4 + 5 4 + 6 4 + 9 5 + a 5 + b 5 + c 5 + d 5 + e 7 + f 7 + 10 7 + 11 7 + 12 7 + 15 8 + 16 8 + 17 8 + 18 8 + 19 8 + 1a 8 + 1b 8 + 1c 8 + 1d 8 + 1f 8 + 20 8 + 21 8 + 25 10 + 29 10 + 2c 22 + 2d 22 + 2e 22 + 2f 22 + 30 22 + 31 22 + 32 22 + 33 22 + 34 22 + 36 22 + 37 22 + 38 22 + 3c 11 + 3d 11 + 3e 11 + 3f 11 + 40 11 + 43 12 + 44 12 + 45 12 + 46 12 + 47 12 + 48 12 + 49 12 + 4a 12 + 4b 12 + 4c 12 + 4d 12 + 4e 12 + 4f 12 + 50 12 + 51 12 + 52 12 + 53 12 + 54 12 + 55 12 + 56 12 + 57 12 + 58 12 + 59 12 + 5a 12 + 5b 12 + 5c 12 + 5d 12 + 5e 12 + 5f 15 + 60 15 + 63 16 + 64 16 + 65 16 + 66 19 + 67 19 + 68 19 + 69 19 + 6a 19 + 6b 19 + 6c 19 + 6d 25 + 6f 25 + 77 26 + 78 26 + 7c 26 + 7d 28 + 7e 28 + 7f 28 + 80 28 + 81 28 + 82 28 + 83 28 + } + + method 'testIf (Ljava/lang/Object;)Ljava/lang/String;' { + 0 34 + 4 34 + 7 34 + b 34 + e 34 + f 34 + 10 34 + 11 34 + 12 34 + 13 34 + 14 34 + 16 34 + 17 34 + 18 34 + 19 34 + 1a 34 + 1b 34 + } + + method 'testIf2 (Ljava/lang/Object;)Ljava/lang/String;' { + 0 38 + 1 38 + 2 38 + 3 38 + 4 38 + 7 39 + 8 39 + 9 39 + a 39 + b 39 + e 39 + f 39 + 10 39 + 11 39 + 12 39 + 15 40 + 16 40 + 17 40 + 18 40 + 19 40 + 1a 40 + 1b 40 + 1c 40 + 1d 40 + 1e 40 + 20 40 + 23 43 + 24 43 + 25 43 + 26 43 + 27 43 + 2a 44 + 2b 44 + 2c 44 + 2d 44 + 2e 44 + 31 45 + 32 45 + 33 45 + 34 45 + 35 45 + 36 45 + 37 45 + 38 45 + 39 45 + 3a 45 + 3c 45 + 3d 45 + 3e 45 + 3f 46 + 40 46 + 41 46 + 42 46 + 43 46 + 44 46 + 45 46 + 46 46 + 47 46 + 48 46 + 4a 46 + 4d 49 + 4e 49 + 4f 49 + 50 49 + 51 49 + 54 49 + 55 49 + 56 49 + 57 49 + 58 49 + 59 49 + 5a 49 + 5b 49 + 5c 49 + 60 49 + 63 49 + 64 49 + 65 49 + 66 49 + 67 49 + 6a 50 + 6b 50 + 6c 50 + 6d 50 + 6e 50 + 6f 50 + 70 50 + 71 50 + 72 50 + 73 50 + 75 50 + 78 55 + 79 55 + 7a 55 + 7b 55 + 7c 55 + 7d 55 + 7e 55 + } + + method 'testCast (Ljava/lang/Object;)V' { + 0 59 + 1 59 + 2 59 + 3 59 + 4 59 + 5 59 + 6 59 + 7 60 + 9 60 + 11 61 + 12 61 + 16 61 + 1b 63 + 1c 63 + 1d 63 + 1e 63 + 1f 63 + 21 63 + 22 63 + 23 63 + 24 64 + 25 64 + 26 64 + 27 64 + 28 64 + 29 64 + 2a 64 + 2b 65 + 2c 65 + 2d 65 + 2e 65 + 2f 65 + 30 65 + 31 65 + 32 65 + 34 66 + 35 66 + 36 66 + 37 66 + 38 66 + 39 66 + 3a 66 + 3b 66 + 3d 66 + 3e 66 + 3f 66 + 40 66 + 41 66 + 42 66 + 43 66 + 44 68 + } + + method 'testSealedIf (Lpkg/TestSmartCasts$A;)Ljava/lang/String;' { + 6 71 + 7 71 + 8 71 + 9 71 + a 71 + d 72 + e 72 + f 72 + 10 72 + 11 72 + 12 72 + 13 72 + 14 72 + 15 74 + 16 74 + 17 74 + 18 74 + 19 74 + 1c 74 + 1d 74 + 1e 74 + 1f 74 + 20 74 + 21 74 + 22 74 + 24 74 + 25 74 + 26 74 + 27 74 + } + + method 'testDoubleType (Ljava/util/List;)Ljava/lang/String;' { + 6 79 + 7 79 + 8 79 + 9 79 + a 79 + d 79 + 11 79 + 12 79 + 13 79 + 14 79 + 15 79 + 16 79 + 17 79 + 18 79 + 19 79 + 1a 79 + 1b 79 + 1c 79 + 1d 79 + 1f 79 + 20 79 + 21 79 + 22 79 + 23 79 + 24 79 + 25 79 + 26 79 + 27 79 + 28 79 + } +} + +class 'pkg/TestSmartCasts$A' { + method 'test ()Ljava/lang/String;' { + 0 84 + 1 84 + 2 84 + } +} + +class 'pkg/TestSmartCasts$A$B' { + method ' ()V' { + 2 87 + 3 87 + 4 87 + } + + method 'testB ()Ljava/lang/String;' { + 0 89 + 1 89 + 2 89 + } +} + +class 'pkg/TestSmartCasts$A$C' { + method ' ()V' { + 2 93 + 3 93 + 4 93 + } + + method 'testC ()Ljava/lang/String;' { + 0 95 + 1 95 + 2 95 + } +} + +class 'pkg/TestSmartCasts$X$DefaultImpls' { + method 'woo (Lpkg/TestSmartCasts$X;Ljava/lang/Iterable;)Ljava/lang/String;' { + 6 108 + 7 108 + 8 108 + } +} + +Lines mapping: +5 <-> 109 +8 <-> 88 +9 <-> 90 +11 <-> 94 +12 <-> 96 +15 <-> 85 +19 <-> 5 +20 <-> 5 +21 <-> 6 +24 <-> 8 +25 <-> 11 +26 <-> 12 +27 <-> 16 +28 <-> 20 +31 <-> 26 +35 <-> 35 +36 <-> 35 +39 <-> 35 +43 <-> 39 +44 <-> 40 +45 <-> 41 +48 <-> 44 +49 <-> 45 +50 <-> 46 +51 <-> 47 +54 <-> 50 +55 <-> 51 +60 <-> 56 +64 <-> 60 +65 <-> 61 +66 <-> 64 +67 <-> 65 +68 <-> 66 +69 <-> 67 +70 <-> 69 +73 <-> 72 +74 <-> 73 +75 <-> 75 +76 <-> 75 +78 <-> 75 +83 <-> 80 +84 <-> 80 +87 <-> 80 diff --git a/plugins/kotlin/testData/results/pkg/TestSynchronized.dec b/plugins/kotlin/testData/results/pkg/TestSynchronized.dec new file mode 100644 index 0000000000..0d166bac19 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestSynchronized.dec @@ -0,0 +1,40 @@ +package pkg + +class TestSynchronized { + public fun test() { + synchronized (this) {// 5 + System.out.println("Hello");// 6 + } + } +} + +class 'pkg/TestSynchronized' { + method 'test ()V' { + 0 4 + 2 4 + 3 4 + 7 5 + 8 5 + 9 5 + a 5 + b 5 + d 5 + e 5 + f 5 + 17 7 + 18 7 + 19 7 + 1a 7 + 1b 7 + 1c 7 + 1d 7 + 1e 7 + } +} + +Lines mapping: +5 <-> 5 +6 <-> 6 +Not mapped: +7 +8 diff --git a/plugins/kotlin/testData/results/pkg/TestTailrecFunctions.dec b/plugins/kotlin/testData/results/pkg/TestTailrecFunctions.dec new file mode 100644 index 0000000000..39e7eeff61 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestTailrecFunctions.dec @@ -0,0 +1,472 @@ +package pkg + +class TestTailrecFunctions { + public tailrec fun sum(x: Long, sum: Long): Long { + var var5: TestTailrecFunctions = this;// 4 + var var6: Long = x; + var var8: Long = sum; + + while (var6 != 0L) {// 5 + var var15: Long = var6 - 1L;// 6 + var var17: Long = var8 + var6; + var5 = var5; + var6 = var15; + var8 = var17; + } + + return var8; + } + + public tailrec fun testFinally() { + label12: { + try { + ; + } catch (java.lang.Throwable var2) { + this.testFinally();// 13 + } + + this.testFinally();// 12 14 + } + } + + public tailrec fun testFinallyReturn(): Int { + try { + ; + } catch (java.lang.Throwable var2) { + return this.testFinallyReturn();// 21 + } + + return this.testFinallyReturn(); + } + + public tailrec fun fooTry() { + try { + this.fooTry();// 27 + } catch (java.lang.Throwable var2) {// 29 + } + }// 31 + + public tailrec fun testTryCatchFinally() { + label31: { + label32: { + try { + try { + this.testTryCatchFinally();// 35 + break label32; + } catch (Exception var2) {// 36 + this.testTryCatchFinally();// 37 + } + } catch (java.lang.Throwable var3) { + this.testTryCatchFinally();// 39 + } + + this.testTryCatchFinally(); + return;// 41 + } + + this.testTryCatchFinally(); + } + } + + public tailrec fun fastPow(x: Long, n: Long, acc: Long): Long { + var var7: TestTailrecFunctions = this; + var var8: Long = x; + var var10: Long = n; + var var12: Long = acc; + + while (var10 != 0L) {// 44 + if (var10 % (long)2 == 0L) {// 45 + var var21: Long = var8 * var8; + var var23: Long = var10 / (long)2; + var7 = var7; + var8 = var21; + var10 = var23; + var12 = var12; + } else { + var var25: Long = var10 - 1L;// 46 + var var26: Long = var12 * var8; + var7 = var7; + var8 = var8; + var10 = var25; + var12 = var26; + } + } + + return var12; + } + + @JvmStatic + @JvmSynthetic + fun `fastPow$default`(var0: TestTailrecFunctions, var1: Long, var3: Long, var5: Long, var7: Int, var8: Any): Long { + if ((var7 and 4) != 0) {// 43 + var5 = 1L; + } + + return var0.fastPow(var1, var3, var5); + } + + public tailrec fun fastPow(x: Long, n: Long): Long { + var var5: TestTailrecFunctions = this;// 49 + var var6: Long = x; + var var8: Long = n; + + var var10000: Long; + while (true) { + if (var8 == 0L) {// 50 + var10000 = 1L; + break; + } + + if (var8 % (long)2 != 0L) {// 51 + var10000 = var6 * var5.fastPow(var6, var8 - 1L);// 52 + break; + } + + var var15: Long = var6 * var6; + var var17: Long = var8 / (long)2; + var5 = var5; + var6 = var15; + var8 = var17; + } + + return var10000;// 53 + } +} + +class 'pkg/TestTailrecFunctions' { + method 'sum (JJ)J' { + 0 4 + 1 4 + 2 4 + 3 5 + 4 5 + 5 5 + 6 6 + 7 6 + 8 6 + 9 11 + a 11 + d 8 + e 8 + 11 10 + 12 10 + 15 8 + 16 8 + 17 8 + 18 8 + 1c 16 + 1d 16 + 1e 16 + 1f 9 + 20 9 + 21 9 + 22 9 + 23 9 + 24 9 + 25 10 + 26 10 + 27 10 + 28 10 + 29 10 + 2a 10 + 2b 10 + 2c 11 + 2d 11 + 2e 11 + 2f 11 + 30 12 + 31 12 + 32 12 + 33 12 + 34 13 + 35 13 + 36 13 + 37 13 + } + + method 'testFinally ()V' { + 1 27 + 2 27 + 3 27 + 4 27 + 5 27 + 6 27 + 7 27 + 8 27 + 9 24 + a 24 + b 27 + c 27 + d 27 + e 27 + } + + method 'testFinallyReturn ()I' { + 1 38 + 2 38 + 3 38 + 4 38 + 5 38 + 7 35 + 8 35 + 9 35 + a 35 + b 35 + } + + method 'fooTry ()V' { + 1 43 + 2 43 + 3 43 + 4 43 + 5 46 + 6 44 + 7 46 + } + + method 'testTryCatchFinally ()V' { + 1 53 + 2 53 + 5 66 + 6 66 + 7 66 + 8 66 + c 55 + d 56 + e 56 + 11 62 + 12 62 + 13 62 + 14 62 + 19 59 + 1a 59 + 1f 63 + } + + method 'fastPow (JJJ)J' { + 0 71 + 1 71 + 2 71 + 3 72 + 4 72 + 5 72 + 6 73 + 7 73 + 8 73 + 9 74 + a 74 + b 74 + c 74 + d 80 + e 80 + 11 78 + 12 78 + 15 76 + 16 76 + 19 83 + 1a 83 + 1d 76 + 1e 76 + 1f 76 + 20 76 + 24 94 + 25 94 + 26 94 + 27 77 + 28 77 + 29 77 + 2a 77 + 2b 77 + 2c 77 + 2d 77 + 2e 77 + 31 78 + 32 78 + 33 78 + 34 78 + 35 78 + 36 78 + 37 78 + 38 79 + 39 79 + 3a 79 + 3b 79 + 3c 79 + 3d 79 + 3e 79 + 3f 80 + 40 80 + 41 80 + 42 80 + 43 81 + 44 81 + 45 81 + 46 81 + 47 82 + 48 82 + 49 82 + 4a 82 + 4b 83 + 4c 83 + 4d 83 + 4e 83 + 52 85 + 53 85 + 54 85 + 55 85 + 56 85 + 57 85 + 58 86 + 59 86 + 5a 86 + 5b 86 + 5c 86 + 5d 86 + 5e 86 + 5f 87 + 60 87 + 61 87 + 62 87 + 63 88 + 64 88 + 65 88 + 66 88 + 67 89 + 68 89 + 69 89 + 6a 89 + 6b 90 + 6c 90 + 6d 90 + 6e 90 + } + + method 'fastPow$default (Lpkg/TestTailrecFunctions;JJJILjava/lang/Object;)J' { + 0 100 + 1 100 + 2 100 + 3 100 + 4 100 + 7 101 + 8 101 + 9 101 + a 104 + b 104 + c 104 + d 104 + e 104 + f 104 + 10 104 + 11 104 + 12 104 + } + + method 'fastPow (JJ)J' { + 0 108 + 1 108 + 2 108 + 3 109 + 4 109 + 5 109 + 6 110 + 7 110 + 8 110 + 9 120 + a 120 + d 120 + e 120 + 11 114 + 12 114 + 15 114 + 16 114 + 17 114 + 18 114 + 19 114 + 1c 115 + 1d 116 + 20 119 + 21 119 + 22 119 + 23 119 + 24 119 + 25 119 + 26 119 + 27 119 + 2a 124 + 2b 124 + 2c 124 + 2d 124 + 2e 124 + 2f 124 + 30 124 + 31 125 + 32 125 + 33 125 + 34 125 + 35 125 + 36 125 + 37 125 + 38 126 + 39 126 + 3a 126 + 3b 126 + 3c 127 + 3d 127 + 3e 127 + 3f 127 + 40 128 + 41 128 + 42 128 + 43 128 + 47 120 + 48 120 + 49 120 + 4a 120 + 4b 120 + 4c 120 + 4d 120 + 4e 120 + 4f 120 + 50 120 + 51 120 + 52 120 + 53 120 + 54 120 + 55 131 + } +} + +Lines mapping: +4 <-> 5 +5 <-> 9 +6 <-> 10 +12 <-> 28 +13 <-> 25 +14 <-> 28 +21 <-> 36 +27 <-> 44 +29 <-> 45 +31 <-> 47 +35 <-> 54 +36 <-> 56 +37 <-> 57 +39 <-> 60 +41 <-> 64 +43 <-> 101 +44 <-> 77 +45 <-> 78 +46 <-> 86 +49 <-> 109 +50 <-> 115 +51 <-> 120 +52 <-> 121 +53 <-> 132 +Not mapped: +10 +15 +18 +20 +26 +34 +38 +40 \ No newline at end of file diff --git a/plugins/kotlin/testData/results/pkg/TestTopLevelKt.dec b/plugins/kotlin/testData/results/pkg/TestTopLevelKt.dec new file mode 100644 index 0000000000..6c4f34d637 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestTopLevelKt.dec @@ -0,0 +1,21 @@ +package pkg + +public const val topLevelConstVal: Int = 926 +public final val topLevelVal: Regex = new Regex("") +public final var topLevelVar: Int = 42 + internal set + +public fun topLevelFun() { +}// 5 + + +class 'pkg/TestTopLevelKt' { + method 'topLevelFun ()V' { + 2 8 + } +} + +Lines mapping: +5 <-> 9 +Not mapped: +4 diff --git a/plugins/kotlin/testData/results/pkg/TestTryCatchExpressions.dec b/plugins/kotlin/testData/results/pkg/TestTryCatchExpressions.dec new file mode 100644 index 0000000000..53bf0f58a2 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestTryCatchExpressions.dec @@ -0,0 +1,398 @@ +package pkg + +import java.io.IOException + +class TestTryCatchExpressions { + public fun test0(s: String) { + var var2: java.lang.String; + try { + var2 = StringsKt.repeat(s as java.lang.CharSequence, 5);// 9 + } catch (RuntimeException var4) {// 10 + var var10000: java.lang.String = var4.getMessage();// 11 + if (var10000 == null) { + var10000 = "ERROR"; + } + + var2 = var10000; + } + + System.out.print(var2);// 7 + }// 14 + + public fun test1(a: String, b: String) { + var x: java.lang.String = a;// 17 + var var6: TestTryCatchExpressions = this;// 19 + + var var4: java.lang.String; + var var10000: TestTryCatchExpressions; + try { + var10000 = var6; + var4 = StringsKt.repeat(x as java.lang.CharSequence, 5);// 20 + } catch (RuntimeException var9) {// 21 + var10000 = this; + x = b;// 22 + var var10001: java.lang.String = var9.getMessage();// 23 + if (var10001 == null) { + var10001 = "ERROR"; + } + + var4 = var10001; + } + + var var7: java.lang.String = var4;// 24 + var6 = var10000; + + var var13: java.lang.String; + try { + var10000 = var6; + var13 = var7; + var4 = StringsKt.repeat(x as java.lang.CharSequence, 5);// 25 + } catch (RuntimeException var8) {// 26 + var10000 = var10000; + var13 = var4; + var var10002: java.lang.String = var8.getMessage();// 27 + if (var10002 == null) { + var10002 = "ERROR"; + } + + var4 = var10002; + } + + var10000.test0(var13 + var4);// 18 + }// 30 + + public fun test2(a: String, b: String) { + var var15: java.lang.String = a;// 33 + var var7: TestTryCatchExpressions = this; + + var var4: java.lang.String; + var var10000: TestTryCatchExpressions; + try { + var10000 = var7; + if (a.length() != b.length()) {// 35 + return;// 36 + } + + var15 = b;// 38 + this.test0(b);// 39 + var4 = b;// 40 + } catch (IOException var13) {// 41 + var10000 = this; + var15 = a;// 42 + this.test1(a, a);// 43 + var4 = a;// 44 + } catch (RuntimeException var14) {// 46 + var10000 = this;// 45 + var15 = if (var15 == a) b else a;// 47 49 + var4 = var15;// 51 + } + + var var8: java.lang.String = var4;// 52 + var7 = var10000; + + var var10001: java.lang.String; + try { + var4 = StringsKt.repeat(var15 as java.lang.CharSequence, 5); + var15 = var4; + var10000 = var7; + var10001 = var8; + var4 = var4;// 53 + } catch (RuntimeException var12) {// 54 + var10000 = var10000; + var10001 = var4; + System.out.println(var15);// 55 + var var10003: java.lang.String = var12.getMessage(); + if (var10003 == null) { + var10003 = ""; + } + + var4 = var15 + "!!" + var10003;// 56 + } + + var10000.test1(var10001, var4);// 34 + }// 58 +} + +class 'pkg/TestTryCatchExpressions' { + method 'test0 (Ljava/lang/String;)V' { + 7 8 + 8 8 + 9 8 + a 8 + b 8 + c 8 + d 8 + e 8 + f 8 + 13 9 + 14 10 + 15 10 + 16 10 + 17 10 + 19 11 + 1d 12 + 1f 15 + 20 18 + 22 18 + 23 18 + 24 18 + 25 18 + 26 18 + 27 18 + 28 18 + 29 19 + } + + method 'test1 (Ljava/lang/String;Ljava/lang/String;)V' { + c 22 + d 22 + e 23 + f 23 + 10 23 + 12 28 + 13 28 + 14 29 + 15 29 + 16 29 + 17 29 + 18 29 + 19 29 + 1a 29 + 1b 29 + 1c 29 + 1d 29 + 21 30 + 23 31 + 24 31 + 25 32 + 26 32 + 27 33 + 28 33 + 29 33 + 2a 33 + 2b 33 + 2d 34 + 31 35 + 33 38 + 34 38 + 35 41 + 36 41 + 37 41 + 38 41 + 39 42 + 3a 42 + 3c 46 + 3d 46 + 3e 47 + 3f 47 + 40 48 + 41 48 + 42 48 + 43 48 + 44 48 + 45 48 + 46 48 + 47 48 + 48 48 + 49 48 + 4d 49 + 4f 50 + 50 50 + 51 51 + 52 51 + 53 52 + 54 52 + 55 52 + 56 52 + 57 52 + 59 53 + 5d 54 + 5f 57 + 60 57 + 61 60 + 62 60 + 63 60 + 64 60 + 65 60 + 66 60 + 67 60 + 68 60 + 69 60 + 6a 60 + 6b 61 + } + + method 'test2 (Ljava/lang/String;Ljava/lang/String;)V' { + e 64 + f 64 + 10 65 + 11 65 + 12 65 + 14 70 + 15 70 + 16 71 + 17 71 + 18 71 + 19 71 + 1a 71 + 1b 71 + 1c 71 + 1d 71 + 1e 71 + 21 72 + 22 75 + 23 75 + 24 76 + 25 76 + 26 76 + 27 76 + 28 76 + 29 77 + 2a 77 + 2b 77 + 2f 78 + 31 79 + 32 79 + 33 80 + 34 80 + 35 81 + 36 81 + 37 81 + 38 81 + 39 81 + 3a 81 + 3b 81 + 3c 81 + 3d 81 + 3e 82 + 3f 82 + 40 82 + 41 82 + 42 82 + 43 82 + 49 84 + 4a 84 + 4b 85 + 4c 85 + 50 85 + 53 85 + 57 85 + 58 83 + 59 86 + 5a 86 + 5b 86 + 5c 86 + 5d 86 + 5e 86 + 5f 89 + 60 89 + 61 89 + 62 89 + 63 90 + 64 90 + 66 96 + 67 96 + 68 97 + 69 97 + 6a 94 + 6b 94 + 6c 94 + 6d 94 + 6e 94 + 6f 94 + 70 94 + 71 94 + 72 94 + 73 94 + 74 95 + 75 95 + 7f 95 + 80 95 + 81 95 + 87 96 + 88 96 + 89 97 + 8a 97 + 8b 98 + 8c 98 + 8d 98 + 8e 98 + 92 99 + 94 100 + 95 100 + 96 101 + 97 101 + 98 102 + 99 102 + 9a 102 + 9b 102 + 9d 102 + 9e 102 + 9f 102 + a0 108 + a1 103 + a2 103 + a3 103 + a4 103 + a5 103 + a7 104 + ab 105 + ad 108 + ae 108 + af 108 + b0 108 + b1 108 + b2 108 + b3 108 + b4 111 + b5 111 + b6 111 + b7 111 + b8 111 + b9 112 + } +} + +Lines mapping: +7 <-> 19 +9 <-> 9 +10 <-> 10 +11 <-> 11 +14 <-> 20 +17 <-> 23 +18 <-> 61 +19 <-> 24 +20 <-> 30 +21 <-> 31 +22 <-> 33 +23 <-> 34 +24 <-> 42 +25 <-> 49 +26 <-> 50 +27 <-> 53 +30 <-> 62 +33 <-> 65 +34 <-> 112 +35 <-> 72 +36 <-> 73 +38 <-> 76 +39 <-> 77 +40 <-> 78 +41 <-> 79 +42 <-> 81 +43 <-> 82 +44 <-> 83 +45 <-> 85 +46 <-> 84 +47 <-> 86 +49 <-> 86 +51 <-> 87 +52 <-> 90 +53 <-> 99 +54 <-> 100 +55 <-> 103 +56 <-> 109 +58 <-> 113 +Not mapped: +8 +60 diff --git a/plugins/kotlin/testData/results/pkg/TestTryFinallyExpressions.dec b/plugins/kotlin/testData/results/pkg/TestTryFinallyExpressions.dec new file mode 100644 index 0000000000..f90533dff6 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestTryFinallyExpressions.dec @@ -0,0 +1,344 @@ +package pkg + +import java.io.IOException + +class TestTryFinallyExpressions { + public fun test0(s: String) { + label15: { + try { + var var2: java.lang.String = StringsKt.repeat(s as java.lang.CharSequence, 5);// 8 + } catch (java.lang.Throwable var4) { + System.out.println("bye");// 10 + } + + System.out.println("bye");// 7 9 11 + } + } + + public fun test1(a: String, b: String) { + label24: { + var x: java.lang.String = a;// 15 + + try { + var var4: java.lang.String = StringsKt.repeat(x as java.lang.CharSequence, 5);// 17 + } catch (java.lang.Throwable var9) { + ; + } + + x = b;// 18 19 20 + + try { + var var15: java.lang.String = StringsKt.repeat(x as java.lang.CharSequence, 5);// 21 + } catch (java.lang.Throwable var8) { + System.out.println(a);// 23 + } + + System.out.println(a);// 16 22 24 + } + } + + public fun test2(a: String, b: String) { + label68: { + var var19: java.lang.String = a;// 28 + + label69: { + label70: { + label71: { + try { + try { + if (a == b) {// 30 + break label69; + } + + var19 = b;// 33 + this.test0(b);// 34 + break label71; + } catch (IOException var11) {// 36 + var19 = a;// 37 + this.test1(a, a);// 38 + var var4: java.lang.String = a;// 39 + } + } catch (java.lang.Throwable var12) { + var19 = if (var19 == a) b else a;// 41 42 44 + } + + var19 = if (var19 == a) b else a; + break label70;// 46 + } + + var19 = if (var19 == a) b else a; + } + + try { + var19 = StringsKt.repeat(var19 as java.lang.CharSequence, 5);// 47 + } catch (java.lang.Throwable var10) { + System.out.println(var19);// 49 + } + + System.out.println(var19);// 29 48 50 + } + + var19 = if (a == a) b else a; + } + } +} + +class 'pkg/TestTryFinallyExpressions' { + method 'test0 (Ljava/lang/String;)V' { + 7 8 + 8 8 + 9 8 + a 8 + b 8 + c 8 + d 8 + e 8 + f 8 + 10 13 + 11 13 + 12 13 + 13 13 + 14 13 + 16 13 + 17 13 + 18 13 + 19 13 + 1a 13 + 1b 13 + 1c 13 + 1d 10 + 1e 10 + 1f 10 + 20 10 + 21 10 + 22 13 + 23 10 + 24 13 + 25 13 + 26 13 + 27 13 + 28 13 + 29 13 + 2a 13 + 2b 13 + 2c 13 + 2d 13 + 2e 13 + 2f 13 + 30 13 + } + + method 'test1 (Ljava/lang/String;Ljava/lang/String;)V' { + c 19 + d 19 + 14 22 + 15 22 + 16 22 + 17 22 + 18 22 + 19 22 + 1a 22 + 1b 22 + 1c 22 + 1d 22 + 1e 27 + 1f 27 + 20 27 + 21 27 + 22 27 + 23 27 + 24 27 + 25 27 + 26 27 + 27 27 + 28 27 + 29 27 + 2a 27 + 2b 27 + 2c 27 + 2d 27 + 35 30 + 36 30 + 37 30 + 38 30 + 39 30 + 3a 30 + 3b 30 + 3c 30 + 3d 30 + 3e 30 + 3f 35 + 40 35 + 41 35 + 42 35 + 43 35 + 44 35 + 45 35 + 46 35 + 47 35 + 48 35 + 49 35 + 4a 35 + 4b 32 + 4c 32 + 4d 32 + 4e 32 + 4f 32 + 50 35 + 51 35 + 52 35 + 53 35 + 54 35 + 55 35 + 56 35 + 57 35 + 58 35 + 59 35 + 5a 35 + 5b 35 + 5c 35 + 5d 35 + 5e 35 + } + + method 'test2 (Ljava/lang/String;Ljava/lang/String;)V' { + e 41 + f 41 + 16 48 + 17 48 + 1b 48 + 1f 80 + 20 80 + 24 80 + 27 80 + 2b 80 + 2c 80 + 2e 52 + 2f 52 + 30 53 + 31 53 + 32 53 + 33 53 + 34 53 + 38 68 + 39 68 + 3d 68 + 40 68 + 44 68 + 45 68 + 49 55 + 4d 56 + 4e 56 + 4f 57 + 50 57 + 51 57 + 52 57 + 53 57 + 54 57 + 55 57 + 56 57 + 57 57 + 58 58 + 59 58 + 5a 58 + 5b 58 + 5c 58 + 5d 58 + 5e 64 + 5f 64 + 63 64 + 66 64 + 6a 64 + 6b 64 + 6c 65 + 71 61 + 72 61 + 76 61 + 79 61 + 7d 61 + 7e 61 + 8d 72 + 8e 72 + 8f 72 + 90 72 + 91 72 + 92 72 + 93 72 + 94 72 + 97 72 + 98 72 + a2 72 + a3 72 + a4 72 + b2 77 + b3 77 + b4 77 + b5 77 + b7 77 + b8 77 + b9 77 + ba 77 + bb 77 + bc 77 + bd 77 + be 77 + bf 74 + c0 74 + c1 74 + c2 74 + c3 77 + c4 74 + c5 77 + c6 77 + c7 77 + c8 77 + c9 77 + ca 77 + cb 77 + cc 77 + cd 77 + ce 77 + } +} + +Lines mapping: +7 <-> 14 +8 <-> 9 +9 <-> 14 +10 <-> 11 +11 <-> 14 +15 <-> 20 +16 <-> 36 +17 <-> 23 +18 <-> 28 +19 <-> 28 +20 <-> 28 +21 <-> 31 +22 <-> 36 +23 <-> 33 +24 <-> 36 +28 <-> 42 +29 <-> 78 +30 <-> 49 +33 <-> 53 +34 <-> 54 +36 <-> 56 +37 <-> 57 +38 <-> 58 +39 <-> 59 +41 <-> 62 +42 <-> 62 +44 <-> 62 +46 <-> 66 +47 <-> 73 +48 <-> 78 +49 <-> 75 +50 <-> 78 +Not mapped: +12 +25 +31 +35 +40 +51 +53 diff --git a/plugins/kotlin/testData/results/pkg/TestVars.dec b/plugins/kotlin/testData/results/pkg/TestVars.dec new file mode 100644 index 0000000000..e2eb7f5894 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestVars.dec @@ -0,0 +1,109 @@ +package pkg + +class TestVars { + public fun testVar() { + System.out.println("initial");// 5 6 + }// 9 + + public fun testVal() { + System.out.println("initial");// 12 13 + }// 16 + + public fun testPhi(bl: Boolean) { + var var3: Byte; + if (bl) {// 21 + var3 = 1;// 22 + } else { + var3 = 2;// 24 + } + + System.out.println(var3);// 27 + }// 28 + + public fun testIfExpr(bl: Boolean) { + System.out.println(if (bl) 1 else 2);// 31 32 34 37 + }// 38 +} + +class 'pkg/TestVars' { + method 'testVar ()V' { + 0 4 + 1 4 + 3 4 + 4 4 + 5 4 + 6 4 + 7 4 + 8 4 + 9 4 + e 5 + } + + method 'testVal ()V' { + 0 8 + 1 8 + 3 8 + 4 8 + 5 8 + 6 8 + 7 8 + 8 8 + 9 8 + e 9 + } + + method 'testPhi (Z)V' { + 2 13 + 3 13 + 6 14 + 7 14 + b 16 + c 16 + d 19 + e 19 + f 19 + 10 19 + 11 19 + 12 19 + 13 19 + 14 20 + } + + method 'testIfExpr (Z)V' { + 0 23 + 1 23 + 4 23 + 8 23 + a 23 + b 23 + c 23 + d 23 + e 23 + f 23 + 10 23 + 11 24 + } +} + +Lines mapping: +5 <-> 5 +6 <-> 5 +9 <-> 6 +12 <-> 9 +13 <-> 9 +16 <-> 10 +21 <-> 14 +22 <-> 15 +24 <-> 17 +27 <-> 20 +28 <-> 21 +31 <-> 24 +32 <-> 24 +34 <-> 24 +37 <-> 24 +38 <-> 25 +Not mapped: +7 +8 +14 +15 diff --git a/plugins/kotlin/testData/results/pkg/TestWhen.dec b/plugins/kotlin/testData/results/pkg/TestWhen.dec new file mode 100644 index 0000000000..a29a8ffc73 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestWhen.dec @@ -0,0 +1,355 @@ +package pkg + +class TestWhen { + public fun testStatement(obj: Any) { + if (obj == 1) {// 5 6 + System.out.println("1"); + } else if (obj == "2") {// 7 + System.out.println("2"); + } else if (obj is java.lang.Double) {// 8 + System.out.println("Double"); + } else if (obj !is java.lang.Long) {// 9 + System.out.println("Not Long"); + } else { + System.out.println("else");// 10 + } + }// 12 + + public fun testExpression(obj: Any): Int { + return if (obj == 1) 1 else (if (obj is java.lang.Double) 2 else (if (obj == "4") 4 else (if (obj !is java.lang.Long) 3 else 5)));// 15 16 17 18 19 20 + } + + public fun testStatement2(a: Any, b: Any) { + if (a == 15) {// 26 + System.out.println("a == 15"); + } else if (a == "!!") {// 27 + System.out.println("a == !!"); + } else if (a is Integer) {// 28 + System.out.println("a is Int"); + } else if (a is java.lang.String) {// 29 + System.out.println("a is String"); + } else if (b is java.lang.Double) {// 30 + System.out.println("b is Double"); + } else if (a is Unit) {// 31 + System.out.println("a is Unit"); + } else { + System.out.println("else");// 32 + } + }// 34 + + public fun booleanNightmares(a: Boolean, b: Boolean, c: Boolean, d: Boolean, e: Boolean, f: Boolean, g: Boolean) { + if (a == (b != c)) {// 37 38 + System.out.println("-_-"); + } else if (a == (b && !e)) {// 39 + System.out.println("xxx"); + } else if (a == (!a || d)) {// 40 + System.out.println("ohno"); + } else if (!a) {// 41 + System.out.println("NIGHTMARE"); + } else if (a == (g || e && f != c)) {// 42 + System.out.println("hello"); + } else { + System.out.println("else");// 43 + } + }// 45 +} + +class 'pkg/TestWhen' { + method 'testStatement (Ljava/lang/Object;)V' { + 6 4 + 8 4 + 9 4 + a 4 + b 4 + c 4 + 10 4 + 13 5 + 14 5 + 15 5 + 16 5 + 17 5 + 19 5 + 1a 5 + 1b 5 + 1f 6 + 20 6 + 21 6 + 25 6 + 28 7 + 29 7 + 2a 7 + 2b 7 + 2c 7 + 2e 7 + 2f 7 + 30 7 + 34 8 + 35 8 + 36 8 + 37 8 + 38 8 + 3b 9 + 3c 9 + 3d 9 + 3e 9 + 3f 9 + 41 9 + 42 9 + 43 9 + 47 10 + 4b 10 + 4e 11 + 4f 11 + 50 11 + 51 11 + 52 11 + 54 11 + 55 11 + 56 11 + 5a 13 + 5b 13 + 5c 13 + 5d 13 + 5e 13 + 60 13 + 63 15 + } + + method 'testExpression (Ljava/lang/Object;)I' { + 6 18 + 8 18 + 9 18 + a 18 + b 18 + c 18 + 10 18 + 13 18 + 17 18 + 18 18 + 19 18 + 1a 18 + 1b 18 + 1e 18 + 22 18 + 23 18 + 24 18 + 28 18 + 2b 18 + 2f 18 + 33 18 + 36 18 + 3a 18 + 3b 18 + } + + method 'testStatement2 (Ljava/lang/Object;Ljava/lang/Object;)V' { + d 22 + e 22 + f 22 + 10 22 + 11 22 + 12 22 + 16 22 + 19 23 + 1a 23 + 1b 23 + 1c 23 + 1d 23 + 1f 23 + 20 23 + 21 23 + 25 24 + 26 24 + 27 24 + 2b 24 + 2e 25 + 2f 25 + 30 25 + 31 25 + 32 25 + 34 25 + 35 25 + 36 25 + 3a 26 + 3b 26 + 3c 26 + 3d 26 + 3e 26 + 41 27 + 42 27 + 43 27 + 44 27 + 45 27 + 47 27 + 48 27 + 49 27 + 4d 28 + 4e 28 + 4f 28 + 50 28 + 51 28 + 54 29 + 55 29 + 56 29 + 57 29 + 58 29 + 5a 29 + 5b 29 + 5c 29 + 60 30 + 61 30 + 62 30 + 63 30 + 64 30 + 67 31 + 68 31 + 69 31 + 6a 31 + 6b 31 + 6d 31 + 6e 31 + 6f 31 + 73 32 + 74 32 + 75 32 + 76 32 + 77 32 + 7a 33 + 7b 33 + 7c 33 + 7d 33 + 7e 33 + 80 33 + 81 33 + 82 33 + 86 35 + 87 35 + 88 35 + 89 35 + 8a 35 + 8c 35 + 8f 37 + } + + method 'booleanNightmares (ZZZZZZZ)V' { + 0 40 + 3 40 + 4 40 + 5 40 + 6 40 + 7 40 + f 40 + 12 41 + 13 41 + 14 41 + 15 41 + 16 41 + 18 41 + 19 41 + 1a 41 + 1e 42 + 1f 42 + 20 42 + 21 42 + 24 42 + 25 42 + 26 42 + 2e 42 + 31 43 + 32 43 + 33 43 + 34 43 + 35 43 + 37 43 + 38 43 + 39 43 + 3d 44 + 3e 44 + 3f 44 + 40 44 + 43 44 + 44 44 + 45 44 + 4d 44 + 50 45 + 51 45 + 52 45 + 53 45 + 54 45 + 56 45 + 57 45 + 58 45 + 5c 46 + 5d 46 + 5e 46 + 61 47 + 62 47 + 63 47 + 64 47 + 65 47 + 67 47 + 68 47 + 69 47 + 6d 48 + 6e 48 + 6f 48 + 70 48 + 71 48 + 74 48 + 75 48 + 76 48 + 79 48 + 7a 48 + 7b 48 + 7c 48 + 84 48 + 87 49 + 88 49 + 89 49 + 8a 49 + 8b 49 + 8d 49 + 8e 49 + 8f 49 + 93 51 + 94 51 + 95 51 + 96 51 + 97 51 + 99 51 + 9c 53 + } +} + +Lines mapping: +5 <-> 5 +6 <-> 5 +7 <-> 7 +8 <-> 9 +9 <-> 11 +10 <-> 14 +12 <-> 16 +15 <-> 19 +16 <-> 19 +17 <-> 19 +18 <-> 19 +19 <-> 19 +20 <-> 19 +26 <-> 23 +27 <-> 25 +28 <-> 27 +29 <-> 29 +30 <-> 31 +31 <-> 33 +32 <-> 36 +34 <-> 38 +37 <-> 41 +38 <-> 41 +39 <-> 43 +40 <-> 45 +41 <-> 47 +42 <-> 49 +43 <-> 52 +45 <-> 54 +Not mapped: +25 diff --git a/plugins/kotlin/testData/results/pkg/TestWhenBoolean.dec b/plugins/kotlin/testData/results/pkg/TestWhenBoolean.dec new file mode 100644 index 0000000000..e288eca3d1 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestWhenBoolean.dec @@ -0,0 +1,106 @@ +package pkg + +class TestWhenBoolean { + public fun testIf(a: Int, b: Int, c: Int, d: Int) { + if (a == 1// 6 18 + || b == 2// 7 + || c != 3// 8 + && ( + a > b// 9 + || (a > c || c > b) && (if (d == 7) b == 100 - a else a != -100 || b == 0 || c != 55 && (if (d == 66) 25 <= c && c < 34 else c < d && a < b))// 10 11 12 13 14 15 16 + )) { + System.out.println("hello");// 21 + } + }// 23 +} + +class 'pkg/TestWhenBoolean' { + method 'testIf (IIII)V' { + 1 4 + 2 4 + 3 4 + a 5 + b 5 + c 5 + 13 6 + 14 6 + 15 6 + 1c 8 + 1d 8 + 1e 8 + 25 9 + 26 9 + 27 9 + 2a 9 + 2b 9 + 2c 9 + 38 9 + 3f 9 + 40 9 + 41 9 + 42 9 + 43 9 + 46 9 + 47 9 + 48 9 + 49 9 + 4a 9 + 4b 9 + 56 9 + 57 9 + 58 9 + 59 9 + 5c 9 + 5d 9 + 64 9 + 65 9 + 66 9 + 67 9 + 6e 9 + 6f 9 + 70 9 + 71 9 + 72 9 + 75 9 + 76 9 + 77 9 + 78 9 + 7b 9 + 7c 9 + 7d 9 + 7e 9 + 8d 9 + 8e 9 + 8f 9 + 90 9 + 93 9 + 94 9 + 95 9 + a1 4 + a4 11 + a5 11 + a6 11 + a7 11 + a8 11 + aa 11 + ad 13 + } +} + +Lines mapping: +6 <-> 5 +7 <-> 6 +8 <-> 7 +9 <-> 9 +10 <-> 10 +11 <-> 10 +12 <-> 10 +13 <-> 10 +14 <-> 10 +15 <-> 10 +16 <-> 10 +18 <-> 5 +21 <-> 12 +23 <-> 14 +Not mapped: +5 diff --git a/plugins/kotlin/testData/results/pkg/TestWhenControlFlow.dec b/plugins/kotlin/testData/results/pkg/TestWhenControlFlow.dec new file mode 100644 index 0000000000..850f632e23 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestWhenControlFlow.dec @@ -0,0 +1,68 @@ +package pkg + +class TestWhenControlFlow { + public fun test1(x: Int) { + var xx: Int = x;// 5 + + while (xx > 0) {// 7 + if (--xx == 10) {// 8 9 10 + break; + } + + if (xx != 5) {// 11 + if (3 <= xx && xx < 5) {// 12 + return; + } + + System.out.println(xx);// 15 + } + } + }// 17 +} + +class 'pkg/TestWhenControlFlow' { + method 'test1 (I)V' { + 0 4 + 1 4 + 2 6 + 3 6 + 6 7 + 7 7 + 8 7 + 9 7 + b 7 + c 7 + d 7 + e 7 + 11 11 + 12 11 + 13 11 + 16 12 + 17 12 + 18 12 + 1b 12 + 1c 12 + 1d 12 + 29 12 + 2c 13 + 2d 16 + 2e 16 + 2f 16 + 30 16 + 31 16 + 32 16 + 33 16 + 37 19 + } +} + +Lines mapping: +5 <-> 5 +7 <-> 7 +8 <-> 8 +9 <-> 8 +10 <-> 8 +11 <-> 12 +12 <-> 13 +15 <-> 17 +17 <-> 20 diff --git a/plugins/kotlin/testData/src/kt/pkg/TestAnnotations.kt b/plugins/kotlin/testData/src/kt/pkg/TestAnnotations.kt new file mode 100644 index 0000000000..b4f6ead740 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestAnnotations.kt @@ -0,0 +1,22 @@ +package pkg + +class TestAnnotations { + annotation class TestAnnotation( + val first: String, + val second: Int, + ) + + @Repeatable + annotation class RepeatableAnnotation( + val value: String, + ) + + @TestAnnotation("test", 1) + fun test() { + } + + @RepeatableAnnotation("test") + @RepeatableAnnotation("test2") + fun test2() { + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestAnyType.kt b/plugins/kotlin/testData/src/kt/pkg/TestAnyType.kt new file mode 100644 index 0000000000..1189fa9765 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestAnyType.kt @@ -0,0 +1,13 @@ +package pkg + +class TestAnyType { + fun test(param: Any): Int { + if (param is String) { + return param.length + } + + println(param) + + return 0 + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestBitwiseFunctions.kt b/plugins/kotlin/testData/src/kt/pkg/TestBitwiseFunctions.kt new file mode 100644 index 0000000000..d70c08cbe7 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestBitwiseFunctions.kt @@ -0,0 +1,27 @@ +package pkg + +class TestBitwiseFunctions { + fun and(a: Int, b: Int): Int { + return a and b + } + + fun or(a: Int, b: Int): Int { + return a or b + } + + fun xor(a: Int, b: Int): Int { + return a xor b + } + + fun shl(a: Int, b: Int): Int { + return a shl b + } + + fun shr(a: Int, b: Int): Int { + return a shr b + } + + fun ushr(a: Int, b: Int): Int { + return a ushr b + } +} diff --git a/plugins/kotlin/testData/src/kt/pkg/TestClassDec.kt b/plugins/kotlin/testData/src/kt/pkg/TestClassDec.kt new file mode 100644 index 0000000000..921541eedc --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestClassDec.kt @@ -0,0 +1,22 @@ +package pkg + +class TestClassDec { + class EmptyDec + + class Vec2iVar(var x: Int, var y: Int) + class Vec2iVal(val x: Int, val y: Int) + class Vec2i(x: Int, y: Int) + + fun Vec2iVal.dot(v: Vec2iVal): Int { + return x * v.x + y * v.y + } + + fun test() { + var a = EmptyDec() + var vec = Vec2iVal(1, 2) + var vec1 = Vec2iVal(2, 4) + + println(vec.x) + println(vec.dot(vec1)) + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestComparison.kt b/plugins/kotlin/testData/src/kt/pkg/TestComparison.kt new file mode 100644 index 0000000000..5bdcb7a31a --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestComparison.kt @@ -0,0 +1,27 @@ +package pkg + +class TestComparison { + fun test2(a: Any, b: Any): Boolean { + return a == b + } + + fun test3(a: Any, b: Any): Boolean { + return a === b + } + + fun testNull2(a: Any?): Boolean { + return a == null + } + + fun testNull3(a: Any?): Boolean { + return a === null + } + + fun testNullDouble2(a: Any?, b: Any?): Boolean { + return a == b + } + + fun testNullDouble3(a: Any?, b: Any?): Boolean { + return a === b + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestCompileTimeErrors.kt b/plugins/kotlin/testData/src/kt/pkg/TestCompileTimeErrors.kt new file mode 100644 index 0000000000..9a6961a065 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestCompileTimeErrors.kt @@ -0,0 +1,19 @@ +package pkg + +class TestCompileTimeErrors { + interface Test { + val testValue: Int + } + + @Suppress("BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER") + fun test(i: I): O where O : I, O : Test { + TODO() + } + + fun test2(i: Int?): Test? { + return if (i == null) null else object : Test { + @Suppress("PROPERTY_TYPE_MISMATCH_ON_OVERRIDE") + override val testValue = i + } + } +} diff --git a/plugins/kotlin/testData/src/kt/pkg/TestConstructors.kt b/plugins/kotlin/testData/src/kt/pkg/TestConstructors.kt new file mode 100644 index 0000000000..e08833f293 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestConstructors.kt @@ -0,0 +1,11 @@ +package pkg + +class TestConstructors private constructor() { + constructor(a: Int) : this() { + println("a = $a") + } + + constructor(a: Int, b: Int) : this(a) { + println("b = $b") + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestContracts.kt b/plugins/kotlin/testData/src/kt/pkg/TestContracts.kt new file mode 100644 index 0000000000..70b75e4084 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestContracts.kt @@ -0,0 +1,55 @@ +package pkg + +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +@OptIn(ExperimentalContracts::class) +class TestContracts { + fun testSimpleContract(x: Int?): Int { + contract { + returns() implies (x != null) + } + if (x == null) error("x is null") + return x + } + + fun testBooleanContract(a: Boolean, b: Boolean): Boolean? { + contract { + returns(true) implies (!a && !b) + returns(null) implies (a && b) + returns(false) implies ((a && !b) || (!a && b)) + } + + return if (a && b) null else a || b + } + + fun testTypeContract(x: Any?): Int { + contract { + returns() implies (x is Int) + } + if (x !is Int) error("x is not Int") + return x + } + + fun testFunctionalContract(f: () -> Int): Int { + contract { + callsInPlace(f, InvocationKind.EXACTLY_ONCE) + } + return f() + } + + fun testFunctionalContract2(f: () -> Int, b: Boolean): Int { + contract { + callsInPlace(f, InvocationKind.AT_MOST_ONCE) + } + return if (b) f() else 0 + } + + fun testFunctionalContract3(f: () -> Int, i: Int): Int { + contract { + callsInPlace(f) + } + return (0..i).sumOf { f() } + } +} diff --git a/plugins/kotlin/testData/src/kt/pkg/TestConvertedK2JOps.kt b/plugins/kotlin/testData/src/kt/pkg/TestConvertedK2JOps.kt new file mode 100644 index 0000000000..2861703f8a --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestConvertedK2JOps.kt @@ -0,0 +1,13 @@ +package pkg + +class TestConvertedK2JOps { + val list: List = listOf("a", "b", "c") + val set: Set = setOf("a", "b", "c") + val map: Map = mapOf("a" to "b", "c" to "d") + val any: Any = Any() + + fun codeConstructs() { + println("Hello, world!") + val concatenations = "a" + "b" + "c" + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestDataClass.kt b/plugins/kotlin/testData/src/kt/pkg/TestDataClass.kt new file mode 100644 index 0000000000..8f3c0a18cc --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestDataClass.kt @@ -0,0 +1,8 @@ +package pkg + +data class TestDataClass( + val dataClassVal: Regex, + val variableWithVeryLongName: Int, + val requestLineWrapsIfTheParamListIsTooLong: List, + val nullability: String?, +) diff --git a/plugins/kotlin/testData/src/kt/pkg/TestDestructors.kt b/plugins/kotlin/testData/src/kt/pkg/TestDestructors.kt new file mode 100644 index 0000000000..7f6b67229f --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestDestructors.kt @@ -0,0 +1,84 @@ +package pkg + +class TestDestructors { + fun destructDataClasses( + x: Pair, + y: Triple + ) { + val (a, b) = x + println("$a $b") + + val (c, d, e) = y + println("$c $d $e") + } + + fun destructDataClassesSpecial( + x: Pair, + y: Triple, Nothing?, Unit> + ) { + val (a, b) = x + println("$a $b") + + val (c, d, e) = y + println("$c $d $e") + } + + fun destructDataClassesSkip( + x: Triple, + y: Triple + ) { + val (_, a) = x + println(a) + + val (d, _, e) = y + println("$d $e") + } + + fun destructorImpossible(x: Pair) : String { + val (a, b) = x + // https://youtrack.jetbrains.com/issue/KT-12604/No-Unreachable-code-with-componentN-function-returning-Nothing + return a + } + + fun destructExtensionFunction(x: Int) { + val (a, b, c) = x + println("$a$b$c") + } + + inline fun destructInlineLambda(x: () -> Int) { + val (a, b, c) = x + println("$a$b$c") + } + + fun callDestructInlineLambda() { + destructInlineLambda { 123 } + } + + fun callDestructInlineLambdaWithControlFlow(x: Int) { + destructInlineLambda { if (x in 100..999) x else return } + } + + fun destructInlineLambdaNoInline(x: () -> Int) { + val (a, b, c) = x + println("$a$b$c") + } + + fun destructLambdaInline(x: Int) { + val (a, b, c) = { x } + println("$a$b$c") + } +// +// fun destructLambdaInlineControlFlow(x: Int) { +// val (a, b, c) = { if (x in 100..999) x else return } +// println("$a$b$c") +// } + + + operator fun Int.component1() = this.toString()[0] - '0' + operator fun Int.component2() = this.toString()[1] - '0' + operator fun Int.component3() = this.toString()[2] - '0' + + inline operator fun (() -> Int).component1() = this().component1() + inline operator fun (() -> Int).component2() = this().component2() + inline operator fun (() -> Int).component3() = this().component3() +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestExtensionFun.kt b/plugins/kotlin/testData/src/kt/pkg/TestExtensionFun.kt new file mode 100644 index 0000000000..8c65569e9b --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestExtensionFun.kt @@ -0,0 +1,11 @@ +package pkg + +class TestExtensionFun { + fun CharSequence.repeat2(n: Int): String { + return this.repeat(n); + } + + fun test() { + println("Bye ".repeat2(2)) + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestForRange.kt b/plugins/kotlin/testData/src/kt/pkg/TestForRange.kt new file mode 100644 index 0000000000..fe2399f0eb --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestForRange.kt @@ -0,0 +1,81 @@ +package pkg + +class TestForRange { + fun testInt() { + for (i in 1..10) { + println(i) + } + } + + fun testChar() { + for (c in 'a'..'z') { + println(c) + } + } + + fun testIntStep() { + for (i in 1..10 step 2) { + println(i) + } + } + + fun testIntStepX(x: Int) { + for (i in 1..100 step x) { + println(i) + } + } + + fun testIntDownTo() { + for (i in 10 downTo 1) { + println(i) + } + } + + fun testIntDownToStep() { + for (i in 10 downTo 1 step 2) { + println(i) + } + } + + fun testIntDownToStepX(x: Int) { + for (i in 100 downTo 1 step x) { + println(i) + } + } + + fun testUntil() { + for (i in 1 until 10) { + println(i) + } + } + + fun testUntilStep() { + for (i in 1 until 100 step 2) { + println(i) + } + } + + fun testUntilStepX(x: Int) { + for (i in 1 until 100 step x) { + println(i) + } + } + + fun testIntY(x: Int, y: Int) { + for (i in x..y) { + println(i) + } + } + + fun testIntYStep(x: Int, y: Int) { + for (i in x..y step 2) { + println(i) + } + } + + fun testIntYStepX(x: Int, y: Int, z: Int) { + for (i in x..y step z) { + println(i) + } + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestFunVarargs.kt b/plugins/kotlin/testData/src/kt/pkg/TestFunVarargs.kt new file mode 100644 index 0000000000..8fe9a303bf --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestFunVarargs.kt @@ -0,0 +1,24 @@ +package pkg + +class TestFunVarargs { + fun printAll(vararg messages: String) { + for (m in messages) println(m) + } + + fun printAllArray(messages: Array) { + for (m in messages) println(m) + } + + fun log(vararg entries: String) { + printAll(*entries) + printAllArray(entries) + } + + fun test() { + log("a", "b", "c") + } + + fun nestedArrays(e0: Array, e1: Array, e2: Array>>){ + + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestFuncRef.kt b/plugins/kotlin/testData/src/kt/pkg/TestFuncRef.kt new file mode 100644 index 0000000000..425957a2ec --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestFuncRef.kt @@ -0,0 +1,11 @@ +package pkg + +fun accept(f: (Int) -> String) = println(f.invoke(5)) + +fun function(r: Int): String { + return "OK".repeat(r) +} + +fun test() { + accept(::function) +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestGenerics.kt b/plugins/kotlin/testData/src/kt/pkg/TestGenerics.kt new file mode 100644 index 0000000000..f7e67c8180 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestGenerics.kt @@ -0,0 +1,20 @@ +package pkg + +class TestGenerics { + fun genericFun(v: T): T { + return v + } + + fun nullableGeneric(v: T): T? { + return null + } + + fun subType(v: TestGenerics) { + } + + fun superType(v: TestGenerics) { + } + + fun any(v: TestGenerics<*>) { + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestIfRange.kt b/plugins/kotlin/testData/src/kt/pkg/TestIfRange.kt new file mode 100644 index 0000000000..1941233aa5 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestIfRange.kt @@ -0,0 +1,74 @@ +package pkg + +class TestIfRange { + fun testInt(x: Int) { + if (x in 1..10) { + println(x) + } + } + + fun testChar(x: Char) { + if (x in 'a'..'z') { + println(x) + } + } + + fun testInvertedInt(x: Int) { + if (x !in 1..10) { + println(x) + } + } + + fun testIntStep(x: Int) { + if (x in 1..100 step 2) { + println(x) + } + } + fun testIntStepY(x: Int, y: Int) { + if (x in 1..100 step y) { + println(x) + } + } + + fun testIntY(x: Int, y: Int) { + if (x in 1..y) { + println(x) + } + } + + fun testIntDownTo(x: Int) { + if (x in 10 downTo 1) { + println(x) + } + } + + fun testIntDownToStep(x: Int) { + if (x in 10 downTo 1 step 2) { + println(x) + } + } + + fun testIntUntil(x: Int) { + if (x in 1 until 10) { + println(x) + } + } + + fun testIntUntilStep(x: Int) { + if (x in 1 until 100 step 2) { + println(x) + } + } + + fun testIntUntilY(x: Int, y: Int) { + if (x in 1 until y) { + println(x) + } + } + + fun testIntUntilSelf(x: Int) { + if (x in 1 until x) { + println(x) + } + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestInfixFun.kt b/plugins/kotlin/testData/src/kt/pkg/TestInfixFun.kt new file mode 100644 index 0000000000..22a2ba5136 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestInfixFun.kt @@ -0,0 +1,22 @@ +package pkg + +class TestInfixFun { + fun test() { + infix fun Int.times(str: String) = str.repeat(this) + + println(2 times "Bye ") + } + + infix fun Int.mult(str: String) = str.repeat(this) + + fun testOuter() { + + println(2 mult "Bye ") + } + + fun testDuplicate() { + infix fun Int.mult(str: String) = str.repeat(this + 1) + + println(2 mult "Bye ") + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestKotlinTypes.kt b/plugins/kotlin/testData/src/kt/pkg/TestKotlinTypes.kt new file mode 100644 index 0000000000..772fa6d5a9 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestKotlinTypes.kt @@ -0,0 +1,9 @@ +package pkg + +class TestKotlinTypes { + fun throwAlways(): Nothing { + throw Exception() + } + + val consumer: (Int) -> Unit = {} +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestKt.kt b/plugins/kotlin/testData/src/kt/pkg/TestKt.kt new file mode 100644 index 0000000000..d50167a0ef --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestKt.kt @@ -0,0 +1,7 @@ +package pkg + +class TestKt { + fun test() { + println("Hello, world!") + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestLabeledJumps.kt b/plugins/kotlin/testData/src/kt/pkg/TestLabeledJumps.kt new file mode 100644 index 0000000000..6d10861488 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestLabeledJumps.kt @@ -0,0 +1,31 @@ +package pkg + +class TestLabeledJumps { + fun testContinue(tester: (Int) -> Boolean) { + loop@ for (i in 1..100) { + for (j in 1..100) { + if (tester.invoke(j)) { + continue@loop + } + + println("$j $i") + } + + println("loop") + } + } + + fun testBreak(tester: (Int) -> Boolean) { + loop@ for (i in 1..100) { + for (j in 1..100) { + if (tester.invoke(j)) { + break@loop + } + + println("$j $i") + } + } + + println("end") + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestNonInlineLambda.kt b/plugins/kotlin/testData/src/kt/pkg/TestNonInlineLambda.kt new file mode 100644 index 0000000000..cd0932f7f5 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestNonInlineLambda.kt @@ -0,0 +1,142 @@ +package pkg + +@Suppress("RedundantVisibilityModifier") +open class TestNonInlineLambda { + + fun testCaptureInt(x : Int) { + val y = x + execute { + println(y) + } + } + + fun testCaptureObject(x: String) { + val y = x + execute { + println(y) + } + } + + fun testCaptureIntIterationValue(x: Iterable) { + for (i in x){ + execute { + println(i) + } + } + } + + fun testCaptureObjectIterationValue(x : Iterable) { + for (i in x){ + execute { + println(i) + } + } + } + + fun testCaptureMutableInt(x: Int){ + var y = x + execute { + println(y) + } + y++ + execute { + println(y) + } + y *= 500 + execute { + println(y) + } + y = 100 + execute { + println(y) + } + y += x + execute { + println(y) + } + } + + fun testCaptureMutableObject(x: String){ + var y = x + execute { + println(y) + } + y += "!!" + execute { + println(y) + } + y += y + y + execute { + println(y) + } + y = "Hello: " + execute { + println(y) + } + y += x + execute { + println(y) + } + } + + fun testCaptureAndMutateInt(x: Int){ + var y = 0 + execute { + while (y < 10) { + println(y++) + } + } + y = 5 + x + execute { + while (y > 0) { + println(y--) + } + } + } + + fun testCaptureAndMutateString(x: String){ + var y = "" + execute { + while (y.length < 10) { + y = " " + y + println(y) + } + } + y = "Hello: " + x + execute { + while (y.isNotBlank()) { + println() + y = y.drop(1) + } + } + } + + public var intField = 0 + + fun testCapturePublicMutableIntField() { + execute { intField++ } + } + + public var stringField = "" + + fun testCapturePublicMutableStringField() { + execute { stringField += "!" } + } + + private var privateIntField = 0 + + fun testCapturePrivateMutableIntField() { + execute { privateIntField++ } + } + + private var privateStringField = "" + + fun testCapturePrivateMutableStringField() { + execute { privateStringField += "!" } + } + + + open fun execute(block: () -> Unit) { + + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestNothingReturns.kt b/plugins/kotlin/testData/src/kt/pkg/TestNothingReturns.kt new file mode 100644 index 0000000000..19eba4e75a --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestNothingReturns.kt @@ -0,0 +1,38 @@ +package pkg + +class TestNothingReturns { + fun loop(): Nothing { + while (true) { + println("loop") + } + } + + fun test1(): Nothing { + loop() + } + + fun test2(): Long { + test1() + } + + fun test3(i: Int): Int { + if (i == 0) { + loop() + } + + return test3(i - 1) + 1 + } + + fun test4() { + loop() + println("hello") + } + + fun test5(s:String): String { + return s.repeat(5) + loop() + } + + fun test6(s: String?): String { + return s ?: loop() + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestNullable.kt b/plugins/kotlin/testData/src/kt/pkg/TestNullable.kt new file mode 100644 index 0000000000..8cfcb9e517 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestNullable.kt @@ -0,0 +1,15 @@ +package pkg + +class TestNullable { + fun nullableParams(v: String, vn: String?) { + + } + + fun nullableReturn(): String? { + return null + } + + fun nullableGenerics(v: List): List? { + return v + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestNullableOperator.kt b/plugins/kotlin/testData/src/kt/pkg/TestNullableOperator.kt new file mode 100644 index 0000000000..ab83490e65 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestNullableOperator.kt @@ -0,0 +1,45 @@ +package pkg + +class TestNullableOperator { + fun test(x: Int?): Int { + return x ?: 0 + } + + fun test2(x: String?): String { + return x ?: "default" + } + + fun test2_1(x: Any?): Any { + return x ?: "default" + } + + fun test2_2(x: Any?): Any { + return x ?: "default" + } + + fun test3(x: Int?): Int { + return x ?: throw Exception() + } + + fun test4(x: Exception?) { + x?.printStackTrace() + } + + fun test5(x: Exception?) { + x?.printStackTrace() ?: throw Exception() + } + + fun test6(x: Int?): Int { + val y = x ?: return 0 + + println(y) + + return y + } + + fun test6_1(x: Int?) { + val y = x ?: return + + println(y) + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestObject.kt b/plugins/kotlin/testData/src/kt/pkg/TestObject.kt new file mode 100644 index 0000000000..76da1e4469 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestObject.kt @@ -0,0 +1,18 @@ +package pkg + +object TestObject { + fun objectFun() { + objectVar-- + } + + private var objectVar = 42 + + val objectVal = Regex("") + + const val objectConstVal = 926 + + @JvmStatic + fun objectJvmStaticFun() { + objectVar++ + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestParams.kt b/plugins/kotlin/testData/src/kt/pkg/TestParams.kt new file mode 100644 index 0000000000..29be04d872 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestParams.kt @@ -0,0 +1,26 @@ +package pkg + +class TestParams { + fun printMessageUnit(message: String): Unit { + println(message) + } + + fun printMessageVoid(message: String) { + println(message) + } + + fun multiply(x: Int, y: Int) = x * y + + fun multiplyBraces(x: Int, y: Int): Int { + return x * y + } + + fun printMessageWithPrefix(message: String, prefix: String = "Info") { + println("[$prefix] $message") + } + + fun callPrintMessage() { + printMessageWithPrefix("Test") + printMessageWithPrefix("Test", "Debug") + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestPoorNames.kt b/plugins/kotlin/testData/src/kt/pkg/TestPoorNames.kt new file mode 100644 index 0000000000..3db0e9da25 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestPoorNames.kt @@ -0,0 +1,26 @@ +package pkg + +class TestPoorNames { + fun `Function with spaces`() { + } + + fun `Dangerous function name?!`() { + } + + val `Property with spaces` = 42 + val `Dangerous property name?!` = "test" + + fun `functionWith$Dollar`() { + } + + fun functionWithParameters(`Parameter with spaces`: Int, `Dangerous parameter name?!`: String) { + } + + class `Class with spaces` + + fun test() { + val instance = `Class with spaces`() + `Dangerous function name?!`() + functionWithParameters(`Parameter with spaces` = 42, `Dangerous parameter name?!` = "test") + } +} diff --git a/plugins/kotlin/testData/src/kt/pkg/TestReflection.kt b/plugins/kotlin/testData/src/kt/pkg/TestReflection.kt new file mode 100644 index 0000000000..bf052aa450 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestReflection.kt @@ -0,0 +1,27 @@ +package pkg + +class TestReflection { + fun testClassReference() { + println(TestReflection::class) + println(TestReflection::class.java) + } + + fun testPrimitiveWrapper() { + println(Int::class) + println(Int::class.javaObjectType) + } + + fun testPrimitiveType() { + println(Int::class.javaPrimitiveType) + } + + fun testInferredPrimitive() { + println(Int::class.java) + } + + fun testFunctionReference() { + val f = TestReflection::testClassReference + println(f) + f(TestReflection()) + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestSafeCasts.kt b/plugins/kotlin/testData/src/kt/pkg/TestSafeCasts.kt new file mode 100644 index 0000000000..cdc87352ed --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestSafeCasts.kt @@ -0,0 +1,67 @@ +package pkg + +class TestSafeCasts { + fun test(obj: Any): Boolean { + val t = obj as? Int + + return t == 1 + } + + fun testTestBefore(obj: Any): Boolean? { + if (obj !is Int) { + return null + } + + val t = obj as? Int + + return t == 1 + } + + fun testHardIncompatible(obj: Int): Boolean { + val t = obj as? String + + return t == "1" + } + + fun testSmartCastIncompatible(obj: Any): Boolean { + if (obj !is Int) { + return false + } + + val t = obj as? String + + return t == "1" + } + + fun testCastNonNullToNullable(obj: Any): Boolean { + val t = obj as? Int? + + return t == 1 + } + + fun testBeforeNonNullToNullable(obj: Any): Boolean? { + if (obj !is Int?) { + return null + } + + val t = obj as? Int? + + return t == 1 + } + + fun testCastNullableToNullable(obj: Any?): Boolean { + val t = obj as? Int? + + return t == 1 + } + + fun testBeforeNullableToNullable(obj: Any?): Boolean? { + if (obj !is Int?) { + return null + } + + val t = obj as? Int? + + return t == 1 + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestSealedHierarchy.kt b/plugins/kotlin/testData/src/kt/pkg/TestSealedHierarchy.kt new file mode 100644 index 0000000000..2a1e8a69df --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestSealedHierarchy.kt @@ -0,0 +1,6 @@ +package pkg + +sealed class TestSealedHierarchy { + object TestObject : TestSealedHierarchy() + class TestClass(val x: Int) : TestSealedHierarchy() +} diff --git a/plugins/kotlin/testData/src/kt/pkg/TestShadowParam.kt b/plugins/kotlin/testData/src/kt/pkg/TestShadowParam.kt new file mode 100644 index 0000000000..da08675c3c --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestShadowParam.kt @@ -0,0 +1,12 @@ +package pkg + +class TestShadowParam { + fun test(x: Int) { + var x = x + x-- + println(x) + if (x < 0) { + println(x) + } + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestSmartCasts.kt b/plugins/kotlin/testData/src/kt/pkg/TestSmartCasts.kt new file mode 100644 index 0000000000..60f89a5aed --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestSmartCasts.kt @@ -0,0 +1,89 @@ +package pkg + +class TestSmartCasts { + interface X { + fun Iterable<*>.woo() = "A" + } + sealed class A { + class B : A() { + fun testB(): String = "B" + } + class C : A() { + fun testC(): String = "C" + } + + fun test(): String = "" + } + + fun testWhen(o: Any?): String { + when (o) { + is String -> { + return o + } + + is A.B -> println("B: $o") + is A.C -> println("C: $o") + is Pair<*, *> -> return "<${testWhen(o.first)}, ${testWhen(o.second)}>" + null -> return "null" + else -> return "else: $o" + } + + return (o as A).test() + } + + fun testIf(a: Any?): String { + if (a is A.B || a is A.C) { + return (a as A).test() + } + + return "else: $a" + } + + fun testIf2(a: Any?): String { + if (a is A) { + if (a is A.B || a is A.C) { + println(a.test()) + } + + if (a is A.B) { + if (a is A.C) { + println(a.testB()) + println(a.testC()) + } + + if (a is A.C && a.testC() == "C" || a is A.B) { + println(a.testB()) + } + } + } + + return "else: $a" + } + + fun testCast(a: Any?) { + println(a) + a as String + println("hello") + println(a) + a[0] + println(a[0]) + } + + fun testSealedIf(a: A): String { + if (a is A.B) { + return a.testB() + } else if (a is A.C) { + return a.testC() + } else { + return a.test() + } + } + + fun testDoubleType(t: List) : String { + if (t is X) with(t){ + return woo() + } + + return t[0] + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestSynchronized.kt b/plugins/kotlin/testData/src/kt/pkg/TestSynchronized.kt new file mode 100644 index 0000000000..827fe57c03 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestSynchronized.kt @@ -0,0 +1,9 @@ +package pkg + +class TestSynchronized { + fun test() { + synchronized (this) { + println("Hello") + } + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestTailrecFunctions.kt b/plugins/kotlin/testData/src/kt/pkg/TestTailrecFunctions.kt new file mode 100644 index 0000000000..14cc11df8e --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestTailrecFunctions.kt @@ -0,0 +1,56 @@ +package pkg + +class TestTailrecFunctions { + tailrec fun sum(x: Long, sum: Long): Long { + if (x == 0.toLong()) return sum + return sum(x - 1, sum + x) + } + + tailrec fun testFinally() { + try { + // do nothing + } finally { + testFinally() + } + } + + tailrec fun testFinallyReturn() : Int { + try { + // do nothing + } finally { + return testFinallyReturn() + } + } + + tailrec fun fooTry() { + try { + return fooTry() + } + catch (e: Throwable) { + } + } + + tailrec fun testTryCatchFinally() : Unit { + try { + testTryCatchFinally() + } catch (any : Exception) { + testTryCatchFinally() + } finally { + testTryCatchFinally() + } + } + + tailrec fun fastPow(x: Long, n: Long, acc: Long = 1L): Long { + if (n == 0L) return acc + if (n % 2 == 0L) return fastPow(x * x, n / 2, acc) + return fastPow(x, n - 1, acc * x) + } + + tailrec fun fastPow(x: Long, n: Long) : Long = when{ + n == 0L -> 1L + n % 2 == 0L -> fastPow(x * x, n / 2) + else -> x * fastPow(x, n - 1) + } + + +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestTopLevel.kt b/plugins/kotlin/testData/src/kt/pkg/TestTopLevel.kt new file mode 100644 index 0000000000..74b94d49f6 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestTopLevel.kt @@ -0,0 +1,11 @@ +package pkg + +fun topLevelFun() { + val x = 1 +} + +var topLevelVar = 42 + +val topLevelVal = Regex("") + +const val topLevelConstVal = 926 diff --git a/plugins/kotlin/testData/src/kt/pkg/TestTryCatchExpressions.kt b/plugins/kotlin/testData/src/kt/pkg/TestTryCatchExpressions.kt new file mode 100644 index 0000000000..029ad7d87d --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestTryCatchExpressions.kt @@ -0,0 +1,59 @@ +package pkg + +import java.io.IOException + +class TestTryCatchExpressions { + fun test0(s: String) { + print( + try { + s.repeat(5) + } catch (e: RuntimeException) { + e.message ?: "ERROR" + } + ) + } + + fun test1(a: String, b: String) { + var x = a + test0( + try { + x.repeat(5) + } catch (e: RuntimeException) { + x = b + e.message ?: "ERROR" + } + try { + x.repeat(5) + } catch (e: RuntimeException) { + e.message ?: "ERROR" + } + ) + } + + fun test2(a: String, b: String) { + var x = a + test1(try { + if (a.length != b.length) { + return + } + x = b + test0(x) + x + } catch (e: IOException) { + x = a + test1(a, x) + x + } catch (e: RuntimeException) { + x = if (x == a) { + b + } else { + a + } + x + }, try { + x.repeat(5).also { x = it } + } catch (e: RuntimeException) { + println(x) + x + "!!" + (e.message ?: "") + }) + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestTryFinallyExpressions.kt b/plugins/kotlin/testData/src/kt/pkg/TestTryFinallyExpressions.kt new file mode 100644 index 0000000000..9d6ec7a582 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestTryFinallyExpressions.kt @@ -0,0 +1,52 @@ +package pkg + +import java.io.IOException + +class TestTryFinallyExpressions { + fun test0(s: String) { + print(try { + s.repeat(5) + } finally { + println("bye") + }) + } + + fun test1(a: String, b: String) { + var x = a + test0(try{ + x.repeat(5) + } finally { + x = b + } + try { + x.repeat(5) + } finally { + println(a) + } ) + } + + fun test2(a: String, b: String) { + var x = a + test1(try{ + if (a == b) { + return + } + x = b + test0(x) + x + } catch (e: IOException) { + x = a + test1(a, x) + x + } finally { + x = if (x == a) { + b + } else { + a + } + }, try { + x.repeat(5).also{x = it} + } finally { + println(x) + }) + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestVars.kt b/plugins/kotlin/testData/src/kt/pkg/TestVars.kt new file mode 100644 index 0000000000..2e8bd2ec40 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestVars.kt @@ -0,0 +1,39 @@ +package pkg + +class TestVars { + fun testVar() { + var a: String = "initial" + println(a) + var b: Int = 1 + var c = 3 + } + + fun testVal() { + val a: String = "initial" + println(a) + val b: Int = 1 + val c = 3 + } + + fun testPhi(bl: Boolean) { + val d: Int + + if (bl) { + d = 1 + } else { + d = 2 + } + + println(d) + } + + fun testIfExpr(bl: Boolean) { + val d: Int = if (bl) { + 1 + } else { + 2 + } + + println(d) + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestWhen.kt b/plugins/kotlin/testData/src/kt/pkg/TestWhen.kt new file mode 100644 index 0000000000..a174d1944d --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestWhen.kt @@ -0,0 +1,46 @@ +package pkg + +class TestWhen { + fun testStatement(obj: Any) { + when (obj) { + 1 -> println("1") + "2" -> println("2") + is Double -> println("Double") + !is Long -> println("Not Long") + else -> println("else") + } + } + + fun testExpression(obj: Any): Int { + return when (obj) { + 1 -> 1 + is Double -> 2 + "4" -> 4 + !is Long -> 3 + else -> 5 + } + } + + fun testStatement2(a: Any, b: Any): Unit { + when { + a == 15 -> println("a == 15") + a == "!!" -> println("a == !!") + a is Int -> println("a is Int") + a is String -> println("a is String") + b is Double -> println("b is Double") + a is Unit -> println("a is Unit") + else -> println("else") + } + } + + fun booleanNightmares(a: Boolean, b: Boolean, c:Boolean, d:Boolean, e:Boolean, f:Boolean, g:Boolean) { + when(a) { + (b != c) -> println("-_-") + (b && !e) -> println("xxx") + (!a || d) -> println("ohno") + false -> println("NIGHTMARE") + (g || (e && (f != c))) -> println("hello") + else -> println("else") + } + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestWhenBoolean.kt b/plugins/kotlin/testData/src/kt/pkg/TestWhenBoolean.kt new file mode 100644 index 0000000000..bb4389b9aa --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestWhenBoolean.kt @@ -0,0 +1,24 @@ +package pkg + +class TestWhenBoolean { + fun testIf(a: Int, b: Int, c: Int, d: Int) { + if (when { + a == 1 -> true + b == 2 -> true + c == 3 -> false + a > b -> true + c in a..b -> false + d == 7 -> b == 100 - a + a == -100 -> when { + b == 0 -> true + c == 55 -> false + d == 66 -> c in 25..33 + else -> c < d && a < b + } + else -> true + } + ) { + println("hello") + } + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestWhenControlFlow.kt b/plugins/kotlin/testData/src/kt/pkg/TestWhenControlFlow.kt new file mode 100644 index 0000000000..a8e8a04f53 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestWhenControlFlow.kt @@ -0,0 +1,19 @@ +package pkg + +class TestWhenControlFlow { + fun test1(x: Int) { + var x = x + + while (x > 0) { + x-- + when (x) { + 10 -> break + 5 -> continue + in 3..4 -> return + } + + println(x) + } + } + +} \ No newline at end of file diff --git a/plugins/scala/.gitignore b/plugins/scala/.gitignore new file mode 100644 index 0000000000..8443abe762 --- /dev/null +++ b/plugins/scala/.gitignore @@ -0,0 +1,4 @@ +/build/ +/out/ +/testData/classes/scala/ +bin \ No newline at end of file diff --git a/plugins/scala/build.gradle b/plugins/scala/build.gradle new file mode 100644 index 0000000000..63a34b997c --- /dev/null +++ b/plugins/scala/build.gradle @@ -0,0 +1,25 @@ +plugins{ + id 'scala' +} + +archivesBaseName = 'vineflower-scala' +sourceSets { + testDataScala.scala.srcDirs files("testData/src/scala/") +} + +dependencies { + implementation project(":") + testImplementation testFixtures(project(":")) + + testDataScalaImplementation 'org.scala-lang:scala3-library_3:3.1.3' +} + +task testDataClasses { + group = 'build' +} +testClasses.dependsOn(testDataClasses) + +compileTestDataScalaScala { + destinationDirectory = file("testData/classes/scala") +} +testDataClasses.dependsOn(testDataScalaClasses) \ No newline at end of file diff --git a/plugins/scala/gradle.properties b/plugins/scala/gradle.properties new file mode 100644 index 0000000000..0d22531f6d --- /dev/null +++ b/plugins/scala/gradle.properties @@ -0,0 +1 @@ +does_shadow=false \ No newline at end of file diff --git a/plugins/scala/src/main/java/org/vineflower/scala/ScalaPlugin.java b/plugins/scala/src/main/java/org/vineflower/scala/ScalaPlugin.java new file mode 100644 index 0000000000..1bddaa9fdf --- /dev/null +++ b/plugins/scala/src/main/java/org/vineflower/scala/ScalaPlugin.java @@ -0,0 +1,15 @@ +package org.vineflower.scala; + +import org.jetbrains.java.decompiler.api.plugin.Plugin; + +public class ScalaPlugin implements Plugin { + + public String id(){ + return "Scala"; + } + + @Override + public String description() { + return "Detects and decompiles Scala class files."; + } +} diff --git a/plugins/scala/src/main/resources/META-INF/services/org.jetbrains.java.decompiler.api.plugin.Plugin b/plugins/scala/src/main/resources/META-INF/services/org.jetbrains.java.decompiler.api.plugin.Plugin new file mode 100644 index 0000000000..80ff156331 --- /dev/null +++ b/plugins/scala/src/main/resources/META-INF/services/org.jetbrains.java.decompiler.api.plugin.Plugin @@ -0,0 +1 @@ +org.vineflower.scala.ScalaPlugin \ No newline at end of file diff --git a/plugins/scala/src/test/java/org/vineflower/scala/ScalaTests.java b/plugins/scala/src/test/java/org/vineflower/scala/ScalaTests.java new file mode 100644 index 0000000000..1c4d9d8d63 --- /dev/null +++ b/plugins/scala/src/test/java/org/vineflower/scala/ScalaTests.java @@ -0,0 +1,49 @@ +package org.vineflower.scala; + +import org.jetbrains.java.decompiler.SingleClassesTestBase; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; + +import java.util.ArrayList; +import java.util.List; + +import static org.jetbrains.java.decompiler.SingleClassesTestBase.TestDefinition.Version.SCALA; + +public class ScalaTests extends SingleClassesTestBase{ + + protected void registerAll(){ + registerSet("Entire Classpath", this::registerScalaTests, + IFernflowerPreferences.BYTECODE_SOURCE_MAPPING, "1", + IFernflowerPreferences.DUMP_ORIGINAL_LINES, "1", + IFernflowerPreferences.DUMP_EXCEPTION_ON_ERROR, "0", + IFernflowerPreferences.IGNORE_INVALID_BYTECODE, "1", + IFernflowerPreferences.VERIFY_ANONYMOUS_CLASSES, "1", + IFernflowerPreferences.INCLUDE_ENTIRE_CLASSPATH, "0", + IFernflowerPreferences.TERNARY_CONDITIONS, "1", + IFernflowerPreferences.FORCE_JSR_INLINE, "1" + ); + } + + private void registerScalaTests(){ + registerSc("TestCaseClasses", "Option1$", "Option2$", "Option3$", "EnumLike$"); + registerSc("TestObject$"); + registerSc("TestCompanionObject$"); + registerSc("TestDefaultParams$"); + registerSc("TestImplicits", "AddMonoid$", "MulMonoid$", "ConcatMonoid$", "Monoid"); + registerSc("TestAssocTypes", "Shuffler", "IntShuffler$", "StringShuffler$"); + } + + private void registerSc(String name, String... extras){ + List args = new ArrayList<>(2 + extras.length * 2); + if(name.endsWith("$")){ + var without = name.substring(0, name.length() - 1); + args.add(name); + name = without; + } + for(String extra : extras){ + if(extra.endsWith("$")) + args.add(extra.substring(0, extra.length() - 1)); + args.add(extra); + } + register(SCALA, name, args.toArray(String[]::new)); + } +} diff --git a/plugins/scala/testData/results/pkg/TestAssocTypes.dec b/plugins/scala/testData/results/pkg/TestAssocTypes.dec new file mode 100644 index 0000000000..c1340b5b55 --- /dev/null +++ b/plugins/scala/testData/results/pkg/TestAssocTypes.dec @@ -0,0 +1,206 @@ +package pkg; + +import scala.Predef.; +import scala.runtime.BoxesRunTime; + +public class TestAssocTypes { + public void user() { + .MODULE$.print(BoxesRunTime.boxToInteger(IntShuffler$.MODULE$.shuffle(3)));// 19 20 + .MODULE$.print(StringShuffler$.MODULE$.shuffle("abcd"));// 21 + } +} + +class 'pkg/TestAssocTypes' { + method 'user ()V' { + 0 7 + 1 7 + 2 7 + 3 7 + 4 7 + 5 7 + 6 7 + 7 7 + 8 7 + 9 7 + a 7 + b 7 + c 7 + d 7 + e 7 + f 7 + 10 8 + 11 8 + 12 8 + 13 8 + 14 8 + 15 8 + 16 8 + 17 8 + 18 8 + 19 8 + 1a 8 + 1b 8 + 1c 8 + 1d 8 + 1e 9 + } +} + +Lines mapping: +19 <-> 8 +20 <-> 8 +21 <-> 9 + +// Decompiled companion from pkg/Shuffler +package pkg; + +public interface Shuffler { + Object shuffle(final Object x); +} + +// Decompiled companion from pkg/IntShuffler +package pkg; + +public final class IntShuffler { + public static int shuffle(int var0) { + return IntShuffler$.MODULE$.shuffle(var0); + } +} + +class 'pkg/IntShuffler' { + method 'shuffle (I)I' { + 0 4 + 1 4 + 2 4 + 3 4 + 4 4 + 5 4 + 6 4 + 7 4 + } +} + +Lines mapping: + +// Decompiled companion from pkg/IntShuffler$ +package pkg; + +import java.io.Serializable; +import scala.runtime.ModuleSerializationProxy; + +public final class IntShuffler$ implements Shuffler, Serializable { + public static final IntShuffler$ MODULE$ = new IntShuffler$(); + + private IntShuffler$() { + } + + private Object writeReplace() { + return new ModuleSerializationProxy(IntShuffler$.class);// 8 + } + + public int shuffle(final int x) { + return x + 1;// 10 + } +} + +class 'pkg/IntShuffler$' { + method ' ()V' { + 4 9 + } + + method 'writeReplace ()Ljava/lang/Object;' { + 4 12 + 5 12 + 9 12 + } + + method 'shuffle (I)I' { + 0 16 + 1 16 + 2 16 + 3 16 + } +} + +Lines mapping: +8 <-> 13 +10 <-> 17 + +// Decompiled companion from pkg/StringShuffler +package pkg; + +public final class StringShuffler { + public static String shuffle(String var0) { + return StringShuffler$.MODULE$.shuffle(var0); + } +} + +class 'pkg/StringShuffler' { + method 'shuffle (Ljava/lang/String;)Ljava/lang/String;' { + 0 4 + 1 4 + 2 4 + 3 4 + 4 4 + 5 4 + 6 4 + 7 4 + } +} + +Lines mapping: + +// Decompiled companion from pkg/StringShuffler$ +package pkg; + +import java.io.Serializable; +import scala.collection.StringOps.; +import scala.runtime.ModuleSerializationProxy; + +public final class StringShuffler$ implements Shuffler, Serializable { + public static final StringShuffler$ MODULE$ = new StringShuffler$(); + + private StringShuffler$() { + } + + private Object writeReplace() { + return new ModuleSerializationProxy(StringShuffler$.class);// 13 + } + + public String shuffle(final String x) { + return .MODULE$.reverse$extension(scala.Predef..MODULE$.augmentString(x));// 15 + } +} + +class 'pkg/StringShuffler$' { + method ' ()V' { + 4 10 + } + + method 'writeReplace ()Ljava/lang/Object;' { + 4 13 + 5 13 + 9 13 + } + + method 'shuffle (Ljava/lang/String;)Ljava/lang/String;' { + 0 17 + 1 17 + 2 17 + 3 17 + 4 17 + 5 17 + 6 17 + 7 17 + 8 17 + 9 17 + a 17 + b 17 + c 17 + d 17 + } +} + +Lines mapping: +13 <-> 14 +15 <-> 18 \ No newline at end of file diff --git a/plugins/scala/testData/results/pkg/TestCaseClasses.dec b/plugins/scala/testData/results/pkg/TestCaseClasses.dec new file mode 100644 index 0000000000..aa83b4ab7b --- /dev/null +++ b/plugins/scala/testData/results/pkg/TestCaseClasses.dec @@ -0,0 +1,1703 @@ +package pkg; + +public interface TestCaseClasses { +} + +// Decompiled companion from pkg/Option1 +package pkg; + +import java.io.Serializable; +import scala.Product; +import scala.runtime.BoxesRunTime; +import scala.runtime.Statics; +import scala.runtime.ScalaRunTime.; + +public class Option1 implements TestCaseClasses, Product, Serializable { + private final int value; + + public static Option1 apply(int var0) { + return Option1$.MODULE$.apply(var0); + } + + public static Option1 fromProduct(Product var0) { + return Option1$.MODULE$.fromProduct(var0); + } + + public static Option1 unapply(Option1 var0) { + return Option1$.MODULE$.unapply(var0); + } + + public Option1(final int value) { + this.value = value; + } + + public int hashCode() { + int var1 = -889275714; + var1 = Statics.mix(var1, this.productPrefix().hashCode()); + var1 = Statics.mix(var1, this.value()); + return Statics.finalizeHash(var1, 1); + } + + public boolean equals(final Object x$0) { + if (this != x$0) { + boolean var10000; + if (x$0 instanceof Option1) { + Option1 var3 = (Option1)x$0; + var10000 = this.value() == var3.value() && var3.canEqual(this); + } else { + var10000 = false; + } + + if (!var10000) { + return false; + } + } + + return true; + } + + public String toString() { + return .MODULE$._toString(this); + } + + public boolean canEqual(final Object that) { + return that instanceof Option1; + } + + public int productArity() { + return 1; + } + + public String productPrefix() { + return "Option1"; + } + + public Object productElement(final int n) { + if (0 == n) { + return BoxesRunTime.boxToInteger(this._1()); + } else { + throw new IndexOutOfBoundsException(BoxesRunTime.boxToInteger(n).toString()); + } + } + + public String productElementName(final int n) { + if (0 == n) { + return "value"; + } else { + throw new IndexOutOfBoundsException(BoxesRunTime.boxToInteger(n).toString()); + } + } + + public int value() { + return this.value; + } + + public Option1 copy(final int value) { + return new Option1(value); + } + + public int copy$default$1() { + return this.value(); + } + + public int _1() { + return this.value();// 5 + } +} + +class 'pkg/Option1' { + method 'apply (I)Lpkg/Option1;' { + 0 12 + 1 12 + 2 12 + 3 12 + 4 12 + 5 12 + 6 12 + 7 12 + } + + method 'fromProduct (Lscala/Product;)Lpkg/Option1;' { + 0 16 + 1 16 + 2 16 + 3 16 + 4 16 + 5 16 + 6 16 + 7 16 + } + + method 'unapply (Lpkg/Option1;)Lpkg/Option1;' { + 0 20 + 1 20 + 2 20 + 3 20 + 4 20 + 5 20 + 6 20 + 7 20 + } + + method ' (I)V' { + 0 24 + 1 24 + 2 24 + 3 24 + 4 24 + 9 25 + } + + method 'hashCode ()I' { + 0 28 + 1 28 + 2 28 + 3 29 + 4 29 + 5 29 + 6 29 + 7 29 + 8 29 + 9 29 + a 29 + b 29 + c 29 + d 29 + e 29 + f 30 + 10 30 + 11 30 + 12 30 + 13 30 + 14 30 + 15 30 + 16 30 + 17 30 + 18 31 + 19 31 + 1a 31 + 1b 31 + 1c 31 + 1d 31 + } + + method 'equals (Ljava/lang/Object;)Z' { + 0 35 + 1 35 + 2 35 + 5 37 + 7 37 + 8 37 + 9 37 + a 37 + b 37 + e 38 + f 38 + 10 38 + 11 38 + 12 38 + 13 39 + 14 39 + 15 39 + 16 39 + 17 39 + 18 39 + 19 39 + 1a 39 + 1b 39 + 1e 39 + 1f 39 + 20 39 + 21 39 + 22 39 + 23 39 + 2e 41 + 32 44 + 35 49 + 39 45 + 3a 45 + } + + method 'toString ()Ljava/lang/String;' { + 0 53 + 1 53 + 2 53 + 3 53 + 4 53 + 5 53 + 6 53 + 7 53 + } + + method 'canEqual (Ljava/lang/Object;)Z' { + 0 57 + 1 57 + 2 57 + 3 57 + 4 57 + } + + method 'productArity ()I' { + 0 61 + 1 61 + } + + method 'productPrefix ()Ljava/lang/String;' { + 0 65 + 1 65 + 2 65 + } + + method 'productElement (I)Ljava/lang/Object;' { + 0 69 + 2 69 + 3 69 + 4 69 + 7 70 + 8 70 + 9 70 + a 70 + b 70 + c 70 + d 70 + e 70 + f 70 + 10 70 + 11 70 + 12 70 + 13 70 + 14 70 + 15 70 + 16 70 + 17 70 + 18 70 + 19 70 + 1a 70 + 1b 70 + 1c 70 + 1d 70 + 1e 70 + 1f 70 + } + + method 'productElementName (I)Ljava/lang/String;' { + 0 77 + 2 77 + 3 77 + 4 77 + 7 78 + 8 78 + 9 78 + a 78 + b 78 + c 78 + d 78 + e 78 + f 78 + 10 78 + 11 78 + 12 78 + 13 78 + 14 78 + 15 78 + 16 78 + 17 78 + 18 78 + 19 78 + 1a 78 + } + + method 'value ()I' { + 0 85 + 1 85 + 2 85 + 3 85 + 4 85 + } + + method 'copy (I)Lpkg/Option1;' { + 4 89 + 8 89 + } + + method 'copy$default$1 ()I' { + 0 93 + 1 93 + 2 93 + 3 93 + 4 93 + } + + method '_1 ()I' { + 0 97 + 1 97 + 2 97 + 3 97 + 4 97 + } +} + +Lines mapping: +5 <-> 98 + +// Decompiled companion from pkg/Option1$ +package pkg; + +import java.io.Serializable; +import scala.deriving.Mirror.Product; +import scala.runtime.BoxesRunTime; +import scala.runtime.ModuleSerializationProxy; + +public final class Option1$ implements Product, Serializable { + public static final Option1$ MODULE$ = new Option1$(); + + private Option1$() { + } + + private Object writeReplace() { + return new ModuleSerializationProxy(Option1$.class); + } + + public Option1 apply(final int value) { + return new Option1(value); + } + + public Option1 unapply(final Option1 x$1) { + return x$1; + } + + public String toString() { + return "Option1"; + } + + public Option1 fromProduct(final scala.Product x$0) { + return new Option1(BoxesRunTime.unboxToInt(x$0.productElement(0)));// 5 + } +} + +class 'pkg/Option1$' { + method ' ()V' { + 4 11 + } + + method 'writeReplace ()Ljava/lang/Object;' { + 4 14 + 5 14 + 9 14 + } + + method 'apply (I)Lpkg/Option1;' { + 4 18 + 8 18 + } + + method 'unapply (Lpkg/Option1;)Lpkg/Option1;' { + 0 22 + 1 22 + } + + method 'toString ()Ljava/lang/String;' { + 0 26 + 1 26 + 2 26 + } + + method 'fromProduct (Lscala/Product;)Lpkg/Option1;' { + 4 30 + 5 30 + 6 30 + 7 30 + 8 30 + 9 30 + a 30 + b 30 + c 30 + d 30 + 11 30 + } +} + +Lines mapping: +5 <-> 31 + +// Decompiled companion from pkg/Option2 +package pkg; + +import java.io.Serializable; +import scala.Product; +import scala.runtime.BoxesRunTime; +import scala.runtime.Statics; +import scala.runtime.ScalaRunTime.; + +public class Option2 implements TestCaseClasses, Product, Serializable { + private final double x; + private final double y; + private final double z; + + public static Option2 apply(double var0, double var2, double var4) { + return Option2$.MODULE$.apply(var0, var2, var4); + } + + public static Option2 fromProduct(Product var0) { + return Option2$.MODULE$.fromProduct(var0); + } + + public static Option2 unapply(Option2 var0) { + return Option2$.MODULE$.unapply(var0); + } + + public Option2(final double x, final double y, final double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public int hashCode() { + int var1 = -889275714; + var1 = Statics.mix(var1, this.productPrefix().hashCode()); + var1 = Statics.mix(var1, Statics.doubleHash(this.x())); + var1 = Statics.mix(var1, Statics.doubleHash(this.y())); + var1 = Statics.mix(var1, Statics.doubleHash(this.z())); + return Statics.finalizeHash(var1, 3); + } + + public boolean equals(final Object x$0) { + if (this != x$0) { + boolean var10000; + if (x$0 instanceof Option2) { + Option2 var3 = (Option2)x$0; + var10000 = this.x() == var3.x() && this.y() == var3.y() && this.z() == var3.z() && var3.canEqual(this); + } else { + var10000 = false; + } + + if (!var10000) { + return false; + } + } + + return true; + } + + public String toString() { + return .MODULE$._toString(this); + } + + public boolean canEqual(final Object that) { + return that instanceof Option2; + } + + public int productArity() { + return 3; + } + + public String productPrefix() { + return "Option2"; + } + + public Object productElement(final int n) { + double var10000; + switch (n) { + case 0: + var10000 = this._1(); + break; + case 1: + var10000 = this._2(); + break; + case 2: + var10000 = this._3(); + break; + default: + throw new IndexOutOfBoundsException(BoxesRunTime.boxToInteger(n).toString()); + } + + return BoxesRunTime.boxToDouble(var10000); + } + + public String productElementName(final int n) { + String var10000; + switch (n) { + case 0: + var10000 = "x"; + break; + case 1: + var10000 = "y"; + break; + case 2: + var10000 = "z"; + break; + default: + throw new IndexOutOfBoundsException(BoxesRunTime.boxToInteger(n).toString()); + } + + return var10000; + } + + public double x() { + return this.x; + } + + public double y() { + return this.y; + } + + public double z() { + return this.z; + } + + public Option2 copy(final double x, final double y, final double z) { + return new Option2(x, y, z); + } + + public double copy$default$1() { + return this.x(); + } + + public double copy$default$2() { + return this.y(); + } + + public double copy$default$3() { + return this.z(); + } + + public double _1() { + return this.x(); + } + + public double _2() { + return this.y(); + } + + public double _3() { + return this.z();// 6 + } +} + +class 'pkg/Option2' { + method 'apply (DDD)Lpkg/Option2;' { + 0 14 + 1 14 + 2 14 + 3 14 + 4 14 + 5 14 + 6 14 + 7 14 + 8 14 + 9 14 + a 14 + } + + method 'fromProduct (Lscala/Product;)Lpkg/Option2;' { + 0 18 + 1 18 + 2 18 + 3 18 + 4 18 + 5 18 + 6 18 + 7 18 + } + + method 'unapply (Lpkg/Option2;)Lpkg/Option2;' { + 0 22 + 1 22 + 2 22 + 3 22 + 4 22 + 5 22 + 6 22 + 7 22 + } + + method ' (DDD)V' { + 0 26 + 1 26 + 2 26 + 3 26 + 4 26 + 5 27 + 6 27 + 7 27 + 8 27 + 9 27 + a 28 + b 28 + c 28 + d 28 + e 28 + f 28 + 14 29 + } + + method 'hashCode ()I' { + 0 32 + 1 32 + 2 32 + 3 33 + 4 33 + 5 33 + 6 33 + 7 33 + 8 33 + 9 33 + a 33 + b 33 + c 33 + d 33 + e 33 + f 34 + 10 34 + 11 34 + 12 34 + 13 34 + 14 34 + 15 34 + 16 34 + 17 34 + 18 34 + 19 34 + 1a 34 + 1b 35 + 1c 35 + 1d 35 + 1e 35 + 1f 35 + 20 35 + 21 35 + 22 35 + 23 35 + 24 35 + 25 35 + 26 35 + 27 36 + 28 36 + 29 36 + 2a 36 + 2b 36 + 2c 36 + 2d 36 + 2e 36 + 2f 36 + 30 36 + 31 36 + 32 36 + 33 37 + 34 37 + 35 37 + 36 37 + 37 37 + 38 37 + } + + method 'equals (Ljava/lang/Object;)Z' { + 0 41 + 1 41 + 2 41 + 5 43 + 7 43 + 8 43 + 9 43 + a 43 + b 43 + e 44 + f 44 + 10 44 + 11 44 + 12 44 + 13 45 + 14 45 + 15 45 + 16 45 + 17 45 + 18 45 + 19 45 + 1a 45 + 1b 45 + 1c 45 + 1f 45 + 20 45 + 21 45 + 22 45 + 23 45 + 24 45 + 25 45 + 26 45 + 27 45 + 28 45 + 2b 45 + 2c 45 + 2d 45 + 2e 45 + 2f 45 + 30 45 + 31 45 + 32 45 + 33 45 + 34 45 + 37 45 + 38 45 + 39 45 + 3a 45 + 3b 45 + 3c 45 + 47 47 + 4b 50 + 4e 55 + 52 51 + 53 51 + } + + method 'toString ()Ljava/lang/String;' { + 0 59 + 1 59 + 2 59 + 3 59 + 4 59 + 5 59 + 6 59 + 7 59 + } + + method 'canEqual (Ljava/lang/Object;)Z' { + 0 63 + 1 63 + 2 63 + 3 63 + 4 63 + } + + method 'productArity ()I' { + 0 67 + 1 67 + } + + method 'productPrefix ()Ljava/lang/String;' { + 0 71 + 1 71 + 2 71 + } + + method 'productElement (I)Ljava/lang/Object;' { + 0 76 + 2 76 + 3 76 + 1c 78 + 1d 78 + 1e 78 + 1f 78 + 20 79 + 26 81 + 27 81 + 28 81 + 29 81 + 2a 82 + 30 84 + 31 84 + 32 84 + 33 84 + 34 85 + 3e 87 + 3f 87 + 40 87 + 41 87 + 42 87 + 43 87 + 44 87 + 48 87 + 4d 90 + 4e 90 + 4f 90 + 50 90 + } + + method 'productElementName (I)Ljava/lang/String;' { + 0 95 + 2 95 + 3 95 + 1c 97 + 1d 97 + 1e 98 + 24 100 + 25 100 + 26 101 + 2c 103 + 2d 103 + 2e 104 + 38 106 + 39 106 + 3a 106 + 3b 106 + 3c 106 + 3d 106 + 3e 106 + 42 106 + 47 109 + } + + method 'x ()D' { + 0 113 + 1 113 + 2 113 + 3 113 + 4 113 + } + + method 'y ()D' { + 0 117 + 1 117 + 2 117 + 3 117 + 4 117 + } + + method 'z ()D' { + 0 121 + 1 121 + 2 121 + 3 121 + 4 121 + } + + method 'copy (DDD)Lpkg/Option2;' { + 4 125 + 5 125 + 6 125 + 7 125 + b 125 + } + + method 'copy$default$1 ()D' { + 0 129 + 1 129 + 2 129 + 3 129 + 4 129 + } + + method 'copy$default$2 ()D' { + 0 133 + 1 133 + 2 133 + 3 133 + 4 133 + } + + method 'copy$default$3 ()D' { + 0 137 + 1 137 + 2 137 + 3 137 + 4 137 + } + + method '_1 ()D' { + 0 141 + 1 141 + 2 141 + 3 141 + 4 141 + } + + method '_2 ()D' { + 0 145 + 1 145 + 2 145 + 3 145 + 4 145 + } + + method '_3 ()D' { + 0 149 + 1 149 + 2 149 + 3 149 + 4 149 + } +} + +Lines mapping: +6 <-> 150 + +// Decompiled companion from pkg/Option2$ +package pkg; + +import java.io.Serializable; +import scala.deriving.Mirror.Product; +import scala.runtime.BoxesRunTime; +import scala.runtime.ModuleSerializationProxy; + +public final class Option2$ implements Product, Serializable { + public static final Option2$ MODULE$ = new Option2$(); + + private Option2$() { + } + + private Object writeReplace() { + return new ModuleSerializationProxy(Option2$.class); + } + + public Option2 apply(final double x, final double y, final double z) { + return new Option2(x, y, z); + } + + public Option2 unapply(final Option2 x$1) { + return x$1; + } + + public String toString() { + return "Option2"; + } + + public Option2 fromProduct(final scala.Product x$0) { + return new Option2( + BoxesRunTime.unboxToDouble(x$0.productElement(0)),// 6 + BoxesRunTime.unboxToDouble(x$0.productElement(1)), + BoxesRunTime.unboxToDouble(x$0.productElement(2)) + ); + } +} + +class 'pkg/Option2$' { + method ' ()V' { + 4 11 + } + + method 'writeReplace ()Ljava/lang/Object;' { + 4 14 + 5 14 + 9 14 + } + + method 'apply (DDD)Lpkg/Option2;' { + 4 18 + 5 18 + 6 18 + 7 18 + b 18 + } + + method 'unapply (Lpkg/Option2;)Lpkg/Option2;' { + 0 22 + 1 22 + } + + method 'toString ()Ljava/lang/String;' { + 0 26 + 1 26 + 2 26 + } + + method 'fromProduct (Lscala/Product;)Lpkg/Option2;' { + 4 31 + 5 31 + 6 31 + 7 31 + 8 31 + 9 31 + a 31 + b 31 + c 31 + d 31 + e 32 + f 32 + 10 32 + 11 32 + 12 32 + 13 32 + 14 32 + 15 32 + 16 32 + 17 32 + 18 33 + 19 33 + 1a 33 + 1b 33 + 1c 33 + 1d 33 + 1e 33 + 1f 33 + 20 33 + 21 33 + 25 30 + } +} + +Lines mapping: +6 <-> 32 + +// Decompiled companion from pkg/Option3 +package pkg; + +import java.io.Serializable; +import scala.Product; +import scala.collection.immutable.List; +import scala.runtime.BoxesRunTime; +import scala.runtime.ScalaRunTime.; + +public class Option3 implements TestCaseClasses, Product, Serializable { + private final List value; + + public static Option3 apply(List var0) { + return Option3$.MODULE$.apply(var0); + } + + public static Option3 fromProduct(Product var0) { + return Option3$.MODULE$.fromProduct(var0); + } + + public static Option3 unapply(Option3 var0) { + return Option3$.MODULE$.unapply(var0); + } + + public Option3(final List value) { + this.value = value; + } + + public int hashCode() { + return .MODULE$._hashCode(this); + } + + public boolean equals(final Object x$0) { + if (this != x$0) { + boolean var5; + if (!(x$0 instanceof Option3)) { + var5 = false; + } else { + Option3 var3 = (Option3)x$0; + List var10000 = this.value(); + List var4 = var3.value(); + var5 = (var10000 == null ? var4 == null : var10000.equals(var4)) && var3.canEqual(this); + } + + if (!var5) { + return false; + } + } + + return true; + } + + public String toString() { + return .MODULE$._toString(this); + } + + public boolean canEqual(final Object that) { + return that instanceof Option3; + } + + public int productArity() { + return 1; + } + + public String productPrefix() { + return "Option3"; + } + + public Object productElement(final int n) { + if (0 == n) { + return this._1(); + } else { + throw new IndexOutOfBoundsException(BoxesRunTime.boxToInteger(n).toString()); + } + } + + public String productElementName(final int n) { + if (0 == n) { + return "value"; + } else { + throw new IndexOutOfBoundsException(BoxesRunTime.boxToInteger(n).toString()); + } + } + + public List value() { + return this.value; + } + + public Option3 copy(final List value) { + return new Option3(value); + } + + public List copy$default$1() { + return this.value(); + } + + public List _1() { + return this.value();// 7 + } +} + +class 'pkg/Option3' { + method 'apply (Lscala/collection/immutable/List;)Lpkg/Option3;' { + 0 12 + 1 12 + 2 12 + 3 12 + 4 12 + 5 12 + 6 12 + 7 12 + } + + method 'fromProduct (Lscala/Product;)Lpkg/Option3;' { + 0 16 + 1 16 + 2 16 + 3 16 + 4 16 + 5 16 + 6 16 + 7 16 + } + + method 'unapply (Lpkg/Option3;)Lpkg/Option3;' { + 0 20 + 1 20 + 2 20 + 3 20 + 4 20 + 5 20 + 6 20 + 7 20 + } + + method ' (Lscala/collection/immutable/List;)V' { + 0 24 + 1 24 + 2 24 + 3 24 + 4 24 + 9 25 + } + + method 'hashCode ()I' { + 0 28 + 1 28 + 2 28 + 3 28 + 4 28 + 5 28 + 6 28 + 7 28 + } + + method 'equals (Ljava/lang/Object;)Z' { + 0 32 + 1 32 + 2 32 + 5 34 + 7 34 + 8 34 + 9 34 + a 34 + b 34 + e 37 + f 37 + 10 37 + 11 37 + 12 37 + 13 38 + 14 38 + 15 38 + 16 38 + 17 39 + 18 39 + 19 39 + 1a 39 + 1b 39 + 1c 39 + 1e 40 + 22 40 + 23 40 + 24 40 + 2a 40 + 2b 40 + 2c 40 + 2d 40 + 2e 40 + 2f 40 + 32 40 + 33 40 + 34 40 + 35 40 + 36 40 + 37 40 + 42 35 + 46 43 + 49 48 + 4d 44 + 4e 44 + } + + method 'toString ()Ljava/lang/String;' { + 0 52 + 1 52 + 2 52 + 3 52 + 4 52 + 5 52 + 6 52 + 7 52 + } + + method 'canEqual (Ljava/lang/Object;)Z' { + 0 56 + 1 56 + 2 56 + 3 56 + 4 56 + } + + method 'productArity ()I' { + 0 60 + 1 60 + } + + method 'productPrefix ()Ljava/lang/String;' { + 0 64 + 1 64 + 2 64 + } + + method 'productElement (I)Ljava/lang/Object;' { + 0 68 + 2 68 + 3 68 + 4 68 + 7 69 + 8 69 + 9 69 + a 69 + b 69 + c 69 + d 69 + e 69 + f 69 + 10 69 + 11 69 + 12 69 + 13 69 + 14 69 + 15 69 + 16 69 + 17 69 + 18 69 + 19 69 + 1a 69 + 1b 69 + 1c 69 + } + + method 'productElementName (I)Ljava/lang/String;' { + 0 76 + 2 76 + 3 76 + 4 76 + 7 77 + 8 77 + 9 77 + a 77 + b 77 + c 77 + d 77 + e 77 + f 77 + 10 77 + 11 77 + 12 77 + 13 77 + 14 77 + 15 77 + 16 77 + 17 77 + 18 77 + 19 77 + 1a 77 + } + + method 'value ()Lscala/collection/immutable/List;' { + 0 84 + 1 84 + 2 84 + 3 84 + 4 84 + } + + method 'copy (Lscala/collection/immutable/List;)Lpkg/Option3;' { + 4 88 + 8 88 + } + + method 'copy$default$1 ()Lscala/collection/immutable/List;' { + 0 92 + 1 92 + 2 92 + 3 92 + 4 92 + } + + method '_1 ()Lscala/collection/immutable/List;' { + 0 96 + 1 96 + 2 96 + 3 96 + 4 96 + } +} + +Lines mapping: +7 <-> 97 + +// Decompiled companion from pkg/Option3$ +package pkg; + +import java.io.Serializable; +import scala.collection.immutable.List; +import scala.deriving.Mirror.Product; +import scala.runtime.ModuleSerializationProxy; + +public final class Option3$ implements Product, Serializable { + public static final Option3$ MODULE$ = new Option3$(); + + private Option3$() { + } + + private Object writeReplace() { + return new ModuleSerializationProxy(Option3$.class); + } + + public Option3 apply(final List value) { + return new Option3(value); + } + + public Option3 unapply(final Option3 x$1) { + return x$1; + } + + public String toString() { + return "Option3"; + } + + public Option3 fromProduct(final scala.Product x$0) { + return new Option3((List)x$0.productElement(0));// 7 + } +} + +class 'pkg/Option3$' { + method ' ()V' { + 4 11 + } + + method 'writeReplace ()Ljava/lang/Object;' { + 4 14 + 5 14 + 9 14 + } + + method 'apply (Lscala/collection/immutable/List;)Lpkg/Option3;' { + 4 18 + 8 18 + } + + method 'unapply (Lpkg/Option3;)Lpkg/Option3;' { + 0 22 + 1 22 + } + + method 'toString ()Ljava/lang/String;' { + 0 26 + 1 26 + 2 26 + } + + method 'fromProduct (Lscala/Product;)Lpkg/Option3;' { + 4 30 + 5 30 + 6 30 + 7 30 + 8 30 + 9 30 + a 30 + b 30 + c 30 + d 30 + 11 30 + } +} + +Lines mapping: +7 <-> 31 + +// Decompiled companion from pkg/EnumLike +package pkg; + +import scala.Product; +import scala.collection.Iterator; +import scala.deriving.Mirror.Singleton; + +public final class EnumLike { + public static boolean canEqual(Object var0) { + return EnumLike$.MODULE$.canEqual(var0); + } + + public static Singleton fromProduct(Product var0) { + return EnumLike$.MODULE$.fromProduct(var0); + } + + public static int hashCode() { + return EnumLike$.MODULE$.hashCode(); + } + + public static int productArity() { + return EnumLike$.MODULE$.productArity(); + } + + public static Object productElement(int var0) { + return EnumLike$.MODULE$.productElement(var0); + } + + public static String productElementName(int var0) { + return EnumLike$.MODULE$.productElementName(var0); + } + + public static Iterator productElementNames() { + return EnumLike$.MODULE$.productElementNames(); + } + + public static Iterator productIterator() { + return EnumLike$.MODULE$.productIterator(); + } + + public static String productPrefix() { + return EnumLike$.MODULE$.productPrefix(); + } + + public static String toString() { + return EnumLike$.MODULE$.toString(); + } +} + +class 'pkg/EnumLike' { + method 'canEqual (Ljava/lang/Object;)Z' { + 0 8 + 1 8 + 2 8 + 3 8 + 4 8 + 5 8 + 6 8 + 7 8 + } + + method 'fromProduct (Lscala/Product;)Lscala/deriving/Mirror$Singleton;' { + 0 12 + 1 12 + 2 12 + 3 12 + 4 12 + 5 12 + 6 12 + 7 12 + } + + method 'hashCode ()I' { + 0 16 + 1 16 + 2 16 + 3 16 + 4 16 + 5 16 + 6 16 + } + + method 'productArity ()I' { + 0 20 + 1 20 + 2 20 + 3 20 + 4 20 + 5 20 + 6 20 + } + + method 'productElement (I)Ljava/lang/Object;' { + 0 24 + 1 24 + 2 24 + 3 24 + 4 24 + 5 24 + 6 24 + 7 24 + } + + method 'productElementName (I)Ljava/lang/String;' { + 0 28 + 1 28 + 2 28 + 3 28 + 4 28 + 5 28 + 6 28 + 7 28 + } + + method 'productElementNames ()Lscala/collection/Iterator;' { + 0 32 + 1 32 + 2 32 + 3 32 + 4 32 + 5 32 + 6 32 + } + + method 'productIterator ()Lscala/collection/Iterator;' { + 0 36 + 1 36 + 2 36 + 3 36 + 4 36 + 5 36 + 6 36 + } + + method 'productPrefix ()Ljava/lang/String;' { + 0 40 + 1 40 + 2 40 + 3 40 + 4 40 + 5 40 + 6 40 + } + + method 'toString ()Ljava/lang/String;' { + 0 44 + 1 44 + 2 44 + 3 44 + 4 44 + 5 44 + 6 44 + } +} + +Lines mapping: + +// Decompiled companion from pkg/EnumLike$ +package pkg; + +import java.io.Serializable; +import scala.Product; +import scala.deriving.Mirror.Singleton; +import scala.runtime.BoxesRunTime; +import scala.runtime.ModuleSerializationProxy; + +public final class EnumLike$ implements TestCaseClasses, Product, Serializable, Singleton { + public static final EnumLike$ MODULE$ = new EnumLike$(); + + private EnumLike$() { + } + + private Object writeReplace() { + return new ModuleSerializationProxy(EnumLike$.class); + } + + public int hashCode() { + return -2006338472; + } + + public String toString() { + return "EnumLike"; + } + + public boolean canEqual(final Object that) { + return that instanceof EnumLike$; + } + + public int productArity() { + return 0; + } + + public String productPrefix() { + return "EnumLike"; + } + + public Object productElement(final int n) { + throw new IndexOutOfBoundsException(BoxesRunTime.boxToInteger(n).toString()); + } + + public String productElementName(final int n) { + throw new IndexOutOfBoundsException(BoxesRunTime.boxToInteger(n).toString());// 8 + } +} + +class 'pkg/EnumLike$' { + method ' ()V' { + 4 12 + } + + method 'writeReplace ()Ljava/lang/Object;' { + 4 15 + 5 15 + 9 15 + } + + method 'hashCode ()I' { + 0 19 + 1 19 + 2 19 + } + + method 'toString ()Ljava/lang/String;' { + 0 23 + 1 23 + 2 23 + } + + method 'canEqual (Ljava/lang/Object;)Z' { + 0 27 + 1 27 + 2 27 + 3 27 + 4 27 + } + + method 'productArity ()I' { + 0 31 + 1 31 + } + + method 'productPrefix ()Ljava/lang/String;' { + 0 35 + 1 35 + 2 35 + } + + method 'productElement (I)Ljava/lang/Object;' { + 6 39 + 7 39 + 8 39 + 9 39 + a 39 + b 39 + c 39 + 10 39 + } + + method 'productElementName (I)Ljava/lang/String;' { + 6 43 + 7 43 + 8 43 + 9 43 + a 43 + b 43 + c 43 + 10 43 + } +} + +Lines mapping: +8 <-> 44 \ No newline at end of file diff --git a/plugins/scala/testData/results/pkg/TestCompanionObject.dec b/plugins/scala/testData/results/pkg/TestCompanionObject.dec new file mode 100644 index 0000000000..f343eba315 --- /dev/null +++ b/plugins/scala/testData/results/pkg/TestCompanionObject.dec @@ -0,0 +1,126 @@ +package pkg; + +public interface TestCompanionObject { + static String FIELD() { + return TestCompanionObject$.MODULE$.FIELD(); + } + + static void main(String[] var0) { + TestCompanionObject$.MODULE$.main(var0); + } + + static int defaultMethod$(final TestCompanionObject $this) { + return $this.defaultMethod();// 3 + } + + default int defaultMethod() { + return 0;// 5 + } + + void abstractMethod(); +} + +class 'pkg/TestCompanionObject' { + method 'FIELD ()Ljava/lang/String;' { + 0 4 + 1 4 + 2 4 + 3 4 + 4 4 + 5 4 + 6 4 + } + + method 'main ([Ljava/lang/String;)V' { + 0 8 + 1 8 + 2 8 + 3 8 + 4 8 + 5 8 + 6 8 + 7 9 + } + + method 'defaultMethod$ (Lpkg/TestCompanionObject;)I' { + 0 12 + 1 12 + 2 12 + 3 12 + 4 12 + } + + method 'defaultMethod ()I' { + 0 16 + 1 16 + } +} + +Lines mapping: +3 <-> 13 +5 <-> 17 + +// Decompiled companion from pkg/TestCompanionObject$ +package pkg; + +import java.io.Serializable; +import scala.Predef.; +import scala.runtime.ModuleSerializationProxy; + +public final class TestCompanionObject$ implements Serializable { + private static final String FIELD = "constant field"; + public static final TestCompanionObject$ MODULE$ = new TestCompanionObject$(); + + private TestCompanionObject$() { + } + + private Object writeReplace() { + return new ModuleSerializationProxy(TestCompanionObject$.class);// 10 + } + + public String FIELD() { + return FIELD;// 12 + } + + public void main(final String[] args) { + .MODULE$.print(this.FIELD());// 14 + } +} + +class 'pkg/TestCompanionObject$' { + method ' ()V' { + 4 11 + } + + method 'writeReplace ()Ljava/lang/Object;' { + 4 14 + 5 14 + 9 14 + } + + method 'FIELD ()Ljava/lang/String;' { + 0 18 + 1 18 + 2 18 + 3 18 + } + + method 'main ([Ljava/lang/String;)V' { + 0 22 + 1 22 + 2 22 + 3 22 + 4 22 + 5 22 + 6 22 + 7 22 + 8 22 + 9 22 + a 23 + } +} + +Lines mapping: +10 <-> 15 +12 <-> 19 +14 <-> 23 \ No newline at end of file diff --git a/plugins/scala/testData/results/pkg/TestDefaultParams.dec b/plugins/scala/testData/results/pkg/TestDefaultParams.dec new file mode 100644 index 0000000000..f891bb89ac --- /dev/null +++ b/plugins/scala/testData/results/pkg/TestDefaultParams.dec @@ -0,0 +1,136 @@ +package pkg; + +public final class TestDefaultParams { + public static void defaulted(String var0) { + TestDefaultParams$.MODULE$.defaulted(var0); + } + + public static void user() { + TestDefaultParams$.MODULE$.user(); + } + + public static String defaulted$default$1() { + return TestDefaultParams$.MODULE$.defaulted$default$1(); + } +} + +class 'pkg/TestDefaultParams' { + method 'defaulted (Ljava/lang/String;)V' { + 0 4 + 1 4 + 2 4 + 3 4 + 4 4 + 5 4 + 6 4 + 7 5 + } + + method 'user ()V' { + 0 8 + 1 8 + 2 8 + 3 8 + 4 8 + 5 8 + 6 9 + } + + method 'defaulted$default$1 ()Ljava/lang/String;' { + 0 12 + 1 12 + 2 12 + 3 12 + 4 12 + 5 12 + 6 12 + } +} + +Lines mapping: + +// Decompiled companion from pkg/TestDefaultParams$ +package pkg; + +import java.io.Serializable; +import scala.Predef.; +import scala.runtime.ModuleSerializationProxy; + +public final class TestDefaultParams$ implements Serializable { + public static final TestDefaultParams$ MODULE$ = new TestDefaultParams$(); + + private TestDefaultParams$() { + } + + private Object writeReplace() { + return new ModuleSerializationProxy(TestDefaultParams$.class);// 3 + } + + public void defaulted(final String s) { + .MODULE$.print(s);// 6 + } + + public String defaulted$default$1() { + return "hello!";// 5 + } + + public void user() { + this.defaulted(this.defaulted$default$1());// 9 10 + this.defaulted("world!");// 11 + } +} + +class 'pkg/TestDefaultParams$' { + method ' ()V' { + 4 10 + } + + method 'writeReplace ()Ljava/lang/Object;' { + 4 13 + 5 13 + 9 13 + } + + method 'defaulted (Ljava/lang/String;)V' { + 0 17 + 1 17 + 2 17 + 3 17 + 4 17 + 5 17 + 6 17 + 7 18 + } + + method 'defaulted$default$1 ()Ljava/lang/String;' { + 0 21 + 1 21 + 2 21 + } + + method 'user ()V' { + 0 25 + 1 25 + 2 25 + 3 25 + 4 25 + 5 25 + 6 25 + 7 25 + 8 26 + 9 26 + a 26 + b 26 + c 26 + d 26 + e 27 + } +} + +Lines mapping: +3 <-> 14 +5 <-> 22 +6 <-> 18 +9 <-> 26 +10 <-> 26 +11 <-> 27 \ No newline at end of file diff --git a/plugins/scala/testData/results/pkg/TestImplicits.dec b/plugins/scala/testData/results/pkg/TestImplicits.dec new file mode 100644 index 0000000000..1a2aea8915 --- /dev/null +++ b/plugins/scala/testData/results/pkg/TestImplicits.dec @@ -0,0 +1,396 @@ +package pkg; + +import scala.Predef.; +import scala.runtime.BoxesRunTime; + +public class TestImplicits { + public AddMonoid$ am() { + return AddMonoid$.MODULE$;// 24 + } + + public ConcatMonoid$ cm() { + return ConcatMonoid$.MODULE$;// 25 + } + + public T use(final T x, final Monoid monoid) { + return (T)monoid.op(x, x);// 27 28 + } + + public void user() { + .MODULE$.print(this.use(BoxesRunTime.boxToInteger(3), this.am()));// 31 32 + .MODULE$.print(this.use("hia", this.cm()));// 33 + } +} + +class 'pkg/TestImplicits' { + method 'am ()Lpkg/AddMonoid$;' { + 0 7 + 1 7 + 2 7 + 3 7 + } + + method 'cm ()Lpkg/ConcatMonoid$;' { + 0 11 + 1 11 + 2 11 + 3 11 + } + + method 'use (Ljava/lang/Object;Lpkg/Monoid;)Ljava/lang/Object;' { + 0 15 + 1 15 + 2 15 + 3 15 + 4 15 + 5 15 + 6 15 + 7 15 + 8 15 + } + + method 'user ()V' { + 0 19 + 1 19 + 2 19 + 3 19 + 4 19 + 5 19 + 6 19 + 7 19 + 8 19 + 9 19 + a 19 + b 19 + c 19 + d 19 + e 19 + f 19 + 10 19 + 11 19 + 12 20 + 13 20 + 14 20 + 15 20 + 16 20 + 17 20 + 18 20 + 19 20 + 1a 20 + 1b 20 + 1c 20 + 1d 20 + 1e 20 + 1f 20 + 20 20 + 21 20 + 22 21 + } +} + +Lines mapping: +24 <-> 8 +25 <-> 12 +27 <-> 16 +28 <-> 16 +31 <-> 20 +32 <-> 20 +33 <-> 21 + +// Decompiled companion from pkg/AddMonoid +package pkg; + +public final class AddMonoid { + public static int id() { + return AddMonoid$.MODULE$.id(); + } + + public static int op(int var0, int var1) { + return AddMonoid$.MODULE$.op(var0, var1); + } +} + +class 'pkg/AddMonoid' { + method 'id ()I' { + 0 4 + 1 4 + 2 4 + 3 4 + 4 4 + 5 4 + 6 4 + } + + method 'op (II)I' { + 0 8 + 1 8 + 2 8 + 3 8 + 4 8 + 5 8 + 6 8 + 7 8 + 8 8 + } +} + +Lines mapping: + +// Decompiled companion from pkg/AddMonoid$ +package pkg; + +import scala.runtime.ModuleSerializationProxy; + +public final class AddMonoid$ implements Monoid, { + public static final AddMonoid$ MODULE$ = new AddMonoid$(); + + private AddMonoid$() { + } + + private Object writeReplace() { + return new ModuleSerializationProxy(AddMonoid$.class);// 8 + } + + public int op(final int x, final int y) { + return x + y;// 9 + } + + public int id() { + return 0;// 10 + } +} + +class 'pkg/AddMonoid$' { + method ' ()V' { + 4 8 + } + + method 'writeReplace ()Ljava/lang/Object;' { + 4 11 + 5 11 + 9 11 + } + + method 'op (II)I' { + 0 15 + 1 15 + 2 15 + 3 15 + } + + method 'id ()I' { + 0 19 + 1 19 + } +} + +Lines mapping: +8 <-> 12 +9 <-> 16 +10 <-> 20 + +// Decompiled companion from pkg/MulMonoid +package pkg; + +public final class MulMonoid { + public static int id() { + return MulMonoid$.MODULE$.id(); + } + + public static int op(int var0, int var1) { + return MulMonoid$.MODULE$.op(var0, var1); + } +} + +class 'pkg/MulMonoid' { + method 'id ()I' { + 0 4 + 1 4 + 2 4 + 3 4 + 4 4 + 5 4 + 6 4 + } + + method 'op (II)I' { + 0 8 + 1 8 + 2 8 + 3 8 + 4 8 + 5 8 + 6 8 + 7 8 + 8 8 + } +} + +Lines mapping: + +// Decompiled companion from pkg/MulMonoid$ +package pkg; + +import scala.runtime.ModuleSerializationProxy; + +public final class MulMonoid$ implements Monoid, { + public static final MulMonoid$ MODULE$ = new MulMonoid$(); + + private MulMonoid$() { + } + + private Object writeReplace() { + return new ModuleSerializationProxy(MulMonoid$.class);// 13 + } + + public int op(final int x, final int y) { + return x * y;// 14 + } + + public int id() { + return 1;// 15 + } +} + +class 'pkg/MulMonoid$' { + method ' ()V' { + 4 8 + } + + method 'writeReplace ()Ljava/lang/Object;' { + 4 11 + 5 11 + 9 11 + } + + method 'op (II)I' { + 0 15 + 1 15 + 2 15 + 3 15 + } + + method 'id ()I' { + 0 19 + 1 19 + } +} + +Lines mapping: +13 <-> 12 +14 <-> 16 +15 <-> 20 + +// Decompiled companion from pkg/ConcatMonoid +package pkg; + +public final class ConcatMonoid { + public static String id() { + return ConcatMonoid$.MODULE$.id(); + } + + public static String op(String var0, String var1) { + return ConcatMonoid$.MODULE$.op(var0, var1); + } +} + +class 'pkg/ConcatMonoid' { + method 'id ()Ljava/lang/String;' { + 0 4 + 1 4 + 2 4 + 3 4 + 4 4 + 5 4 + 6 4 + } + + method 'op (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;' { + 0 8 + 1 8 + 2 8 + 3 8 + 4 8 + 5 8 + 6 8 + 7 8 + 8 8 + } +} + +Lines mapping: + +// Decompiled companion from pkg/ConcatMonoid$ +package pkg; + +import scala.collection.StringOps.; +import scala.runtime.ModuleSerializationProxy; + +public final class ConcatMonoid$ implements Monoid, { + public static final ConcatMonoid$ MODULE$ = new ConcatMonoid$(); + + private ConcatMonoid$() { + } + + private Object writeReplace() { + return new ModuleSerializationProxy(ConcatMonoid$.class);// 18 + } + + public String op(final String x, final String y) { + return .MODULE$.$plus$plus$extension(scala.Predef..MODULE$.augmentString(x), y);// 19 + } + + public String id() { + return "";// 20 + } +} + +class 'pkg/ConcatMonoid$' { + method ' ()V' { + 4 9 + } + + method 'writeReplace ()Ljava/lang/Object;' { + 4 12 + 5 12 + 9 12 + } + + method 'op (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;' { + 0 16 + 1 16 + 2 16 + 3 16 + 4 16 + 5 16 + 6 16 + 7 16 + 8 16 + 9 16 + a 16 + b 16 + c 16 + d 16 + e 16 + } + + method 'id ()Ljava/lang/String;' { + 0 20 + 1 20 + 2 20 + } +} + +Lines mapping: +18 <-> 13 +19 <-> 17 +20 <-> 21 + +// Decompiled companion from pkg/Monoid +package pkg; + +public interface Monoid { + T op(final T x, final T y); + + T id(); +} \ No newline at end of file diff --git a/plugins/scala/testData/results/pkg/TestObject.dec b/plugins/scala/testData/results/pkg/TestObject.dec new file mode 100644 index 0000000000..079ac4cf43 --- /dev/null +++ b/plugins/scala/testData/results/pkg/TestObject.dec @@ -0,0 +1,144 @@ +package pkg; + +public final class TestObject { + public static int SOME_INT() { + return TestObject$.MODULE$.SOME_INT(); + } + + public static String SOME_STRING() { + return TestObject$.MODULE$.SOME_STRING(); + } + + public static int fib(int var0) { + return TestObject$.MODULE$.fib(var0); + } +} + +class 'pkg/TestObject' { + method 'SOME_INT ()I' { + 0 4 + 1 4 + 2 4 + 3 4 + 4 4 + 5 4 + 6 4 + } + + method 'SOME_STRING ()Ljava/lang/String;' { + 0 8 + 1 8 + 2 8 + 3 8 + 4 8 + 5 8 + 6 8 + } + + method 'fib (I)I' { + 0 12 + 1 12 + 2 12 + 3 12 + 4 12 + 5 12 + 6 12 + 7 12 + } +} + +Lines mapping: + +// Decompiled companion from pkg/TestObject$ +package pkg; + +import java.io.Serializable; +import scala.runtime.ModuleSerializationProxy; + +public final class TestObject$ implements Serializable { + private static final String SOME_STRING = "some string"; + private static final int SOME_INT = 0; + public static final TestObject$ MODULE$ = new TestObject$(); + + private TestObject$() { + } + + private Object writeReplace() { + return new ModuleSerializationProxy(TestObject$.class);// 3 + } + + public String SOME_STRING() { + return SOME_STRING;// 5 + } + + public int SOME_INT() { + return SOME_INT;// 6 + } + + public int fib(final int idx) { + return idx == 0 ? 0 : (idx == 1 ? 1 : this.fib(idx - 1) + this.fib(idx - 2));// 9 10 11 12 14 + } +} + +class 'pkg/TestObject$' { + method ' ()V' { + 4 11 + } + + method 'writeReplace ()Ljava/lang/Object;' { + 4 14 + 5 14 + 9 14 + } + + method 'SOME_STRING ()Ljava/lang/String;' { + 0 18 + 1 18 + 2 18 + 3 18 + } + + method 'SOME_INT ()I' { + 0 22 + 1 22 + 2 22 + 3 22 + } + + method 'fib (I)I' { + 0 26 + 1 26 + 2 26 + 5 26 + 9 26 + a 26 + b 26 + e 26 + 12 26 + 13 26 + 14 26 + 15 26 + 16 26 + 17 26 + 18 26 + 19 26 + 1a 26 + 1b 26 + 1c 26 + 1d 26 + 1e 26 + 1f 26 + 20 26 + 21 26 + } +} + +Lines mapping: +3 <-> 15 +5 <-> 19 +6 <-> 23 +9 <-> 27 +10 <-> 27 +11 <-> 27 +12 <-> 27 +14 <-> 27 \ No newline at end of file diff --git a/plugins/scala/testData/src/scala/pkg/TestAssocTypes.scala b/plugins/scala/testData/src/scala/pkg/TestAssocTypes.scala new file mode 100644 index 0000000000..1d1be3e8c5 --- /dev/null +++ b/plugins/scala/testData/src/scala/pkg/TestAssocTypes.scala @@ -0,0 +1,23 @@ +package pkg + +trait Shuffler { + type T + def shuffle(x: T): T +} + +object IntShuffler extends Shuffler { + override type T = Int + override def shuffle(x: Int): Int = x + 1 +} + +object StringShuffler extends Shuffler { + override type T = String + override def shuffle(x: String): String = x.reverse +} + +class TestAssocTypes { + def user(): Unit = { + print(IntShuffler.shuffle(3)) + print(StringShuffler.shuffle("abcd")) + } +} \ No newline at end of file diff --git a/plugins/scala/testData/src/scala/pkg/TestCaseClasses.scala b/plugins/scala/testData/src/scala/pkg/TestCaseClasses.scala new file mode 100644 index 0000000000..bc6790e604 --- /dev/null +++ b/plugins/scala/testData/src/scala/pkg/TestCaseClasses.scala @@ -0,0 +1,8 @@ +package pkg + +sealed trait TestCaseClasses + +case class Option1(value: Int) extends TestCaseClasses +case class Option2(x: Double, y: Double, z: Double) extends TestCaseClasses +case class Option3(value: List[String]) extends TestCaseClasses +case object EnumLike extends TestCaseClasses \ No newline at end of file diff --git a/plugins/scala/testData/src/scala/pkg/TestCompanionObject.scala b/plugins/scala/testData/src/scala/pkg/TestCompanionObject.scala new file mode 100644 index 0000000000..ac1955d318 --- /dev/null +++ b/plugins/scala/testData/src/scala/pkg/TestCompanionObject.scala @@ -0,0 +1,15 @@ +package pkg + +trait TestCompanionObject { + + def defaultMethod(): Int = 0 + + def abstractMethod(): Unit +} + +object TestCompanionObject { + + val FIELD = "constant field" + + def main(args: Array[String]): Unit = print(FIELD) +} \ No newline at end of file diff --git a/plugins/scala/testData/src/scala/pkg/TestDefaultParams.scala b/plugins/scala/testData/src/scala/pkg/TestDefaultParams.scala new file mode 100644 index 0000000000..51a5e72b22 --- /dev/null +++ b/plugins/scala/testData/src/scala/pkg/TestDefaultParams.scala @@ -0,0 +1,13 @@ +package pkg + +object TestDefaultParams { + + def defaulted(s: String = "hello!"): Unit = { + print(s) + } + + def user(): Unit = { + defaulted() + defaulted("world!") + } +} \ No newline at end of file diff --git a/plugins/scala/testData/src/scala/pkg/TestImplicits.scala b/plugins/scala/testData/src/scala/pkg/TestImplicits.scala new file mode 100644 index 0000000000..e2e227c0f0 --- /dev/null +++ b/plugins/scala/testData/src/scala/pkg/TestImplicits.scala @@ -0,0 +1,35 @@ +package pkg + +trait Monoid[T] { + def op(x: T, y: T): T + def id: T +} + +object AddMonoid extends Monoid[Int] { + override def op(x: Int, y: Int): Int = x + y + override def id: Int = 0 +} + +object MulMonoid extends Monoid[Int]{ + override def op(x: Int, y: Int): Int = x * y + override def id: Int = 1 +} + +object ConcatMonoid extends Monoid[String] { + override def op(x: String, y: String): String = x ++ y + override def id: String = "" +} + +class TestImplicits { + implicit def am: AddMonoid.type = AddMonoid // not Mul + implicit def cm: ConcatMonoid.type = ConcatMonoid + + def use[T](x: T)(implicit monoid: Monoid[T]): T = { + monoid.op(x, x) + } + + def user(): Unit = { + print(use(3)) + print(use("hia")) + } +} \ No newline at end of file diff --git a/plugins/scala/testData/src/scala/pkg/TestObject.scala b/plugins/scala/testData/src/scala/pkg/TestObject.scala new file mode 100644 index 0000000000..84e17e0d42 --- /dev/null +++ b/plugins/scala/testData/src/scala/pkg/TestObject.scala @@ -0,0 +1,15 @@ +package pkg + +object TestObject { + + val SOME_STRING = "some string" + val SOME_INT = 0 + + def fib(idx: Int): Int = + if idx == 0 then + 0 + else if idx == 1 then + 1 + else + fib(idx - 1) + fib(idx - 2) +} diff --git a/plugins/variable-renaming/.gitignore b/plugins/variable-renaming/.gitignore new file mode 100644 index 0000000000..427e4b40a4 --- /dev/null +++ b/plugins/variable-renaming/.gitignore @@ -0,0 +1,4 @@ +/build/ +/out/ +testData/classes/java*/ +bin \ No newline at end of file diff --git a/plugins/variable-renaming/build.gradle b/plugins/variable-renaming/build.gradle new file mode 100644 index 0000000000..18767f6c97 --- /dev/null +++ b/plugins/variable-renaming/build.gradle @@ -0,0 +1,61 @@ +import org.vineflower.build.TestDataRuntimesProvider + +archivesBaseName = 'vineflower-variable-renaming' + +dependencies { + implementation project(":") + testImplementation testFixtures(project(":")) +} + +task testDataClasses { + group = 'build' +} +testClasses.dependsOn(testDataClasses) + +void createJavaTestDataSet(int version, String suffix = "", List compilerArgs = []) { + sourceSets.create("testDataJava${version}${suffix}") { + it.java.srcDirs file("testData/src/java${version}${suffix.toLowerCase()}") + } + tasks.getByName("compileTestDataJava${version}${suffix}Java") { + destinationDirectory = file("testData/classes/java${version}${suffix.toLowerCase()}") + if (project.isArm && version > 8 && version < 11) { + // On ARM systems, a more limited set of JVM versions are available + // We'll accept the `--release` flag so development is at least somewhat possible + javaCompiler = javaToolchains.compilerFor { + languageVersion = JavaLanguageVersion.of(11) + } + options.release = version + } else { + javaCompiler = javaToolchains.compilerFor { + languageVersion = JavaLanguageVersion.of(version) + } + } + + options.compilerArgs = compilerArgs + } + testDataClasses.dependsOn("testDataJava${version}${suffix}Classes") +} + +def testJavaRuntimes = [:] + +[8].forEach { version -> + def runtimeVersion = isArm && version > 8 && version < 11 ? 11 : version + createJavaTestDataSet(version) + testJavaRuntimes[version] = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(runtimeVersion) + } +} + +test { + maxHeapSize = "512M" + + systemProperty "DOT_EXPORT_DIR", System.getProperty("DOT_EXPORT_DIR", null) + systemProperty "DOT_ERROR_EXPORT_DIR", System.getProperty("DOT_ERROR_EXPORT_DIR", null) + systemProperty "VALIDATE_DECOMPILED_CODE", System.getProperty("VALIDATE_DECOMPILED_CODE", "false") + + def provider = objects.newInstance(TestDataRuntimesProvider) + testJavaRuntimes.each { k, v -> + provider.launchers.put(k, v) + } + jvmArgumentProviders << provider +} \ No newline at end of file diff --git a/plugins/variable-renaming/gradle.properties b/plugins/variable-renaming/gradle.properties new file mode 100644 index 0000000000..0d22531f6d --- /dev/null +++ b/plugins/variable-renaming/gradle.properties @@ -0,0 +1 @@ +does_shadow=false \ No newline at end of file diff --git a/src/org/jetbrains/java/decompiler/util/JADNameProvider.java b/plugins/variable-renaming/src/main/java/org/vineflower/variablerenaming/JADNameProvider.java similarity index 87% rename from src/org/jetbrains/java/decompiler/util/JADNameProvider.java rename to plugins/variable-renaming/src/main/java/org/vineflower/variablerenaming/JADNameProvider.java index c3168adff5..408e31b3d1 100644 --- a/src/org/jetbrains/java/decompiler/util/JADNameProvider.java +++ b/plugins/variable-renaming/src/main/java/org/vineflower/variablerenaming/JADNameProvider.java @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jetbrains.java.decompiler.util; +package org.vineflower.variablerenaming; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -27,19 +26,23 @@ import java.util.regex.Pattern; import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; import org.jetbrains.java.decompiler.main.extern.IVariableNameProvider; import org.jetbrains.java.decompiler.main.extern.IVariableNamingFactory; +import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor; import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPair; import org.jetbrains.java.decompiler.struct.StructMethod; import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.Pair; public class JADNameProvider implements IVariableNameProvider { - private HashMap last = null; - private HashMap remap = null; + private HashMap last; + private HashMap remap; private final HashMap parameters = new HashMap<>(); - private StructMethod method = null; - private boolean renameParameters = false; + private final StructMethod method; + private final boolean renameParameters; private static final Pattern CAPS_START = Pattern.compile("^[A-Z]"); private static final Pattern ARRAY = Pattern.compile("(\\[|\\.\\.\\.)"); @@ -100,7 +103,7 @@ public Holder(int t1, boolean skip_zero, List names) { } @Override - public Map rename(Map entries) { + public Map rename(Map> entries) { int params = 0; if ((this.method.getAccessFlags() & CodeConstants.ACC_STATIC) != CodeConstants.ACC_STATIC) { params++; @@ -116,7 +119,7 @@ public Map rename(Map entries) { Map result = new LinkedHashMap<>(); for (VarVersionPair ver : keys) { - String type = cleanType(entries.get(ver)); + String type = cleanType(entries.get(ver).b); if ("this".equals(type)) { continue; } @@ -201,24 +204,20 @@ else if (remap.containsKey(type)) { } @Override - public String renameParameter(int flags, String type, String name, int index) { + public String renameParameter(int flags, VarType type, String name, int index) { + String typeName = ExprProcessor.getCastTypeName(type); if (!this.renameParameters) { return IVariableNameProvider.super.renameParameter(flags, type, name, index); } - return this.parameters.computeIfAbsent(index, k -> getNewName(cleanType(type))); + return this.parameters.computeIfAbsent(index, k -> getNewName(cleanType(typeName))); } public static class JADNameProviderFactory implements IVariableNamingFactory { - private final boolean renameParameters; - - public JADNameProviderFactory(boolean renameParameters) { - this.renameParameters = renameParameters; - } - @Override public IVariableNameProvider createFactory(StructMethod method) { - return new JADNameProvider(renameParameters, method); + return new JADNameProvider(DecompilerContext.getOption(VariableRenamingOptions.RENAME_PARAMETERS) + || DecompilerContext.getOption(VariableRenamingOptions.USE_JAD_PARAMETER_NAMING), method); } } } diff --git a/plugins/variable-renaming/src/main/java/org/vineflower/variablerenaming/Renamers.java b/plugins/variable-renaming/src/main/java/org/vineflower/variablerenaming/Renamers.java new file mode 100644 index 0000000000..2d5e59990d --- /dev/null +++ b/plugins/variable-renaming/src/main/java/org/vineflower/variablerenaming/Renamers.java @@ -0,0 +1,18 @@ +package org.vineflower.variablerenaming; + +import org.jetbrains.java.decompiler.main.extern.IVariableNamingFactory; + +import java.util.HashMap; +import java.util.Map; + +public class Renamers { + private static final Map PROVIDERS = new HashMap<>(); + + public static void registerProvider(String name, IVariableNamingFactory factory) { + PROVIDERS.put(name, factory); + } + + public static IVariableNamingFactory get(String name) { + return PROVIDERS.get(name); + } +} diff --git a/plugins/variable-renaming/src/main/java/org/vineflower/variablerenaming/TinyNameProvider.java b/plugins/variable-renaming/src/main/java/org/vineflower/variablerenaming/TinyNameProvider.java new file mode 100644 index 0000000000..dd59747082 --- /dev/null +++ b/plugins/variable-renaming/src/main/java/org/vineflower/variablerenaming/TinyNameProvider.java @@ -0,0 +1,188 @@ +package org.vineflower.variablerenaming; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.collectors.ImportCollector; +import org.jetbrains.java.decompiler.main.extern.IVariableNameProvider; +import org.jetbrains.java.decompiler.main.extern.IVariableNamingFactory; +import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPair; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.Pair; +import org.jetbrains.java.decompiler.util.TextUtil; + +import java.util.*; + +// Adapted from https://github.com/FabricMC/tiny-remapper/blob/master/src/main/java/net/fabricmc/tinyremapper/AsmClassRemapper.java +public class TinyNameProvider implements IVariableNameProvider { + private final HashMap parameters = new HashMap<>(); + + private final StructMethod method; + private final boolean renameParameters; + private final Set usedNames = new HashSet<>(); + + public TinyNameProvider(boolean renameParameters, StructMethod method) { + this.renameParameters = renameParameters; + this.method = method; + } + + @Override + public Map rename(Map> entries) { + int params = 0; + if ((this.method.getAccessFlags() & CodeConstants.ACC_STATIC) != CodeConstants.ACC_STATIC) { + params++; + } + + MethodDescriptor md = MethodDescriptor.parseDescriptor(this.method.getDescriptor()); + for (VarType param : md.params) { + params += param.stackSize; + } + + List keys = new ArrayList<>(entries.keySet()); + Collections.sort(keys, (o1, o2) -> (o1.var != o2.var) ? o1.var - o2.var : o1.version - o2.version); + + Map result = new LinkedHashMap<>(); + for (VarVersionPair ver : keys) { + String type = cleanType(entries.get(ver).b); + + if (ver.var >= params) { + result.put(ver, getNewName(Pair.of(entries.get(ver).a, type))); + } else if (renameParameters) { + result.put(ver, this.parameters.computeIfAbsent(ver.var, k -> getNewName(Pair.of(entries.get(ver).a, type)))); + } + } + + return result; + } + + private String getNewName(Pair pair) { + VarType type = pair.a; + usedNames.add(pair.b); + + boolean increment = true; + String name; + switch (type.type) { + case BYTECHAR: case SHORTCHAR: + case INT: name = "i"; break; + case LONG: name = "l"; break; + case BYTE: name = "b"; break; + case SHORT: name = "s"; break; + case CHAR: name = "c"; break; + case FLOAT: name = "f"; break; + case DOUBLE: name = "d"; break; + case BOOLEAN: + name = "bl"; + increment = false; + break; + case OBJECT: + case GENVAR: + name = pair.b; + + // Lowercase first letter + if (Character.isUpperCase(name.charAt(0))) { + name = Character.toLowerCase(name.charAt(0)) + name.substring(1); + } + + if (type.arrayDim > 0 && !name.endsWith("s")) { + name += "s"; + } + + increment = false; + break; + default: return null; + } + + if (increment) { + // Must be of length 1 + int idxStart = name.charAt(0) - 'a'; + while (usedNames.contains(name)) { + name = convertToName(idxStart++); + } + } else { + // Increment via numbers + String oname = name; + int idx = 0; + while (usedNames.contains(name)) { + name = oname + (++idx); + } + } + + if (TextUtil.isKeyword(name, method.getBytecodeVersion(), method)) { + name += "_"; + } + + usedNames.add(name); + + return name; + } + + @Override + public String renameParameter(int flags, VarType type, String name, int index) { + String typeName; + try (var lock = DecompilerContext.getImportCollector().lock()) { + typeName = ExprProcessor.getCastTypeName(type); + } + if (!this.renameParameters) { + return IVariableNameProvider.super.renameParameter(flags, type, name, index); + } + + return this.parameters.computeIfAbsent(index, k -> getNewName(Pair.of(type, cleanType(typeName)))); + } + + private static String convertToName(int idx) { + // Convert to base 26 + String str = Integer.toString(idx, 26); + + // Remap start of name from '0' to 'a' + StringBuilder res = new StringBuilder(); + for (int i = str.length() - 1; i >= 0; i--) { + char c = str.charAt(i); + // If we're numerical, remap to lowercase ascii range + if (c <= '9') { + c = (char) ('a' + (c - '0')); + } else { + // If we're not, simply shift up 10 ascii characters + c += 10; + } + + // TODO: idx 26 starts at 'ba', when it should start at 'aa' + res.insert(0, c); + } + return res.toString(); + } + + private String cleanType(String type) { + if (type.indexOf('<') != -1) { + type = type.substring(0, type.indexOf('<')); + } + + if (type.indexOf('.') != -1) { + type = type.substring(type.lastIndexOf('.') + 1); + } + + if (type.indexOf('$') != -1) { + type = type.substring(type.lastIndexOf('$') + 1); + } + + type = type.replaceAll("\\[\\]", ""); + + return type; + } + + @Override + public void addParentContext(IVariableNameProvider renamer) { + TinyNameProvider prov = (TinyNameProvider) renamer; + + this.usedNames.addAll(prov.usedNames); + } + + public static class TinyNameProviderFactory implements IVariableNamingFactory { + + @Override + public IVariableNameProvider createFactory(StructMethod structMethod) { + return new TinyNameProvider(DecompilerContext.getOption(VariableRenamingOptions.RENAME_PARAMETERS), structMethod); + } + } +} diff --git a/plugins/variable-renaming/src/main/java/org/vineflower/variablerenaming/VariableRenamingOptions.java b/plugins/variable-renaming/src/main/java/org/vineflower/variablerenaming/VariableRenamingOptions.java new file mode 100644 index 0000000000..d3d92dad40 --- /dev/null +++ b/plugins/variable-renaming/src/main/java/org/vineflower/variablerenaming/VariableRenamingOptions.java @@ -0,0 +1,34 @@ +package org.vineflower.variablerenaming; + +import org.jetbrains.java.decompiler.api.plugin.PluginOptions; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences.*; + +public interface VariableRenamingOptions { + @Name("Variable Renaming") + @Description("Use a custom renamer for variable names. Built-in options include \"jad\" and \"tiny\".") + @Type(Type.STRING) + String VARIABLE_RENAMER = "variable-renaming"; + + @Name("Rename Parameters") + @Description("Use the custom renamer for parameters in addition to locals.") + @Type(Type.BOOLEAN) + String RENAME_PARAMETERS = "rename-parameters"; + + @Name("[Deprecated] JAD-Style Variable Naming") + @Description("Use JAD-style variable naming. Deprecated, set \"variable-renamer=jad\" instead.") + @Type(Type.BOOLEAN) + String USE_JAD_VARNAMING = "jad-style-variable-naming"; + + @Name("[Deprecated] JAD-Style Parameter Naming") + @Description("Alias for \"rename-parameters\". Deprecated, use that option instead.") + @Type(Type.BOOLEAN) + String USE_JAD_PARAMETER_NAMING = "jad-style-parameter-naming"; + + static void addDefaults(PluginOptions.AddDefaults cons) { + cons.addDefault(VARIABLE_RENAMER, null); + cons.addDefault(RENAME_PARAMETERS, "0"); + cons.addDefault(USE_JAD_VARNAMING, "0"); + cons.addDefault(USE_JAD_PARAMETER_NAMING, "0"); + } +} diff --git a/plugins/variable-renaming/src/main/java/org/vineflower/variablerenaming/VariableRenamingPlugin.java b/plugins/variable-renaming/src/main/java/org/vineflower/variablerenaming/VariableRenamingPlugin.java new file mode 100644 index 0000000000..185d44990f --- /dev/null +++ b/plugins/variable-renaming/src/main/java/org/vineflower/variablerenaming/VariableRenamingPlugin.java @@ -0,0 +1,45 @@ +package org.vineflower.variablerenaming; + +import org.jetbrains.annotations.Nullable; +import org.jetbrains.java.decompiler.api.plugin.Plugin; +import org.jetbrains.java.decompiler.api.plugin.PluginOptions; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.extern.IVariableNamingFactory; +import org.jetbrains.java.decompiler.util.Pair; + +public class VariableRenamingPlugin implements Plugin { + @Override + public String id() { + return "VariableRenaming"; + } + + @Override + public String description() { + return "Allows automatic renaming of variables with a common naming scheme."; + } + + @Override + public @Nullable PluginOptions getPluginOptions() { + return () -> Pair.of(VariableRenamingOptions.class, VariableRenamingOptions::addDefaults); + } + + @Override + public @Nullable IVariableNamingFactory getRenamingFactory() { + String name = (String) DecompilerContext.getProperty(VariableRenamingOptions.VARIABLE_RENAMER); + if (name != null) { + return Renamers.get(name); + } + + // Honor legacy option + if (DecompilerContext.getOption(VariableRenamingOptions.USE_JAD_VARNAMING)) { + return Renamers.get("jad"); + } + + return null; + } + + static { + Renamers.registerProvider("jad", new JADNameProvider.JADNameProviderFactory()); + Renamers.registerProvider("tiny", new TinyNameProvider.TinyNameProviderFactory()); + } +} diff --git a/plugins/variable-renaming/src/main/resources/META-INF/services/org.jetbrains.java.decompiler.api.plugin.Plugin b/plugins/variable-renaming/src/main/resources/META-INF/services/org.jetbrains.java.decompiler.api.plugin.Plugin new file mode 100644 index 0000000000..99fee7267f --- /dev/null +++ b/plugins/variable-renaming/src/main/resources/META-INF/services/org.jetbrains.java.decompiler.api.plugin.Plugin @@ -0,0 +1 @@ +org.vineflower.variablerenaming.VariableRenamingPlugin \ No newline at end of file diff --git a/plugins/variable-renaming/src/test/java/org/vineflower/variablerenaming/VariableRenamingTest.java b/plugins/variable-renaming/src/test/java/org/vineflower/variablerenaming/VariableRenamingTest.java new file mode 100644 index 0000000000..ab969a6907 --- /dev/null +++ b/plugins/variable-renaming/src/test/java/org/vineflower/variablerenaming/VariableRenamingTest.java @@ -0,0 +1,30 @@ +package org.vineflower.variablerenaming; + +import org.jetbrains.java.decompiler.SingleClassesTestBase; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; + +import static org.jetbrains.java.decompiler.SingleClassesTestBase.TestDefinition.Version.CUSTOM; +import static org.jetbrains.java.decompiler.SingleClassesTestBase.TestDefinition.Version.JAVA_8; + +public class VariableRenamingTest extends SingleClassesTestBase { + @Override + protected void registerAll() { + registerSet("JAD Naming", () -> { + register(JAVA_8, "TestJADNaming"); + // TODO: loop part fails + registerRaw(CUSTOM, "TestJadLvtCollision"); // created by placing a class in java8 sources and remapping its param using tinyremapper + }, IFernflowerPreferences.BYTECODE_SOURCE_MAPPING, "1", + IFernflowerPreferences.DUMP_ORIGINAL_LINES, "1", + IFernflowerPreferences.DUMP_EXCEPTION_ON_ERROR, "0", + VariableRenamingOptions.VARIABLE_RENAMER, "jad" + ); + + registerSet("Tiny Naming", () -> { + register(JAVA_8, "TestTinyNaming"); + }, IFernflowerPreferences.BYTECODE_SOURCE_MAPPING, "1", + IFernflowerPreferences.DUMP_ORIGINAL_LINES, "1", + IFernflowerPreferences.DUMP_EXCEPTION_ON_ERROR, "0", + VariableRenamingOptions.VARIABLE_RENAMER, "tiny" + ); + } +} diff --git a/testData/classes/custom/TestJadLvtCollision.class b/plugins/variable-renaming/testData/classes/custom/TestJadLvtCollision.class similarity index 100% rename from testData/classes/custom/TestJadLvtCollision.class rename to plugins/variable-renaming/testData/classes/custom/TestJadLvtCollision.class diff --git a/testData/results/TestJadLvtCollision.dec b/plugins/variable-renaming/testData/results/TestJadLvtCollision.dec similarity index 95% rename from testData/results/TestJadLvtCollision.dec rename to plugins/variable-renaming/testData/results/TestJadLvtCollision.dec index e0ae532329..f215f8dabc 100644 --- a/testData/results/TestJadLvtCollision.dec +++ b/plugins/variable-renaming/testData/results/TestJadLvtCollision.dec @@ -7,7 +7,7 @@ public class TestJadLvtCollision implements Iterable { }// 10 public void testLoop(TestJadLvtCollision testjadlvtcollision) { - for(TestJadLvtCollision testjadlvtcollisionx : testjadlvtcollision) {// 13 + for (TestJadLvtCollision testjadlvtcollisionx : testjadlvtcollision) {// 13 System.out.println(testjadlvtcollision.toString() + testjadlvtcollisionx.toString());// 14 } }// 16 diff --git a/plugins/variable-renaming/testData/results/pkg/TestJADNaming.dec b/plugins/variable-renaming/testData/results/pkg/TestJADNaming.dec new file mode 100644 index 0000000000..a8429c1090 --- /dev/null +++ b/plugins/variable-renaming/testData/results/pkg/TestJADNaming.dec @@ -0,0 +1,211 @@ +package pkg; + +public class TestJADNaming { + public void Func() { + int i = 1000;// 5 + int j = 2000; + int k = 3000; + int l = 4000; + if (i != j && k == l) {// 6 + } + + boolean flag = true;// 7 + boolean flag1 = false; + boolean flag2 = true; + boolean flag3 = false; + if (flag != flag1 && flag2 == flag3) {// 8 + } + }// 9 + + public void types() { + short short1 = 0;// 12 + short short2 = 0;// 13 + byte b0 = 0;// 14 + byte b1 = 0;// 15 + char c0 = 'c';// 16 + char c1 = 'c';// 17 + double d0 = 0.5;// 18 + double d1 = 0.5;// 19 + float f = 0.5F;// 20 + float f1 = 0.5F;// 21 + int i = 1;// 23 + int j = 1;// 24 + long k = 1L;// 25 + long l = 1L;// 26 + int i1 = 1;// 27 + int j1 = 1;// 28 + long k1 = 1L;// 29 + }// 30 + + public void objects() { + Class oclass = null;// 33 + Class oclass1 = null;// 34 + Enum oenum = null;// 36 + Enum oenum1 = null;// 37 + Package opackage = null;// 39 + Package opackage1 = null;// 40 + }// 41 +} + +class 'pkg/TestJADNaming' { + method 'Func ()V' { + 0 4 + 1 4 + 2 4 + 3 4 + 4 5 + 5 5 + 6 5 + 7 5 + 8 6 + 9 6 + a 6 + b 6 + c 7 + d 7 + e 7 + f 7 + 10 7 + 11 8 + 12 8 + 13 8 + 16 8 + 17 8 + 18 8 + 19 8 + 1c 11 + 1d 11 + 1e 11 + 1f 12 + 20 12 + 21 12 + 22 13 + 23 13 + 24 13 + 25 14 + 26 14 + 27 14 + 28 15 + 29 15 + 2a 15 + 2b 15 + 2c 15 + 2f 15 + 30 15 + 31 15 + 32 15 + 33 15 + 36 17 + } + + method 'types ()V' { + 0 20 + 1 20 + 2 21 + 3 21 + 4 22 + 5 22 + 6 23 + 7 23 + 8 23 + 9 24 + a 24 + b 24 + c 24 + d 25 + e 25 + f 25 + 10 25 + 11 26 + 12 26 + 13 26 + 14 26 + 15 26 + 16 27 + 17 27 + 18 27 + 19 27 + 1a 27 + 1b 28 + 1c 28 + 1d 28 + 1e 28 + 1f 29 + 20 29 + 21 29 + 22 29 + 23 30 + 24 30 + 25 30 + 26 31 + 27 31 + 28 31 + 29 32 + 2a 32 + 2b 32 + 2c 33 + 2d 33 + 2e 33 + 2f 34 + 30 34 + 31 34 + 32 35 + 33 35 + 34 35 + 35 36 + 36 36 + 37 36 + 38 37 + } + + method 'objects ()V' { + 0 40 + 1 40 + 2 41 + 3 41 + 4 42 + 5 42 + 6 43 + 7 43 + 8 43 + 9 44 + a 44 + b 44 + c 45 + d 45 + e 45 + f 46 + } +} + +Lines mapping: +5 <-> 5 +6 <-> 9 +7 <-> 12 +8 <-> 16 +9 <-> 18 +12 <-> 21 +13 <-> 22 +14 <-> 23 +15 <-> 24 +16 <-> 25 +17 <-> 26 +18 <-> 27 +19 <-> 28 +20 <-> 29 +21 <-> 30 +23 <-> 31 +24 <-> 32 +25 <-> 33 +26 <-> 34 +27 <-> 35 +28 <-> 36 +29 <-> 37 +30 <-> 38 +33 <-> 41 +34 <-> 42 +36 <-> 43 +37 <-> 44 +39 <-> 45 +40 <-> 46 +41 <-> 47 \ No newline at end of file diff --git a/plugins/variable-renaming/testData/results/pkg/TestTinyNaming.dec b/plugins/variable-renaming/testData/results/pkg/TestTinyNaming.dec new file mode 100644 index 0000000000..f068b961e8 --- /dev/null +++ b/plugins/variable-renaming/testData/results/pkg/TestTinyNaming.dec @@ -0,0 +1,836 @@ +package pkg; + +public class TestTinyNaming { + public void ints() { + int i = 1;// 5 + int j = 1;// 6 + int k = 1;// 7 + int l = 1;// 8 + int m = 1;// 9 + int n = 1;// 10 + int o = 1;// 11 + int p = 1;// 12 + int q = 1;// 13 + int r = 1;// 14 + int s = 1;// 15 + int t = 1;// 16 + int u = 1;// 17 + int v = 1;// 18 + int w = 1;// 19 + int x = 1;// 20 + int y = 1;// 21 + int z = 1;// 22 + int ba = 1;// 23 + }// 24 + + public void floats() { + float f = 1.0F;// 27 + float g = 1.0F;// 28 + float h = 1.0F;// 29 + float i = 1.0F;// 30 + float j = 1.0F;// 31 + float k = 1.0F;// 32 + float l = 1.0F;// 33 + float m = 1.0F;// 34 + float n = 1.0F;// 35 + float o = 1.0F;// 36 + float p = 1.0F;// 37 + float q = 1.0F;// 38 + float r = 1.0F;// 39 + float s = 1.0F;// 40 + float t = 1.0F;// 41 + float u = 1.0F;// 42 + float v = 1.0F;// 43 + float w = 1.0F;// 44 + float x = 1.0F;// 45 + }// 46 + + public void doubles() { + double d = 1.0;// 49 + double e = 1.0;// 50 + double f = 1.0;// 51 + double g = 1.0;// 52 + double h = 1.0;// 53 + double i = 1.0;// 54 + double j = 1.0;// 55 + double k = 1.0;// 56 + double l = 1.0;// 57 + double m = 1.0;// 58 + double n = 1.0;// 59 + double o = 1.0;// 60 + double p = 1.0;// 61 + double q = 1.0;// 62 + double r = 1.0;// 63 + double s = 1.0;// 64 + double t = 1.0;// 65 + double u = 1.0;// 66 + double v = 1.0;// 67 + }// 68 + + public void shorts() { + short s = 1;// 71 + short t = 1;// 72 + short u = 1;// 73 + short v = 1;// 74 + short w = 1;// 75 + short x = 1;// 76 + short y = 1;// 77 + short z = 1;// 78 + short ba = 1;// 79 + short bb = 1;// 80 + short bc = 1;// 81 + short bd = 1;// 82 + short be = 1;// 83 + short bf = 1;// 84 + short bg = 1;// 85 + short bh = 1;// 86 + short bi = 1;// 87 + short bj = 1;// 88 + short bk = 1;// 89 + }// 90 + + public void bytes() { + byte b = 1;// 93 + byte c = 1;// 94 + byte d = 1;// 95 + byte e = 1;// 96 + byte f = 1;// 97 + byte g = 1;// 98 + byte h = 1;// 99 + byte i = 1;// 100 + byte j = 1;// 101 + byte k = 1;// 102 + byte l = 1;// 103 + byte m = 1;// 104 + byte n = 1;// 105 + byte o = 1;// 106 + byte p = 1;// 107 + byte q = 1;// 108 + byte r = 1;// 109 + byte s = 1;// 110 + byte t = 1;// 111 + }// 112 + + public void chars() { + char c = 'a';// 115 + char d = 'a';// 116 + char e = 'a';// 117 + char f = 'a';// 118 + char g = 'a';// 119 + char h = 'a';// 120 + char i = 'a';// 121 + char j = 'a';// 122 + char k = 'a';// 123 + char l = 'a';// 124 + char m = 'a';// 125 + char n = 'a';// 126 + char o = 'a';// 127 + char p = 'a';// 128 + char q = 'a';// 129 + char r = 'a';// 130 + char s = 'a';// 131 + char t = 'a';// 132 + char u = 'a';// 133 + }// 134 + + public void booleans() { + boolean bl = true;// 137 + boolean bl1 = true;// 138 + boolean bl2 = true;// 139 + boolean bl3 = true;// 140 + boolean bl4 = true;// 141 + boolean bl5 = true;// 142 + boolean bl6 = true;// 143 + boolean bl7 = true;// 144 + boolean bl8 = true;// 145 + boolean bl9 = true;// 146 + boolean bl10 = true;// 147 + boolean bl11 = true;// 148 + boolean bl12 = true;// 149 + boolean bl13 = true;// 150 + boolean bl14 = true;// 151 + boolean bl15 = true;// 152 + boolean bl16 = true;// 153 + boolean bl17 = true;// 154 + boolean bl18 = true;// 155 + }// 156 + + public void types() { + TestTinyNaming.PascalCase pascalCase = null;// 159 + TestTinyNaming.PascalCase pascalCase1 = null;// 160 + TestTinyNaming.PascalCase pascalCase2 = null;// 161 + TestTinyNaming.camelCase camelCase1 = null;// 163 + TestTinyNaming.camelCase camelCase2 = null;// 164 + TestTinyNaming.camelCase camelCase3 = null;// 165 + TestTinyNaming.clas clas1 = null;// 167 + TestTinyNaming.clas clas2 = null;// 168 + TestTinyNaming.clas clas3 = null;// 169 + }// 170 + + public void arrays() { + TestTinyNaming.PascalCase[] pascalCases = null;// 173 + TestTinyNaming.PascalCase[] pascalCases1 = null;// 174 + TestTinyNaming.PascalCase[] pascalCases2 = null;// 175 + TestTinyNaming.camelCase[] camelCases = null;// 177 + TestTinyNaming.camelCase[] camelCases1 = null;// 178 + TestTinyNaming.camelCase[] camelCases2 = null;// 179 + TestTinyNaming.clas[] clas1 = null;// 181 + TestTinyNaming.clas[] clas2 = null;// 182 + TestTinyNaming.clas[] clas3 = null;// 183 + }// 184 + + static class PascalCase { + } + + static class camelCase { + } + + static class clas { + } +} + +class 'pkg/TestTinyNaming' { + method 'ints ()V' { + 0 4 + 1 4 + 2 5 + 3 5 + 4 6 + 5 6 + 6 7 + 7 7 + 8 7 + 9 8 + a 8 + b 8 + c 9 + d 9 + e 9 + f 10 + 10 10 + 11 10 + 12 11 + 13 11 + 14 11 + 15 12 + 16 12 + 17 12 + 18 13 + 19 13 + 1a 13 + 1b 14 + 1c 14 + 1d 14 + 1e 15 + 1f 15 + 20 15 + 21 16 + 22 16 + 23 16 + 24 17 + 25 17 + 26 17 + 27 18 + 28 18 + 29 18 + 2a 19 + 2b 19 + 2c 19 + 2d 20 + 2e 20 + 2f 20 + 30 21 + 31 21 + 32 21 + 33 22 + 34 22 + 35 22 + 36 23 + } + + method 'floats ()V' { + 0 26 + 1 26 + 2 27 + 3 27 + 4 28 + 5 28 + 6 29 + 7 29 + 8 29 + 9 30 + a 30 + b 30 + c 31 + d 31 + e 31 + f 32 + 10 32 + 11 32 + 12 33 + 13 33 + 14 33 + 15 34 + 16 34 + 17 34 + 18 35 + 19 35 + 1a 35 + 1b 36 + 1c 36 + 1d 36 + 1e 37 + 1f 37 + 20 37 + 21 38 + 22 38 + 23 38 + 24 39 + 25 39 + 26 39 + 27 40 + 28 40 + 29 40 + 2a 41 + 2b 41 + 2c 41 + 2d 42 + 2e 42 + 2f 42 + 30 43 + 31 43 + 32 43 + 33 44 + 34 44 + 35 44 + 36 45 + } + + method 'doubles ()V' { + 0 48 + 1 48 + 2 49 + 3 49 + 4 50 + 5 50 + 6 50 + 7 51 + 8 51 + 9 51 + a 52 + b 52 + c 52 + d 53 + e 53 + f 53 + 10 54 + 11 54 + 12 54 + 13 55 + 14 55 + 15 55 + 16 56 + 17 56 + 18 56 + 19 57 + 1a 57 + 1b 57 + 1c 58 + 1d 58 + 1e 58 + 1f 59 + 20 59 + 21 59 + 22 60 + 23 60 + 24 60 + 25 61 + 26 61 + 27 61 + 28 62 + 29 62 + 2a 62 + 2b 63 + 2c 63 + 2d 63 + 2e 64 + 2f 64 + 30 64 + 31 65 + 32 65 + 33 65 + 34 66 + 35 66 + 36 66 + 37 67 + } + + method 'shorts ()V' { + 0 70 + 1 70 + 2 71 + 3 71 + 4 72 + 5 72 + 6 73 + 7 73 + 8 73 + 9 74 + a 74 + b 74 + c 75 + d 75 + e 75 + f 76 + 10 76 + 11 76 + 12 77 + 13 77 + 14 77 + 15 78 + 16 78 + 17 78 + 18 79 + 19 79 + 1a 79 + 1b 80 + 1c 80 + 1d 80 + 1e 81 + 1f 81 + 20 81 + 21 82 + 22 82 + 23 82 + 24 83 + 25 83 + 26 83 + 27 84 + 28 84 + 29 84 + 2a 85 + 2b 85 + 2c 85 + 2d 86 + 2e 86 + 2f 86 + 30 87 + 31 87 + 32 87 + 33 88 + 34 88 + 35 88 + 36 89 + } + + method 'bytes ()V' { + 0 92 + 1 92 + 2 93 + 3 93 + 4 94 + 5 94 + 6 95 + 7 95 + 8 95 + 9 96 + a 96 + b 96 + c 97 + d 97 + e 97 + f 98 + 10 98 + 11 98 + 12 99 + 13 99 + 14 99 + 15 100 + 16 100 + 17 100 + 18 101 + 19 101 + 1a 101 + 1b 102 + 1c 102 + 1d 102 + 1e 103 + 1f 103 + 20 103 + 21 104 + 22 104 + 23 104 + 24 105 + 25 105 + 26 105 + 27 106 + 28 106 + 29 106 + 2a 107 + 2b 107 + 2c 107 + 2d 108 + 2e 108 + 2f 108 + 30 109 + 31 109 + 32 109 + 33 110 + 34 110 + 35 110 + 36 111 + } + + method 'chars ()V' { + 0 114 + 1 114 + 2 114 + 3 115 + 4 115 + 5 115 + 6 116 + 7 116 + 8 116 + 9 117 + a 117 + b 117 + c 117 + d 118 + e 118 + f 118 + 10 118 + 11 119 + 12 119 + 13 119 + 14 119 + 15 120 + 16 120 + 17 120 + 18 120 + 19 121 + 1a 121 + 1b 121 + 1c 121 + 1d 122 + 1e 122 + 1f 122 + 20 122 + 21 123 + 22 123 + 23 123 + 24 123 + 25 124 + 26 124 + 27 124 + 28 124 + 29 125 + 2a 125 + 2b 125 + 2c 125 + 2d 126 + 2e 126 + 2f 126 + 30 126 + 31 127 + 32 127 + 33 127 + 34 127 + 35 128 + 36 128 + 37 128 + 38 128 + 39 129 + 3a 129 + 3b 129 + 3c 129 + 3d 130 + 3e 130 + 3f 130 + 40 130 + 41 131 + 42 131 + 43 131 + 44 131 + 45 132 + 46 132 + 47 132 + 48 132 + 49 133 + } + + method 'booleans ()V' { + 0 136 + 1 136 + 2 137 + 3 137 + 4 138 + 5 138 + 6 139 + 7 139 + 8 139 + 9 140 + a 140 + b 140 + c 141 + d 141 + e 141 + f 142 + 10 142 + 11 142 + 12 143 + 13 143 + 14 143 + 15 144 + 16 144 + 17 144 + 18 145 + 19 145 + 1a 145 + 1b 146 + 1c 146 + 1d 146 + 1e 147 + 1f 147 + 20 147 + 21 148 + 22 148 + 23 148 + 24 149 + 25 149 + 26 149 + 27 150 + 28 150 + 29 150 + 2a 151 + 2b 151 + 2c 151 + 2d 152 + 2e 152 + 2f 152 + 30 153 + 31 153 + 32 153 + 33 154 + 34 154 + 35 154 + 36 155 + } + + method 'types ()V' { + 0 158 + 1 158 + 2 159 + 3 159 + 4 160 + 5 160 + 6 161 + 7 161 + 8 161 + 9 162 + a 162 + b 162 + c 163 + d 163 + e 163 + f 164 + 10 164 + 11 164 + 12 165 + 13 165 + 14 165 + 15 166 + 16 166 + 17 166 + 18 167 + } + + method 'arrays ()V' { + 0 170 + 1 170 + 2 171 + 3 171 + 4 172 + 5 172 + 6 173 + 7 173 + 8 173 + 9 174 + a 174 + b 174 + c 175 + d 175 + e 175 + f 176 + 10 176 + 11 176 + 12 177 + 13 177 + 14 177 + 15 178 + 16 178 + 17 178 + 18 179 + } +} + +Lines mapping: +5 <-> 5 +6 <-> 6 +7 <-> 7 +8 <-> 8 +9 <-> 9 +10 <-> 10 +11 <-> 11 +12 <-> 12 +13 <-> 13 +14 <-> 14 +15 <-> 15 +16 <-> 16 +17 <-> 17 +18 <-> 18 +19 <-> 19 +20 <-> 20 +21 <-> 21 +22 <-> 22 +23 <-> 23 +24 <-> 24 +27 <-> 27 +28 <-> 28 +29 <-> 29 +30 <-> 30 +31 <-> 31 +32 <-> 32 +33 <-> 33 +34 <-> 34 +35 <-> 35 +36 <-> 36 +37 <-> 37 +38 <-> 38 +39 <-> 39 +40 <-> 40 +41 <-> 41 +42 <-> 42 +43 <-> 43 +44 <-> 44 +45 <-> 45 +46 <-> 46 +49 <-> 49 +50 <-> 50 +51 <-> 51 +52 <-> 52 +53 <-> 53 +54 <-> 54 +55 <-> 55 +56 <-> 56 +57 <-> 57 +58 <-> 58 +59 <-> 59 +60 <-> 60 +61 <-> 61 +62 <-> 62 +63 <-> 63 +64 <-> 64 +65 <-> 65 +66 <-> 66 +67 <-> 67 +68 <-> 68 +71 <-> 71 +72 <-> 72 +73 <-> 73 +74 <-> 74 +75 <-> 75 +76 <-> 76 +77 <-> 77 +78 <-> 78 +79 <-> 79 +80 <-> 80 +81 <-> 81 +82 <-> 82 +83 <-> 83 +84 <-> 84 +85 <-> 85 +86 <-> 86 +87 <-> 87 +88 <-> 88 +89 <-> 89 +90 <-> 90 +93 <-> 93 +94 <-> 94 +95 <-> 95 +96 <-> 96 +97 <-> 97 +98 <-> 98 +99 <-> 99 +100 <-> 100 +101 <-> 101 +102 <-> 102 +103 <-> 103 +104 <-> 104 +105 <-> 105 +106 <-> 106 +107 <-> 107 +108 <-> 108 +109 <-> 109 +110 <-> 110 +111 <-> 111 +112 <-> 112 +115 <-> 115 +116 <-> 116 +117 <-> 117 +118 <-> 118 +119 <-> 119 +120 <-> 120 +121 <-> 121 +122 <-> 122 +123 <-> 123 +124 <-> 124 +125 <-> 125 +126 <-> 126 +127 <-> 127 +128 <-> 128 +129 <-> 129 +130 <-> 130 +131 <-> 131 +132 <-> 132 +133 <-> 133 +134 <-> 134 +137 <-> 137 +138 <-> 138 +139 <-> 139 +140 <-> 140 +141 <-> 141 +142 <-> 142 +143 <-> 143 +144 <-> 144 +145 <-> 145 +146 <-> 146 +147 <-> 147 +148 <-> 148 +149 <-> 149 +150 <-> 150 +151 <-> 151 +152 <-> 152 +153 <-> 153 +154 <-> 154 +155 <-> 155 +156 <-> 156 +159 <-> 159 +160 <-> 160 +161 <-> 161 +163 <-> 162 +164 <-> 163 +165 <-> 164 +167 <-> 165 +168 <-> 166 +169 <-> 167 +170 <-> 168 +173 <-> 171 +174 <-> 172 +175 <-> 173 +177 <-> 174 +178 <-> 175 +179 <-> 176 +181 <-> 177 +182 <-> 178 +183 <-> 179 +184 <-> 180 \ No newline at end of file diff --git a/plugins/variable-renaming/testData/src/java8/pkg/TestJADNaming.java b/plugins/variable-renaming/testData/src/java8/pkg/TestJADNaming.java new file mode 100644 index 0000000000..b7e65cc8d9 --- /dev/null +++ b/plugins/variable-renaming/testData/src/java8/pkg/TestJADNaming.java @@ -0,0 +1,42 @@ +package pkg; + +public class TestJADNaming { + public void Func() { + int a = 1000, b = 2000, c = 3000, d = 4000; + if (a == b || c == d); + boolean xa = true, xb = false, xc = true, xd = false; + if (xa == xb || xc == xd); + } + + public void types() { + short a = 0; + short aa = 0; + byte b = 0; + byte bb = 0; + char c = 'c'; + char cc = 'c'; + double d = 0.5; + double dd = 0.5; + float f = 0.5f; + float ff = 0.5f; + + int e = 1; + int ee = 1; + long g = 1; + long gg = 1; + int ge = 1; + int gf = 1; + long gh = 1; + } + + public void objects() { + Class a = null; + Class b = null; + + Enum e = null; + Enum ee = null; + + Package p = null; + Package pp = null; + } +} \ No newline at end of file diff --git a/plugins/variable-renaming/testData/src/java8/pkg/TestTinyNaming.java b/plugins/variable-renaming/testData/src/java8/pkg/TestTinyNaming.java new file mode 100644 index 0000000000..f948597d94 --- /dev/null +++ b/plugins/variable-renaming/testData/src/java8/pkg/TestTinyNaming.java @@ -0,0 +1,197 @@ +package pkg; + +public class TestTinyNaming { + public void ints() { + int i1 = 1; + int i2 = 1; + int i3 = 1; + int i4 = 1; + int i5 = 1; + int i6 = 1; + int i7 = 1; + int i8 = 1; + int i9 = 1; + int i10 = 1; + int i11 = 1; + int i12 = 1; + int i13 = 1; + int i14 = 1; + int i15 = 1; + int i16 = 1; + int i17 = 1; + int i18 = 1; + int i19 = 1; + } + + public void floats() { + float i1 = 1; + float i2 = 1; + float i3 = 1; + float i4 = 1; + float i5 = 1; + float i6 = 1; + float i7 = 1; + float i8 = 1; + float i9 = 1; + float i10 = 1; + float i11 = 1; + float i12 = 1; + float i13 = 1; + float i14 = 1; + float i15 = 1; + float i16 = 1; + float i17 = 1; + float i18 = 1; + float i19 = 1; + } + + public void doubles() { + double i1 = 1; + double i2 = 1; + double i3 = 1; + double i4 = 1; + double i5 = 1; + double i6 = 1; + double i7 = 1; + double i8 = 1; + double i9 = 1; + double i10 = 1; + double i11 = 1; + double i12 = 1; + double i13 = 1; + double i14 = 1; + double i15 = 1; + double i16 = 1; + double i17 = 1; + double i18 = 1; + double i19 = 1; + } + + public void shorts() { + short i1 = 1; + short i2 = 1; + short i3 = 1; + short i4 = 1; + short i5 = 1; + short i6 = 1; + short i7 = 1; + short i8 = 1; + short i9 = 1; + short i10 = 1; + short i11 = 1; + short i12 = 1; + short i13 = 1; + short i14 = 1; + short i15 = 1; + short i16 = 1; + short i17 = 1; + short i18 = 1; + short i19 = 1; + } + + public void bytes() { + byte i1 = 1; + byte i2 = 1; + byte i3 = 1; + byte i4 = 1; + byte i5 = 1; + byte i6 = 1; + byte i7 = 1; + byte i8 = 1; + byte i9 = 1; + byte i10 = 1; + byte i11 = 1; + byte i12 = 1; + byte i13 = 1; + byte i14 = 1; + byte i15 = 1; + byte i16 = 1; + byte i17 = 1; + byte i18 = 1; + byte i19 = 1; + } + + public void chars() { + char i1 = 'a'; + char i2 = 'a'; + char i3 = 'a'; + char i4 = 'a'; + char i5 = 'a'; + char i6 = 'a'; + char i7 = 'a'; + char i8 = 'a'; + char i9 = 'a'; + char i10 = 'a'; + char i11 = 'a'; + char i12 = 'a'; + char i13 = 'a'; + char i14 = 'a'; + char i15 = 'a'; + char i16 = 'a'; + char i17 = 'a'; + char i18 = 'a'; + char i19 = 'a'; + } + + public void booleans() { + boolean i1 = true; + boolean i2 = true; + boolean i3 = true; + boolean i4 = true; + boolean i5 = true; + boolean i6 = true; + boolean i7 = true; + boolean i8 = true; + boolean i9 = true; + boolean i10 = true; + boolean i11 = true; + boolean i12 = true; + boolean i13 = true; + boolean i14 = true; + boolean i15 = true; + boolean i16 = true; + boolean i17 = true; + boolean i18 = true; + boolean i19 = true; + } + + public void types() { + PascalCase ar = null; + PascalCase ar1 = null; + PascalCase ar2 = null; + + camelCase br = null; + camelCase br1 = null; + camelCase br2 = null; + + clas cr = null; + clas cr1 = null; + clas cr2 = null; + } + + public void arrays() { + PascalCase[] ar = null; + PascalCase[] ar1 = null; + PascalCase[] ar2 = null; + + camelCase[] br = null; + camelCase[] br1 = null; + camelCase[] br2 = null; + + clas[] cr = null; + clas[] cr1 = null; + clas[] cr2 = null; + } + + static class PascalCase { + + } + + static class camelCase { + + } + + static class clas { + + } +} diff --git a/settings.gradle b/settings.gradle index 57ee3c41fe..5efe867adb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,21 @@ +pluginManagement { + repositories { + mavenCentral() // avoid delegating to JCenter to minimize outages + gradlePluginPortal() + } +} + +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.6.0' +} + rootProject.name = 'vineflower' + +[ + 'idea-not-null', + 'kotlin', + 'variable-renaming' +].each { + include it + project(":$it").projectDir = new File(rootProject.projectDir, "plugins/$it") +} diff --git a/src/net/fabricmc/fernflower/api/IFabricJavadocProvider.java b/src/net/fabricmc/fernflower/api/IFabricJavadocProvider.java index fc1d51f32e..ed4fe7d836 100644 --- a/src/net/fabricmc/fernflower/api/IFabricJavadocProvider.java +++ b/src/net/fabricmc/fernflower/api/IFabricJavadocProvider.java @@ -5,8 +5,6 @@ import org.jetbrains.java.decompiler.struct.StructField; import org.jetbrains.java.decompiler.struct.StructMethod; -import java.util.Map; - /** * Provides (optional) javadoc for Classes/Methods/Fields encountered by * {@link org.jetbrains.java.decompiler.main.ClassWriter}. diff --git a/src/org/jetbrains/java/decompiler/api/ClassAttributeRegistry.java b/src/org/jetbrains/java/decompiler/api/ClassAttributeRegistry.java new file mode 100644 index 0000000000..8ec9c32656 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/api/ClassAttributeRegistry.java @@ -0,0 +1,24 @@ +package org.jetbrains.java.decompiler.api; + +import org.jetbrains.java.decompiler.util.Key; +import org.jetbrains.java.decompiler.struct.attr.StructGeneralAttribute; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +public final class ClassAttributeRegistry { + private static final Map, Supplier> REGISTRY = new HashMap<>(); + + public static void register(Key key, Supplier supplier) { + REGISTRY.put(key, supplier); + } + + public static T get(Key key) { + return (T) REGISTRY.get(key).get(); + } + + public static Map, Supplier> getRegistry() { + return REGISTRY; + } +} diff --git a/src/org/jetbrains/java/decompiler/api/Decompiler.java b/src/org/jetbrains/java/decompiler/api/Decompiler.java new file mode 100644 index 0000000000..4892a981f0 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/api/Decompiler.java @@ -0,0 +1,152 @@ +package org.jetbrains.java.decompiler.api; + +import org.jetbrains.java.decompiler.main.Fernflower; +import org.jetbrains.java.decompiler.main.extern.IContextSource; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.main.extern.IResultSaver; +import org.jetbrains.java.decompiler.util.Either; + +import java.io.File; +import java.util.*; + +public final class Decompiler { + private final Fernflower engine; + + private Decompiler(Fernflower engine) { + this.engine = engine; + } + + public void decompile() { + try { + this.engine.decompileContext(); + } finally { + this.engine.clearContext(); + } + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private final List> sources = new ArrayList<>(); + private final List> libraries = new ArrayList<>(); + private final List allowedPrefixes = new ArrayList<>(); + private IResultSaver saver = null; + private IFernflowerLogger logger = IFernflowerLogger.NO_OP; + private final Map options = new HashMap<>(); + + public Builder inputs(IContextSource... sources) { + for (IContextSource source : sources) { + this.sources.add(Either.left(source)); + } + + return this; + } + + public Builder inputs(File... files) { + for (File file : files) { + this.sources.add(Either.right(file)); + } + + return this; + } + + public Builder output(IResultSaver saver) { + this.saver = saver; + + return this; + } + + public Builder logger(IFernflowerLogger logger) { + this.logger = logger; + + return this; + } + + public Builder option(String key, Object value) { + if (value instanceof Boolean) { + boolean bl = (Boolean)value; + value = bl ? "1" : "0"; + } + + if ("true".equals(value)) { + value = "1"; + } + if ("false".equals(value)) { + value = "0"; + } + + this.options.put(key, value); + + return this; + } + + public Builder options(Object... values) { + if (values.length % 2 != 0) { + throw new IllegalArgumentException("provided values must be in the format 'key, pair'"); + } + for (int i = 0; i < values.length; i += 2) { + Object key = values[i]; + Object value = values[i + 1]; + + if (!(key instanceof String)) { + throw new IllegalArgumentException("key must be a string!"); + } + + option((String) key, value); + } + + return this; + } + + public Builder libraries(IContextSource... sources) { + for (IContextSource source : sources) { + this.libraries.add(Either.left(source)); + } + + return this; + } + + public Builder libraries(File... files) { + for (File file : files) { + this.libraries.add(Either.right(file)); + } + + return this; + } + + public Builder allowedPrefixes(String... strings) { + this.allowedPrefixes.addAll(Arrays.asList(strings)); + + return this; + } + + public Decompiler build() { + if (saver == null) { + throw new IllegalArgumentException("Decompiler needs an output to write to!"); + } + + if (sources.isEmpty()) { + throw new IllegalArgumentException("Decompiler needs at least one input!"); + } + + Fernflower engine = new Fernflower(this.saver, this.options, this.logger); + + for (Either source : this.sources) { + source.map(engine::addSource, engine::addSource); + } + + for (Either source : this.libraries) { + source.map(engine::addLibrary, engine::addLibrary); + } + + for (String prefix : this.allowedPrefixes) { + engine.addWhitelist(prefix); + } + + return new Decompiler(engine); + } + } +} diff --git a/src/org/jetbrains/java/decompiler/api/FlattenedGraph.java b/src/org/jetbrains/java/decompiler/api/FlattenedGraph.java deleted file mode 100644 index 69f675f368..0000000000 --- a/src/org/jetbrains/java/decompiler/api/FlattenedGraph.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.jetbrains.java.decompiler.api; - -public interface FlattenedGraph { -} diff --git a/src/org/jetbrains/java/decompiler/api/GraphFlattener.java b/src/org/jetbrains/java/decompiler/api/GraphFlattener.java deleted file mode 100644 index 60049e55d4..0000000000 --- a/src/org/jetbrains/java/decompiler/api/GraphFlattener.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.jetbrains.java.decompiler.api; - -import org.jetbrains.java.decompiler.modules.decompiler.flow.DirectGraph; -import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; - -public interface GraphFlattener { - DirectGraph buildDirectGraph(RootStatement root); -} diff --git a/src/org/jetbrains/java/decompiler/api/SFormsCreator.java b/src/org/jetbrains/java/decompiler/api/SFormsCreator.java deleted file mode 100644 index b8dd6bab70..0000000000 --- a/src/org/jetbrains/java/decompiler/api/SFormsCreator.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.jetbrains.java.decompiler.api; - -import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; -import org.jetbrains.java.decompiler.struct.StructMethod; - -public interface SFormsCreator { - void splitVariables(RootStatement root, StructMethod mt); -} diff --git a/src/org/jetbrains/java/decompiler/api/StatementWriter.java b/src/org/jetbrains/java/decompiler/api/StatementWriter.java deleted file mode 100644 index 359089015f..0000000000 --- a/src/org/jetbrains/java/decompiler/api/StatementWriter.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.jetbrains.java.decompiler.api; - -public interface StatementWriter { -} diff --git a/src/org/jetbrains/java/decompiler/api/java/JavaPassLocation.java b/src/org/jetbrains/java/decompiler/api/java/JavaPassLocation.java new file mode 100644 index 0000000000..cbe814001d --- /dev/null +++ b/src/org/jetbrains/java/decompiler/api/java/JavaPassLocation.java @@ -0,0 +1,42 @@ +package org.jetbrains.java.decompiler.api.java; + +/** + * Species where a specific Java pass should be run. + */ +public enum JavaPassLocation { + /** + * Runs before most resugaring passes run in the main loop. + * Returning true has no effect. + */ + BEFORE_MAIN(false), + /** + * Runs during the loop statement resugaring loop. + * Returning true restarts the loop. + */ + IN_LOOP_DECOMP(true), + /** + * Runs during the main loop, after labels have been resolved. + * Returning true restarts the loop. + */ + MAIN_LOOP(true), + /** + * Runs after the main loop has finished, before variable definitions have been resolved. + * Returning true has no effect. + */ + AFTER_MAIN(false), + /** + * Runs at the last moment to make changes to the decompiled code, after variable definitions have been resolved. + * Returning true has no effect. + */ + AT_END(false); + + private final boolean loop; + + JavaPassLocation(boolean loop) { + this.loop = loop; + } + + public boolean isLoop() { + return loop; + } +} diff --git a/src/org/jetbrains/java/decompiler/api/java/JavaPassRegistrar.java b/src/org/jetbrains/java/decompiler/api/java/JavaPassRegistrar.java new file mode 100644 index 0000000000..f44685fe43 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/api/java/JavaPassRegistrar.java @@ -0,0 +1,31 @@ +package org.jetbrains.java.decompiler.api.java; + +import org.jetbrains.java.decompiler.api.plugin.pass.NamedPass; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public final class JavaPassRegistrar { + private final Map> passes = new HashMap<>(); + + /** + * Registers a pass to be run at the given code location. + * + * @param location When to run this pass + * @param pass The pass object to run + */ + public void register(JavaPassLocation location, NamedPass pass) { + passes.computeIfAbsent(location, k -> new ArrayList<>()).add(pass); + } + + // Deep, immutable copy + public Map> getPasses() { + return Map.copyOf( + passes.keySet() + .stream().collect(Collectors.toMap(k -> k, v -> List.copyOf(passes.get(v)))) + ); + } +} diff --git a/src/org/jetbrains/java/decompiler/api/passes/MainPassBuilder.java b/src/org/jetbrains/java/decompiler/api/passes/MainPassBuilder.java deleted file mode 100644 index 07b08b32e9..0000000000 --- a/src/org/jetbrains/java/decompiler/api/passes/MainPassBuilder.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.jetbrains.java.decompiler.api.passes; - -import org.jetbrains.java.decompiler.api.GraphParser; - -import java.util.ArrayList; -import java.util.List; - -public final class MainPassBuilder { - private final List passes = new ArrayList<>(); - private GraphParser parser; - - public void setGraphParser(GraphParser parser) { - this.parser = parser; - } - - public void addPass(String name, Pass pass) { - passes.add(new NamedPass(name, pass)); - } -} diff --git a/src/org/jetbrains/java/decompiler/api/passes/Pass.java b/src/org/jetbrains/java/decompiler/api/passes/Pass.java deleted file mode 100644 index db625fdb9f..0000000000 --- a/src/org/jetbrains/java/decompiler/api/passes/Pass.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.jetbrains.java.decompiler.api.passes; - -@FunctionalInterface -public interface Pass { - Pass NO_OP = ctx -> false; - - boolean run(PassContext ctx); -} diff --git a/src/org/jetbrains/java/decompiler/api/GraphParser.java b/src/org/jetbrains/java/decompiler/api/plugin/GraphParser.java similarity index 87% rename from src/org/jetbrains/java/decompiler/api/GraphParser.java rename to src/org/jetbrains/java/decompiler/api/plugin/GraphParser.java index 0c10610da9..45397cf34a 100644 --- a/src/org/jetbrains/java/decompiler/api/GraphParser.java +++ b/src/org/jetbrains/java/decompiler/api/plugin/GraphParser.java @@ -1,4 +1,4 @@ -package org.jetbrains.java.decompiler.api; +package org.jetbrains.java.decompiler.api.plugin; import org.jetbrains.java.decompiler.code.cfg.ControlFlowGraph; import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; diff --git a/src/org/jetbrains/java/decompiler/api/plugin/LanguageChooser.java b/src/org/jetbrains/java/decompiler/api/plugin/LanguageChooser.java new file mode 100644 index 0000000000..c8edd37141 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/api/plugin/LanguageChooser.java @@ -0,0 +1,7 @@ +package org.jetbrains.java.decompiler.api.plugin; + +import org.jetbrains.java.decompiler.struct.StructClass; + +public interface LanguageChooser { + boolean isLanguage(StructClass cl); +} diff --git a/src/org/jetbrains/java/decompiler/api/plugin/LanguageSpec.java b/src/org/jetbrains/java/decompiler/api/plugin/LanguageSpec.java new file mode 100644 index 0000000000..77a3d6cf7b --- /dev/null +++ b/src/org/jetbrains/java/decompiler/api/plugin/LanguageSpec.java @@ -0,0 +1,19 @@ +package org.jetbrains.java.decompiler.api.plugin; + +import org.jetbrains.java.decompiler.api.plugin.pass.Pass; + +public final class LanguageSpec { + public final String name; + public final LanguageChooser chooser; + public final GraphParser graphParser; + public final StatementWriter writer; + public final Pass pass; + + public LanguageSpec(String name, LanguageChooser chooser, GraphParser graphParser, StatementWriter writer, Pass pass) { + this.name = name; + this.chooser = chooser; + this.graphParser = graphParser; + this.writer = writer; + this.pass = pass; + } +} diff --git a/src/org/jetbrains/java/decompiler/api/plugin/Plugin.java b/src/org/jetbrains/java/decompiler/api/plugin/Plugin.java new file mode 100644 index 0000000000..9e5e0f1785 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/api/plugin/Plugin.java @@ -0,0 +1,51 @@ +package org.jetbrains.java.decompiler.api.plugin; + +import org.jetbrains.annotations.Nullable; +import org.jetbrains.java.decompiler.api.java.JavaPassRegistrar; +import org.jetbrains.java.decompiler.main.extern.IVariableNamingFactory; + +/** + * Plugins allow users to interface with Vineflower's decompilation process by providing user-defined passes or language specifications. + */ +public interface Plugin { + + /** + * Unique id of the current plugin. Must be unique across all loaded plugins, otherwise an error will be raised at runtime. + * @return id of the plugin + */ + String id(); + + /** + * Short (1-2 sentence) long description of the plugin and its function. This will be presented to users when requested via terminal. + * @return a short description of the plugin + */ + String description(); + + /** + * Allows addition to the list of passes that will be run during Java decompilation. + * + * @param registrar The registrar object to register into + */ + default void registerJavaPasses(JavaPassRegistrar registrar) { + + } + + /** + * Allows the plugin to specify a totally custom decompilation process for a language based on the JVM. + * @return The language spec, if any. + */ + @Nullable + default LanguageSpec getLanguageSpec() { + return null; + } + + @Nullable + default PluginOptions getPluginOptions() { + return null; + } + + @Nullable + default IVariableNamingFactory getRenamingFactory() { + return null; + } +} \ No newline at end of file diff --git a/src/org/jetbrains/java/decompiler/api/plugin/PluginOptions.java b/src/org/jetbrains/java/decompiler/api/plugin/PluginOptions.java new file mode 100644 index 0000000000..79197d5706 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/api/plugin/PluginOptions.java @@ -0,0 +1,16 @@ +package org.jetbrains.java.decompiler.api.plugin; + +import org.jetbrains.java.decompiler.util.Pair; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +@FunctionalInterface +public interface PluginOptions { + Pair, Consumer> provideOptions(); + + @FunctionalInterface + interface AddDefaults { + void addDefault(String key, Object defaultVal); + } +} diff --git a/src/org/jetbrains/java/decompiler/api/plugin/PluginSource.java b/src/org/jetbrains/java/decompiler/api/plugin/PluginSource.java new file mode 100644 index 0000000000..d5324df0ac --- /dev/null +++ b/src/org/jetbrains/java/decompiler/api/plugin/PluginSource.java @@ -0,0 +1,7 @@ +package org.jetbrains.java.decompiler.api.plugin; + +import java.util.List; + +public interface PluginSource { + List findPlugins(); +} diff --git a/src/org/jetbrains/java/decompiler/api/plugin/StatementWriter.java b/src/org/jetbrains/java/decompiler/api/plugin/StatementWriter.java new file mode 100644 index 0000000000..d2406618e6 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/api/plugin/StatementWriter.java @@ -0,0 +1,19 @@ +package org.jetbrains.java.decompiler.api.plugin; + +import org.jetbrains.java.decompiler.main.ClassesProcessor; +import org.jetbrains.java.decompiler.main.collectors.ImportCollector; +import org.jetbrains.java.decompiler.main.rels.ClassWrapper; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructField; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.util.TextBuffer; + +public interface StatementWriter { + void writeClassHeader(StructClass cl, TextBuffer buffer, ImportCollector importCollector); + + void writeClass(ClassesProcessor.ClassNode node, TextBuffer buffer, int indent); + + void writeField(ClassWrapper wrapper, StructClass cl, StructField fd, TextBuffer buffer, int indent); + + boolean writeMethod(ClassesProcessor.ClassNode node, StructMethod mt, int methodIndex, TextBuffer buffer, int indent); +} diff --git a/src/org/jetbrains/java/decompiler/api/passes/LoopingPassBuilder.java b/src/org/jetbrains/java/decompiler/api/plugin/pass/LoopingPassBuilder.java similarity index 82% rename from src/org/jetbrains/java/decompiler/api/passes/LoopingPassBuilder.java rename to src/org/jetbrains/java/decompiler/api/plugin/pass/LoopingPassBuilder.java index ebde29386e..720f926527 100644 --- a/src/org/jetbrains/java/decompiler/api/passes/LoopingPassBuilder.java +++ b/src/org/jetbrains/java/decompiler/api/plugin/pass/LoopingPassBuilder.java @@ -1,4 +1,4 @@ -package org.jetbrains.java.decompiler.api.passes; +package org.jetbrains.java.decompiler.api.plugin.pass; import org.jetbrains.java.decompiler.util.Pair; @@ -10,16 +10,17 @@ public final class LoopingPassBuilder { private final String name; public LoopingPassBuilder(String name) { - this.name = name; } - public void addFallthroughPass(String name, Pass pass) { + public LoopingPassBuilder addFallthroughPass(String name, Pass pass) { passes.add(Pair.of(new NamedPass(name, pass), false)); + return this; } - public void addLoopingPass(String name, Pass pass) { + public LoopingPassBuilder addLoopingPass(String name, Pass pass) { passes.add(Pair.of(new NamedPass(name, pass), true)); + return this; } public Pass build() { diff --git a/src/org/jetbrains/java/decompiler/api/plugin/pass/MainPassBuilder.java b/src/org/jetbrains/java/decompiler/api/plugin/pass/MainPassBuilder.java new file mode 100644 index 0000000000..99c1365f70 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/api/plugin/pass/MainPassBuilder.java @@ -0,0 +1,40 @@ +package org.jetbrains.java.decompiler.api.plugin.pass; + +import java.util.ArrayList; +import java.util.List; + +public final class MainPassBuilder { + private final List passes = new ArrayList<>(); + + public MainPassBuilder addPass(String name, Pass pass) { + passes.add(new NamedPass(name, pass)); + return this; + } + + public MainPassBuilder addPass(NamedPass pass) { + passes.add(pass); + return this; + } + + public Pass build() { + return new CompiledPass(passes); + } + + private static final class CompiledPass implements Pass { + private final List passes; + + public CompiledPass(List passes) { + this.passes = new ArrayList<>(passes); + } + + @Override + public boolean run(PassContext ctx) { + boolean res = false; + for (Pass pass : passes) { + res |= pass.run(ctx); + } + + return res; + } + } +} diff --git a/src/org/jetbrains/java/decompiler/api/passes/NamedPass.java b/src/org/jetbrains/java/decompiler/api/plugin/pass/NamedPass.java similarity index 71% rename from src/org/jetbrains/java/decompiler/api/passes/NamedPass.java rename to src/org/jetbrains/java/decompiler/api/plugin/pass/NamedPass.java index b1fff43c08..d71507af44 100644 --- a/src/org/jetbrains/java/decompiler/api/passes/NamedPass.java +++ b/src/org/jetbrains/java/decompiler/api/plugin/pass/NamedPass.java @@ -1,4 +1,4 @@ -package org.jetbrains.java.decompiler.api.passes; +package org.jetbrains.java.decompiler.api.plugin.pass; public final class NamedPass implements Pass { private final String name; @@ -9,6 +9,10 @@ public NamedPass(String name, Pass pass) { this.pass = pass; } + public static NamedPass of(String name, Pass pass) { + return new NamedPass(name, pass); + } + @Override public boolean run(PassContext ctx) { boolean res = this.pass.run(ctx); diff --git a/src/org/jetbrains/java/decompiler/api/plugin/pass/Pass.java b/src/org/jetbrains/java/decompiler/api/plugin/pass/Pass.java new file mode 100644 index 0000000000..1595fad023 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/api/plugin/pass/Pass.java @@ -0,0 +1,18 @@ +package org.jetbrains.java.decompiler.api.plugin.pass; + +/** + * A pass that can be run on the decompiled code. + */ +@FunctionalInterface +public interface Pass { + Pass NO_OP = ctx -> false; + + /** + * Runs this pass on the given decompiled code. + * + * @param ctx The decompiled code context + * @return Whether the decompiled code was modified + */ + + boolean run(PassContext ctx); +} diff --git a/src/org/jetbrains/java/decompiler/api/passes/PassContext.java b/src/org/jetbrains/java/decompiler/api/plugin/pass/PassContext.java similarity index 82% rename from src/org/jetbrains/java/decompiler/api/passes/PassContext.java rename to src/org/jetbrains/java/decompiler/api/plugin/pass/PassContext.java index 09b41491c7..0d4e158f2e 100644 --- a/src/org/jetbrains/java/decompiler/api/passes/PassContext.java +++ b/src/org/jetbrains/java/decompiler/api/plugin/pass/PassContext.java @@ -1,4 +1,4 @@ -package org.jetbrains.java.decompiler.api.passes; +package org.jetbrains.java.decompiler.api.plugin.pass; import org.jetbrains.java.decompiler.code.cfg.ControlFlowGraph; import org.jetbrains.java.decompiler.main.rels.DecompileRecord; @@ -6,6 +6,7 @@ import org.jetbrains.java.decompiler.modules.decompiler.vars.VarProcessor; import org.jetbrains.java.decompiler.struct.StructClass; import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; public final class PassContext { private RootStatement root; @@ -14,6 +15,7 @@ public final class PassContext { private final StructClass cl; private final VarProcessor varProc; private final DecompileRecord rec; + private final MethodDescriptor md; public PassContext(RootStatement root, ControlFlowGraph graph, StructMethod mt, StructClass cl, VarProcessor varProc, DecompileRecord rec) { this.root = root; @@ -22,6 +24,7 @@ public PassContext(RootStatement root, ControlFlowGraph graph, StructMethod mt, this.cl = cl; this.varProc = varProc; this.rec = rec; + this.md = MethodDescriptor.parseDescriptor(mt, null); } public RootStatement getRoot() { @@ -51,4 +54,8 @@ public VarProcessor getVarProc() { public DecompileRecord getRec() { return rec; } + + public MethodDescriptor getMethodDescriptor() { + return md; + } } diff --git a/src/org/jetbrains/java/decompiler/api/plugin/pass/WrappedPass.java b/src/org/jetbrains/java/decompiler/api/plugin/pass/WrappedPass.java new file mode 100644 index 0000000000..6d698a865e --- /dev/null +++ b/src/org/jetbrains/java/decompiler/api/plugin/pass/WrappedPass.java @@ -0,0 +1,22 @@ +package org.jetbrains.java.decompiler.api.plugin.pass; + +import java.util.function.Consumer; + +public final class WrappedPass implements Pass { + private final Consumer pass; + + private WrappedPass(Consumer pass) { + this.pass = pass; + } + + public static Pass of(Consumer pass) { + return new WrappedPass(pass); + } + + @Override + public boolean run(PassContext ctx) { + this.pass.accept(ctx); + + return true; + } +} diff --git a/src/org/jetbrains/java/decompiler/code/CodeConstants.java b/src/org/jetbrains/java/decompiler/code/CodeConstants.java index 70b167449c..15ba0bf6d7 100644 --- a/src/org/jetbrains/java/decompiler/code/CodeConstants.java +++ b/src/org/jetbrains/java/decompiler/code/CodeConstants.java @@ -32,18 +32,6 @@ public interface CodeConstants { int TYPE_UNKNOWN = 17; int TYPE_GENVAR = 18; - // ---------------------------------------------------------------------- - // VARIABLE TYPE FAMILIES - // ---------------------------------------------------------------------- - - int TYPE_FAMILY_UNKNOWN = 0; - int TYPE_FAMILY_BOOLEAN = 1; - int TYPE_FAMILY_INTEGER = 2; - int TYPE_FAMILY_FLOAT = 3; - int TYPE_FAMILY_LONG = 4; - int TYPE_FAMILY_DOUBLE = 5; - int TYPE_FAMILY_OBJECT = 6; - // ---------------------------------------------------------------------- // ACCESS FLAGS // ---------------------------------------------------------------------- diff --git a/src/org/jetbrains/java/decompiler/code/FullInstructionSequence.java b/src/org/jetbrains/java/decompiler/code/FullInstructionSequence.java index 97f078f5b9..dacf5ffbc8 100644 --- a/src/org/jetbrains/java/decompiler/code/FullInstructionSequence.java +++ b/src/org/jetbrains/java/decompiler/code/FullInstructionSequence.java @@ -1,7 +1,7 @@ // Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.code; -import org.jetbrains.java.decompiler.util.VBStyleCollection; +import org.jetbrains.java.decompiler.util.collections.VBStyleCollection; public class FullInstructionSequence extends InstructionSequence { diff --git a/src/org/jetbrains/java/decompiler/code/InstructionSequence.java b/src/org/jetbrains/java/decompiler/code/InstructionSequence.java index f27af0e772..84434e54bc 100644 --- a/src/org/jetbrains/java/decompiler/code/InstructionSequence.java +++ b/src/org/jetbrains/java/decompiler/code/InstructionSequence.java @@ -3,7 +3,7 @@ import org.jetbrains.java.decompiler.main.DecompilerContext; import org.jetbrains.java.decompiler.util.TextUtil; -import org.jetbrains.java.decompiler.util.VBStyleCollection; +import org.jetbrains.java.decompiler.util.collections.VBStyleCollection; import java.util.Iterator; diff --git a/src/org/jetbrains/java/decompiler/code/SimpleInstructionSequence.java b/src/org/jetbrains/java/decompiler/code/SimpleInstructionSequence.java index 0b62c51cb1..a87449f76c 100644 --- a/src/org/jetbrains/java/decompiler/code/SimpleInstructionSequence.java +++ b/src/org/jetbrains/java/decompiler/code/SimpleInstructionSequence.java @@ -1,7 +1,7 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.code; -import org.jetbrains.java.decompiler.util.VBStyleCollection; +import org.jetbrains.java.decompiler.util.collections.VBStyleCollection; public class SimpleInstructionSequence extends InstructionSequence { diff --git a/src/org/jetbrains/java/decompiler/code/cfg/BasicBlock.java b/src/org/jetbrains/java/decompiler/code/cfg/BasicBlock.java index 451e142569..b4c598bbd5 100644 --- a/src/org/jetbrains/java/decompiler/code/cfg/BasicBlock.java +++ b/src/org/jetbrains/java/decompiler/code/cfg/BasicBlock.java @@ -8,6 +8,7 @@ import org.jetbrains.java.decompiler.modules.decompiler.decompose.IGraphNode; import java.util.ArrayList; +import java.util.Collection; import java.util.List; public class BasicBlock implements IGraphNode { @@ -174,7 +175,7 @@ public int getId() { } @Override - public List getPredecessors() { + public Collection getPredecessors() { List lst = new ArrayList<>(preds); lst.addAll(predExceptions); return lst; diff --git a/src/org/jetbrains/java/decompiler/code/cfg/ControlFlowGraph.java b/src/org/jetbrains/java/decompiler/code/cfg/ControlFlowGraph.java index 8c90dde975..8ab03bb6cc 100644 --- a/src/org/jetbrains/java/decompiler/code/cfg/ControlFlowGraph.java +++ b/src/org/jetbrains/java/decompiler/code/cfg/ControlFlowGraph.java @@ -8,10 +8,11 @@ import org.jetbrains.java.decompiler.struct.StructClass; import org.jetbrains.java.decompiler.struct.StructMethod; import org.jetbrains.java.decompiler.struct.consts.ConstantPool; +import org.jetbrains.java.decompiler.struct.gen.CodeType; import org.jetbrains.java.decompiler.struct.gen.DataPoint; import org.jetbrains.java.decompiler.struct.gen.VarType; -import org.jetbrains.java.decompiler.util.ListStack; -import org.jetbrains.java.decompiler.util.VBStyleCollection; +import org.jetbrains.java.decompiler.util.collections.ListStack; +import org.jetbrains.java.decompiler.util.collections.VBStyleCollection; import java.util.*; import java.util.Map.Entry; @@ -747,7 +748,7 @@ private static void removeJsrInstructions(ConstantPool pool, BasicBlock block, D break; case CodeConstants.opc_astore: case CodeConstants.opc_pop: - if (var.type == CodeConstants.TYPE_ADDRESS) { + if (var.type == CodeType.ADDRESS) { seq.removeInstruction(i); i--; } @@ -769,7 +770,7 @@ private static void removeJsrInstructions(ConstantPool pool, BasicBlock block, D DataPoint point = new DataPoint(); point.setLocalVariables(new ArrayList<>(data.getLocalVariables())); - point.getStack().push(new VarType(CodeConstants.TYPE_OBJECT, 0, null)); + point.getStack().push(new VarType(CodeType.OBJECT, 0, null)); removeJsrInstructions(pool, suc, point); } diff --git a/src/org/jetbrains/java/decompiler/code/interpreter/InstructionImpact.java b/src/org/jetbrains/java/decompiler/code/interpreter/InstructionImpact.java index d4b223d678..3a9251015a 100644 --- a/src/org/jetbrains/java/decompiler/code/interpreter/InstructionImpact.java +++ b/src/org/jetbrains/java/decompiler/code/interpreter/InstructionImpact.java @@ -7,16 +7,13 @@ import org.jetbrains.java.decompiler.struct.consts.LinkConstant; import org.jetbrains.java.decompiler.struct.consts.PooledConstant; import org.jetbrains.java.decompiler.struct.consts.PrimitiveConstant; -import org.jetbrains.java.decompiler.struct.gen.DataPoint; -import org.jetbrains.java.decompiler.struct.gen.FieldDescriptor; -import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; -import org.jetbrains.java.decompiler.struct.gen.VarType; -import org.jetbrains.java.decompiler.util.ListStack; +import org.jetbrains.java.decompiler.struct.gen.*; +import org.jetbrains.java.decompiler.util.collections.ListStack; public final class InstructionImpact { // {read, write} - private static final int[][][] stack_impact = { + private static final CodeType[][][] stack_impact = { {null, null}, // public final static int opc_nop = 0; null, // public final static int opc_aconst_null = 1; @@ -27,22 +24,22 @@ public final class InstructionImpact { null, // public final static int opc_iconst_3 = 6; null, // public final static int opc_iconst_4 = 7; null, // public final static int opc_iconst_5 = 8; - {null, {CodeConstants.TYPE_LONG}}, // public final static int opc_lconst_0 = 9; - {null, {CodeConstants.TYPE_LONG}}, // public final static int opc_lconst_1 = 10; - {null, {CodeConstants.TYPE_FLOAT}}, // public final static int opc_fconst_0 = 11; - {null, {CodeConstants.TYPE_FLOAT}}, // public final static int opc_fconst_1 = 12; - {null, {CodeConstants.TYPE_FLOAT}}, // public final static int opc_fconst_2 = 13; - {null, {CodeConstants.TYPE_DOUBLE}}, // public final static int opc_dconst_0 = 14; - {null, {CodeConstants.TYPE_DOUBLE}}, // public final static int opc_dconst_1 = 15; - {null, {CodeConstants.TYPE_INT}}, // public final static int opc_bipush = 16; - {null, {CodeConstants.TYPE_INT}}, // public final static int opc_sipush = 17; + {null, {CodeType.LONG}}, // public final static int opc_lconst_0 = 9; + {null, {CodeType.LONG}}, // public final static int opc_lconst_1 = 10; + {null, {CodeType.FLOAT}}, // public final static int opc_fconst_0 = 11; + {null, {CodeType.FLOAT}}, // public final static int opc_fconst_1 = 12; + {null, {CodeType.FLOAT}}, // public final static int opc_fconst_2 = 13; + {null, {CodeType.DOUBLE}}, // public final static int opc_dconst_0 = 14; + {null, {CodeType.DOUBLE}}, // public final static int opc_dconst_1 = 15; + {null, {CodeType.INT}}, // public final static int opc_bipush = 16; + {null, {CodeType.INT}}, // public final static int opc_sipush = 17; null, // public final static int opc_ldc = 18; null, // public final static int opc_ldc_w = 19; null, // public final static int opc_ldc2_w = 20; - {null, {CodeConstants.TYPE_INT}}, // public final static int opc_iload = 21; - {null, {CodeConstants.TYPE_LONG}}, // public final static int opc_lload = 22; - {null, {CodeConstants.TYPE_FLOAT}}, // public final static int opc_fload = 23; - {null, {CodeConstants.TYPE_DOUBLE}}, // public final static int opc_dload = 24; + {null, {CodeType.INT}}, // public final static int opc_iload = 21; + {null, {CodeType.LONG}}, // public final static int opc_lload = 22; + {null, {CodeType.FLOAT}}, // public final static int opc_fload = 23; + {null, {CodeType.DOUBLE}}, // public final static int opc_dload = 24; null, // public final static int opc_aload = 25; null, // public final static int opc_iload_0 = 26; null, // public final static int opc_iload_1 = 27; @@ -64,25 +61,25 @@ public final class InstructionImpact { null, // public final static int opc_aload_1 = 43; null, // public final static int opc_aload_2 = 44; null, // public final static int opc_aload_3 = 45; - {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + {{CodeType.OBJECT, CodeType.INT}, {CodeType.INT}}, // public final static int opc_iaload = 46; - {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_LONG}}, + {{CodeType.OBJECT, CodeType.INT}, {CodeType.LONG}}, // public final static int opc_laload = 47; - {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_FLOAT}}, + {{CodeType.OBJECT, CodeType.INT}, {CodeType.FLOAT}}, // public final static int opc_faload = 48; - {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_DOUBLE}}, + {{CodeType.OBJECT, CodeType.INT}, {CodeType.DOUBLE}}, // public final static int opc_daload = 49; null, // public final static int opc_aaload = 50; - {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + {{CodeType.OBJECT, CodeType.INT}, {CodeType.INT}}, // public final static int opc_baload = 51; - {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + {{CodeType.OBJECT, CodeType.INT}, {CodeType.INT}}, // public final static int opc_caload = 52; - {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + {{CodeType.OBJECT, CodeType.INT}, {CodeType.INT}}, // public final static int opc_saload = 53; - {{CodeConstants.TYPE_INT}, null}, // public final static int opc_istore = 54; - {{CodeConstants.TYPE_LONG}, null}, // public final static int opc_lstore = 55; - {{CodeConstants.TYPE_FLOAT}, null}, // public final static int opc_fstore = 56; - {{CodeConstants.TYPE_DOUBLE}, null}, // public final static int opc_dstore = 57; + {{CodeType.INT}, null}, // public final static int opc_istore = 54; + {{CodeType.LONG}, null}, // public final static int opc_lstore = 55; + {{CodeType.FLOAT}, null}, // public final static int opc_fstore = 56; + {{CodeType.DOUBLE}, null}, // public final static int opc_dstore = 57; null, // public final static int opc_astore = 58; null, // public final static int opc_istore_0 = 59; null, // public final static int opc_istore_1 = 60; @@ -104,24 +101,24 @@ public final class InstructionImpact { null, // public final static int opc_astore_1 = 76; null, // public final static int opc_astore_2 = 77; null, // public final static int opc_astore_3 = 78; - {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, null}, + {{CodeType.OBJECT, CodeType.INT, CodeType.INT}, null}, // public final static int opc_iastore = 79; - {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT, CodeConstants.TYPE_LONG}, null}, + {{CodeType.OBJECT, CodeType.INT, CodeType.LONG}, null}, // public final static int opc_lastore = 80; - {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT, CodeConstants.TYPE_FLOAT}, null}, + {{CodeType.OBJECT, CodeType.INT, CodeType.FLOAT}, null}, // public final static int opc_fastore = 81; - {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT, CodeConstants.TYPE_DOUBLE}, null}, + {{CodeType.OBJECT, CodeType.INT, CodeType.DOUBLE}, null}, // public final static int opc_dastore = 82; - {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT, CodeConstants.TYPE_OBJECT}, null}, + {{CodeType.OBJECT, CodeType.INT, CodeType.OBJECT}, null}, // public final static int opc_aastore = 83; - {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, null}, + {{CodeType.OBJECT, CodeType.INT, CodeType.INT}, null}, // public final static int opc_bastore = 84; - {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, null}, + {{CodeType.OBJECT, CodeType.INT, CodeType.INT}, null}, // public final static int opc_castore = 85; - {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, null}, + {{CodeType.OBJECT, CodeType.INT, CodeType.INT}, null}, // public final static int opc_sastore = 86; - {{CodeConstants.TYPE_ANY}, null}, // public final static int opc_pop = 87; - {{CodeConstants.TYPE_ANY, CodeConstants.TYPE_ANY}, null}, // public final static int opc_pop2 = 88; + {{CodeType.ANY}, null}, // public final static int opc_pop = 87; + {{CodeType.ANY, CodeType.ANY}, null}, // public final static int opc_pop2 = 88; null, // public final static int opc_dup = 89; null, // public final static int opc_dup_x1 = 90; null, // public final static int opc_dup_x2 = 91; @@ -129,126 +126,126 @@ public final class InstructionImpact { null, // public final static int opc_dup2_x1 = 93; null, // public final static int opc_dup2_x2 = 94; null, // public final static int opc_swap = 95; - {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + {{CodeType.INT, CodeType.INT}, {CodeType.INT}}, // public final static int opc_iadd = 96; - {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_LONG}}, + {{CodeType.LONG, CodeType.LONG}, {CodeType.LONG}}, // public final static int opc_ladd = 97; - {{CodeConstants.TYPE_FLOAT, CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_FLOAT}}, + {{CodeType.FLOAT, CodeType.FLOAT}, {CodeType.FLOAT}}, // public final static int opc_fadd = 98; - {{CodeConstants.TYPE_DOUBLE, CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_DOUBLE}}, + {{CodeType.DOUBLE, CodeType.DOUBLE}, {CodeType.DOUBLE}}, // public final static int opc_dadd = 99; - {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + {{CodeType.INT, CodeType.INT}, {CodeType.INT}}, // public final static int opc_isub = 100; - {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_LONG}}, + {{CodeType.LONG, CodeType.LONG}, {CodeType.LONG}}, // public final static int opc_lsub = 101; - {{CodeConstants.TYPE_FLOAT, CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_FLOAT}}, + {{CodeType.FLOAT, CodeType.FLOAT}, {CodeType.FLOAT}}, // public final static int opc_fsub = 102; - {{CodeConstants.TYPE_DOUBLE, CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_DOUBLE}}, + {{CodeType.DOUBLE, CodeType.DOUBLE}, {CodeType.DOUBLE}}, // public final static int opc_dsub = 103; - {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + {{CodeType.INT, CodeType.INT}, {CodeType.INT}}, // public final static int opc_imul = 104; - {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_LONG}}, + {{CodeType.LONG, CodeType.LONG}, {CodeType.LONG}}, // public final static int opc_lmul = 105; - {{CodeConstants.TYPE_FLOAT, CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_FLOAT}}, + {{CodeType.FLOAT, CodeType.FLOAT}, {CodeType.FLOAT}}, // public final static int opc_fmul = 106; - {{CodeConstants.TYPE_DOUBLE, CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_DOUBLE}}, + {{CodeType.DOUBLE, CodeType.DOUBLE}, {CodeType.DOUBLE}}, // public final static int opc_dmul = 107; - {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + {{CodeType.INT, CodeType.INT}, {CodeType.INT}}, // public final static int opc_idiv = 108; - {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_LONG}}, + {{CodeType.LONG, CodeType.LONG}, {CodeType.LONG}}, // public final static int opc_ldiv = 109; - {{CodeConstants.TYPE_FLOAT, CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_FLOAT}}, + {{CodeType.FLOAT, CodeType.FLOAT}, {CodeType.FLOAT}}, // public final static int opc_fdiv = 110; - {{CodeConstants.TYPE_DOUBLE, CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_DOUBLE}}, + {{CodeType.DOUBLE, CodeType.DOUBLE}, {CodeType.DOUBLE}}, // public final static int opc_ddiv = 111; - {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + {{CodeType.INT, CodeType.INT}, {CodeType.INT}}, // public final static int opc_irem = 112; - {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_LONG}}, + {{CodeType.LONG, CodeType.LONG}, {CodeType.LONG}}, // public final static int opc_lrem = 113; - {{CodeConstants.TYPE_FLOAT, CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_FLOAT}}, + {{CodeType.FLOAT, CodeType.FLOAT}, {CodeType.FLOAT}}, // public final static int opc_frem = 114; - {{CodeConstants.TYPE_DOUBLE, CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_DOUBLE}}, + {{CodeType.DOUBLE, CodeType.DOUBLE}, {CodeType.DOUBLE}}, // public final static int opc_drem = 115; - {{CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, // public final static int opc_ineg = 116; - {{CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_LONG}}, // public final static int opc_lneg = 117; - {{CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_FLOAT}}, // public final static int opc_fneg = 118; - {{CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_DOUBLE}}, // public final static int opc_dneg = 119; - {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + {{CodeType.INT}, {CodeType.INT}}, // public final static int opc_ineg = 116; + {{CodeType.LONG}, {CodeType.LONG}}, // public final static int opc_lneg = 117; + {{CodeType.FLOAT}, {CodeType.FLOAT}}, // public final static int opc_fneg = 118; + {{CodeType.DOUBLE}, {CodeType.DOUBLE}}, // public final static int opc_dneg = 119; + {{CodeType.INT, CodeType.INT}, {CodeType.INT}}, // public final static int opc_ishl = 120; - {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_LONG}}, + {{CodeType.LONG, CodeType.INT}, {CodeType.LONG}}, // public final static int opc_lshl = 121; - {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + {{CodeType.INT, CodeType.INT}, {CodeType.INT}}, // public final static int opc_ishr = 122; - {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_LONG}}, + {{CodeType.LONG, CodeType.INT}, {CodeType.LONG}}, // public final static int opc_lshr = 123; - {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + {{CodeType.INT, CodeType.INT}, {CodeType.INT}}, // public final static int opc_iushr = 124; - {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_LONG}}, + {{CodeType.LONG, CodeType.INT}, {CodeType.LONG}}, // public final static int opc_lushr = 125; - {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + {{CodeType.INT, CodeType.INT}, {CodeType.INT}}, // public final static int opc_iand = 126; - {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_LONG}}, + {{CodeType.LONG, CodeType.LONG}, {CodeType.LONG}}, // public final static int opc_land = 127; - {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + {{CodeType.INT, CodeType.INT}, {CodeType.INT}}, // public final static int opc_ior = 128; - {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_LONG}}, + {{CodeType.LONG, CodeType.LONG}, {CodeType.LONG}}, // public final static int opc_lor = 129; - {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + {{CodeType.INT, CodeType.INT}, {CodeType.INT}}, // public final static int opc_ixor = 130; - {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_LONG}}, + {{CodeType.LONG, CodeType.LONG}, {CodeType.LONG}}, // public final static int opc_lxor = 131; {null, null}, // public final static int opc_iinc = 132; - {{CodeConstants.TYPE_INT}, {CodeConstants.TYPE_LONG}}, // public final static int opc_i2l = 133; - {{CodeConstants.TYPE_INT}, {CodeConstants.TYPE_FLOAT}}, // public final static int opc_i2f = 134; - {{CodeConstants.TYPE_INT}, {CodeConstants.TYPE_DOUBLE}}, // public final static int opc_i2d = 135; - {{CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_INT}}, // public final static int opc_l2i = 136; - {{CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_FLOAT}}, // public final static int opc_l2f = 137; - {{CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_DOUBLE}}, // public final static int opc_l2d = 138; - {{CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_INT}}, // public final static int opc_f2i = 139; - {{CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_LONG}}, // public final static int opc_f2l = 140; - {{CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_DOUBLE}}, // public final static int opc_f2d = 141; - {{CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_INT}}, // public final static int opc_d2i = 142; - {{CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_LONG}}, // public final static int opc_d2l = 143; - {{CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_FLOAT}}, // public final static int opc_d2f = 144; - {{CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, // public final static int opc_i2b = 145; - {{CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, // public final static int opc_i2c = 146; - {{CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, // public final static int opc_i2s = 147; - {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_INT}}, + {{CodeType.INT}, {CodeType.LONG}}, // public final static int opc_i2l = 133; + {{CodeType.INT}, {CodeType.FLOAT}}, // public final static int opc_i2f = 134; + {{CodeType.INT}, {CodeType.DOUBLE}}, // public final static int opc_i2d = 135; + {{CodeType.LONG}, {CodeType.INT}}, // public final static int opc_l2i = 136; + {{CodeType.LONG}, {CodeType.FLOAT}}, // public final static int opc_l2f = 137; + {{CodeType.LONG}, {CodeType.DOUBLE}}, // public final static int opc_l2d = 138; + {{CodeType.FLOAT}, {CodeType.INT}}, // public final static int opc_f2i = 139; + {{CodeType.FLOAT}, {CodeType.LONG}}, // public final static int opc_f2l = 140; + {{CodeType.FLOAT}, {CodeType.DOUBLE}}, // public final static int opc_f2d = 141; + {{CodeType.DOUBLE}, {CodeType.INT}}, // public final static int opc_d2i = 142; + {{CodeType.DOUBLE}, {CodeType.LONG}}, // public final static int opc_d2l = 143; + {{CodeType.DOUBLE}, {CodeType.FLOAT}}, // public final static int opc_d2f = 144; + {{CodeType.INT}, {CodeType.INT}}, // public final static int opc_i2b = 145; + {{CodeType.INT}, {CodeType.INT}}, // public final static int opc_i2c = 146; + {{CodeType.INT}, {CodeType.INT}}, // public final static int opc_i2s = 147; + {{CodeType.LONG, CodeType.LONG}, {CodeType.INT}}, // public final static int opc_lcmp = 148; - {{CodeConstants.TYPE_FLOAT, CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_INT}}, + {{CodeType.FLOAT, CodeType.FLOAT}, {CodeType.INT}}, // public final static int opc_fcmpl = 149; - {{CodeConstants.TYPE_FLOAT, CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_INT}}, + {{CodeType.FLOAT, CodeType.FLOAT}, {CodeType.INT}}, // public final static int opc_fcmpg = 150; - {{CodeConstants.TYPE_DOUBLE, CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_INT}}, + {{CodeType.DOUBLE, CodeType.DOUBLE}, {CodeType.INT}}, // public final static int opc_dcmpl = 151; - {{CodeConstants.TYPE_DOUBLE, CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_INT}}, + {{CodeType.DOUBLE, CodeType.DOUBLE}, {CodeType.INT}}, // public final static int opc_dcmpg = 152; - {{CodeConstants.TYPE_INT}, null}, // public final static int opc_ifeq = 153; - {{CodeConstants.TYPE_INT}, null}, // public final static int opc_ifne = 154; - {{CodeConstants.TYPE_INT}, null}, // public final static int opc_iflt = 155; - {{CodeConstants.TYPE_INT}, null}, // public final static int opc_ifge = 156; - {{CodeConstants.TYPE_INT}, null}, // public final static int opc_ifgt = 157; - {{CodeConstants.TYPE_INT}, null}, // public final static int opc_ifle = 158; - {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, null}, // public final static int opc_if_icmpeq = 159; - {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, null}, // public final static int opc_if_icmpne = 160; - {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, null}, // public final static int opc_if_icmplt = 161; - {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, null}, // public final static int opc_if_icmpge = 162; - {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, null}, // public final static int opc_if_icmpgt = 163; - {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, null}, // public final static int opc_if_icmple = 164; - {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_OBJECT}, null}, + {{CodeType.INT}, null}, // public final static int opc_ifeq = 153; + {{CodeType.INT}, null}, // public final static int opc_ifne = 154; + {{CodeType.INT}, null}, // public final static int opc_iflt = 155; + {{CodeType.INT}, null}, // public final static int opc_ifge = 156; + {{CodeType.INT}, null}, // public final static int opc_ifgt = 157; + {{CodeType.INT}, null}, // public final static int opc_ifle = 158; + {{CodeType.INT, CodeType.INT}, null}, // public final static int opc_if_icmpeq = 159; + {{CodeType.INT, CodeType.INT}, null}, // public final static int opc_if_icmpne = 160; + {{CodeType.INT, CodeType.INT}, null}, // public final static int opc_if_icmplt = 161; + {{CodeType.INT, CodeType.INT}, null}, // public final static int opc_if_icmpge = 162; + {{CodeType.INT, CodeType.INT}, null}, // public final static int opc_if_icmpgt = 163; + {{CodeType.INT, CodeType.INT}, null}, // public final static int opc_if_icmple = 164; + {{CodeType.OBJECT, CodeType.OBJECT}, null}, // public final static int opc_if_acmpeq = 165; - {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_OBJECT}, null}, + {{CodeType.OBJECT, CodeType.OBJECT}, null}, // public final static int opc_if_acmpne = 166; {null, null}, // public final static int opc_goto = 167; - {null, {CodeConstants.TYPE_ADDRESS}}, // public final static int opc_jsr = 168; + {null, {CodeType.ADDRESS}}, // public final static int opc_jsr = 168; {null, null}, // public final static int opc_ret = 169; - {{CodeConstants.TYPE_INT}, null}, // public final static int opc_tableswitch = 170; - {{CodeConstants.TYPE_INT}, null}, // public final static int opc_lookupswitch = 171; - {{CodeConstants.TYPE_INT}, null}, // public final static int opc_ireturn = 172; - {{CodeConstants.TYPE_LONG}, null}, // public final static int opc_lreturn = 173; - {{CodeConstants.TYPE_FLOAT}, null}, // public final static int opc_freturn = 174; - {{CodeConstants.TYPE_DOUBLE}, null}, // public final static int opc_dreturn = 175; - {{CodeConstants.TYPE_OBJECT}, null}, // public final static int opc_areturn = 176; + {{CodeType.INT}, null}, // public final static int opc_tableswitch = 170; + {{CodeType.INT}, null}, // public final static int opc_lookupswitch = 171; + {{CodeType.INT}, null}, // public final static int opc_ireturn = 172; + {{CodeType.LONG}, null}, // public final static int opc_lreturn = 173; + {{CodeType.FLOAT}, null}, // public final static int opc_freturn = 174; + {{CodeType.DOUBLE}, null}, // public final static int opc_dreturn = 175; + {{CodeType.OBJECT}, null}, // public final static int opc_areturn = 176; {null, null}, // public final static int opc_return = 177; null, // public final static int opc_getstatic = 178; null, // public final static int opc_putstatic = 179; @@ -262,34 +259,34 @@ public final class InstructionImpact { null, // public final static int opc_new = 187; null, // public final static int opc_newarray = 188; null, // public final static int opc_anewarray = 189; - {{CodeConstants.TYPE_OBJECT}, {CodeConstants.TYPE_INT}}, // public final static int opc_arraylength = 190; + {{CodeType.OBJECT}, {CodeType.INT}}, // public final static int opc_arraylength = 190; null, // public final static int opc_athrow = 191; null, // public final static int opc_checkcast = 192; null, // public final static int opc_instanceof = 193; - {{CodeConstants.TYPE_OBJECT}, null}, // public final static int opc_monitorenter = 194; - {{CodeConstants.TYPE_OBJECT}, null}, // public final static int opc_monitorexit = 195; + {{CodeType.OBJECT}, null}, // public final static int opc_monitorenter = 194; + {{CodeType.OBJECT}, null}, // public final static int opc_monitorexit = 195; null, // public final static int opc_wide = 196; null, // public final static int opc_multianewarray = 197; - {{CodeConstants.TYPE_OBJECT}, null}, // public final static int opc_ifnull = 198; - {{CodeConstants.TYPE_OBJECT}, null}, // public final static int opc_ifnonnull = 199; + {{CodeType.OBJECT}, null}, // public final static int opc_ifnull = 198; + {{CodeType.OBJECT}, null}, // public final static int opc_ifnonnull = 199; {null, null}, // public final static int opc_goto_w = 200; - {null, {CodeConstants.TYPE_ADDRESS}}, // public final static int opc_jsr_w = 201; + {null, {CodeType.ADDRESS}}, // public final static int opc_jsr_w = 201; }; - private static final int[] arr_type = new int[]{ - CodeConstants.TYPE_BOOLEAN, - CodeConstants.TYPE_CHAR, - CodeConstants.TYPE_FLOAT, - CodeConstants.TYPE_DOUBLE, - CodeConstants.TYPE_BYTE, - CodeConstants.TYPE_SHORT, - CodeConstants.TYPE_INT, - CodeConstants.TYPE_LONG + private static final CodeType[] arr_type = new CodeType[]{ + CodeType.BOOLEAN, + CodeType.CHAR, + CodeType.FLOAT, + CodeType.DOUBLE, + CodeType.BYTE, + CodeType.SHORT, + CodeType.INT, + CodeType.LONG }; @@ -327,20 +324,20 @@ public final class InstructionImpact { public static void stepTypes(DataPoint data, Instruction instr, ConstantPool pool) { ListStack stack = data.getStack(); - int[][] arr = stack_impact[instr.opcode]; + CodeType[][] arr = stack_impact[instr.opcode]; if (arr != null) { // simple types only - int[] read = arr[0]; - int[] write = arr[1]; + CodeType[] read = arr[0]; + CodeType[] write = arr[1]; if (read != null) { int depth = 0; - for (int type : read) { + for (CodeType type : read) { depth++; - if (type == CodeConstants.TYPE_LONG || - type == CodeConstants.TYPE_DOUBLE) { + if (type == CodeType.LONG || + type == CodeType.DOUBLE) { depth++; } } @@ -349,11 +346,11 @@ public static void stepTypes(DataPoint data, Instruction instr, ConstantPool poo } if (write != null) { - for (int type : write) { + for (CodeType type : write) { stack.push(new VarType(type)); - if (type == CodeConstants.TYPE_LONG || - type == CodeConstants.TYPE_DOUBLE) { - stack.push(new VarType(CodeConstants.TYPE_GROUP2EMPTY)); + if (type == CodeType.LONG || + type == CodeType.DOUBLE) { + stack.push(new VarType(CodeType.GROUP2EMPTY)); } } } @@ -374,7 +371,7 @@ private static void processSpecialInstructions(DataPoint data, Instruction instr switch (instr.opcode) { case CodeConstants.opc_aconst_null: - stack.push(new VarType(CodeConstants.TYPE_NULL, 0, null)); + stack.push(new VarType(CodeType.NULL, 0, null)); break; case CodeConstants.opc_ldc: case CodeConstants.opc_ldc_w: @@ -382,24 +379,24 @@ private static void processSpecialInstructions(DataPoint data, Instruction instr PooledConstant constant = pool.getConstant(instr.operand(0)); switch (constant.type) { case CodeConstants.CONSTANT_Integer: - stack.push(new VarType(CodeConstants.TYPE_INT)); + stack.push(new VarType(CodeType.INT)); break; case CodeConstants.CONSTANT_Float: - stack.push(new VarType(CodeConstants.TYPE_FLOAT)); + stack.push(new VarType(CodeType.FLOAT)); break; case CodeConstants.CONSTANT_Long: - stack.push(new VarType(CodeConstants.TYPE_LONG)); - stack.push(new VarType(CodeConstants.TYPE_GROUP2EMPTY)); + stack.push(new VarType(CodeType.LONG)); + stack.push(new VarType(CodeType.GROUP2EMPTY)); break; case CodeConstants.CONSTANT_Double: - stack.push(new VarType(CodeConstants.TYPE_DOUBLE)); - stack.push(new VarType(CodeConstants.TYPE_GROUP2EMPTY)); + stack.push(new VarType(CodeType.DOUBLE)); + stack.push(new VarType(CodeType.GROUP2EMPTY)); break; case CodeConstants.CONSTANT_String: - stack.push(new VarType(CodeConstants.TYPE_OBJECT, 0, "java/lang/String")); + stack.push(new VarType(CodeType.OBJECT, 0, "java/lang/String")); break; case CodeConstants.CONSTANT_Class: - stack.push(new VarType(CodeConstants.TYPE_OBJECT, 0, "java/lang/Class")); + stack.push(new VarType(CodeType.OBJECT, 0, "java/lang/Class")); break; case CodeConstants.CONSTANT_MethodHandle: stack.push(new VarType(((LinkConstant)constant).descriptor)); @@ -407,10 +404,10 @@ private static void processSpecialInstructions(DataPoint data, Instruction instr case CodeConstants.CONSTANT_Dynamic: ck = pool.getLinkConstant(instr.operand(0)); FieldDescriptor fd = FieldDescriptor.parseDescriptor(ck.descriptor); - if (fd.type.type != CodeConstants.TYPE_VOID) { + if (fd.type.type != CodeType.VOID) { stack.push(fd.type); if (fd.type.stackSize == 2) { - stack.push(new VarType(CodeConstants.TYPE_GROUP2EMPTY)); + stack.push(new VarType(CodeType.GROUP2EMPTY)); } } break; @@ -422,7 +419,7 @@ private static void processSpecialInstructions(DataPoint data, Instruction instr stack.push(var1); } else { - stack.push(new VarType(CodeConstants.TYPE_OBJECT, 0, null)); + stack.push(new VarType(CodeType.OBJECT, 0, null)); } break; case CodeConstants.opc_aaload: @@ -436,14 +433,14 @@ private static void processSpecialInstructions(DataPoint data, Instruction instr case CodeConstants.opc_dup_x1: case CodeConstants.opc_dup_x2: int depth1 = 88 - instr.opcode; - stack.insertByOffset(depth1, stack.getByOffset(-1).copy()); + stack.insertByOffset(depth1, stack.getByOffset(-1)); break; case CodeConstants.opc_dup2: case CodeConstants.opc_dup2_x1: case CodeConstants.opc_dup2_x2: int depth2 = 90 - instr.opcode; - stack.insertByOffset(depth2, stack.getByOffset(-2).copy()); - stack.insertByOffset(depth2, stack.getByOffset(-1).copy()); + stack.insertByOffset(depth2, stack.getByOffset(-2)); + stack.insertByOffset(depth2, stack.getByOffset(-1)); break; case CodeConstants.opc_swap: var1 = stack.pop(); @@ -456,7 +453,7 @@ private static void processSpecialInstructions(DataPoint data, Instruction instr var1 = new VarType(ck.descriptor); stack.push(var1); if (var1.stackSize == 2) { - stack.push(new VarType(CodeConstants.TYPE_GROUP2EMPTY)); + stack.push(new VarType(CodeType.GROUP2EMPTY)); } break; case CodeConstants.opc_putfield: @@ -478,17 +475,17 @@ private static void processSpecialInstructions(DataPoint data, Instruction instr for (int i = 0; i < md.params.length; i++) { stack.pop(md.params[i].stackSize); } - if (md.ret.type != CodeConstants.TYPE_VOID) { + if (md.ret.type != CodeType.VOID) { stack.push(md.ret); if (md.ret.stackSize == 2) { - stack.push(new VarType(CodeConstants.TYPE_GROUP2EMPTY)); + stack.push(new VarType(CodeType.GROUP2EMPTY)); } } } break; case CodeConstants.opc_new: cn = pool.getPrimitiveConstant(instr.operand(0)); - stack.push(new VarType(CodeConstants.TYPE_OBJECT, 0, cn.getString())); + stack.push(new VarType(CodeType.OBJECT, 0, cn.getString())); break; case CodeConstants.opc_newarray: stack.pop(); @@ -503,7 +500,7 @@ private static void processSpecialInstructions(DataPoint data, Instruction instr case CodeConstants.opc_instanceof: stack.pop(); cn = pool.getPrimitiveConstant(instr.operand(0)); - stack.push(new VarType(CodeConstants.TYPE_OBJECT, 0, cn.getString())); + stack.push(new VarType(CodeType.OBJECT, 0, cn.getString())); break; case CodeConstants.opc_anewarray: case CodeConstants.opc_multianewarray: @@ -511,12 +508,12 @@ private static void processSpecialInstructions(DataPoint data, Instruction instr stack.pop(dimensions); cn = pool.getPrimitiveConstant(instr.operand(0)); if (cn.isArray) { - var1 = new VarType(CodeConstants.TYPE_OBJECT, 0, cn.getString()); + var1 = new VarType(CodeType.OBJECT, 0, cn.getString()); var1 = var1.resizeArrayDim(var1.arrayDim + dimensions); stack.push(var1); } else { - stack.push(new VarType(CodeConstants.TYPE_OBJECT, dimensions, cn.getString())); + stack.push(new VarType(CodeType.OBJECT, dimensions, cn.getString())); } } } diff --git a/src/org/jetbrains/java/decompiler/main/AssertProcessor.java b/src/org/jetbrains/java/decompiler/main/AssertProcessor.java index 134e7960e8..8bf8f9b033 100644 --- a/src/org/jetbrains/java/decompiler/main/AssertProcessor.java +++ b/src/org/jetbrains/java/decompiler/main/AssertProcessor.java @@ -14,6 +14,7 @@ import org.jetbrains.java.decompiler.modules.decompiler.exps.FunctionExprent.FunctionType; import org.jetbrains.java.decompiler.modules.decompiler.stats.*; import org.jetbrains.java.decompiler.struct.StructField; +import org.jetbrains.java.decompiler.struct.gen.CodeType; import org.jetbrains.java.decompiler.struct.gen.FieldDescriptor; import org.jetbrains.java.decompiler.struct.gen.VarType; import org.jetbrains.java.decompiler.util.InterpreterUtil; @@ -24,7 +25,7 @@ public final class AssertProcessor { - private static final VarType CLASS_ASSERTION_ERROR = new VarType(CodeConstants.TYPE_OBJECT, 0, "java/lang/AssertionError"); + private static final VarType CLASS_ASSERTION_ERROR = new VarType(CodeType.OBJECT, 0, "java/lang/AssertionError"); public static void buildAssertions(ClassNode node) { @@ -187,8 +188,7 @@ private static boolean replaceAssertion(Statement parent, IfStatement stat, Stri AssertExprent asexpr = new AssertExprent(lstParams); - Statement newstat = new BasicBlockStatement(new BasicBlock( - DecompilerContext.getCounterContainer().getCounterAndIncrement(CounterContainer.STATEMENT_COUNTER))); + Statement newstat = BasicBlockStatement.create(); newstat.setExprents(Arrays.asList(new Exprent[]{asexpr})); Statement first = stat.getFirst(); diff --git a/src/org/jetbrains/java/decompiler/main/ClassReference14Processor.java b/src/org/jetbrains/java/decompiler/main/ClassReference14Processor.java index 6d21279841..a9aebdc4f0 100644 --- a/src/org/jetbrains/java/decompiler/main/ClassReference14Processor.java +++ b/src/org/jetbrains/java/decompiler/main/ClassReference14Processor.java @@ -14,10 +14,11 @@ import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; import org.jetbrains.java.decompiler.struct.StructField; import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.gen.CodeType; import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; import org.jetbrains.java.decompiler.struct.gen.VarType; import org.jetbrains.java.decompiler.util.InterpreterUtil; -import org.jetbrains.java.decompiler.util.VBStyleCollection; +import org.jetbrains.java.decompiler.util.collections.VBStyleCollection; import java.util.*; import java.util.Map.Entry; @@ -42,7 +43,7 @@ public final class ClassReference14Processor { ctor.setStringDescriptor("()V"); ctor.setFunctype(InvocationExprent.Type.INIT); ctor.setDescriptor(MethodDescriptor.parseDescriptor("()V")); - NewExprent newExpr = new NewExprent(new VarType(CodeConstants.TYPE_OBJECT, 0, "java/lang/NoClassDefFoundError"), new ArrayList<>(), null); + NewExprent newExpr = new NewExprent(new VarType(CodeType.OBJECT, 0, "java/lang/NoClassDefFoundError"), new ArrayList<>(), null); newExpr.setConstructor(ctor); InvocationExprent invCause = new InvocationExprent(); invCause.setName("initCause"); @@ -51,7 +52,7 @@ public final class ClassReference14Processor { invCause.setDescriptor(MethodDescriptor.parseDescriptor("(Ljava/lang/Throwable;)Ljava/lang/Throwable;")); invCause.setInstance(newExpr); invCause.setLstParameters( - Collections.singletonList(new VarExprent(2, new VarType(CodeConstants.TYPE_OBJECT, 0, "java/lang/ClassNotFoundException"), null))); + Collections.singletonList(new VarExprent(2, new VarType(CodeType.OBJECT, 0, "java/lang/ClassNotFoundException"), null))); HANDLER_EXPR = new ExitExprent(ExitExprent.Type.THROW, invCause, null, null, null); } @@ -137,7 +138,7 @@ private static void mapClassMethods(ClassNode node, Map ERROR_DUMP_STOP_POINTS = new HashSet<>(Arrays.asList( "Fernflower.decompileContext", "MethodProcessor.codeToJava", - "ClassWriter.methodToJava", + "ClassWriter.writeMethod", "ClassWriter.methodLambdaToJava", "ClassWriter.classLambdaToJava" )); @@ -78,6 +82,8 @@ private static boolean invokeProcessors(TextBuffer buffer, ClassNode node) { if (method.root != null) { try { SwitchHelper.simplifySwitches(method.root, method.methodStruct, method.root); + } catch (CancelationManager.CanceledException e) { + throw e; } catch (Throwable e) { DecompilerContext.getLogger().writeMessage("Method " + method.methodStruct.getName() + " " + method.methodStruct.getDescriptor() + " in class " + node.classStruct.qualifiedName + " couldn't be written.", IFernflowerLogger.Severity.WARN, @@ -101,16 +107,18 @@ private static boolean invokeProcessors(TextBuffer buffer, ClassNode node) { EnumProcessor.clearEnum(wrapper); } - // FIXME: when 1.10 merge, this needs to be removed + if (DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ASSERTIONS)) { + AssertProcessor.buildAssertions(node); + } + for (MethodWrapper mw : wrapper.getMethods()) { + RecordHelper.fixupCanonicalConstructor(mw, cl); if (mw.root != null) { mw.varproc.rerunClashing(mw.root); } } - - if (DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ASSERTIONS)) { - AssertProcessor.buildAssertions(node); - } + } catch (CancelationManager.CanceledException e) { + throw e; } catch (Throwable t) { DecompilerContext.getLogger().writeMessage("Class " + node.simpleName + " couldn't be written.", IFernflowerLogger.Severity.WARN, @@ -146,7 +154,7 @@ public void classLambdaToJava(ClassNode node, TextBuffer buffer, Exprent method_ boolean lambdaToAnonymous = DecompilerContext.getOption(IFernflowerPreferences.LAMBDA_TO_ANONYMOUS_CLASS); - ClassNode outerNode = (ClassNode)DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASS_NODE); + ClassNode outerNode = (ClassNode)DecompilerContext.getContextProperty(DecompilerContext.CURRENT_CLASS_NODE); DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_NODE, node); try { @@ -157,7 +165,7 @@ public void classLambdaToJava(ClassNode node, TextBuffer buffer, Exprent method_ if (node.lambdaInformation.is_method_reference) { if (!node.lambdaInformation.is_content_method_static && method_object != null) { // reference to a virtual method - method_object.getInferredExprType(new VarType(CodeConstants.TYPE_OBJECT, 0, node.lambdaInformation.content_class_name)); + method_object.getInferredExprType(new VarType(CodeType.OBJECT, 0, node.lambdaInformation.content_class_name)); TextBuffer instance = method_object.toJava(indent); // If the instance is casted, then we need to wrap it if (method_object instanceof FunctionExprent && ((FunctionExprent)method_object).getFuncType() == FunctionType.CAST && ((FunctionExprent)method_object).doesCast()) { @@ -169,11 +177,12 @@ public void classLambdaToJava(ClassNode node, TextBuffer buffer, Exprent method_ } else { // reference to a static method - buffer.append(ExprProcessor.getCastTypeName(new VarType(node.lambdaInformation.content_class_name, true))); + buffer.appendCastTypeName(new VarType(node.lambdaInformation.content_class_name, true)); } buffer.append("::") - .append(CodeConstants.INIT_NAME.equals(node.lambdaInformation.content_method_name) ? "new" : node.lambdaInformation.content_method_name); + .appendMethod(CodeConstants.INIT_NAME.equals(node.lambdaInformation.content_method_name) ? "new" : node.lambdaInformation.content_method_name, + false, node.lambdaInformation.content_class_name, node.lambdaInformation.content_method_name, node.lambdaInformation.content_method_descriptor); } else { // lambda method @@ -183,86 +192,128 @@ public void classLambdaToJava(ClassNode node, TextBuffer buffer, Exprent method_ MethodDescriptor md_lambda = MethodDescriptor.parseDescriptor(node.lambdaInformation.method_descriptor); boolean simpleLambda = false; + boolean written = false; if (!lambdaToAnonymous) { - boolean lambdaParametersNeedParentheses = md_lambda.params.length != 1; - - if (lambdaParametersNeedParentheses) { - buffer.append('('); + RootStatement root = wrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()).root; + if (DecompilerContext.getOption(IFernflowerPreferences.MARK_CORRESPONDING_SYNTHETICS)) { + buffer.append("/* ") + .appendMethod(node.lambdaInformation.content_method_name, + true, node.lambdaInformation.content_class_name, node.lambdaInformation.content_method_name, node.lambdaInformation.content_method_descriptor) + .append(" */ "); } - - boolean firstParameter = true; - int index = node.lambdaInformation.is_content_method_static ? 0 : 1; - int start_index = md_content.params.length - md_lambda.params.length; - - for (int i = 0; i < md_content.params.length; i++) { - if (i >= start_index) { - if (!firstParameter) { - buffer.append(", "); - } - VarType type = md_content.params[i]; - - String parameterName = methodWrapper.varproc.getVarName(new VarVersionPair(index, 0)); - if (parameterName == null) { - parameterName = "param" + index; // null iff decompiled with errors + // Array constructor lambda + if (md_lambda.params.length == 1 && md_lambda.params[0].equals(VarType.VARTYPE_INT) && md_lambda.ret.arrayDim > 0) { + if (root.getFirst() instanceof BasicBlockStatement && root.getFirst().getExprents().size() == 1) { + Exprent exp = root.getFirst().getExprents().get(0); + if (exp instanceof ExitExprent) { + ExitExprent exit = (ExitExprent) exp; + Exprent returnValue = exit.getValue(); + if (returnValue instanceof NewExprent) { + NewExprent newExp = (NewExprent) returnValue; + if (newExp.getNewType().arrayDim > 0 && !newExp.isDirectArrayInit() && newExp.getLstArrayElements().isEmpty() && newExp.getLstDims().size() > 0) { + Exprent size = newExp.getLstDims().get(newExp.getLstDims().size() - 1); + if (size instanceof VarExprent) { + VarExprent sizeVar = (VarExprent) size; + if (sizeVar.getIndex() == (node.lambdaInformation.is_content_method_static ? 0 : 1)) { + VarType returnType = md_lambda.ret; + buffer.appendCastTypeName(returnType); + buffer.append("::new"); + written = true; + } + } + } + } } - parameterName = methodWrapper.methodStruct.getVariableNamer().renameParameter(mt.getAccessFlags(), ExprProcessor.getCastTypeName(type), parameterName, index); - buffer.append(parameterName); - - firstParameter = false; } - - index += md_content.params[i].stackSize; } - - if (lambdaParametersNeedParentheses) { - buffer.append(")"); - } - buffer.append(" ->"); - - RootStatement root = wrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()).root; - if (DecompilerContext.getOption(IFernflowerPreferences.INLINE_SIMPLE_LAMBDAS) && methodWrapper.decompileError == null && root != null) { - Statement firstStat = root.getFirst(); - if (firstStat instanceof BasicBlockStatement && firstStat.getExprents() != null && firstStat.getExprents().size() == 1) { - Exprent firstExpr = firstStat.getExprents().get(0); - boolean isVarDefinition = firstExpr instanceof AssignmentExprent && - ((AssignmentExprent)firstExpr).getLeft() instanceof VarExprent && - ((VarExprent)((AssignmentExprent)firstExpr).getLeft()).isDefinition(); - - boolean isThrow = firstExpr instanceof ExitExprent && - ((ExitExprent)firstExpr).getExitType() == ExitExprent.Type.THROW; - - if (!isVarDefinition && !isThrow) { - simpleLambda = true; - MethodWrapper outerWrapper = (MethodWrapper)DecompilerContext.getProperty(DecompilerContext.CURRENT_METHOD_WRAPPER); - DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, methodWrapper); - try { - TextBuffer codeBuffer = firstExpr.toJava(indent + 1); - - if (firstExpr instanceof ExitExprent) - codeBuffer.setStart(6); // skip return - else - codeBuffer.prepend(" "); - - codeBuffer.addBytecodeMapping(root.getDummyExit().bytecode); - buffer.append(codeBuffer, node.classStruct.qualifiedName, InterpreterUtil.makeUniqueKey(methodWrapper.methodStruct.getName(), methodWrapper.methodStruct.getDescriptor())); + if (!written) { + boolean lambdaParametersNeedParentheses = md_lambda.params.length != 1; + + if (lambdaParametersNeedParentheses) { + buffer.append('('); + } + + boolean firstParameter = true; + int index = node.lambdaInformation.is_content_method_static ? 0 : 1; + int start_index = md_content.params.length - md_lambda.params.length; + + for (int i = 0; i < md_content.params.length; i++) { + if (i >= start_index) { + if (!firstParameter) { + buffer.append(", "); + } + VarType type = md_content.params[i]; + + String clashingName = methodWrapper.varproc.getClashingName(new VarVersionPair(index, 0)); + String parameterName = methodWrapper.varproc.getVarName(new VarVersionPair(index, 0)); + if (parameterName == null) { + parameterName = "param" + index; // null iff decompiled with errors } - catch (Throwable ex) { - DecompilerContext.getLogger().writeMessage("Method " + mt.getName() + " " + mt.getDescriptor() + " in class " + node.classStruct.qualifiedName + " couldn't be written.", - IFernflowerLogger.Severity.WARN, - ex); - methodWrapper.decompileError = ex; - buffer.append(" // $VF: Couldn't be decompiled"); + // Must use clashing name if it exists + if (clashingName != null) { + parameterName = clashingName; } - finally { - DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, outerWrapper); + parameterName = methodWrapper.methodStruct.getVariableNamer().renameParameter(mt.getAccessFlags(), type, parameterName, index); + buffer.appendVariable(parameterName, true, true, node.lambdaInformation.content_class_name, node.lambdaInformation.content_method_name, md_content, index, parameterName); + + firstParameter = false; + } + + index += md_content.params[i].stackSize; + } + + if (lambdaParametersNeedParentheses) { + buffer.append(")"); + } + buffer.append(" ->"); + + if (DecompilerContext.getOption(IFernflowerPreferences.INLINE_SIMPLE_LAMBDAS) && methodWrapper.decompileError == null && root != null) { + Statement firstStat = root.getFirst(); + if (firstStat instanceof BasicBlockStatement && firstStat.getExprents() != null && firstStat.getExprents().size() == 1) { + Exprent firstExpr = firstStat.getExprents().get(0); + boolean isVarDefinition = firstExpr instanceof AssignmentExprent && + ((AssignmentExprent)firstExpr).getLeft() instanceof VarExprent && + ((VarExprent)((AssignmentExprent)firstExpr).getLeft()).isDefinition(); + + boolean isThrow = firstExpr instanceof ExitExprent && + ((ExitExprent)firstExpr).getExitType() == ExitExprent.Type.THROW; + + if (!isVarDefinition && !isThrow) { + simpleLambda = true; + MethodWrapper outerWrapper = (MethodWrapper)DecompilerContext.getContextProperty(DecompilerContext.CURRENT_METHOD_WRAPPER); + DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, methodWrapper); + try { + TextBuffer codeBuffer = firstExpr.toJava(indent + 1); + + if (firstExpr instanceof ExitExprent) + codeBuffer.setStart(6); // skip return + else + codeBuffer.prepend(" "); + + codeBuffer.addBytecodeMapping(root.getDummyExit().bytecode); + buffer.append(codeBuffer, node.classStruct.qualifiedName, InterpreterUtil.makeUniqueKey(methodWrapper.methodStruct.getName(), methodWrapper.methodStruct.getDescriptor())); + } + catch (CancelationManager.CanceledException e) { + throw e; + } + catch (Throwable ex) { + DecompilerContext.getLogger().writeMessage("Method " + mt.getName() + " " + mt.getDescriptor() + " in class " + node.classStruct.qualifiedName + " couldn't be written.", + IFernflowerLogger.Severity.WARN, + ex); + methodWrapper.decompileError = ex; + buffer.append(" // $VF: Couldn't be decompiled"); + } + finally { + DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, outerWrapper); + } } } } } } - if (!simpleLambda) { + if ((!simpleLambda && !written) || lambdaToAnonymous) { buffer.append(" {").appendLineSeparator(); methodLambdaToJava(node, wrapper, mt, buffer, indent + 1, !lambdaToAnonymous); @@ -278,8 +329,18 @@ public void classLambdaToJava(ClassNode node, TextBuffer buffer, Exprent method_ DecompilerContext.getLogger().endWriteClass(); } - public void classToJava(ClassNode node, TextBuffer buffer, int indent) { - ClassNode outerNode = (ClassNode)DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASS_NODE); + public void writeClassHeader(StructClass cl, TextBuffer buffer, ImportCollector importCollector) { + int index = cl.qualifiedName.lastIndexOf('/'); + if (index >= 0) { + String packageName = cl.qualifiedName.substring(0, index).replace('/', '.'); + buffer.append("package ").append(packageName).append(';').appendLineSeparator().appendLineSeparator(); + } + + importCollector.writeImports(buffer, true); + } + + public void writeClass(ClassNode node, TextBuffer buffer, int indent) { + ClassNode outerNode = (ClassNode)DecompilerContext.getContextProperty(DecompilerContext.CURRENT_CLASS_NODE); DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_NODE, node); try { @@ -370,14 +431,14 @@ else if (enumFields) { // If the fields after are non enum, readd the fields found scattered throughout the enum for (StructField fd2 : deferredEnumFields) { TextBuffer fieldBuffer = new TextBuffer(); - fieldToJava(wrapper, cl, fd2, fieldBuffer, indent + 1); + writeField(wrapper, cl, fd2, fieldBuffer, indent + 1); fieldBuffer.clearUnassignedBytecodeMappingData(); buffer.append(fieldBuffer); } } TextBuffer fieldBuffer = new TextBuffer(); - fieldToJava(wrapper, cl, fd, fieldBuffer, indent + 1); + writeField(wrapper, cl, fd, fieldBuffer, indent + 1); fieldBuffer.clearUnassignedBytecodeMappingData(); buffer.append(fieldBuffer); @@ -390,7 +451,7 @@ else if (enumFields) { // If we end with enum fields, readd the fields found mixed in for (StructField fd2 : deferredEnumFields) { TextBuffer fieldBuffer = new TextBuffer(); - fieldToJava(wrapper, cl, fd2, fieldBuffer, indent + 1); + writeField(wrapper, cl, fd2, fieldBuffer, indent + 1); fieldBuffer.clearUnassignedBytecodeMappingData(); buffer.append(fieldBuffer); } @@ -406,7 +467,7 @@ else if (enumFields) { if (hide) continue; TextBuffer methodBuffer = new TextBuffer(); - boolean methodSkipped = !methodToJava(node, mt, i, methodBuffer, indent + 1); + boolean methodSkipped = !writeMethod(node, mt, i, methodBuffer, indent + 1); if (!methodSkipped) { if (hasContent) { buffer.appendLineSeparator(); @@ -428,7 +489,7 @@ else if (enumFields) { if (hasContent) { buffer.appendLineSeparator(); } - classToJava(inner, buffer, indent + 1); + writeClass(inner, buffer, indent + 1); hasContent = true; } @@ -550,14 +611,18 @@ private static boolean isGenerated(int flags) { } private void writeClassDefinition(ClassNode node, TextBuffer buffer, int indent) { + boolean markSynthetics = DecompilerContext.getOption(IFernflowerPreferences.MARK_CORRESPONDING_SYNTHETICS); + ClassWrapper wrapper = node.getWrapper(); + StructClass cl = wrapper.getClassStruct(); + if (node.type == ClassNode.Type.ANONYMOUS) { + if (markSynthetics) { + appendSyntheticClassComment(cl, buffer); + } buffer.append(" {").appendLineSeparator(); return; } - ClassWrapper wrapper = node.getWrapper(); - StructClass cl = wrapper.getClassStruct(); - int flags = node.type == ClassNode.Type.ROOT ? cl.getAccessFlags() : node.access; boolean isDeprecated = cl.hasAttribute(StructGeneralAttribute.ATTRIBUTE_DEPRECATED); boolean isSynthetic = (flags & CodeConstants.ACC_SYNTHETIC) != 0 || cl.hasAttribute(StructGeneralAttribute.ATTRIBUTE_SYNTHETIC); @@ -644,7 +709,7 @@ else if (components != null) { else { buffer.append("class "); } - buffer.append(node.simpleName); + buffer.appendClass(node.simpleName, true, cl.qualifiedName); GenericClassDescriptor descriptor = cl.getSignature(); if (descriptor != null && !descriptor.fparameters.isEmpty()) { @@ -664,7 +729,7 @@ else if (components != null) { if (!VarType.VARTYPE_OBJECT.equals(supertype)) { buffer.appendPossibleNewline(" "); buffer.append("extends "); - buffer.append(ExprProcessor.getCastTypeName(descriptor == null ? supertype : descriptor.superclass)); + buffer.appendCastTypeName(descriptor == null ? supertype : descriptor.superclass); } } @@ -680,7 +745,7 @@ else if (components != null) { } if (descriptor == null || descriptor.superinterfaces.size() > i) { - buffer.append(ExprProcessor.getCastTypeName(descriptor == null ? new VarType(cl.getInterface(i), true) : descriptor.superinterfaces.get(i))); + buffer.appendCastTypeName(descriptor == null ? new VarType(cl.getInterface(i), true) : descriptor.superinterfaces.get(i)); } } } @@ -694,12 +759,16 @@ else if (components != null) { buffer.append(","); buffer.appendPossibleNewline(" "); } - buffer.append(ExprProcessor.getCastTypeName(new VarType(permittedSubClasses.get(i), true))); + buffer.appendCastTypeName(new VarType(permittedSubClasses.get(i), true)); } } buffer.popNewlineGroup(); + if (markSynthetics && node.type == ClassNode.Type.LOCAL) { + appendSyntheticClassComment(cl, buffer); + } + buffer.append(" {").appendLineSeparator(); } @@ -719,7 +788,11 @@ private static boolean isSuperClassSealed(StructClass cl) { return false; } - private void fieldToJava(ClassWrapper wrapper, StructClass cl, StructField fd, TextBuffer buffer, int indent) { + public void writeField(ClassWrapper wrapper, StructClass cl, StructField fd, TextBuffer buffer, int indent) { + if (RecordHelper.isHiddenRecordField(cl.getRecordComponents(), fd)) { + return; + } + boolean isInterface = cl.hasModifier(CodeConstants.ACC_INTERFACE); boolean isDeprecated = fd.hasAttribute(StructGeneralAttribute.ATTRIBUTE_DEPRECATED); boolean isEnum = fd.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM); @@ -764,11 +837,11 @@ private void fieldToJava(ClassWrapper wrapper, StructClass cl, StructField fd, T GenericFieldDescriptor descriptor = fieldTypeData.getValue(); if (!isEnum) { - buffer.append(ExprProcessor.getCastTypeName(descriptor == null ? fieldType : descriptor.type)); + buffer.appendCastTypeName(descriptor == null ? fieldType : descriptor.type); buffer.append(' '); } - buffer.append(name); + buffer.appendField(name, true, cl.qualifiedName, name, fd.getDescriptor()); Exprent initializer; if (fd.hasModifier(CodeConstants.ACC_STATIC)) { @@ -817,7 +890,7 @@ private static void methodLambdaToJava(ClassNode lambdaNode, boolean codeOnly) { MethodWrapper methodWrapper = classWrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()); - MethodWrapper outerWrapper = (MethodWrapper)DecompilerContext.getProperty(DecompilerContext.CURRENT_METHOD_WRAPPER); + MethodWrapper outerWrapper = (MethodWrapper)DecompilerContext.getContextProperty(DecompilerContext.CURRENT_METHOD_WRAPPER); DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, methodWrapper); try { @@ -841,21 +914,22 @@ private static void methodLambdaToJava(ClassNode lambdaNode, buffer.append(", "); } - String typeName = ExprProcessor.getCastTypeName(md_content.params[i].copy()); + VarType type = md_content.params[i]; + String typeName = ExprProcessor.getCastTypeName(type); if (ExprProcessor.UNDEFINED_TYPE_STRING.equals(typeName) && DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) { typeName = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT); } - buffer.append(typeName); + buffer.appendCastTypeName(typeName, type); buffer.append(" "); String parameterName = methodWrapper.varproc.getVarName(new VarVersionPair(index, 0)); if (parameterName == null) { parameterName = "param" + index; // null iff decompiled with errors } - parameterName = methodWrapper.methodStruct.getVariableNamer().renameParameter(mt.getAccessFlags(), typeName, parameterName, index); - buffer.append(parameterName); + parameterName = methodWrapper.methodStruct.getVariableNamer().renameParameter(mt.getAccessFlags(), type, parameterName, index); + buffer.appendVariable(parameterName, true, true, classWrapper.getClassStruct().qualifiedName, method_name, md_content, index, parameterName); firstParameter = false; } @@ -876,6 +950,9 @@ private static void methodLambdaToJava(ClassNode lambdaNode, childBuf.addBytecodeMapping(root.getDummyExit().bytecode); buffer.append(childBuf, classWrapper.getClassStruct().qualifiedName, InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor())); } + catch (CancelationManager.CanceledException e) { + throw e; + } catch (Throwable t) { String message = "Method " + mt.getName() + " " + mt.getDescriptor() + " in class " + lambdaNode.classStruct.qualifiedName + " couldn't be written."; DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN, t); @@ -920,7 +997,7 @@ private static String toValidJavaIdentifier(String name) { return res.append("/* $VF was: ").append(name).append("*/").toString(); } - private boolean methodToJava(ClassNode node, StructMethod mt, int methodIndex, TextBuffer buffer, int indent) { + public boolean writeMethod(ClassNode node, StructMethod mt, int methodIndex, TextBuffer buffer, int indent) { ClassWrapper wrapper = node.getWrapper(); StructClass cl = wrapper.getClassStruct(); // Get method by index, this keeps duplicate methods (with the same key) separate @@ -928,7 +1005,7 @@ private boolean methodToJava(ClassNode node, StructMethod mt, int methodIndex, T boolean hideMethod = false; - MethodWrapper outerWrapper = (MethodWrapper)DecompilerContext.getProperty(DecompilerContext.CURRENT_METHOD_WRAPPER); + MethodWrapper outerWrapper = (MethodWrapper)DecompilerContext.getContextProperty(DecompilerContext.CURRENT_METHOD_WRAPPER); DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, methodWrapper); try { @@ -995,13 +1072,24 @@ private boolean methodToJava(ClassNode node, StructMethod mt, int methodIndex, T appendAnnotations(buffer, indent, mt, TypeAnnotation.METHOD_RETURN_TYPE); + StructAnnotationAttribute annotationAttribute = mt.getAttribute(StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS); + boolean shouldApplyOverride = DecompilerContext.getOption(IFernflowerPreferences.OVERRIDE_ANNOTATION) + && mt.getBytecodeVersion().hasOverride() + && !CodeConstants.INIT_NAME.equals(mt.getName()) + && !CodeConstants.CLINIT_NAME.equals(mt.getName()) + && !mt.hasModifier(CodeConstants.ACC_STATIC) + && !mt.hasModifier(CodeConstants.ACC_PRIVATE); + // Try append @Override after all other annotations - if (DecompilerContext.getOption(IFernflowerPreferences.OVERRIDE_ANNOTATION) && mt.getBytecodeVersion().hasOverride() && !CodeConstants.INIT_NAME.equals(mt.getName()) && !CodeConstants.CLINIT_NAME.equals(mt.getName()) && !mt.hasModifier(CodeConstants.ACC_STATIC) && !mt.hasModifier(CodeConstants.ACC_PRIVATE)) { + if (shouldApplyOverride) { // Search superclasses for methods that match the name and descriptor of this one. // Make sure not to search the current class otherwise it will return the current method itself! // TODO: record overrides boolean isOverride = searchForMethod(cl, mt.getName(), md, false); - if (isOverride) { + boolean alreadyHasOverride = annotationAttribute != null && annotationAttribute.getAnnotations() + .stream().anyMatch(annotation -> "java/lang/Override".equals(annotation.getClassName())); + + if (isOverride && !alreadyHasOverride) { buffer.appendIndent(indent); buffer.append("@Override"); buffer.appendLineSeparator(); @@ -1045,11 +1133,11 @@ private boolean methodToJava(ClassNode node, StructMethod mt, int methodIndex, T } if (!init) { - buffer.append(ExprProcessor.getCastTypeName(descriptor == null ? md.ret : descriptor.returnType)); + buffer.appendCastTypeName(descriptor == null ? md.ret : descriptor.returnType); buffer.append(' '); } - buffer.append(toValidJavaIdentifier(name)); + buffer.appendMethod(toValidJavaIdentifier(name), true, cl.qualifiedName, mt.getName(), md); buffer.append('('); List mask = methodWrapper.synthParameters; @@ -1109,7 +1197,7 @@ else if (methodWrapper.varproc.getVarFinal(new VarVersionPair(index, 0)) == VarT DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) { typeName = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT); } - buffer.append(typeName); + buffer.appendCastTypeName(typeName, parameterType); if (isVarArg) { buffer.append("..."); } @@ -1117,20 +1205,23 @@ else if (methodWrapper.varproc.getVarFinal(new VarVersionPair(index, 0)) == VarT buffer.append(' '); String parameterName; - if (methodParameters != null && i < methodParameters.size()) { + String clashingName = methodWrapper.varproc.getClashingName(new VarVersionPair(index, 0)); + if (clashingName != null) { + parameterName = clashingName; + } else if (methodParameters != null && i < methodParameters.size()) { parameterName = methodParameters.get(i).myName; - } - else { + } else { parameterName = methodWrapper.varproc.getVarName(new VarVersionPair(index, 0)); } - String newParameterName = methodWrapper.methodStruct.getVariableNamer().renameParameter(flags, typeName, parameterName, index); + String newParameterName = methodWrapper.methodStruct.getVariableNamer().renameParameter(flags, parameterType, parameterName, index); if ((flags & (CodeConstants.ACC_ABSTRACT | CodeConstants.ACC_NATIVE)) != 0 && Objects.equals(newParameterName, parameterName)) { newParameterName = DecompilerContext.getStructContext().renameAbstractParameter(methodWrapper.methodStruct.getClassQualifiedName(), mt.getName(), mt.getDescriptor(), index - (((flags & CodeConstants.ACC_STATIC) == 0) ? 1 : 0), parameterName); } parameterName = newParameterName; - buffer.append(parameterName == null ? "param" + index : parameterName); // null iff decompiled with errors + buffer.appendVariable(parameterName == null ? "param" + index : parameterName, // null iff decompiled with errors + true, true, cl.qualifiedName, mt.getName(), md, index, parameterName); paramCount++; } @@ -1156,7 +1247,7 @@ else if (methodWrapper.varproc.getVarFinal(new VarVersionPair(index, 0)) == VarT buffer.append(", "); } VarType type = useDescriptor ? descriptor.exceptionTypes.get(i) : new VarType(attr.getExcClassname(i, cl.getPool()), true); - buffer.append(ExprProcessor.getCastTypeName(type)); + buffer.appendCastTypeName(type); } } } @@ -1195,6 +1286,9 @@ else if (methodWrapper.varproc.getVarFinal(new VarVersionPair(index, 0)) == VarT buffer.append(code, cl.qualifiedName, InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor())); } } + catch (CancelationManager.CanceledException e) { + throw e; + } catch (Throwable t) { String message = "Method " + mt.getName() + " " + mt.getDescriptor() + " in class " + node.classStruct.qualifiedName + " couldn't be written."; DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN, t); @@ -1287,7 +1381,7 @@ public static void collectErrorLines(Throwable error, List lines) { } private static void collectBytecode(MethodWrapper wrapper, List lines) throws IOException { - ClassNode classNode = (ClassNode)DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASS_NODE); + ClassNode classNode = (ClassNode)DecompilerContext.getContextProperty(DecompilerContext.CURRENT_CLASS_NODE); StructMethod method = wrapper.methodStruct; InstructionSequence instructions = method.getInstructionSequence(); if (instructions == null) { @@ -1411,15 +1505,15 @@ private static boolean hideConstructor(ClassNode node, boolean init, boolean thr } ClassWrapper wrapper = node.getWrapper(); - StructClass cl = wrapper.getClassStruct(); + StructClass cl = wrapper.getClassStruct(); - int classAccessFlags = node.type == ClassNode.Type.ROOT ? cl.getAccessFlags() : node.access; + int classAccessFlags = node.type == ClassNode.Type.ROOT ? cl.getAccessFlags() : node.access; boolean isEnum = cl.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM); // default constructor requires same accessibility flags. Exception: enum constructor which is always private - if(!isEnum && ((classAccessFlags & ACCESSIBILITY_FLAGS) != (methodAccessFlags & ACCESSIBILITY_FLAGS))) { - return false; - } + if(!isEnum && ((classAccessFlags & ACCESSIBILITY_FLAGS) != (methodAccessFlags & ACCESSIBILITY_FLAGS))) { + return false; + } int count = 0; for (StructMethod mt : cl.getMethods()) { @@ -1441,8 +1535,8 @@ private static Map.Entry getFieldTypeData(Struc } private static boolean containsDeprecatedAnnotation(StructMember mb) { - for (StructGeneralAttribute.Key key : ANNOTATION_ATTRIBUTES) { - StructAnnotationAttribute attribute = (StructAnnotationAttribute) mb.getAttribute(key); + for (Key key : ANNOTATION_ATTRIBUTES) { + StructAnnotationAttribute attribute = mb.getAttribute((Key) key); if (attribute != null) { for (AnnotationExprent annotation : attribute.getAnnotations()) { if (annotation.getClassName().equals("java/lang/Deprecated")) { @@ -1516,7 +1610,7 @@ public static List getErrorComment() { private static void appendComment(TextBuffer buffer, String comment, int indent) { buffer.appendIndent(indent).append("// $VF: ").append(comment).appendLineSeparator(); } - + private static void appendJavadoc(TextBuffer buffer, String javaDoc, int indent) { if (javaDoc == null) return; buffer.appendIndent(indent).append("/**").appendLineSeparator(); @@ -1526,23 +1620,29 @@ private static void appendJavadoc(TextBuffer buffer, String javaDoc, int indent) buffer.appendIndent(indent).append(" */").appendLineSeparator(); } - static final StructGeneralAttribute.Key[] ANNOTATION_ATTRIBUTES = { + public static void appendSyntheticClassComment(StructClass cl, TextBuffer buffer) { + String className = cl.qualifiedName.substring(cl.qualifiedName.lastIndexOf("/") + 1); + buffer.append(" /* ").appendClass(className, true, cl.qualifiedName).append(" */"); + } + + static final Key[] ANNOTATION_ATTRIBUTES = { StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS, StructGeneralAttribute.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS}; - static final StructGeneralAttribute.Key[] PARAMETER_ANNOTATION_ATTRIBUTES = { + static final Key[] PARAMETER_ANNOTATION_ATTRIBUTES = { StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, StructGeneralAttribute.ATTRIBUTE_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS}; - static final StructGeneralAttribute.Key[] TYPE_ANNOTATION_ATTRIBUTES = { + static final Key[] TYPE_ANNOTATION_ATTRIBUTES = { StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_TYPE_ANNOTATIONS, StructGeneralAttribute.ATTRIBUTE_RUNTIME_INVISIBLE_TYPE_ANNOTATIONS}; static void appendAnnotations(TextBuffer buffer, int indent, StructMember mb, int targetType) { Set filter = new HashSet<>(); - for (StructGeneralAttribute.Key key : ANNOTATION_ATTRIBUTES) { - StructAnnotationAttribute attribute = (StructAnnotationAttribute)mb.getAttribute(key); + for (Key key : ANNOTATION_ATTRIBUTES) { + StructAnnotationAttribute attribute = mb.getAttribute((Key)key); if (attribute != null) { for (AnnotationExprent annotation : attribute.getAnnotations()) { - String text = annotation.toJava(indent).convertToStringAndAllowDataDiscard(); - filter.add(text); - buffer.append(text); + buffer.appendIndent(indent); + TextBuffer text = annotation.toJava(indent); + filter.add(text.convertToStringAndAllowDataDiscard()); + buffer.appendText(text); if (indent < 0) { buffer.append(' '); } @@ -1605,15 +1705,15 @@ private static boolean searchForMethod(StructClass cl, String name, MethodDescri private static void appendParameterAnnotations(TextBuffer buffer, StructMethod mt, int param) { Set filter = new HashSet<>(); - for (StructGeneralAttribute.Key key : PARAMETER_ANNOTATION_ATTRIBUTES) { - StructAnnotationParameterAttribute attribute = (StructAnnotationParameterAttribute)mt.getAttribute(key); + for (Key key : PARAMETER_ANNOTATION_ATTRIBUTES) { + StructAnnotationParameterAttribute attribute = mt.getAttribute((Key) key); if (attribute != null) { List> annotations = attribute.getParamAnnotations(); if (param < annotations.size()) { for (AnnotationExprent annotation : annotations.get(param)) { - String text = annotation.toJava(-1).convertToStringAndAllowDataDiscard(); - filter.add(text); - buffer.append(text).append(' '); + TextBuffer text = annotation.toJava(-1); + filter.add(text.convertToStringAndAllowDataDiscard()); + buffer.appendText(text).append(' '); } } } @@ -1623,14 +1723,15 @@ private static void appendParameterAnnotations(TextBuffer buffer, StructMethod m } private static void appendTypeAnnotations(TextBuffer buffer, int indent, StructMember mb, int targetType, int index, Set filter) { - for (StructGeneralAttribute.Key key : TYPE_ANNOTATION_ATTRIBUTES) { - StructTypeAnnotationAttribute attribute = (StructTypeAnnotationAttribute)mb.getAttribute(key); + for (Key key : TYPE_ANNOTATION_ATTRIBUTES) { + StructTypeAnnotationAttribute attribute = mb.getAttribute((Key) key); if (attribute != null) { for (TypeAnnotation annotation : attribute.getAnnotations()) { if (annotation.isTopLevel() && annotation.getTargetType() == targetType && (index < 0 || annotation.getIndex() == index)) { - String text = annotation.getAnnotation().toJava(indent).convertToStringAndAllowDataDiscard(); - if (!filter.contains(text)) { - buffer.append(text); + TextBuffer text = annotation.getAnnotation().toJava(indent); + if (!filter.contains(text.convertToStringAndAllowDataDiscard())) { + buffer.appendIndent(indent); + buffer.appendText(text); if (indent < 0) { buffer.append(' '); } @@ -1704,10 +1805,10 @@ public static void appendTypeParameters(TextBuffer buffer, List paramete List parameterBounds = bounds.get(i); if (parameterBounds.size() > 1 || !"java/lang/Object".equals(parameterBounds.get(0).value)) { buffer.append(" extends "); - buffer.append(ExprProcessor.getCastTypeName(parameterBounds.get(0))); + buffer.appendCastTypeName(parameterBounds.get(0)); for (int j = 1; j < parameterBounds.size(); j++) { buffer.append(" & "); - buffer.append(ExprProcessor.getCastTypeName(parameterBounds.get(j))); + buffer.appendCastTypeName(parameterBounds.get(j)); } } } diff --git a/src/org/jetbrains/java/decompiler/main/ClassesProcessor.java b/src/org/jetbrains/java/decompiler/main/ClassesProcessor.java index e7fa05926d..287ce0f5cd 100644 --- a/src/org/jetbrains/java/decompiler/main/ClassesProcessor.java +++ b/src/org/jetbrains/java/decompiler/main/ClassesProcessor.java @@ -1,14 +1,18 @@ // Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.main; +import org.jetbrains.java.decompiler.api.plugin.StatementWriter; +import org.jetbrains.java.decompiler.api.plugin.LanguageSpec; import org.jetbrains.java.decompiler.code.CodeConstants; import org.jetbrains.java.decompiler.code.Instruction; import org.jetbrains.java.decompiler.code.InstructionSequence; import org.jetbrains.java.decompiler.main.collectors.BytecodeSourceMapper; import org.jetbrains.java.decompiler.main.collectors.ImportCollector; +import org.jetbrains.java.decompiler.main.decompiler.CancelationManager; import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; import org.jetbrains.java.decompiler.main.extern.IIdentifierRenamer; +import org.jetbrains.java.decompiler.main.plugins.PluginContext; import org.jetbrains.java.decompiler.main.rels.ClassWrapper; import org.jetbrains.java.decompiler.main.rels.LambdaProcessor; import org.jetbrains.java.decompiler.main.rels.NestedClassProcessor; @@ -417,14 +421,22 @@ public void processClass(StructClass cl) throws IOException { // add simple class names to implicit import addClassNameToImport(root, importCollector); + LanguageSpec spec = PluginContext.getCurrentContext().getLanguageSpec(cl); + // build wrappers for all nested classes (that's where actual processing takes place) - initWrappers(root); + initWrappers(root, spec); - // Java specific last minute processing - new NestedClassProcessor().processClass(root, root); + if (spec == null) { + // Java specific last minute processing + new NestedClassProcessor().processClass(root, root); - new NestedMemberAccess().propagateMemberAccess(root); + new NestedMemberAccess().propagateMemberAccess(root); + } } + } catch (CancelationManager.CanceledException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); } finally { DecompilerContext.getLogger().endProcessingClass(); } @@ -455,9 +467,11 @@ else if (moduleInfo) { buffer.append(moduleBuffer); } else { + LanguageSpec spec = PluginContext.getCurrentContext().getLanguageSpec(cl); TextBuffer classBuffer = new TextBuffer(AVERAGE_CLASS_SIZE); + StatementWriter writer = spec != null ? spec.writer : new ClassWriter(); - new ClassWriter().classToJava(root, classBuffer, 0); + writer.writeClass(root, classBuffer, 0); classBuffer.reformat(); classBuffer.getTracers().forEach((classAndMethod, tracer) -> { @@ -475,14 +489,7 @@ else if (moduleInfo) { } }); - int index = cl.qualifiedName.lastIndexOf('/'); - if (index >= 0) { - String packageName = cl.qualifiedName.substring(0, index).replace('/', '.'); - buffer.append("package ").append(packageName).append(';').appendLineSeparator().appendLineSeparator(); - } - - DecompilerContext.getImportCollector().writeImports(buffer, true); - + writer.writeClassHeader(cl, buffer, DecompilerContext.getImportCollector()); int offsetLines = buffer.countLines(); buffer.append(classBuffer); @@ -499,14 +506,15 @@ else if (moduleInfo) { } } } - } - finally { + } catch (CancelationManager.CanceledException e) { + throw e; + } finally { destroyWrappers(root); DecompilerContext.getLogger().endReadingClass(); } } - private static void initWrappers(ClassNode node) { + private static void initWrappers(ClassNode node, LanguageSpec spec) { if (node.type == ClassNode.Type.LAMBDA) { return; } @@ -515,18 +523,18 @@ private static void initWrappers(ClassNode node) { for (ClassNode nd : node.nested) { if (shouldInitEarly(nd)) { - initWrappers(nd); + initWrappers(nd, spec); nestedCopy.remove(nd); } } ClassWrapper wrapper = new ClassWrapper(node.classStruct); - wrapper.init(); + wrapper.init(spec); node.wrapper = wrapper; for (ClassNode nd : nestedCopy) { - initWrappers(nd); + initWrappers(nd, spec); } } diff --git a/src/org/jetbrains/java/decompiler/main/DecompilerContext.java b/src/org/jetbrains/java/decompiler/main/DecompilerContext.java index b63e827bca..70a5fe4ce4 100644 --- a/src/org/jetbrains/java/decompiler/main/DecompilerContext.java +++ b/src/org/jetbrains/java/decompiler/main/DecompilerContext.java @@ -1,33 +1,39 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.main; +import org.jetbrains.annotations.Nullable; import org.jetbrains.java.decompiler.main.collectors.BytecodeSourceMapper; import org.jetbrains.java.decompiler.main.collectors.CounterContainer; import org.jetbrains.java.decompiler.main.collectors.ImportCollector; import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.main.rels.ClassWrapper; +import org.jetbrains.java.decompiler.main.rels.MethodWrapper; import org.jetbrains.java.decompiler.modules.decompiler.vars.VarProcessor; import org.jetbrains.java.decompiler.main.extern.IVariableNamingFactory; import org.jetbrains.java.decompiler.modules.renamer.PoolInterceptor; +import org.jetbrains.java.decompiler.util.Key; +import org.jetbrains.java.decompiler.struct.StructClass; import org.jetbrains.java.decompiler.struct.StructContext; +import java.util.HashMap; import java.util.Map; import java.util.Objects; public class DecompilerContext { - public static final String CURRENT_CLASS = "CURRENT_CLASS"; - public static final String CURRENT_CLASS_WRAPPER = "CURRENT_CLASS_WRAPPER"; - public static final String CURRENT_CLASS_NODE = "CURRENT_CLASS_NODE"; - public static final String CURRENT_METHOD_WRAPPER = "CURRENT_METHOD_WRAPPER"; - public static final String CURRENT_VAR_PROCESSOR = "CURRENT_VAR_PROCESSOR"; - public static final String RENAMER_FACTORY = "RENAMER_FACTORY"; + public static final Key CURRENT_CLASS = Key.of("CURRENT_CLASS"); + public static final Key CURRENT_CLASS_WRAPPER = Key.of("CURRENT_CLASS_WRAPPER"); + public static final Key CURRENT_CLASS_NODE = Key.of("CURRENT_CLASS_NODE"); + public static final Key CURRENT_METHOD_WRAPPER = Key.of("CURRENT_METHOD_WRAPPER"); + public static final Key CURRENT_VAR_PROCESSOR = Key.of("CURRENT_VAR_PROCESSOR"); + public final Map, Object> staticProps = new HashMap<>(); public final Map properties; public final IFernflowerLogger logger; public final StructContext structContext; public final ClassesProcessor classProcessor; public final PoolInterceptor poolInterceptor; - public final IVariableNamingFactory renamerFactory; + public IVariableNamingFactory renamerFactory; private ImportCollector importCollector; private VarProcessor varProcessor; private CounterContainer counterContainer; @@ -37,8 +43,7 @@ public DecompilerContext(Map properties, IFernflowerLogger logger, StructContext structContext, ClassesProcessor classProcessor, - PoolInterceptor interceptor, - IVariableNamingFactory renamerFactory) { + PoolInterceptor interceptor) { Objects.requireNonNull(properties); Objects.requireNonNull(logger); Objects.requireNonNull(structContext); @@ -49,7 +54,6 @@ public DecompilerContext(Map properties, this.structContext = structContext; this.classProcessor = classProcessor; this.poolInterceptor = interceptor; - this.renamerFactory = renamerFactory; this.counterContainer = new CounterContainer(); } @@ -71,6 +75,10 @@ public static void setCurrentContext(DecompilerContext context) { } } + public static void setProperty(Key key, T value) { + getCurrentContext().staticProps.put(key, value); + } + public static void setProperty(String key, Object value) { getCurrentContext().properties.put(key, value); } @@ -88,10 +96,18 @@ public static void startMethod(VarProcessor varProcessor) { context.counterContainer = new CounterContainer(); } + public static void setImportCollector(ImportCollector importCollector) { + getCurrentContext().importCollector = importCollector; + } + // ***************************************************************************** // context access // ***************************************************************************** + public static @Nullable T getContextProperty(Key key) { + return (T) getCurrentContext().staticProps.get(key); + } + public static Object getProperty(String key) { return getCurrentContext().properties.get(key); } diff --git a/src/org/jetbrains/java/decompiler/main/Fernflower.java b/src/org/jetbrains/java/decompiler/main/Fernflower.java index 86ea353b7e..5c5f2e171c 100644 --- a/src/org/jetbrains/java/decompiler/main/Fernflower.java +++ b/src/org/jetbrains/java/decompiler/main/Fernflower.java @@ -1,22 +1,28 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.main; +import java.io.IOException; +import org.jetbrains.java.decompiler.api.plugin.Plugin; import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; +import org.jetbrains.java.decompiler.main.decompiler.OptionParser; import org.jetbrains.java.decompiler.main.extern.*; +import org.jetbrains.java.decompiler.main.plugins.JarPluginLoader; +import org.jetbrains.java.decompiler.api.plugin.PluginSource; +import org.jetbrains.java.decompiler.main.plugins.PluginSources; import org.jetbrains.java.decompiler.modules.renamer.ConverterHelper; import org.jetbrains.java.decompiler.modules.renamer.IdentifierConverter; import org.jetbrains.java.decompiler.modules.renamer.PoolInterceptor; import org.jetbrains.java.decompiler.struct.IDecompiledData; +import org.jetbrains.java.decompiler.main.plugins.PluginContext; import org.jetbrains.java.decompiler.struct.StructClass; import org.jetbrains.java.decompiler.struct.StructContext; -import org.jetbrains.java.decompiler.struct.lazy.LazyLoader; +import org.jetbrains.java.decompiler.struct.attr.StructGeneralAttribute; import org.jetbrains.java.decompiler.util.ClasspathScanner; -import org.jetbrains.java.decompiler.util.JADNameProvider; import org.jetbrains.java.decompiler.util.JrtFinder; import org.jetbrains.java.decompiler.util.TextBuffer; +import org.jetbrains.java.decompiler.util.token.TextTokenDumpVisitor; import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -37,7 +43,14 @@ public Fernflower(IResultSaver saver, Map customProperties, IFer public Fernflower(IBytecodeProvider provider, IResultSaver saver, Map customProperties, IFernflowerLogger logger) { Map properties = new HashMap<>(IFernflowerPreferences.DEFAULTS); if (customProperties != null) { - properties.putAll(customProperties); + for (Map.Entry entry : customProperties.entrySet()) { + if (entry.getKey().length() == 3) { + // Short name, reparse to long name + OptionParser.parseShort("-" + entry.getKey() + "=" + entry.getValue(), properties); + } else { + properties.put(entry.getKey(), entry.getValue()); + } + } } String level = (String)properties.get(IFernflowerPreferences.LOG_LEVEL); @@ -62,26 +75,22 @@ public Fernflower(IBytecodeProvider provider, IResultSaver saver, Map new TextTokenDumpVisitor(next, buffer))); + } else { + buffer.visitTokens(TextTokenVisitor.createVisitor()); + } + String res = buffer.convertToStringAndAllowDataDiscard(); if (res == null) { return "$ VF: Unable to decompile class " + cl.qualifiedName; @@ -202,4 +218,8 @@ public String getClassContent(StructClass cl) { } } } + + static { + Init.init(); + } } \ No newline at end of file diff --git a/src/org/jetbrains/java/decompiler/main/IdentityRenamerFactory.java b/src/org/jetbrains/java/decompiler/main/IdentityRenamerFactory.java index 4b62af8a05..d49b0ed8ff 100644 --- a/src/org/jetbrains/java/decompiler/main/IdentityRenamerFactory.java +++ b/src/org/jetbrains/java/decompiler/main/IdentityRenamerFactory.java @@ -21,6 +21,8 @@ import org.jetbrains.java.decompiler.main.extern.IVariableNamingFactory; import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPair; import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.Pair; public class IdentityRenamerFactory implements IVariableNamingFactory, IVariableNameProvider { @Override @@ -29,12 +31,7 @@ public IVariableNameProvider createFactory(StructMethod method) { } @Override - public String renameAbstractParameter(String abstractParam, int index) { - return abstractParam; - } - - @Override - public Map rename(Map variables) { + public Map rename(Map> variables) { return null; } diff --git a/src/org/jetbrains/java/decompiler/main/Init.java b/src/org/jetbrains/java/decompiler/main/Init.java new file mode 100644 index 0000000000..ecd380f9ff --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/Init.java @@ -0,0 +1,21 @@ +package org.jetbrains.java.decompiler.main; + +import org.jetbrains.java.decompiler.main.plugins.JarPluginLoader; +import org.jetbrains.java.decompiler.struct.attr.StructGeneralAttribute; + +public final class Init { + private static boolean initialized = false; + public static void init() { + if (initialized) { + return; + } + + initialized = true; + + // Load all Java code attributes + StructGeneralAttribute.init(); + + // Class-load all plugins that potentially could be included in the jar + JarPluginLoader.init(); + } +} diff --git a/src/org/jetbrains/java/decompiler/main/InitializerProcessor.java b/src/org/jetbrains/java/decompiler/main/InitializerProcessor.java index 458d624b9e..c6f1410a89 100644 --- a/src/org/jetbrains/java/decompiler/main/InitializerProcessor.java +++ b/src/org/jetbrains/java/decompiler/main/InitializerProcessor.java @@ -3,6 +3,7 @@ import org.jetbrains.java.decompiler.code.CodeConstants; import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; +import org.jetbrains.java.decompiler.main.decompiler.CancelationManager; import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; import org.jetbrains.java.decompiler.main.rels.ClassWrapper; @@ -16,6 +17,7 @@ import org.jetbrains.java.decompiler.struct.StructClass; import org.jetbrains.java.decompiler.struct.StructField; import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.gen.CodeType; import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; import org.jetbrains.java.decompiler.struct.gen.VarType; import org.jetbrains.java.decompiler.util.InterpreterUtil; @@ -29,6 +31,8 @@ public static void extractInitializers(ClassWrapper wrapper) { if (method != null && method.root != null) { // successfully decompiled static constructor extractStaticInitializers(wrapper, method); } + } catch (CancelationManager.CanceledException e) { + throw e; } catch (Throwable t) { StructMethod mt = method.methodStruct; String message = "Method " + mt.getName() + " " + mt.getDescriptor() + " in class " + wrapper.getClassStruct().qualifiedName + " couldn't be written."; @@ -112,7 +116,7 @@ private static void hideEmptySuper(ClassWrapper wrapper) { continue; } VarType type = invExpr.getDescriptor().params[i]; - if (type.type == CodeConstants.TYPE_OBJECT) { + if (type.type == CodeType.OBJECT) { ClassNode node = DecompilerContext.getClassProcessor().getMapRootClasses().get(type.value); if (node != null && (node.type == ClassNode.Type.ANONYMOUS || (node.access & CodeConstants.ACC_SYNTHETIC) != 0)) { break; // Should be last @@ -142,7 +146,7 @@ public static void hideInitalizers(ClassWrapper wrapper) { MethodDescriptor md = MethodDescriptor.parseDescriptor(desc); if (md.params.length > 0) { VarType type = md.params[md.params.length - 1]; - if (type.type == CodeConstants.TYPE_OBJECT) { + if (type.type == CodeType.OBJECT) { ClassNode node = DecompilerContext.getClassProcessor().getMapRootClasses().get(type.value); if (node != null && ((node.type == ClassNode.Type.ANONYMOUS) || (node.access & CodeConstants.ACC_SYNTHETIC) != 0)) { //TODO: Verify that the body is JUST a this([args]) call? @@ -190,6 +194,8 @@ private static void extractStaticInitializers(ClassWrapper wrapper, MethodWrappe } } + List notInlined = new ArrayList<>(); + Iterator itr = firstData.getExprents().iterator(); while (itr.hasNext()) { Exprent exprent = itr.next(); @@ -203,7 +209,7 @@ private static void extractStaticInitializers(ClassWrapper wrapper, MethodWrappe // interfaces fields should always be initialized inline String keyField = InterpreterUtil.makeUniqueKey(fExpr.getName(), fExpr.getDescriptor().descriptorString); - boolean exprentIndependent = isExprentIndependent(fExpr, assignExpr.getRight(), method, cl, whitelist, multiAssign, cl.getFields().getIndexByKey(keyField), true); + boolean exprentIndependent = isExprentIndependent(fExpr, assignExpr.getRight(), method, cl, whitelist, multiAssign, notInlined, cl.getFields().getIndexByKey(keyField), true); if (inlineInitializers || exprentIndependent) { if (!wrapper.getStaticFieldInitializers().containsKey(keyField)) { if (exprentIndependent) { @@ -233,6 +239,8 @@ private static void extractStaticInitializers(ClassWrapper wrapper, MethodWrappe } } } + } else { + notInlined.add(fExpr); } } } else if (inlineInitializers) { @@ -324,7 +332,7 @@ private static void extractDynamicInitializers(ClassWrapper wrapper) { String fieldKey = InterpreterUtil.makeUniqueKey(fExpr.getName(), fExpr.getDescriptor().descriptorString); int fidx = cl.getFields().getIndexByKey(fieldKey); - if (prev_fidx <= fidx && isExprentIndependent(fExpr, assignExpr.getRight(), lstMethodWrappers.get(i), cl, whitelist, new ArrayList<>() /* TODO */, fidx, false)) { + if (prev_fidx <= fidx && isExprentIndependent(fExpr, assignExpr.getRight(), lstMethodWrappers.get(i), cl, whitelist, new ArrayList<>() /* TODO */, new ArrayList<>(), fidx, false)) { prev_fidx = fidx; if (fieldWithDescr == null) { fieldWithDescr = fieldKey; @@ -402,10 +410,9 @@ private static Exprent processBoxingCast(Exprent expr) { return expr; } - private static boolean isExprentIndependent(FieldExprent field, Exprent exprent, MethodWrapper method, StructClass cl, Set whitelist, List multiAssign, int fidx, boolean isStatic) { + private static boolean isExprentIndependent(FieldExprent field, Exprent exprent, MethodWrapper method, StructClass cl, Set whitelist, List multiAssign, List notInlined, int fidx, boolean isStatic) { String keyField = InterpreterUtil.makeUniqueKey(field.getName(), field.getDescriptor().descriptorString); - List lst = exprent.getAllExprents(true); - lst.add(exprent); + List lst = exprent.getAllExprents(true, true); for (Exprent expr : lst) { switch (expr.type) { @@ -420,6 +427,10 @@ private static boolean isExprentIndependent(FieldExprent field, Exprent exprent, break; case FIELD: FieldExprent fexpr = (FieldExprent)expr; + if (notInlined.contains(fexpr)) { + return false; + } + if (cl.hasField(fexpr.getName(), fexpr.getDescriptor().descriptorString)) { String key = InterpreterUtil.makeUniqueKey(fexpr.getName(), fexpr.getDescriptor().descriptorString); if (isStatic) { @@ -428,7 +439,6 @@ private static boolean isExprentIndependent(FieldExprent field, Exprent exprent, return false; } - // There is a very stupid section of the JLS if (!fexpr.isStatic()) { return false; } else if (cl.getFields().getIndexByKey(key) >= fidx) { diff --git a/src/org/jetbrains/java/decompiler/main/RecordHelper.java b/src/org/jetbrains/java/decompiler/main/RecordHelper.java index 6f426f0449..eb1b591ff1 100644 --- a/src/org/jetbrains/java/decompiler/main/RecordHelper.java +++ b/src/org/jetbrains/java/decompiler/main/RecordHelper.java @@ -1,19 +1,23 @@ package org.jetbrains.java.decompiler.main; import org.jetbrains.java.decompiler.code.CodeConstants; -import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor; +import org.jetbrains.java.decompiler.main.rels.MethodWrapper; import org.jetbrains.java.decompiler.modules.decompiler.exps.*; +import org.jetbrains.java.decompiler.modules.decompiler.flow.DirectGraph; import org.jetbrains.java.decompiler.modules.decompiler.stats.BasicBlockStatement; import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; import org.jetbrains.java.decompiler.modules.decompiler.stats.Statement; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPair; import org.jetbrains.java.decompiler.struct.*; import org.jetbrains.java.decompiler.struct.attr.StructAnnotationAttribute; import org.jetbrains.java.decompiler.struct.attr.StructAnnotationParameterAttribute; import org.jetbrains.java.decompiler.struct.attr.StructGeneralAttribute; import org.jetbrains.java.decompiler.struct.attr.StructTypeAnnotationAttribute; import org.jetbrains.java.decompiler.struct.consts.LinkConstant; +import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; import org.jetbrains.java.decompiler.struct.gen.VarType; import org.jetbrains.java.decompiler.struct.gen.generics.GenericFieldDescriptor; +import org.jetbrains.java.decompiler.util.Key; import org.jetbrains.java.decompiler.util.TextBuffer; import java.util.*; @@ -26,6 +30,20 @@ public static boolean isHiddenRecordMethod(StructClass cl, StructMethod mt, Root (mt.getName().equals(CodeConstants.INIT_NAME) && !hasAnnotations(mt) && isDefaultRecordConstructor(cl, root)); } + public static boolean isHiddenRecordField(List components, StructField fd) { + if (components == null) { + return false; + } + + for (StructRecordComponent component : components) { + if (component.getName().equals(fd.getName()) && component.getDescriptor().equals(fd.getDescriptor()) && !fd.hasModifier(CodeConstants.ACC_STATIC)) { + return true; + } + } + + return false; + } + public static void appendRecordComponents(TextBuffer buffer, StructClass cl, List components, int indent) { buffer.pushNewlineGroup(indent, 1); buffer.appendPossibleNewline(); @@ -119,58 +137,65 @@ private static boolean isVarArgRecord(StructClass cl) { return init != null && init.hasModifier(CodeConstants.ACC_VARARGS); } - private static Set getRecordComponentAnnotations(StructClass cl, StructRecordComponent cd, int param) { + private static Set getRecordComponentAnnotations(StructClass cl, StructRecordComponent cd, int param) { Set annotations = new LinkedHashSet<>(); + Set buffers = new LinkedHashSet<>(); List members = new ArrayList<>(); members.add(cd); StructMethod getter = getGetter(cl, cd); if (getter != null) members.add(getter); for (StructMember member : members) { - for (StructGeneralAttribute.Key key : ClassWriter.ANNOTATION_ATTRIBUTES) { - StructAnnotationAttribute attribute = (StructAnnotationAttribute) member.getAttribute(key); + for (Key key : ClassWriter.ANNOTATION_ATTRIBUTES) { + StructAnnotationAttribute attribute = member.getAttribute((Key) key); if (attribute == null) continue; for (AnnotationExprent annotation : attribute.getAnnotations()) { - String text = annotation.toJava(-1).convertToStringAndAllowDataDiscard(); - annotations.add(text); + TextBuffer text = annotation.toJava(-1); + if (annotations.add(text.convertToStringAndAllowDataDiscard())) { + buffers.add(text); + } } } - for (StructGeneralAttribute.Key key : ClassWriter.TYPE_ANNOTATION_ATTRIBUTES) { - StructTypeAnnotationAttribute attribute = (StructTypeAnnotationAttribute) member.getAttribute(key); + for (Key key : ClassWriter.TYPE_ANNOTATION_ATTRIBUTES) { + StructTypeAnnotationAttribute attribute = member.getAttribute((Key) key); if (attribute == null) continue; for (TypeAnnotation annotation : attribute.getAnnotations()) { if (!annotation.isTopLevel()) continue; int type = annotation.getTargetType(); if (type == TypeAnnotation.FIELD || type == TypeAnnotation.METHOD_PARAMETER) { - String text = annotation.getAnnotation().toJava(-1).convertToStringAndAllowDataDiscard(); - annotations.add(text); + TextBuffer text = annotation.getAnnotation().toJava(-1); + if (annotations.add(text.convertToStringAndAllowDataDiscard())) { + buffers.add(text); + } } } } } StructMember constr = getCanonicalConstructor(cl); - if (constr == null) return annotations; + if (constr == null) return buffers; - for (StructGeneralAttribute.Key key : ClassWriter.PARAMETER_ANNOTATION_ATTRIBUTES) { - StructAnnotationParameterAttribute attribute = (StructAnnotationParameterAttribute) constr.getAttribute(key); + for (Key key : ClassWriter.PARAMETER_ANNOTATION_ATTRIBUTES) { + StructAnnotationParameterAttribute attribute = constr.getAttribute((Key)key); if (attribute == null) continue; List> paramAnnotations = attribute.getParamAnnotations(); if (param >= paramAnnotations.size()) continue; for (AnnotationExprent annotation : paramAnnotations.get(param)) { - String text = annotation.toJava(-1).convertToStringAndAllowDataDiscard(); - annotations.add(text); + TextBuffer text = annotation.toJava(-1); + if (annotations.add(text.convertToStringAndAllowDataDiscard())) { + buffers.add(text); + } } } - return annotations; + return buffers; } private static void recordComponentToJava(TextBuffer buffer, StructClass cl, StructRecordComponent cd, int param, boolean varArgComponent) { - Set annotations = getRecordComponentAnnotations(cl, cd, param); - for (String annotation : annotations) { - buffer.append(annotation).append(' '); + Set annotations = getRecordComponentAnnotations(cl, cd, param); + for (TextBuffer annotation : annotations) { + buffer.appendText(annotation).append(' '); } VarType fieldType = new VarType(cd.getDescriptor(), false); @@ -178,16 +203,41 @@ private static void recordComponentToJava(TextBuffer buffer, StructClass cl, Str if (descriptor != null) fieldType = descriptor.type; - buffer.append(ExprProcessor.getCastTypeName(varArgComponent ? fieldType.decreaseArrayDim() : fieldType)); + buffer.appendCastTypeName(varArgComponent ? fieldType.decreaseArrayDim() : fieldType); if (varArgComponent) { buffer.append("..."); } buffer.append(' '); - buffer.append(cd.getName()); + buffer.appendField(cd.getName(), true, cl.qualifiedName, cd.getName(), cd.getDescriptor()); } + private static boolean hasAnnotations(StructMethod mt) { return mt.getAttribute(StructGeneralAttribute.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS) != null || mt.getAttribute(StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS) != null; } + + public static void fixupCanonicalConstructor(MethodWrapper mw, StructClass cl) { + if (cl.getRecordComponents() == null) { + return; + } + + if (mw.methodStruct != getCanonicalConstructor(cl)) { + return; + } + + MethodDescriptor md = mw.desc(); + int params = md.params.length; + + if (params != cl.getRecordComponents().size()) { + return; + } + + int varidx = 1; + for (int i = 0; i < cl.getRecordComponents().size(); i++) { + mw.varproc.setClashingName(new VarVersionPair(varidx, 0), cl.getRecordComponents().get(i).getName()); + + varidx += md.params[i].stackSize; + } + } } diff --git a/src/org/jetbrains/java/decompiler/main/collectors/ImportCollector.java b/src/org/jetbrains/java/decompiler/main/collectors/ImportCollector.java index c6c9d233e1..e681ace76d 100644 --- a/src/org/jetbrains/java/decompiler/main/collectors/ImportCollector.java +++ b/src/org/jetbrains/java/decompiler/main/collectors/ImportCollector.java @@ -3,6 +3,8 @@ import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor; import org.jetbrains.java.decompiler.struct.StructClass; import org.jetbrains.java.decompiler.struct.StructContext; import org.jetbrains.java.decompiler.struct.StructField; @@ -16,14 +18,14 @@ public class ImportCollector { private static final String JAVA_LANG_PACKAGE = "java.lang"; - private final Map mapSimpleNames = new HashMap<>(); - private final Set setNotImportedNames = new HashSet<>(); + protected final Map mapSimpleNames = new HashMap<>(); + protected final Set setNotImportedNames = new HashSet<>(); // set of field names in this class and all its predecessors. - private final Set setFieldNames = new HashSet<>(); - private final Map> mapInnerClassNames = new HashMap<>(); - private final String currentPackageSlash; - private final String currentPackagePoint; - private boolean writeLocked = false; + protected final Set setFieldNames = new HashSet<>(); + protected final Map> mapInnerClassNames = new HashMap<>(); + protected final String currentPackageSlash; + protected final String currentPackagePoint; + protected boolean writeLocked = false; public ImportCollector(ClassNode root) { String clName = root.classStruct.qualifiedName; @@ -62,6 +64,16 @@ public ImportCollector(ClassNode root) { collectConflictingShortNames(root, new HashMap<>()); } + + public ImportCollector(ImportCollector other) { + this.mapSimpleNames.putAll(other.mapSimpleNames); + this.setNotImportedNames.addAll(other.setNotImportedNames); + this.setFieldNames.addAll(other.setFieldNames); + this.mapInnerClassNames.putAll(other.mapInnerClassNames); + this.currentPackageSlash = other.currentPackageSlash; + this.currentPackagePoint = other.currentPackagePoint; + this.writeLocked = other.writeLocked; + } /** * Check whether the package-less name ClassName is shaded by variable in a context of @@ -71,6 +83,10 @@ public ImportCollector(ClassNode root) { */ public String getShortNameInClassContext(String classToName) { String shortName = getShortName(classToName); + if (shortName == null) { + return ""; + } + if (setFieldNames.contains(shortName)) { return classToName; } @@ -90,7 +106,7 @@ public String getShortName(String fullName, boolean imported) { if (node != null && node.classStruct.isOwn()) { result = node.simpleName; - while (node.parent != null && node.type == ClassNode.Type.MEMBER) { + while (node.parent != null && node.parent.simpleName != null && node.type == ClassNode.Type.MEMBER) { //noinspection StringConcatenationInLoop result = node.parent.simpleName + '.' + result; node = node.parent; @@ -99,8 +115,10 @@ public String getShortName(String fullName, boolean imported) { if (node.type == ClassNode.Type.ROOT) { fullName = node.classStruct.qualifiedName; fullName = fullName.replace('/', '.'); - } - else { + } else { + if (result == null && node.type == ClassNode.Type.ANONYMOUS) { + result = ExprProcessor.UNREPRESENTABLE_TYPE_STRING; + } return result; } } @@ -127,7 +145,7 @@ public String getShortName(String fullName, boolean imported) { (context.getClass(currentPackageSlash + shortName) != null && !packageName.equals(currentPackagePoint)) || // current package (context.getClass(shortName) != null && !currentPackagePoint.isEmpty()); - ClassNode currCls = (ClassNode)DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASS_NODE); + ClassNode currCls = (ClassNode)DecompilerContext.getContextProperty(DecompilerContext.CURRENT_CLASS_NODE); String mapKey = currCls == null ? "" : currCls.classStruct.qualifiedName; Map innerClassNames = mapInnerClassNames.getOrDefault(mapKey, new HashMap<>()); if (!existsDefaultClass && innerClassNames.containsKey(shortName) && !innerClassNames.get(shortName).equals(fullName)) { @@ -169,6 +187,10 @@ else if (!mapSimpleNames.containsKey(shortName)) { } public void writeImports(TextBuffer buffer, boolean addSeparator) { + if (DecompilerContext.getOption(IFernflowerPreferences.REMOVE_IMPORTS)) { + return; + } + List imports = packImports(); for (String line : imports) { buffer.append("import ").append(line).append(';').appendLineSeparator(); @@ -178,21 +200,27 @@ public void writeImports(TextBuffer buffer, boolean addSeparator) { } } - private List packImports() { + protected List packImports() { return mapSimpleNames.entrySet().stream() - .filter(ent -> - // exclude the current class or one of the nested ones - // empty, java.lang and the current packages - !setNotImportedNames.contains(ent.getKey()) && - !ent.getValue().isEmpty() && - !JAVA_LANG_PACKAGE.equals(ent.getValue()) && - !ent.getValue().equals(currentPackagePoint) - ) + .filter(this::keepImport) .sorted(Map.Entry.comparingByValue().thenComparing(Map.Entry.comparingByKey())) .map(ent -> ent.getValue() + "." + ent.getKey()) .collect(Collectors.toList()); } + /** + * Check whether to keep the given entry in the import list. + * + * @param ent the entry in the map containing the class name and its corresponding package name + * @return true if the entry should be kept for importing, false otherwise + */ + protected boolean keepImport(Map.Entry ent) { + return !setNotImportedNames.contains(ent.getKey()) && + !ent.getValue().isEmpty() && + !JAVA_LANG_PACKAGE.equals(ent.getValue()) && + !ent.getValue().equals(currentPackagePoint); + } + private void collectConflictingShortNames(ClassNode root, Map rootNames) { Map names = new HashMap<>(rootNames); getSuperClassInnerClasses(root, names); @@ -241,11 +269,24 @@ private void getSuperClassInnerClasses(ClassNode node, Map names } } - public boolean isWriteLocked() { - return writeLocked; + private void setWriteLocked(boolean writeLocked) { + this.writeLocked = writeLocked; } - public void setWriteLocked(boolean writeLocked) { - this.writeLocked = writeLocked; + public Lock lock() { + return new Lock(); + } + + public class Lock implements AutoCloseable { + private final boolean target; + private Lock() { + this.target = writeLocked; + setWriteLocked(true); + } + + @Override + public void close() { + setWriteLocked(target); + } } } \ No newline at end of file diff --git a/src/org/jetbrains/java/decompiler/main/decompiler/CancelationManager.java b/src/org/jetbrains/java/decompiler/main/decompiler/CancelationManager.java new file mode 100644 index 0000000000..1e692e331d --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/decompiler/CancelationManager.java @@ -0,0 +1,49 @@ +package org.jetbrains.java.decompiler.main.decompiler; + +/** + * Used for cancelling the decompilation process. This can for example be useful in GUI frontends with cancelation + * support. + */ +public final class CancelationManager { + private static Runnable cancelationChecker = () -> {}; + + private CancelationManager() { + } + + /** + * Cancels the decompilation process by throwing a {@linkplain CanceledException}. + */ + public static void cancel() { + throw CanceledException.INSTANCE; + } + + /** + * Polled frequently by the decompiler to check if decompilation has been canceled. Use + * {@linkplain #setCancelationChecker(Runnable)} to set the logic for checking cancelation. + */ + public static void checkCanceled() { + cancelationChecker.run(); + } + + /** + * Sets the logic for checking cancelation. To cancel decompilation, call {@linkplain #cancel()} inside the checker. + */ + public static void setCancelationChecker(Runnable checker) { + cancelationChecker = checker; + } + + /** + * The exception that is thrown upon cancelation. + */ + public static final class CanceledException extends RuntimeException { + public static final CanceledException INSTANCE = new CanceledException(); + + private CanceledException() { + } + + @Override + public Throwable fillInStackTrace() { + return this; + } + } +} diff --git a/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java b/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java index 8956f020a5..e7f9577cd2 100644 --- a/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java +++ b/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java @@ -3,6 +3,7 @@ import org.jetbrains.java.decompiler.main.DecompilerContext; import org.jetbrains.java.decompiler.main.Fernflower; +import org.jetbrains.java.decompiler.main.extern.IContextSource; import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; import org.jetbrains.java.decompiler.main.extern.IResultSaver; @@ -68,10 +69,17 @@ else if (args.length > x+1) { return; } + if (Arrays.stream(args).anyMatch(arg -> arg.equals("--list-plugins"))) { + ConsoleHelp.printPlugins(); + return; + } + if (args.length < 1) { System.out.println( - "Usage: java -jar vineflower.jar [-