├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md └── workflows │ └── build.yml ├── .gitignore ├── .idea ├── .gitignore ├── decompiler-rust.iml ├── inspectionProfiles │ └── Project_Default.xml ├── modules.xml └── vcs.xml ├── .vscode ├── launch.json └── settings.json ├── Cargo.toml ├── LICENSE.md ├── README.md ├── class_format ├── Cargo.toml ├── LICENSE.md ├── README.md └── src │ ├── access_flags.rs │ ├── attribute.rs │ ├── constant.rs │ ├── error.rs │ ├── ext.rs │ ├── lib.rs │ ├── member.rs │ ├── method.rs │ ├── op.rs │ └── ty.rs ├── docs ├── Supported_Features.md ├── jvm_20.pdf └── lang_20.pdf ├── sample_output.java ├── src ├── bin │ └── main.rs ├── dependency.rs ├── error.rs ├── file │ ├── jar.rs │ ├── manifest.rs │ └── mod.rs ├── gen │ ├── indent.rs │ ├── java │ │ ├── class.rs │ │ ├── code.rs │ │ ├── field.rs │ │ ├── method.rs │ │ └── mod.rs │ └── mod.rs ├── ir │ ├── expression.rs │ ├── frame.rs │ └── mod.rs ├── lib.rs └── settings.rs └── tests ├── .gitignore ├── run_units.rs └── units ├── 00_empty.java ├── 01_return_lit.java └── 02_hello_world.java /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | ## Description 10 | A clear and concise description of the bug. 11 | 12 | ## Expected behaviour 13 | Output or behaviour that was expected. 14 | 15 | ## Attachments 16 | If possible, please attach JARs and/or output files in question. 17 | 18 | ## Environment 19 | - OS: \[e.g. macOS 10.15] 20 | - Decompiler Version: \[e.g. 0.1.7] 21 | - Rust Compiler Version: \[e.g. 1.53.0] 22 | - Rust Toolchain: \[e.g. stable-x86_64-unknown-linux-gnu] 23 | 24 | ## Additional context 25 | Put additional context about the problem here. 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Request a new feature to be added 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | ## Description 10 | Describe what you'd like to be added to the decompiler. 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask something that's not covered by documentation 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | --- 8 | 9 | ## Description 10 | Put your question here. 11 | 12 | If there are several related questions, feel free to ask them together. 13 | If you have multiple unrelated questions, put them into separate issues. 14 | 15 | These types of issues are used to fill gaps in the documentation. 16 | Please refrain from creating issues for questions like "why isn't this working?", 17 | send those directly to my [email](mailto:tin.svagelj@live.com), I'll respond faster. 18 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | check: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/cache@v1 15 | with: 16 | path: ~/.cargo/registry 17 | key: '${{ runner.os }}-cargo-registry-${{ hashFiles(''**/Cargo.lock'') }}' 18 | - uses: actions/cache@v1 19 | with: 20 | path: ~/.cargo/git 21 | key: '${{ runner.os }}-cargo-index-${{ hashFiles(''**/Cargo.lock'') }}' 22 | - uses: actions/cache@v1 23 | with: 24 | path: target 25 | key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} 26 | - uses: actions-rs/toolchain@v1 27 | with: 28 | profile: minimal 29 | toolchain: stable 30 | override: true 31 | - uses: actions-rs/cargo@v1 32 | with: 33 | command: check 34 | test: 35 | runs-on: '${{ matrix.os }}' 36 | strategy: 37 | matrix: 38 | include: 39 | - os: macos-latest 40 | - os: ubuntu-latest 41 | - os: windows-latest 42 | steps: 43 | - uses: actions/checkout@v2 44 | - uses: actions/cache@v1 45 | with: 46 | path: ~/.cargo/registry 47 | key: '${{ runner.os }}-cargo-registry-${{ hashFiles(''**/Cargo.lock'') }}' 48 | - uses: actions/cache@v1 49 | with: 50 | path: ~/.cargo/git 51 | key: '${{ runner.os }}-cargo-index-${{ hashFiles(''**/Cargo.lock'') }}' 52 | - uses: actions/cache@v1 53 | with: 54 | path: target 55 | key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} 56 | - uses: actions-rs/toolchain@v1 57 | with: 58 | profile: minimal 59 | toolchain: stable 60 | override: true 61 | - uses: actions-rs/cargo@v1 62 | with: 63 | command: test 64 | - uses: actions-rs/cargo@v1 65 | with: 66 | command: run 67 | args: '-- -h' 68 | lints: 69 | runs-on: ubuntu-latest 70 | steps: 71 | - uses: actions/checkout@v2 72 | - uses: actions/cache@v1 73 | with: 74 | path: ~/.cargo/registry 75 | key: '${{ runner.os }}-cargo-registry-${{ hashFiles(''**/Cargo.lock'') }}' 76 | - uses: actions/cache@v1 77 | with: 78 | path: ~/.cargo/git 79 | key: '${{ runner.os }}-cargo-index-${{ hashFiles(''**/Cargo.lock'') }}' 80 | - uses: actions/cache@v1 81 | with: 82 | path: target 83 | key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} 84 | - uses: actions-rs/toolchain@v1 85 | with: 86 | profile: minimal 87 | toolchain: stable 88 | override: true 89 | components: 'rustfmt, clippy' 90 | - uses: actions-rs/cargo@v1 91 | with: 92 | command: fmt 93 | args: '--all -- --check' 94 | - uses: actions-rs/cargo@v1 95 | with: 96 | command: clippy 97 | args: '-- -D warnings' 98 | version: 99 | runs-on: ubuntu-latest 100 | steps: 101 | - uses: actions/checkout@master 102 | with: 103 | lfs: true 104 | - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* 105 | - id: get_previous_version 106 | run: echo ::set-output name=PREVIOUS_VERSION::$(git describe --tags "$(git rev-list --tags --max-count=1)") 107 | shell: bash 108 | - id: semvers 109 | uses: WyriHaximus/github-action-next-semvers@master 110 | with: 111 | version: '${{ steps.get_previous_version.outputs.PREVIOUS_VERSION }}' 112 | - run: mkdir -p ./version 113 | - if: "!contains(github.event.head_commit.message, 'BC BREAK') && !contains(github.event.head_commit.message, 'Signed-off-by: dependabot-preview[bot] ')" 114 | run: echo "$VERSION" > ./version/version 115 | env: 116 | VERSION: ${{ steps.semvers.outputs.v_minor }} 117 | - if: "contains(github.event.head_commit.message, 'Signed-off-by: dependabot-preview[bot] ')" 118 | run: echo "$VERSION" > ./version/version 119 | env: 120 | VERSION: ${{ steps.semvers.outputs.v_patch }} 121 | - run: echo "$VERSION" > ./version/version 122 | env: 123 | VERSION: ${{ steps.semvers.outputs.v_major }} 124 | if: "contains(github.event.head_commit.message, 'BC BREAK')" 125 | - uses: actions/upload-artifact@master 126 | with: 127 | name: version 128 | path: ./version/version 129 | build: 130 | needs: 131 | - version 132 | - lints 133 | - test 134 | - check 135 | runs-on: '${{ matrix.os }}' 136 | strategy: 137 | matrix: 138 | include: 139 | - os: macos-latest 140 | target: x86_64-apple-darwin 141 | suffix: '' 142 | - os: ubuntu-latest 143 | target: x86_64-unknown-linux-gnu 144 | suffix: '' 145 | - os: windows-latest 146 | target: x86_64-pc-windows-msvc 147 | suffix: .exe 148 | steps: 149 | - uses: actions/checkout@master 150 | with: 151 | lfs: true 152 | - id: get_repository_name 153 | run: echo ::set-output name=REPOSITORY_NAME::$(echo "$GITHUB_REPOSITORY" | awk -F / '{print $2}' | sed -e "s/:refs//") 154 | shell: bash 155 | - uses: actions/download-artifact@master 156 | with: 157 | name: version 158 | - id: get_version 159 | run: 'echo ::set-output "name=VERSION::$(cat ./version/version)"' 160 | shell: bash 161 | - uses: actions/cache@v1 162 | with: 163 | path: ~/.cargo/registry 164 | key: '${{ runner.os }}-cargo-registry-${{ hashFiles(''**/Cargo.lock'') }}' 165 | - uses: actions/cache@v1 166 | with: 167 | path: ~/.cargo/git 168 | key: '${{ runner.os }}-cargo-index-${{ hashFiles(''**/Cargo.lock'') }}' 169 | - uses: actions/cache@v1 170 | with: 171 | path: target 172 | key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} 173 | - uses: actions-rs/toolchain@v1 174 | with: 175 | profile: minimal 176 | toolchain: stable 177 | override: true 178 | - uses: actions-rs/cargo@v1 179 | with: 180 | command: install 181 | args: 'toml-cli' 182 | - shell: bash 183 | env: 184 | VERSION: '${{ steps.get_version.outputs.VERSION }}' 185 | run: | 186 | TEMP_FILE="$(mktemp)" 187 | toml set Cargo.toml package.version "${VERSION:1}" > "$TEMP_FILE" 188 | mv "$TEMP_FILE" Cargo.toml 189 | - uses: actions-rs/cargo@v1 190 | env: 191 | VERSION: '${{ steps.get_version.outputs.VERSION }}' 192 | REPOSITORY_NAME: '${{ steps.get_repository_name.outputs.REPOSITORY_NAME }}' 193 | with: 194 | command: build 195 | args: '--release' 196 | - uses: actions/upload-artifact@master 197 | with: 198 | name: ${{ steps.get_repository_name.outputs.REPOSITORY_NAME }}-${{ matrix.target }} 199 | path: ./target/release/${{ steps.get_repository_name.outputs.REPOSITORY_NAME }}${{ matrix.suffix }} 200 | release: 201 | if: github.ref == 'refs/heads/master' 202 | needs: 203 | - build 204 | runs-on: ubuntu-latest 205 | steps: 206 | - uses: actions/checkout@master 207 | with: 208 | lfs: true 209 | - id: get_repository_name 210 | run: echo ::set-output name=REPOSITORY_NAME::$(echo "$GITHUB_REPOSITORY" | awk -F / '{print $2}' | sed -e "s/:refs//") 211 | shell: bash 212 | - uses: actions/download-artifact@master 213 | with: 214 | name: version 215 | - id: get_version 216 | run: 'echo ::set-output name=VERSION::$(cat ./version/version)' 217 | shell: bash 218 | - uses: actions/download-artifact@master 219 | with: 220 | name: ${{ steps.get_repository_name.outputs.REPOSITORY_NAME }}-x86_64-unknown-linux-gnu 221 | - uses: actions/download-artifact@master 222 | with: 223 | name: ${{ steps.get_repository_name.outputs.REPOSITORY_NAME }}-x86_64-apple-darwin 224 | - uses: actions/download-artifact@master 225 | with: 226 | name: ${{ steps.get_repository_name.outputs.REPOSITORY_NAME }}-x86_64-pc-windows-msvc 227 | - uses: actions-rs/toolchain@v1 228 | with: 229 | profile: minimal 230 | toolchain: stable 231 | override: true 232 | - uses: actions-rs/cargo@v1 233 | with: 234 | command: install 235 | args: 'toml-cli' 236 | - run: | 237 | TEMP_FILE="$(mktemp)" 238 | toml set Cargo.toml package.version "${VERSION:1}" > "$TEMP_FILE" 239 | mv "$TEMP_FILE" Cargo.toml 240 | shell: bash 241 | env: 242 | VERSION: '${{ steps.get_version.outputs.VERSION }}' 243 | - uses: stefanzweifel/git-auto-commit-action@v4.1.3 244 | with: 245 | commit_message: Bump cargo version 246 | branch: ${{ github.head_ref }} 247 | file_pattern: Cargo.toml 248 | - id: create_release 249 | uses: actions/create-release@v1.0.0 250 | env: 251 | GITHUB_TOKEN: '${{ secrets.COMMITTER_TOKEN }}' 252 | with: 253 | tag_name: '${{ steps.get_version.outputs.VERSION }}' 254 | release_name: 'Release ${{ steps.get_version.outputs.VERSION }}' 255 | draft: false 256 | prerelease: false 257 | - uses: actions/upload-release-asset@v1.0.1 258 | env: 259 | GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' 260 | with: 261 | upload_url: '${{ steps.create_release.outputs.upload_url }}' 262 | asset_path: ./${{ steps.get_repository_name.outputs.REPOSITORY_NAME }}-x86_64-unknown-linux-gnu/${{ steps.get_repository_name.outputs.REPOSITORY_NAME }} 263 | asset_name: ${{ steps.get_repository_name.outputs.REPOSITORY_NAME }}-x86_64-unknown-linux-gnu 264 | asset_content_type: application/octet-stream 265 | - uses: actions/upload-release-asset@v1.0.1 266 | env: 267 | GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' 268 | with: 269 | upload_url: '${{ steps.create_release.outputs.upload_url }}' 270 | asset_path: ./${{ steps.get_repository_name.outputs.REPOSITORY_NAME }}-x86_64-apple-darwin/${{ steps.get_repository_name.outputs.REPOSITORY_NAME }} 271 | asset_name: ${{ steps.get_repository_name.outputs.REPOSITORY_NAME }}-x86_64-apple-darwin 272 | asset_content_type: application/octet-stream 273 | - uses: actions/upload-release-asset@v1.0.1 274 | env: 275 | GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' 276 | with: 277 | upload_url: '${{ steps.create_release.outputs.upload_url }}' 278 | asset_path: ./${{ steps.get_repository_name.outputs.REPOSITORY_NAME }}-x86_64-pc-windows-msvc/${{ steps.get_repository_name.outputs.REPOSITORY_NAME }}.exe 279 | asset_name: ${{ steps.get_repository_name.outputs.REPOSITORY_NAME }}-x86_64-pc-windows-msvc.exe 280 | asset_content_type: application/octet-stream 281 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/rust,windows,linux,macos,visualstudiocode,clion 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=rust,windows,linux,macos,visualstudiocode,clion 4 | 5 | ### CLion ### 6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 8 | 9 | # User-specific stuff 10 | .idea/**/workspace.xml 11 | .idea/**/tasks.xml 12 | .idea/**/usage.statistics.xml 13 | .idea/**/dictionaries 14 | .idea/**/shelf 15 | 16 | # AWS User-specific 17 | .idea/**/aws.xml 18 | 19 | # Generated files 20 | .idea/**/contentModel.xml 21 | 22 | # Sensitive or high-churn files 23 | .idea/**/dataSources/ 24 | .idea/**/dataSources.ids 25 | .idea/**/dataSources.local.xml 26 | .idea/**/sqlDataSources.xml 27 | .idea/**/dynamic.xml 28 | .idea/**/uiDesigner.xml 29 | .idea/**/dbnavigator.xml 30 | 31 | # Gradle 32 | .idea/**/gradle.xml 33 | .idea/**/libraries 34 | 35 | # Gradle and Maven with auto-import 36 | # When using Gradle or Maven with auto-import, you should exclude module files, 37 | # since they will be recreated, and may cause churn. Uncomment if using 38 | # auto-import. 39 | # .idea/artifacts 40 | # .idea/compiler.xml 41 | # .idea/jarRepositories.xml 42 | # .idea/modules.xml 43 | # .idea/*.iml 44 | # .idea/modules 45 | # *.iml 46 | # *.ipr 47 | 48 | # CMake 49 | cmake-build-*/ 50 | 51 | # Mongo Explorer plugin 52 | .idea/**/mongoSettings.xml 53 | 54 | # File-based project format 55 | *.iws 56 | 57 | # IntelliJ 58 | out/ 59 | 60 | # mpeltonen/sbt-idea plugin 61 | .idea_modules/ 62 | 63 | # JIRA plugin 64 | atlassian-ide-plugin.xml 65 | 66 | # Cursive Clojure plugin 67 | .idea/replstate.xml 68 | 69 | # Crashlytics plugin (for Android Studio and IntelliJ) 70 | com_crashlytics_export_strings.xml 71 | crashlytics.properties 72 | crashlytics-build.properties 73 | fabric.properties 74 | 75 | # Editor-based Rest Client 76 | .idea/httpRequests 77 | 78 | # Android studio 3.1+ serialized cache file 79 | .idea/caches/build_file_checksums.ser 80 | 81 | ### CLion Patch ### 82 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 83 | 84 | # *.iml 85 | # modules.xml 86 | # .idea/misc.xml 87 | # *.ipr 88 | 89 | # Sonarlint plugin 90 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 91 | .idea/**/sonarlint/ 92 | 93 | # SonarQube Plugin 94 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 95 | .idea/**/sonarIssues.xml 96 | 97 | # Markdown Navigator plugin 98 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 99 | .idea/**/markdown-navigator.xml 100 | .idea/**/markdown-navigator-enh.xml 101 | .idea/**/markdown-navigator/ 102 | 103 | # Cache file creation bug 104 | # See https://youtrack.jetbrains.com/issue/JBR-2257 105 | .idea/$CACHE_FILE$ 106 | 107 | # CodeStream plugin 108 | # https://plugins.jetbrains.com/plugin/12206-codestream 109 | .idea/codestream.xml 110 | 111 | ### Linux ### 112 | *~ 113 | 114 | # temporary files which can be created if a process still has a handle open of a deleted file 115 | .fuse_hidden* 116 | 117 | # KDE directory preferences 118 | .directory 119 | 120 | # Linux trash folder which might appear on any partition or disk 121 | .Trash-* 122 | 123 | # .nfs files are created when an open file is removed but is still being accessed 124 | .nfs* 125 | 126 | ### macOS ### 127 | # General 128 | .DS_Store 129 | .AppleDouble 130 | .LSOverride 131 | 132 | # Icon must end with two \r 133 | Icon 134 | 135 | 136 | # Thumbnails 137 | ._* 138 | 139 | # Files that might appear in the root of a volume 140 | .DocumentRevisions-V100 141 | .fseventsd 142 | .Spotlight-V100 143 | .TemporaryItems 144 | .Trashes 145 | .VolumeIcon.icns 146 | .com.apple.timemachine.donotpresent 147 | 148 | # Directories potentially created on remote AFP share 149 | .AppleDB 150 | .AppleDesktop 151 | Network Trash Folder 152 | Temporary Items 153 | .apdisk 154 | 155 | ### Rust ### 156 | # Generated by Cargo 157 | # will have compiled files and executables 158 | debug/ 159 | target/ 160 | 161 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 162 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 163 | Cargo.lock 164 | 165 | # These are backup files generated by rustfmt 166 | **/*.rs.bk 167 | 168 | # MSVC Windows builds of rustc generate these, which store debugging information 169 | *.pdb 170 | 171 | ### VisualStudioCode ### 172 | .vscode/* 173 | !.vscode/settings.json 174 | !.vscode/tasks.json 175 | !.vscode/launch.json 176 | !.vscode/extensions.json 177 | *.code-workspace 178 | 179 | # Local History for Visual Studio Code 180 | .history/ 181 | 182 | ### VisualStudioCode Patch ### 183 | # Ignore all local history of files 184 | .history 185 | .ionide 186 | 187 | ### Windows ### 188 | # Windows thumbnail cache files 189 | Thumbs.db 190 | Thumbs.db:encryptable 191 | ehthumbs.db 192 | ehthumbs_vista.db 193 | 194 | # Dump file 195 | *.stackdump 196 | 197 | # Folder config file 198 | [Dd]esktop.ini 199 | 200 | # Recycle Bin used on file shares 201 | $RECYCLE.BIN/ 202 | 203 | # Windows Installer files 204 | *.cab 205 | *.msi 206 | *.msix 207 | *.msm 208 | *.msp 209 | 210 | # Windows shortcuts 211 | *.lnk 212 | 213 | # End of https://www.toptal.com/developers/gitignore/api/rust,windows,linux,macos,visualstudiocode,clion 214 | 215 | # References 216 | ref/ 217 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/decompiler-rust.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug unit tests in library 'jaded'", 11 | "cargo": { 12 | "args": [ 13 | "test", 14 | "--no-run", 15 | "--lib", 16 | "--package=jaded" 17 | ], 18 | "filter": { 19 | "name": "jaded", 20 | "kind": "lib" 21 | } 22 | }, 23 | "args": [], 24 | "cwd": "${workspaceFolder}" 25 | }, 26 | { 27 | "type": "lldb", 28 | "request": "launch", 29 | "name": "Debug executable 'jaded'", 30 | "cargo": { 31 | "args": [ 32 | "build", 33 | "--bin=jaded", 34 | "--package=jaded" 35 | ], 36 | "filter": { 37 | "name": "jaded", 38 | "kind": "bin" 39 | } 40 | }, 41 | "args": [], 42 | "cwd": "${workspaceFolder}" 43 | }, 44 | { 45 | "type": "lldb", 46 | "request": "launch", 47 | "name": "Debug unit tests in executable 'jaded'", 48 | "cargo": { 49 | "args": [ 50 | "test", 51 | "--no-run", 52 | "--bin=jaded", 53 | "--package=jaded" 54 | ], 55 | "filter": { 56 | "name": "jaded", 57 | "kind": "bin" 58 | } 59 | }, 60 | "args": [], 61 | "cwd": "${workspaceFolder}" 62 | }, 63 | { 64 | "type": "lldb", 65 | "request": "launch", 66 | "name": "Debug integration test 'run_units'", 67 | "cargo": { 68 | "args": [ 69 | "test", 70 | "--no-run", 71 | "--test=run_units", 72 | "--package=jaded" 73 | ], 74 | "filter": { 75 | "name": "run_units", 76 | "kind": "test" 77 | } 78 | }, 79 | "args": [], 80 | "cwd": "${workspaceFolder}" 81 | } 82 | ] 83 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "lldb.displayFormat": "auto", 3 | "lldb.showDisassembly": "auto", 4 | "lldb.dereferencePointers": true, 5 | "lldb.consoleMode": "commands" 6 | } 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jaded" 3 | version = "0.1.0" 4 | description = "Decompiler for Java programming language." 5 | authors = ["Tin Svagelj "] 6 | edition = "2021" 7 | 8 | [[bin]] 9 | name = "jaded" 10 | path = "src/bin/main.rs" 11 | required-features = ["tracing-subscriber", "clap"] 12 | 13 | [dependencies] 14 | jvm-class-format = { path = "class_format" } 15 | 16 | tracing = { version = "0.1", features = ["log"] } 17 | tracing-subscriber = { version = "0.3", optional = true } 18 | thiserror = "1.0" 19 | 20 | paste = "1.0" 21 | 22 | byteorder = "1.4" 23 | bytemuck = "1.7" 24 | num_enum = "0.7" 25 | bitflags = "2.2" 26 | 27 | zip = "0.6" 28 | 29 | serde = { version = "1.0", features = ["derive"] } 30 | 31 | clap = { version = "4.0", features = ["derive"], optional = true } 32 | 33 | [dev-dependencies] 34 | pretty_assertions = "1.4" 35 | tracing-subscriber = { version = "0.3" } 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU General Public License 2 | ========================== 3 | 4 | _Version 3, 29 June 2007_ 5 | _Copyright © 2007 Free Software Foundation, Inc. <>_ 6 | 7 | Everyone is permitted to copy and distribute verbatim copies of this license 8 | document, but changing it is not allowed. 9 | 10 | ## Preamble 11 | 12 | The GNU General Public License is a free, copyleft license for software and other 13 | kinds of works. 14 | 15 | The licenses for most software and other practical works are designed to take away 16 | your freedom to share and change the works. By contrast, the GNU General Public 17 | License is intended to guarantee your freedom to share and change all versions of a 18 | program--to make sure it remains free software for all its users. We, the Free 19 | Software Foundation, use the GNU General Public License for most of our software; it 20 | applies also to any other work released this way by its authors. You can apply it to 21 | your programs, too. 22 | 23 | When we speak of free software, we are referring to freedom, not price. Our General 24 | Public Licenses are designed to make sure that you have the freedom to distribute 25 | copies of free software (and charge for them if you wish), that you receive source 26 | code or can get it if you want it, that you can change the software or use pieces of 27 | it in new free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you these rights or 30 | asking you to surrender the rights. Therefore, you have certain responsibilities if 31 | you distribute copies of the software, or if you modify it: responsibilities to 32 | respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether gratis or for a fee, 35 | you must pass on to the recipients the same freedoms that you received. You must make 36 | sure that they, too, receive or can get the source code. And you must show them these 37 | terms so they know their rights. 38 | 39 | Developers that use the GNU GPL protect your rights with two steps: **(1)** assert 40 | copyright on the software, and **(2)** offer you this License giving you legal permission 41 | to copy, distribute and/or modify it. 42 | 43 | For the developers' and authors' protection, the GPL clearly explains that there is 44 | no warranty for this free software. For both users' and authors' sake, the GPL 45 | requires that modified versions be marked as changed, so that their problems will not 46 | be attributed erroneously to authors of previous versions. 47 | 48 | Some devices are designed to deny users access to install or run modified versions of 49 | the software inside them, although the manufacturer can do so. This is fundamentally 50 | incompatible with the aim of protecting users' freedom to change the software. The 51 | systematic pattern of such abuse occurs in the area of products for individuals to 52 | use, which is precisely where it is most unacceptable. Therefore, we have designed 53 | this version of the GPL to prohibit the practice for those products. If such problems 54 | arise substantially in other domains, we stand ready to extend this provision to 55 | those domains in future versions of the GPL, as needed to protect the freedom of 56 | users. 57 | 58 | Finally, every program is threatened constantly by software patents. States should 59 | not allow patents to restrict development and use of software on general-purpose 60 | computers, but in those that do, we wish to avoid the special danger that patents 61 | applied to a free program could make it effectively proprietary. To prevent this, the 62 | GPL assures that patents cannot be used to render the program non-free. 63 | 64 | The precise terms and conditions for copying, distribution and modification follow. 65 | 66 | ## TERMS AND CONDITIONS 67 | 68 | ### 0. Definitions 69 | 70 | “This License” refers to version 3 of the GNU General Public License. 71 | 72 | “Copyright” also means copyright-like laws that apply to other kinds of 73 | works, such as semiconductor masks. 74 | 75 | “The Program” refers to any copyrightable work licensed under this 76 | License. Each licensee is addressed as “you”. “Licensees” and 77 | “recipients” may be individuals or organizations. 78 | 79 | To “modify” a work means to copy from or adapt all or part of the work in 80 | a fashion requiring copyright permission, other than the making of an exact copy. The 81 | resulting work is called a “modified version” of the earlier work or a 82 | work “based on” the earlier work. 83 | 84 | A “covered work” means either the unmodified Program or a work based on 85 | the Program. 86 | 87 | To “propagate” a work means to do anything with it that, without 88 | permission, would make you directly or secondarily liable for infringement under 89 | applicable copyright law, except executing it on a computer or modifying a private 90 | copy. Propagation includes copying, distribution (with or without modification), 91 | making available to the public, and in some countries other activities as well. 92 | 93 | To “convey” a work means any kind of propagation that enables other 94 | parties to make or receive copies. Mere interaction with a user through a computer 95 | network, with no transfer of a copy, is not conveying. 96 | 97 | An interactive user interface displays “Appropriate Legal Notices” to the 98 | extent that it includes a convenient and prominently visible feature that **(1)** 99 | displays an appropriate copyright notice, and **(2)** tells the user that there is no 100 | warranty for the work (except to the extent that warranties are provided), that 101 | licensees may convey the work under this License, and how to view a copy of this 102 | License. If the interface presents a list of user commands or options, such as a 103 | menu, a prominent item in the list meets this criterion. 104 | 105 | ### 1. Source Code 106 | 107 | The “source code” for a work means the preferred form of the work for 108 | making modifications to it. “Object code” means any non-source form of a 109 | work. 110 | 111 | A “Standard Interface” means an interface that either is an official 112 | standard defined by a recognized standards body, or, in the case of interfaces 113 | specified for a particular programming language, one that is widely used among 114 | developers working in that language. 115 | 116 | The “System Libraries” of an executable work include anything, other than 117 | the work as a whole, that **(a)** is included in the normal form of packaging a Major 118 | Component, but which is not part of that Major Component, and **(b)** serves only to 119 | enable use of the work with that Major Component, or to implement a Standard 120 | Interface for which an implementation is available to the public in source code form. 121 | A “Major Component”, in this context, means a major essential component 122 | (kernel, window system, and so on) of the specific operating system (if any) on which 123 | the executable work runs, or a compiler used to produce the work, or an object code 124 | interpreter used to run it. 125 | 126 | The “Corresponding Source” for a work in object code form means all the 127 | source code needed to generate, install, and (for an executable work) run the object 128 | code and to modify the work, including scripts to control those activities. However, 129 | it does not include the work's System Libraries, or general-purpose tools or 130 | generally available free programs which are used unmodified in performing those 131 | activities but which are not part of the work. For example, Corresponding Source 132 | includes interface definition files associated with source files for the work, and 133 | the source code for shared libraries and dynamically linked subprograms that the work 134 | is specifically designed to require, such as by intimate data communication or 135 | control flow between those subprograms and other parts of the work. 136 | 137 | The Corresponding Source need not include anything that users can regenerate 138 | automatically from other parts of the Corresponding Source. 139 | 140 | The Corresponding Source for a work in source code form is that same work. 141 | 142 | ### 2. Basic Permissions 143 | 144 | All rights granted under this License are granted for the term of copyright on the 145 | Program, and are irrevocable provided the stated conditions are met. This License 146 | explicitly affirms your unlimited permission to run the unmodified Program. The 147 | output from running a covered work is covered by this License only if the output, 148 | given its content, constitutes a covered work. This License acknowledges your rights 149 | of fair use or other equivalent, as provided by copyright law. 150 | 151 | You may make, run and propagate covered works that you do not convey, without 152 | conditions so long as your license otherwise remains in force. You may convey covered 153 | works to others for the sole purpose of having them make modifications exclusively 154 | for you, or provide you with facilities for running those works, provided that you 155 | comply with the terms of this License in conveying all material for which you do not 156 | control copyright. Those thus making or running the covered works for you must do so 157 | exclusively on your behalf, under your direction and control, on terms that prohibit 158 | them from making any copies of your copyrighted material outside their relationship 159 | with you. 160 | 161 | Conveying under any other circumstances is permitted solely under the conditions 162 | stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 163 | 164 | ### 3. Protecting Users' Legal Rights From Anti-Circumvention Law 165 | 166 | No covered work shall be deemed part of an effective technological measure under any 167 | applicable law fulfilling obligations under article 11 of the WIPO copyright treaty 168 | adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention 169 | of such measures. 170 | 171 | When you convey a covered work, you waive any legal power to forbid circumvention of 172 | technological measures to the extent such circumvention is effected by exercising 173 | rights under this License with respect to the covered work, and you disclaim any 174 | intention to limit operation or modification of the work as a means of enforcing, 175 | against the work's users, your or third parties' legal rights to forbid circumvention 176 | of technological measures. 177 | 178 | ### 4. Conveying Verbatim Copies 179 | 180 | You may convey verbatim copies of the Program's source code as you receive it, in any 181 | medium, provided that you conspicuously and appropriately publish on each copy an 182 | appropriate copyright notice; keep intact all notices stating that this License and 183 | any non-permissive terms added in accord with section 7 apply to the code; keep 184 | intact all notices of the absence of any warranty; and give all recipients a copy of 185 | this License along with the Program. 186 | 187 | You may charge any price or no price for each copy that you convey, and you may offer 188 | support or warranty protection for a fee. 189 | 190 | ### 5. Conveying Modified Source Versions 191 | 192 | You may convey a work based on the Program, or the modifications to produce it from 193 | the Program, in the form of source code under the terms of section 4, provided that 194 | you also meet all of these conditions: 195 | 196 | * **a)** The work must carry prominent notices stating that you modified it, and giving a 197 | relevant date. 198 | * **b)** The work must carry prominent notices stating that it is released under this 199 | License and any conditions added under section 7. This requirement modifies the 200 | requirement in section 4 to “keep intact all notices”. 201 | * **c)** You must license the entire work, as a whole, under this License to anyone who 202 | comes into possession of a copy. This License will therefore apply, along with any 203 | applicable section 7 additional terms, to the whole of the work, and all its parts, 204 | regardless of how they are packaged. This License gives no permission to license the 205 | work in any other way, but it does not invalidate such permission if you have 206 | separately received it. 207 | * **d)** If the work has interactive user interfaces, each must display Appropriate Legal 208 | Notices; however, if the Program has interactive interfaces that do not display 209 | Appropriate Legal Notices, your work need not make them do so. 210 | 211 | A compilation of a covered work with other separate and independent works, which are 212 | not by their nature extensions of the covered work, and which are not combined with 213 | it such as to form a larger program, in or on a volume of a storage or distribution 214 | medium, is called an “aggregate” if the compilation and its resulting 215 | copyright are not used to limit the access or legal rights of the compilation's users 216 | beyond what the individual works permit. Inclusion of a covered work in an aggregate 217 | does not cause this License to apply to the other parts of the aggregate. 218 | 219 | ### 6. Conveying Non-Source Forms 220 | 221 | You may convey a covered work in object code form under the terms of sections 4 and 222 | 5, provided that you also convey the machine-readable Corresponding Source under the 223 | terms of this License, in one of these ways: 224 | 225 | * **a)** Convey the object code in, or embodied in, a physical product (including a 226 | physical distribution medium), accompanied by the Corresponding Source fixed on a 227 | durable physical medium customarily used for software interchange. 228 | * **b)** Convey the object code in, or embodied in, a physical product (including a 229 | physical distribution medium), accompanied by a written offer, valid for at least 230 | three years and valid for as long as you offer spare parts or customer support for 231 | that product model, to give anyone who possesses the object code either **(1)** a copy of 232 | the Corresponding Source for all the software in the product that is covered by this 233 | License, on a durable physical medium customarily used for software interchange, for 234 | a price no more than your reasonable cost of physically performing this conveying of 235 | source, or **(2)** access to copy the Corresponding Source from a network server at no 236 | charge. 237 | * **c)** Convey individual copies of the object code with a copy of the written offer to 238 | provide the Corresponding Source. This alternative is allowed only occasionally and 239 | noncommercially, and only if you received the object code with such an offer, in 240 | accord with subsection 6b. 241 | * **d)** Convey the object code by offering access from a designated place (gratis or for 242 | a charge), and offer equivalent access to the Corresponding Source in the same way 243 | through the same place at no further charge. You need not require recipients to copy 244 | the Corresponding Source along with the object code. If the place to copy the object 245 | code is a network server, the Corresponding Source may be on a different server 246 | (operated by you or a third party) that supports equivalent copying facilities, 247 | provided you maintain clear directions next to the object code saying where to find 248 | the Corresponding Source. Regardless of what server hosts the Corresponding Source, 249 | you remain obligated to ensure that it is available for as long as needed to satisfy 250 | these requirements. 251 | * **e)** Convey the object code using peer-to-peer transmission, provided you inform 252 | other peers where the object code and Corresponding Source of the work are being 253 | offered to the general public at no charge under subsection 6d. 254 | 255 | A separable portion of the object code, whose source code is excluded from the 256 | Corresponding Source as a System Library, need not be included in conveying the 257 | object code work. 258 | 259 | A “User Product” is either **(1)** a “consumer product”, which 260 | means any tangible personal property which is normally used for personal, family, or 261 | household purposes, or **(2)** anything designed or sold for incorporation into a 262 | dwelling. In determining whether a product is a consumer product, doubtful cases 263 | shall be resolved in favor of coverage. For a particular product received by a 264 | particular user, “normally used” refers to a typical or common use of 265 | that class of product, regardless of the status of the particular user or of the way 266 | in which the particular user actually uses, or expects or is expected to use, the 267 | product. A product is a consumer product regardless of whether the product has 268 | substantial commercial, industrial or non-consumer uses, unless such uses represent 269 | the only significant mode of use of the product. 270 | 271 | “Installation Information” for a User Product means any methods, 272 | procedures, authorization keys, or other information required to install and execute 273 | modified versions of a covered work in that User Product from a modified version of 274 | its Corresponding Source. The information must suffice to ensure that the continued 275 | functioning of the modified object code is in no case prevented or interfered with 276 | solely because modification has been made. 277 | 278 | If you convey an object code work under this section in, or with, or specifically for 279 | use in, a User Product, and the conveying occurs as part of a transaction in which 280 | the right of possession and use of the User Product is transferred to the recipient 281 | in perpetuity or for a fixed term (regardless of how the transaction is 282 | characterized), the Corresponding Source conveyed under this section must be 283 | accompanied by the Installation Information. But this requirement does not apply if 284 | neither you nor any third party retains the ability to install modified object code 285 | on the User Product (for example, the work has been installed in ROM). 286 | 287 | The requirement to provide Installation Information does not include a requirement to 288 | continue to provide support service, warranty, or updates for a work that has been 289 | modified or installed by the recipient, or for the User Product in which it has been 290 | modified or installed. Access to a network may be denied when the modification itself 291 | materially and adversely affects the operation of the network or violates the rules 292 | and protocols for communication across the network. 293 | 294 | Corresponding Source conveyed, and Installation Information provided, in accord with 295 | this section must be in a format that is publicly documented (and with an 296 | implementation available to the public in source code form), and must require no 297 | special password or key for unpacking, reading or copying. 298 | 299 | ### 7. Additional Terms 300 | 301 | “Additional permissions” are terms that supplement the terms of this 302 | License by making exceptions from one or more of its conditions. Additional 303 | permissions that are applicable to the entire Program shall be treated as though they 304 | were included in this License, to the extent that they are valid under applicable 305 | law. If additional permissions apply only to part of the Program, that part may be 306 | used separately under those permissions, but the entire Program remains governed by 307 | this License without regard to the additional permissions. 308 | 309 | When you convey a copy of a covered work, you may at your option remove any 310 | additional permissions from that copy, or from any part of it. (Additional 311 | permissions may be written to require their own removal in certain cases when you 312 | modify the work.) You may place additional permissions on material, added by you to a 313 | covered work, for which you have or can give appropriate copyright permission. 314 | 315 | Notwithstanding any other provision of this License, for material you add to a 316 | covered work, you may (if authorized by the copyright holders of that material) 317 | supplement the terms of this License with terms: 318 | 319 | * **a)** Disclaiming warranty or limiting liability differently from the terms of 320 | sections 15 and 16 of this License; or 321 | * **b)** Requiring preservation of specified reasonable legal notices or author 322 | attributions in that material or in the Appropriate Legal Notices displayed by works 323 | containing it; or 324 | * **c)** Prohibiting misrepresentation of the origin of that material, or requiring that 325 | modified versions of such material be marked in reasonable ways as different from the 326 | original version; or 327 | * **d)** Limiting the use for publicity purposes of names of licensors or authors of the 328 | material; or 329 | * **e)** Declining to grant rights under trademark law for use of some trade names, 330 | trademarks, or service marks; or 331 | * **f)** Requiring indemnification of licensors and authors of that material by anyone 332 | who conveys the material (or modified versions of it) with contractual assumptions of 333 | liability to the recipient, for any liability that these contractual assumptions 334 | directly impose on those licensors and authors. 335 | 336 | All other non-permissive additional terms are considered “further 337 | restrictions” within the meaning of section 10. If the Program as you received 338 | it, or any part of it, contains a notice stating that it is governed by this License 339 | along with a term that is a further restriction, you may remove that term. If a 340 | license document contains a further restriction but permits relicensing or conveying 341 | under this License, you may add to a covered work material governed by the terms of 342 | that license document, provided that the further restriction does not survive such 343 | relicensing or conveying. 344 | 345 | If you add terms to a covered work in accord with this section, you must place, in 346 | the relevant source files, a statement of the additional terms that apply to those 347 | files, or a notice indicating where to find the applicable terms. 348 | 349 | Additional terms, permissive or non-permissive, may be stated in the form of a 350 | separately written license, or stated as exceptions; the above requirements apply 351 | either way. 352 | 353 | ### 8. Termination 354 | 355 | You may not propagate or modify a covered work except as expressly provided under 356 | this License. Any attempt otherwise to propagate or modify it is void, and will 357 | automatically terminate your rights under this License (including any patent licenses 358 | granted under the third paragraph of section 11). 359 | 360 | However, if you cease all violation of this License, then your license from a 361 | particular copyright holder is reinstated **(a)** provisionally, unless and until the 362 | copyright holder explicitly and finally terminates your license, and **(b)** permanently, 363 | if the copyright holder fails to notify you of the violation by some reasonable means 364 | prior to 60 days after the cessation. 365 | 366 | Moreover, your license from a particular copyright holder is reinstated permanently 367 | if the copyright holder notifies you of the violation by some reasonable means, this 368 | is the first time you have received notice of violation of this License (for any 369 | work) from that copyright holder, and you cure the violation prior to 30 days after 370 | your receipt of the notice. 371 | 372 | Termination of your rights under this section does not terminate the licenses of 373 | parties who have received copies or rights from you under this License. If your 374 | rights have been terminated and not permanently reinstated, you do not qualify to 375 | receive new licenses for the same material under section 10. 376 | 377 | ### 9. Acceptance Not Required for Having Copies 378 | 379 | You are not required to accept this License in order to receive or run a copy of the 380 | Program. Ancillary propagation of a covered work occurring solely as a consequence of 381 | using peer-to-peer transmission to receive a copy likewise does not require 382 | acceptance. However, nothing other than this License grants you permission to 383 | propagate or modify any covered work. These actions infringe copyright if you do not 384 | accept this License. Therefore, by modifying or propagating a covered work, you 385 | indicate your acceptance of this License to do so. 386 | 387 | ### 10. Automatic Licensing of Downstream Recipients 388 | 389 | Each time you convey a covered work, the recipient automatically receives a license 390 | from the original licensors, to run, modify and propagate that work, subject to this 391 | License. You are not responsible for enforcing compliance by third parties with this 392 | License. 393 | 394 | An “entity transaction” is a transaction transferring control of an 395 | organization, or substantially all assets of one, or subdividing an organization, or 396 | merging organizations. If propagation of a covered work results from an entity 397 | transaction, each party to that transaction who receives a copy of the work also 398 | receives whatever licenses to the work the party's predecessor in interest had or 399 | could give under the previous paragraph, plus a right to possession of the 400 | Corresponding Source of the work from the predecessor in interest, if the predecessor 401 | has it or can get it with reasonable efforts. 402 | 403 | You may not impose any further restrictions on the exercise of the rights granted or 404 | affirmed under this License. For example, you may not impose a license fee, royalty, 405 | or other charge for exercise of rights granted under this License, and you may not 406 | initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging 407 | that any patent claim is infringed by making, using, selling, offering for sale, or 408 | importing the Program or any portion of it. 409 | 410 | ### 11. Patents 411 | 412 | A “contributor” is a copyright holder who authorizes use under this 413 | License of the Program or a work on which the Program is based. The work thus 414 | licensed is called the contributor's “contributor version”. 415 | 416 | A contributor's “essential patent claims” are all patent claims owned or 417 | controlled by the contributor, whether already acquired or hereafter acquired, that 418 | would be infringed by some manner, permitted by this License, of making, using, or 419 | selling its contributor version, but do not include claims that would be infringed 420 | only as a consequence of further modification of the contributor version. For 421 | purposes of this definition, “control” includes the right to grant patent 422 | sublicenses in a manner consistent with the requirements of this License. 423 | 424 | Each contributor grants you a non-exclusive, worldwide, royalty-free patent license 425 | under the contributor's essential patent claims, to make, use, sell, offer for sale, 426 | import and otherwise run, modify and propagate the contents of its contributor 427 | version. 428 | 429 | In the following three paragraphs, a “patent license” is any express 430 | agreement or commitment, however denominated, not to enforce a patent (such as an 431 | express permission to practice a patent or covenant not to sue for patent 432 | infringement). To “grant” such a patent license to a party means to make 433 | such an agreement or commitment not to enforce a patent against the party. 434 | 435 | If you convey a covered work, knowingly relying on a patent license, and the 436 | Corresponding Source of the work is not available for anyone to copy, free of charge 437 | and under the terms of this License, through a publicly available network server or 438 | other readily accessible means, then you must either **(1)** cause the Corresponding 439 | Source to be so available, or **(2)** arrange to deprive yourself of the benefit of the 440 | patent license for this particular work, or **(3)** arrange, in a manner consistent with 441 | the requirements of this License, to extend the patent license to downstream 442 | recipients. “Knowingly relying” means you have actual knowledge that, but 443 | for the patent license, your conveying the covered work in a country, or your 444 | recipient's use of the covered work in a country, would infringe one or more 445 | identifiable patents in that country that you have reason to believe are valid. 446 | 447 | If, pursuant to or in connection with a single transaction or arrangement, you 448 | convey, or propagate by procuring conveyance of, a covered work, and grant a patent 449 | license to some of the parties receiving the covered work authorizing them to use, 450 | propagate, modify or convey a specific copy of the covered work, then the patent 451 | license you grant is automatically extended to all recipients of the covered work and 452 | works based on it. 453 | 454 | A patent license is “discriminatory” if it does not include within the 455 | scope of its coverage, prohibits the exercise of, or is conditioned on the 456 | non-exercise of one or more of the rights that are specifically granted under this 457 | License. You may not convey a covered work if you are a party to an arrangement with 458 | a third party that is in the business of distributing software, under which you make 459 | payment to the third party based on the extent of your activity of conveying the 460 | work, and under which the third party grants, to any of the parties who would receive 461 | the covered work from you, a discriminatory patent license **(a)** in connection with 462 | copies of the covered work conveyed by you (or copies made from those copies), or **(b)** 463 | primarily for and in connection with specific products or compilations that contain 464 | the covered work, unless you entered into that arrangement, or that patent license 465 | was granted, prior to 28 March 2007. 466 | 467 | Nothing in this License shall be construed as excluding or limiting any implied 468 | license or other defenses to infringement that may otherwise be available to you 469 | under applicable patent law. 470 | 471 | ### 12. No Surrender of Others' Freedom 472 | 473 | If conditions are imposed on you (whether by court order, agreement or otherwise) 474 | that contradict the conditions of this License, they do not excuse you from the 475 | conditions of this License. If you cannot convey a covered work so as to satisfy 476 | simultaneously your obligations under this License and any other pertinent 477 | obligations, then as a consequence you may not convey it at all. For example, if you 478 | agree to terms that obligate you to collect a royalty for further conveying from 479 | those to whom you convey the Program, the only way you could satisfy both those terms 480 | and this License would be to refrain entirely from conveying the Program. 481 | 482 | ### 13. Use with the GNU Affero General Public License 483 | 484 | Notwithstanding any other provision of this License, you have permission to link or 485 | combine any covered work with a work licensed under version 3 of the GNU Affero 486 | General Public License into a single combined work, and to convey the resulting work. 487 | The terms of this License will continue to apply to the part which is the covered 488 | work, but the special requirements of the GNU Affero General Public License, section 489 | 13, concerning interaction through a network will apply to the combination as such. 490 | 491 | ### 14. Revised Versions of this License 492 | 493 | The Free Software Foundation may publish revised and/or new versions of the GNU 494 | General Public License from time to time. Such new versions will be similar in spirit 495 | to the present version, but may differ in detail to address new problems or concerns. 496 | 497 | Each version is given a distinguishing version number. If the Program specifies that 498 | a certain numbered version of the GNU General Public License “or any later 499 | version” applies to it, you have the option of following the terms and 500 | conditions either of that numbered version or of any later version published by the 501 | Free Software Foundation. If the Program does not specify a version number of the GNU 502 | General Public License, you may choose any version ever published by the Free 503 | Software Foundation. 504 | 505 | If the Program specifies that a proxy can decide which future versions of the GNU 506 | General Public License can be used, that proxy's public statement of acceptance of a 507 | version permanently authorizes you to choose that version for the Program. 508 | 509 | Later license versions may give you additional or different permissions. However, no 510 | additional obligations are imposed on any author or copyright holder as a result of 511 | your choosing to follow a later version. 512 | 513 | ### 15. Disclaimer of Warranty 514 | 515 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 516 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 517 | PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER 518 | EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 519 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE 520 | QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE 521 | DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 522 | 523 | ### 16. Limitation of Liability 524 | 525 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY 526 | COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS 527 | PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, 528 | INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 529 | PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE 530 | OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE 531 | WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 532 | POSSIBILITY OF SUCH DAMAGES. 533 | 534 | ### 17. Interpretation of Sections 15 and 16 535 | 536 | If the disclaimer of warranty and limitation of liability provided above cannot be 537 | given local legal effect according to their terms, reviewing courts shall apply local 538 | law that most closely approximates an absolute waiver of all civil liability in 539 | connection with the Program, unless a warranty or assumption of liability accompanies 540 | a copy of the Program in return for a fee. 541 | 542 | _END OF TERMS AND CONDITIONS_ 543 | 544 | ## How to Apply These Terms to Your New Programs 545 | 546 | If you develop a new program, and you want it to be of the greatest possible use to 547 | the public, the best way to achieve this is to make it free software which everyone 548 | can redistribute and change under these terms. 549 | 550 | To do so, attach the following notices to the program. It is safest to attach them 551 | to the start of each source file to most effectively state the exclusion of warranty; 552 | and each file should have at least the “copyright” line and a pointer to 553 | where the full notice is found. 554 | 555 | 556 | Copyright (C) 557 | 558 | This program is free software: you can redistribute it and/or modify 559 | it under the terms of the GNU General Public License as published by 560 | the Free Software Foundation, either version 3 of the License, or 561 | (at your option) any later version. 562 | 563 | This program is distributed in the hope that it will be useful, 564 | but WITHOUT ANY WARRANTY; without even the implied warranty of 565 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 566 | GNU General Public License for more details. 567 | 568 | You should have received a copy of the GNU General Public License 569 | along with this program. If not, see . 570 | 571 | Also add information on how to contact you by electronic and paper mail. 572 | 573 | If the program does terminal interaction, make it output a short notice like this 574 | when it starts in an interactive mode: 575 | 576 | Copyright (C) 577 | This program comes with ABSOLUTELY NO WARRANTY; for details type 'show w'. 578 | This is free software, and you are welcome to redistribute it 579 | under certain conditions; type 'show c' for details. 580 | 581 | The hypothetical commands `show w` and `show c` should show the appropriate parts of 582 | the General Public License. Of course, your program's commands might be different; 583 | for a GUI interface, you would use an “about box”. 584 | 585 | You should also get your employer (if you work as a programmer) or school, if any, to 586 | sign a “copyright disclaimer” for the program, if necessary. For more 587 | information on this, and how to apply and follow the GNU GPL, see 588 | <>. 589 | 590 | The GNU General Public License does not permit incorporating your program into 591 | proprietary programs. If your program is a subroutine library, you may consider it 592 | more useful to permit linking proprietary applications with the library. If this is 593 | what you want to do, use the GNU Lesser General Public License instead of this 594 | License. But first, please read 595 | <>. 596 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java Decompiler 2 | 3 | [![Build](https://github.com/Caellian/java-decompiler/workflows/Build/badge.svg)](https://github.com/Caellian/java-decompiler/actions?query=workflow%3A%22Build%22) 4 | 5 | A modern and very efficient Java decompiler. 6 | 7 | ## Developement 8 | 9 | It can read a decompile class files. 10 | Pattern matching doesn't cover all instructions yet so a lot of the output is commented out assembly. 11 | 12 | Most command line arguments aren't properly handled yet. 13 | 14 | ### Testing 15 | 16 | In order to run the test suite `JAVA_HOME` environment variable must be set or `javac` must be in path. 17 | 18 | ## JVM support status 19 | 20 | Latest version is currently being written to support any semi-recent Java version. 21 | At a later stage, I'd like to go through [specs](https://docs.oracle.com/javase/specs/) and allow targetting different versions better. 22 | 23 | ## License 24 | 25 | This project is licensed under the GPL license, version 3. 26 | A copy of the GPL license is provided in the [LICENSE.md](LICENSE.md) file. 27 | -------------------------------------------------------------------------------- /class_format/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jvm-class-format" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | byteorder = "1.4" 10 | 11 | num_enum = "0.7" 12 | bitflags = "2.2" 13 | 14 | ordered-float = "4.1" 15 | 16 | paste = "1.0" 17 | 18 | log = { version = "0.4", features = ["release_max_level_info"] } 19 | 20 | thiserror = "1.0" 21 | -------------------------------------------------------------------------------- /class_format/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Tin Švagelj 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | -------------------------------------------------------------------------------- /class_format/README.md: -------------------------------------------------------------------------------- 1 | # Java .class file format 2 | 3 | This library loads .class files contained in Jar files. 4 | 5 | ## Progress 6 | 7 | Can properly load `.class` files. 8 | 9 | Some areas where the library can be improved upon are: 10 | - Some Attributes are missing 11 | - 12 | 13 | ## License 14 | This library is licensed under MIT license. 15 | A copy of the MIT license is provided in the [LICENSE.md](LICENSE.md) file. 16 | -------------------------------------------------------------------------------- /class_format/src/access_flags.rs: -------------------------------------------------------------------------------- 1 | use crate::error::AccessFlagError; 2 | use byteorder::{ReadBytesExt, BE}; 3 | use std::io::Read; 4 | 5 | bitflags::bitflags! { 6 | /// Access flags used by classes and class members. 7 | #[derive(Debug, Clone, Copy)] 8 | pub struct AccessFlags: u16 { 9 | /// Declared public; may be accessed from outside its package. 10 | const PUBLIC = 0x0001; 11 | 12 | /// Declared private; accessible only within the 13 | /// defining class and other classes belonging to the same nest. 14 | const PRIVATE = 0x0002; 15 | 16 | /// Declared protected; may be accessed within subclasses. 17 | const PROTECTED = 0x0004; 18 | 19 | /// Declared static. 20 | const STATIC = 0x0008; 21 | 22 | /// Declared final; never directly assigned to after object construction. 23 | const FINAL = 0x0010; 24 | 25 | /// Treat superclass methods specially when invoked by 26 | /// the invokespecial instruction. 27 | const SUPER = 0x0020; 28 | 29 | /// Declared final; no subclasses allowed. 30 | const VOLATILE = 0x0040; 31 | 32 | /// Declared transient; not written or read by a 33 | /// persistent object manager. 34 | const TRANSIENT = 0x0080; 35 | 36 | /// Declared native; implemented in a language other 37 | /// than the Java programming language. 38 | const NATIVE = 0x0100; 39 | 40 | /// Is an interface, not a class. 41 | const INTERFACE = 0x0200; 42 | 43 | /// Declared abstract; must not be instantiated. 44 | const ABSTRACT = 0x0400; 45 | 46 | /// Declared strictfp; floating-point mode is FP-strict. 47 | const STRICT = 0x0800; 48 | 49 | /// Declared synthetic; generated by compiler (not in source code). 50 | const SYNTHETIC = 0x1000; 51 | 52 | /// Declared as an annotation interface. 53 | const ANNOTATION = 0x2000; 54 | 55 | /// Declared as an enum class. 56 | const ENUM = 0x4000; 57 | 58 | /// Is a module, not a class or interface. 59 | const MODULE = 0x8000; 60 | } 61 | } 62 | 63 | impl AccessFlags { 64 | pub fn read_from(r: &mut R) -> Result { 65 | let found = r.read_u16::()?; 66 | AccessFlags::from_bits(found).ok_or(AccessFlagError::InvalidValue { found }) 67 | } 68 | } 69 | 70 | impl Default for AccessFlags { 71 | fn default() -> Self { 72 | AccessFlags::empty() 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /class_format/src/attribute.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{ReadBytesExt, BE}; 2 | use paste::paste; 3 | use std::{ 4 | collections::HashMap, 5 | io::{Cursor, Read}, 6 | }; 7 | 8 | use crate::{error::AttributeError, ext::ReadByteVecExt, Constant, ConstantPool}; 9 | 10 | macro_rules! flat_entry { 11 | ($name:ident {$($entry:ident:$entry_t:ty,)+}) => { 12 | #[derive(Debug, Clone)] 13 | pub struct $name { 14 | $( 15 | pub $entry: $entry_t 16 | ),+ 17 | } 18 | 19 | impl $name { 20 | pub fn read_from(r: &mut R) -> Result<$name, std::io::Error> { 21 | Ok($name { 22 | $( 23 | $entry: paste! { r.[]::()? } 24 | ),+ 25 | }) 26 | } 27 | } 28 | }; 29 | } 30 | 31 | flat_entry!(ExceptionTableEntry { 32 | start_pc: u16, 33 | end_pc: u16, 34 | handler_pc: u16, 35 | catch_type: u16, 36 | }); 37 | 38 | flat_entry!(LineNumber { 39 | start_pc: u16, 40 | line_number: u16, 41 | }); 42 | 43 | flat_entry!(InnerClass { 44 | inner_class_info_index: u16, 45 | outer_class_info_index: u16, 46 | inner_name_index: u16, 47 | inner_class_access_flags: u16, 48 | }); 49 | 50 | flat_entry!(LocalVariable { 51 | start_pc: u16, 52 | length: u16, 53 | name_index: u16, 54 | descriptor_index: u16, 55 | index: u16, 56 | }); 57 | 58 | flat_entry!(LocalVariableType { 59 | start_pc: u16, 60 | length: u16, 61 | name_index: u16, 62 | signature_index: u16, 63 | index: u16, 64 | }); 65 | 66 | flat_entry!(MethodParameter { 67 | name_index: u16, 68 | access_flags: u16, 69 | }); 70 | 71 | pub struct Annotation { 72 | pub type_index: u16, 73 | } 74 | 75 | pub trait Attribute: Into + Sized { 76 | const NAME: &'static str; 77 | 78 | fn read_data( 79 | r: &mut R, 80 | constant_pool: &ConstantPool, 81 | ) -> Result; 82 | } 83 | 84 | pub trait AsData { 85 | fn as_data(&self) -> Result<&D, AttributeError>; 86 | } 87 | 88 | #[derive(Debug, Clone)] 89 | pub struct CodeData { 90 | pub max_stack: usize, 91 | pub max_locals: usize, 92 | pub code: Vec, 93 | pub exception_table: Vec, 94 | pub attributes: HashMap, 95 | } 96 | 97 | impl Attribute for CodeData { 98 | const NAME: &'static str = "Code"; 99 | 100 | #[inline] 101 | fn read_data( 102 | r: &mut R, 103 | constant_pool: &ConstantPool, 104 | ) -> Result { 105 | let max_stack = r.read_u16::()? as usize; 106 | let max_locals = r.read_u16::()? as usize; 107 | 108 | let code_length = r.read_u32::()? as usize; 109 | let code = r.read_byte_vec(code_length)?; 110 | 111 | let exception_table_length = r.read_u16::()? as usize; 112 | let mut exception_table = Vec::with_capacity(exception_table_length); 113 | 114 | for _ in 0..exception_table_length { 115 | exception_table.push(ExceptionTableEntry::read_from(r)?) 116 | } 117 | 118 | let attributes = AttributeValue::read_all(r, constant_pool)?; 119 | 120 | Ok(CodeData { 121 | max_stack, 122 | max_locals, 123 | code, 124 | exception_table, 125 | attributes, 126 | }) 127 | } 128 | } 129 | 130 | impl AsData for AttributeValue { 131 | fn as_data(&self) -> Result<&CodeData, AttributeError> { 132 | match self { 133 | AttributeValue::Code(it) => Ok(it), 134 | _ => Err(AttributeError::InvalidData), 135 | } 136 | } 137 | } 138 | 139 | impl From for AttributeValue { 140 | fn from(value: CodeData) -> Self { 141 | AttributeValue::Code(value) 142 | } 143 | } 144 | 145 | #[derive(Debug, Clone)] 146 | pub struct ExceptionData { 147 | pub exceptions: Vec, 148 | } 149 | 150 | impl Attribute for ExceptionData { 151 | const NAME: &'static str = "Exceptions"; 152 | 153 | #[inline] 154 | fn read_data( 155 | r: &mut R, 156 | _constant_pool: &ConstantPool, 157 | ) -> Result { 158 | let number_of_exceptions = r.read_u16::()? as usize; 159 | let mut exceptions = Vec::with_capacity(number_of_exceptions); 160 | 161 | for _ in 0..number_of_exceptions { 162 | exceptions.push(r.read_u16::()? as usize); 163 | } 164 | 165 | Ok(ExceptionData { exceptions }) 166 | } 167 | } 168 | 169 | impl AsData for AttributeValue { 170 | fn as_data(&self) -> Result<&ExceptionData, AttributeError> { 171 | match self { 172 | AttributeValue::Exceptions(it) => Ok(it), 173 | _ => Err(AttributeError::InvalidData), 174 | } 175 | } 176 | } 177 | 178 | impl From for AttributeValue { 179 | fn from(value: ExceptionData) -> Self { 180 | AttributeValue::Exceptions(value) 181 | } 182 | } 183 | 184 | #[derive(Debug, Clone)] 185 | pub struct InnerClassData { 186 | pub classes: Vec, 187 | } 188 | 189 | impl Attribute for InnerClassData { 190 | const NAME: &'static str = "InnerClasses"; 191 | 192 | #[inline] 193 | fn read_data( 194 | r: &mut R, 195 | _constant_pool: &ConstantPool, 196 | ) -> Result { 197 | let number_of_classes = r.read_u16::()? as usize; 198 | let mut classes = Vec::with_capacity(number_of_classes); 199 | 200 | for _ in 0..number_of_classes { 201 | classes.push(InnerClass::read_from(r)?); 202 | } 203 | 204 | Ok(InnerClassData { classes }) 205 | } 206 | } 207 | 208 | impl AsData for AttributeValue { 209 | fn as_data(&self) -> Result<&InnerClassData, AttributeError> { 210 | match self { 211 | AttributeValue::InnerClasses(it) => Ok(it), 212 | _ => Err(AttributeError::InvalidData), 213 | } 214 | } 215 | } 216 | 217 | impl From for AttributeValue { 218 | fn from(value: InnerClassData) -> Self { 219 | AttributeValue::InnerClasses(value) 220 | } 221 | } 222 | 223 | #[derive(Debug, Clone)] 224 | pub struct LineNumberTable { 225 | pub table: Vec, 226 | } 227 | 228 | impl Attribute for LineNumberTable { 229 | const NAME: &'static str = "LineNumberTable"; 230 | 231 | #[inline] 232 | fn read_data( 233 | r: &mut R, 234 | _constant_pool: &ConstantPool, 235 | ) -> Result { 236 | let line_number_table_length = r.read_u16::()? as usize; 237 | let mut table = Vec::with_capacity(line_number_table_length); 238 | 239 | for _ in 0..line_number_table_length { 240 | table.push(LineNumber::read_from(r)?); 241 | } 242 | 243 | Ok(LineNumberTable { table }) 244 | } 245 | } 246 | 247 | impl AsData for AttributeValue { 248 | fn as_data(&self) -> Result<&LineNumberTable, AttributeError> { 249 | match self { 250 | AttributeValue::LineNumberTable(it) => Ok(it), 251 | _ => Err(AttributeError::InvalidData), 252 | } 253 | } 254 | } 255 | 256 | impl From for AttributeValue { 257 | fn from(value: LineNumberTable) -> Self { 258 | AttributeValue::LineNumberTable(value) 259 | } 260 | } 261 | 262 | #[derive(Debug, Clone)] 263 | pub struct LocalVariableTable { 264 | pub table: Vec, 265 | } 266 | 267 | impl Attribute for LocalVariableTable { 268 | const NAME: &'static str = "LocalVariableTable"; 269 | 270 | fn read_data( 271 | r: &mut R, 272 | _constant_pool: &ConstantPool, 273 | ) -> Result { 274 | let local_variable_table_length = r.read_u16::()? as usize; 275 | let mut table = Vec::with_capacity(local_variable_table_length); 276 | 277 | for _ in 0..local_variable_table_length { 278 | table.push(LocalVariable::read_from(r)?); 279 | } 280 | 281 | Ok(LocalVariableTable { table }) 282 | } 283 | } 284 | 285 | impl AsData for AttributeValue { 286 | fn as_data(&self) -> Result<&LocalVariableTable, AttributeError> { 287 | match self { 288 | AttributeValue::LocalVariableTable(it) => Ok(it), 289 | _ => Err(AttributeError::InvalidData), 290 | } 291 | } 292 | } 293 | 294 | impl From for AttributeValue { 295 | fn from(value: LocalVariableTable) -> Self { 296 | AttributeValue::LocalVariableTable(value) 297 | } 298 | } 299 | 300 | #[derive(Debug, Clone)] 301 | pub struct AnnotationDefaultData { 302 | pub default: Vec, 303 | } 304 | 305 | impl Attribute for AnnotationDefaultData { 306 | const NAME: &'static str = "AnnotationDefault"; 307 | 308 | fn read_data( 309 | r: &mut R, 310 | _constant_pool: &ConstantPool, 311 | ) -> Result { 312 | let mut default = Vec::with_capacity(256); 313 | r.read_to_end(&mut default)?; 314 | Ok(AnnotationDefaultData { default }) 315 | } 316 | } 317 | 318 | impl AsData for AttributeValue { 319 | fn as_data(&self) -> Result<&AnnotationDefaultData, AttributeError> { 320 | match self { 321 | AttributeValue::AnnotationDefault(it) => Ok(it), 322 | _ => Err(AttributeError::InvalidData), 323 | } 324 | } 325 | } 326 | 327 | impl From for AttributeValue { 328 | fn from(value: AnnotationDefaultData) -> Self { 329 | AttributeValue::AnnotationDefault(value) 330 | } 331 | } 332 | 333 | #[derive(Debug, Clone)] 334 | pub struct EnclosingMethodData { 335 | pub class_index: usize, 336 | pub method_index: usize, 337 | } 338 | 339 | impl Attribute for EnclosingMethodData { 340 | const NAME: &'static str = "EnclosingMethod"; 341 | 342 | fn read_data( 343 | r: &mut R, 344 | _constant_pool: &ConstantPool, 345 | ) -> Result { 346 | Ok(EnclosingMethodData { 347 | class_index: r.read_u16::()? as usize, 348 | method_index: r.read_u16::()? as usize, 349 | }) 350 | } 351 | } 352 | 353 | impl AsData for AttributeValue { 354 | fn as_data(&self) -> Result<&EnclosingMethodData, AttributeError> { 355 | match self { 356 | AttributeValue::EnclosingMethod(it) => Ok(it), 357 | _ => Err(AttributeError::InvalidData), 358 | } 359 | } 360 | } 361 | 362 | impl From for AttributeValue { 363 | fn from(value: EnclosingMethodData) -> Self { 364 | AttributeValue::EnclosingMethod(value) 365 | } 366 | } 367 | 368 | #[derive(Debug, Clone)] 369 | pub struct LocalVariableTypeTable { 370 | pub table: Vec, 371 | } 372 | 373 | impl Attribute for LocalVariableTypeTable { 374 | const NAME: &'static str = "LocalVariableTypeTable"; 375 | 376 | fn read_data( 377 | r: &mut R, 378 | _constant_pool: &ConstantPool, 379 | ) -> Result { 380 | let local_variable_type_table_length = r.read_u16::()? as usize; 381 | let mut table = Vec::with_capacity(local_variable_type_table_length); 382 | 383 | for _ in 0..local_variable_type_table_length { 384 | table.push(LocalVariableType::read_from(r)?); 385 | } 386 | 387 | Ok(LocalVariableTypeTable { table }) 388 | } 389 | } 390 | 391 | impl AsData for AttributeValue { 392 | fn as_data(&self) -> Result<&LocalVariableTypeTable, AttributeError> { 393 | match self { 394 | AttributeValue::LocalVariableTypeTable(it) => Ok(it), 395 | _ => Err(AttributeError::InvalidData), 396 | } 397 | } 398 | } 399 | 400 | impl From for AttributeValue { 401 | fn from(value: LocalVariableTypeTable) -> Self { 402 | AttributeValue::LocalVariableTypeTable(value) 403 | } 404 | } 405 | 406 | #[derive(Debug, Clone)] 407 | pub struct SignatureData { 408 | pub signature: String, 409 | } 410 | 411 | impl Attribute for SignatureData { 412 | const NAME: &'static str = "Signature"; 413 | fn read_data( 414 | r: &mut R, 415 | constant_pool: &ConstantPool, 416 | ) -> Result { 417 | let index = r.read_u16::()? as usize; 418 | 419 | match constant_pool.try_get(index)? { 420 | Constant::Utf8 { value } => Ok(SignatureData { 421 | signature: value.clone(), 422 | }), 423 | _ => return Err(AttributeError::InvalidData), 424 | } 425 | } 426 | } 427 | 428 | impl AsData for AttributeValue { 429 | fn as_data(&self) -> Result<&SignatureData, AttributeError> { 430 | match self { 431 | AttributeValue::Signature(it) => Ok(it), 432 | _ => Err(AttributeError::InvalidData), 433 | } 434 | } 435 | } 436 | 437 | impl From for AttributeValue { 438 | fn from(value: SignatureData) -> Self { 439 | AttributeValue::Signature(value) 440 | } 441 | } 442 | 443 | #[derive(Debug, Clone)] 444 | pub struct MethodParameterData { 445 | pub parameters: Vec, 446 | } 447 | 448 | impl Attribute for MethodParameterData { 449 | const NAME: &'static str = "MethodParameters"; 450 | 451 | fn read_data( 452 | r: &mut R, 453 | _constant_pool: &ConstantPool, 454 | ) -> Result { 455 | let length = r.read_u8()? as usize; 456 | let mut parameters = Vec::with_capacity(length); 457 | for _ in 0..length { 458 | let parameter = MethodParameter::read_from(r)?; 459 | parameters.push(parameter); 460 | } 461 | 462 | Ok(MethodParameterData { parameters }) 463 | } 464 | } 465 | 466 | impl AsData for AttributeValue { 467 | fn as_data(&self) -> Result<&MethodParameterData, AttributeError> { 468 | match self { 469 | AttributeValue::MethodParameters(it) => Ok(it), 470 | _ => Err(AttributeError::InvalidData), 471 | } 472 | } 473 | } 474 | 475 | impl From for AttributeValue { 476 | fn from(value: MethodParameterData) -> Self { 477 | AttributeValue::MethodParameters(value) 478 | } 479 | } 480 | 481 | #[derive(Debug, Clone)] 482 | pub enum AttributeValue { 483 | Unknown { name: String, data: Vec }, 484 | Code(CodeData), 485 | ConstantValue(u16), 486 | Deprecated, 487 | Exceptions(ExceptionData), 488 | InnerClasses(InnerClassData), 489 | LineNumberTable(LineNumberTable), 490 | LocalVariableTable(LocalVariableTable), 491 | SourceFile(u16), 492 | Synthetic, 493 | AnnotationDefault(AnnotationDefaultData), 494 | EnclosingMethod(EnclosingMethodData), 495 | LocalVariableTypeTable(LocalVariableTypeTable), 496 | RuntimeVisibleAnnotations, 497 | RuntimeInvisibleAnnotations, 498 | RuntimeVisibleParameterAnnotations, 499 | RuntimeInvisibleParameterAnnotations, 500 | Signature(SignatureData), 501 | SourceDebugExtension, 502 | StackMapTable, 503 | BootstrapMethods, 504 | MethodParameters(MethodParameterData), 505 | RuntimeInvisibleTypeAnnotations, 506 | RuntimeVisibleTypeAnnotations, 507 | Module, 508 | ModuleMainClass, 509 | ModulePackages, 510 | NestHost, 511 | NestMembers, 512 | } 513 | 514 | impl AttributeValue { 515 | pub fn read_from( 516 | r: &mut R, 517 | constant_pool: &ConstantPool, 518 | ) -> Result<(String, AttributeValue), AttributeError> { 519 | let name_i = r.read_u16::()? as usize; 520 | 521 | let name = match constant_pool.try_get(name_i)? { 522 | Constant::Utf8 { value } => value.clone(), 523 | _ => return Err(AttributeError::InvalidNameType), 524 | }; 525 | 526 | let length = r.read_u32::()? as usize; 527 | let data = r.read_byte_vec(length)?; 528 | 529 | let value = Self::from_name_and_data(&name, &data, constant_pool)?; 530 | 531 | Ok((name, value)) 532 | } 533 | 534 | pub fn read_all( 535 | r: &mut R, 536 | constant_pool: &ConstantPool, 537 | ) -> Result, AttributeError> { 538 | let attributes_count = r.read_u16::()? as usize; 539 | let mut attributes = HashMap::with_capacity(attributes_count); 540 | 541 | for _ in 0..attributes_count { 542 | let (name, attrib) = AttributeValue::read_from(r, constant_pool)?; 543 | attributes.insert(name, attrib); 544 | } 545 | 546 | Ok(attributes) 547 | } 548 | 549 | pub fn from_name_and_data( 550 | name: impl AsRef, 551 | data: &[u8], 552 | constant_pool: &ConstantPool, 553 | ) -> Result { 554 | let mut r = Cursor::new(data); 555 | 556 | Ok(match name.as_ref() { 557 | "Code" => AttributeValue::Code(CodeData::read_data(&mut r, constant_pool)?), 558 | "ConstantValue" => AttributeValue::ConstantValue(r.read_u16::()?), 559 | "Deprecated" => AttributeValue::Deprecated, 560 | "Exceptions" => { 561 | AttributeValue::Exceptions(ExceptionData::read_data(&mut r, constant_pool)?) 562 | } 563 | "InnerClasses" => { 564 | AttributeValue::InnerClasses(InnerClassData::read_data(&mut r, constant_pool)?) 565 | } 566 | "Signature" => { 567 | AttributeValue::Signature(SignatureData::read_data(&mut r, constant_pool)?) 568 | } 569 | "LineNumberTable" => { 570 | AttributeValue::LineNumberTable(LineNumberTable::read_data(&mut r, constant_pool)?) 571 | } 572 | "LocalVariableTable" => AttributeValue::LocalVariableTable( 573 | LocalVariableTable::read_data(&mut r, constant_pool)?, 574 | ), 575 | "SourceFile" => AttributeValue::SourceFile(r.read_u16::()?), 576 | "Synthetic" => AttributeValue::Synthetic, 577 | "AnnotationDefault" => AttributeValue::AnnotationDefault( 578 | AnnotationDefaultData::read_data(&mut r, constant_pool)?, 579 | ), 580 | "EnclosingMethod" => AttributeValue::EnclosingMethod(EnclosingMethodData::read_data( 581 | &mut r, 582 | constant_pool, 583 | )?), 584 | "LocalVariableTypeTable" => AttributeValue::LocalVariableTypeTable( 585 | LocalVariableTypeTable::read_data(&mut r, constant_pool)?, 586 | ), 587 | "MethodParameters" => AttributeValue::MethodParameters(MethodParameterData::read_data( 588 | &mut r, 589 | constant_pool, 590 | )?), 591 | other => AttributeValue::Unknown { 592 | name: other.to_string(), 593 | data: data.to_vec(), 594 | }, 595 | }) 596 | } 597 | } 598 | -------------------------------------------------------------------------------- /class_format/src/constant.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{ConstantError, ConstantPoolError}; 2 | use crate::ext::ReadByteVecExt; 3 | use byteorder::{ReadBytesExt, BE}; 4 | use num_enum::{IntoPrimitive, TryFromPrimitive}; 5 | use ordered_float::OrderedFloat; 6 | use std::collections::HashMap; 7 | use std::convert::TryFrom; 8 | use std::io::Read; 9 | 10 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, IntoPrimitive, TryFromPrimitive)] 11 | #[repr(u8)] 12 | pub enum ReferenceKind { 13 | GetField = 1, 14 | GetStatic = 2, 15 | PutField = 3, 16 | PutStatic = 4, 17 | InvokeVirtual = 5, 18 | InvokeStatic = 6, 19 | InvokeSpecial = 7, 20 | NewInvokeSpecial = 8, 21 | InvokeInterface = 9, 22 | } 23 | 24 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, IntoPrimitive, TryFromPrimitive)] 25 | #[repr(u8)] 26 | pub enum ConstantTag { 27 | Utf8 = 1, 28 | Data = 2, 29 | Integer = 3, 30 | Float = 4, 31 | Long = 5, 32 | Double = 6, 33 | Class = 7, 34 | String = 8, 35 | Fieldref = 9, 36 | Methodref = 10, 37 | InterfaceMethodref = 11, 38 | NameAndType = 12, 39 | MethodHandle = 15, 40 | MethodType = 16, 41 | Dynamic = 17, 42 | InvokeDynamic = 18, 43 | Module = 19, 44 | Package = 20, 45 | } 46 | 47 | impl ConstantTag { 48 | pub fn length(&self) -> usize { 49 | match self { 50 | ConstantTag::Long => 2, 51 | ConstantTag::Double => 2, 52 | _ => 1, 53 | } 54 | } 55 | } 56 | 57 | #[derive(Debug, Clone, Eq, PartialEq, Hash)] 58 | pub enum Constant { 59 | Class { 60 | name_index: u16, 61 | }, 62 | Fieldref { 63 | class_index: u16, 64 | name_and_type_info: u16, 65 | }, 66 | Methodref { 67 | class_index: u16, 68 | name_and_type_info: u16, 69 | }, 70 | InterfaceMethodref { 71 | class_index: u16, 72 | name_and_type_info: u16, 73 | }, 74 | String { 75 | string_index: u16, 76 | }, 77 | Integer { 78 | value: i32, 79 | }, 80 | Float { 81 | value: OrderedFloat, 82 | }, 83 | Long { 84 | value: i64, 85 | }, 86 | Double { 87 | value: OrderedFloat, 88 | }, 89 | NameAndType { 90 | name_index: u16, 91 | descriptor_index: u16, 92 | }, 93 | Utf8 { 94 | value: String, 95 | }, 96 | /// Not yet part of JVM spec. 97 | /// Suggested as replacement for never implemented Unicode tag 98 | /// to allow storing binary blobs in class files. 99 | Data { 100 | content: Vec, 101 | }, 102 | MethodHandle { 103 | reference_kind: ReferenceKind, 104 | reference_index: u16, 105 | }, 106 | MethodType { 107 | descriptor_index: u16, 108 | }, 109 | Dynamic { 110 | bootstrap_method_attr_index: u16, 111 | name_and_type_index: u16, 112 | }, 113 | InvokeDynamic { 114 | bootstrap_method_attr_index: u16, 115 | name_and_type_index: u16, 116 | }, 117 | Module { 118 | name_index: u16, 119 | }, 120 | Package { 121 | name_index: u16, 122 | }, 123 | } 124 | 125 | // Future changes: https://bugs.openjdk.java.net/browse/JDK-8161256 126 | impl Constant { 127 | pub fn tag(&self) -> ConstantTag { 128 | match self { 129 | Constant::Utf8 { .. } => ConstantTag::Utf8, 130 | Constant::Data { .. } => ConstantTag::Data, 131 | Constant::Integer { .. } => ConstantTag::Integer, 132 | Constant::Float { .. } => ConstantTag::Float, 133 | Constant::Long { .. } => ConstantTag::Long, 134 | Constant::Double { .. } => ConstantTag::Double, 135 | Constant::Class { .. } => ConstantTag::Class, 136 | Constant::String { .. } => ConstantTag::String, 137 | Constant::Fieldref { .. } => ConstantTag::Fieldref, 138 | Constant::Methodref { .. } => ConstantTag::Methodref, 139 | Constant::InterfaceMethodref { .. } => ConstantTag::InterfaceMethodref, 140 | Constant::NameAndType { .. } => ConstantTag::NameAndType, 141 | Constant::MethodHandle { .. } => ConstantTag::MethodHandle, 142 | Constant::MethodType { .. } => ConstantTag::MethodType, 143 | Constant::Dynamic { .. } => ConstantTag::Dynamic, 144 | Constant::InvokeDynamic { .. } => ConstantTag::InvokeDynamic, 145 | Constant::Module { .. } => ConstantTag::Module, 146 | Constant::Package { .. } => ConstantTag::Package, 147 | } 148 | } 149 | 150 | pub fn read_from(r: &mut R) -> Result { 151 | log::trace!("enter Constant::read_from(impl Read)"); 152 | 153 | let tag = ConstantTag::try_from(r.read_u8()?)?; 154 | 155 | Ok(match tag { 156 | ConstantTag::Utf8 => { 157 | let len = r.read_u16::()? as usize; 158 | let buff = r.read_byte_vec(len)?; 159 | 160 | Constant::Utf8 { 161 | value: String::from_utf8(buff)?, 162 | } 163 | } 164 | ConstantTag::Data => { 165 | let len = r.read_u16::()? as usize; 166 | let content = r.read_byte_vec(len)?; 167 | 168 | Constant::Data { content } 169 | } 170 | ConstantTag::Integer => Constant::Integer { 171 | value: r.read_i32::()?, 172 | }, 173 | ConstantTag::Float => Constant::Float { 174 | value: OrderedFloat::from(r.read_f32::()?), 175 | }, 176 | ConstantTag::Long => Constant::Long { 177 | value: r.read_i64::()?, 178 | }, 179 | ConstantTag::Double => Constant::Double { 180 | value: OrderedFloat::from(r.read_f64::()?), 181 | }, 182 | ConstantTag::Class => Constant::Class { 183 | name_index: r.read_u16::()?, 184 | }, 185 | ConstantTag::String => Constant::String { 186 | string_index: r.read_u16::()?, 187 | }, 188 | ConstantTag::Fieldref => Constant::Fieldref { 189 | class_index: r.read_u16::()?, 190 | name_and_type_info: r.read_u16::()?, 191 | }, 192 | ConstantTag::Methodref => Constant::Methodref { 193 | class_index: r.read_u16::()?, 194 | name_and_type_info: r.read_u16::()?, 195 | }, 196 | ConstantTag::InterfaceMethodref => Constant::InterfaceMethodref { 197 | class_index: r.read_u16::()?, 198 | name_and_type_info: r.read_u16::()?, 199 | }, 200 | ConstantTag::NameAndType => Constant::NameAndType { 201 | name_index: r.read_u16::()?, 202 | descriptor_index: r.read_u16::()?, 203 | }, 204 | ConstantTag::MethodHandle => Constant::MethodHandle { 205 | reference_kind: ReferenceKind::try_from(r.read_u8()?)?, 206 | reference_index: r.read_u16::()?, 207 | }, 208 | ConstantTag::MethodType => Constant::MethodType { 209 | descriptor_index: r.read_u16::()?, 210 | }, 211 | ConstantTag::Dynamic => Constant::Dynamic { 212 | bootstrap_method_attr_index: r.read_u16::()?, 213 | name_and_type_index: r.read_u16::()?, 214 | }, 215 | ConstantTag::InvokeDynamic => Constant::InvokeDynamic { 216 | bootstrap_method_attr_index: r.read_u16::()?, 217 | name_and_type_index: r.read_u16::()?, 218 | }, 219 | ConstantTag::Module => Constant::Module { 220 | name_index: r.read_u16::()?, 221 | }, 222 | ConstantTag::Package => Constant::Package { 223 | name_index: r.read_u16::()?, 224 | }, 225 | }) 226 | } 227 | } 228 | 229 | #[derive(Debug, Clone)] 230 | pub struct ConstantPool { 231 | pub members: HashMap, 232 | size: usize, 233 | } 234 | 235 | #[macro_export] 236 | macro_rules! constant_match { 237 | ($constant: expr, Constant::$exp: ident { $($param: ident),* } ) => { 238 | match $constant { 239 | Constant::$exp { $($param),* } => { 240 | Ok(($($param),*)) 241 | } 242 | other => { 243 | Err(ConstantPoolError::UnexpectedType { found: other.tag(), expected: ConstantTag::$exp }) 244 | } 245 | } 246 | }; 247 | ($constant: expr, Constant::$exp: ident { $($param: ident, )* .. } ) => { 248 | match $constant { 249 | Constant::$exp { $($param,)* .. } => { 250 | Ok(($($param),*)) 251 | } 252 | other => { 253 | Err(ConstantPoolError::UnexpectedType { found: other.tag(), expected: ConstantTag::$exp }) 254 | } 255 | } 256 | }; 257 | ($constant: expr, Constant::$exp: ident { $($param: ident),* } => $code: block) => { 258 | match $constant { 259 | Constant::$exp { $($param),* } => { 260 | Ok($code) 261 | } 262 | other => { 263 | Err(ConstantPoolError::UnexpectedType { found: other.tag(), expected: ConstantTag::$exp }) 264 | } 265 | } 266 | }; 267 | ($constant: expr, Constant::$exp: ident { $($param: ident,)* .. } => $code: block) => { 268 | match $constant { 269 | Constant::$exp { $($param,)* .. } => { 270 | Ok($code) 271 | } 272 | other => { 273 | Err(ConstantPoolError::UnexpectedType { found: other.tag(), expected: ConstantTag::$exp }) 274 | } 275 | } 276 | }; 277 | } 278 | 279 | impl ConstantPool { 280 | pub fn new() -> ConstantPool { 281 | ConstantPool { 282 | members: HashMap::new(), 283 | size: 1, 284 | } 285 | } 286 | 287 | pub fn with_capacity(capacity: usize) -> ConstantPool { 288 | ConstantPool { 289 | members: HashMap::with_capacity(capacity), 290 | size: 1, 291 | } 292 | } 293 | 294 | pub fn try_get(&self, index: usize) -> Result<&Constant, ConstantPoolError> { 295 | self.members 296 | .get(&index) 297 | .ok_or(ConstantPoolError::InvalidIndex { 298 | index, 299 | length: self.size, 300 | }) 301 | } 302 | 303 | pub fn get(&self, index: usize) -> &Constant { 304 | match self.members.get(&index) { 305 | Some(it) => it, 306 | None => panic!( 307 | "no constant pool member at index {} (size: {})", 308 | index, 309 | self.members.len() 310 | ), 311 | } 312 | } 313 | 314 | pub unsafe fn get_unchecked(&self, index: usize) -> &Constant { 315 | self.members.get(&index).unwrap_unchecked() 316 | } 317 | 318 | pub fn insert(&mut self, constant: Constant) { 319 | log::trace!("ConstantPool::insert(&self, {:?})", &constant); 320 | let constant_length = constant.tag().length(); 321 | self.members.insert(self.size, constant); 322 | self.size += constant_length; 323 | } 324 | 325 | pub fn size(&self) -> usize { 326 | self.size 327 | } 328 | 329 | pub fn get_class_name(&mut self, class_index: usize) -> Result { 330 | let name_i = constant_match!(self.get(class_index), Constant::Class { name_index })?; 331 | constant_match!(self.get(*name_i as usize), Constant::Utf8 { value }).cloned() 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /class_format/src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::constant::ReferenceKind; 2 | use crate::ty::JVMType; 3 | use crate::{ConstantTag, Op}; 4 | use thiserror::Error; 5 | 6 | #[derive(Error, Debug)] 7 | pub enum ConstantPoolError { 8 | #[error("accessing invalid constant pool index: {index}; length: {length}")] 9 | InvalidIndex { index: usize, length: usize }, 10 | #[error("unexpected constant type: {found:?}; expected {expected:?}")] 11 | UnexpectedType { 12 | found: ConstantTag, 13 | expected: ConstantTag, 14 | }, 15 | } 16 | 17 | #[derive(Error, Debug)] 18 | pub enum OpReadError { 19 | #[error("unknown op code: 0x{0:X}")] 20 | Unknown(u8), 21 | #[error("op '{op}' requires {expected} arguments; found {available}")] 22 | MissingArgs { 23 | op: Op, 24 | expected: usize, 25 | available: usize, 26 | }, 27 | } 28 | 29 | #[derive(Error, Debug)] 30 | pub enum ClassReadError { 31 | #[error("expected magic: 0xCAFEBABE; got 0x{found:X}")] 32 | InvalidMagic { found: u32 }, 33 | 34 | #[error(transparent)] 35 | ConstantPoolIndex(#[from] ConstantPoolError), 36 | #[error("invalid reference to class constant")] 37 | InvalidClassReference, 38 | #[error("invalid reference to class name constant")] 39 | InvalidClassNameReference, 40 | #[error("invalid reference to interface constant or name constant")] 41 | InvalidInterfaceReference, 42 | 43 | #[error(transparent)] 44 | Constant(#[from] ConstantError), 45 | #[error(transparent)] 46 | ClassPath(#[from] ClassPathError), 47 | #[error(transparent)] 48 | AccessFlag(#[from] AccessFlagError), 49 | #[error(transparent)] 50 | Member(#[from] MemberError), 51 | #[error(transparent)] 52 | Attribute(#[from] AttributeError), 53 | 54 | #[error(transparent)] 55 | IOError(#[from] std::io::Error), 56 | } 57 | 58 | #[derive(Error, Debug)] 59 | pub enum AccessFlagError { 60 | #[error("received one or more unsupported access flags 0x{found:X}")] 61 | InvalidValue { found: u16 }, 62 | 63 | #[error("io error: {0}")] 64 | IOError(#[from] std::io::Error), 65 | } 66 | 67 | #[derive(Error, Debug)] 68 | pub enum ConstantError { 69 | #[error("constant data abruptly ended")] 70 | IncompleteData, 71 | #[error("tag with ID {tag} isn't supported")] 72 | UnsupportedTag { tag: u8 }, 73 | #[error("reference kind of 0x{value:X} isn't supported")] 74 | UnsupportedReferenceKind { value: u8 }, 75 | #[error("invalid class path: {0}")] 76 | InvalidClassPath(String), 77 | 78 | #[error("utf8 error: {0}")] 79 | UTF8ParseError(#[from] std::string::FromUtf8Error), 80 | #[error("io error: {0}")] 81 | IOError(#[from] std::io::Error), 82 | } 83 | 84 | impl From> for ConstantError { 85 | fn from(primitive: num_enum::TryFromPrimitiveError) -> Self { 86 | ConstantError::UnsupportedTag { 87 | tag: primitive.number as u8, 88 | } 89 | } 90 | } 91 | 92 | impl From> for ConstantError { 93 | fn from(primitive: num_enum::TryFromPrimitiveError) -> Self { 94 | ConstantError::UnsupportedReferenceKind { 95 | value: primitive.number as u8, 96 | } 97 | } 98 | } 99 | 100 | #[derive(Error, Debug)] 101 | pub enum MemberError { 102 | #[error("class member name is pointing to a non-existent constant")] 103 | NoMemberName, 104 | #[error("class member name is pointing to a non-utf8 constant")] 105 | InvalidNameType, 106 | #[error("class member descriptor is pointing to a non-existent constant")] 107 | NoMemberDesc, 108 | 109 | #[error(transparent)] 110 | AccessFlagError(#[from] AccessFlagError), 111 | #[error(transparent)] 112 | AttributeReadError(#[from] AttributeError), 113 | #[error(transparent)] 114 | ConstantPool(#[from] ConstantPoolError), 115 | #[error(transparent)] 116 | JVMTypeError(#[from] JVMTypeError), 117 | 118 | #[error(transparent)] 119 | IOError(#[from] std::io::Error), 120 | } 121 | 122 | #[derive(Error, Debug)] 123 | pub enum AttributeError { 124 | #[error("attribute name is pointing to a non-existent constant")] 125 | NoAttribName, 126 | #[error("attribute name is pointing to a non-utf8 constant")] 127 | InvalidNameType, 128 | #[error("attribute data wasn't fully read")] 129 | IncompleteData, 130 | #[error("attribute data is invalid")] 131 | InvalidData, 132 | #[error(transparent)] 133 | ConstantPool(#[from] ConstantPoolError), 134 | 135 | #[error(transparent)] 136 | IOError(#[from] std::io::Error), 137 | } 138 | 139 | #[derive(Error, Debug)] 140 | pub enum ClassPathError { 141 | #[error("expected 'L' in byte stream for class path declaration; got '{0}'")] 142 | NoPrefix(char), 143 | #[error("expected ';' in byte stream for end of class path declaration")] 144 | NotTerminated, 145 | #[error("invalid identifier '{identifier}'; reason: {reason}")] 146 | InvalidIdentifier { 147 | identifier: String, 148 | reason: &'static str, 149 | }, 150 | 151 | #[error("constant pool error: {0}")] 152 | ConstantPool(#[from] ConstantPoolError), 153 | 154 | #[error(transparent)] 155 | IOError(std::io::Error), 156 | } 157 | 158 | impl From for ClassPathError { 159 | fn from(value: std::io::Error) -> Self { 160 | match value.kind() { 161 | std::io::ErrorKind::UnexpectedEof => ClassPathError::NotTerminated, 162 | _ => ClassPathError::IOError(value), 163 | } 164 | } 165 | } 166 | 167 | #[derive(Error, Debug)] 168 | pub enum JVMTypeError { 169 | #[error("invalid JVM type {found}; expected {expected}")] 170 | InvalidType { found: char, expected: &'static str }, 171 | #[error("type is not a primitive")] 172 | NotPrimitive(JVMType), 173 | #[error("invalid type classpath; error: {0}")] 174 | ClassPath(#[from] ClassPathError), 175 | 176 | #[error(transparent)] 177 | IOError(#[from] std::io::Error), 178 | } 179 | -------------------------------------------------------------------------------- /class_format/src/ext.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | pub trait ReadByteVecExt: io::Read { 4 | #[inline] 5 | fn read_byte_vec(&mut self, byte_count: usize) -> Result, std::io::Error> { 6 | let mut result = Vec::with_capacity(byte_count); 7 | unsafe { 8 | result.set_len(byte_count); 9 | } 10 | 11 | self.read_exact(result.as_mut_slice())?; 12 | Ok(result) 13 | } 14 | } 15 | 16 | impl ReadByteVecExt for R {} 17 | -------------------------------------------------------------------------------- /class_format/src/lib.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{ClassReadError, ConstantPoolError}; 2 | use attribute::AttributeValue; 3 | use byteorder::{ReadBytesExt, BE}; 4 | use error::ClassPathError; 5 | use std::collections::HashMap; 6 | use std::fmt::{Display, Formatter}; 7 | use std::fs::File; 8 | use std::io::{BufReader, Cursor, Read}; 9 | use std::path::Path; 10 | 11 | pub use crate::access_flags::AccessFlags; 12 | pub use crate::attribute::Attribute; 13 | pub use crate::constant::{Constant, ConstantPool, ConstantTag}; 14 | pub use crate::member::Member; 15 | pub use crate::op::{Instruction, Op}; 16 | pub use crate::ty::*; 17 | 18 | pub mod access_flags; 19 | pub mod attribute; 20 | pub mod constant; 21 | pub mod error; 22 | pub mod ext; 23 | pub mod member; 24 | pub mod method; 25 | pub mod op; 26 | pub mod ty; 27 | 28 | pub const CLASS_SIGNATURE: u32 = 0xCAFEBABE; 29 | 30 | #[derive(Debug, Copy, Clone)] 31 | #[repr(u8)] 32 | pub enum SourceLanguage { 33 | Java, 34 | Kotlin, 35 | Scala, 36 | Groovy, 37 | } 38 | 39 | #[derive(Debug, Clone)] 40 | pub struct CompilerInfo { 41 | pub major: u16, 42 | pub minor: u16, 43 | pub language: SourceLanguage, 44 | } 45 | 46 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 47 | pub struct ClassPath { 48 | pub package: Vec, 49 | pub inner_classes: Vec, 50 | pub name: String, 51 | } 52 | 53 | impl Default for ClassPath { 54 | fn default() -> Self { 55 | ClassPath::java_lang_class("Object") 56 | } 57 | } 58 | 59 | pub enum ClassPathParseState { 60 | Init { with_prefix: bool }, 61 | } 62 | 63 | impl ClassPath { 64 | pub fn read_from(r: &mut R) -> Result { 65 | let mut path_tokens = vec![]; 66 | 67 | let mut last = '\0'; 68 | let mut token = String::with_capacity(8); 69 | while let Some(curr) = r.read_u8().ok().map(|it| it as char) { 70 | if !curr.is_alphanumeric() && curr != '/' { 71 | last = curr; 72 | break; 73 | } 74 | 75 | if curr != '/' { 76 | // curr.is_alphanumeric() is true, append char to token 77 | token.push(curr); 78 | // advance 79 | continue; 80 | } 81 | // else curr is '/' 82 | 83 | if token.len() == 0 { 84 | if path_tokens.is_empty() { 85 | return Err(ClassPathError::InvalidIdentifier { 86 | identifier: token, 87 | reason: "class path starts with a separator ('/')", 88 | }); 89 | } else { 90 | return Err(ClassPathError::InvalidIdentifier { 91 | identifier: token, 92 | reason: "class path contains two consecutive separators (\"//\")", 93 | }); 94 | } 95 | } 96 | 97 | if token.chars().next().unwrap().is_numeric() { 98 | return Err(ClassPathError::InvalidIdentifier { 99 | identifier: token, 100 | reason: "class path and name (identifiers) can't start with a digit", 101 | }); 102 | } 103 | 104 | path_tokens.push(token); 105 | token = String::with_capacity(8); 106 | } 107 | // push last (unterminated, or '$'/';' terminated) token 108 | if !token.is_empty() { 109 | path_tokens.push(token); 110 | } 111 | 112 | let (package, name) = path_tokens.split_at(path_tokens.len() - 1); 113 | let name = name[0].clone(); 114 | 115 | let mut inner_classes = Vec::with_capacity(2); 116 | if last == '$' { 117 | let mut inner = String::with_capacity(8); 118 | while let Some(curr) = r.read_u8().ok().map(|it| it as char) { 119 | match curr as char { 120 | '$' => { 121 | inner_classes.push(inner); 122 | inner = String::with_capacity(8); 123 | } 124 | c if c.is_alphanumeric() => { 125 | inner.push(c); 126 | } 127 | _ => { 128 | break; 129 | } 130 | } 131 | } 132 | if !inner.is_empty() { 133 | inner_classes.push(inner); 134 | } 135 | } 136 | 137 | return Ok(ClassPath { 138 | package: package.to_vec(), 139 | inner_classes, 140 | name, 141 | }); 142 | } 143 | 144 | pub fn from_class_index( 145 | pool: &ConstantPool, 146 | class_index: usize, 147 | ) -> Result { 148 | let utf8_index = constant_match!(pool.get(class_index), Constant::Class { name_index } => { *name_index as usize })?; 149 | ClassPath::try_from(pool.get(utf8_index)) 150 | } 151 | 152 | pub fn parse(name: impl AsRef) -> Result { 153 | let mut cursor = std::io::Cursor::new(name.as_ref()); 154 | Self::read_from(&mut cursor) 155 | } 156 | 157 | pub fn package_path(&self) -> String { 158 | self.package.join(".") 159 | } 160 | 161 | pub fn jar_path(&self) -> String { 162 | let mut builder = self.package.join("/"); 163 | builder += self.name.as_str(); 164 | if !self.inner_classes.is_empty() { 165 | builder += "$"; 166 | builder += self.inner_classes.join("$").as_str(); 167 | } 168 | builder += ".class"; 169 | 170 | builder 171 | } 172 | 173 | pub fn full_path(&self) -> String { 174 | let mut builder: String = self.package.join("."); 175 | if !builder.is_empty() { 176 | builder += "."; 177 | } 178 | builder += self.name.to_string().as_str(); 179 | 180 | for inner_c in &self.inner_classes { 181 | builder += format!(".{}", inner_c).as_str(); 182 | } 183 | 184 | builder 185 | } 186 | 187 | pub fn is_in_java_lang(&self) -> bool { 188 | if self.package.len() != 2 { 189 | return false; 190 | } 191 | 192 | self.package[0] == "java" && self.package[1] == "lang" 193 | } 194 | 195 | pub fn is_object(&self) -> bool { 196 | self.is_in_java_lang() && self.inner_classes.is_empty() && self.name == "Object" 197 | } 198 | 199 | pub(crate) fn java_lang_class(name: impl ToString) -> Self { 200 | ClassPath { 201 | package: vec!["java".to_string(), "lang".to_string()], 202 | inner_classes: vec![], 203 | name: name.to_string(), 204 | } 205 | } 206 | } 207 | 208 | impl TryFrom<&Constant> for ClassPath { 209 | type Error = ClassPathError; 210 | 211 | fn try_from(value: &Constant) -> Result { 212 | constant_match!(value, Constant::Utf8 { value } => { ClassPath::parse(value)? }) 213 | .map_err(Into::into) 214 | } 215 | } 216 | 217 | impl Display for ClassPath { 218 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 219 | f.write_str(self.full_path().as_str()) 220 | } 221 | } 222 | 223 | #[derive(Debug, Clone)] 224 | pub struct Class { 225 | pub compiler_info: CompilerInfo, 226 | 227 | pub access_flags: AccessFlags, 228 | 229 | pub constant_pool: ConstantPool, 230 | 231 | pub class_name: ClassPath, 232 | pub super_name: Option, 233 | pub interfaces: Vec, 234 | 235 | pub fields: Vec, 236 | pub methods: Vec, 237 | 238 | pub attributes: HashMap, 239 | } 240 | 241 | impl Class { 242 | pub fn open(path: impl AsRef) -> Result { 243 | let mut file = File::open(path)?; 244 | let mut r = BufReader::new(&mut file); 245 | Class::read_from(&mut r) 246 | } 247 | 248 | pub fn read(bytes: impl AsRef<[u8]>) -> Result { 249 | let mut r = Cursor::new(bytes.as_ref()); 250 | Class::read_from(&mut r) 251 | } 252 | 253 | pub fn read_from(r: &mut R) -> Result { 254 | log::debug!("Reading class"); 255 | 256 | let magic_number = r.read_u32::()?; 257 | if magic_number != CLASS_SIGNATURE { 258 | return Err(ClassReadError::InvalidMagic { 259 | found: magic_number, 260 | }); 261 | } 262 | 263 | let minor = r.read_u16::()?; 264 | let major = r.read_u16::()?; 265 | log::trace!("Class::read_from(impl Read)::version = {}.{}", major, minor); 266 | 267 | log::trace!("Class::read_from(impl Read)::constant_pool"); 268 | let const_pool_size = r.read_u16::()? as usize; 269 | let mut constant_pool = ConstantPool::with_capacity(const_pool_size); 270 | 271 | log::trace!( 272 | "Class::read_from(impl Read)::constant_pool::max = {}", 273 | const_pool_size 274 | ); 275 | while constant_pool.size() < const_pool_size { 276 | let const_info = Constant::read_from(r)?; 277 | let tag = const_info.tag(); 278 | 279 | let index = constant_pool.size(); 280 | constant_pool.insert(const_info); 281 | log::trace!( 282 | "index: {}, read tag: {:?}; length: {}", 283 | index, 284 | tag, 285 | constant_pool.size() 286 | ); 287 | } 288 | 289 | log::trace!("Class::read_from(impl Read)::access_flags"); 290 | let access_flags = AccessFlags::read_from(r)?; 291 | 292 | let class_const_index = r.read_u16::()? as usize; 293 | log::trace!( 294 | "Class::try_from(impl Read)::class_name#{}", 295 | class_const_index 296 | ); 297 | let class_name = ClassPath::from_class_index(&constant_pool, class_const_index)?; 298 | 299 | let super_const_index = r.read_u16::()? as usize; 300 | log::trace!( 301 | "Class::read_from(impl Read)::super_name#{}", 302 | super_const_index 303 | ); 304 | let super_name = if super_const_index != 0 { 305 | Some(ClassPath::from_class_index( 306 | &constant_pool, 307 | super_const_index, 308 | )?) 309 | } else { 310 | None 311 | }; 312 | 313 | log::trace!("Class::read_from(impl Read)::interfaces"); 314 | let interface_count = r.read_u16::()? as usize; 315 | let mut interfaces = Vec::with_capacity(interface_count); 316 | 317 | for _ in 0..interface_count { 318 | let interface_index = r.read_u16::()? as usize; 319 | let interface_name = ClassPath::from_class_index(&constant_pool, interface_index)?; 320 | 321 | interfaces.push(interface_name); 322 | } 323 | 324 | log::trace!("Class::read_from(impl Read)::fields"); 325 | let field_count = r.read_u16::()? as usize; 326 | let mut fields = Vec::with_capacity(interface_count); 327 | 328 | for _ in 0..field_count { 329 | fields.push(Member::read_from(r, &constant_pool)?); 330 | } 331 | 332 | log::trace!("Class::read_from(impl Read)::methods"); 333 | let method_count = r.read_u16::()? as usize; 334 | let mut methods = Vec::with_capacity(method_count); 335 | 336 | for _ in 0..method_count { 337 | methods.push(Member::read_from(r, &constant_pool)?); 338 | } 339 | 340 | log::trace!("Class::read_from(impl Read)::attributes"); 341 | let attributes = AttributeValue::read_all(r, &constant_pool)?; 342 | 343 | // TODO: Detect source language 344 | 345 | log::trace!("leave Class::read_from(impl Read)"); 346 | 347 | Ok(Class { 348 | compiler_info: CompilerInfo { 349 | major, 350 | minor, 351 | language: SourceLanguage::Java, 352 | }, 353 | 354 | access_flags, 355 | 356 | constant_pool, 357 | 358 | class_name, 359 | super_name, 360 | interfaces, 361 | 362 | fields, 363 | methods, 364 | 365 | attributes, 366 | }) 367 | } 368 | } 369 | 370 | #[cfg(test)] 371 | mod class_path_tests { 372 | use crate::ty::JVMPrimitive; 373 | 374 | use super::*; 375 | 376 | #[test] 377 | fn class_path_parse() { 378 | let valid = &[ 379 | "Ljava/lang/String;", 380 | "Lcom/example/Simple;", 381 | "Lcom/example/Of$Nested$Class;", 382 | ]; 383 | 384 | let invalid = &[ 385 | "no/l/Prefix;", 386 | "Lno/semicolon/Suffix", 387 | "Lempty/child/Name$;", 388 | ]; 389 | 390 | for v in valid { 391 | assert!(ClassPath::parse(v).is_ok(), "unable to parse"); 392 | } 393 | 394 | for v in valid { 395 | assert!(ClassPath::parse(v).is_err(), "invalid sample parsed ok"); 396 | } 397 | } 398 | } 399 | -------------------------------------------------------------------------------- /class_format/src/member.rs: -------------------------------------------------------------------------------- 1 | use crate::access_flags::AccessFlags; 2 | use crate::attribute::AttributeValue; 3 | use crate::constant::{Constant, ConstantPool}; 4 | use crate::error::{ConstantPoolError, MemberError}; 5 | use crate::Descriptor; 6 | use byteorder::{ReadBytesExt, BE}; 7 | use std::collections::HashMap; 8 | use std::io::Read; 9 | use std::str::FromStr; 10 | 11 | #[derive(Debug, Clone)] 12 | pub struct Member { 13 | pub access_flags: AccessFlags, 14 | pub name: String, 15 | pub descriptor: Descriptor, // TODO: Specialize 16 | pub attributes: HashMap, 17 | } 18 | 19 | impl Member { 20 | pub fn read_from( 21 | r: &mut R, 22 | constant_pool: &ConstantPool, 23 | ) -> Result { 24 | log::trace!("enter Member::read_from(impl Read, ConstantPool)"); 25 | let access_flags = AccessFlags::read_from(r)?; 26 | 27 | let name_i = r.read_u16::()? as usize; 28 | let name = match constant_pool.try_get(name_i)? { 29 | Constant::Utf8 { value } => value.clone(), 30 | _ => return Err(MemberError::InvalidNameType), 31 | }; 32 | log::trace!("member name: {}", &name); 33 | 34 | let desc_i = r.read_u16::()? as usize; 35 | let descriptor = match constant_pool.get(desc_i) { 36 | Constant::Utf8 { value } => Descriptor::from_str(&value).map_err(MemberError::from)?, 37 | other => { 38 | return Err(ConstantPoolError::UnexpectedType { 39 | found: other.tag(), 40 | expected: crate::ConstantTag::Utf8, 41 | } 42 | .into()) 43 | } 44 | }; 45 | 46 | let attributes = AttributeValue::read_all(r, constant_pool)?; 47 | 48 | Ok(Member { 49 | access_flags, 50 | name, 51 | descriptor, 52 | attributes, 53 | }) 54 | } 55 | 56 | pub fn is_constructor(&self) -> bool { 57 | self.name == "" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /class_format/src/method.rs: -------------------------------------------------------------------------------- 1 | use num_enum::{IntoPrimitive, TryFromPrimitive}; 2 | 3 | #[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] 4 | #[repr(u8)] 5 | pub enum MethodHandle { 6 | GetField = 1, 7 | GetStatic = 2, 8 | PutField = 3, 9 | PutStatic = 4, 10 | InvokeVirtual = 5, 11 | InvokeStatic = 6, 12 | InvokeSpecial = 7, 13 | NewInvokeSpecial = 8, 14 | InvokeInterface = 9, 15 | } 16 | 17 | impl MethodHandle { 18 | #[allow(dead_code)] 19 | const fn name(&self) -> &'static str { 20 | match self { 21 | MethodHandle::GetField => "GetField", 22 | MethodHandle::GetStatic => "GetStatic", 23 | MethodHandle::PutField => "PutField", 24 | MethodHandle::PutStatic => "PutStatic", 25 | MethodHandle::InvokeVirtual => "InvokeVirtual", 26 | MethodHandle::InvokeStatic => "InvokeStatic", 27 | MethodHandle::InvokeSpecial => "InvokeSpecial", 28 | MethodHandle::NewInvokeSpecial => "NewInvokeSpecial", 29 | MethodHandle::InvokeInterface => "InvokeInterface", 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /class_format/src/op.rs: -------------------------------------------------------------------------------- 1 | use byteorder::ReadBytesExt; 2 | use num_enum::{IntoPrimitive, TryFromPrimitive}; 3 | use std::convert::TryFrom; 4 | use std::fmt::{Display, Formatter}; 5 | use std::io::{ErrorKind, Read}; 6 | 7 | use crate::error::OpReadError; 8 | 9 | macro_rules! impl_ops { 10 | [$(($op: ident, $code: literal, $argc: literal)),+] => {paste::paste!{ 11 | #[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] 12 | #[num_enum(error_type(name = OpReadError, constructor = OpReadError::Unknown))] 13 | #[non_exhaustive] 14 | #[repr(u8)] 15 | pub enum Op { 16 | $([<$op:camel>] = $code), 17 | + 18 | } 19 | 20 | const OP_NAMES: [&'static str; 256] = { 21 | let mut r = [""; 256]; 22 | $(r[Op::[<$op:camel>] as u8 as usize] = stringify!($op);) 23 | + 24 | r 25 | }; 26 | 27 | const OP_ARGC: [u8; 256] = { 28 | let mut r = [0; 256]; 29 | $(r[Op::[<$op:camel>] as u8 as usize] = $argc;) 30 | + 31 | r 32 | }; 33 | 34 | impl Op { 35 | pub const fn name(&self) -> &'static str { 36 | OP_NAMES[*self as u8 as usize] 37 | } 38 | 39 | pub const fn argc(&self) -> usize { 40 | OP_ARGC[*self as u8 as usize] as usize 41 | } 42 | } 43 | }}; 44 | } 45 | 46 | // Instruction Code ArgC 47 | #[rustfmt::skip] 48 | impl_ops![ 49 | (aaload, 0x32, 0), 50 | (aastore, 0x53, 0), 51 | (aconst_null, 0x01, 0), 52 | (aload, 0x19, 1), 53 | (aload_0, 0x2a, 0), 54 | (aload_1, 0x2b, 0), 55 | (aload_2, 0x2c, 0), 56 | (aload_3, 0x2d, 0), 57 | (anewarray, 0xbd, 2), 58 | (areturn, 0xb0, 0), 59 | (arraylength, 0xbe, 0), 60 | (astore, 0x3a, 1), 61 | (astore_0, 0x4b, 0), 62 | (astore_1, 0x4c, 0), 63 | (astore_2, 0x4d, 0), 64 | (astore_3, 0x4e, 0), 65 | (athrow, 0xbf, 0), 66 | (baload, 0x33, 0), 67 | (bastore, 0x54, 0), 68 | (bipush, 0x10, 1), 69 | (breakpoint, 0xca, 0), 70 | (caload, 0x34, 0), 71 | (castore, 0x55, 0), 72 | (checkcast, 0xc0, 2), 73 | (d_2f, 0x90, 0), 74 | (d_2i, 0x8e, 0), 75 | (d_2l, 0x8f, 0), 76 | (dadd, 0x63, 0), 77 | (daload, 0x31, 0), 78 | (dastore, 0x52, 0), 79 | (dcmpg, 0x98, 0), 80 | (dcmpl, 0x97, 0), 81 | (dconst_0, 0x0e, 0), 82 | (dconst_1, 0x0f, 0), 83 | (ddiv, 0x6f, 0), 84 | (dload, 0x18, 1), 85 | (dload_0, 0x26, 0), 86 | (dload_1, 0x27, 0), 87 | (dload_2, 0x28, 0), 88 | (dload_3, 0x29, 0), 89 | (dmul, 0x6b, 0), 90 | (dneg, 0x77, 0), 91 | (drem, 0x73, 0), 92 | (dreturn, 0xaf, 0), 93 | (dstore, 0x39, 1), 94 | (dstore_0, 0x47, 0), 95 | (dstore_1, 0x48, 0), 96 | (dstore_2, 0x49, 0), 97 | (dstore_3, 0x4a, 0), 98 | (dsub, 0x67, 0), 99 | (dup, 0x59, 0), 100 | (dupX1, 0x5a, 0), 101 | (dupX2, 0x5b, 0), 102 | (dup_2, 0x5c, 0), 103 | (dup2X1, 0x5d, 0), 104 | (dup2X2, 0x5e, 0), 105 | (f_2d, 0x8d, 0), 106 | (f_2i, 0x8b, 0), 107 | (f_2l, 0x8c, 0), 108 | (fadd, 0x62, 0), 109 | (faload, 0x30, 0), 110 | (fastore, 0x51, 0), 111 | (fcmpg, 0x96, 0), 112 | (fcmpl, 0x95, 0), 113 | (fconst_0, 0x0b, 0), 114 | (fconst_1, 0x0c, 0), 115 | (fconst_2, 0x0d, 0), 116 | (fdiv, 0x6e, 0), 117 | (fload, 0x17, 1), 118 | (fload_0, 0x22, 0), 119 | (fload_1, 0x23, 0), 120 | (fload_2, 0x24, 0), 121 | (fload_3, 0x25, 0), 122 | (fmul, 0x6a, 0), 123 | (fneg, 0x76, 0), 124 | (frem, 0x72, 0), 125 | (freturn, 0xae, 0), 126 | (fstore, 0x38, 1), 127 | (fstore_0, 0x43, 0), 128 | (fstore_1, 0x44, 0), 129 | (fstore_2, 0x45, 0), 130 | (fstore_3, 0x46, 0), 131 | (fsub, 0x66, 0), 132 | (getfield, 0xb4, 2), 133 | (getstatic, 0xb2, 2), 134 | (goto, 0xa7, 2), 135 | (goto_w, 0xc8, 4), 136 | (i_2b, 0x91, 0), 137 | (i_2c, 0x92, 0), 138 | (i_2d, 0x87, 0), 139 | (i_2f, 0x86, 0), 140 | (i_2l, 0x85, 0), 141 | (i_2s, 0x93, 0), 142 | (iadd, 0x60, 0), 143 | (iaload, 0x2e, 0), 144 | (iand, 0x7e, 0), 145 | (iastore, 0x4f, 0), 146 | (iconstM1, 0x02, 0), 147 | (iconst_0, 0x03, 0), 148 | (iconst_1, 0x04, 0), 149 | (iconst_2, 0x05, 0), 150 | (iconst_3, 0x06, 0), 151 | (iconst_4, 0x07, 0), 152 | (iconst_5, 0x08, 0), 153 | (idiv, 0x6c, 0), 154 | (if_acmpeq, 0xa5, 2), 155 | (if_acmpne, 0xa6, 2), 156 | (if_icmpeq, 0x9f, 2), 157 | (if_icmpge, 0xa2, 2), 158 | (if_icmpgt, 0xa3, 2), 159 | (if_icmple, 0xa4, 2), 160 | (if_icmplt, 0xa1, 2), 161 | (if_icmpne, 0xa0, 2), 162 | (ifeq, 0x99, 2), 163 | (ifge, 0x9c, 2), 164 | (ifgt, 0x9d, 2), 165 | (ifle, 0x9e, 2), 166 | (iflt, 0x9b, 2), 167 | (ifne, 0x9a, 2), 168 | (ifnonnull, 0xc7, 2), 169 | (ifnull, 0xc6, 2), 170 | (iinc, 0x84, 2), 171 | (iload, 0x15, 1), 172 | (iload_0, 0x1a, 0), 173 | (iload_1, 0x1b, 0), 174 | (iload_2, 0x1c, 0), 175 | (iload_3, 0x1d, 0), 176 | (impdep_1, 0xfe, 0), 177 | (impdep_2, 0xff, 0), 178 | (imul, 0x68, 0), 179 | (ineg, 0x74, 0), 180 | (instanceof, 0xc1, 2), 181 | (invokedynamic, 0xba, 4), 182 | (invokeinterface, 0xb9, 4), 183 | (invokespecial, 0xb7, 2), 184 | (invokestatic, 0xb8, 2), 185 | (invokevirtual, 0xb6, 2), 186 | (ior, 0x80, 0), 187 | (irem, 0x70, 0), 188 | (ireturn, 0xac, 0), 189 | (ishl, 0x78, 0), 190 | (ishr, 0x7a, 0), 191 | (istore, 0x36, 1), 192 | (istore_0, 0x3b, 0), 193 | (istore_1, 0x3c, 0), 194 | (istore_2, 0x3d, 0), 195 | (istore_3, 0x3e, 0), 196 | (isub, 0x64, 0), 197 | (iushr, 0x7c, 0), 198 | (ixor, 0x82, 0), 199 | (l_2d, 0x8a, 0), 200 | (l_2f, 0x89, 0), 201 | (l_2i, 0x88, 0), 202 | (ladd, 0x61, 0), 203 | (laload, 0x2f, 0), 204 | (land, 0x7f, 0), 205 | (lastore, 0x50, 0), 206 | (lcmp, 0x94, 0), 207 | (lconst_0, 0x09, 0), 208 | (lconst_1, 0x0a, 0), 209 | (ldc, 0x12, 1), 210 | (ldc_w, 0x13, 2), 211 | (ldc2W, 0x14, 2), 212 | (ldiv, 0x6d, 0), 213 | (lload, 0x16, 1), 214 | (lload_0, 0x1e, 0), 215 | (lload_1, 0x1f, 0), 216 | (lload_2, 0x20, 0), 217 | (lload_3, 0x21, 0), 218 | (lmul, 0x69, 0), 219 | (lneg, 0x75, 0), 220 | (lookupswitch, 0xab, 0), 221 | (lor, 0x81, 0), 222 | (lrem, 0x71, 0), 223 | (lreturn, 0xad, 0), 224 | (lshl, 0x79, 0), 225 | (lshr, 0x7b, 0), 226 | (lstore, 0x37, 1), 227 | (lstore_0, 0x3f, 0), 228 | (lstore_1, 0x40, 0), 229 | (lstore_2, 0x41, 0), 230 | (lstore_3, 0x42, 0), 231 | (lsub, 0x65, 0), 232 | (lushr, 0x7d, 0), 233 | (lxor, 0x83, 0), 234 | (monitorenter, 0xc2, 0), 235 | (monitorexit, 0xc3, 0), 236 | (multianewarray, 0xc5, 3), 237 | (new, 0xbb, 2), 238 | (newarray, 0xbc, 1), 239 | (nop, 0x00, 0), 240 | (pop, 0x57, 0), 241 | (pop_2, 0x58, 0), 242 | (putfield, 0xb5, 2), 243 | (putstatic, 0xb3, 2), 244 | (return, 0xb1, 0), 245 | (saload, 0x35, 0), 246 | (sastore, 0x56, 0), 247 | (sipush, 0x11, 2), 248 | (swap, 0x5f, 0) 249 | ]; 250 | 251 | impl Display for Op { 252 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 253 | f.write_str(self.name()) 254 | } 255 | } 256 | 257 | #[derive(Clone, Copy)] 258 | #[repr(C)] 259 | union OpArgs { 260 | none: [u8; 0], 261 | one: [u8; 1], 262 | two: [u8; 2], 263 | three: [u8; 3], 264 | four: [u8; 4], 265 | } 266 | 267 | impl OpArgs { 268 | fn new(op: Op) -> OpArgs { 269 | match op.argc() { 270 | 0 => OpArgs { none: [0; 0] }, 271 | 1 => OpArgs { one: [0; 1] }, 272 | 2 => OpArgs { two: [0; 2] }, 273 | 3 => OpArgs { three: [0; 3] }, 274 | 4 => OpArgs { four: [0; 4] }, 275 | _ => unreachable!("invalid argument count"), 276 | } 277 | } 278 | 279 | unsafe fn args_of(&self, op: Op) -> &[u8] { 280 | match op.argc() { 281 | 0 => &self.none, 282 | 1 => &self.one, 283 | 2 => &self.two, 284 | 3 => &self.three, 285 | 4 => &self.four, 286 | argc => unreachable!("invalid argument count: {}", argc), 287 | } 288 | } 289 | 290 | unsafe fn mut_args_of(&mut self, op: Op) -> &mut [u8] { 291 | match op.argc() { 292 | 0 => &mut self.none, 293 | 1 => &mut self.one, 294 | 2 => &mut self.two, 295 | 3 => &mut self.three, 296 | 4 => &mut self.four, 297 | argc => unreachable!("invalid argument count: {}", argc), 298 | } 299 | } 300 | } 301 | 302 | // TODO: Switch to #[repr(C, u8)] union? 303 | // - Using an union feels less cumbersome. 304 | // - Construction from bytes is weird with repr(C, u8) 305 | #[derive(Clone, Copy)] 306 | #[repr(C)] 307 | pub struct Instruction { 308 | op: Op, 309 | args: OpArgs, 310 | } 311 | 312 | impl std::fmt::Debug for Instruction { 313 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 314 | f.debug_struct("Instruction") 315 | .field("op", &self.op) 316 | .field("args", &self.args()) 317 | .finish() 318 | } 319 | } 320 | 321 | impl PartialEq for Instruction { 322 | fn eq(&self, other: &Self) -> bool { 323 | self.op == other.op && self.args() == other.args() 324 | } 325 | } 326 | impl Eq for Instruction {} 327 | 328 | impl Instruction { 329 | pub fn read_from(r: &mut R) -> Result { 330 | let op = Op::try_from(r.read_u8()?) 331 | .map_err(|e| std::io::Error::new(ErrorKind::InvalidData, e))?; 332 | 333 | let mut instruction = Instruction { 334 | op, 335 | args: OpArgs::new(op), 336 | }; 337 | r.read_exact(instruction.args_mut())?; 338 | 339 | Ok(instruction) 340 | } 341 | 342 | pub fn from_slice<'a>(slice: &'a [u8]) -> Result<&'a Instruction, OpReadError> { 343 | let op = Op::try_from(*slice.get(0).ok_or(OpReadError::Unknown(0))?)?; 344 | if slice.len() <= op.argc() { 345 | return Err(OpReadError::MissingArgs { 346 | op, 347 | expected: op.argc(), 348 | available: slice.len() - 1, 349 | }); 350 | } 351 | Ok(unsafe { 352 | // SAFETY: 353 | // - Checked that the slice is big enough, that the Op code is valid 354 | // and that there's enough tailing argument bytes - so the memory is 355 | // definitely a valid Instruction. 356 | // - Instruction is unaligned so alignment doesn't have to be checked. 357 | // - 'a reference is passed to returned data so we're not messing up 358 | // borrow lifetimes. 359 | 360 | (slice.as_ptr() as *const Instruction) 361 | .as_ref() 362 | .unwrap_unchecked() 363 | }) 364 | } 365 | 366 | pub fn collect_instructions(code: &[u8]) -> Vec<&Instruction> { 367 | InstructionIterator::new(code).collect() 368 | } 369 | 370 | #[inline] 371 | pub fn op(&self) -> Op { 372 | self.op 373 | } 374 | 375 | #[inline] 376 | pub fn args(&self) -> &[u8] { 377 | unsafe { self.args.args_of(self.op) } 378 | } 379 | 380 | #[inline] 381 | pub fn args_mut(&mut self) -> &mut [u8] { 382 | unsafe { self.args.mut_args_of(self.op) } 383 | } 384 | } 385 | 386 | pub struct InstructionIterator<'a> { 387 | pub bytecode: &'a [u8], 388 | pub pos: usize, 389 | } 390 | 391 | impl<'a> InstructionIterator<'a> { 392 | pub fn new(bytecode: &'a [u8]) -> Self { 393 | InstructionIterator { bytecode, pos: 0 } 394 | } 395 | } 396 | 397 | impl<'a> Iterator for InstructionIterator<'a> { 398 | type Item = &'a Instruction; 399 | 400 | fn next(&mut self) -> Option { 401 | if self.pos >= self.bytecode.len() { 402 | return None; 403 | } 404 | 405 | let i = Instruction::from_slice(&self.bytecode[self.pos..]).expect("invalid instruction"); 406 | self.pos += 1 + i.op.argc(); 407 | 408 | return Some(i); 409 | } 410 | } 411 | -------------------------------------------------------------------------------- /class_format/src/ty.rs: -------------------------------------------------------------------------------- 1 | use crate::error::JVMTypeError; 2 | use crate::ClassPath; 3 | use byteorder::ReadBytesExt; 4 | use std::io::{Cursor, Read}; 5 | use std::str::FromStr; 6 | 7 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 8 | #[repr(u8)] 9 | pub enum JVMPrimitive { 10 | TByte, 11 | TChar, 12 | TDouble, 13 | TFloat, 14 | TInt, 15 | TLong, 16 | TShort, 17 | TBoolean, 18 | TVoid, 19 | } 20 | 21 | impl JVMPrimitive { 22 | pub fn class_path(self) -> ClassPath { 23 | match self { 24 | JVMPrimitive::TByte => ClassPath::java_lang_class("Byte"), 25 | JVMPrimitive::TChar => ClassPath::java_lang_class("Character"), 26 | JVMPrimitive::TDouble => ClassPath::java_lang_class("Double"), 27 | JVMPrimitive::TFloat => ClassPath::java_lang_class("Float"), 28 | JVMPrimitive::TInt => ClassPath::java_lang_class("Integer"), 29 | JVMPrimitive::TLong => ClassPath::java_lang_class("Long"), 30 | JVMPrimitive::TShort => ClassPath::java_lang_class("Short"), 31 | JVMPrimitive::TBoolean => ClassPath::java_lang_class("Boolean"), 32 | JVMPrimitive::TVoid => ClassPath::java_lang_class("Void"), 33 | } 34 | } 35 | } 36 | 37 | impl TryFrom for JVMPrimitive { 38 | type Error = JVMTypeError; 39 | 40 | fn try_from(value: char) -> Result { 41 | Ok(match value { 42 | 'B' => JVMPrimitive::TByte, 43 | 'C' => JVMPrimitive::TChar, 44 | 'D' => JVMPrimitive::TDouble, 45 | 'F' => JVMPrimitive::TFloat, 46 | 'I' => JVMPrimitive::TInt, 47 | 'J' => JVMPrimitive::TLong, 48 | 'S' => JVMPrimitive::TShort, 49 | 'Z' => JVMPrimitive::TBoolean, 50 | 'V' => JVMPrimitive::TVoid, 51 | _ => { 52 | return Err(JVMTypeError::InvalidType { 53 | found: value, 54 | expected: "a primitive type", 55 | }) 56 | } 57 | }) 58 | } 59 | } 60 | 61 | impl TryFrom for JVMPrimitive { 62 | type Error = JVMTypeError; 63 | 64 | fn try_from(value: JVMType) -> Result { 65 | match value { 66 | JVMType::TPrimitive(it) => Ok(it), 67 | _ => Err(JVMTypeError::NotPrimitive(value)), 68 | } 69 | } 70 | } 71 | 72 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 73 | pub enum JVMType { 74 | TPrimitive(JVMPrimitive), 75 | TClass(ClassPath), 76 | TPrimitiveArray { depth: usize, inner: JVMPrimitive }, 77 | TClassArray { depth: usize, inner: ClassPath }, 78 | } 79 | 80 | impl From for JVMType { 81 | fn from(prim: JVMPrimitive) -> Self { 82 | JVMType::TPrimitive(prim) 83 | } 84 | } 85 | 86 | impl From for JVMType { 87 | fn from(cp: ClassPath) -> Self { 88 | JVMType::TClass(cp) 89 | } 90 | } 91 | 92 | #[derive(Debug, Clone)] 93 | pub struct TypeSpecifier(pub JVMType); 94 | 95 | impl JVMType { 96 | pub fn read_from(r: &mut R) -> Result { 97 | JVMType::read_from_with_prefix(r, None) 98 | } 99 | 100 | pub fn read_from_with_prefix( 101 | r: &mut R, 102 | prefix: Option, 103 | ) -> Result { 104 | let c = match prefix { 105 | Some(p) => p, 106 | None => r.read_u8()? as char, 107 | }; 108 | 109 | if let Ok(it) = JVMPrimitive::try_from(c) { 110 | return Ok(JVMType::TPrimitive(it)); 111 | } 112 | 113 | Ok(match c { 114 | 'L' => { 115 | let mut reference = String::with_capacity(8); 116 | let mut next = r.read_u8()? as char; 117 | while next != ';' { 118 | reference.push(next); 119 | next = r.read_u8()? as char; 120 | } 121 | 122 | JVMType::TClass(ClassPath::parse(&reference)?) 123 | } 124 | '[' => { 125 | let mut depth = 1; 126 | 127 | let mut next = r.read_u8()? as char; 128 | while next == '[' { 129 | next = r.read_u8()? as char; 130 | depth += 1; 131 | } 132 | 133 | if next == 'L' { 134 | JVMType::TClassArray { 135 | depth, 136 | inner: ClassPath::read_from(r)?, 137 | } 138 | } else { 139 | JVMType::TPrimitiveArray { 140 | depth, 141 | inner: JVMPrimitive::try_from(next)?, 142 | } 143 | } 144 | } 145 | _ => { 146 | return Err(JVMTypeError::InvalidType { 147 | found: c, 148 | expected: "a JVM type", 149 | }) 150 | } 151 | }) 152 | } 153 | 154 | pub fn from_string(value: String) -> Result { 155 | let mut c = Cursor::new(value); 156 | JVMType::read_from(&mut c) 157 | } 158 | 159 | pub fn array_depth(&self) -> usize { 160 | match self { 161 | JVMType::TClassArray { depth, .. } | JVMType::TPrimitiveArray { depth, .. } => *depth, 162 | _ => 0, 163 | } 164 | } 165 | 166 | pub fn strip_arrays(&self) -> JVMType { 167 | match self { 168 | JVMType::TClassArray { inner, .. } => JVMType::TClass(inner.clone()), 169 | JVMType::TPrimitiveArray { inner, .. } => JVMType::TPrimitive(*inner), 170 | _ => self.clone(), 171 | } 172 | } 173 | 174 | pub fn class(&self) -> Option { 175 | Some(match self { 176 | JVMType::TPrimitive(primitive) => primitive.class_path(), 177 | JVMType::TClass(inner) => inner.clone(), 178 | _ => return None, 179 | }) 180 | } 181 | } 182 | 183 | impl FromStr for JVMType { 184 | type Err = JVMTypeError; 185 | 186 | fn from_str(s: &str) -> Result { 187 | let mut c = Cursor::new(s); 188 | JVMType::read_from(&mut c) 189 | } 190 | } 191 | 192 | #[derive(Debug, Clone)] 193 | pub struct Descriptor { 194 | pub value: JVMType, 195 | pub arguments: Vec, 196 | } 197 | 198 | impl Descriptor { 199 | pub fn read_from(r: &mut R) -> Result { 200 | let mut next = r.read_u8()? as char; 201 | 202 | Ok(if next == '(' { 203 | let mut arguments = Vec::new(); 204 | 205 | loop { 206 | next = r.read_u8()? as char; 207 | if next == ')' { 208 | break; 209 | } 210 | let jvmt = JVMType::read_from_with_prefix(r, Some(next))?; 211 | arguments.push(jvmt); 212 | } 213 | 214 | Descriptor { 215 | value: JVMType::read_from(r)?, 216 | arguments, 217 | } 218 | } else { 219 | Descriptor { 220 | value: JVMType::read_from_with_prefix(r, Some(next))?, 221 | arguments: Vec::new(), 222 | } 223 | }) 224 | } 225 | 226 | pub fn from_string(value: String) -> Result { 227 | let mut c = Cursor::new(value); 228 | Descriptor::read_from(&mut c) 229 | } 230 | } 231 | 232 | impl FromStr for Descriptor { 233 | type Err = JVMTypeError; 234 | 235 | fn from_str(s: &str) -> Result { 236 | let mut c = Cursor::new(s); 237 | Descriptor::read_from(&mut c) 238 | } 239 | } 240 | 241 | #[cfg(test)] 242 | mod descriptor_tests { 243 | use super::*; 244 | 245 | #[test] 246 | fn descriptor_parsing_works() { 247 | let descriptors = &["(ILjava/lang/String;[I)J"]; 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /docs/Supported_Features.md: -------------------------------------------------------------------------------- 1 | - [x] Erasure of empty constructors 2 | - [ ] Erasure of synthetic functions 3 | - [ ] Modules 4 | - [ ] -------------------------------------------------------------------------------- /docs/jvm_20.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Caellian/java-decompiler/eb578faf274127c3b1ff1907e7214be6edbd493c/docs/jvm_20.pdf -------------------------------------------------------------------------------- /docs/lang_20.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Caellian/java-decompiler/eb578faf274127c3b1ff1907e7214be6edbd493c/docs/lang_20.pdf -------------------------------------------------------------------------------- /sample_output.java: -------------------------------------------------------------------------------- 1 | 2 | class Unit { 3 | Unit() { 4 | super(); 5 | // asm: return 6 | } 7 | public static void main(String[] arg_0) { 8 | // asm: getstatic 0xB2 0x0 9 | // asm: ldc 0x12 10 | // asm: invokevirtual 0xB6 0x0 11 | // asm: return 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/bin/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::BufWriter; 3 | use std::io::Write; 4 | use std::path::PathBuf; 5 | 6 | use jaded::gen::java::JavaBackend; 7 | use jaded::gen::GenerateCode; 8 | use jaded::gen::GeneratorBuilder; 9 | use jaded::settings::Settings; 10 | use jvm_class_format::Class; 11 | 12 | use clap::Parser; 13 | use tracing::Level; 14 | 15 | #[derive(Parser)] 16 | #[command(author, version, about, long_about = None)] 17 | struct Arguments { 18 | pub input: PathBuf, 19 | pub output: PathBuf, 20 | 21 | #[command(flatten)] 22 | pub settings: Settings, 23 | } 24 | 25 | fn main() { 26 | #[cfg(debug_assertions)] 27 | let log_level = Level::DEBUG; 28 | #[cfg(not(debug_assertions))] 29 | let log_level = Level::INFO; 30 | 31 | tracing_subscriber::fmt().with_max_level(log_level).init(); 32 | 33 | let args = Arguments::parse(); 34 | 35 | let class = Class::open(args.input).expect("can't open class"); 36 | 37 | let lang = GeneratorBuilder::java().build(); 38 | let out = File::create(args.output).expect("unable to create output file"); 39 | 40 | let mut w = BufWriter::new(out); 41 | 42 | JavaBackend 43 | .write_value(&lang, &(), &class, &mut w) 44 | .expect("unable to generate class code"); 45 | 46 | w.flush().expect("unable to flush"); 47 | } 48 | -------------------------------------------------------------------------------- /src/dependency.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | #[derive(Clone)] 4 | pub enum Dependency { 5 | MavenDependency { 6 | namespace: String, 7 | name: String, 8 | version: String, 9 | }, 10 | Jar { 11 | path: PathBuf, 12 | }, 13 | Remote { 14 | url: String, 15 | }, 16 | } 17 | 18 | pub struct DependencyInfo { 19 | pub dependency: Dependency, 20 | 21 | pub classes: Vec, 22 | } 23 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum ManifestParseError { 5 | #[error("misplaced continuation line")] 6 | MisplacedContinuation, 7 | #[error("invalid header field")] 8 | InvalidHeader, 9 | #[error("invalid manifest entry")] 10 | InvalidEntry, 11 | 12 | #[error(transparent)] 13 | IOError { 14 | #[from] 15 | inner: std::io::Error, 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /src/file/jar.rs: -------------------------------------------------------------------------------- 1 | use crate::file::manifest::Manifest; 2 | use jvm_class_format::Class; 3 | use std::fs::File; 4 | use std::path::{Path, PathBuf}; 5 | use zip::ZipArchive; 6 | 7 | #[derive(Debug)] 8 | pub struct Jar { 9 | path: PathBuf, 10 | 11 | pub manifest: Option, 12 | pub main_class: Option, 13 | 14 | file_count: usize, 15 | } 16 | 17 | impl Jar { 18 | pub fn open>(path: T) -> Result { 19 | let file = File::open(path.as_ref())?; 20 | let mut archive = ZipArchive::new(file)?; 21 | 22 | let (manifest, main_class) = { 23 | let mut mf_file = archive.by_name("META-INF/MANIFEST.MF")?; 24 | let manifest = match Manifest::read_from(&mut mf_file) { 25 | Ok(m) => Some(m), 26 | Err(err) => { 27 | tracing::warn!("manifest parsing error: {}", err); 28 | None 29 | } 30 | }; 31 | let main_class = match &manifest { 32 | Some(m) => m.get("Main-Class").map(|s| s.to_string()), 33 | None => None, 34 | }; 35 | (manifest, main_class) 36 | }; 37 | 38 | Ok(Jar { 39 | path: path.as_ref().to_path_buf(), 40 | 41 | manifest, 42 | main_class, 43 | 44 | file_count: archive.len(), 45 | }) 46 | } 47 | 48 | pub fn classes(&self) -> Classes { 49 | Classes { 50 | last: self.file_count - 1, 51 | current: 0, 52 | 53 | over: self, 54 | } 55 | } 56 | 57 | /* 58 | pub fn decompile(&mut self) { 59 | for file_number in 0..self.archive.len() { 60 | let mut f = &self.archive.by_index(file_number)?; 61 | 62 | if f.name().ends_with('/') { // Directory 63 | // Do nothing 64 | } else if f.name().ends_with(".class") { 65 | let mut content = Vec::with_capacity(f.size() as usize); 66 | f.read_to_end(&mut content)?; 67 | 68 | let mut reader = Cursor::new(content); 69 | 70 | let class = Class::read_from(&mut reader).unwrap(); 71 | 72 | println!("{}:", f.name()); 73 | println!("CP: {:?}", class.constant_pool); 74 | println!("Attributes: {:?}", class.attributes); 75 | println!("Fields: {:?}", class.fields); 76 | println!("Methods: {:?}", class.methods); 77 | 78 | let mut output = File::create(format!("ref/decomp/{}.java", class.class_name.name))?; 79 | 80 | let jg = JavaGenerator { 81 | target_version: JavaVersion::Java16, 82 | header_message: None 83 | }; 84 | 85 | jg.generate(&class, &mut output); 86 | output.flush()?; 87 | } else { 88 | println!("{}", f.name()); 89 | } 90 | } 91 | } 92 | */ 93 | } 94 | 95 | pub struct Classes<'a> { 96 | pub over: &'a Jar, 97 | pub last: usize, 98 | 99 | pub current: usize, 100 | } 101 | 102 | impl<'a> Iterator for Classes<'a> { 103 | type Item = Class; 104 | 105 | fn next(&mut self) -> Option { 106 | if self.current > self.last { 107 | return None; 108 | } 109 | 110 | let file = match File::open(self.over.path.clone()) { 111 | Ok(f) => f, 112 | Err(err) => { 113 | tracing::error!( 114 | "unable to open zip file '{}'; error: {}", 115 | self.over.path.to_string_lossy(), 116 | err 117 | ); 118 | return None; 119 | } 120 | }; 121 | let mut archive = match ZipArchive::new(file) { 122 | Ok(a) => a, 123 | Err(err) => { 124 | tracing::error!( 125 | "unable to open zip archive ('{}'): {}", 126 | self.over.path.to_string_lossy(), 127 | err 128 | ); 129 | return None; 130 | } 131 | }; 132 | 133 | loop { 134 | let mut zip_file = match archive.by_index(self.current) { 135 | Ok(a) => a, 136 | Err(err) => { 137 | tracing::error!( 138 | "unable to open ZIP file entry with index {}: {}", 139 | self.current, 140 | err 141 | ); 142 | return None; 143 | } 144 | }; 145 | 146 | self.current += 1; 147 | 148 | let name = zip_file.name(); 149 | if name.ends_with(".class") { 150 | let class = match Class::read_from(&mut zip_file) { 151 | Ok(c) => c, 152 | Err(err) => { 153 | tracing::error!("unable to read class '{}': {}", zip_file.name(), err); 154 | return None; 155 | } 156 | }; 157 | 158 | return Some(class); 159 | } 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/file/manifest.rs: -------------------------------------------------------------------------------- 1 | use crate::error::ManifestParseError; 2 | use std::collections::HashMap; 3 | use std::io::{BufRead, BufReader, Read}; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct Attributes(HashMap); 7 | 8 | impl Attributes { 9 | pub fn get(&self, key: &str) -> Option<&str> { 10 | self.0.get(key).map(|s| s.as_str()) 11 | } 12 | } 13 | 14 | #[derive(Debug, Clone)] 15 | pub struct Manifest { 16 | main_section: Attributes, 17 | entries: HashMap, 18 | } 19 | 20 | impl Manifest { 21 | pub fn read_from(r: &mut R) -> Result { 22 | let mut main_section: Option> = None; 23 | let mut section: HashMap = HashMap::with_capacity(2); 24 | let mut entries: HashMap = HashMap::new(); 25 | 26 | let mf_reader = BufReader::new(r); 27 | 28 | let mut last_key: Option = None; 29 | 30 | for line in mf_reader.lines() { 31 | let l = line.unwrap(); 32 | 33 | if l.is_empty() { 34 | if main_section.is_none() { 35 | main_section = Some(section.clone()); 36 | } else { 37 | entries.insert( 38 | section 39 | .get("Name") 40 | .ok_or(ManifestParseError::InvalidEntry)? 41 | .clone(), 42 | Attributes(section.clone()), 43 | ); 44 | } 45 | } 46 | 47 | match l.chars().next() { 48 | None => {} 49 | Some(first_char) => match first_char { 50 | '#' => continue, 51 | ' ' => { 52 | let mut appended = String::new(); 53 | l.chars().skip(1).for_each(|c| appended.push(c)); 54 | 55 | match last_key { 56 | None => return Err(ManifestParseError::MisplacedContinuation), 57 | Some(ref key) => { 58 | section.insert( 59 | key.clone(), 60 | section.get(key).expect("previous key not set").clone() 61 | + appended.as_str(), 62 | ); 63 | } 64 | } 65 | 66 | continue; 67 | } 68 | _ => { 69 | let mut kv: Vec<&str> = l.split(": ").collect(); 70 | let key = kv.remove(0).to_string(); 71 | let value = kv.join(""); 72 | 73 | last_key = Some(key.clone()); 74 | section.insert(key, value); 75 | } 76 | }, 77 | } 78 | } 79 | 80 | if main_section.is_none() { 81 | main_section = Some(section); 82 | } else { 83 | entries.insert( 84 | section 85 | .get("Name") 86 | .ok_or(ManifestParseError::InvalidEntry)? 87 | .clone(), 88 | Attributes(section.clone()), 89 | ); 90 | } 91 | 92 | Ok(Manifest { 93 | main_section: Attributes(main_section.unwrap()), 94 | entries, 95 | }) 96 | } 97 | 98 | pub fn get(&self, key: &str) -> Option<&str> { 99 | self.main_section.get(key) 100 | } 101 | 102 | pub fn entry_get(&self, entry: &str, key: &str) -> Option<&str> { 103 | self.entries.get(entry).and_then(|a| a.get(key)) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/file/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod jar; 2 | pub mod manifest; 3 | -------------------------------------------------------------------------------- /src/gen/indent.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashSet, io::Write}; 2 | 3 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 4 | #[repr(u8)] 5 | pub enum IndentKind { 6 | Space(usize), 7 | Tab, 8 | } 9 | 10 | impl ToString for IndentKind { 11 | fn to_string(&self) -> String { 12 | match self { 13 | IndentKind::Space(count) => " ".repeat(*count), 14 | IndentKind::Tab => "\t".to_string(), 15 | } 16 | } 17 | } 18 | 19 | pub struct Indented { 20 | inner: W, 21 | pub level: usize, 22 | pub indent: IndentKind, 23 | pub pending: bool, 24 | 25 | pub enter_block_on: HashSet, 26 | pub exit_block_on: HashSet, 27 | } 28 | 29 | impl Indented { 30 | pub fn new( 31 | writer: W, 32 | indent: IndentKind, 33 | level: usize, 34 | enter_block_on: impl AsRef<[u8]>, 35 | exit_block_on: impl AsRef<[u8]>, 36 | ) -> Indented { 37 | Indented { 38 | inner: writer, 39 | level, 40 | indent, 41 | pending: true, 42 | 43 | enter_block_on: HashSet::from_iter(enter_block_on.as_ref().iter().cloned()), 44 | exit_block_on: HashSet::from_iter(exit_block_on.as_ref().iter().cloned()), 45 | } 46 | } 47 | 48 | #[inline] 49 | pub fn enter_block(&mut self) { 50 | self.level += 1; 51 | } 52 | 53 | #[inline] 54 | pub fn exit_block(&mut self) { 55 | if self.level > 0 { 56 | self.level -= 1; 57 | } else { 58 | tracing::warn!("tried exiting block indentation on 0 indentation") 59 | } 60 | } 61 | 62 | #[inline] 63 | pub fn indent_string(&self) -> String { 64 | if self.level == 0 { 65 | return String::new(); 66 | } 67 | 68 | self.indent.to_string().repeat(self.level) 69 | } 70 | 71 | pub fn into_inner(self) -> W { 72 | self.inner 73 | } 74 | } 75 | 76 | impl Write for Indented { 77 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 78 | let mut total = 0; 79 | 80 | for byte in buf { 81 | total += match *byte as char { 82 | '\n' => { 83 | let nl_len = self.inner.write(b"\n")?; 84 | self.pending = true; 85 | nl_len 86 | } 87 | _ => { 88 | if self.enter_block_on.contains(byte) { 89 | self.enter_block(); 90 | } else if self.exit_block_on.contains(byte) { 91 | self.exit_block(); 92 | } 93 | 94 | if self.pending { 95 | total += self.inner.write(self.indent_string().as_bytes())?; 96 | self.pending = false; 97 | } 98 | 99 | self.inner.write(&[*byte])? 100 | } 101 | }; 102 | } 103 | 104 | Ok(total) 105 | } 106 | 107 | fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { 108 | let written = self.write(buf)?; 109 | // TODO: Do a proper check 110 | // needs to handle indentation levels 111 | // rope science - https://xi-editor.io/docs/rope_science_04.html 112 | if written < buf.len() { 113 | return Err(std::io::Error::new( 114 | std::io::ErrorKind::Other, 115 | "didn't write enough code", 116 | )); 117 | } 118 | Ok(()) 119 | } 120 | 121 | fn flush(&mut self) -> std::io::Result<()> { 122 | self.inner.flush() 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/gen/java/class.rs: -------------------------------------------------------------------------------- 1 | use jvm_class_format::{AccessFlags, Class}; 2 | use std::io::{Cursor, Write}; 3 | 4 | use crate::gen::{ 5 | indent::Indented, 6 | java::{field::FieldContext, method::ClassContext}, 7 | java::{JavaBackend, JavaContext, JavaScopeRequirements}, 8 | GenerateCode, 9 | }; 10 | 11 | pub fn class_signature(access_flags: AccessFlags) -> String { 12 | let mut parts = Vec::with_capacity(4); 13 | 14 | // visibility is one of following 15 | if access_flags.contains(AccessFlags::PUBLIC) { 16 | parts.push("public"); 17 | } else if access_flags.contains(AccessFlags::PROTECTED) { 18 | parts.push("protected"); 19 | } else if access_flags.contains(AccessFlags::PRIVATE) { 20 | parts.push("private"); 21 | } 22 | 23 | // inner classes can be static 24 | if access_flags.contains(AccessFlags::STATIC) { 25 | parts.push("static"); 26 | } 27 | 28 | // a class can be abstract 29 | if access_flags.contains(AccessFlags::ABSTRACT) { 30 | parts.push("abstract"); 31 | } 32 | 33 | // class inheritance can be prevented 34 | if access_flags.contains(AccessFlags::FINAL) { 35 | parts.push("final"); 36 | } 37 | 38 | // class type 39 | if access_flags.contains(AccessFlags::ENUM) { 40 | parts.push("enum"); 41 | } else if access_flags.contains(AccessFlags::INTERFACE) { 42 | parts.push("interface"); 43 | } else if access_flags.contains(AccessFlags::ANNOTATION) { 44 | parts.push("@interface"); 45 | } else { 46 | parts.push("class"); 47 | } 48 | 49 | parts.join(" ") 50 | } 51 | 52 | impl GenerateCode for JavaBackend { 53 | fn write_value( 54 | &self, 55 | lang: &JavaContext, 56 | _: &(), 57 | class: &Class, 58 | w: &mut W, 59 | ) -> Result { 60 | let mut req = JavaScopeRequirements::default(); 61 | 62 | tracing::debug!("Generating class: {}", class.class_name); 63 | 64 | // TODO: don't clone constant_pool, pass it as a reference 65 | let lang = JavaContext { 66 | constant_pool: Some(class.constant_pool.clone()), 67 | ..(*lang).clone() 68 | }; 69 | 70 | if lang.header_message.is_some() { 71 | let lines: Vec<&str> = lang.header_message.as_ref().unwrap().split('\n').collect(); 72 | 73 | writeln!(w, "/*")?; 74 | for l in lines { 75 | writeln!(w, " * {}", l)?; 76 | } 77 | writeln!(w, " */")?; 78 | } 79 | 80 | if !class.class_name.inner_classes.is_empty() { 81 | todo!("handle inner classes") 82 | } 83 | 84 | let package_path = class.class_name.package_path(); 85 | 86 | if !package_path.is_empty() { 87 | write!(w, "package {};\n\n", class.class_name.package_path())?; 88 | } 89 | 90 | let delayed = { 91 | let mut result = Vec::with_capacity(512); 92 | let mut w: Cursor<&mut Vec> = Cursor::new(&mut result); 93 | 94 | w.write_all(class_signature(class.access_flags).as_bytes())?; 95 | 96 | let class_name = class.class_name.clone(); 97 | w.write_all(b" ")?; 98 | w.write_all(class_name.name.as_bytes())?; 99 | 100 | if class.super_name.is_some() && !class.super_name.as_ref().unwrap().is_object() { 101 | let super_name = class.super_name.as_ref().unwrap(); 102 | w.write_all(b" extends ")?; 103 | w.write_all(super_name.name.as_bytes())?; 104 | req.imports.insert(super_name.clone()); 105 | } 106 | 107 | if !class.interfaces.is_empty() { 108 | w.write_all(b" implements ")?; 109 | 110 | for (i, interface) in class.interfaces.iter().enumerate() { 111 | req.imports.insert(interface.clone()); 112 | w.write_all(interface.name.as_bytes())?; 113 | 114 | if i != class.interfaces.len() - 1 { 115 | w.write_all(b", ")?; 116 | } 117 | } 118 | } 119 | w.write_all(b" {")?; 120 | 121 | // TODO: generate enum entries 122 | 123 | let contents = { 124 | let mut content_buffer = Vec::with_capacity(512); 125 | let mut w: Cursor<&mut Vec> = Cursor::new(&mut content_buffer); 126 | 127 | tracing::debug!("- Generating fields for {}", class_name); 128 | 129 | let mut class_indent = Indented::new(&mut w, lang.indentation, 1, b"{", b"}"); 130 | 131 | for field in &class.fields { 132 | let field_requirements = 133 | self.write_value(&lang, &FieldContext, field, &mut class_indent)?; 134 | req.add_import(field_requirements.imports); 135 | } 136 | 137 | tracing::debug!("- Generating methods for {}", class_name); 138 | 139 | for method in &class.methods { 140 | let method_ctx = ClassContext { 141 | class_name: class.class_name.clone(), 142 | ..Default::default() 143 | }; 144 | let method_requirements = 145 | self.write_value(&lang, &method_ctx, method, &mut class_indent)?; 146 | req.add_import(method_requirements.imports); 147 | } 148 | 149 | content_buffer 150 | }; 151 | 152 | if !contents.is_empty() { 153 | w.write_all(b"\n")?; 154 | } 155 | w.write_all(&contents)?; 156 | w.write_all(b"}\n")?; 157 | w.flush()?; 158 | 159 | result 160 | }; 161 | 162 | let mut has_imports = false; 163 | for import in req.imports.drain() { 164 | has_imports = true; 165 | w.write_all(b"import ")?; 166 | w.write_all(import.full_path().as_bytes())?; 167 | w.write_all(b";\n")?; 168 | } 169 | 170 | if has_imports { 171 | w.write_all(b"\n")?; 172 | } 173 | 174 | w.write_all(&delayed)?; 175 | 176 | tracing::debug!("- Done."); 177 | 178 | Ok(req) 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/gen/java/code.rs: -------------------------------------------------------------------------------- 1 | use jvm_class_format::{attribute::CodeData, ConstantPool, Member}; 2 | 3 | use crate::{ 4 | gen::{GenerateCode, GeneratorBackend}, 5 | ir::expression::{EmptySuperCall, Expression, InstructionComment}, 6 | }; 7 | 8 | use super::JavaBackend; 9 | 10 | pub type CodeGenContext<'m, 'data> = (&'m Member, &'data CodeData); 11 | 12 | impl<'m, 'data> GenerateCode> for JavaBackend { 13 | fn write_value( 14 | &self, 15 | lang: &Self::LanguageContext, 16 | ctx: &CodeGenContext, 17 | input: &Expression, 18 | w: &mut W, 19 | ) -> Result { 20 | #[allow(unreachable_patterns)] 21 | match input { 22 | Expression::Comment(it) => self.write_value(lang, ctx, it, w), 23 | Expression::Super(it) => self.write_value(lang, ctx, it, w), 24 | Expression::EmptyConstructor(_) => Ok(Default::default()), 25 | expr => todo!("unimplemented expression: {:?}", expr), 26 | } 27 | } 28 | } 29 | 30 | impl<'m, 'data, B: GeneratorBackend> GenerateCode> for B { 31 | fn write_value( 32 | &self, 33 | _: &Self::LanguageContext, 34 | _: &CodeGenContext<'m, 'data>, 35 | _: &EmptySuperCall, 36 | w: &mut W, 37 | ) -> Result { 38 | #[cfg(debug_assertions)] 39 | w.write_all(b"super();\n")?; 40 | Ok(Default::default()) 41 | } 42 | } 43 | 44 | impl<'m, 'data, B: GeneratorBackend> GenerateCode> 45 | for B 46 | { 47 | fn write_value( 48 | &self, 49 | _: &Self::LanguageContext, 50 | _: &CodeGenContext<'m, 'data>, 51 | input: &InstructionComment, 52 | w: &mut W, 53 | ) -> Result { 54 | w.write(b"// asm: ")?; 55 | w.write(input.0.op().name().as_bytes())?; 56 | for arg in input.0.args() { 57 | write!(w, " 0x{:X}", *arg)?; 58 | } 59 | w.write(b"\n")?; 60 | 61 | Ok(Default::default()) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/gen/java/field.rs: -------------------------------------------------------------------------------- 1 | use jvm_class_format::{AccessFlags, Member}; 2 | 3 | use crate::gen::{ 4 | java::{JavaBackend, JavaScopeRequirements}, 5 | GenerateCode, 6 | }; 7 | 8 | pub struct FieldContext; 9 | 10 | impl FieldContext { 11 | pub fn signature(access_flags: AccessFlags) -> String { 12 | let mut result = String::with_capacity(64); 13 | if access_flags.contains(AccessFlags::PUBLIC) { 14 | result.push_str("public"); 15 | } else if access_flags.contains(AccessFlags::PROTECTED) { 16 | result.push_str("protected"); 17 | } else if access_flags.contains(AccessFlags::PRIVATE) { 18 | result.push_str("private"); 19 | } 20 | 21 | if access_flags.contains(AccessFlags::STATIC) { 22 | if !result.is_empty() { 23 | result.push(' '); 24 | } 25 | result.push_str("static"); 26 | } 27 | 28 | result 29 | } 30 | } 31 | 32 | impl GenerateCode for JavaBackend { 33 | fn write_value( 34 | &self, 35 | lang: &Self::LanguageContext, 36 | _c: &FieldContext, 37 | field: &Member, 38 | w: &mut W, 39 | ) -> Result { 40 | let mut req = JavaScopeRequirements::default(); 41 | 42 | w.write_all(FieldContext::signature(field.access_flags).as_bytes())?; 43 | w.write_all(b" ")?; 44 | 45 | let (type_name, type_req) = self.generate(lang, &(), &field.descriptor.value)?; 46 | req.add_import(type_req.imports); 47 | w.write_all(type_name.as_bytes())?; 48 | w.write_all(b" ")?; 49 | 50 | w.write_all(field.name.as_bytes())?; 51 | w.write(format!(";\n").as_bytes())?; 52 | 53 | Ok(req) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/gen/java/method.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | 3 | use jvm_class_format::{ 4 | attribute::{AsData, CodeData, MethodParameterData}, 5 | AccessFlags, ClassPath, Constant, Member, 6 | }; 7 | 8 | use crate::{ 9 | gen::{ 10 | java::{JavaBackend, JavaScopeRequirements}, 11 | GenerateCode, GeneratorBackend, GeneratorVerbosity, 12 | }, 13 | ir::decompile, 14 | }; 15 | 16 | #[derive(Debug, Default)] 17 | pub struct ClassContext { 18 | pub class_name: ClassPath, 19 | 20 | pub synthetic: bool, 21 | } 22 | 23 | pub fn method_signature(access_flags: AccessFlags) -> String { 24 | let mut result = String::with_capacity(64); 25 | if access_flags.contains(AccessFlags::PUBLIC) { 26 | result.push_str("public"); 27 | } else if access_flags.contains(AccessFlags::PROTECTED) { 28 | result.push_str("protected"); 29 | } else if access_flags.contains(AccessFlags::PRIVATE) { 30 | result.push_str("private"); 31 | } 32 | 33 | if access_flags.contains(AccessFlags::STATIC) { 34 | if !result.is_empty() { 35 | result.push(' '); 36 | } 37 | result.push_str("static"); 38 | } 39 | 40 | result 41 | } 42 | 43 | impl GenerateCode for JavaBackend { 44 | fn write_value( 45 | &self, 46 | lang: &Self::LanguageContext, 47 | ctx: &ClassContext, 48 | method: &Member, 49 | w: &mut W, 50 | ) -> Result { 51 | let mut req = JavaScopeRequirements::default(); 52 | 53 | let code: &CodeData = method 54 | .attributes 55 | .get("Code") 56 | .expect("expected a code attribute") 57 | .as_data() 58 | .unwrap(); 59 | 60 | let constant_pool = lang.constant_pool.as_ref().expect("no contant pool"); 61 | 62 | let expressions = decompile(constant_pool, method, code); 63 | 64 | let mut generated = Vec::new(); 65 | { 66 | let mut gen_w = Cursor::new(&mut generated); 67 | for expression in expressions { 68 | let e_req = self.write_value(lang, &(method, code), &expression, &mut gen_w)?; 69 | req.include(e_req); 70 | } 71 | } 72 | 73 | if self.verbosity() == GeneratorVerbosity::All { 74 | if method.is_constructor() 75 | && method.descriptor.arguments.len() == 0 76 | && generated.len() == 0 77 | { 78 | return Ok(req); 79 | } 80 | } 81 | 82 | if ctx.synthetic { 83 | w.write_all(b"// synthetic method\n\n")?; 84 | } 85 | 86 | w.write_all(method_signature(method.access_flags).as_bytes())?; 87 | 88 | if !method.is_constructor() { 89 | let (tn, method_req) = self.generate(lang, &(), &method.descriptor.value)?; 90 | req.add_import(method_req.imports); 91 | 92 | write!(w, " {} {}(", tn, method.name)?; 93 | } else { 94 | write!(w, " {}(", ctx.class_name.name)?; 95 | } 96 | 97 | if !method.descriptor.arguments.is_empty() { 98 | let params = if let Some(MethodParameterData { parameters }) = method 99 | .attributes 100 | .get("MethodParameters") 101 | .and_then(|attr| attr.as_data().ok()) 102 | { 103 | Some(parameters) 104 | } else { 105 | None 106 | }; 107 | 108 | for (i, arg) in method.descriptor.arguments.iter().enumerate() { 109 | let (arg_type, tr) = self.generate(lang, &(), arg)?; 110 | req.add_import(tr.imports); 111 | 112 | let arg_name: String = if let Some(param) = params.and_then(|it| it.get(i)) { 113 | // let flags = param.access_flags; // TODO: Check spec 114 | 115 | if let Some(Constant::Utf8 { value }) = lang 116 | .constant_pool 117 | .as_ref() 118 | .and_then(|it| it.try_get(param.name_index as usize).ok()) 119 | { 120 | value.to_string() 121 | } else { 122 | format!("arg_{}", i) 123 | } 124 | } else { 125 | format!("arg_{}", i) 126 | }; 127 | 128 | write!(w, "{} {}", arg_type, arg_name)?; 129 | if i < method.descriptor.arguments.len() - 1 { 130 | write!(w, ", ")?; 131 | } 132 | } 133 | } 134 | w.write_all(b") {\n")?; 135 | w.write_all(&generated)?; 136 | w.write_all(b"}\n")?; 137 | 138 | Ok(req) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/gen/java/mod.rs: -------------------------------------------------------------------------------- 1 | use super::{indent::IndentKind, GenerateCode, GeneratorBackend, GeneratorVerbosity}; 2 | use jvm_class_format::{ClassPath, ConstantPool, JVMPrimitive, JVMType}; 3 | use std::collections::HashSet; 4 | 5 | pub mod class; 6 | pub mod code; 7 | pub mod field; 8 | pub mod method; 9 | 10 | pub fn primitive_name(primitive: JVMPrimitive) -> &'static str { 11 | match primitive { 12 | JVMPrimitive::TByte => "byte", 13 | JVMPrimitive::TChar => "char", 14 | JVMPrimitive::TDouble => "double", 15 | JVMPrimitive::TFloat => "float", 16 | JVMPrimitive::TInt => "int", 17 | JVMPrimitive::TLong => "long", 18 | JVMPrimitive::TShort => "short", 19 | JVMPrimitive::TBoolean => "boolean", 20 | JVMPrimitive::TVoid => "void", 21 | } 22 | } 23 | 24 | pub struct Type; 25 | 26 | impl GenerateCode for JavaBackend { 27 | fn write_value( 28 | &self, 29 | _lang: &Self::LanguageContext, 30 | _: &(), 31 | input: &JVMType, 32 | w: &mut W, 33 | ) -> Result { 34 | let mut req = JavaScopeRequirements::default(); 35 | match input { 36 | JVMType::TPrimitive(primitive) => { 37 | w.write_all(primitive_name(*primitive).as_bytes())?; 38 | } 39 | JVMType::TClass(class) => { 40 | if !class.is_in_java_lang() { 41 | req.imports.insert(class.clone()); 42 | } 43 | w.write_all(class.name.as_bytes())?; 44 | } 45 | JVMType::TPrimitiveArray { depth, inner } => { 46 | w.write_all(primitive_name(*inner).as_bytes())?; 47 | w.write_all("[]".repeat(*depth).as_bytes())?; 48 | } 49 | JVMType::TClassArray { depth, inner } => { 50 | if !inner.is_in_java_lang() { 51 | req.add_import([inner.clone()]); 52 | } 53 | w.write_all(inner.name.as_bytes())?; 54 | w.write_all("[]".repeat(*depth).as_bytes())?; 55 | } 56 | }; 57 | 58 | Ok(req) 59 | } 60 | } 61 | 62 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 63 | #[non_exhaustive] 64 | #[repr(u32)] 65 | pub enum JavaVersion { 66 | Unsupported = 0, 67 | Java1, 68 | Java5, 69 | Java6, 70 | Java7, 71 | Java8, 72 | Java9, 73 | Java10, 74 | Java11, 75 | Java12, 76 | Java13, 77 | Java14, 78 | Java15, 79 | Java16, 80 | Java17, 81 | Java18, 82 | Java19, 83 | Java20, 84 | } 85 | 86 | impl Default for JavaVersion { 87 | fn default() -> Self { 88 | Self::Java20 89 | } 90 | } 91 | 92 | #[derive(Debug, Default)] 93 | pub struct JavaGeneratorBuilder { 94 | result: JavaContext, 95 | } 96 | 97 | impl JavaGeneratorBuilder { 98 | pub fn new() -> JavaGeneratorBuilder { 99 | Self::default() 100 | } 101 | 102 | pub fn version(mut self, version: JavaVersion) -> Self { 103 | self.result.target_version = version; 104 | self 105 | } 106 | 107 | pub fn no_header(mut self) -> Self { 108 | self.result.header_message = None; 109 | self 110 | } 111 | 112 | pub fn header(mut self, header: impl ToString) -> Self { 113 | self.result.header_message = Some(header.to_string()); 114 | self 115 | } 116 | 117 | pub fn build(self) -> JavaContext { 118 | self.result 119 | } 120 | } 121 | 122 | #[derive(Debug, Clone)] 123 | pub struct JavaContext { 124 | pub target_version: JavaVersion, 125 | 126 | pub header_message: Option, 127 | pub indentation: IndentKind, 128 | 129 | pub constant_pool: Option, 130 | } 131 | 132 | #[derive(Debug, Default)] 133 | pub struct JavaScopeRequirements { 134 | pub imports: HashSet, 135 | pub language_level: JavaVersion, 136 | } 137 | 138 | impl JavaScopeRequirements { 139 | pub fn add_import<'a>(&mut self, imports: impl IntoIterator + 'a) { 140 | let iter = imports.into_iter(); 141 | for import in iter { 142 | self.imports.insert(import); 143 | } 144 | } 145 | 146 | pub fn include(&mut self, other: Self) { 147 | self.add_import(other.imports); 148 | } 149 | } 150 | 151 | pub struct JavaBackend; 152 | 153 | impl GeneratorBackend for JavaBackend { 154 | const NAME: &'static str = "Java"; 155 | 156 | type LanguageContext = JavaContext; 157 | type ScopeRequirements = JavaScopeRequirements; 158 | 159 | #[rustfmt::skip] 160 | fn verbosity(&self) -> GeneratorVerbosity { 161 | #[cfg(debug_assertions)] 162 | {GeneratorVerbosity::All} 163 | #[cfg(not(debug_assertions))] 164 | {GeneratorVerbosity::Clean} 165 | } 166 | } 167 | 168 | impl Default for JavaContext { 169 | fn default() -> JavaContext { 170 | JavaContext { 171 | target_version: JavaVersion::default(), 172 | header_message: Some( 173 | "Generated file - do not edit, your changes will be lost.".to_string(), 174 | ), 175 | indentation: IndentKind::Space(2), 176 | constant_pool: None, 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/gen/mod.rs: -------------------------------------------------------------------------------- 1 | use java::JavaGeneratorBuilder; 2 | use std::{io::Cursor, ops::Deref}; 3 | 4 | use self::indent::IndentKind; 5 | 6 | pub mod java; 7 | pub mod indent; 8 | 9 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] 10 | #[cfg_attr(feature = "clap", derive(clap::ValueEnum))] 11 | pub enum GeneratorVerbosity { 12 | /// Generate all expressions, including synthetic and implicit ones (e.g. `super()`) 13 | All, 14 | /// Generate clean, minimal sources that produce the same output classes 15 | Clean 16 | } 17 | 18 | pub trait GeneratorBackend: Sized { 19 | const NAME: &'static str; 20 | 21 | type LanguageContext; 22 | type ScopeRequirements: Default + Sized; 23 | 24 | fn verbosity(&self) -> GeneratorVerbosity; 25 | } 26 | 27 | pub struct GeneratorResult { 28 | pub inner: Result, 29 | pub requirements: B::ScopeRequirements, 30 | } 31 | 32 | pub struct GeneratorResultOk { 33 | pub value: R, 34 | pub requirements: B::ScopeRequirements, 35 | } 36 | 37 | impl TryInto> 38 | for GeneratorResult 39 | { 40 | type Error = E; 41 | fn try_into(self) -> Result, Self::Error> { 42 | self.inner.map(|it| GeneratorResultOk { 43 | value: it, 44 | requirements: self.requirements, 45 | }) 46 | } 47 | } 48 | 49 | impl Deref for GeneratorResult { 50 | type Target = Result; 51 | fn deref(&self) -> &Self::Target { 52 | &self.inner 53 | } 54 | } 55 | 56 | impl From<(Result, B::ScopeRequirements)> 57 | for GeneratorResult 58 | { 59 | fn from(value: (Result, B::ScopeRequirements)) -> Self { 60 | let (inner, requirements) = value; 61 | GeneratorResult { 62 | inner, 63 | requirements, 64 | } 65 | } 66 | } 67 | 68 | pub trait GenerateCode: GeneratorBackend + Sized { 69 | fn write_value( 70 | &self, 71 | lang: &Self::LanguageContext, 72 | ctx: &C, 73 | input: &I, 74 | w: &mut W, 75 | ) -> Result; 76 | 77 | fn generate( 78 | &self, 79 | lang: &Self::LanguageContext, 80 | ctx: &C, 81 | input: &I, 82 | ) -> Result<(String, Self::ScopeRequirements), std::io::Error> { 83 | let mut buff = Vec::with_capacity(64); 84 | let mut w = Cursor::new(&mut buff); 85 | let req = self.write_value(lang, ctx, input, &mut w)?; 86 | 87 | let string = std::str::from_utf8(w.into_inner().as_slice()) 88 | .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))? 89 | .to_string(); 90 | Ok((string, req)) 91 | } 92 | } 93 | 94 | pub struct GeneratorBuilder; 95 | impl GeneratorBuilder { 96 | #[inline(always)] 97 | pub fn java() -> JavaGeneratorBuilder { 98 | JavaGeneratorBuilder::new() 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/ir/expression.rs: -------------------------------------------------------------------------------- 1 | use jvm_class_format::{attribute::CodeData, Instruction, Op}; 2 | 3 | use super::frame::RuntimeFrame; 4 | 5 | pub struct OpSeq(pub [Op; LENGTH]); 6 | 7 | impl OpSeq { 8 | pub fn test<'code>(&self, buffer: &[&'code Instruction], offset: usize) -> bool { 9 | if L > buffer.as_ref()[offset..].len() { 10 | return false; 11 | } 12 | for (i, op) in self.0.iter().enumerate() { 13 | if buffer.as_ref()[offset + i].op() != *op { 14 | return false; 15 | } 16 | } 17 | 18 | return true; 19 | } 20 | } 21 | 22 | pub struct SeqVariants(pub [OpSeq; COUNT]); 23 | 24 | #[macro_export] 25 | macro_rules! test_many_expr { 26 | (&[$first: ty $(,$other: ty) *], $instructions: expr, $offset: expr, $ctx: expr) => { 27 | <$first>::test($instructions, $offset, $ctx)$( 28 | .or_else(|| <$other>::test($instructions, $offset, $ctx)))* 29 | }; 30 | } 31 | 32 | pub trait CheckExpression { 33 | fn test<'cp, 'code>( 34 | buffer: &[&'code Instruction], 35 | offset: usize, 36 | ctx: &RuntimeFrame<'cp, 'code>, 37 | ) -> Option<(usize, Expression)>; 38 | } 39 | 40 | #[derive(Debug)] 41 | pub enum Expression { 42 | EmptyConstructor(EmptyConstructor), 43 | ReturnStatement(ReturnStatement), 44 | Super(EmptySuperCall), 45 | Comment(InstructionComment), 46 | } 47 | 48 | #[derive(Debug)] 49 | pub struct InstructionComment(pub Instruction); 50 | 51 | impl CheckExpression for InstructionComment { 52 | fn test<'cp, 'code>( 53 | instr: &[&'code Instruction], 54 | offset: usize, 55 | _: &RuntimeFrame<'cp, 'code>, 56 | ) -> Option<(usize, Expression)> { 57 | unsafe { 58 | Some(( 59 | 1, 60 | Expression::Comment(InstructionComment(**instr.as_ref().get_unchecked(offset))), 61 | )) 62 | } 63 | } 64 | } 65 | 66 | #[derive(Debug)] 67 | pub struct EmptyConstructor; 68 | 69 | impl CheckExpression for EmptyConstructor { 70 | fn test<'cp, 'code>( 71 | buffer: &[&'code Instruction], 72 | offset: usize, 73 | _: &RuntimeFrame<'cp, 'code>, 74 | ) -> Option<(usize, Expression)> { 75 | if buffer.as_ref().len() != 3 { 76 | return None; 77 | } 78 | 79 | let result = OpSeq([ 80 | Op::Aload0, // push this to stack 81 | Op::Invokespecial, // call this. 82 | Op::Return, // return with object on stack 83 | ]) 84 | .test(buffer, 0); 85 | 86 | if !result { 87 | return None; 88 | } 89 | 90 | Some((3 - offset, Expression::EmptyConstructor(Self))) 91 | } 92 | } 93 | 94 | #[derive(Debug)] 95 | pub struct ReturnStatement; 96 | 97 | impl CheckExpression for ReturnStatement { 98 | fn test<'cp, 'code>( 99 | buffer: &[&'code Instruction], 100 | offset: usize, 101 | _: &RuntimeFrame<'cp, 'code>, 102 | ) -> Option<(usize, Expression)> { 103 | let result = OpSeq([ 104 | Op::Return, // return with object on stack 105 | ]) 106 | .test(buffer, offset); 107 | 108 | if !result { 109 | return None; 110 | } 111 | 112 | Some((1, Expression::ReturnStatement(Self))) 113 | } 114 | } 115 | 116 | #[derive(Debug)] 117 | pub struct EmptySuperCall; 118 | 119 | impl CheckExpression for EmptySuperCall { 120 | fn test<'cp, 'code>( 121 | buffer: &[&'code Instruction], 122 | offset: usize, 123 | _: &RuntimeFrame<'cp, 'code>, 124 | ) -> Option<(usize, Expression)> { 125 | let result = OpSeq([ 126 | Op::Aload0, // push this to stack 127 | Op::Invokespecial, // call 128 | ]) 129 | .test(buffer, offset); 130 | 131 | if !result { 132 | return None; 133 | } 134 | 135 | Some((2, Expression::Super(Self))) 136 | } 137 | } 138 | 139 | #[derive(Debug)] 140 | pub struct LocalDeclaration {} 141 | -------------------------------------------------------------------------------- /src/ir/frame.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use jvm_class_format::{ 4 | attribute::{AttributeValue, ExceptionTableEntry, CodeData}, 5 | ConstantPool, 6 | }; 7 | 8 | pub struct RuntimeBase { 9 | pub constant_pool: ConstantPool, 10 | } 11 | 12 | #[derive(Debug, Clone, Copy)] 13 | pub enum StackValue {} 14 | 15 | #[derive(Debug, Clone)] 16 | pub struct RuntimeFrame<'cp, 'code> { 17 | pub constant_pool: &'cp ConstantPool, 18 | 19 | pub exception_table: &'code [ExceptionTableEntry], 20 | pub attributes: &'code HashMap, 21 | 22 | pub stack_size: usize, 23 | pub stack: Vec, 24 | 25 | pub max_locals: usize, 26 | } 27 | 28 | impl<'cp, 'code> RuntimeFrame<'cp, 'code> { 29 | pub fn new(base: &'cp ConstantPool, code: &'code CodeData) -> Self { 30 | RuntimeFrame { 31 | constant_pool: base, 32 | 33 | exception_table: &code.exception_table, 34 | attributes: &code.attributes, 35 | 36 | stack_size: code.max_stack, 37 | stack: Vec::with_capacity(code.max_stack), 38 | 39 | max_locals: code.max_locals, 40 | } 41 | } 42 | 43 | pub fn new_inner(&self) -> Self { 44 | RuntimeFrame { 45 | constant_pool: self.constant_pool, 46 | 47 | exception_table: self.exception_table, 48 | attributes: self.attributes, 49 | 50 | stack_size: self.stack_size, 51 | stack: self.stack.clone(), 52 | 53 | max_locals: self.max_locals, 54 | } 55 | } 56 | 57 | pub fn push_to_stack(&mut self, value: StackValue) { 58 | if self.stack_size < self.stack.len() { 59 | tracing::warn!("exceeded stack limit!"); 60 | } 61 | 62 | self.stack.push(value); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/ir/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod expression; 2 | pub mod frame; 3 | 4 | use jvm_class_format::attribute::CodeData; 5 | use jvm_class_format::{ConstantPool, Instruction, Member}; 6 | 7 | use crate::test_many_expr; 8 | 9 | use expression::*; 10 | use frame::*; 11 | 12 | // JVM spec, pg. 620 - 15.12.4. Run-Time Evaluation of Method Invocation 13 | 14 | pub fn decompile<'cp, 'code>( 15 | constant_pool: &'cp ConstantPool, 16 | method: &Member, 17 | code: &CodeData, 18 | ) -> Vec { 19 | let instructions = Instruction::collect_instructions(&code.code); 20 | 21 | let frame = RuntimeFrame::new(constant_pool, code); 22 | 23 | let mut result = Vec::with_capacity(instructions.len()); 24 | 25 | if method.is_constructor() { 26 | if let Some((_, expr)) = EmptyConstructor::test(instructions.as_slice(), 0, &frame) { 27 | return vec![expr]; 28 | } 29 | } 30 | 31 | let mut offset = 0; 32 | while offset < instructions.len() { 33 | #[rustfmt::skip] 34 | let (instruction_count, expr) = test_many_expr!(&[ 35 | EmptySuperCall, 36 | InstructionComment 37 | ], instructions.as_slice(), offset, &frame) 38 | .unwrap(); 39 | 40 | offset += instruction_count; 41 | result.push(expr); 42 | } 43 | debug_assert_eq!( 44 | offset, 45 | instructions.len(), 46 | "to_ir overshot instruction buffer" 47 | ); 48 | 49 | result.shrink_to_fit(); 50 | result 51 | } 52 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod dependency; 2 | pub mod error; 3 | pub mod file; 4 | pub mod gen; 5 | pub mod ir; 6 | pub mod settings; 7 | -------------------------------------------------------------------------------- /src/settings.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Serialize, Deserialize)] 4 | #[cfg_attr(feature = "clap", derive(clap::Args))] 5 | pub struct Settings { 6 | 7 | pub project_name: Option, 8 | } 9 | 10 | /* 11 | fernflower: 12 | rbr (1): hide bridge methods 13 | rsy (0): hide synthetic class members 14 | din (1): decompile inner classes 15 | dc4 (1): collapse 1.4 class references 16 | das (1): decompile assertions 17 | hes (1): hide empty super invocation 18 | hdc (1): hide empty default constructor 19 | dgs (0): decompile generic signatures 20 | ner (1): assume return not throwing exceptions 21 | den (1): decompile enumerations 22 | rgn (1): remove getClass() invocation, when it is part of a qualified new statement 23 | lit (0): output numeric literals "as-is" 24 | asc (0): encode non-ASCII characters in string and character literals as Unicode escapes 25 | bto (1): interpret int 1 as boolean true (workaround to a compiler bug) 26 | nns (0): allow for not set synthetic attribute (workaround to a compiler bug) 27 | uto (1): consider nameless types as java.lang.Object (workaround to a compiler architecture flaw) 28 | udv (1): reconstruct variable names from debug information, if present 29 | ump (1): reconstruct parameter names from corresponding attributes, if present 30 | rer (1): remove empty exception ranges 31 | fdi (1): de-inline finally structures 32 | mpm (0): maximum allowed processing time per decompiled method, in seconds. 0 means no upper limit 33 | ren (0): rename ambiguous (resp. obfuscated) classes and class elements 34 | urc (-): full name of a user-supplied class implementing IIdentifierRenamer interface. It is used to determine which class identifiers should be renamed and provides new identifier names (see "Renaming identifiers") 35 | inn (1): check for IntelliJ IDEA-specific @NotNull annotation and remove inserted code if found 36 | lac (0): decompile lambda expressions to anonymous classes 37 | nls (0): define new line character to be used for output. 0 - '\r\n' (Windows), 1 - '\n' (Unix), default is OS-dependent 38 | ind: indentation string (default is 3 spaces) 39 | log (INFO): a logging level, possible values are TRACE, INFO, WARN, ERROR 40 | */ 41 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | **/*.class -------------------------------------------------------------------------------- /tests/run_units.rs: -------------------------------------------------------------------------------- 1 | use std::cell::OnceCell; 2 | use std::fs::{DirEntry, File}; 3 | use std::io::{BufWriter, Read, Write}; 4 | use std::path::{Path, PathBuf}; 5 | use std::process::{Command, Stdio}; 6 | use std::str::FromStr; 7 | 8 | use jaded::gen::java::JavaBackend; 9 | use jaded::gen::{GenerateCode, GeneratorBuilder}; 10 | use jvm_class_format::error::ClassReadError; 11 | use jvm_class_format::Class; 12 | 13 | fn javac(source: impl AsRef) -> Command { 14 | static mut JAVA_HOME: OnceCell = OnceCell::new(); 15 | let javac = unsafe { 16 | JAVA_HOME.get_or_init(|| match std::env::var("JAVA_HOME") { 17 | Ok(it) => PathBuf::from_str((it + "/bin/javac").as_str()).unwrap(), 18 | Err(_) => PathBuf::from_str("javac").unwrap(), 19 | }) 20 | }; 21 | 22 | let mut c = Command::new(javac); 23 | c.args([ 24 | "-nowarn", 25 | source.as_ref().to_str().expect("invalid source path"), 26 | ]); 27 | c 28 | } 29 | 30 | // root structure of a java file is a class 31 | pub fn compile(source: impl AsRef) -> Result, std::io::Error> { 32 | let mut javac_command = javac(source) 33 | .stdout(Stdio::null()) 34 | .stderr(Stdio::piped()) 35 | .spawn()?; 36 | 37 | let compilation_result = javac_command.wait()?; 38 | 39 | if !compilation_result.success() { 40 | // If compilation fails, capture the error output and include it in the error message. 41 | let mut error_output = String::new(); 42 | if let Some(mut stderr) = javac_command.stderr { 43 | stderr.read_to_string(&mut error_output)?; 44 | } 45 | 46 | return Err(std::io::Error::new( 47 | std::io::ErrorKind::Other, 48 | format!("compile error:\n{}", error_output), 49 | )); 50 | } 51 | 52 | let result = std::fs::read("tests/units/Unit.class")?; 53 | let _ = std::fs::remove_file("tests/units/Unit.class"); // it will be overriden 54 | Ok(result) 55 | } 56 | 57 | fn entry_num(entry: &DirEntry) -> usize { 58 | entry 59 | .file_name() 60 | .to_string_lossy() 61 | .chars() 62 | .take_while(|it| it.is_numeric()) 63 | .collect::() 64 | .parse::() 65 | .unwrap() 66 | } 67 | 68 | #[test] 69 | fn run_units() -> Result<(), ClassReadError> { 70 | tracing_subscriber::fmt() 71 | .with_max_level(tracing::Level::INFO) 72 | .init(); 73 | 74 | let lang = GeneratorBuilder::java().no_header().build(); 75 | 76 | let units = std::fs::read_dir("tests/units").expect("can't iterate test units"); 77 | let mut units = units 78 | .into_iter() 79 | .filter_map(|it| it.ok()) 80 | .collect::>(); 81 | units.sort_by_key(entry_num); 82 | 83 | let mut any_failed = false; 84 | for unit in units.into_iter() { 85 | let filename = unit.file_name().to_string_lossy().to_string(); 86 | tracing::info!("Testing unit: {}", filename); 87 | let binary = match compile(unit.path()) { 88 | Ok(it) => it, 89 | Err(err) => { 90 | tracing::error!("{}", err); 91 | continue; 92 | } 93 | }; 94 | 95 | let hello_world = Class::read(binary)?; 96 | 97 | let result = JavaBackend 98 | .generate(&lang, &(), &hello_world) 99 | .expect("unable to generate class code") 100 | .0; 101 | 102 | let source = std::fs::read_to_string(unit.path()).unwrap(); 103 | 104 | if source != result { 105 | tracing::error!( 106 | "Unit {} failed\n{}", 107 | filename, 108 | pretty_assertions::StrComparison::new(&source, &result).to_string() 109 | ); 110 | any_failed = true; 111 | } else { 112 | tracing::info!("Unit {} passed", filename); 113 | } 114 | } 115 | assert!(!any_failed, "some tests failed; check the logs"); 116 | 117 | Ok(()) 118 | } 119 | -------------------------------------------------------------------------------- /tests/units/00_empty.java: -------------------------------------------------------------------------------- 1 | class Unit {} 2 | -------------------------------------------------------------------------------- /tests/units/01_return_lit.java: -------------------------------------------------------------------------------- 1 | class Unit { 2 | public static int literal_return() { 3 | return 20; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/units/02_hello_world.java: -------------------------------------------------------------------------------- 1 | class Unit { 2 | public static void main(String[] arg_0) { 3 | System.out.println("Hello, World!"); 4 | } 5 | } 6 | --------------------------------------------------------------------------------