├── .gitattributes ├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .java-version ├── .vscode └── settings.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── TODO list for reader.md ├── justfile ├── reader ├── Cargo.toml ├── src │ ├── attribute.rs │ ├── buffer.rs │ ├── class_access_flags.rs │ ├── class_file.rs │ ├── class_file_field.rs │ ├── class_file_method.rs │ ├── class_file_version.rs │ ├── class_reader.rs │ ├── class_reader_error.rs │ ├── constant_pool.rs │ ├── exception_table.rs │ ├── field_flags.rs │ ├── field_type.rs │ ├── instruction.rs │ ├── lib.rs │ ├── line_number.rs │ ├── line_number_table.rs │ ├── method_descriptor.rs │ ├── method_flags.rs │ ├── program_counter.rs │ └── type_conversion.rs └── tests │ ├── integration │ ├── assertions.rs │ ├── constants_class_test.rs │ ├── deprecated_class_test.rs │ ├── exceptions.rs │ ├── main.rs │ ├── pojo_class_test.rs │ └── utils.rs │ └── resources │ ├── compile.sh │ └── rjvm │ ├── Complex.class │ ├── Complex.java │ ├── Constants.class │ ├── Constants.java │ ├── DeprecatedClass.class │ ├── DeprecatedClass.java │ ├── ExceptionsHandlers.class │ └── ExceptionsHandlers.java ├── rustfmt.toml ├── vm ├── Cargo.toml ├── rt.jar ├── src │ ├── abstract_object.rs │ ├── alloc_entry.rs │ ├── array.rs │ ├── array_entry_type.rs │ ├── call_frame.rs │ ├── call_stack.rs │ ├── class.rs │ ├── class_and_method.rs │ ├── class_loader.rs │ ├── class_manager.rs │ ├── class_path.rs │ ├── class_path_entry.rs │ ├── class_resolver_by_id.rs │ ├── exceptions.rs │ ├── file_system_class_path_entry.rs │ ├── gc.rs │ ├── jar_file_class_path_entry.rs │ ├── java_objects_creation.rs │ ├── lib.rs │ ├── native_methods_impl.rs │ ├── native_methods_registry.rs │ ├── object.rs │ ├── stack_trace_element.rs │ ├── time.rs │ ├── value.rs │ ├── value_stack.rs │ ├── vm.rs │ └── vm_error.rs └── tests │ ├── integration │ ├── main.rs │ └── real_code_tests.rs │ └── resources │ ├── compile.sh │ ├── rjvm │ ├── CheckCast$C1.class │ ├── CheckCast$C2.class │ ├── CheckCast.class │ ├── CheckCast.java │ ├── ControlFlow.class │ ├── ControlFlow.java │ ├── ExceptionsThrowingAndCatching$E1.class │ ├── ExceptionsThrowingAndCatching$E2.class │ ├── ExceptionsThrowingAndCatching.class │ ├── ExceptionsThrowingAndCatching.java │ ├── GarbageCollection$ALargeObject.class │ ├── GarbageCollection$ASmallObject.class │ ├── GarbageCollection$AWrapperObject.class │ ├── GarbageCollection.class │ ├── GarbageCollection.java │ ├── Generic.class │ ├── Generic.java │ ├── InstanceOf$C1.class │ ├── InstanceOf$C2.class │ ├── InstanceOf$C3.class │ ├── InstanceOf$C4.class │ ├── InstanceOf$C5.class │ ├── InstanceOf$Intf1.class │ ├── InstanceOf$Intf2.class │ ├── InstanceOf$Intf3.class │ ├── InstanceOf.class │ ├── InstanceOf.java │ ├── InstanceOfArray$C0.class │ ├── InstanceOfArray$C1.class │ ├── InstanceOfArray$Intf1.class │ ├── InstanceOfArray.class │ ├── InstanceOfArray.java │ ├── InvokeInterface$NotReallyASquare.class │ ├── InvokeInterface$Polygon.class │ ├── InvokeInterface$Rectangle.class │ ├── InvokeInterface$Square.class │ ├── InvokeInterface.class │ ├── InvokeInterface.java │ ├── NumericArrays.class │ ├── NumericArrays.java │ ├── NumericTypes.class │ ├── NumericTypes.java │ ├── ObjectArrays$Square.class │ ├── ObjectArrays.class │ ├── ObjectArrays.java │ ├── SimpleMain$Generator.class │ ├── SimpleMain.class │ ├── SimpleMain.java │ ├── StackTracePrinting.class │ ├── StackTracePrinting.java │ ├── Statics$MyObject.class │ ├── Statics.class │ ├── Statics.java │ ├── Strings.class │ ├── Strings.java │ ├── SuperClasses$BaseClass.class │ ├── SuperClasses$DerivedClass.class │ ├── SuperClasses.class │ └── SuperClasses.java │ └── sample.jar └── vm_cli ├── Cargo.toml └── src └── main.rs /.gitattributes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/.gitattributes -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | env: 15 | CARGO_TERM_COLOR: always 16 | ENV CARGO_INCREMENTAL: 0 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | - uses: extractions/setup-just@v1 22 | 23 | - run: | 24 | rustup set auto-self-update disable 25 | rustup toolchain install stable --profile minimal 26 | 27 | - uses: taiki-e/install-action@nextest 28 | 29 | - uses: Swatinem/rust-cache@v2 30 | 31 | - name: Build and test 32 | run: just build test 33 | 34 | - name: Lint 35 | run: just lint 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | *.iml 4 | flamegraph.svg 5 | -------------------------------------------------------------------------------- /.java-version: -------------------------------------------------------------------------------- 1 | 1.8 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["bitflags", "CLASSFILE", "rjvm", "thiserror"] 3 | } 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "reader", 4 | "vm", 5 | "vm_cli" 6 | ] 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RJVM 2 | 3 | This project is an attempt to write a minimal JVM 7 using Rust. 4 | 5 | Important note: **this is a hobby project, built for fun and for learning purposes**. In particular, it is my first real 6 | program in Rust and I've used to learn the language - thus, I'm sure some parts of the code are not very "idiomatic" 7 | Rust since I'm just learning the language. 8 | 9 | The code quality is definitely not production ready - there are not enough tests, there isn't enough documentation and 10 | some of the initial decision should be revisited. (I.e.: this is not representative of the code I write for work 😊.) 11 | 12 | The code is licensed under the [Apache v2 license](./LICENSE). 13 | 14 | The architecture is discussed in a series of posts on my blog, [https://andreabergia.com](https://andreabergia.com/series/writing-a-jvm-in-rust/). 15 | 16 | ## What has been implemented and what hasn't 17 | 18 | The current code can execute [various simple programs](./vm/tests/resources/rjvm), but it has a lot of limitations. 19 | 20 | Here is a list of the implemented features: 21 | 22 | - parsing .class files 23 | - resolving classes from a jar file, or from a folder 24 | - execution of real code: 25 | - primitive types, arrays, strings 26 | - control flow statements 27 | - classes, subclasses, interfaces 28 | - methods (virtual, static, natives) 29 | - exception throwing and catching 30 | - stack traces 31 | - garbage collection 32 | 33 | However, there are a lot of important things not implemented (and not planned to): 34 | 35 | - threading 36 | - multi dimensional arrays 37 | - reflection 38 | - annotations 39 | - [class file verification](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10) 40 | - I/O 41 | - just in time code execution (JIT) 42 | - proper class loaders 43 | 44 | The JVM uses the _real classes_ from [OpenJDK 7](https://jdk.java.net/java-se-ri/7) - meaning the classes such as 45 | `java.lang.Object`, `java.lang.String` or `java.lang.Exception` are real production classes, without any modifications. 46 | The JVM is "good enough" to parse and execute their code, something which makes me very happy indeed. 😊 47 | 48 | The VM is limited to 64 bits platforms, as there are quite a few places where I assume that the size of a pointer 49 | is exactly 8 bytes. 50 | 51 | ## Implementations that should be modified 52 | 53 | One poor implementation detail is that for things like stack overflow, accessing an array out of bounds, divisions by 54 | zero, etc. I should be throwing real java exceptions, rather than internal errors that will abort executions. 55 | In general, the error handling is not great - there are no details when you get an internal error, something that made 56 | debugging more painful than it should have been. 57 | 58 | There's also quite a few things whose implementation is quite poor, or not really coherent with the JVM specs, 59 | but it is "good enough" to execute some simple code; for example I do not have a class for arrays. If you're curious, 60 | look for the TODO in the code. 61 | 62 | I'm also quite sure there's a million bugs in the code. 😅 63 | 64 | ## Code structure 65 | 66 | The code is currently structured in three crates: 67 | 68 | - `reader`, which is able to read a `.class` file and contains various data structures for modelling their content; 69 | - `vm`, which contains the virtual machine that can execute the code as a library; 70 | - `vm_cli`, which contains a very simple command-line launcher to run the vm, in the spirit of the `java` executable. 71 | 72 | There are some unit test and some integration tests - definitely not enough, but since this is not production code but 73 | just a learning exercise, I'm not that worried about it. Still, IntelliJ tells me I have a bit above 80% of coverage, 74 | which is not bad. The error paths aren't really tested, though. 75 | 76 | I use [just](https://github.com/casey/just) as a command runner, but most tasks are just cargo commands. 77 | 78 | # Project status and further works 79 | 80 | I consider the project complete. It was super instructive, but I do not plan to keep working on it. 81 | 82 | The only thing I'm considering is to extract the `reader` crate in a separate repository, and publish it on 83 | [crates.io](https://crates.io/), since it could actually be useful to someone else. 84 | -------------------------------------------------------------------------------- /TODO list for reader.md: -------------------------------------------------------------------------------- 1 | Things still to implement in the reader: 2 | 3 | - [ ] class attributes 4 | - [ ] [InnerClasses](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.6) 5 | - [ ] [EnclosingMethod](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.7) 6 | - [ ] [synthetic](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.8) 7 | - [ ] [signature](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.9) 8 | - [x] [SourceFile](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.10) 9 | - [ ] [SourceDebugExtension](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.11) 10 | - [x] [deprecated](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.15) 11 | - [ ] [runtime visible annotations](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.16) 12 | - [ ] [runtime invisible annotations](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.17) 13 | - [ ] [BootstrapMethods](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.21) 14 | - [ ] methods 15 | - [ ] code 16 | - [ ] exception tables 17 | - [ ] attributes 18 | - [x] [LineNumberTable](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.12) 19 | - [ ] [LocalVariableTable](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.13) 20 | - [ ] [LocalVariableTypeTable](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.14) 21 | - [ ] [StackMapTable](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.4) 22 | - [ ] source code mappings 23 | - [ ] attributes 24 | - [ ] [synthetic](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.8) 25 | - [ ] [signature](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.9) 26 | - [x] [deprecated](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.15) 27 | - [ ] [exceptions](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.5) 28 | - [ ] [runtime visible annotations](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.16) 29 | - [ ] [runtime invisible annotations](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.17) 30 | - [ ] [runtime visible parameter annotations](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.18) 31 | - [ ] [runtime invisible parameter annotations](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.19) 32 | - [ ] [annotation default](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.20) 33 | - [ ] field 34 | - [ ] attributes 35 | - [x] constant value 36 | - [ ] [synthetic](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.8) 37 | - [ ] [signature](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.9) 38 | - [x] [deprecated](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.15) 39 | - [ ] [runtime visible annotations](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.16) 40 | - [ ] [runtime invisible annotations](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.17) 41 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | # https://github.com/casey/just 2 | 3 | default: build test lint 4 | 5 | build: 6 | cargo build 7 | 8 | test: 9 | RUST_LOG=trace cargo nextest run 10 | 11 | test-verbose: 12 | RUST_LOG=trace cargo nextest run --no-capture 13 | 14 | lint: 15 | cargo clippy --fix --allow-dirty --allow-staged 16 | 17 | clean: 18 | cargo clean 19 | 20 | fmt: 21 | cargo +nightly fmt 22 | 23 | generate-test-classes: 24 | cd ./reader/tests/resources && rm -f *.class && ./compile.sh 25 | cd ./vm/tests/resources && rm -f *.class && ./compile.sh 26 | 27 | count-lines: 28 | wc -l */{src,tests}/**/*.rs */{src,tests}/*.rs */tests/resources/**/*.java 29 | 30 | miri: 31 | cargo clean 32 | MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-report-progress" cargo +nightly miri test 33 | 34 | prof-vm-integration: clean 35 | cd vm && CARGO_PROFILE_BENCH_DEBUG=true cargo flamegraph --test integration --root && open flamegraph.svg 36 | 37 | find-unused-dependencies: 38 | cargo +nightly udeps --all-targets 39 | -------------------------------------------------------------------------------- /reader/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rjvm_reader" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | thiserror = "1" 8 | bitflags = "1.3" 9 | strum_macros = "0.24" 10 | result = "1.0.0" 11 | log = "0.4.17" 12 | test-log = "0.2.11" 13 | env_logger = "*" 14 | itertools = "0.10.5" 15 | cesu8 = "1.1" 16 | -------------------------------------------------------------------------------- /reader/src/attribute.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, fmt::Formatter}; 2 | 3 | /// An attribute in the class file, which can belong to a class, field, method, or code block. 4 | #[derive(Debug, Default, PartialEq)] 5 | pub struct Attribute { 6 | pub name: String, 7 | pub bytes: Vec, 8 | } 9 | 10 | impl fmt::Display for Attribute { 11 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 12 | write!(f, "{} (data = {} bytes)", self.name, self.bytes.len()) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /reader/src/buffer.rs: -------------------------------------------------------------------------------- 1 | use cesu8::from_java_cesu8; 2 | use thiserror::Error; 3 | 4 | /// A buffer reader, used to marshall data from a generic byte array 5 | pub struct Buffer<'a> { 6 | buffer: &'a [u8], 7 | position: usize, 8 | } 9 | 10 | /// Errors related to reading from a [Buffer] 11 | #[derive(Error, Debug, PartialEq)] 12 | pub enum BufferError { 13 | #[error("unexpected end of data")] 14 | UnexpectedEndOfData, 15 | 16 | #[error("invalid cesu8 string")] 17 | InvalidCesu8String, 18 | } 19 | 20 | type Result = std::result::Result; 21 | 22 | impl<'a> Buffer<'a> { 23 | pub fn new(data: &'a [u8]) -> Self { 24 | Buffer { 25 | buffer: data, 26 | position: 0, 27 | } 28 | } 29 | 30 | fn advance(&mut self, size: usize) -> Result<&'a [u8]> { 31 | if self.position + size > self.buffer.len() { 32 | Err(BufferError::UnexpectedEndOfData) 33 | } else { 34 | let slice = &self.buffer[self.position..self.position + size]; 35 | self.position += size; 36 | Ok(slice) 37 | } 38 | } 39 | 40 | pub fn read_u8(&mut self) -> Result { 41 | self.advance(std::mem::size_of::()) 42 | .map(|bytes| u8::from_be_bytes(bytes.try_into().unwrap())) 43 | } 44 | 45 | pub fn read_u16(&mut self) -> Result { 46 | self.advance(std::mem::size_of::()) 47 | .map(|bytes| u16::from_be_bytes(bytes.try_into().unwrap())) 48 | } 49 | 50 | pub fn read_u32(&mut self) -> Result { 51 | self.advance(std::mem::size_of::()) 52 | .map(|bytes| u32::from_be_bytes(bytes.try_into().unwrap())) 53 | } 54 | 55 | pub fn read_i32(&mut self) -> Result { 56 | self.advance(std::mem::size_of::()) 57 | .map(|bytes| i32::from_be_bytes(bytes.try_into().unwrap())) 58 | } 59 | 60 | pub fn read_i64(&mut self) -> Result { 61 | self.advance(std::mem::size_of::()) 62 | .map(|bytes| i64::from_be_bytes(bytes.try_into().unwrap())) 63 | } 64 | 65 | pub fn read_f32(&mut self) -> Result { 66 | self.advance(std::mem::size_of::()) 67 | .map(|bytes| f32::from_be_bytes(bytes.try_into().unwrap())) 68 | } 69 | 70 | pub fn read_f64(&mut self) -> Result { 71 | self.advance(std::mem::size_of::()) 72 | .map(|bytes| f64::from_be_bytes(bytes.try_into().unwrap())) 73 | } 74 | 75 | pub fn read_utf8(&mut self, len: usize) -> Result { 76 | self.advance(len) 77 | .and_then(|bytes| from_java_cesu8(bytes).map_err(|_| BufferError::InvalidCesu8String)) 78 | .map(|cow_string| cow_string.into_owned()) 79 | } 80 | 81 | pub fn read_bytes(&mut self, len: usize) -> Result<&'a [u8]> { 82 | self.advance(len) 83 | } 84 | 85 | #[allow(dead_code)] 86 | pub fn has_more_data(&self) -> bool { 87 | self.position < self.buffer.len() 88 | } 89 | } 90 | 91 | #[cfg(test)] 92 | mod tests { 93 | use crate::buffer::Buffer; 94 | 95 | #[test] 96 | fn buffer_works() { 97 | let data = vec![0x00, 0x00, 0x00, 0x42]; 98 | let mut buffer = Buffer::new(&data); 99 | 100 | assert!(buffer.has_more_data()); 101 | assert_eq!(0x42u32, buffer.read_u32().unwrap()); 102 | assert!(!buffer.has_more_data()); 103 | 104 | assert!(buffer.read_u32().is_err()); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /reader/src/class_access_flags.rs: -------------------------------------------------------------------------------- 1 | bitflags! { 2 | /// Class flags 3 | pub struct ClassAccessFlags: u16 { 4 | const PUBLIC = 0x0001; 5 | const FINAL = 0x0010; 6 | const SUPER = 0x0020; 7 | const INTERFACE = 0x0200; 8 | const ABSTRACT = 0x0400; 9 | const SYNTHETIC = 0x1000; 10 | const ANNOTATION = 0x2000; 11 | const ENUM = 0x4000; 12 | } 13 | } 14 | 15 | impl Default for ClassAccessFlags { 16 | fn default() -> ClassAccessFlags { 17 | ClassAccessFlags::empty() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /reader/src/class_file.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use crate::{ 4 | class_access_flags::ClassAccessFlags, class_file_field::ClassFileField, 5 | class_file_method::ClassFileMethod, class_file_version::ClassFileVersion, 6 | constant_pool::ConstantPool, 7 | }; 8 | 9 | /// Represents the content of a .class file. 10 | #[derive(Debug, Default)] 11 | pub struct ClassFile { 12 | pub version: ClassFileVersion, 13 | pub constants: ConstantPool, 14 | pub flags: ClassAccessFlags, 15 | pub name: String, 16 | pub superclass: Option, 17 | pub interfaces: Vec, 18 | pub fields: Vec, 19 | pub methods: Vec, 20 | pub deprecated: bool, 21 | pub source_file: Option, 22 | } 23 | 24 | impl fmt::Display for ClassFile { 25 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 26 | write!(f, "Class {} ", self.name,)?; 27 | if let Some(superclass) = self.superclass.as_ref() { 28 | write!(f, "(extends {}) ", superclass)?; 29 | } 30 | writeln!(f, "version: {}", self.version)?; 31 | write!(f, "{}", self.constants)?; 32 | writeln!( 33 | f, 34 | "flags: {:?}, deprecated: {}", 35 | self.flags, self.deprecated 36 | )?; 37 | writeln!(f, "interfaces: {:?}", self.interfaces)?; 38 | writeln!(f, "fields:")?; 39 | for field in self.fields.iter() { 40 | writeln!(f, " - {field}")?; 41 | } 42 | writeln!(f, "methods:")?; 43 | for method in self.methods.iter() { 44 | writeln!(f, " - {method}")?; 45 | } 46 | Ok(()) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /reader/src/class_file_field.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, fmt::Formatter}; 2 | 3 | use crate::{field_flags::FieldFlags, field_type::FieldType}; 4 | 5 | /// Models a field in a class 6 | #[derive(Debug, PartialEq)] 7 | pub struct ClassFileField { 8 | pub flags: FieldFlags, 9 | pub name: String, 10 | pub type_descriptor: FieldType, 11 | /// Fields which model a constant (final) will have an attribute specifying the value 12 | pub constant_value: Option, 13 | pub deprecated: bool, 14 | } 15 | 16 | impl fmt::Display for ClassFileField { 17 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 18 | write!( 19 | f, 20 | "{:?} {}: {} constant {:?}{}", 21 | self.flags, 22 | self.name, 23 | self.type_descriptor, 24 | self.constant_value, 25 | if self.deprecated { " (deprecated)" } else { "" } 26 | ) 27 | } 28 | } 29 | 30 | /// Possible constant values of a field 31 | #[derive(Debug, PartialEq, strum_macros::Display)] 32 | pub enum FieldConstantValue { 33 | Int(i32), 34 | Float(f32), 35 | Long(i64), 36 | Double(f64), 37 | String(String), 38 | } 39 | -------------------------------------------------------------------------------- /reader/src/class_file_method.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, fmt::Formatter}; 2 | 3 | use crate::{ 4 | attribute::Attribute, 5 | exception_table::ExceptionTable, 6 | field_type::{BaseType, FieldType}, 7 | instruction::Instruction, 8 | line_number_table::LineNumberTable, 9 | method_descriptor::MethodDescriptor, 10 | method_flags::MethodFlags, 11 | }; 12 | 13 | /// Models a method in a class 14 | #[derive(Debug, PartialEq)] 15 | pub struct ClassFileMethod { 16 | pub flags: MethodFlags, 17 | pub name: String, 18 | /// The type descriptor in the internal JVM form, i.e. something like (L)I in the unparsed form 19 | pub type_descriptor: String, 20 | /// Parsed form of the method descriptor 21 | pub parsed_type_descriptor: MethodDescriptor, 22 | /// Generic attributes of the method 23 | // TODO: replace with some proper struct 24 | pub attributes: Vec, 25 | pub code: Option, 26 | pub deprecated: bool, 27 | /// List of exceptions in the `throws` clause of the method 28 | pub thrown_exceptions: Vec, 29 | } 30 | 31 | impl fmt::Display for ClassFileMethod { 32 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 33 | writeln!( 34 | f, 35 | "{:?} {}: {}{} throws {:?}", 36 | self.flags, 37 | self.name, 38 | self.parsed_type_descriptor, 39 | if self.deprecated { " (deprecated)" } else { "" }, 40 | self.thrown_exceptions, 41 | )?; 42 | if let Some(code) = &self.code { 43 | writeln!(f, " code: {code}")?; 44 | } 45 | write!(f, " raw_attributes: {:?}", self.attributes) 46 | } 47 | } 48 | 49 | impl ClassFileMethod { 50 | pub fn is_static(&self) -> bool { 51 | self.flags.contains(MethodFlags::STATIC) 52 | } 53 | 54 | pub fn is_native(&self) -> bool { 55 | self.flags.contains(MethodFlags::NATIVE) 56 | } 57 | 58 | pub fn is_void(&self) -> bool { 59 | self.parsed_type_descriptor.return_type.is_none() 60 | } 61 | 62 | pub fn returns(&self, expected_type: FieldType) -> bool { 63 | match self.parsed_type_descriptor.return_type { 64 | Some(FieldType::Base(BaseType::Int)) 65 | | Some(FieldType::Base(BaseType::Short)) 66 | | Some(FieldType::Base(BaseType::Char)) 67 | | Some(FieldType::Base(BaseType::Byte)) 68 | | Some(FieldType::Base(BaseType::Boolean)) => { 69 | FieldType::Base(BaseType::Int) == expected_type 70 | } 71 | _ => self.parsed_type_descriptor.return_type == Some(expected_type), 72 | } 73 | } 74 | } 75 | 76 | /// Code of a given method 77 | #[derive(Debug, Default, PartialEq)] 78 | pub struct ClassFileMethodCode { 79 | /// Maximum depth of the stack at any time 80 | pub max_stack: u16, 81 | /// Number of local variables used by the method 82 | pub max_locals: u16, 83 | /// Raw bytecode 84 | pub code: Vec, 85 | pub exception_table: ExceptionTable, 86 | pub line_number_table: Option, 87 | 88 | /// Generic unmapped attributes of the code 89 | // TODO: replace with some proper struct 90 | pub attributes: Vec, 91 | } 92 | 93 | impl fmt::Display for ClassFileMethodCode { 94 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 95 | writeln!( 96 | f, 97 | "max_stack = {}, max_locals = {}, exception_table = {:?}, line_number_table: {:?}, attributes = {:?}, instructions:", 98 | self.max_stack, self.max_locals, self.exception_table, self.line_number_table, self.attributes, 99 | )?; 100 | 101 | let instructions = Instruction::parse_instructions(&self.code); 102 | if let Ok(instructions) = instructions { 103 | for (address, instruction) in instructions { 104 | writeln!(f, " {address:3} {instruction:?}")?; 105 | } 106 | } else { 107 | writeln!(f, " unparsable code: {:?}", self.code)?; 108 | } 109 | Ok(()) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /reader/src/class_file_version.rs: -------------------------------------------------------------------------------- 1 | use crate::class_reader_error::{ClassReaderError, Result}; 2 | 3 | /// Versions of the JVM class file format. 4 | #[derive(Debug, PartialEq, Default, strum_macros::Display)] 5 | #[allow(dead_code)] 6 | pub enum ClassFileVersion { 7 | Jdk1_1, 8 | Jdk1_2, 9 | Jdk1_3, 10 | Jdk1_4, 11 | Jdk1_5, 12 | Jdk6, 13 | Jdk7, 14 | #[default] 15 | Jdk8, 16 | Jdk9, 17 | Jdk10, 18 | Jdk11, 19 | Jdk12, 20 | Jdk13, 21 | Jdk14, 22 | Jdk15, 23 | Jdk16, 24 | Jdk17, 25 | Jdk18, 26 | Jdk19, 27 | Jdk20, 28 | Jdk21, 29 | Jdk22, 30 | } 31 | 32 | impl ClassFileVersion { 33 | /// Creates a version from the major and minor versions specified in the class file 34 | pub fn from(major: u16, minor: u16) -> Result { 35 | match major { 36 | 45 => Ok(ClassFileVersion::Jdk1_1), 37 | 46 => Ok(ClassFileVersion::Jdk1_2), 38 | 47 => Ok(ClassFileVersion::Jdk1_3), 39 | 48 => Ok(ClassFileVersion::Jdk1_4), 40 | 49 => Ok(ClassFileVersion::Jdk1_5), 41 | 50 => Ok(ClassFileVersion::Jdk6), 42 | 51 => Ok(ClassFileVersion::Jdk7), 43 | 52 => Ok(ClassFileVersion::Jdk8), 44 | 53 => Ok(ClassFileVersion::Jdk9), 45 | 54 => Ok(ClassFileVersion::Jdk10), 46 | 55 => Ok(ClassFileVersion::Jdk11), 47 | 56 => Ok(ClassFileVersion::Jdk12), 48 | 57 => Ok(ClassFileVersion::Jdk13), 49 | 58 => Ok(ClassFileVersion::Jdk14), 50 | 59 => Ok(ClassFileVersion::Jdk15), 51 | 60 => Ok(ClassFileVersion::Jdk16), 52 | 61 => Ok(ClassFileVersion::Jdk17), 53 | 62 => Ok(ClassFileVersion::Jdk18), 54 | 63 => Ok(ClassFileVersion::Jdk19), 55 | 64 => Ok(ClassFileVersion::Jdk20), 56 | 65 => Ok(ClassFileVersion::Jdk21), 57 | 66 => Ok(ClassFileVersion::Jdk22), 58 | _ => Err(ClassReaderError::UnsupportedVersion(major, minor)), 59 | } 60 | } 61 | } 62 | 63 | #[cfg(test)] 64 | mod tests { 65 | use crate::{class_file_version::ClassFileVersion, class_reader_error::ClassReaderError}; 66 | 67 | #[test] 68 | fn can_parse_known_versions() { 69 | assert_eq!( 70 | ClassFileVersion::Jdk6, 71 | ClassFileVersion::from(50, 0).unwrap() 72 | ); 73 | } 74 | 75 | #[test] 76 | fn can_parse_future_versions() { 77 | assert_eq!( 78 | Err(ClassReaderError::UnsupportedVersion(99, 65535)), 79 | ClassFileVersion::from(99, 65535), 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /reader/src/class_reader_error.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | error::Error, 3 | fmt::{Display, Formatter}, 4 | }; 5 | 6 | use crate::{buffer::BufferError, constant_pool::InvalidConstantPoolIndexError}; 7 | 8 | /// Models the possible errors returned when reading a .class file 9 | #[derive(Debug, PartialEq, Eq)] 10 | pub enum ClassReaderError { 11 | /// Generic error meaning that the class file is invalid 12 | InvalidClassData(String, Option), 13 | UnsupportedVersion(u16, u16), 14 | /// Error while parsing a given type descriptor in the file 15 | InvalidTypeDescriptor(String), 16 | } 17 | 18 | impl ClassReaderError { 19 | pub fn invalid_class_data(message: String) -> Self { 20 | ClassReaderError::InvalidClassData(message, None) 21 | } 22 | } 23 | 24 | impl Display for ClassReaderError { 25 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 26 | match self { 27 | ClassReaderError::InvalidClassData(details, _) => { 28 | write!(f, "invalid class file: {details}") 29 | } 30 | ClassReaderError::UnsupportedVersion(major, minor) => { 31 | write!(f, "unsupported class file version {major}.{minor}") 32 | } 33 | ClassReaderError::InvalidTypeDescriptor(descriptor) => { 34 | write!(f, "invalid type descriptor: {descriptor}") 35 | } 36 | } 37 | } 38 | } 39 | 40 | impl Error for ClassReaderError { 41 | fn source(&self) -> Option<&(dyn Error + 'static)> { 42 | match self { 43 | ClassReaderError::InvalidClassData(_, Some(source)) => Some(source), 44 | _ => None, 45 | } 46 | } 47 | } 48 | 49 | pub type Result = std::result::Result; 50 | 51 | impl From for ClassReaderError { 52 | fn from(err: InvalidConstantPoolIndexError) -> Self { 53 | Self::InvalidClassData(err.to_string(), Some(err)) 54 | } 55 | } 56 | 57 | impl From for ClassReaderError { 58 | fn from(err: BufferError) -> Self { 59 | match err { 60 | BufferError::UnexpectedEndOfData => { 61 | Self::invalid_class_data("unexpected end of class file".to_string()) 62 | } 63 | BufferError::InvalidCesu8String => { 64 | Self::invalid_class_data("invalid cesu8 string".to_string()) 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /reader/src/constant_pool.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, vec::Vec}; 2 | use thiserror::Error; 3 | 4 | /// Types of a constant in the constant pool of a class, following the JVM spec: 5 | /// https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4 6 | #[derive(Debug, PartialEq)] 7 | pub enum ConstantPoolEntry { 8 | Utf8(String), 9 | Integer(i32), 10 | Float(f32), 11 | Long(i64), 12 | Double(f64), 13 | ClassReference(u16), 14 | StringReference(u16), 15 | FieldReference(u16, u16), 16 | MethodReference(u16, u16), 17 | InterfaceMethodReference(u16, u16), 18 | NameAndTypeDescriptor(u16, u16), 19 | } 20 | 21 | /// Constants in the pool generally take one slot, but long and double take two. We do not use 22 | /// the second one, so we have a tombstone to ensure the indexes match. 23 | #[derive(Debug)] 24 | enum ConstantPoolPhysicalEntry { 25 | Entry(ConstantPoolEntry), 26 | MultiByteEntryTombstone(), 27 | } 28 | 29 | /// Implementation of the constant pool of a java class. 30 | /// Note that constants are 1-based in java. 31 | #[derive(Debug, Default)] 32 | pub struct ConstantPool { 33 | entries: Vec, 34 | } 35 | 36 | /// Error used to signal that an attempt was made to access a non existing constant pool entry. 37 | #[derive(Error, Debug, PartialEq, Eq)] 38 | #[error("invalid constant pool index: {index}")] 39 | pub struct InvalidConstantPoolIndexError { 40 | pub index: u16, 41 | } 42 | 43 | impl InvalidConstantPoolIndexError { 44 | fn new(index: u16) -> Self { 45 | InvalidConstantPoolIndexError { index } 46 | } 47 | } 48 | 49 | impl ConstantPool { 50 | pub fn new() -> ConstantPool { 51 | Default::default() 52 | } 53 | 54 | /// Adds a new entry. 55 | pub fn add(&mut self, entry: ConstantPoolEntry) { 56 | let add_tombstone = matches!( 57 | &entry, 58 | ConstantPoolEntry::Long(_) | ConstantPoolEntry::Double(_) 59 | ); 60 | self.entries.push(ConstantPoolPhysicalEntry::Entry(entry)); 61 | 62 | if add_tombstone { 63 | self.entries 64 | .push(ConstantPoolPhysicalEntry::MultiByteEntryTombstone()) 65 | } 66 | } 67 | 68 | /// Accesses an entry given its index. Note that it must be 1-based! 69 | pub fn get( 70 | &self, 71 | input_index: u16, 72 | ) -> Result<&ConstantPoolEntry, InvalidConstantPoolIndexError> { 73 | if input_index == 0 || input_index as usize > self.entries.len() { 74 | Err(InvalidConstantPoolIndexError::new(input_index)) 75 | } else { 76 | let i = (input_index - 1) as usize; 77 | let entry = &self.entries[i]; 78 | match entry { 79 | ConstantPoolPhysicalEntry::Entry(entry) => Ok(entry), 80 | ConstantPoolPhysicalEntry::MultiByteEntryTombstone() => { 81 | Err(InvalidConstantPoolIndexError::new(input_index)) 82 | } 83 | } 84 | } 85 | } 86 | 87 | fn fmt_entry(&self, idx: u16) -> Result { 88 | let entry = self.get(idx)?; 89 | let text = match entry { 90 | ConstantPoolEntry::Utf8(ref s) => format!("String: \"{s}\""), 91 | ConstantPoolEntry::Integer(n) => format!("Integer: {n}"), 92 | ConstantPoolEntry::Float(n) => format!("Float: {n}"), 93 | ConstantPoolEntry::Long(n) => format!("Long: {n}"), 94 | ConstantPoolEntry::Double(n) => format!("Double: {n}"), 95 | ConstantPoolEntry::ClassReference(n) => { 96 | format!("ClassReference: {} => ({})", n, self.fmt_entry(*n)?) 97 | } 98 | ConstantPoolEntry::StringReference(n) => { 99 | format!("StringReference: {} => ({})", n, self.fmt_entry(*n)?) 100 | } 101 | ConstantPoolEntry::FieldReference(i, j) => { 102 | format!( 103 | "FieldReference: {}, {} => ({}), ({})", 104 | i, 105 | j, 106 | self.fmt_entry(*i)?, 107 | self.fmt_entry(*j)? 108 | ) 109 | } 110 | ConstantPoolEntry::MethodReference(i, j) => { 111 | format!( 112 | "MethodReference: {}, {} => ({}), ({})", 113 | i, 114 | j, 115 | self.fmt_entry(*i)?, 116 | self.fmt_entry(*j)? 117 | ) 118 | } 119 | ConstantPoolEntry::InterfaceMethodReference(i, j) => { 120 | format!( 121 | "InterfaceMethodReference: {}, {} => ({}), ({})", 122 | i, 123 | j, 124 | self.fmt_entry(*i)?, 125 | self.fmt_entry(*j)? 126 | ) 127 | } 128 | &ConstantPoolEntry::NameAndTypeDescriptor(i, j) => { 129 | format!( 130 | "NameAndTypeDescriptor: {}, {} => ({}), ({})", 131 | i, 132 | j, 133 | self.fmt_entry(i)?, 134 | self.fmt_entry(j)? 135 | ) 136 | } 137 | }; 138 | Ok(text) 139 | } 140 | 141 | pub fn text_of(&self, idx: u16) -> Result { 142 | let entry = self.get(idx)?; 143 | let text = match entry { 144 | ConstantPoolEntry::Utf8(ref s) => s.clone(), 145 | ConstantPoolEntry::Integer(n) => n.to_string(), 146 | ConstantPoolEntry::Float(n) => n.to_string(), 147 | ConstantPoolEntry::Long(n) => n.to_string(), 148 | ConstantPoolEntry::Double(n) => n.to_string(), 149 | ConstantPoolEntry::ClassReference(n) => self.text_of(*n)?, 150 | ConstantPoolEntry::StringReference(n) => self.text_of(*n)?, 151 | ConstantPoolEntry::FieldReference(i, j) => { 152 | format!("{}.{}", self.text_of(*i)?, self.text_of(*j)?) 153 | } 154 | ConstantPoolEntry::MethodReference(i, j) => { 155 | format!("{}.{}", self.text_of(*i)?, self.text_of(*j)?) 156 | } 157 | ConstantPoolEntry::InterfaceMethodReference(i, j) => { 158 | format!("{}.{}", self.text_of(*i)?, self.text_of(*j)?) 159 | } 160 | ConstantPoolEntry::NameAndTypeDescriptor(i, j) => { 161 | format!("{}: {}", self.text_of(*i)?, self.text_of(*j)?) 162 | } 163 | }; 164 | Ok(text) 165 | } 166 | } 167 | 168 | impl fmt::Display for ConstantPool { 169 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 170 | writeln!(f, "Constant pool: (size: {})", self.entries.len())?; 171 | for (raw_idx, _) in self.entries.iter().enumerate() { 172 | let index = (raw_idx + 1) as u16; 173 | let entry_text = self.fmt_entry(index).map_err(|_| fmt::Error::default())?; 174 | writeln!(f, " {}, {}", index, entry_text)?; 175 | } 176 | Ok(()) 177 | } 178 | } 179 | 180 | #[cfg(test)] 181 | mod tests { 182 | use crate::constant_pool::{ConstantPool, ConstantPoolEntry, InvalidConstantPoolIndexError}; 183 | 184 | #[test] 185 | fn constant_pool_works() { 186 | let mut cp = ConstantPool::new(); 187 | cp.add(ConstantPoolEntry::Utf8("hey".to_string())); 188 | cp.add(ConstantPoolEntry::Integer(1)); 189 | cp.add(ConstantPoolEntry::Float(2.1)); 190 | cp.add(ConstantPoolEntry::Long(123)); 191 | cp.add(ConstantPoolEntry::Double(3.56)); 192 | cp.add(ConstantPoolEntry::ClassReference(1)); 193 | cp.add(ConstantPoolEntry::StringReference(1)); 194 | cp.add(ConstantPoolEntry::Utf8("joe".to_string())); 195 | cp.add(ConstantPoolEntry::FieldReference(1, 10)); 196 | cp.add(ConstantPoolEntry::MethodReference(1, 10)); 197 | cp.add(ConstantPoolEntry::InterfaceMethodReference(1, 10)); 198 | cp.add(ConstantPoolEntry::NameAndTypeDescriptor(1, 10)); 199 | 200 | assert_eq!( 201 | ConstantPoolEntry::Utf8("hey".to_string()), 202 | *cp.get(1).unwrap() 203 | ); 204 | assert_eq!(ConstantPoolEntry::Integer(1), *cp.get(2).unwrap()); 205 | assert_eq!(ConstantPoolEntry::Float(2.1), *cp.get(3).unwrap()); 206 | assert_eq!(ConstantPoolEntry::Long(123i64), *cp.get(4).unwrap()); 207 | assert_eq!(Err(InvalidConstantPoolIndexError::new(5)), cp.get(5)); 208 | assert_eq!(ConstantPoolEntry::Double(3.56), *cp.get(6).unwrap()); 209 | assert_eq!(Err(InvalidConstantPoolIndexError::new(7)), cp.get(7)); 210 | assert_eq!(ConstantPoolEntry::ClassReference(1), *cp.get(8).unwrap()); 211 | assert_eq!(ConstantPoolEntry::StringReference(1), *cp.get(9).unwrap()); 212 | assert_eq!( 213 | ConstantPoolEntry::Utf8("joe".to_string()), 214 | *cp.get(10).unwrap() 215 | ); 216 | assert_eq!( 217 | ConstantPoolEntry::FieldReference(1, 10), 218 | *cp.get(11).unwrap() 219 | ); 220 | assert_eq!( 221 | ConstantPoolEntry::MethodReference(1, 10), 222 | *cp.get(12).unwrap() 223 | ); 224 | assert_eq!( 225 | ConstantPoolEntry::InterfaceMethodReference(1, 10), 226 | *cp.get(13).unwrap() 227 | ); 228 | assert_eq!( 229 | ConstantPoolEntry::NameAndTypeDescriptor(1, 10), 230 | *cp.get(14).unwrap() 231 | ); 232 | 233 | assert_eq!("hey", cp.text_of(1).unwrap()); 234 | assert_eq!("1", cp.text_of(2).unwrap()); 235 | assert_eq!("2.1", cp.text_of(3).unwrap()); 236 | assert_eq!("123", cp.text_of(4).unwrap()); 237 | assert_eq!(Err(InvalidConstantPoolIndexError::new(5)), cp.text_of(5)); 238 | assert_eq!("3.56", cp.text_of(6).unwrap()); 239 | assert_eq!(Err(InvalidConstantPoolIndexError::new(7)), cp.text_of(7)); 240 | assert_eq!("hey", cp.text_of(8).unwrap()); 241 | assert_eq!("hey", cp.text_of(9).unwrap()); 242 | assert_eq!("joe", cp.text_of(10).unwrap()); 243 | assert_eq!("hey.joe", cp.text_of(11).unwrap()); 244 | assert_eq!("hey.joe", cp.text_of(12).unwrap()); 245 | assert_eq!("hey.joe", cp.text_of(13).unwrap()); 246 | assert_eq!("hey: joe", cp.text_of(14).unwrap()); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /reader/src/exception_table.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | use crate::program_counter::ProgramCounter; 4 | 5 | /// Exception table of a method's code 6 | #[derive(Debug, Default, PartialEq)] 7 | pub struct ExceptionTable { 8 | entries: Vec, 9 | } 10 | 11 | impl ExceptionTable { 12 | pub fn new(entries: Vec) -> Self { 13 | Self { entries } 14 | } 15 | 16 | pub fn lookup(&self, pc: ProgramCounter) -> Vec<&ExceptionTableEntry> { 17 | self.entries 18 | .iter() 19 | .filter(|entry| entry.range.contains(&pc)) 20 | .collect() 21 | } 22 | } 23 | 24 | /// Entries of the exception table 25 | #[derive(Debug, PartialEq, Eq, Clone)] 26 | pub struct ExceptionTableEntry { 27 | /// The range of program counters that this entry covers 28 | pub range: Range, 29 | /// The address of the handler of this entry 30 | pub handler_pc: ProgramCounter, 31 | /// The class or superclass that matches this entry 32 | pub catch_class: Option, 33 | } 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | use crate::{ 38 | exception_table::{ExceptionTable, ExceptionTableEntry}, 39 | program_counter::ProgramCounter, 40 | }; 41 | 42 | #[test] 43 | fn can_lookup_catch_handler() { 44 | let entry_1 = ExceptionTableEntry { 45 | range: ProgramCounter(0)..ProgramCounter(4), 46 | handler_pc: ProgramCounter(99), 47 | catch_class: None, 48 | }; 49 | let entry_2 = ExceptionTableEntry { 50 | range: ProgramCounter(8)..ProgramCounter(14), 51 | handler_pc: ProgramCounter(88), 52 | catch_class: Some("java/lang/RuntimeException".to_string()), 53 | }; 54 | let entry_3 = ExceptionTableEntry { 55 | range: ProgramCounter(13)..ProgramCounter(14), 56 | handler_pc: ProgramCounter(77), 57 | catch_class: Some("java/lang/ClassCastException".to_string()), 58 | }; 59 | let table = ExceptionTable::new(vec![entry_1.clone(), entry_2.clone(), entry_3.clone()]); 60 | 61 | assert_eq!(vec![&entry_1], table.lookup(ProgramCounter(0))); 62 | assert_eq!(vec![&entry_1], table.lookup(ProgramCounter(1))); 63 | assert!(table.lookup(ProgramCounter(4)).is_empty()); 64 | assert_eq!(vec![&entry_2], table.lookup(ProgramCounter(8))); 65 | assert_eq!(vec![&entry_2, &entry_3], table.lookup(ProgramCounter(13))); 66 | assert!(table.lookup(ProgramCounter(14)).is_empty()); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /reader/src/field_flags.rs: -------------------------------------------------------------------------------- 1 | bitflags! { 2 | /// Possible flags of a class field 3 | pub struct FieldFlags: u16 { 4 | const PUBLIC = 0x0001; 5 | const PRIVATE = 0x0002; 6 | const PROTECTED = 0x004; 7 | const STATIC = 0x0008; 8 | const FINAL = 0x0010; 9 | const VOLATILE = 0x0040; 10 | const TRANSIENT = 0x0080; 11 | const SYNTHETIC = 0x1000; 12 | const ENUM = 0x4000; 13 | } 14 | } 15 | 16 | impl Default for FieldFlags { 17 | fn default() -> FieldFlags { 18 | FieldFlags::empty() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /reader/src/field_type.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, fmt::Formatter, str::Chars}; 2 | 3 | use itertools::Itertools; 4 | 5 | use ClassReaderError::InvalidTypeDescriptor; 6 | 7 | use crate::class_reader_error::ClassReaderError; 8 | 9 | /// Models the type of one field, or one parameter of a method 10 | #[derive(Debug, Clone, PartialEq)] 11 | pub enum FieldType { 12 | /// Primitive types 13 | Base(BaseType), 14 | 15 | /// Standard object 16 | Object(String), 17 | 18 | /// Array 19 | Array(Box), 20 | } 21 | 22 | impl fmt::Display for FieldType { 23 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 24 | match self { 25 | FieldType::Base(base) => write!(f, "{base}"), 26 | FieldType::Object(class) => f.write_str(class), 27 | FieldType::Array(component_type) => write!(f, "{component_type}[]"), 28 | } 29 | } 30 | } 31 | 32 | /// Possible primitive types 33 | #[derive(Debug, Clone, PartialEq, strum_macros::Display)] 34 | #[repr(u8)] 35 | pub enum BaseType { 36 | Byte, 37 | Char, 38 | Double, 39 | Float, 40 | Int, 41 | Long, 42 | Short, 43 | Boolean, 44 | } 45 | 46 | impl FieldType { 47 | /// Parses a type descriptor as specified in the JVM specs: 48 | /// https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.2 49 | pub fn parse(type_descriptor: &str) -> Result { 50 | let mut chars = type_descriptor.chars(); 51 | let descriptor = Self::parse_from(type_descriptor, &mut chars)?; 52 | match chars.next() { 53 | None => Ok(descriptor), 54 | Some(_) => Err(InvalidTypeDescriptor(type_descriptor.to_string())), 55 | } 56 | } 57 | 58 | pub(crate) fn parse_from( 59 | type_descriptor: &str, 60 | chars: &mut Chars, 61 | ) -> Result { 62 | let first_char = chars 63 | .next() 64 | .ok_or(InvalidTypeDescriptor(type_descriptor.to_string()))?; 65 | 66 | Ok(match first_char { 67 | 'B' => FieldType::Base(BaseType::Byte), 68 | 'C' => FieldType::Base(BaseType::Char), 69 | 'D' => FieldType::Base(BaseType::Double), 70 | 'F' => FieldType::Base(BaseType::Float), 71 | 'I' => FieldType::Base(BaseType::Int), 72 | 'J' => FieldType::Base(BaseType::Long), 73 | 'S' => FieldType::Base(BaseType::Short), 74 | 'Z' => FieldType::Base(BaseType::Boolean), 75 | 'L' => { 76 | let class_name: String = chars.take_while_ref(|c| *c != ';').collect(); 77 | match chars.next() { 78 | Some(';') => FieldType::Object(class_name), 79 | _ => return Err(InvalidTypeDescriptor(type_descriptor.to_string())), 80 | } 81 | } 82 | '[' => { 83 | let component_type = Self::parse_from(type_descriptor, chars)?; 84 | FieldType::Array(Box::new(component_type)) 85 | } 86 | _ => return Err(InvalidTypeDescriptor(type_descriptor.to_string())), 87 | }) 88 | } 89 | } 90 | 91 | #[cfg(test)] 92 | mod tests { 93 | use crate::{ 94 | class_reader_error::ClassReaderError, 95 | field_type::{BaseType, FieldType}, 96 | }; 97 | 98 | #[test] 99 | fn cannot_parse_empty_descriptor() { 100 | assert!(matches!( 101 | FieldType::parse(""), 102 | Err(ClassReaderError::InvalidTypeDescriptor(s)) if s.is_empty() 103 | )); 104 | } 105 | 106 | #[test] 107 | fn cannot_parse_invalid_primitive() { 108 | assert!(matches!( 109 | FieldType::parse("W"), 110 | Err(ClassReaderError::InvalidTypeDescriptor(s)) if s == "W" 111 | )); 112 | } 113 | 114 | #[test] 115 | fn cannot_parse_missing_semicolon() { 116 | assert!(matches!( 117 | FieldType::parse("Ljava/lang/String"), 118 | Err(ClassReaderError::InvalidTypeDescriptor(s)) if s == "Ljava/lang/String" 119 | )); 120 | } 121 | 122 | #[test] 123 | fn cannot_parse_invalid_array() { 124 | assert!(matches!( 125 | FieldType::parse("["), 126 | Err(ClassReaderError::InvalidTypeDescriptor(s)) if s == "[" 127 | )); 128 | } 129 | 130 | #[test] 131 | fn can_parse_primitive_descriptors() { 132 | assert_eq!(Ok(FieldType::Base(BaseType::Byte)), FieldType::parse("B")); 133 | assert_eq!(Ok(FieldType::Base(BaseType::Char)), FieldType::parse("C")); 134 | assert_eq!(Ok(FieldType::Base(BaseType::Double)), FieldType::parse("D")); 135 | assert_eq!(Ok(FieldType::Base(BaseType::Float)), FieldType::parse("F")); 136 | assert_eq!(Ok(FieldType::Base(BaseType::Int)), FieldType::parse("I")); 137 | assert_eq!(Ok(FieldType::Base(BaseType::Long)), FieldType::parse("J")); 138 | assert_eq!(Ok(FieldType::Base(BaseType::Short)), FieldType::parse("S")); 139 | assert_eq!( 140 | Ok(FieldType::Base(BaseType::Boolean)), 141 | FieldType::parse("Z") 142 | ); 143 | } 144 | 145 | #[test] 146 | fn can_parse_object_descriptors() { 147 | assert_eq!( 148 | Ok(FieldType::Object("rjvm/Test".to_string())), 149 | FieldType::parse("Lrjvm/Test;") 150 | ); 151 | } 152 | 153 | #[test] 154 | fn can_parse_array_description() { 155 | assert_eq!( 156 | Ok(FieldType::Array(Box::new(FieldType::Base(BaseType::Int)))), 157 | FieldType::parse("[I") 158 | ); 159 | assert_eq!( 160 | Ok(FieldType::Array(Box::new(FieldType::Object( 161 | "java/lang/String".to_string() 162 | )))), 163 | FieldType::parse("[Ljava/lang/String;") 164 | ); 165 | 166 | assert_eq!( 167 | Ok(FieldType::Array(Box::new(FieldType::Array(Box::new( 168 | FieldType::Base(BaseType::Double) 169 | ))))), 170 | FieldType::parse("[[D") 171 | ); 172 | } 173 | 174 | #[test] 175 | fn can_format_base_type() { 176 | assert_eq!("Long", format!("{}", FieldType::parse("J").unwrap())); 177 | } 178 | 179 | #[test] 180 | fn can_format_object() { 181 | assert_eq!( 182 | "java/lang/String", 183 | format!("{}", FieldType::parse("Ljava/lang/String;").unwrap()) 184 | ); 185 | } 186 | 187 | #[test] 188 | fn can_format_array() { 189 | assert_eq!("Int[]", format!("{}", FieldType::parse("[I").unwrap())); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /reader/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate bitflags; 3 | 4 | mod attribute; 5 | mod buffer; 6 | pub mod class_access_flags; 7 | pub mod class_file; 8 | pub mod class_file_field; 9 | pub mod class_file_method; 10 | pub mod class_file_version; 11 | pub mod class_reader; 12 | pub mod class_reader_error; 13 | pub mod constant_pool; 14 | pub mod exception_table; 15 | pub mod field_flags; 16 | pub mod field_type; 17 | pub mod instruction; 18 | pub mod line_number; 19 | pub mod line_number_table; 20 | pub mod method_descriptor; 21 | pub mod method_flags; 22 | pub mod program_counter; 23 | pub mod type_conversion; 24 | -------------------------------------------------------------------------------- /reader/src/line_number.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt, 3 | fmt::{Display, Formatter}, 4 | }; 5 | 6 | /// Line number in the source code 7 | #[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] 8 | pub struct LineNumber(pub u16); 9 | 10 | impl Display for LineNumber { 11 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 12 | write!(f, "{}", self.0) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /reader/src/line_number_table.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | 3 | use crate::line_number::LineNumber; 4 | use itertools::Itertools; 5 | 6 | use crate::program_counter::ProgramCounter; 7 | 8 | /// Table that models the relationship between program counters and line numbers in the source code. 9 | /// Entries are sorted by program counter. A table with two entries, the first starting at 0 and 10 | /// the second at 3, means that the first three instructions in the bytecode correspond to line 1 11 | /// and the rest to line 2. 12 | #[derive(Debug, PartialEq)] 13 | pub struct LineNumberTable { 14 | entries: Vec, 15 | } 16 | 17 | impl LineNumberTable { 18 | pub fn new(entries: Vec) -> Self { 19 | Self { 20 | entries: entries.into_iter().sorted().collect(), 21 | } 22 | } 23 | 24 | pub fn lookup_pc(&self, pc: ProgramCounter) -> LineNumber { 25 | let best_matching_entry_index = match self 26 | .entries 27 | .binary_search_by(|e| e.program_counter.cmp(&pc)) 28 | { 29 | Ok(index) => index, 30 | Err(index) => index - 1, 31 | }; 32 | self.entries[best_matching_entry_index].line_number 33 | } 34 | } 35 | 36 | /// Entries of a [LineNumberTable] 37 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 38 | pub struct LineNumberTableEntry { 39 | pub program_counter: ProgramCounter, 40 | pub line_number: LineNumber, 41 | } 42 | 43 | impl PartialOrd for LineNumberTableEntry { 44 | fn partial_cmp(&self, other: &Self) -> Option { 45 | self.program_counter.partial_cmp(&other.program_counter) 46 | } 47 | } 48 | 49 | impl Ord for LineNumberTableEntry { 50 | fn cmp(&self, other: &Self) -> Ordering { 51 | self.program_counter.cmp(&other.program_counter) 52 | } 53 | } 54 | 55 | impl LineNumberTableEntry { 56 | pub fn new(program_counter: ProgramCounter, line_number: LineNumber) -> Self { 57 | Self { 58 | program_counter, 59 | line_number, 60 | } 61 | } 62 | } 63 | 64 | #[cfg(test)] 65 | mod tests { 66 | use crate::{ 67 | line_number::LineNumber, 68 | line_number_table::{LineNumberTable, LineNumberTableEntry}, 69 | program_counter::ProgramCounter, 70 | }; 71 | 72 | #[test] 73 | fn can_lookup_line_number() { 74 | let table = LineNumberTable::new(vec![ 75 | LineNumberTableEntry::new(ProgramCounter(0), LineNumber(4)), 76 | LineNumberTableEntry::new(ProgramCounter(12), LineNumber(5)), 77 | LineNumberTableEntry::new(ProgramCounter(20), LineNumber(6)), 78 | ]); 79 | 80 | assert_eq!(LineNumber(4), table.lookup_pc(ProgramCounter(0))); 81 | assert_eq!(LineNumber(4), table.lookup_pc(ProgramCounter(11))); 82 | assert_eq!(LineNumber(5), table.lookup_pc(ProgramCounter(12))); 83 | assert_eq!(LineNumber(6), table.lookup_pc(ProgramCounter(20))); 84 | assert_eq!(LineNumber(6), table.lookup_pc(ProgramCounter(21))); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /reader/src/method_descriptor.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, fmt::Formatter, str::Chars}; 2 | 3 | use itertools::Itertools; 4 | 5 | use crate::{ 6 | class_reader_error::{ClassReaderError, ClassReaderError::InvalidTypeDescriptor}, 7 | field_type::FieldType, 8 | }; 9 | 10 | /// Models the signature of a method, i.e. the type of the parameters it takes and the type 11 | /// of the return value 12 | #[derive(Debug, Default, Clone, PartialEq)] 13 | pub struct MethodDescriptor { 14 | pub parameters: Vec, 15 | pub return_type: Option, 16 | } 17 | 18 | impl fmt::Display for MethodDescriptor { 19 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 20 | f.write_str("(")?; 21 | f.write_str(&self.parameters.iter().join(", "))?; 22 | match &self.return_type { 23 | Some(field_type) => write!(f, ") -> {field_type}"), 24 | None => f.write_str(") -> void"), 25 | } 26 | } 27 | } 28 | 29 | impl MethodDescriptor { 30 | /// Parses a method descriptor as specified in the JVM specs: 31 | /// https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.3 32 | pub fn parse(descriptor: &str) -> Result { 33 | let mut chars = descriptor.chars(); 34 | match chars.next() { 35 | Some('(') => { 36 | let parameters = Self::parse_parameters(descriptor, &mut chars)?; 37 | if Some(')') == chars.next() { 38 | let return_type = Self::parse_return_type(descriptor, &mut chars)?; 39 | Ok(MethodDescriptor { 40 | parameters, 41 | return_type, 42 | }) 43 | } else { 44 | Err(InvalidTypeDescriptor(descriptor.to_string())) 45 | } 46 | } 47 | _ => Err(InvalidTypeDescriptor(descriptor.to_string())), 48 | } 49 | } 50 | 51 | fn parse_parameters( 52 | descriptor: &str, 53 | chars: &mut Chars, 54 | ) -> Result, ClassReaderError> { 55 | let mut parameters = Vec::new(); 56 | loop { 57 | match chars.clone().next() { 58 | Some(')') => return Ok(parameters), 59 | Some(_) => { 60 | let param = FieldType::parse_from(descriptor, chars)?; 61 | parameters.push(param); 62 | } 63 | None => return Err(InvalidTypeDescriptor(descriptor.to_string())), 64 | } 65 | } 66 | } 67 | 68 | fn parse_return_type( 69 | descriptor: &str, 70 | chars: &mut Chars, 71 | ) -> Result, ClassReaderError> { 72 | match chars.clone().next() { 73 | Some('V') => Ok(None), 74 | Some(_) => { 75 | let return_type = Some(FieldType::parse_from(descriptor, chars)?); 76 | if chars.next().is_none() { 77 | Ok(return_type) 78 | } else { 79 | Err(InvalidTypeDescriptor(descriptor.to_string())) 80 | } 81 | } 82 | _ => Err(InvalidTypeDescriptor(descriptor.to_string())), 83 | } 84 | } 85 | 86 | pub fn num_arguments(&self) -> usize { 87 | self.parameters.len() 88 | } 89 | } 90 | 91 | #[cfg(test)] 92 | mod tests { 93 | use crate::{ 94 | class_reader_error::ClassReaderError, 95 | field_type::{BaseType, FieldType}, 96 | method_descriptor::MethodDescriptor, 97 | }; 98 | 99 | #[test] 100 | fn cannot_parse_empty_descriptor() { 101 | assert_cannot_parse("") 102 | } 103 | 104 | #[test] 105 | fn cannot_parse_invalid_descriptor_no_arguments() { 106 | assert_cannot_parse("J") 107 | } 108 | 109 | #[test] 110 | fn cannot_parse_invalid_descriptor_no_return_type() { 111 | assert_cannot_parse("(J)") 112 | } 113 | 114 | #[test] 115 | fn cannot_parse_invalid_descriptor_trash_after() { 116 | assert_cannot_parse("()JJ") 117 | } 118 | 119 | fn assert_cannot_parse(descriptor: &str) { 120 | assert!(matches!( 121 | MethodDescriptor::parse(descriptor), 122 | Err(ClassReaderError::InvalidTypeDescriptor(s)) if s == descriptor 123 | )); 124 | } 125 | 126 | #[test] 127 | fn can_parse_primitives() { 128 | assert_eq!( 129 | Ok(MethodDescriptor { 130 | parameters: vec![ 131 | FieldType::Base(BaseType::Long), 132 | FieldType::Base(BaseType::Int) 133 | ], 134 | return_type: Some(FieldType::Base(BaseType::Double)), 135 | }), 136 | MethodDescriptor::parse("(JI)D"), 137 | ); 138 | } 139 | 140 | #[test] 141 | fn can_parse_no_args_void_return() { 142 | assert_eq!( 143 | Ok(MethodDescriptor { 144 | parameters: vec![], 145 | return_type: None, 146 | }), 147 | MethodDescriptor::parse("()V"), 148 | ); 149 | } 150 | 151 | #[test] 152 | fn can_parse_arrays_objects() { 153 | assert_eq!( 154 | Ok(MethodDescriptor { 155 | parameters: vec![ 156 | FieldType::Object("java/lang/String".to_string()), 157 | FieldType::Base(BaseType::Int), 158 | ], 159 | return_type: Some(FieldType::Array(Box::new(FieldType::Base(BaseType::Long)))), 160 | }), 161 | MethodDescriptor::parse("(Ljava/lang/String;I)[J"), 162 | ); 163 | } 164 | 165 | #[test] 166 | fn can_format_void_to_void() { 167 | assert_eq!( 168 | "() -> void", 169 | format!("{}", MethodDescriptor::parse("()V").unwrap()) 170 | ); 171 | } 172 | 173 | #[test] 174 | fn can_format_parameters_to_return_type() { 175 | assert_eq!( 176 | "(java/lang/String, Int) -> Long[]", 177 | format!( 178 | "{}", 179 | MethodDescriptor::parse("(Ljava/lang/String;I)[J").unwrap() 180 | ) 181 | ); 182 | } 183 | 184 | #[test] 185 | fn can_get_num_arguments() { 186 | assert_eq!( 187 | 2, 188 | MethodDescriptor::parse("(Ljava/lang/String;I)[J") 189 | .unwrap() 190 | .num_arguments(), 191 | ); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /reader/src/method_flags.rs: -------------------------------------------------------------------------------- 1 | bitflags! { 2 | /// Flags of a class method 3 | pub struct MethodFlags: u16 { 4 | const PUBLIC = 0x0001; 5 | const PRIVATE = 0x0002; 6 | const PROTECTED = 0x004; 7 | const STATIC = 0x0008; 8 | const FINAL = 0x0010; 9 | const SYNCHRONIZED = 0x0020; 10 | const BRIDGE = 0x0040; 11 | const VARARGS = 0x0080; 12 | const NATIVE = 0x0100; 13 | const ABSTRACT = 0x0400; 14 | const STRICT = 0x0800; 15 | const SYNTHETIC = 0x1000; 16 | } 17 | } 18 | 19 | impl Default for MethodFlags { 20 | fn default() -> MethodFlags { 21 | MethodFlags::empty() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /reader/src/program_counter.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt, 3 | fmt::{Display, Formatter}, 4 | }; 5 | 6 | /// Models the program counter, i.e. the address of an instruction in the bytecode of a method 7 | #[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] 8 | pub struct ProgramCounter(pub u16); 9 | 10 | impl Display for ProgramCounter { 11 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 12 | write!(f, "{}", self.0) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /reader/src/type_conversion.rs: -------------------------------------------------------------------------------- 1 | /// Simple utility trait that models the conversion of various integer types into usize. 2 | /// Assumes that these types are all smaller than usize, or panics. 3 | pub trait ToUsizeSafe { 4 | fn into_usize_safe(self) -> usize; 5 | } 6 | 7 | impl ToUsizeSafe for u8 { 8 | fn into_usize_safe(self) -> usize { 9 | usize::try_from(self).expect("usize should have at least 8 bits") 10 | } 11 | } 12 | 13 | impl ToUsizeSafe for u16 { 14 | fn into_usize_safe(self) -> usize { 15 | usize::try_from(self).expect("usize should have at least 16 bits") 16 | } 17 | } 18 | 19 | impl ToUsizeSafe for u32 { 20 | fn into_usize_safe(self) -> usize { 21 | usize::try_from(self).expect("usize should have at least 32 bits") 22 | } 23 | } 24 | 25 | impl ToUsizeSafe for i32 { 26 | fn into_usize_safe(self) -> usize { 27 | usize::try_from(self).expect("usize should have at least 64 bits") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /reader/tests/integration/assertions.rs: -------------------------------------------------------------------------------- 1 | extern crate rjvm_reader; 2 | 3 | use rjvm_reader::{class_file_method::ClassFileMethod, method_flags::MethodFlags}; 4 | 5 | pub fn check_method( 6 | method: &ClassFileMethod, 7 | flags: MethodFlags, 8 | name: &str, 9 | type_descriptor: &str, 10 | ) { 11 | assert_eq!(method.flags, flags); 12 | assert_eq!(method.name, name); 13 | assert_eq!(method.type_descriptor, type_descriptor); 14 | } 15 | -------------------------------------------------------------------------------- /reader/tests/integration/constants_class_test.rs: -------------------------------------------------------------------------------- 1 | extern crate rjvm_reader; 2 | 3 | use rjvm_reader::{ 4 | class_file_field::{ClassFileField, FieldConstantValue}, 5 | field_flags::FieldFlags, 6 | field_type::{BaseType, FieldType}, 7 | }; 8 | use utils::read_class_from_bytes; 9 | 10 | use crate::utils; 11 | 12 | #[test_log::test] 13 | fn can_read_constants() { 14 | let class = read_class_from_bytes(include_bytes!("../resources/rjvm/Constants.class")); 15 | assert_eq!( 16 | vec!( 17 | ClassFileField { 18 | flags: FieldFlags::PUBLIC | FieldFlags::STATIC | FieldFlags::FINAL, 19 | name: "AN_INT".to_string(), 20 | type_descriptor: FieldType::Base(BaseType::Int), 21 | constant_value: Some(FieldConstantValue::Int(2023)), 22 | deprecated: false, 23 | }, 24 | ClassFileField { 25 | flags: FieldFlags::PROTECTED | FieldFlags::STATIC | FieldFlags::FINAL, 26 | name: "A_FLOAT".to_string(), 27 | type_descriptor: FieldType::Base(BaseType::Float), 28 | constant_value: Some(FieldConstantValue::Float(20.23)), 29 | deprecated: false, 30 | }, 31 | ClassFileField { 32 | flags: FieldFlags::PRIVATE | FieldFlags::STATIC | FieldFlags::FINAL, 33 | name: "A_LONG".to_string(), 34 | type_descriptor: FieldType::Base(BaseType::Long), 35 | constant_value: Some(FieldConstantValue::Long(2023)), 36 | deprecated: false, 37 | }, 38 | ClassFileField { 39 | flags: FieldFlags::PUBLIC | FieldFlags::STATIC | FieldFlags::FINAL, 40 | name: "A_DOUBLE".to_string(), 41 | type_descriptor: FieldType::Base(BaseType::Double), 42 | constant_value: Some(FieldConstantValue::Double(20.23)), 43 | deprecated: false, 44 | }, 45 | ClassFileField { 46 | flags: FieldFlags::PUBLIC | FieldFlags::STATIC | FieldFlags::FINAL, 47 | name: "A_STRING".to_string(), 48 | type_descriptor: FieldType::Object("java/lang/String".to_string()), 49 | constant_value: Some(FieldConstantValue::String("2023".to_string())), 50 | deprecated: false, 51 | } 52 | ), 53 | class.fields 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /reader/tests/integration/deprecated_class_test.rs: -------------------------------------------------------------------------------- 1 | extern crate rjvm_reader; 2 | 3 | use crate::utils; 4 | use utils::read_class_from_bytes; 5 | 6 | #[test_log::test] 7 | fn can_read_deprecated_attribute() { 8 | let class = read_class_from_bytes(include_bytes!("../resources/rjvm/DeprecatedClass.class")); 9 | assert!(class.deprecated); 10 | 11 | class.fields.get(0).unwrap(); 12 | 13 | let field = class 14 | .fields 15 | .into_iter() 16 | .find(|f| f.name == "deprecatedField") 17 | .expect("should find field"); 18 | assert!(field.deprecated); 19 | 20 | let method = class 21 | .methods 22 | .into_iter() 23 | .find(|m| m.name == "deprecatedMethod") 24 | .expect("should find method"); 25 | assert!(method.deprecated); 26 | } 27 | -------------------------------------------------------------------------------- /reader/tests/integration/exceptions.rs: -------------------------------------------------------------------------------- 1 | extern crate rjvm_reader; 2 | 3 | use rjvm_reader::{ 4 | class_file::ClassFile, 5 | exception_table::{ExceptionTable, ExceptionTableEntry}, 6 | method_flags::MethodFlags, 7 | program_counter::ProgramCounter, 8 | }; 9 | use utils::read_class_from_bytes; 10 | 11 | use crate::{assertions::check_method, utils}; 12 | 13 | #[test_log::test] 14 | fn can_read_class_with_exception_handler_and_throws() { 15 | let class = read_class_from_bytes(include_bytes!("../resources/rjvm/ExceptionsHandlers.class")); 16 | assert_eq!("rjvm/ExceptionsHandlers", class.name); 17 | 18 | check_methods(&class); 19 | } 20 | 21 | fn check_methods(class: &ClassFile) { 22 | assert_eq!(4, class.methods.len()); 23 | 24 | check_method(&class.methods[0], MethodFlags::empty(), "", "()V"); 25 | check_method(&class.methods[1], MethodFlags::empty(), "foo", "()V"); 26 | 27 | check_method(&class.methods[2], MethodFlags::empty(), "bar", "()V"); 28 | assert_eq!( 29 | vec![ 30 | "java/lang/IllegalArgumentException".to_string(), 31 | "java/lang/IllegalStateException".to_string() 32 | ], 33 | class.methods[2].thrown_exceptions 34 | ); 35 | 36 | check_method(&class.methods[3], MethodFlags::empty(), "test", "()V"); 37 | assert_eq!( 38 | ExceptionTable::new(vec![ 39 | ExceptionTableEntry { 40 | range: ProgramCounter(0)..ProgramCounter(4), 41 | handler_pc: ProgramCounter(11), 42 | catch_class: None 43 | }, 44 | ExceptionTableEntry { 45 | range: ProgramCounter(18)..ProgramCounter(22), 46 | handler_pc: ProgramCounter(25), 47 | catch_class: Some("java/lang/IllegalStateException".to_string()) 48 | } 49 | ]), 50 | class.methods[3].code.as_ref().unwrap().exception_table 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /reader/tests/integration/main.rs: -------------------------------------------------------------------------------- 1 | mod assertions; 2 | mod constants_class_test; 3 | mod deprecated_class_test; 4 | mod exceptions; 5 | mod pojo_class_test; 6 | mod utils; 7 | -------------------------------------------------------------------------------- /reader/tests/integration/pojo_class_test.rs: -------------------------------------------------------------------------------- 1 | extern crate rjvm_reader; 2 | 3 | use rjvm_reader::{ 4 | class_access_flags::ClassAccessFlags, 5 | class_file::ClassFile, 6 | class_file_field::ClassFileField, 7 | class_file_version::ClassFileVersion, 8 | field_flags::FieldFlags, 9 | field_type::{BaseType, FieldType}, 10 | line_number::LineNumber, 11 | line_number_table::{LineNumberTable, LineNumberTableEntry}, 12 | method_flags::MethodFlags, 13 | program_counter::ProgramCounter, 14 | }; 15 | use utils::read_class_from_bytes; 16 | 17 | use crate::{assertions::check_method, utils}; 18 | 19 | #[test_log::test] 20 | fn can_read_pojo_class_file() { 21 | let class = read_class_from_bytes(include_bytes!("../resources/rjvm/Complex.class")); 22 | assert_eq!(ClassFileVersion::Jdk6, class.version); 23 | assert_eq!( 24 | ClassAccessFlags::PUBLIC | ClassAccessFlags::SUPER, 25 | class.flags 26 | ); 27 | assert_eq!("rjvm/Complex", class.name); 28 | assert_eq!( 29 | "java/lang/Object", 30 | class.superclass.as_ref().expect("a valid superclass") 31 | ); 32 | assert_eq!( 33 | vec!("java/lang/Cloneable", "java/io/Serializable"), 34 | class.interfaces 35 | ); 36 | assert_eq!(Some("Complex.java".to_string()), class.source_file); 37 | 38 | check_fields(&class); 39 | check_methods(&class); 40 | } 41 | 42 | fn check_fields(class: &ClassFile) { 43 | assert_eq!( 44 | vec!( 45 | ClassFileField { 46 | flags: FieldFlags::PRIVATE | FieldFlags::FINAL, 47 | name: "real".to_string(), 48 | type_descriptor: FieldType::Base(BaseType::Double), 49 | constant_value: None, 50 | deprecated: false, 51 | }, 52 | ClassFileField { 53 | flags: FieldFlags::PRIVATE | FieldFlags::FINAL, 54 | name: "imag".to_string(), 55 | type_descriptor: FieldType::Base(BaseType::Double), 56 | constant_value: None, 57 | deprecated: false, 58 | } 59 | ), 60 | class.fields 61 | ); 62 | } 63 | 64 | fn check_methods(class: &ClassFile) { 65 | assert_eq!(5, class.methods.len()); 66 | 67 | check_method(&class.methods[0], MethodFlags::PUBLIC, "", "(D)V"); 68 | assert_eq!( 69 | Some(LineNumberTable::new(vec![ 70 | LineNumberTableEntry::new(ProgramCounter(0), LineNumber(9)), 71 | LineNumberTableEntry::new(ProgramCounter(4), LineNumber(10)), 72 | LineNumberTableEntry::new(ProgramCounter(9), LineNumber(11)), 73 | LineNumberTableEntry::new(ProgramCounter(14), LineNumber(12)), 74 | ])), 75 | class.methods[0].code.as_ref().unwrap().line_number_table 76 | ); 77 | 78 | check_method(&class.methods[1], MethodFlags::PUBLIC, "", "(DD)V"); 79 | check_method(&class.methods[2], MethodFlags::PUBLIC, "getReal", "()D"); 80 | check_method(&class.methods[3], MethodFlags::PUBLIC, "getImag", "()D"); 81 | check_method(&class.methods[4], MethodFlags::PUBLIC, "abs", "()D"); 82 | assert_eq!( 83 | Some(LineNumberTable::new(vec![LineNumberTableEntry::new( 84 | ProgramCounter(0), 85 | LineNumber(28) 86 | )])), 87 | class.methods[4].code.as_ref().unwrap().line_number_table 88 | ); 89 | } 90 | -------------------------------------------------------------------------------- /reader/tests/integration/utils.rs: -------------------------------------------------------------------------------- 1 | use log::info; 2 | 3 | use rjvm_reader::{class_file::ClassFile, class_reader}; 4 | 5 | pub fn read_class_from_bytes(bytes: &[u8]) -> ClassFile { 6 | let class = class_reader::read_buffer(bytes).unwrap(); 7 | info!("read class file: {}", class); 8 | class 9 | } 10 | -------------------------------------------------------------------------------- /reader/tests/resources/compile.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | javac -source 6 -target 6 rjvm/*.java 3 | -------------------------------------------------------------------------------- /reader/tests/resources/rjvm/Complex.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/reader/tests/resources/rjvm/Complex.class -------------------------------------------------------------------------------- /reader/tests/resources/rjvm/Complex.java: -------------------------------------------------------------------------------- 1 | package rjvm; 2 | 3 | import java.io.Serializable; 4 | 5 | public class Complex implements Cloneable, Serializable { 6 | private final double real; 7 | private final double imag; 8 | 9 | public Complex(double real) { 10 | this.real = real; 11 | this.imag = 0; 12 | } 13 | 14 | public Complex(double real, double imag) { 15 | this.real = real; 16 | this.imag = imag; 17 | } 18 | 19 | public double getReal() { 20 | return this.real; 21 | } 22 | 23 | public double getImag() { 24 | return this.imag; 25 | } 26 | 27 | public double abs() { 28 | return Math.sqrt(this.real * this.real + this.imag * this.imag); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /reader/tests/resources/rjvm/Constants.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/reader/tests/resources/rjvm/Constants.class -------------------------------------------------------------------------------- /reader/tests/resources/rjvm/Constants.java: -------------------------------------------------------------------------------- 1 | package rjvm; 2 | 3 | class Constants { 4 | public static final int AN_INT = 2023; 5 | protected static final float A_FLOAT = 20.23f; 6 | private static final long A_LONG = 2023L; 7 | public static final double A_DOUBLE = 20.23; 8 | public static final String A_STRING = "2023"; 9 | } -------------------------------------------------------------------------------- /reader/tests/resources/rjvm/DeprecatedClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/reader/tests/resources/rjvm/DeprecatedClass.class -------------------------------------------------------------------------------- /reader/tests/resources/rjvm/DeprecatedClass.java: -------------------------------------------------------------------------------- 1 | package rjvm; 2 | 3 | @Deprecated 4 | class DeprecatedClass { 5 | @Deprecated 6 | int deprecatedField; 7 | 8 | @Deprecated 9 | void deprecatedMethod() { 10 | } 11 | } -------------------------------------------------------------------------------- /reader/tests/resources/rjvm/ExceptionsHandlers.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/reader/tests/resources/rjvm/ExceptionsHandlers.class -------------------------------------------------------------------------------- /reader/tests/resources/rjvm/ExceptionsHandlers.java: -------------------------------------------------------------------------------- 1 | package rjvm; 2 | 3 | class ExceptionsHandlers { 4 | void foo() { 5 | } 6 | 7 | void bar() throws IllegalArgumentException, IllegalStateException { 8 | } 9 | 10 | void test() throws Exception { 11 | try { 12 | bar(); 13 | } finally { 14 | foo(); 15 | } 16 | 17 | try { 18 | bar(); 19 | } catch (IllegalStateException e) { 20 | bar(); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | imports_granularity = "Crate" 2 | -------------------------------------------------------------------------------- /vm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rjvm_vm" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | rjvm_reader = { path = "../reader" } 8 | thiserror = "1" 9 | log = "0.4.17" 10 | test-log = "0.2.11" 11 | env_logger = "*" 12 | result = "1.0.0" 13 | typed-arena = "2.0.2" 14 | zip = { version = "0.6.4", features = ["deflate"] } 15 | indexmap = "1.9.2" 16 | bitfield-struct = "0.4.4" 17 | const_format = "0.2.31" 18 | -------------------------------------------------------------------------------- /vm/rt.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/rt.jar -------------------------------------------------------------------------------- /vm/src/alloc_entry.rs: -------------------------------------------------------------------------------- 1 | /// An allocation on our memory chunk 2 | pub struct AllocEntry { 3 | pub(crate) ptr: *mut u8, 4 | pub(crate) alloc_size: usize, 5 | } 6 | -------------------------------------------------------------------------------- /vm/src/array.rs: -------------------------------------------------------------------------------- 1 | use crate::{array_entry_type::ArrayEntryType, value::Value, vm_error::VmError}; 2 | 3 | /// A java array, allocated on our memory chunk 4 | pub trait Array<'a> { 5 | fn elements_type(&self) -> ArrayEntryType; 6 | 7 | fn len(&self) -> u32; 8 | 9 | fn is_empty(&self) -> bool { 10 | self.len() == 0 11 | } 12 | 13 | /// Errors will be returned if the type of the given value does not match the array type, or if the index is invalid 14 | fn set_element(&self, index: usize, value: Value<'a>) -> Result<(), VmError>; 15 | 16 | /// Errors will be returned if the index is invalid 17 | fn get_element(&self, index: usize) -> Result, VmError>; 18 | } 19 | -------------------------------------------------------------------------------- /vm/src/array_entry_type.rs: -------------------------------------------------------------------------------- 1 | use rjvm_reader::field_type::{BaseType, FieldType}; 2 | 3 | use crate::{class::ClassId, class_resolver_by_id::ClassByIdResolver}; 4 | 5 | #[derive(PartialEq, Clone, Debug)] 6 | #[repr(u8)] 7 | // TODO: this should eventually be removed. 8 | /// This models the entries of an array, and it is stored in the same memory as the entries. 9 | /// Ideally, we'd want to reuse [FieldType], but unfortunately we cannot since it contains a 10 | /// String, and its points to heap-allocated data. We could use modify that to use a raw 11 | /// &str and create it from our memory chunk, but it would be complicated. 12 | pub enum ArrayEntryType { 13 | Base(BaseType), 14 | Object(ClassId), 15 | // Note: here we would have to keep the sub-element type. Not doing this means that we do not 16 | // correctly support arrays of arrays! 17 | Array, 18 | } 19 | 20 | impl ArrayEntryType { 21 | pub fn into_field_type<'a>( 22 | self, 23 | class_resolver: &impl ClassByIdResolver<'a>, 24 | ) -> Option { 25 | match self { 26 | ArrayEntryType::Base(base_type) => Some(FieldType::Base(base_type)), 27 | ArrayEntryType::Object(class_id) => class_resolver 28 | .find_class_by_id(class_id) 29 | .map(|class| FieldType::Object(class.name.clone())), 30 | ArrayEntryType::Array => { 31 | todo!("Arrays of arrays are not supported at the moment") 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /vm/src/call_stack.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, fmt::Formatter}; 2 | 3 | use typed_arena::Arena; 4 | 5 | use rjvm_reader::{ 6 | class_file_method::ClassFileMethodCode, method_flags::MethodFlags, type_conversion::ToUsizeSafe, 7 | }; 8 | 9 | use crate::{ 10 | abstract_object::AbstractObject, call_frame::CallFrame, class_and_method::ClassAndMethod, 11 | stack_trace_element::StackTraceElement, value::Value, vm_error::VmError, 12 | }; 13 | 14 | /// A call stack, which will include multiple frames, one for each method call. 15 | // The allocator will allocate and ensure that our call frames are alive while the call stack is. 16 | // Thus, we can do some unsafe magic to avoid Rc>, which would mess up our code when 17 | // we try to get a stack trace _while_ executing a method, which we need for exceptions. 18 | // This also means that we _never_ deallocate the call frames, even after we have finished 19 | // executing them! 20 | #[derive(Default)] 21 | pub struct CallStack<'a> { 22 | frames: Vec>, 23 | allocator: Arena>, 24 | } 25 | 26 | // SAFETY: The pointer will be valid until the generating call stack is, 27 | // since the pointee it is valid until the arena is. 28 | // We try to instruct the compiler with the <'a> 29 | #[derive(Debug, Clone)] 30 | pub struct CallFrameReference<'a>(*mut CallFrame<'a>); 31 | 32 | impl<'a> AsRef> for CallFrameReference<'a> { 33 | fn as_ref(&self) -> &CallFrame<'a> { 34 | unsafe { self.0.as_ref() }.unwrap() 35 | } 36 | } 37 | 38 | impl<'a> AsMut> for CallFrameReference<'a> { 39 | fn as_mut(&mut self) -> &mut CallFrame<'a> { 40 | unsafe { self.0.as_mut() }.unwrap() 41 | } 42 | } 43 | 44 | impl<'a> CallStack<'a> { 45 | pub fn new() -> Self { 46 | Default::default() 47 | } 48 | 49 | /// Adds a new frame to the call stack. 50 | /// Only supports bytecode methods (i.e. non native). 51 | pub fn add_frame( 52 | &mut self, 53 | class_and_method: ClassAndMethod<'a>, 54 | receiver: Option>, 55 | args: Vec>, 56 | ) -> Result, VmError> { 57 | Self::check_receiver(&class_and_method, receiver.clone())?; 58 | let code = Self::get_code(&class_and_method)?; 59 | let locals = Self::prepare_locals(code, receiver, args); 60 | let new_frame = self 61 | .allocator 62 | .alloc(CallFrame::new(class_and_method, locals)); 63 | 64 | let reference = CallFrameReference(new_frame); 65 | self.frames.push(reference.clone()); 66 | Ok(reference) 67 | } 68 | 69 | fn check_receiver( 70 | class_and_method: &ClassAndMethod, 71 | receiver: Option, 72 | ) -> Result<(), VmError> { 73 | if class_and_method.method.flags.contains(MethodFlags::STATIC) { 74 | if receiver.is_some() { 75 | return Err(VmError::ValidationException); 76 | } 77 | } else if receiver.is_none() { 78 | return Err(VmError::NullPointerException); 79 | } 80 | Ok(()) 81 | } 82 | 83 | fn get_code<'b>( 84 | class_and_method: &'b ClassAndMethod, 85 | ) -> Result<&'b ClassFileMethodCode, VmError> { 86 | if class_and_method.is_native() { 87 | return Err(VmError::NotImplemented); 88 | }; 89 | 90 | let code = &class_and_method.method.code.as_ref().unwrap(); 91 | Ok(code) 92 | } 93 | 94 | /// Returns a Vec filled with one `Uninitialized` per variable 95 | fn prepare_locals( 96 | code: &ClassFileMethodCode, 97 | receiver: Option>, 98 | args: Vec>, 99 | ) -> Vec> { 100 | let mut locals: Vec> = receiver 101 | .map(Value::Object) 102 | .into_iter() 103 | .chain(args.into_iter()) 104 | .collect(); 105 | while locals.len() < code.max_locals.into_usize_safe() { 106 | locals.push(Value::Uninitialized); 107 | } 108 | locals 109 | } 110 | 111 | pub fn pop_frame(&mut self) -> Result<(), VmError> { 112 | self.frames 113 | .pop() 114 | .map(|_| ()) 115 | .ok_or(VmError::ValidationException) 116 | } 117 | 118 | pub fn get_stack_trace_elements(&self) -> Vec> { 119 | self.frames 120 | .iter() 121 | .rev() 122 | .map(|frame| frame.as_ref().to_stack_trace_element()) 123 | .collect() 124 | } 125 | 126 | pub fn gc_roots(&mut self) -> impl Iterator> { 127 | let mut roots = vec![]; 128 | roots.extend( 129 | self.frames 130 | .iter_mut() 131 | .flat_map(|frame| frame.as_mut().gc_roots()), 132 | ); 133 | roots.into_iter() 134 | } 135 | } 136 | 137 | impl<'a> fmt::Debug for CallStack<'a> { 138 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 139 | write!(f, "CallStack{{frames={:?}}}", self.frames) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /vm/src/class.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, fmt::Formatter}; 2 | 3 | use rjvm_reader::{ 4 | class_access_flags::ClassAccessFlags, class_file_field::ClassFileField, 5 | class_file_method::ClassFileMethod, constant_pool::ConstantPool, 6 | }; 7 | 8 | /// In various data structures, we store the class id of the object, i..e. a progressive 9 | /// number assigned when we load the class. Note that, while we do not support it yet, 10 | /// multiple class loaders could load the same class more than once, but they would be 11 | /// required to assign different id to them. 12 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 13 | #[repr(transparent)] 14 | pub struct ClassId(u32); 15 | 16 | impl fmt::Display for ClassId { 17 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 18 | write!(f, "{}", self.0) 19 | } 20 | } 21 | 22 | impl ClassId { 23 | pub fn new(id: u32) -> Self { 24 | Self(id) 25 | } 26 | 27 | pub fn as_u32(&self) -> u32 { 28 | self.0 29 | } 30 | } 31 | 32 | /// A loaded java class 33 | #[derive(Debug)] 34 | pub struct Class<'a> { 35 | pub id: ClassId, 36 | pub name: String, 37 | /// Source file is stored as an attribute in the .class file, but might be missing 38 | /// for synthetic classes or if the compiler didn't write it. 39 | pub source_file: Option, 40 | pub constants: ConstantPool, 41 | pub flags: ClassAccessFlags, 42 | pub superclass: Option>, 43 | pub interfaces: Vec>, 44 | pub fields: Vec, 45 | pub methods: Vec, 46 | // Base classes field have the same index they have in the base class, and our own 47 | // field come after. This is the index of the first "owned" field. 48 | // Note that this will include the static fields, as required by the bytecode specs. 49 | pub first_field_index: usize, 50 | // The total number of fields in this class, including those in the base class. 51 | pub num_total_fields: usize, 52 | } 53 | 54 | pub type ClassRef<'a> = &'a Class<'a>; 55 | 56 | impl<'a> Class<'a> { 57 | /// Returns whether self is a subclass of the given class, or implements 58 | /// the given interface 59 | pub fn is_subclass_of(&self, base: ClassRef) -> bool { 60 | self.name == base.name 61 | || self 62 | .superclass 63 | .map_or(false, |superclass| superclass.is_subclass_of(base)) 64 | || self.interfaces.iter().any(|intf| intf.is_subclass_of(base)) 65 | } 66 | 67 | pub fn find_method( 68 | &self, 69 | method_name: &str, 70 | type_descriptor: &str, 71 | ) -> Option<&ClassFileMethod> { 72 | // Maybe replace linear search with something faster... 73 | self.methods 74 | .iter() 75 | .find(|method| method.name == method_name && method.type_descriptor == type_descriptor) 76 | } 77 | 78 | pub fn find_field(&self, field_name: &str) -> Option<(usize, &ClassFileField)> { 79 | // Maybe replace linear search with something faster... 80 | self.fields 81 | .iter() 82 | .enumerate() 83 | .find(|entry| entry.1.name == field_name) 84 | .map(|(index, field)| (index + self.first_field_index, field)) 85 | .or_else(|| { 86 | if let Some(superclass) = &self.superclass { 87 | superclass.find_field(field_name) 88 | } else { 89 | None 90 | } 91 | }) 92 | } 93 | 94 | pub fn field_at_index(&self, index: usize) -> Option<&ClassFileField> { 95 | if index < self.first_field_index { 96 | self.superclass 97 | .and_then(|superclass| superclass.field_at_index(index)) 98 | } else { 99 | self.fields.get(index - self.first_field_index) 100 | } 101 | } 102 | 103 | pub fn all_fields(&self) -> impl Iterator { 104 | let mut all_fields = Vec::from_iter( 105 | self.superclass 106 | .iter() 107 | .flat_map(|superclass| superclass.all_fields()), 108 | ); 109 | all_fields.extend(self.fields.iter()); 110 | all_fields.into_iter() 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /vm/src/class_and_method.rs: -------------------------------------------------------------------------------- 1 | use rjvm_reader::{class_file_method::ClassFileMethod, field_type::FieldType}; 2 | 3 | use crate::class::ClassRef; 4 | 5 | /// A pair of a class and a method, used to avoid passing around two arguments 6 | #[derive(Debug, Clone)] 7 | pub struct ClassAndMethod<'a> { 8 | pub class: ClassRef<'a>, 9 | pub method: &'a ClassFileMethod, 10 | } 11 | 12 | impl<'a> ClassAndMethod<'a> { 13 | pub fn num_arguments(&self) -> usize { 14 | self.method.parsed_type_descriptor.num_arguments() 15 | } 16 | 17 | pub fn return_type(&self) -> Option { 18 | self.method.parsed_type_descriptor.return_type.clone() 19 | } 20 | 21 | pub fn is_static(&self) -> bool { 22 | self.method.is_static() 23 | } 24 | 25 | pub fn is_native(&self) -> bool { 26 | self.method.is_native() 27 | } 28 | 29 | pub fn is_void(&self) -> bool { 30 | self.method.is_void() 31 | } 32 | 33 | pub fn returns(&self, expected_type: FieldType) -> bool { 34 | self.method.returns(expected_type) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /vm/src/class_loader.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::class::ClassRef; 4 | 5 | // The mapping object of a java ClassLoader, with a ton of limitations. 6 | // Currently just contains a map name -> class. 7 | // TODO: class loaders should be a hierarchy 8 | 9 | #[derive(Debug, Default)] 10 | pub struct ClassLoader<'a> { 11 | classes_by_name: HashMap>, 12 | } 13 | 14 | // TODO: we should use this! 15 | #[allow(dead_code)] 16 | impl<'a> ClassLoader<'a> { 17 | pub fn register_class(&mut self, class: ClassRef<'a>) { 18 | self.classes_by_name.insert(class.name.clone(), class); 19 | } 20 | 21 | pub fn find_class_by_name(&self, name: &str) -> Option> { 22 | self.classes_by_name.get(name).cloned() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /vm/src/class_manager.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, fmt, fmt::Formatter}; 2 | 3 | use indexmap::IndexMap; 4 | use log::debug; 5 | use typed_arena::Arena; 6 | 7 | use rjvm_reader::{class_file::ClassFile, class_reader}; 8 | 9 | use crate::{ 10 | class::{Class, ClassId, ClassRef}, 11 | class_loader::ClassLoader, 12 | class_path::{ClassPath, ClassPathParseError}, 13 | class_resolver_by_id::ClassByIdResolver, 14 | vm_error::VmError, 15 | }; 16 | 17 | /// An object that will allocate and manage Class objects 18 | pub(crate) struct ClassManager<'a> { 19 | class_path: ClassPath, 20 | classes_by_id: HashMap>, 21 | classes_by_name: HashMap>, 22 | /// Used to allocate class instances that will be alive as long as the arena 23 | /// (and thus the `ClassManager` are alive). 24 | arena: Arena>, 25 | 26 | /// Used to generate ClassId 27 | next_id: u32, 28 | 29 | /// In a real implementation, we would have a current class loader for each thread, 30 | /// in a hierarchy. Currently, we only have exactly ONE global class loader. 31 | current_class_loader: ClassLoader<'a>, 32 | } 33 | 34 | impl<'a> Default for ClassManager<'a> { 35 | fn default() -> Self { 36 | Self { 37 | class_path: Default::default(), 38 | classes_by_id: Default::default(), 39 | classes_by_name: Default::default(), 40 | arena: Arena::with_capacity(100), 41 | next_id: 1, 42 | current_class_loader: Default::default(), 43 | } 44 | } 45 | } 46 | 47 | impl<'a> fmt::Debug for ClassManager<'a> { 48 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 49 | write!(f, "class_manager={{loaded classes={}}}", self.arena.len()) 50 | } 51 | } 52 | 53 | /// When a class instance is requested, returns whether the class was already loaded, 54 | /// or whether the requeste loaded a new class (which will need to be initialized). 55 | #[derive(Debug, Clone)] 56 | pub(crate) enum ResolvedClass<'a> { 57 | AlreadyLoaded(ClassRef<'a>), 58 | NewClass(ClassesToInitialize<'a>), 59 | } 60 | 61 | impl<'a> ResolvedClass<'a> { 62 | pub fn get_class(&self) -> ClassRef<'a> { 63 | match self { 64 | ResolvedClass::AlreadyLoaded(class) => class, 65 | ResolvedClass::NewClass(classes_to_initialize) => classes_to_initialize.resolved_class, 66 | } 67 | } 68 | } 69 | 70 | /// In case a new class was loaded, maps the whole list of the classes that require 71 | /// initialization, in order so that a base class is initialized _before_ the derived classes. 72 | /// Includes the newly resolved class in the list [to_initialize]. 73 | #[derive(Debug, Clone)] 74 | pub(crate) struct ClassesToInitialize<'a> { 75 | resolved_class: ClassRef<'a>, 76 | pub(crate) to_initialize: Vec>, 77 | } 78 | 79 | impl<'a> ClassByIdResolver<'a> for ClassManager<'a> { 80 | fn find_class_by_id(&self, id: ClassId) -> Option> { 81 | self.classes_by_id.get(&id).cloned() 82 | } 83 | } 84 | 85 | impl<'a> ClassManager<'a> { 86 | pub fn append_class_path(&mut self, class_path: &str) -> Result<(), ClassPathParseError> { 87 | self.class_path.push(class_path) 88 | } 89 | 90 | pub fn find_class_by_name(&self, class_name: &str) -> Option> { 91 | self.classes_by_name.get(class_name).cloned() 92 | } 93 | 94 | pub fn get_or_resolve_class(&mut self, class_name: &str) -> Result, VmError> { 95 | if let Some(already_loaded_class) = self.find_class_by_name(class_name) { 96 | Ok(ResolvedClass::AlreadyLoaded(already_loaded_class)) 97 | } else { 98 | self.resolve_and_load_class(class_name) 99 | .map(ResolvedClass::NewClass) 100 | } 101 | } 102 | 103 | fn resolve_and_load_class( 104 | &mut self, 105 | class_name: &str, 106 | ) -> Result, VmError> { 107 | let class_file_bytes = self 108 | .class_path 109 | .resolve(class_name) 110 | .map_err(|err| VmError::ClassLoadingError(err.to_string()))? 111 | .ok_or(VmError::ClassNotFoundException(class_name.to_string()))?; 112 | let class_file = class_reader::read_buffer(&class_file_bytes) 113 | .map_err(|err| VmError::ClassLoadingError(err.to_string()))?; 114 | self.load_class(class_file) 115 | } 116 | 117 | fn load_class(&mut self, class_file: ClassFile) -> Result, VmError> { 118 | let referenced_classes = self.resolve_super_and_interfaces(&class_file)?; 119 | let loaded_class = self.allocate(class_file, referenced_classes)?; 120 | self.register_loaded_class(loaded_class.resolved_class); 121 | Ok(loaded_class) 122 | } 123 | 124 | fn resolve_super_and_interfaces( 125 | &mut self, 126 | class_file: &ClassFile, 127 | ) -> Result>, VmError> { 128 | let mut resolved_classes: IndexMap> = Default::default(); 129 | if let Some(superclass_name) = &class_file.superclass { 130 | self.resolve_and_collect_class(superclass_name, &mut resolved_classes)?; 131 | } 132 | for interface_name in class_file.interfaces.iter() { 133 | self.resolve_and_collect_class(interface_name, &mut resolved_classes)?; 134 | } 135 | Ok(resolved_classes) 136 | } 137 | 138 | fn resolve_and_collect_class( 139 | &mut self, 140 | class_name: &str, 141 | resolved_classes: &mut IndexMap>, 142 | ) -> Result<(), VmError> { 143 | let class = self.get_or_resolve_class(class_name)?; 144 | resolved_classes.insert(class_name.to_string(), class); 145 | Ok(()) 146 | } 147 | 148 | fn allocate( 149 | &mut self, 150 | class_file: ClassFile, 151 | referenced_classes: IndexMap>, 152 | ) -> Result, VmError> { 153 | let next_id = self.next_id; 154 | self.next_id += 1; 155 | 156 | let id = ClassId::new(next_id); 157 | debug!("loading class {} from file {}", id, class_file.name); 158 | let class = Self::new_class(class_file, id, &referenced_classes)?; 159 | let class_ref = self.arena.alloc(class); 160 | 161 | // SAFETY: our reference class_ref is alive only for 'b. 162 | // However we actually know that the arena will keep the value alive for 'a, 163 | // and I cannot find a way to convince the compiler of this fact. Thus 164 | // I'm using this pointer "trick" to make the compiler happy. 165 | // I expect this can be done with safe Rust, I just do not know how at the moment... 166 | let class_ref = unsafe { 167 | let class_ptr: *const Class<'a> = class_ref; 168 | &*class_ptr 169 | }; 170 | 171 | let mut classes_to_init: Vec> = Vec::new(); 172 | for resolved_class in referenced_classes.values() { 173 | if let ResolvedClass::NewClass(new_class) = resolved_class { 174 | for to_initialize in new_class.to_initialize.iter() { 175 | classes_to_init.push(to_initialize) 176 | } 177 | } 178 | } 179 | classes_to_init.push(class_ref); 180 | 181 | debug!( 182 | "initializing class {}, classes to init {:?}", 183 | class_ref.name, 184 | classes_to_init 185 | .iter() 186 | .map(|c| &c.name) 187 | .collect::>() 188 | ); 189 | 190 | Ok(ClassesToInitialize { 191 | resolved_class: class_ref, 192 | to_initialize: classes_to_init, 193 | }) 194 | } 195 | 196 | fn new_class( 197 | class_file: ClassFile, 198 | id: ClassId, 199 | resolved_classes: &IndexMap>, 200 | ) -> Result, VmError> { 201 | let superclass = class_file 202 | .superclass 203 | .as_ref() 204 | .map(|superclass_name| resolved_classes.get(superclass_name).unwrap().get_class()); 205 | let interfaces: Vec> = class_file 206 | .interfaces 207 | .iter() 208 | .map(|interface_name| resolved_classes.get(interface_name).unwrap().get_class()) 209 | .collect(); 210 | 211 | let num_superclass_fields = match superclass { 212 | Some(superclass) => superclass.num_total_fields, 213 | None => 0, 214 | }; 215 | let num_this_class_fields = class_file.fields.len(); 216 | 217 | Ok(Class { 218 | id, 219 | name: class_file.name, 220 | source_file: class_file.source_file, 221 | constants: class_file.constants, 222 | flags: class_file.flags, 223 | superclass, 224 | interfaces, 225 | fields: class_file.fields, 226 | methods: class_file.methods, 227 | num_total_fields: num_superclass_fields + num_this_class_fields, 228 | first_field_index: num_superclass_fields, 229 | }) 230 | } 231 | 232 | fn register_loaded_class(&mut self, class: ClassRef<'a>) { 233 | self.classes_by_name.insert(class.name.clone(), class); 234 | self.classes_by_id.insert(class.id, class); 235 | self.current_class_loader.register_class(class); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /vm/src/class_path.rs: -------------------------------------------------------------------------------- 1 | use log::debug; 2 | use thiserror::Error; 3 | 4 | use crate::{ 5 | class_path_entry::{ClassLoadingError, ClassPathEntry}, 6 | file_system_class_path_entry::FileSystemClassPathEntry, 7 | jar_file_class_path_entry::JarFileClassPathEntry, 8 | }; 9 | 10 | /// Models a class path, i.e. a list of [ClassPathEntry] 11 | #[allow(dead_code)] 12 | #[derive(Default, Debug)] 13 | pub struct ClassPath { 14 | entries: Vec>, 15 | } 16 | 17 | /// Error that models the fact that a class path entry was not valid 18 | #[derive(Error, Debug, PartialEq)] 19 | pub enum ClassPathParseError { 20 | #[error("invalid classpath entry: {0}")] 21 | InvalidEntry(String), 22 | } 23 | 24 | impl ClassPath { 25 | /// Parses and adds class path entries. 26 | /// These should be separated by a colon (:), just like in a real JVM. 27 | pub fn push(&mut self, string: &str) -> Result<(), ClassPathParseError> { 28 | let mut entries_to_add: Vec> = Vec::new(); 29 | for entry in string.split(':') { 30 | debug!("trying to parse class path entry {}", entry); 31 | let parsed_entry = Self::try_parse_entry(entry)?; 32 | entries_to_add.push(parsed_entry); 33 | } 34 | self.entries.append(&mut entries_to_add); 35 | Ok(()) 36 | } 37 | 38 | fn try_parse_entry(path: &str) -> Result, ClassPathParseError> { 39 | Self::try_parse_entry_as_jar(path).or_else(|_| Self::try_parse_entry_as_directory(path)) 40 | } 41 | 42 | fn try_parse_entry_as_jar(path: &str) -> Result, ClassPathParseError> { 43 | let entry = JarFileClassPathEntry::new(path) 44 | .map_err(|_| ClassPathParseError::InvalidEntry(path.to_string()))?; 45 | Ok(Box::new(entry)) 46 | } 47 | 48 | fn try_parse_entry_as_directory( 49 | path: &str, 50 | ) -> Result, ClassPathParseError> { 51 | let entry = FileSystemClassPathEntry::new(path) 52 | .map_err(|_| ClassPathParseError::InvalidEntry(path.to_string()))?; 53 | Ok(Box::new(entry)) 54 | } 55 | 56 | /// Attempts to resolve a class from the various entries. 57 | /// Stops at the first entry that has a match or an error. 58 | pub fn resolve(&self, class_name: &str) -> Result>, ClassLoadingError> { 59 | for entry in self.entries.iter() { 60 | debug!("looking up class {} in {:?}", class_name, entry); 61 | let entry_result = entry.resolve(class_name)?; 62 | if let Some(class_bytes) = entry_result { 63 | return Ok(Some(class_bytes)); 64 | } 65 | } 66 | Ok(None) 67 | } 68 | } 69 | 70 | #[cfg(test)] 71 | mod tests { 72 | use super::ClassPath; 73 | 74 | #[test] 75 | fn can_parse_valid_classpath_entries() { 76 | let dir = env!("CARGO_MANIFEST_DIR"); 77 | let mut class_path: ClassPath = Default::default(); 78 | class_path 79 | .push(&format!( 80 | "{dir}/tests/resources/sample.jar:{dir}/tests/resources", 81 | )) 82 | .expect("should be able to parse classpath"); 83 | assert_can_find_class(&class_path, "rjvm/NumericTypes"); // From jar 84 | assert_can_find_class(&class_path, "rjvm/SimpleMain"); // From directory 85 | assert_cannot_find_class(&class_path, "foo"); 86 | } 87 | 88 | fn assert_can_find_class(class_path: &ClassPath, class_name: &str) { 89 | let buf = class_path 90 | .resolve(class_name) 91 | .expect("should not have had any errors") 92 | .expect("should have been able to find file"); 93 | let magic_number = 94 | u32::from_be_bytes(buf[0..4].try_into().expect("file should have 4 bytes")); 95 | assert_eq!(0xCAFEBABE, magic_number); 96 | } 97 | 98 | fn assert_cannot_find_class(class_path: &ClassPath, class_name: &str) { 99 | assert!(class_path 100 | .resolve(class_name) 101 | .expect("should not have had any errors") 102 | .is_none()); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /vm/src/class_path_entry.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, fmt, fmt::Formatter}; 2 | 3 | /// Models an entry in the class path, i.e. a single Jar or directory 4 | pub trait ClassPathEntry: fmt::Debug { 5 | // TODO: should `class_name` be a newtype? 6 | fn resolve(&self, class_name: &str) -> Result>, ClassLoadingError>; 7 | } 8 | 9 | /// Error returned when loading a class does not work 10 | #[derive(Debug)] 11 | pub struct ClassLoadingError { 12 | message: String, 13 | source: Box, 14 | } 15 | 16 | impl ClassLoadingError { 17 | pub fn new(error: impl Error + 'static) -> Self { 18 | Self { 19 | message: error.to_string(), 20 | source: Box::new(error), 21 | } 22 | } 23 | } 24 | 25 | impl fmt::Display for ClassLoadingError { 26 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 27 | write!(f, "{}", self.message) 28 | } 29 | } 30 | 31 | impl Error for ClassLoadingError { 32 | fn source(&self) -> Option<&(dyn Error + 'static)> { 33 | Some(self.source.as_ref()) 34 | } 35 | } 36 | 37 | // Test utilities used by multiple files 38 | #[cfg(test)] 39 | pub mod tests { 40 | use crate::class_path_entry::ClassPathEntry; 41 | 42 | pub fn assert_can_find_class(entry: &impl ClassPathEntry, class_name: &str) { 43 | let buf = entry 44 | .resolve(class_name) 45 | .expect("should have been able to read file") 46 | .expect("should have been able to find file"); 47 | let magic_number = 48 | u32::from_be_bytes(buf[0..4].try_into().expect("file should have 4 bytes")); 49 | assert_eq!(0xCAFEBABE, magic_number); 50 | } 51 | 52 | pub fn assert_cannot_find_class(entry: &impl ClassPathEntry, class_name: &str) { 53 | assert!(entry 54 | .resolve(class_name) 55 | .expect("should not have had any errors") 56 | .is_none()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /vm/src/class_resolver_by_id.rs: -------------------------------------------------------------------------------- 1 | use crate::class::{ClassId, ClassRef}; 2 | 3 | /// Trait that models the fact that a class can be resolved by its given id 4 | pub trait ClassByIdResolver<'a> { 5 | fn find_class_by_id(&self, class_id: ClassId) -> Option>; 6 | } 7 | -------------------------------------------------------------------------------- /vm/src/exceptions.rs: -------------------------------------------------------------------------------- 1 | use crate::{abstract_object::AbstractObject, value_stack::ValueStackError, vm_error::VmError}; 2 | 3 | /// Models the fact that a method execution has failed 4 | #[derive(Debug, PartialEq)] 5 | pub enum MethodCallFailed<'a> { 6 | InternalError(VmError), 7 | ExceptionThrown(JavaException<'a>), 8 | } 9 | 10 | impl<'a> From for MethodCallFailed<'a> { 11 | fn from(value: VmError) -> Self { 12 | Self::InternalError(value) 13 | } 14 | } 15 | 16 | // TODO: need to remove this eventually and manage it with real exceptions 17 | impl<'a> From for MethodCallFailed<'a> { 18 | fn from(_: ValueStackError) -> Self { 19 | Self::InternalError(VmError::ValidationException) 20 | } 21 | } 22 | 23 | /// Newtype that wraps a java exception 24 | #[derive(Debug, PartialEq)] 25 | pub struct JavaException<'a>(pub AbstractObject<'a>); 26 | -------------------------------------------------------------------------------- /vm/src/file_system_class_path_entry.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt, 3 | fmt::Formatter, 4 | path::{Path, PathBuf}, 5 | }; 6 | 7 | use crate::class_path_entry::{ClassLoadingError, ClassPathEntry}; 8 | 9 | /// Implementation of [ClassPathEntry] that searches for `.class` files, 10 | /// using the given directory as the root package 11 | #[derive(Debug)] 12 | pub struct FileSystemClassPathEntry { 13 | base_directory: PathBuf, 14 | } 15 | 16 | impl FileSystemClassPathEntry { 17 | pub fn new>(path: P) -> Result { 18 | let mut base_directory = PathBuf::new(); 19 | base_directory.push(path); 20 | 21 | if !base_directory.exists() || !base_directory.is_dir() { 22 | Err(InvalidDirectoryError { 23 | path: base_directory.to_string_lossy().to_string(), 24 | }) 25 | } else { 26 | Ok(Self { base_directory }) 27 | } 28 | } 29 | } 30 | 31 | impl ClassPathEntry for FileSystemClassPathEntry { 32 | fn resolve(&self, class_name: &str) -> Result>, ClassLoadingError> { 33 | let mut candidate = self.base_directory.clone(); 34 | candidate.push(class_name); 35 | candidate.set_extension("class"); 36 | if candidate.exists() { 37 | std::fs::read(candidate) 38 | .map(Some) 39 | .map_err(ClassLoadingError::new) 40 | } else { 41 | Ok(None) 42 | } 43 | } 44 | } 45 | 46 | /// Error returned when a directory is not valid 47 | #[derive(Debug, PartialEq, Eq)] 48 | pub struct InvalidDirectoryError { 49 | path: String, 50 | } 51 | 52 | impl fmt::Display for InvalidDirectoryError { 53 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 54 | write!(f, "invalid directory: {}", self.path) 55 | } 56 | } 57 | 58 | impl std::error::Error for InvalidDirectoryError {} 59 | 60 | #[cfg(test)] 61 | mod tests { 62 | use std::path::PathBuf; 63 | 64 | use crate::{ 65 | class_path_entry::tests::{assert_can_find_class, assert_cannot_find_class}, 66 | file_system_class_path_entry::{FileSystemClassPathEntry, InvalidDirectoryError}, 67 | }; 68 | 69 | #[test] 70 | fn directory_not_found() { 71 | let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 72 | path.push("foobar"); 73 | assert_eq!( 74 | InvalidDirectoryError { 75 | path: path.to_string_lossy().to_string() 76 | }, 77 | FileSystemClassPathEntry::new(path).expect_err("should not have found directory") 78 | ); 79 | } 80 | 81 | #[test] 82 | fn file_system_class_path_entry_works() { 83 | let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 84 | path.push("tests/resources"); 85 | let entry = FileSystemClassPathEntry::new(path).expect("should find directory"); 86 | 87 | assert_can_find_class(&entry, "rjvm/NumericTypes"); 88 | assert_can_find_class(&entry, "rjvm/ControlFlow"); 89 | assert_cannot_find_class(&entry, "rjvm/Foo"); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /vm/src/jar_file_class_path_entry.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::RefCell, 3 | fmt::{Debug, Formatter}, 4 | fs::File, 5 | io::{BufReader, Read}, 6 | path::Path, 7 | }; 8 | 9 | use thiserror::Error; 10 | use zip::{result::ZipError, ZipArchive}; 11 | 12 | use crate::class_path_entry::{ClassLoadingError, ClassPathEntry}; 13 | 14 | /// Implementation of [ClassPathEntry] that searches for `.class` file inside a `.jar` file 15 | pub struct JarFileClassPathEntry { 16 | file_name: String, 17 | zip: RefCell>>, 18 | } 19 | 20 | impl Debug for JarFileClassPathEntry { 21 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 22 | write!( 23 | f, 24 | "JarFileClassPathEntry {{ file_name: {} }}", 25 | self.file_name 26 | ) 27 | } 28 | } 29 | 30 | impl JarFileClassPathEntry { 31 | pub fn new>(path: P) -> Result { 32 | let path = path.as_ref(); 33 | if !path.exists() { 34 | return Err(JarFileError::NotFound(path.to_string_lossy().to_string())); 35 | } 36 | 37 | let file = File::open(path) 38 | .map_err(|_| JarFileError::ReadingError(path.to_string_lossy().to_string()))?; 39 | let buf_reader = BufReader::new(file); 40 | let zip = ZipArchive::new(buf_reader) 41 | .map_err(|_| JarFileError::InvalidJar(path.to_string_lossy().to_string()))?; 42 | Ok(Self { 43 | file_name: path.to_string_lossy().to_string(), 44 | zip: RefCell::new(zip), 45 | }) 46 | } 47 | } 48 | 49 | impl ClassPathEntry for JarFileClassPathEntry { 50 | fn resolve(&self, class_name: &str) -> Result>, ClassLoadingError> { 51 | let class_file_name = class_name.to_string() + ".class"; 52 | match self.zip.borrow_mut().by_name(&class_file_name) { 53 | Ok(mut zip_file) => { 54 | let mut buffer: Vec = Vec::with_capacity(zip_file.size() as usize); 55 | zip_file 56 | .read_to_end(&mut buffer) 57 | .map_err(ClassLoadingError::new)?; 58 | Ok(Some(buffer)) 59 | } 60 | Err(err) => match err { 61 | ZipError::FileNotFound => Ok(None), 62 | _ => Err(ClassLoadingError::new(err)), 63 | }, 64 | } 65 | } 66 | } 67 | 68 | /// Error returned if searching a class inside a Jar fails 69 | #[derive(Error, Debug, PartialEq)] 70 | pub enum JarFileError { 71 | /// The jar file does not exist! 72 | #[error("file {0} not found")] 73 | NotFound(String), 74 | 75 | /// Generic I/O error reading the file 76 | #[error("error reading file {0}")] 77 | ReadingError(String), 78 | 79 | /// The file is not actually a valid jar 80 | #[error("file {0} is not a valid jar")] 81 | InvalidJar(String), 82 | } 83 | 84 | #[cfg(test)] 85 | mod tests { 86 | use std::path::PathBuf; 87 | 88 | use crate::{ 89 | class_path_entry::tests::{assert_can_find_class, assert_cannot_find_class}, 90 | jar_file_class_path_entry::{JarFileClassPathEntry, JarFileError}, 91 | }; 92 | 93 | #[test] 94 | fn jar_file_not_found() { 95 | let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 96 | path.push("tests/resources/not_found.jar"); 97 | let entry = JarFileClassPathEntry::new(path.clone()); 98 | assert_eq!( 99 | JarFileError::NotFound(path.to_string_lossy().to_string()), 100 | entry.expect_err("should have thrown an error") 101 | ); 102 | } 103 | 104 | #[test] 105 | fn file_is_not_a_jar() { 106 | let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 107 | path.push("tests/resources/compile.sh"); 108 | 109 | let entry = JarFileClassPathEntry::new(path.clone()); 110 | assert_eq!( 111 | JarFileError::InvalidJar(path.to_string_lossy().to_string()), 112 | entry.expect_err("should have thrown an error") 113 | ); 114 | } 115 | 116 | #[test] 117 | fn valid_jar_file_can_search_for_class_file() { 118 | let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 119 | path.push("tests/resources/sample.jar"); 120 | let entry = JarFileClassPathEntry::new(path).expect("should have read the jar file"); 121 | 122 | assert_can_find_class(&entry, "rjvm/NumericTypes"); 123 | assert_can_find_class(&entry, "rjvm/ControlFlow"); 124 | assert_cannot_find_class(&entry, "rjvm/Foo"); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /vm/src/java_objects_creation.rs: -------------------------------------------------------------------------------- 1 | use rjvm_reader::{field_type::BaseType, line_number::LineNumber}; 2 | 3 | use crate::{ 4 | abstract_object::{string_from_char_array, AbstractObject}, 5 | array::Array, 6 | array_entry_type::ArrayEntryType, 7 | call_stack::CallStack, 8 | exceptions::MethodCallFailed, 9 | object::Object, 10 | stack_trace_element::StackTraceElement, 11 | value::Value, 12 | vm::Vm, 13 | vm_error::VmError, 14 | }; 15 | 16 | /// Creates a new instance of a `java.lang.String` with the given content 17 | pub fn new_java_lang_string_object<'a>( 18 | vm: &mut Vm<'a>, 19 | call_stack: &mut CallStack<'a>, 20 | content: &str, 21 | ) -> Result, MethodCallFailed<'a>> { 22 | let char_array: Vec> = content 23 | .encode_utf16() 24 | .map(|c| Value::Int(c as i32)) 25 | .collect(); 26 | 27 | let java_array = vm.new_array(ArrayEntryType::Base(BaseType::Char), char_array.len()); 28 | char_array 29 | .into_iter() 30 | .enumerate() 31 | .for_each(|(index, value)| java_array.set_element(index, value).unwrap()); 32 | 33 | // In our JRE's rt.jar, the fields for String are: 34 | // private final char[] value; 35 | // private int hash; 36 | // private static final long serialVersionUID = -6849794470754667710L; 37 | // private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0]; 38 | // public static final Comparator CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator(); 39 | // private static final int HASHING_SEED; 40 | // private transient int hash32; 41 | let string_object = vm.new_object(call_stack, "java/lang/String")?; 42 | string_object.set_field(0, Value::Object(java_array)); 43 | string_object.set_field(1, Value::Int(0)); 44 | string_object.set_field(6, Value::Int(0)); 45 | Ok(string_object) 46 | } 47 | 48 | /// Given an instance of `java.lang.String`, extracts the content as a Rust `String` 49 | pub fn extract_str_from_java_lang_string<'a>( 50 | vm: &Vm<'a>, 51 | object: &impl Object<'a>, 52 | ) -> Result { 53 | let class = vm.get_class_by_id(object.class_id())?; 54 | if class.name == "java/lang/String" { 55 | // In our JRE's rt.jar, the first fields of String is 56 | // private final char[] value; 57 | if let Value::Object(array) = object.get_field(class, 0) { 58 | return string_from_char_array(array); 59 | } 60 | } 61 | Err(VmError::ValidationException) 62 | } 63 | 64 | pub fn new_java_lang_class_object<'a>( 65 | vm: &mut Vm<'a>, 66 | call_stack: &mut CallStack<'a>, 67 | class_name: &str, 68 | ) -> Result, MethodCallFailed<'a>> { 69 | let class_object = vm.new_object(call_stack, "java/lang/Class")?; 70 | // TODO: build a proper instance of Class object 71 | let string_object = new_java_lang_string_object(vm, call_stack, class_name)?; 72 | class_object.set_field(5, Value::Object(string_object)); 73 | Ok(class_object) 74 | } 75 | 76 | pub fn new_java_lang_stack_trace_element_object<'a>( 77 | vm: &mut Vm<'a>, 78 | call_stack: &mut CallStack<'a>, 79 | stack_trace_element: &StackTraceElement<'a>, 80 | ) -> Result, MethodCallFailed<'a>> { 81 | let class_name = Value::Object(new_java_lang_string_object( 82 | vm, 83 | call_stack, 84 | stack_trace_element.class_name, 85 | )?); 86 | let method_name = Value::Object(new_java_lang_string_object( 87 | vm, 88 | call_stack, 89 | stack_trace_element.method_name, 90 | )?); 91 | let file_name = match stack_trace_element.source_file { 92 | Some(file_name) => Value::Object(new_java_lang_string_object(vm, call_stack, file_name)?), 93 | _ => Value::Null, 94 | }; 95 | let line_number = Value::Int(stack_trace_element.line_number.unwrap_or(LineNumber(0)).0 as i32); 96 | 97 | // The class StackTraceElement has this layout: 98 | // private String declaringClass; 99 | // private String methodName; 100 | // private String fileName; 101 | // private int lineNumber; 102 | let stack_trace_element_java_object = 103 | vm.new_object(call_stack, "java/lang/StackTraceElement")?; 104 | stack_trace_element_java_object.set_field(0, class_name); 105 | stack_trace_element_java_object.set_field(1, method_name); 106 | stack_trace_element_java_object.set_field(2, file_name); 107 | stack_trace_element_java_object.set_field(3, line_number); 108 | 109 | Ok(stack_trace_element_java_object) 110 | } 111 | -------------------------------------------------------------------------------- /vm/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod abstract_object; 2 | pub mod alloc_entry; 3 | pub mod array; 4 | pub mod array_entry_type; 5 | mod call_frame; 6 | pub mod call_stack; 7 | pub mod class; 8 | pub mod class_and_method; 9 | mod class_loader; 10 | mod class_manager; 11 | mod class_path; 12 | mod class_path_entry; 13 | mod class_resolver_by_id; 14 | pub mod exceptions; 15 | mod file_system_class_path_entry; 16 | mod gc; 17 | mod jar_file_class_path_entry; 18 | pub mod java_objects_creation; 19 | mod native_methods_impl; 20 | pub mod native_methods_registry; 21 | pub mod object; 22 | pub mod stack_trace_element; 23 | mod time; 24 | pub mod value; 25 | mod value_stack; 26 | pub mod vm; 27 | pub mod vm_error; 28 | -------------------------------------------------------------------------------- /vm/src/native_methods_impl.rs: -------------------------------------------------------------------------------- 1 | use log::{debug, info}; 2 | 3 | use rjvm_reader::type_conversion::ToUsizeSafe; 4 | 5 | use crate::{ 6 | abstract_object::{AbstractObject, ObjectKind}, 7 | array::Array, 8 | call_frame::MethodCallResult, 9 | call_stack::CallStack, 10 | exceptions::MethodCallFailed, 11 | java_objects_creation::{ 12 | extract_str_from_java_lang_string, new_java_lang_class_object, 13 | new_java_lang_stack_trace_element_object, 14 | }, 15 | native_methods_registry::NativeMethodsRegistry, 16 | object::Object, 17 | time::{get_current_time_millis, get_nano_time}, 18 | value::{ 19 | expect_abstract_object_at, expect_array_at, expect_concrete_object_at, expect_double_at, 20 | expect_float_at, expect_int_at, Value, 21 | }, 22 | vm::Vm, 23 | vm_error::VmError, 24 | }; 25 | 26 | /// Registers the built-in native methods 27 | pub(crate) fn register_natives(registry: &mut NativeMethodsRegistry) { 28 | registry.register_temp_print(|vm, _, _, args| temp_print(vm, args)); 29 | register_noops(registry); 30 | register_time_methods(registry); 31 | register_gc_methods(registry); 32 | register_native_repr_methods(registry); 33 | register_reflection_methods(registry); 34 | register_throwable_methods(registry); 35 | } 36 | 37 | /// These various methods are noop, i.e. they do not do anything 38 | fn register_noops(registry: &mut NativeMethodsRegistry) { 39 | registry.register( 40 | "java/lang/Object", 41 | "registerNatives", 42 | "()V", 43 | |_, _, _, _| Ok(None), 44 | ); 45 | registry.register( 46 | "java/lang/System", 47 | "registerNatives", 48 | "()V", 49 | |_, _, _, _| Ok(None), 50 | ); 51 | registry.register("java/lang/Class", "registerNatives", "()V", |_, _, _, _| { 52 | Ok(None) 53 | }); 54 | registry.register( 55 | "java/lang/ClassLoader", 56 | "registerNatives", 57 | "()V", 58 | |_, _, _, _| Ok(None), 59 | ); 60 | } 61 | 62 | /// Methods to access the system clock 63 | fn register_time_methods(registry: &mut NativeMethodsRegistry) { 64 | registry.register("java/lang/System", "nanoTime", "()J", |_, _, _, _| { 65 | Ok(Some(Value::Long(get_nano_time()))) 66 | }); 67 | registry.register( 68 | "java/lang/System", 69 | "currentTimeMillis", 70 | "()J", 71 | |_, _, _, _| Ok(Some(Value::Long(get_current_time_millis()))), 72 | ); 73 | } 74 | 75 | /// Methods related to the garbage collector 76 | fn register_gc_methods(registry: &mut NativeMethodsRegistry) { 77 | registry.register( 78 | "java/lang/System", 79 | "identityHashCode", 80 | "(Ljava/lang/Object;)I", 81 | |_, _, _, args| identity_hash_code(args), 82 | ); 83 | registry.register("java/lang/System", "gc", "()V", |vm, _, _, _| { 84 | vm.run_garbage_collection()?; 85 | Ok(None) 86 | }); 87 | } 88 | 89 | /// Native methods that deal with the internal representation of data 90 | fn register_native_repr_methods(registry: &mut NativeMethodsRegistry) { 91 | registry.register( 92 | "java/lang/System", 93 | "arraycopy", 94 | "(Ljava/lang/Object;ILjava/lang/Object;II)V", 95 | |_, _, _, args| native_array_copy(args), 96 | ); 97 | registry.register( 98 | "java/lang/Float", 99 | "floatToRawIntBits", 100 | "(F)I", 101 | |_, _, _, args| float_to_raw_int_bits(&args), 102 | ); 103 | registry.register( 104 | "java/lang/Double", 105 | "doubleToRawLongBits", 106 | "(D)J", 107 | |_, _, _, args| double_to_raw_long_bits(&args), 108 | ); 109 | } 110 | 111 | /// Methods related to reflection 112 | fn register_reflection_methods(registry: &mut NativeMethodsRegistry) { 113 | registry.register( 114 | "java/lang/Class", 115 | "getClassLoader0", 116 | "()Ljava/lang/ClassLoader;", 117 | |_, _, receiver, _| get_class_loader(receiver), 118 | ); 119 | registry.register( 120 | "java/lang/Class", 121 | "desiredAssertionStatus0", 122 | "(Ljava/lang/Class;)Z", 123 | |_, _, _, _| Ok(Some(Value::Int(1))), 124 | ); 125 | registry.register( 126 | "java/lang/Class", 127 | "getPrimitiveClass", 128 | "(Ljava/lang/String;)Ljava/lang/Class;", 129 | |vm, stack, _, args| get_primitive_class(vm, stack, &args), 130 | ); 131 | } 132 | 133 | /// Methods of java.lang.Throwable 134 | fn register_throwable_methods(registry: &mut NativeMethodsRegistry) { 135 | registry.register( 136 | "java/lang/Throwable", 137 | "fillInStackTrace", 138 | "(I)Ljava/lang/Throwable;", 139 | |vm, call_stack, receiver, _| fill_in_stack_trace(vm, call_stack, receiver), 140 | ); 141 | registry.register( 142 | "java/lang/Throwable", 143 | "getStackTraceDepth", 144 | "()I", 145 | |vm, _, receiver, _| get_stack_trace_depth(vm, receiver), 146 | ); 147 | registry.register( 148 | "java/lang/Throwable", 149 | "getStackTraceElement", 150 | "(I)Ljava/lang/StackTraceElement;", 151 | get_stack_trace_element, 152 | ); 153 | } 154 | 155 | /// Debug method that does a "println", useful since we do not have real I/O 156 | fn temp_print<'a>(vm: &mut Vm<'a>, args: Vec>) -> MethodCallResult<'a> { 157 | let arg = args.get(0).ok_or(VmError::ValidationException)?; 158 | 159 | let formatted = match arg { 160 | Value::Object(object) if object.kind() == ObjectKind::Object => { 161 | let class = vm 162 | .get_class_by_id(object.class_id()) 163 | .expect("cannot get an object without a valid class id"); 164 | if class.name == "java/lang/String" { 165 | extract_str_from_java_lang_string(vm, object) 166 | .expect("should be able to get a string's content") 167 | } else { 168 | format!("{:?}", object) 169 | } 170 | } 171 | _ => format!("{:?}", arg), 172 | }; 173 | info!("TEMP implementation of native method: printing value {formatted}",); 174 | vm.printed.push(arg.clone()); 175 | Ok(None) 176 | } 177 | 178 | fn identity_hash_code(args: Vec>) -> MethodCallResult<'_> { 179 | let object = expect_abstract_object_at(&args, 0)?; 180 | Ok(Some(Value::Int(object.identity_hash_code()))) 181 | } 182 | 183 | fn native_array_copy(args: Vec) -> MethodCallResult { 184 | // TODO: handle NullPointerException with the correct error 185 | 186 | let src = expect_array_at(&args, 0)?; 187 | let src_pos = expect_int_at(&args, 1)?; 188 | let dest = expect_array_at(&args, 2)?; 189 | let dest_pos = expect_int_at(&args, 3)?; 190 | let length = expect_int_at(&args, 4)?; 191 | array_copy(&src, src_pos, &dest, dest_pos, length.into_usize_safe())?; 192 | Ok(None) 193 | } 194 | 195 | pub fn array_copy<'a>( 196 | src: &impl Array<'a>, 197 | src_pos: i32, 198 | dest: &impl Array<'a>, 199 | dest_pos: i32, 200 | length: usize, 201 | ) -> Result<(), VmError> { 202 | if dest.elements_type() != src.elements_type() { 203 | // TODO: we should throw an instance of ArrayStoreException 204 | return Err(VmError::ValidationException); 205 | } 206 | 207 | for i in 0..length { 208 | let src_index = src_pos.into_usize_safe() + i; 209 | let src_item = src.get_element(src_index)?; 210 | 211 | let dest_index = dest_pos.into_usize_safe() + i; 212 | dest.set_element(dest_index, src_item)?; 213 | } 214 | 215 | Ok(()) 216 | } 217 | 218 | fn float_to_raw_int_bits<'a>(args: &[Value<'a>]) -> MethodCallResult<'a> { 219 | let arg = expect_float_at(args, 0)?; 220 | let int_bits: i32 = arg.to_bits() as i32; 221 | Ok(Some(Value::Int(int_bits))) 222 | } 223 | 224 | fn double_to_raw_long_bits<'a>(args: &[Value<'a>]) -> MethodCallResult<'a> { 225 | let arg = expect_double_at(args, 0)?; 226 | let long_bits: i64 = arg.to_bits() as i64; 227 | Ok(Some(Value::Long(long_bits))) 228 | } 229 | 230 | fn get_class_loader(receiver: Option) -> MethodCallResult { 231 | debug!("invoked get class loader for object {:?}", receiver); 232 | 233 | // It seems ok to return just null for the moment 234 | Ok(Some(Value::Null)) 235 | } 236 | 237 | fn get_primitive_class<'a>( 238 | vm: &mut Vm<'a>, 239 | stack: &mut CallStack<'a>, 240 | args: &[Value<'a>], 241 | ) -> MethodCallResult<'a> { 242 | let arg = expect_concrete_object_at(args, 0)?; 243 | let class_name = extract_str_from_java_lang_string(vm, &arg)?; 244 | let java_lang_class_instance = new_java_lang_class_object(vm, stack, &class_name)?; 245 | Ok(Some(Value::Object(java_lang_class_instance))) 246 | } 247 | 248 | fn fill_in_stack_trace<'a>( 249 | vm: &mut Vm<'a>, 250 | call_stack: &mut CallStack<'a>, 251 | receiver: Option>, 252 | ) -> MethodCallResult<'a> { 253 | let receiver = expect_some_receiver(receiver)?; 254 | let stack_trace_elements = call_stack.get_stack_trace_elements(); 255 | vm.associate_stack_trace_with_throwable(receiver.clone(), stack_trace_elements); 256 | Ok(Some(Value::Object(receiver))) 257 | } 258 | 259 | fn get_stack_trace_depth<'a>( 260 | vm: &mut Vm<'a>, 261 | receiver: Option>, 262 | ) -> MethodCallResult<'a> { 263 | let receiver = expect_some_receiver(receiver)?; 264 | match vm.get_stack_trace_associated_with_throwable(receiver) { 265 | Some(stack_trace_elements) => Ok(Some(Value::Int(stack_trace_elements.len() as i32))), 266 | None => Err(MethodCallFailed::InternalError( 267 | VmError::ValidationException, 268 | )), 269 | } 270 | } 271 | 272 | fn get_stack_trace_element<'a>( 273 | vm: &mut Vm<'a>, 274 | call_stack: &mut CallStack<'a>, 275 | receiver: Option>, 276 | args: Vec>, 277 | ) -> MethodCallResult<'a> { 278 | let receiver = expect_some_receiver(receiver)?; 279 | let index = expect_int_at(&args, 0)?; 280 | match vm.get_stack_trace_associated_with_throwable(receiver) { 281 | Some(stack_trace_elements) => { 282 | let stack_trace_element = &stack_trace_elements[index.into_usize_safe()].clone(); 283 | let stack_trace_element_java_object = 284 | new_java_lang_stack_trace_element_object(vm, call_stack, stack_trace_element)?; 285 | Ok(Some(Value::Object(stack_trace_element_java_object))) 286 | } 287 | None => Err(MethodCallFailed::InternalError( 288 | VmError::ValidationException, 289 | )), 290 | } 291 | } 292 | 293 | fn expect_some_receiver(receiver: Option) -> Result { 294 | match receiver { 295 | Some(v) => Ok(v), 296 | None => Err(VmError::ValidationException), 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /vm/src/native_methods_registry.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, fmt, fmt::Formatter}; 2 | 3 | use crate::{ 4 | abstract_object::AbstractObject, call_frame::MethodCallResult, call_stack::CallStack, 5 | class_and_method::ClassAndMethod, value::Value, vm::Vm, 6 | }; 7 | 8 | /// A callback that implements a java method marked with "native" 9 | pub type NativeCallback<'a> = fn( 10 | &mut Vm<'a>, 11 | &mut CallStack<'a>, 12 | Option>, 13 | Vec>, 14 | ) -> MethodCallResult<'a>; 15 | 16 | /// The registry of all known native methods 17 | #[derive(Default)] 18 | pub struct NativeMethodsRegistry<'a> { 19 | methods: HashMap>, 20 | 21 | // Hack for checking that integration tests can actually print the correct values: 22 | // this just stores the values printed by a method named `tempPrint` into an array 23 | // in the Vm object. This method is used for all classes whose name starts with rjvm. 24 | temp_print_callback: Option>, 25 | } 26 | 27 | impl<'a> fmt::Debug for NativeMethodsRegistry<'a> { 28 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 29 | write!(f, "NativeMethodsRegistry={:?}", self.methods.keys()) 30 | } 31 | } 32 | 33 | impl<'a> NativeMethodsRegistry<'a> { 34 | pub fn register( 35 | &mut self, 36 | class_name: &str, 37 | method_name: &str, 38 | type_descriptor: &str, 39 | callback: NativeCallback<'a>, 40 | ) { 41 | self.methods.insert( 42 | ClassMethodAndDescriptor { 43 | class: class_name.to_string(), 44 | method: method_name.to_string(), 45 | descriptor: type_descriptor.to_string(), 46 | }, 47 | callback, 48 | ); 49 | } 50 | 51 | pub(crate) fn register_temp_print(&mut self, callback: NativeCallback<'a>) { 52 | self.temp_print_callback = Some(callback); 53 | } 54 | 55 | pub fn get_method(&self, class_and_method: &ClassAndMethod) -> Option> { 56 | self.get( 57 | &class_and_method.class.name, 58 | &class_and_method.method.name, 59 | &class_and_method.method.type_descriptor, 60 | ) 61 | } 62 | 63 | pub fn get( 64 | &self, 65 | class_name: &str, 66 | method_name: &str, 67 | type_descriptor: &str, 68 | ) -> Option> { 69 | if class_name.starts_with("rjvm/") && method_name == "tempPrint" { 70 | // Hack: this method is valid for all classes in the rjvm package 71 | self.temp_print_callback 72 | } else { 73 | self.methods 74 | .get(&ClassMethodAndDescriptor { 75 | class: class_name.to_string(), 76 | method: method_name.to_string(), 77 | descriptor: type_descriptor.to_string(), 78 | }) 79 | .cloned() 80 | } 81 | } 82 | } 83 | 84 | /// Hash key for the native method registry 85 | #[derive(Debug, PartialEq, Hash, Eq)] 86 | struct ClassMethodAndDescriptor { 87 | class: String, 88 | method: String, 89 | descriptor: String, 90 | } 91 | -------------------------------------------------------------------------------- /vm/src/object.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | class::{ClassId, ClassRef}, 3 | value::Value, 4 | }; 5 | 6 | /// A java array, allocated on our memory chunk 7 | pub trait Object<'a> { 8 | fn class_id(&self) -> ClassId; 9 | 10 | /// Errors will be returned if the type of the given value does not match the field type, or if the index is invalid 11 | fn set_field(&self, index: usize, value: Value<'a>); 12 | 13 | /// Errors will be returned if the index is invalid 14 | fn get_field(&self, object_class: ClassRef, index: usize) -> Value<'a>; 15 | } 16 | -------------------------------------------------------------------------------- /vm/src/stack_trace_element.rs: -------------------------------------------------------------------------------- 1 | use rjvm_reader::line_number::LineNumber; 2 | use std::{ 3 | fmt, 4 | fmt::{Display, Formatter}, 5 | }; 6 | 7 | /// One element of the stack trace information. Models java.lang.StackTraceElement 8 | #[derive(Debug, Clone)] 9 | pub struct StackTraceElement<'a> { 10 | pub class_name: &'a str, 11 | pub method_name: &'a str, 12 | pub source_file: &'a Option, 13 | pub line_number: Option, 14 | } 15 | 16 | impl<'a> Display for StackTraceElement<'a> { 17 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 18 | if let Some(file_name) = self.source_file { 19 | if let Some(line_number) = self.line_number { 20 | write!( 21 | f, 22 | "{}::{} ({}:{})", 23 | self.class_name, self.method_name, file_name, line_number 24 | ) 25 | } else { 26 | write!( 27 | f, 28 | "{}::{} ({})", 29 | self.class_name, self.method_name, file_name 30 | ) 31 | } 32 | } else { 33 | write!(f, "{}::{}", self.class_name, self.method_name) 34 | } 35 | } 36 | } 37 | 38 | #[cfg(test)] 39 | mod tests { 40 | use crate::stack_trace_element::StackTraceElement; 41 | use rjvm_reader::line_number::LineNumber; 42 | 43 | #[test] 44 | fn can_format_without_source_file_or_line_number() { 45 | let element = StackTraceElement { 46 | class_name: "Object", 47 | method_name: "", 48 | source_file: &None, 49 | line_number: None, 50 | }; 51 | assert_eq!("Object::", format!("{element}")); 52 | } 53 | 54 | #[test] 55 | fn can_format_with_source_file_but_no_line_number() { 56 | let element = StackTraceElement { 57 | class_name: "Object", 58 | method_name: "", 59 | source_file: &Some("Object.java".to_string()), 60 | line_number: None, 61 | }; 62 | assert_eq!("Object:: (Object.java)", format!("{element}")); 63 | } 64 | 65 | #[test] 66 | fn can_format_with_source_file_and_line_number() { 67 | let element = StackTraceElement { 68 | class_name: "Object", 69 | method_name: "", 70 | source_file: &Some("Object.java".to_string()), 71 | line_number: Some(LineNumber(42)), 72 | }; 73 | assert_eq!("Object:: (Object.java:42)", format!("{element}")); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /vm/src/time.rs: -------------------------------------------------------------------------------- 1 | use std::time::{Duration, SystemTime, UNIX_EPOCH}; 2 | 3 | fn time_since_epoch() -> Duration { 4 | let start = SystemTime::now(); 5 | start 6 | .duration_since(UNIX_EPOCH) 7 | .expect("time went backwards") 8 | } 9 | 10 | /// Returns the current epoch as nano seconds 11 | pub(crate) fn get_nano_time() -> i64 { 12 | time_since_epoch().as_nanos() as i64 13 | } 14 | 15 | /// Returns the current epoch as milliseconds 16 | pub(crate) fn get_current_time_millis() -> i64 { 17 | time_since_epoch().as_millis() as i64 18 | } 19 | -------------------------------------------------------------------------------- /vm/src/value.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use rjvm_reader::field_type::{BaseType, FieldType}; 4 | 5 | use crate::{ 6 | abstract_object::{AbstractObject, ObjectKind}, 7 | array::Array, 8 | class::ClassRef, 9 | class_resolver_by_id::ClassByIdResolver, 10 | object::Object, 11 | vm_error::VmError, 12 | }; 13 | 14 | /// Models a generic value that can be stored in a local variable or on the stack. 15 | #[derive(Debug, Default, Clone, PartialEq)] 16 | pub enum Value<'a> { 17 | /// An uninitialized element. 18 | /// Should never be on the stack, but it is the default state for local variables. 19 | #[default] 20 | Uninitialized, 21 | 22 | /// Models all the 32-or-lower-bits types in the jvm: `boolean`, `byte`, `char`, `short`, 23 | /// and `int`. 24 | Int(i32), 25 | 26 | /// Models a `long` value. 27 | Long(i64), 28 | 29 | /// Models a `float` value. 30 | Float(f32), 31 | 32 | /// Models a `double` value. 33 | Double(f64), 34 | 35 | /// Models an object value 36 | Object(AbstractObject<'a>), 37 | 38 | /// Models a null object 39 | Null, 40 | // TODO: the JVM spec says we need to add return address, which are used to implement `finally` 41 | } 42 | 43 | impl<'a> Value<'a> { 44 | /// Used for runtime validations that the value matches the given type. 45 | /// Overly complex; these things, according to the JVM spec, should be checked 46 | /// at class linkage time, but we have not implemented that phase... :-) 47 | pub fn matches_type<'b, 'c, ResByName>( 48 | &self, 49 | expected_type: FieldType, 50 | class_resolver_by_id: &impl ClassByIdResolver<'c>, 51 | class_resolver_by_name: ResByName, 52 | ) -> bool 53 | where 54 | ResByName: FnOnce(&str) -> Option>, 55 | { 56 | match self { 57 | Value::Uninitialized => false, 58 | Value::Int(_) => match expected_type { 59 | FieldType::Base(base_type) => matches!( 60 | base_type, 61 | BaseType::Int 62 | | BaseType::Byte 63 | | BaseType::Char 64 | | BaseType::Short 65 | | BaseType::Boolean 66 | ), 67 | _ => false, 68 | }, 69 | Value::Long(_) => match expected_type { 70 | FieldType::Base(base_type) => base_type == BaseType::Long, 71 | _ => false, 72 | }, 73 | Value::Float(_) => match expected_type { 74 | FieldType::Base(base_type) => base_type == BaseType::Float, 75 | _ => false, 76 | }, 77 | Value::Double(_) => match expected_type { 78 | FieldType::Base(base_type) => base_type == BaseType::Double, 79 | _ => false, 80 | }, 81 | 82 | Value::Object(object) => { 83 | if object.kind() == ObjectKind::Array { 84 | match expected_type { 85 | FieldType::Array(expected_field_type) => { 86 | let array_entry_type = 87 | object.elements_type().into_field_type(class_resolver_by_id); 88 | if let Some(array_entry_type) = array_entry_type { 89 | array_entry_type == *expected_field_type 90 | } else { 91 | false 92 | } 93 | } 94 | _ => false, 95 | } 96 | } else { 97 | match expected_type { 98 | // TODO: with multiple class loaders, we should check the class identity, 99 | // not the name, since the same class could be loaded by multiple class loader 100 | FieldType::Object(expected_class_name) => { 101 | let value_class = 102 | class_resolver_by_id.find_class_by_id(object.class_id()); 103 | if let Some(object_class) = value_class { 104 | let expected_class = class_resolver_by_name(&expected_class_name); 105 | expected_class.map_or(false, |expected_class| { 106 | object_class.is_subclass_of(expected_class) 107 | }) 108 | } else { 109 | false 110 | } 111 | } 112 | _ => false, 113 | } 114 | } 115 | } 116 | 117 | Value::Null => match expected_type { 118 | FieldType::Base(_) => false, 119 | FieldType::Object(_) => true, 120 | FieldType::Array(_) => true, 121 | }, 122 | } 123 | } 124 | } 125 | 126 | /// Checks that the element at the given index is an abstract object and returns it, or an error. 127 | pub fn expect_abstract_object_at<'a>( 128 | vec: &[Value<'a>], 129 | index: usize, 130 | ) -> Result, VmError> { 131 | let value = vec.get(index); 132 | if let Some(Value::Object(object)) = value { 133 | Ok(object.clone()) 134 | } else { 135 | Err(VmError::ValidationException) 136 | } 137 | } 138 | 139 | /// Checks that the element at the given index is a concrete object and returns it, or an error. 140 | pub fn expect_concrete_object_at<'a>( 141 | vec: &[Value<'a>], 142 | index: usize, 143 | ) -> Result, VmError> { 144 | let value = expect_abstract_object_at(vec, index)?; 145 | if value.kind() == ObjectKind::Object { 146 | Ok(value) 147 | } else { 148 | Err(VmError::ValidationException) 149 | } 150 | } 151 | 152 | /// Checks that the element at the given index is an array and returns it, or an error. 153 | pub fn expect_array_at<'a>(vec: &[Value<'a>], index: usize) -> Result, VmError> { 154 | let value = expect_abstract_object_at(vec, index)?; 155 | if value.kind() == ObjectKind::Array { 156 | Ok(value) 157 | } else { 158 | Err(VmError::ValidationException) 159 | } 160 | } 161 | 162 | /// Checks that the element at the given index is an Int and returns it, or an error. 163 | pub fn expect_int_at(vec: &[Value], index: usize) -> Result { 164 | let value = vec.get(index); 165 | if let Some(Value::Int(int)) = value { 166 | Ok(*int) 167 | } else { 168 | Err(VmError::ValidationException) 169 | } 170 | } 171 | 172 | /// Checks that the element at the given index is a Float and returns it, or an error. 173 | pub fn expect_float_at(vec: &[Value], index: usize) -> Result { 174 | let value = vec.get(index); 175 | if let Some(Value::Float(float)) = value { 176 | Ok(*float) 177 | } else { 178 | Err(VmError::ValidationException) 179 | } 180 | } 181 | 182 | /// Checks that the element at the given index is a Double and returns it, or an error. 183 | pub fn expect_double_at(vec: &[Value], index: usize) -> Result { 184 | let value = vec.get(index); 185 | if let Some(Value::Double(double)) = value { 186 | Ok(*double) 187 | } else { 188 | Err(VmError::ValidationException) 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /vm/src/value_stack.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ops::Index, 3 | slice::{Iter, IterMut, SliceIndex}, 4 | }; 5 | 6 | use thiserror::Error; 7 | 8 | use crate::value::Value; 9 | 10 | /// Models the stack in a given call frame 11 | /// 12 | /// The java stack has a few more features over the classical push and pop, 13 | /// i.e. the various dup instructions, so it is modelled explicitly 14 | #[derive(Debug)] 15 | pub struct ValueStack<'a> { 16 | stack: Vec>, 17 | } 18 | 19 | /// Errors returned from various stack operations 20 | #[derive(Error, Debug, PartialEq, Eq, Clone, Copy)] 21 | pub enum ValueStackError { 22 | #[error("trying to grow stack beyond maximum capacity")] 23 | MaximumCapacityReached, 24 | #[error("cannot pop from an empty stack")] 25 | CannotPopFromEmptyStack, 26 | } 27 | 28 | impl<'a> ValueStack<'a> { 29 | /// Maximum size is known because it is stored in the class file, 30 | /// thus we can do only one allocation 31 | pub fn with_max_size(max_size: usize) -> Self { 32 | Self { 33 | stack: Vec::with_capacity(max_size), 34 | } 35 | } 36 | 37 | pub fn len(&self) -> usize { 38 | self.stack.len() 39 | } 40 | 41 | pub fn push(&mut self, value: Value<'a>) -> Result<(), ValueStackError> { 42 | if self.stack.len() < self.stack.capacity() { 43 | self.stack.push(value); 44 | Ok(()) 45 | } else { 46 | Err(ValueStackError::MaximumCapacityReached) 47 | } 48 | } 49 | 50 | pub fn pop(&mut self) -> Result, ValueStackError> { 51 | self.stack 52 | .pop() 53 | .ok_or(ValueStackError::CannotPopFromEmptyStack) 54 | } 55 | 56 | pub fn pop2(&mut self) -> Result, ValueStackError> { 57 | let value = self.pop()?; 58 | match value { 59 | Value::Long(_) | Value::Double(_) => Ok(value), 60 | _ => self.pop().map(|_| value), 61 | } 62 | } 63 | 64 | pub fn truncate(&mut self, len: usize) -> Result<(), ValueStackError> { 65 | if len > self.stack.capacity() { 66 | Err(ValueStackError::MaximumCapacityReached) 67 | } else { 68 | self.stack.truncate(len); 69 | Ok(()) 70 | } 71 | } 72 | 73 | pub fn get(&self, index: usize) -> Option<&Value<'a>> { 74 | self.stack.get(index) 75 | } 76 | 77 | pub fn iter(&self) -> Iter> { 78 | self.stack.iter() 79 | } 80 | 81 | pub fn iter_mut(&mut self) -> IterMut> { 82 | self.stack.iter_mut() 83 | } 84 | 85 | pub fn dup(&mut self) -> Result<(), ValueStackError> { 86 | match self.stack.last() { 87 | None => Err(ValueStackError::CannotPopFromEmptyStack), 88 | Some(head) => self.push(head.clone()), 89 | } 90 | } 91 | 92 | pub fn dup_x1(&mut self) -> Result<(), ValueStackError> { 93 | let value1 = self.pop()?; 94 | let value2 = self.pop()?; 95 | self.push(value1.clone())?; 96 | self.push(value2)?; 97 | self.push(value1) 98 | } 99 | 100 | pub fn dup_x2(&mut self) -> Result<(), ValueStackError> { 101 | let value1 = self.pop()?; 102 | let value2 = self.pop()?; 103 | let value3 = self.pop()?; 104 | self.push(value1.clone())?; 105 | self.push(value3)?; 106 | self.push(value2)?; 107 | self.push(value1) 108 | } 109 | 110 | pub fn dup2(&mut self) -> Result<(), ValueStackError> { 111 | let value1 = self.pop()?; 112 | let value2 = self.pop()?; 113 | self.push(value2.clone())?; 114 | self.push(value1.clone())?; 115 | self.push(value2)?; 116 | self.push(value1) 117 | } 118 | 119 | pub fn dup2_x1(&mut self) -> Result<(), ValueStackError> { 120 | let value1 = self.pop()?; 121 | let value2 = self.pop()?; 122 | let value3 = self.pop()?; 123 | self.push(value2.clone())?; 124 | self.push(value1.clone())?; 125 | self.push(value3)?; 126 | self.push(value2)?; 127 | self.push(value1) 128 | } 129 | 130 | pub fn dup2_x2(&mut self) -> Result<(), ValueStackError> { 131 | let value1 = self.pop()?; 132 | let value2 = self.pop()?; 133 | let value3 = self.pop()?; 134 | let value4 = self.pop()?; 135 | self.push(value2.clone())?; 136 | self.push(value1.clone())?; 137 | self.push(value4)?; 138 | self.push(value3)?; 139 | self.push(value2)?; 140 | self.push(value1) 141 | } 142 | 143 | pub fn swap(&mut self) -> Result<(), ValueStackError> { 144 | let value1 = self.pop()?; 145 | let value2 = self.pop()?; 146 | self.push(value1)?; 147 | self.push(value2) 148 | } 149 | } 150 | 151 | /// Allows using the [] operator 152 | impl<'a, I> Index for ValueStack<'a> 153 | where 154 | I: SliceIndex<[Value<'a>]>, 155 | { 156 | type Output = I::Output; 157 | 158 | fn index(&self, index: I) -> &Self::Output { 159 | self.stack.index(index) 160 | } 161 | } 162 | 163 | #[cfg(test)] 164 | mod tests { 165 | use crate::{value::Value, value_stack::ValueStack}; 166 | 167 | #[test] 168 | fn can_do_push_pop_and_indexing() { 169 | let mut stack = ValueStack::with_max_size(3); 170 | stack.push(Value::Int(1)).expect("should be able to push"); 171 | stack.push(Value::Int(2)).expect("should be able to push"); 172 | stack.push(Value::Int(3)).expect("should be able to push"); 173 | 174 | assert_eq!(Ok(Value::Int(3)), stack.pop()); 175 | assert_eq!(Some(&Value::Int(1)), stack.get(0)); 176 | assert_eq!(Value::Int(2), stack[1]); 177 | assert_eq!(2, stack.len()); 178 | 179 | stack.truncate(1).expect("should be able to truncate"); 180 | assert_eq!(1, stack.len()); 181 | assert_eq!(Ok(Value::Int(1)), stack.pop()); 182 | } 183 | 184 | #[test] 185 | fn cannot_push_above_capacity() { 186 | let mut stack = ValueStack::with_max_size(1); 187 | stack.push(Value::Int(1)).expect("should be able to push"); 188 | assert!(stack.push(Value::Int(2)).is_err()); 189 | } 190 | 191 | #[test] 192 | fn can_invoke_dup() { 193 | let mut stack = ValueStack::with_max_size(2); 194 | stack.push(Value::Int(1)).expect("should be able to push"); 195 | stack.dup().expect("should be able to dup"); 196 | assert_eq!(2, stack.len()); 197 | assert_eq!(Ok(Value::Int(1)), stack.pop()); 198 | assert_eq!(Ok(Value::Int(1)), stack.pop()); 199 | } 200 | 201 | #[test] 202 | fn can_invoke_dup_x1() { 203 | let mut stack = ValueStack::with_max_size(3); 204 | stack.push(Value::Int(2)).expect("should be able to push"); 205 | stack.push(Value::Int(1)).expect("should be able to push"); 206 | stack.dup_x1().expect("should be able to dup_x1"); 207 | assert_eq!(3, stack.len()); 208 | assert_eq!(Ok(Value::Int(1)), stack.pop()); 209 | assert_eq!(Ok(Value::Int(2)), stack.pop()); 210 | assert_eq!(Ok(Value::Int(1)), stack.pop()); 211 | } 212 | 213 | #[test] 214 | fn can_invoke_dup_x2() { 215 | let mut stack = ValueStack::with_max_size(4); 216 | stack.push(Value::Int(3)).expect("should be able to push"); 217 | stack.push(Value::Int(2)).expect("should be able to push"); 218 | stack.push(Value::Int(1)).expect("should be able to push"); 219 | stack.dup_x2().expect("should be able to dup_x2"); 220 | assert_eq!(4, stack.len()); 221 | assert_eq!(Ok(Value::Int(1)), stack.pop()); 222 | assert_eq!(Ok(Value::Int(2)), stack.pop()); 223 | assert_eq!(Ok(Value::Int(3)), stack.pop()); 224 | assert_eq!(Ok(Value::Int(1)), stack.pop()); 225 | } 226 | 227 | #[test] 228 | fn can_invoke_dup2() { 229 | let mut stack = ValueStack::with_max_size(4); 230 | stack.push(Value::Int(2)).expect("should be able to push"); 231 | stack.push(Value::Int(1)).expect("should be able to push"); 232 | stack.dup2().expect("should be able to dup2"); 233 | assert_eq!(4, stack.len()); 234 | assert_eq!(Ok(Value::Int(1)), stack.pop()); 235 | assert_eq!(Ok(Value::Int(2)), stack.pop()); 236 | assert_eq!(Ok(Value::Int(1)), stack.pop()); 237 | assert_eq!(Ok(Value::Int(2)), stack.pop()); 238 | } 239 | 240 | #[test] 241 | fn can_invoke_dup2_x1() { 242 | let mut stack = ValueStack::with_max_size(5); 243 | stack.push(Value::Int(3)).expect("should be able to push"); 244 | stack.push(Value::Int(2)).expect("should be able to push"); 245 | stack.push(Value::Int(1)).expect("should be able to push"); 246 | stack.dup2_x1().expect("should be able to dup2_x1"); 247 | assert_eq!(5, stack.len()); 248 | assert_eq!(Ok(Value::Int(1)), stack.pop()); 249 | assert_eq!(Ok(Value::Int(2)), stack.pop()); 250 | assert_eq!(Ok(Value::Int(3)), stack.pop()); 251 | assert_eq!(Ok(Value::Int(1)), stack.pop()); 252 | assert_eq!(Ok(Value::Int(2)), stack.pop()); 253 | } 254 | 255 | #[test] 256 | fn can_invoke_dup2_x2() { 257 | let mut stack = ValueStack::with_max_size(6); 258 | stack.push(Value::Int(4)).expect("should be able to push"); 259 | stack.push(Value::Int(3)).expect("should be able to push"); 260 | stack.push(Value::Int(2)).expect("should be able to push"); 261 | stack.push(Value::Int(1)).expect("should be able to push"); 262 | stack.dup2_x2().expect("should be able to dup2_x2"); 263 | assert_eq!(6, stack.len()); 264 | assert_eq!(Ok(Value::Int(1)), stack.pop()); 265 | assert_eq!(Ok(Value::Int(2)), stack.pop()); 266 | assert_eq!(Ok(Value::Int(3)), stack.pop()); 267 | assert_eq!(Ok(Value::Int(4)), stack.pop()); 268 | assert_eq!(Ok(Value::Int(1)), stack.pop()); 269 | assert_eq!(Ok(Value::Int(2)), stack.pop()); 270 | } 271 | 272 | #[test] 273 | fn can_invoke_pop2() { 274 | let mut stack = ValueStack::with_max_size(4); 275 | stack 276 | .push(Value::Double(0f64)) 277 | .expect("should be able to push"); 278 | stack.push(Value::Int(1)).expect("should be able to push"); 279 | stack.push(Value::Int(2)).expect("should be able to push"); 280 | stack.push(Value::Long(3)).expect("should be able to push"); 281 | assert_eq!(Ok(Value::Long(3)), stack.pop2()); 282 | assert_eq!(3, stack.len()); 283 | assert_eq!(Ok(Value::Int(2)), stack.pop2()); 284 | assert_eq!(1, stack.len()); 285 | assert_eq!(Ok(Value::Double(0f64)), stack.pop2()); 286 | } 287 | 288 | #[test] 289 | fn can_invoke_swap() { 290 | let mut stack = ValueStack::with_max_size(2); 291 | stack.push(Value::Int(1)).expect("should be able to push"); 292 | stack.push(Value::Int(2)).expect("should be able to push"); 293 | stack.swap().expect("should be able to swap"); 294 | assert_eq!(2, stack.len()); 295 | assert_eq!(Ok(Value::Int(1)), stack.pop()); 296 | assert_eq!(Ok(Value::Int(2)), stack.pop()); 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /vm/src/vm_error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | use crate::value_stack::ValueStackError; 4 | 5 | /// Various errors that are thrown when executing java bytecode 6 | // TODO: this implementation is quite poor: we do not keep track of the origin 7 | // of the errors, and we do not keep many details 8 | #[derive(Debug, Error, PartialEq, Eq)] 9 | pub enum VmError { 10 | #[error("unexpected error loading class: {0}")] 11 | ClassLoadingError(String), 12 | 13 | /// TODO: this should become throwing a real `java.lang.NullPointerException` 14 | #[error("null pointer exception")] 15 | NullPointerException, 16 | 17 | /// TODO: this should become throwing a real `java.lang.ClassNotFoundException` 18 | #[error("class not found: {0}")] 19 | ClassNotFoundException(String), 20 | 21 | #[error("method not found: {0}.{1}#{2}")] 22 | MethodNotFoundException(String, String, String), 23 | 24 | #[error("field not found: {0}.{1}")] 25 | FieldNotFoundException(String, String), 26 | 27 | /// This is an overly generic error, abused to mean "something unexpected happened". 28 | /// It includes mostly errors that should be checked during the linking phase of the class file 29 | /// (which we have not implemented). 30 | #[error("validation exception - invalid class file")] 31 | ValidationException, 32 | 33 | /// TODO: this should become throwing a real `java.lang.ArithmeticException` 34 | #[error("arithmetic exception")] 35 | ArithmeticException, 36 | 37 | #[error("not yet implemented")] 38 | NotImplemented, 39 | 40 | /// TODO: this should become throwing a real `java.lang.ArrayIndexOutOfBoundsException` 41 | #[error("array index out of bounds")] 42 | ArrayIndexOutOfBoundsException, 43 | 44 | /// TODO: this should become throwing a real `java.lang.ClassCastException` 45 | #[error("class cast exception")] 46 | ClassCastException, 47 | } 48 | 49 | // TODO: remove once we implement exceptions 50 | impl From for VmError { 51 | fn from(_: ValueStackError) -> Self { 52 | Self::ValidationException 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /vm/tests/integration/main.rs: -------------------------------------------------------------------------------- 1 | mod real_code_tests; 2 | -------------------------------------------------------------------------------- /vm/tests/integration/real_code_tests.rs: -------------------------------------------------------------------------------- 1 | use rjvm_vm::{ 2 | exceptions::MethodCallFailed, 3 | java_objects_creation::extract_str_from_java_lang_string, 4 | value::{expect_concrete_object_at, Value}, 5 | vm::{Vm, DEFAULT_MAX_MEMORY}, 6 | }; 7 | 8 | // This file tests the real classes in ../resources/rjvm 9 | 10 | fn create_base_vm(max_memory: usize) -> Vm<'static> { 11 | let mut vm = Vm::new(max_memory); 12 | 13 | let src_dir = env!("CARGO_MANIFEST_DIR"); 14 | vm.append_class_path(&format!("{src_dir}/rt.jar:{src_dir}/tests/resources",)) 15 | .expect("should be able to add entries to the classpath"); 16 | vm 17 | } 18 | 19 | fn invoke<'a>( 20 | vm: &mut Vm<'a>, 21 | class_name: &str, 22 | method_name: &str, 23 | descriptor: &str, 24 | ) -> Result>, MethodCallFailed<'a>> { 25 | let call_stack = vm.allocate_call_stack(); 26 | let main_method = vm 27 | .resolve_class_method(call_stack, class_name, method_name, descriptor) 28 | .expect("should find main method"); 29 | 30 | let main_result = vm.invoke(call_stack, main_method, None, vec![]); 31 | vm.debug_stats(); 32 | println!("result of {class_name}::{method_name}: {main_result:?}"); 33 | 34 | main_result 35 | } 36 | 37 | fn extract_printed_string(vm: &Vm, index: usize) -> String { 38 | let string = expect_concrete_object_at(&vm.printed, index) 39 | .unwrap_or_else(|_| panic!("should have printed an object at position {index}")); 40 | extract_str_from_java_lang_string(vm, &string).expect("should have a valid string") 41 | } 42 | 43 | #[test_log::test] 44 | fn simple_main() { 45 | let mut vm = create_base_vm(DEFAULT_MAX_MEMORY); 46 | let main_result = invoke(&mut vm, "rjvm/SimpleMain", "main", "([Ljava/lang/String;)V"); 47 | assert_eq!(Ok(None), main_result); 48 | 49 | assert_eq!(vec![Value::Int(3), Value::Int(6)], vm.printed); 50 | } 51 | 52 | #[test_log::test] 53 | fn superclasses() { 54 | let mut vm = create_base_vm(DEFAULT_MAX_MEMORY); 55 | let main_result = invoke( 56 | &mut vm, 57 | "rjvm/SuperClasses", 58 | "main", 59 | "([Ljava/lang/String;)V", 60 | ); 61 | assert_eq!(Ok(None), main_result); 62 | 63 | assert_eq!(vec![Value::Int(4)], vm.printed); 64 | } 65 | 66 | #[test_log::test] 67 | fn control_flow() { 68 | let mut vm = create_base_vm(DEFAULT_MAX_MEMORY); 69 | let main_result = invoke( 70 | &mut vm, 71 | "rjvm/ControlFlow", 72 | "main", 73 | "([Ljava/lang/String;)V", 74 | ); 75 | assert_eq!(Ok(None), main_result); 76 | 77 | assert_eq!( 78 | vec![ 79 | // Ints 80 | Value::Int(241), 81 | // Objects 82 | Value::Int(42), 83 | Value::Int(43), 84 | // Double 85 | Value::Int(1), 86 | Value::Int(1), 87 | Value::Int(1), 88 | // Arrays 89 | Value::Int(51), 90 | Value::Int(52), 91 | ], 92 | vm.printed 93 | ); 94 | } 95 | 96 | #[test_log::test] 97 | fn numeric_types() { 98 | let mut vm = create_base_vm(DEFAULT_MAX_MEMORY); 99 | let main_result = invoke( 100 | &mut vm, 101 | "rjvm/NumericTypes", 102 | "main", 103 | "([Ljava/lang/String;)V", 104 | ); 105 | assert_eq!(Ok(None), main_result); 106 | 107 | assert_eq!( 108 | vec![ 109 | Value::Int(3), 110 | Value::Float(3.45f32), 111 | Value::Int(3), 112 | Value::Long(3), 113 | Value::Double(3.45f32 as f64), 114 | Value::Long(2), 115 | Value::Int(2), 116 | Value::Float(2f32), 117 | Value::Double(2f64), 118 | Value::Double(4.45), 119 | Value::Int(4), 120 | Value::Float(4.45), 121 | Value::Long(4), 122 | Value::Int(-1), 123 | Value::Long(-1), 124 | Value::Float(-1f32), 125 | Value::Double(-1f64), 126 | Value::Int(1), 127 | Value::Int(((-1i32) as u32 >> 2) as i32), 128 | Value::Int(8), 129 | Value::Long(1), 130 | Value::Long(((-1i64) as u64 >> 2) as i64), 131 | Value::Long(8), 132 | ], 133 | vm.printed 134 | ); 135 | } 136 | 137 | #[test_log::test] 138 | fn numeric_arrays() { 139 | let mut vm = create_base_vm(DEFAULT_MAX_MEMORY); 140 | let main_result = invoke( 141 | &mut vm, 142 | "rjvm/NumericArrays", 143 | "main", 144 | "([Ljava/lang/String;)V", 145 | ); 146 | assert_eq!(Ok(None), main_result); 147 | 148 | assert_eq!( 149 | vec![ 150 | Value::Int(1), 151 | Value::Int(2), 152 | Value::Int(0x03), 153 | Value::Int(2), 154 | Value::Int('b' as i32), 155 | Value::Int(2), 156 | Value::Int(-1), 157 | Value::Int(2), 158 | Value::Int(12), 159 | Value::Int(2), 160 | Value::Long(2), 161 | Value::Int(2), 162 | Value::Float(1.2f32 + 0.2f32), 163 | Value::Int(2), 164 | Value::Double(0f64), 165 | Value::Int(2), 166 | // Arraycopy 167 | Value::Int(0), 168 | Value::Int(2), 169 | Value::Int(3), 170 | Value::Int(4), 171 | Value::Int(0), 172 | Value::Int(5), 173 | ], 174 | vm.printed 175 | ); 176 | } 177 | 178 | #[test_log::test] 179 | fn object_arrays() { 180 | let mut vm = create_base_vm(DEFAULT_MAX_MEMORY); 181 | let main_result = invoke( 182 | &mut vm, 183 | "rjvm/ObjectArrays", 184 | "main", 185 | "([Ljava/lang/String;)V", 186 | ); 187 | assert_eq!(Ok(None), main_result); 188 | 189 | assert_eq!(vec![Value::Int(5),], vm.printed); 190 | } 191 | 192 | #[test_log::test] 193 | fn statics() { 194 | let mut vm = create_base_vm(DEFAULT_MAX_MEMORY); 195 | let main_result = invoke(&mut vm, "rjvm/Statics", "main", "([Ljava/lang/String;)V"); 196 | assert_eq!(Ok(None), main_result); 197 | 198 | assert_eq!(vec![Value::Int(311), Value::Int(322),], vm.printed); 199 | } 200 | 201 | #[test_log::test] 202 | fn instance_of() { 203 | let mut vm = create_base_vm(DEFAULT_MAX_MEMORY); 204 | let main_result = invoke(&mut vm, "rjvm/InstanceOf", "main", "([Ljava/lang/String;)V"); 205 | assert_eq!(Ok(None), main_result); 206 | 207 | assert_eq!( 208 | vec![ 209 | Value::Int(1), 210 | Value::Int(1), 211 | // C1 212 | Value::Int(0), 213 | Value::Int(0), 214 | Value::Int(0), 215 | Value::Int(0), 216 | // C2 217 | Value::Int(1), 218 | Value::Int(0), 219 | Value::Int(0), 220 | Value::Int(0), 221 | // C3 222 | Value::Int(1), 223 | Value::Int(1), 224 | Value::Int(0), 225 | Value::Int(0), 226 | // C4 227 | Value::Int(0), 228 | Value::Int(0), 229 | Value::Int(1), 230 | Value::Int(1), 231 | // C5 232 | Value::Int(1), 233 | Value::Int(0), 234 | Value::Int(1), 235 | Value::Int(1), 236 | ], 237 | vm.printed 238 | ); 239 | } 240 | 241 | #[test_log::test] 242 | fn instance_of_array() { 243 | let mut vm = create_base_vm(DEFAULT_MAX_MEMORY); 244 | let main_result = invoke( 245 | &mut vm, 246 | "rjvm/InstanceOfArray", 247 | "main", 248 | "([Ljava/lang/String;)V", 249 | ); 250 | assert_eq!(Ok(None), main_result); 251 | 252 | assert_eq!( 253 | vec![ 254 | // C0 255 | Value::Int(1), 256 | Value::Int(1), 257 | Value::Int(0), 258 | Value::Int(0), 259 | // C1 260 | Value::Int(1), 261 | Value::Int(0), 262 | Value::Int(1), 263 | Value::Int(1), 264 | ], 265 | vm.printed 266 | ); 267 | } 268 | 269 | #[test_log::test] 270 | fn strings() { 271 | let mut vm = create_base_vm(DEFAULT_MAX_MEMORY); 272 | let main_result = invoke(&mut vm, "rjvm/Strings", "main", "([Ljava/lang/String;)V"); 273 | assert_eq!(Ok(None), main_result); 274 | 275 | assert_eq!(1, vm.printed.len()); 276 | assert_eq!( 277 | "Hello, Andrea, you were born in 1985", 278 | extract_printed_string(&vm, 0) 279 | ); 280 | } 281 | 282 | #[test_log::test] 283 | fn invoke_interface() { 284 | let mut vm = create_base_vm(DEFAULT_MAX_MEMORY); 285 | let main_result = invoke( 286 | &mut vm, 287 | "rjvm/InvokeInterface", 288 | "main", 289 | "([Ljava/lang/String;)V", 290 | ); 291 | assert_eq!(Ok(None), main_result); 292 | 293 | assert_eq!( 294 | vec![Value::Int(12), Value::Int(4), Value::Int(10)], 295 | vm.printed 296 | ); 297 | } 298 | 299 | #[test_log::test] 300 | fn check_cast() { 301 | let mut vm = create_base_vm(DEFAULT_MAX_MEMORY); 302 | let main_result = invoke(&mut vm, "rjvm/CheckCast", "main", "([Ljava/lang/String;)V"); 303 | assert_eq!(Ok(None), main_result); 304 | 305 | assert_eq!(vec![Value::Int(1)], vm.printed); 306 | } 307 | 308 | #[test_log::test] 309 | fn stack_trace_printing() { 310 | let mut vm = create_base_vm(DEFAULT_MAX_MEMORY); 311 | let main_result = invoke( 312 | &mut vm, 313 | "rjvm/StackTracePrinting", 314 | "main", 315 | "([Ljava/lang/String;)V", 316 | ); 317 | assert_eq!(Ok(None), main_result); 318 | 319 | assert_eq!(4, vm.printed.len()); 320 | assert_eq!( 321 | "java/lang/Throwable::fillInStackTrace - Throwable.java:783", 322 | extract_printed_string(&vm, 0) 323 | ); 324 | assert_eq!( 325 | "java/lang/Throwable:: - Throwable.java:250", 326 | extract_printed_string(&vm, 1) 327 | ); 328 | assert_eq!( 329 | "java/lang/Exception:: - Exception.java:55", 330 | extract_printed_string(&vm, 2) 331 | ); 332 | assert_eq!( 333 | "rjvm/StackTracePrinting::main - StackTracePrinting.java:5", 334 | extract_printed_string(&vm, 3) 335 | ); 336 | } 337 | 338 | #[test_log::test] 339 | fn exceptions_throwing_and_catching() { 340 | let mut vm = create_base_vm(DEFAULT_MAX_MEMORY); 341 | let main_result = invoke( 342 | &mut vm, 343 | "rjvm/ExceptionsThrowingAndCatching", 344 | "main", 345 | "([Ljava/lang/String;)V", 346 | ); 347 | assert_eq!(Ok(None), main_result); 348 | 349 | assert_eq!( 350 | vec![ 351 | Value::Int(1), 352 | Value::Int(2), 353 | Value::Int(3), 354 | Value::Int(5), 355 | Value::Int(6) 356 | ], 357 | vm.printed 358 | ); 359 | } 360 | 361 | #[test_log::test] 362 | fn gabarge_collector() { 363 | let mut vm = create_base_vm(10_000_000); 364 | let main_result = invoke( 365 | &mut vm, 366 | "rjvm/GarbageCollection", 367 | "main", 368 | "([Ljava/lang/String;)V", 369 | ); 370 | assert_eq!(Ok(None), main_result); 371 | } 372 | 373 | #[test_log::test] 374 | fn generic() { 375 | let mut vm = create_base_vm(10_000_000); 376 | let main_result = invoke(&mut vm, "rjvm/Generic", "main", "([Ljava/lang/String;)V"); 377 | assert_eq!(Ok(None), main_result); 378 | } 379 | -------------------------------------------------------------------------------- /vm/tests/resources/compile.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | javac -source 6 -target 6 rjvm/*.java 3 | -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/CheckCast$C1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/CheckCast$C1.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/CheckCast$C2.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/CheckCast$C2.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/CheckCast.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/CheckCast.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/CheckCast.java: -------------------------------------------------------------------------------- 1 | package rjvm; 2 | 3 | public class CheckCast { 4 | static class C1 {} 5 | 6 | static class C2 extends C1 {} 7 | 8 | public static void main(String[] args) { 9 | checkCast(new C2()); 10 | } 11 | 12 | private static void checkCast(C1 c) { 13 | checkCasted((C2) c); 14 | } 15 | 16 | private static void checkCasted(C2 c) { 17 | tempPrint(c != null); 18 | } 19 | 20 | private static native void tempPrint(boolean value); 21 | } 22 | -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/ControlFlow.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/ControlFlow.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/ControlFlow.java: -------------------------------------------------------------------------------- 1 | package rjvm; 2 | 3 | public class ControlFlow { 4 | public static void main(String[] args) { 5 | Object o = new Object(); 6 | Object[] arr = new Object[]{o}; 7 | 8 | controlFlowInts(); 9 | controlFlowObjects(o, o, new Object()); 10 | controlFlowLongFloatDouble(1, 1, 1); 11 | controlFlowArrays(arr, arr, new Object[2]); 12 | } 13 | 14 | private static void controlFlowInts() { 15 | int x = 1; 16 | while (x < 100) { 17 | if (x % 2 == 0) { 18 | x = x * 3 + 1; 19 | } else { 20 | x += 1; 21 | } 22 | } 23 | tempPrint(x); 24 | } 25 | 26 | private static void controlFlowObjects(Object a, Object b, Object c) { 27 | if (a != null) { 28 | tempPrint(42); 29 | } 30 | if (a == b) { 31 | tempPrint(43); 32 | } 33 | if (a == c) { 34 | tempPrint(44); 35 | } 36 | } 37 | 38 | private static void controlFlowLongFloatDouble(long l, float f, double d) { 39 | if (l > 0) { 40 | tempPrint(1); 41 | } 42 | if (f > 0) { 43 | tempPrint(1); 44 | } 45 | if (d > 0) { 46 | tempPrint(1); 47 | } 48 | } 49 | 50 | private static void controlFlowArrays(Object[] a, Object[] b, Object[] c) { 51 | if (a != null) { 52 | tempPrint(51); 53 | } 54 | if (a == b) { 55 | tempPrint(52); 56 | } 57 | if (a == c) { 58 | tempPrint(53); 59 | } 60 | } 61 | 62 | private static native void tempPrint(int value); 63 | } 64 | -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/ExceptionsThrowingAndCatching$E1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/ExceptionsThrowingAndCatching$E1.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/ExceptionsThrowingAndCatching$E2.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/ExceptionsThrowingAndCatching$E2.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/ExceptionsThrowingAndCatching.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/ExceptionsThrowingAndCatching.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/ExceptionsThrowingAndCatching.java: -------------------------------------------------------------------------------- 1 | package rjvm; 2 | 3 | class ExceptionsThrowingAndCatching { 4 | public static class E1 extends Exception {} 5 | public static class E2 extends E1 {} 6 | 7 | public static void main(String[] args) { 8 | try { 9 | throw new E1(); 10 | } catch (E1 e1) { 11 | tempPrint(1); 12 | } 13 | 14 | try { 15 | throw new E2(); 16 | } catch (E1 e1) { 17 | tempPrint(2); 18 | } 19 | 20 | try { 21 | throw new E2(); 22 | } catch (E2 e2) { 23 | tempPrint(3); 24 | } catch (E1 e1) { 25 | tempPrint(4); 26 | } 27 | 28 | try { 29 | throwE2(); 30 | } catch (E2 e2) { 31 | tempPrint(5); 32 | } 33 | 34 | throwAndCatchE1(); 35 | } 36 | 37 | private static void throwE2() throws E2 { 38 | throw new E2(); 39 | } 40 | 41 | private static void throwAndCatchE1() { 42 | try { 43 | throw new E1(); 44 | } catch (Exception e) { 45 | tempPrint(6); 46 | } 47 | } 48 | 49 | private static native void tempPrint(int value); 50 | } -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/GarbageCollection$ALargeObject.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/GarbageCollection$ALargeObject.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/GarbageCollection$ASmallObject.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/GarbageCollection$ASmallObject.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/GarbageCollection$AWrapperObject.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/GarbageCollection$AWrapperObject.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/GarbageCollection.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/GarbageCollection.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/GarbageCollection.java: -------------------------------------------------------------------------------- 1 | package rjvm; 2 | 3 | public class GarbageCollection { 4 | public static void main(String[] args) { 5 | // Gc roots can be objects or arrays 6 | AWrapperObject anObjectThatShouldNotBeDestroyed = new AWrapperObject(0); 7 | AWrapperObject[] anotherObjectAlive = new AWrapperObject[]{new AWrapperObject(-1)}; 8 | 9 | int count = 10; 10 | ASmallObject[] anotherArray = new ASmallObject[count]; 11 | for (int i = 1; i <= count; ++i) { 12 | // Trigger GC repeatedly 13 | new AWrapperObject(i); 14 | 15 | anotherArray[i - 1] = new ASmallObject(i); 16 | } 17 | 18 | tempPrint("checking references are still alive..."); 19 | tempPrint(anObjectThatShouldNotBeDestroyed.getValue()); 20 | tempPrint(anotherObjectAlive[0].getValue()); 21 | tempPrint(anotherArray[0].value); 22 | } 23 | 24 | public static class AWrapperObject { 25 | private final int value; 26 | private final ASmallObject aSmallObject; 27 | private final ALargeObject aLargeObject = new ALargeObject(); 28 | 29 | public AWrapperObject(int value) { 30 | this.value = value; 31 | this.aSmallObject = new ASmallObject(value); 32 | tempPrint(value); 33 | 34 | aLargeObject.oneMegabyteOfData[0] = value; 35 | } 36 | 37 | public long getValue() { 38 | return this.value + this.aSmallObject.value + this.aLargeObject.oneMegabyteOfData[0]; 39 | } 40 | } 41 | 42 | public static class ASmallObject { 43 | private final long value; 44 | 45 | public ASmallObject(long value) { 46 | this.value = value; 47 | } 48 | } 49 | 50 | public static class ALargeObject { 51 | private final long[] oneMegabyteOfData = new long[1024 * 1024 / 8]; 52 | } 53 | 54 | private static native void tempPrint(long value); 55 | private static native void tempPrint(String value); 56 | } 57 | -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/Generic.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/Generic.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/Generic.java: -------------------------------------------------------------------------------- 1 | package rjvm; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class Generic { 7 | public static void main(String[] args) { 8 | List strings = new ArrayList(10); 9 | strings.add("hey"); 10 | strings.add("hackernews"); 11 | 12 | for (String s : strings) { 13 | tempPrint(s); 14 | } 15 | } 16 | 17 | private static native void tempPrint(String value); 18 | } 19 | -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/InstanceOf$C1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/InstanceOf$C1.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/InstanceOf$C2.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/InstanceOf$C2.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/InstanceOf$C3.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/InstanceOf$C3.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/InstanceOf$C4.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/InstanceOf$C4.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/InstanceOf$C5.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/InstanceOf$C5.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/InstanceOf$Intf1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/InstanceOf$Intf1.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/InstanceOf$Intf2.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/InstanceOf$Intf2.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/InstanceOf$Intf3.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/InstanceOf$Intf3.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/InstanceOf.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/InstanceOf.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/InstanceOf.java: -------------------------------------------------------------------------------- 1 | package rjvm; 2 | 3 | public class InstanceOf { 4 | interface Intf1 {} 5 | interface Intf2 extends Intf1 {} 6 | interface Intf3 extends Cloneable {} 7 | static class C1 {} 8 | static class C2 implements Intf1 {} 9 | static class C3 implements Intf2 {} 10 | static class C4 implements Intf3 {} 11 | static class C5 implements Intf3, Intf1 {} 12 | 13 | public static void main(String[] args) { 14 | tempPrint(new C1() instanceof Object); 15 | tempPrint(new C1() instanceof C1); 16 | 17 | checkInstanceOfInterfaces(new C1()); 18 | checkInstanceOfInterfaces(new C2()); 19 | checkInstanceOfInterfaces(new C3()); 20 | checkInstanceOfInterfaces(new C4()); 21 | checkInstanceOfInterfaces(new C5()); 22 | } 23 | 24 | private static void checkInstanceOfInterfaces(Object v) { 25 | tempPrint(v instanceof Intf1); 26 | tempPrint(v instanceof Intf2); 27 | tempPrint(v instanceof Intf3); 28 | tempPrint(v instanceof Cloneable); 29 | } 30 | 31 | private static native void tempPrint(boolean value); 32 | } 33 | -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/InstanceOfArray$C0.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/InstanceOfArray$C0.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/InstanceOfArray$C1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/InstanceOfArray$C1.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/InstanceOfArray$Intf1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/InstanceOfArray$Intf1.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/InstanceOfArray.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/InstanceOfArray.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/InstanceOfArray.java: -------------------------------------------------------------------------------- 1 | package rjvm; 2 | 3 | public class InstanceOfArray { 4 | interface Intf1 { 5 | } 6 | 7 | static class C0 { 8 | } 9 | 10 | static class C1 implements Intf1 { 11 | } 12 | 13 | public static void main(String[] args) { 14 | checkInstanceOfInterfaces(new C0[0]); 15 | checkInstanceOfInterfaces(new C1[0]); 16 | } 17 | 18 | private static void checkInstanceOfInterfaces(Object[] v) { 19 | tempPrint(v instanceof Object[]); 20 | tempPrint(v instanceof C0[]); 21 | tempPrint(v instanceof C1[]); 22 | tempPrint(v instanceof Intf1[]); 23 | } 24 | 25 | private static native void tempPrint(boolean value); 26 | } 27 | -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/InvokeInterface$NotReallyASquare.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/InvokeInterface$NotReallyASquare.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/InvokeInterface$Polygon.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/InvokeInterface$Polygon.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/InvokeInterface$Rectangle.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/InvokeInterface$Rectangle.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/InvokeInterface$Square.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/InvokeInterface$Square.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/InvokeInterface.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/InvokeInterface.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/InvokeInterface.java: -------------------------------------------------------------------------------- 1 | package rjvm; 2 | 3 | public class InvokeInterface { 4 | interface Polygon { 5 | int area(); 6 | } 7 | 8 | static class Rectangle implements Polygon { 9 | private final int width; 10 | private final int height; 11 | 12 | Rectangle(int width, int height) { 13 | this.width = width; 14 | this.height = height; 15 | } 16 | 17 | @Override 18 | public int area() { 19 | return width * height; 20 | } 21 | } 22 | 23 | static class Square implements Polygon { 24 | private final int side; 25 | 26 | Square(int side) { 27 | this.side = side; 28 | } 29 | 30 | @Override 31 | public int area() { 32 | return side * side; 33 | } 34 | } 35 | 36 | static class NotReallyASquare extends Square implements Polygon { 37 | NotReallyASquare(int side) { 38 | super(side); 39 | } 40 | 41 | @Override 42 | public int area() { 43 | return super.area() + 1; 44 | } 45 | } 46 | 47 | public static void main(String[] args) { 48 | printAreas(new Polygon[]{ 49 | new Rectangle(3, 4), 50 | new Square(2), 51 | new NotReallyASquare(3), 52 | }); 53 | } 54 | 55 | private static void printAreas(Polygon[] polygons) { 56 | for (Polygon p : polygons) { 57 | tempPrint(p.area()); 58 | } 59 | } 60 | 61 | private static native void tempPrint(int value); 62 | } 63 | -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/NumericArrays.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/NumericArrays.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/NumericArrays.java: -------------------------------------------------------------------------------- 1 | package rjvm; 2 | 3 | public class NumericArrays { 4 | public static void main(String[] args) { 5 | playWithArrayOfBooleans(new boolean[2]); 6 | playWithArrayOfBytes(new byte[]{0x01, 0x02}); 7 | playWithArrayOfChars(new char[]{'a', 'b'}); 8 | playWithArrayOfShorts(new short[]{1, 2}); 9 | playWithArrayOfInts(new int[]{3, 4}); 10 | playWithArrayOfLongs(new long[]{4, 2}); 11 | playWithArrayOfFloats(new float[]{1.2f, 0.2f}); 12 | playWithArrayOfDoubles(new double[]{0.0, 3.3}); 13 | copyArrays(); 14 | } 15 | 16 | private static void playWithArrayOfBooleans(boolean[] array) { 17 | array[0] = true; 18 | tempPrint(array[0] || array[1]); 19 | tempPrint(array.length); 20 | } 21 | 22 | private static void playWithArrayOfBytes(byte[] array) { 23 | tempPrint(array[0] | array[1]); 24 | tempPrint(array.length); 25 | } 26 | 27 | private static void playWithArrayOfChars(char[] array) { 28 | tempPrint(array[0] > array[1] ? array[0] : array[1]); 29 | tempPrint(array.length); 30 | } 31 | 32 | private static void playWithArrayOfShorts(short[] array) { 33 | tempPrint(array[0] - array[1]); 34 | tempPrint(array.length); 35 | } 36 | 37 | private static void playWithArrayOfInts(int[] array) { 38 | tempPrint(array[0] * array[1]); 39 | tempPrint(array.length); 40 | } 41 | 42 | private static void playWithArrayOfLongs(long[] array) { 43 | tempPrint(array[0] / array[1]); 44 | tempPrint(array.length); 45 | } 46 | 47 | private static void playWithArrayOfFloats(float[] array) { 48 | tempPrint(array[0] + array[1]); 49 | tempPrint(array.length); 50 | } 51 | 52 | private static void playWithArrayOfDoubles(double[] array) { 53 | tempPrint(array[0] * array[1]); 54 | tempPrint(array.length); 55 | } 56 | 57 | private static void copyArrays() { 58 | int[] source = new int[]{1, 2, 3, 4}; 59 | int[] dest = new int[]{0, 0, 0, 0, 0}; 60 | System.arraycopy(source, 1, dest, 1, 3); 61 | tempPrint(dest[0]); 62 | tempPrint(dest[1]); 63 | tempPrint(dest[2]); 64 | tempPrint(dest[3]); 65 | tempPrint(dest[4]); 66 | tempPrint(dest.length); 67 | } 68 | 69 | private static native void tempPrint(boolean value); 70 | 71 | private static native void tempPrint(int value); 72 | 73 | private static native void tempPrint(long value); 74 | 75 | private static native void tempPrint(float value); 76 | 77 | private static native void tempPrint(double value); 78 | } 79 | -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/NumericTypes.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/NumericTypes.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/NumericTypes.java: -------------------------------------------------------------------------------- 1 | package rjvm; 2 | 3 | public class NumericTypes { 4 | public static void main(String[] args) { 5 | shortAndCharMath((short) 1, (char) 2); 6 | floatAndIntMath(1, 2.45f); 7 | longMath(1, 3); 8 | doubleMath(1, 3.45); 9 | negate(returnOneInt(), returnOneLong(), returnOneFloat(), returnOneDouble()); 10 | logicalShifts(4, 4); 11 | } 12 | 13 | private static void shortAndCharMath(short s, char c) { 14 | tempPrint(((short) (s + c))); 15 | } 16 | 17 | private static void floatAndIntMath(int i, float f) { 18 | tempPrint(i + f); 19 | tempPrint((int) (i + f)); 20 | tempPrint((long) (i + f)); 21 | tempPrint((double) (i + f)); 22 | } 23 | 24 | private static void longMath(int i, long l) { 25 | tempPrint(l - i); 26 | tempPrint((int) (l - i)); 27 | tempPrint((float) (l - i)); 28 | tempPrint((double) (l - i)); 29 | } 30 | 31 | private static void doubleMath(int i, double d) { 32 | tempPrint(i + d); 33 | tempPrint((int) (i + d)); 34 | tempPrint((float) (i + d)); 35 | tempPrint((long) (i + d)); 36 | } 37 | 38 | private static void negate(int i, long l, float f, double d) { 39 | tempPrint(-i); 40 | tempPrint(-l); 41 | tempPrint(-f); 42 | tempPrint(-d); 43 | } 44 | 45 | private static void logicalShifts(int i, long l) { 46 | tempPrint(i >> 2); 47 | tempPrint((-i) >>> 2); 48 | tempPrint(i << 1); 49 | 50 | tempPrint(l >> 2); 51 | tempPrint((-l) >>> 2); 52 | tempPrint(l << 1); 53 | } 54 | 55 | private static int returnOneInt() { 56 | return 1; 57 | } 58 | 59 | private static long returnOneLong() { 60 | return 1; 61 | } 62 | 63 | private static float returnOneFloat() { 64 | return 1; 65 | } 66 | 67 | private static long returnOneDouble() { 68 | return 1; 69 | } 70 | 71 | private static native void tempPrint(int value); 72 | 73 | private static native void tempPrint(long value); 74 | 75 | private static native void tempPrint(float value); 76 | 77 | private static native void tempPrint(double value); 78 | } 79 | -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/ObjectArrays$Square.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/ObjectArrays$Square.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/ObjectArrays.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/ObjectArrays.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/ObjectArrays.java: -------------------------------------------------------------------------------- 1 | package rjvm; 2 | 3 | public class ObjectArrays { 4 | public static void main(String[] args) { 5 | Square[] squares = new Square[]{ 6 | new Square(1), 7 | new Square(2), 8 | null, 9 | }; 10 | 11 | int totalArea = 0; 12 | for (int i = 0; i < squares.length; ++i) { 13 | if (squares[i] != null) { 14 | totalArea += squares[i].area(); 15 | } 16 | } 17 | tempPrint(totalArea); 18 | } 19 | 20 | private static native void tempPrint(int value); 21 | 22 | public static final class Square { 23 | private final int side; 24 | 25 | public Square(int side) { 26 | this.side = side; 27 | } 28 | 29 | public int area() { 30 | return side * side; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/SimpleMain$Generator.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/SimpleMain$Generator.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/SimpleMain.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/SimpleMain.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/SimpleMain.java: -------------------------------------------------------------------------------- 1 | package rjvm; 2 | 3 | public class SimpleMain { 4 | public static void main(String[] args) { 5 | Generator g = new Generator(0, 3); 6 | tempPrint(g.next()); 7 | tempPrint(g.next()); 8 | } 9 | 10 | private static native void tempPrint(int value); 11 | 12 | static class Generator { 13 | private int curr; 14 | private final int inc; 15 | 16 | Generator(int start, int inc) { 17 | this.curr = start; 18 | this.inc = inc; 19 | } 20 | 21 | public int next() { 22 | this.curr += this.inc; 23 | return this.curr; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/StackTracePrinting.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/StackTracePrinting.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/StackTracePrinting.java: -------------------------------------------------------------------------------- 1 | package rjvm; 2 | 3 | class StackTracePrinting { 4 | public static void main(String[] args) { 5 | Throwable ex = new Exception(); 6 | StackTraceElement[] stackTrace = ex.getStackTrace(); 7 | for (StackTraceElement element : stackTrace) { 8 | tempPrint( 9 | element.getClassName() + "::" + element.getMethodName() + " - " + 10 | element.getFileName() + ":" + element.getLineNumber()); 11 | } 12 | } 13 | 14 | private static native void tempPrint(String value); 15 | } -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/Statics$MyObject.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/Statics$MyObject.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/Statics.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/Statics.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/Statics.java: -------------------------------------------------------------------------------- 1 | package rjvm; 2 | 3 | public class Statics { 4 | public static void main(String[] args) { 5 | MyObject myObject1 = new MyObject(1); 6 | MyObject myObject2 = new MyObject(2); 7 | tempPrint(myObject1.weirdFunForTesting()); 8 | tempPrint(myObject2.weirdFunForTesting()); 9 | } 10 | 11 | private static final class MyObject { 12 | private static int nextId = 1; 13 | private final int value; 14 | private final int id; 15 | 16 | public MyObject(int value) { 17 | this.value = value; 18 | this.id = MyObject.nextId++; 19 | } 20 | 21 | public int weirdFunForTesting() { 22 | return MyObject.nextId * 100 + this.id * 10 + this.value; 23 | } 24 | } 25 | 26 | private static native void tempPrint(int value); 27 | } 28 | -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/Strings.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/Strings.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/Strings.java: -------------------------------------------------------------------------------- 1 | package rjvm; 2 | 3 | public class Strings { 4 | public static void main(String[] args) { 5 | sayHello("Andrea", 1985); 6 | } 7 | 8 | private static void sayHello(String name, int year) { 9 | tempPrint("Hello, " + name + ", you were born in " + year); 10 | } 11 | 12 | private static native void tempPrint(String value); 13 | } 14 | -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/SuperClasses$BaseClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/SuperClasses$BaseClass.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/SuperClasses$DerivedClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/SuperClasses$DerivedClass.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/SuperClasses.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/rjvm/SuperClasses.class -------------------------------------------------------------------------------- /vm/tests/resources/rjvm/SuperClasses.java: -------------------------------------------------------------------------------- 1 | package rjvm; 2 | 3 | public class SuperClasses { 4 | public static void main(String[] args) { 5 | rjvm.SuperClasses.DerivedClass d = new rjvm.SuperClasses.DerivedClass(2); 6 | d.setBaseValue(1); 7 | tempPrint(d.sum()); 8 | } 9 | 10 | private static native void tempPrint(int value); 11 | 12 | static abstract class BaseClass { 13 | protected int baseValue = 0; 14 | 15 | void setBaseValue(int value) { 16 | this.baseValue = value; 17 | } 18 | 19 | int sum() { 20 | return baseValue + derivedClassValue(); 21 | } 22 | 23 | protected abstract int derivedClassValue(); 24 | } 25 | 26 | static class DerivedClass extends rjvm.SuperClasses.BaseClass { 27 | private final int derivedValue; 28 | 29 | public DerivedClass(int derivedValue) { 30 | this.derivedValue = derivedValue; 31 | } 32 | 33 | @Override 34 | int sum() { 35 | return 1 + super.sum(); 36 | } 37 | 38 | @Override 39 | protected int derivedClassValue() { 40 | return this.derivedValue; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /vm/tests/resources/sample.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreabergia/rjvm/201a6b1b4f9aa52ed297332ae7a1822ecc0f253c/vm/tests/resources/sample.jar -------------------------------------------------------------------------------- /vm_cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rjvm_vm_cli" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | rjvm_vm = { path = "../vm" } 8 | env_logger = "*" 9 | clap = { version = "4.2.5", features = ["derive"] } 10 | -------------------------------------------------------------------------------- /vm_cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | use rjvm_vm::{ 4 | array::Array, 5 | array_entry_type::ArrayEntryType, 6 | call_stack::CallStack, 7 | class_and_method::ClassAndMethod, 8 | exceptions::MethodCallFailed, 9 | java_objects_creation::new_java_lang_string_object, 10 | value::Value, 11 | vm::{Vm, DEFAULT_MAX_MEMORY_MB_STR, ONE_MEGABYTE}, 12 | vm_error::VmError, 13 | }; 14 | 15 | #[derive(Parser, Debug)] 16 | #[command(author, version, about, long_about = None)] 17 | struct Args { 18 | /// Class path. Use colon (:) as separator for entries 19 | #[arg(short, long)] 20 | classpath: Option, 21 | 22 | /// Class name to execute 23 | class_name: String, 24 | 25 | /// Maximum memory to use in MB 26 | #[arg(short, long, default_value = DEFAULT_MAX_MEMORY_MB_STR)] 27 | maximum_mb_of_memory: usize, 28 | 29 | /// Java program arguments 30 | java_program_arguments: Vec, 31 | } 32 | 33 | fn main() { 34 | let args = Args::parse(); 35 | env_logger::init_from_env( 36 | env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), 37 | ); 38 | 39 | let result = run(args); 40 | match result { 41 | Ok(exit_code) => std::process::exit(exit_code), 42 | Err(err) => { 43 | eprintln!("{err}"); 44 | std::process::exit(-1); 45 | } 46 | } 47 | } 48 | 49 | fn append_classpath(vm: &mut Vm, args: &Args) -> Result<(), String> { 50 | if let Some(classpath) = &args.classpath { 51 | vm.append_class_path(classpath) 52 | .map_err(|err| err.to_string())?; 53 | } 54 | Ok(()) 55 | } 56 | 57 | fn resolve_class_and_main_method<'a>( 58 | vm: &mut Vm<'a>, 59 | args: &Args, 60 | ) -> Result<(&'a mut CallStack<'a>, ClassAndMethod<'a>), String> { 61 | let call_stack = vm.allocate_call_stack(); 62 | let main_method = vm 63 | .resolve_class_method( 64 | call_stack, 65 | &args.class_name, 66 | "main", 67 | "([Ljava/lang/String;)V", 68 | ) 69 | .map_err(|v| match v { 70 | MethodCallFailed::InternalError(VmError::ClassNotFoundException(name)) => { 71 | format!("class not found: {name}") 72 | } 73 | MethodCallFailed::InternalError(VmError::MethodNotFoundException(..)) => { 74 | "class does not contain a valid
method".to_string() 75 | } 76 | _ => format!("unexpected error: {:?}", v), 77 | })?; 78 | Ok((call_stack, main_method)) 79 | } 80 | 81 | fn run(args: Args) -> Result { 82 | let mut vm = Vm::new(args.maximum_mb_of_memory * ONE_MEGABYTE); 83 | append_classpath(&mut vm, &args)?; 84 | 85 | let (call_stack, main_method) = resolve_class_and_main_method(&mut vm, &args)?; 86 | 87 | let main_args = allocate_java_args(&mut vm, call_stack, &args.java_program_arguments) 88 | .map_err(|err| format!("{err:?}"))?; 89 | let main_result = vm 90 | .invoke(call_stack, main_method, None, vec![main_args]) 91 | .map_err(|v| format!("execution error: {:?}", v))?; 92 | 93 | match main_result { 94 | None => Ok(0), 95 | Some(v) => Err(format!( 96 | "
method should be void, but returned the value: {v:?}", 97 | )), 98 | } 99 | } 100 | 101 | fn allocate_java_args<'a>( 102 | vm: &mut Vm<'a>, 103 | call_stack: &mut CallStack<'a>, 104 | command_line_args: &[String], 105 | ) -> Result, MethodCallFailed<'a>> { 106 | let class_id_java_lang_string = vm.get_or_resolve_class(call_stack, "java/lang/String")?.id; 107 | 108 | let strings: Result>, MethodCallFailed<'a>> = command_line_args 109 | .iter() 110 | .map(|s| new_java_lang_string_object(vm, call_stack, s).map(Value::Object)) 111 | .collect(); 112 | 113 | let strings = strings?; 114 | let array = vm.new_array( 115 | ArrayEntryType::Object(class_id_java_lang_string), 116 | strings.len(), 117 | ); 118 | 119 | for (index, string) in strings.into_iter().enumerate() { 120 | array.set_element(index, string)?; 121 | } 122 | Ok(Value::Object(array)) 123 | } 124 | --------------------------------------------------------------------------------