├── .gitattributes ├── .gitignore ├── .gitmodules ├── README.md ├── build.zig ├── src ├── interpreter │ ├── ClassResolver.zig │ ├── Heap.zig │ ├── Interpreter.zig │ ├── Object.zig │ ├── Stack.zig │ ├── StackFrame.zig │ ├── StaticPool.zig │ ├── array.zig │ ├── primitives.zig │ └── utils.zig └── main.zig └── test └── src └── jaztest ├── Benjamin.java └── IZiguana.java /.gitattributes: -------------------------------------------------------------------------------- 1 | *.zig text eol=lf 2 | *.java text eol=lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /zig-cache 2 | /zig-out 3 | **.class 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libs/cf"] 2 | path = libs/cf 3 | url = https://github.com/zig-java/cf 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jaz 2 | 3 | **Ja**va in **Z**ig. 4 | 5 | Parses Java class files and bytecode. 6 | 7 | ## Taking it for a spin 8 | 9 | To try out jaz for yourself, install jvm 16, then run the following commands: 10 | ```bash 11 | # Compiles Java source 12 | javac test/src/jaztest/*.java 13 | 14 | # Adds user path to javastd 15 | echo "pub const conf = .{.javastd_path = \"/path/to/javastd\"};" > src/conf.zig 16 | 17 | # Runs demo 18 | zig build run 19 | ``` 20 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(b: *std.build.Builder) void { 4 | // Standard target options allows the person running `zig build` to choose 5 | // what target to build for. Here we do not override the defaults, which 6 | // means any target is allowed, and the default is native. Other options 7 | // for restricting supported target set are available. 8 | const target = b.standardTargetOptions(.{}); 9 | 10 | // Standard release options allow the person running `zig build` to select 11 | // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. 12 | const mode = b.standardReleaseOptions(); 13 | 14 | const exe = b.addExecutable("jaz", "src/main.zig"); 15 | exe.addPackagePath("cf", "libs/cf/cf.zig"); 16 | exe.setTarget(target); 17 | exe.setBuildMode(mode); 18 | exe.install(); 19 | 20 | const run_cmd = exe.run(); 21 | run_cmd.step.dependOn(b.getInstallStep()); 22 | if (b.args) |args| { 23 | run_cmd.addArgs(args); 24 | } 25 | 26 | const run_step = b.step("run", "Run the app"); 27 | run_step.dependOn(&run_cmd.step); 28 | 29 | var main_tests = b.addTest("src/main.zig"); 30 | main_tests.setBuildMode(mode); 31 | 32 | const test_step = b.step("test", "Run library tests"); 33 | test_step.dependOn(&main_tests.step); 34 | } 35 | -------------------------------------------------------------------------------- /src/interpreter/ClassResolver.zig: -------------------------------------------------------------------------------- 1 | //! Resolves classes 😎 2 | 3 | const std = @import("std"); 4 | const cf = @import("cf"); 5 | const ClassFile = cf.ClassFile; 6 | 7 | const ClassResolver = @This(); 8 | 9 | allocator: std.mem.Allocator, 10 | /// Transforms "a/b/c" -> "/java/path/a/b/c.class" 11 | absolute_path_map: std.StringHashMapUnmanaged([]const u8) = .{}, 12 | /// Transforms "a/b/c" -> ClassFile(...) 13 | class_file_map: std.StringHashMapUnmanaged(*ClassFile) = .{}, 14 | 15 | pub fn init(allocator: std.mem.Allocator, classpath_dirs: [][]const u8) !ClassResolver { 16 | var class_resolver = ClassResolver{ .allocator = allocator }; 17 | 18 | for (classpath_dirs) |path| { 19 | try populateAbsolutePathMap(&class_resolver, path, ""); 20 | } 21 | 22 | return class_resolver; 23 | } 24 | 25 | fn populateAbsolutePathMap( 26 | resolver: *ClassResolver, 27 | path: []const u8, 28 | slashes: []const u8, 29 | ) !void { 30 | var iterable = try std.fs.openIterableDirAbsolute(path, .{}); 31 | defer iterable.close(); 32 | 33 | var iterator = iterable.iterate(); 34 | 35 | while (try iterator.next()) |it| { 36 | const sub_path = try std.fs.path.join(resolver.allocator, &.{ path, it.name }); 37 | const sub_slashes = if (slashes.len == 0) it.name else try std.mem.join(resolver.allocator, "/", &.{ 38 | slashes, 39 | if (it.kind == .File and std.mem.endsWith(u8, it.name, ".class")) 40 | it.name[0 .. it.name.len - 6] 41 | else 42 | it.name[0..it.name.len], 43 | }); 44 | 45 | switch (it.kind) { 46 | .Directory => { 47 | try resolver.populateAbsolutePathMap(sub_path, sub_slashes); 48 | }, 49 | .File => { 50 | try resolver.absolute_path_map.put(resolver.allocator, sub_slashes, sub_path); 51 | }, 52 | else => { 53 | resolver.allocator.free(sub_slashes); 54 | std.log.warn("Unknown iterator entry: {any}", .{it}); 55 | }, 56 | } 57 | } 58 | } 59 | 60 | /// Resolve Class from "java.abc.def" notations 61 | pub fn resolve(resolver: *ClassResolver, slashes: []const u8) !?*ClassFile { 62 | return resolver.class_file_map.get(slashes) orelse { 63 | var file = try std.fs.openFileAbsolute(resolver.absolute_path_map.get(slashes) orelse return null, .{}); 64 | defer file.close(); 65 | 66 | var class_file = try resolver.allocator.create(ClassFile); 67 | class_file.* = try ClassFile.decode(resolver.allocator, file.reader()); 68 | 69 | try resolver.class_file_map.put( 70 | resolver.allocator, 71 | try resolver.allocator.dupe(u8, slashes), 72 | class_file, 73 | ); 74 | 75 | return class_file; 76 | }; 77 | } 78 | 79 | pub fn deinit(self: ClassResolver) void { 80 | for (self.classpath_dirs) |d| d.close(); 81 | self.allocator.free(self.classpath_dirs); 82 | } 83 | -------------------------------------------------------------------------------- /src/interpreter/Heap.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Object = @import("Object.zig"); 3 | const array = @import("array.zig"); 4 | const primitives = @import("primitives.zig"); 5 | const ClassResolver = @import("ClassResolver.zig"); 6 | 7 | const cf = @import("cf"); 8 | const ClassFile = cf.ClassFile; 9 | 10 | const Heap = @This(); 11 | 12 | allocator: std.mem.Allocator, 13 | heap_values: std.ArrayList(HeapValue), 14 | first_empty_index: ?usize = null, 15 | 16 | pub const HeapValue = union(enum) { object: Object, array: array.Array, empty: void }; 17 | 18 | pub fn init(allocator: std.mem.Allocator) Heap { 19 | return .{ .allocator = allocator, .heap_values = std.ArrayList(HeapValue).init(allocator) }; 20 | } 21 | 22 | const NewHeapValue = struct { value: *HeapValue, index: usize }; 23 | fn new(self: *Heap) !NewHeapValue { 24 | if (self.first_empty_index) |fei| { 25 | var i: usize = fei; 26 | while (i < self.heap_values.items.len) : (i += 1) { 27 | if (self.heap_values.items[i] == .empty) { 28 | self.first_empty_index = i; 29 | return NewHeapValue{ .value = &self.heap_values.items[fei], .index = fei }; 30 | } 31 | } 32 | 33 | self.first_empty_index = null; 34 | return NewHeapValue{ .value = &self.heap_values.items[fei], .index = fei }; 35 | } else return NewHeapValue{ .value = try self.heap_values.addOne(), .index = self.heap_values.items.len - 1 }; 36 | } 37 | 38 | pub fn get(self: *Heap, reference: usize) *HeapValue { 39 | if (reference == 0) @panic("Attempt to use null reference!"); 40 | return &self.heap_values.items[reference - 1]; 41 | } 42 | 43 | pub fn newObject(self: *Heap, class_file: *const ClassFile, class_resolver: *ClassResolver) !usize { 44 | var obj = try Object.initNonStatic(self.allocator, class_file, class_resolver); 45 | var n = try self.new(); 46 | n.value.* = .{ .object = obj }; 47 | return n.index + 1; 48 | } 49 | 50 | pub fn getObject(self: *Heap, reference: usize) *Object { 51 | return &self.heap_values.items[reference - 1].object; 52 | } 53 | 54 | pub fn newArray(self: *Heap, kind: array.ArrayKind, size: primitives.int) !usize { 55 | var arr = try array.Array.init(self.allocator, kind, size); 56 | var n = try self.new(); 57 | n.value.* = .{ .array = arr }; 58 | return n.index + 1; 59 | } 60 | 61 | pub fn getArray(self: *Heap, reference: usize) *array.Array { 62 | return &self.heap_values.items[reference - 1].array; 63 | } 64 | 65 | pub fn free(self: *Heap, reference: usize) void { 66 | switch (self.heap_values.items[reference - 1]) { 67 | .array => |arr| { 68 | arr.deinit(); 69 | }, 70 | .empty => unreachable, 71 | } 72 | 73 | self.heap_values.items[reference - 1] = .empty; 74 | if (self.first_empty_index) |i| { 75 | if (reference - 1 < i) self.first_empty_index = reference - 1; 76 | } else { 77 | self.first_empty_index = reference - 1; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/interpreter/Interpreter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const Heap = @import("Heap.zig"); 4 | const array = @import("array.zig"); 5 | const utils = @import("utils.zig"); 6 | const Object = @import("Object.zig"); 7 | const StaticPool = @import("StaticPool.zig"); 8 | const StackFrame = @import("StackFrame.zig"); 9 | const primitives = @import("primitives.zig"); 10 | const ClassResolver = @import("ClassResolver.zig"); 11 | 12 | const cf = @import("cf"); 13 | 14 | const opcodes = cf.bytecode.ops; 15 | const MethodInfo = cf.MethodInfo; 16 | const ClassFile = cf.ClassFile; 17 | const attributes = cf.attributes; 18 | const descriptors = cf.descriptors; 19 | const ConstantPool = cf.ConstantPool; 20 | 21 | const Interpreter = @This(); 22 | 23 | allocator: std.mem.Allocator, 24 | heap: Heap, 25 | static_pool: StaticPool, 26 | class_resolver: *ClassResolver, 27 | 28 | pub fn init(allocator: std.mem.Allocator, class_resolver: *ClassResolver) Interpreter { 29 | return .{ .allocator = allocator, .heap = Heap.init(allocator), .static_pool = StaticPool.init(allocator), .class_resolver = class_resolver }; 30 | } 31 | 32 | pub fn call(self: *Interpreter, path: []const u8, args: anytype) !primitives.PrimitiveValue { 33 | std.log.info("{s}", .{path}); 34 | var class_file = (try self.class_resolver.resolve(path[0..std.mem.lastIndexOf(u8, path, "/").?])).?; 35 | var method_name = path[std.mem.lastIndexOf(u8, path, "/").? + 1 ..]; 36 | var margs: [std.meta.fields(@TypeOf(args)).len]primitives.PrimitiveValue = undefined; 37 | 38 | var method_info: ?MethodInfo = null; 39 | for (class_file.methods.items) |*m| { 40 | if (!std.mem.eql(u8, m.getName().bytes, method_name)) continue; 41 | 42 | method_info = m.*; 43 | } 44 | 45 | inline for (std.meta.fields(@TypeOf(args))) |field, i| { 46 | margs[i] = primitives.PrimitiveValue.fromNative(@field(args, field.name)); 47 | } 48 | 49 | return self.interpret(class_file, method_name, &margs); 50 | } 51 | 52 | fn useStaticClass(self: *Interpreter, class_name: []const u8) !void { 53 | if (!self.static_pool.hasClass(class_name)) { 54 | var clname = try std.mem.concat(self.allocator, u8, &.{ class_name, "/" }); 55 | defer self.allocator.free(clname); 56 | 57 | std.log.info("STATIC CLASS '{s}' {d}", .{ clname, clname[0] }); 58 | _ = try self.static_pool.addClass(class_name, (try self.class_resolver.resolve(class_name)).?); 59 | 60 | _ = self.call(clname, .{}) catch |err| switch (err) { 61 | error.ClassNotFound => @panic("Big problem!!!"), 62 | error.MethodNotFound => {}, 63 | else => unreachable, 64 | }; 65 | } 66 | } 67 | 68 | pub fn newObject(self: *Interpreter, class_name: []const u8) !primitives.reference { 69 | return try self.heap.newObject((try self.class_resolver.resolve(class_name)).?, self.class_resolver); 70 | } 71 | 72 | pub fn new(self: *Interpreter, class_name: []const u8, args: anytype) !primitives.reference { 73 | var inits = try std.mem.concat(self.allocator, u8, &.{ class_name, "." }); 74 | defer self.allocator.free(inits); 75 | 76 | var object = try self.newObject(class_name); 77 | _ = try self.call(inits, .{object} ++ args); 78 | return object; 79 | } 80 | 81 | pub fn findMethod(self: *Interpreter, class_file: *const ClassFile, method_name: []const u8, args: []primitives.PrimitiveValue) !?*const MethodInfo { 82 | method_search: for (class_file.methods.items) |*method| { 83 | if (!std.mem.eql(u8, method.getName().bytes, method_name)) continue; 84 | var method_descriptor_str = method.getDescriptor().bytes; 85 | var method_descriptor = try descriptors.parseString(self.allocator, method_descriptor_str); 86 | 87 | if (method_descriptor.method.parameters.len != args.len - if (method.access_flags.static) @as(usize, 0) else @as(usize, 1)) continue; 88 | 89 | var tindex: usize = 0; 90 | var toffset = if (method.access_flags.static) @as(usize, 0) else @as(usize, 1); 91 | 92 | while (tindex < method_descriptor.method.parameters.len) : (tindex += 1) { 93 | var param = method_descriptor.method.parameters[tindex]; 94 | switch (param.*) { 95 | .int => if (args[tindex + toffset] != .int) continue :method_search, 96 | .object => |o| { 97 | if (args[tindex + toffset].isNull()) continue :method_search; 98 | switch (self.heap.get(args[tindex + toffset].reference).*) { 99 | .object => |o2| { 100 | var cn = try o2.getClassName(); 101 | // defer self.allocator.free(cn); 102 | if (!std.mem.eql(u8, o, cn)) { 103 | continue :method_search; 104 | } 105 | }, 106 | else => continue :method_search, 107 | } 108 | }, 109 | .array => |a| { 110 | switch (self.heap.get(args[tindex + toffset].reference).*) { 111 | .array => |a2| { 112 | // if (a2.) 113 | switch (a2) { 114 | .byte => if (a.* != .byte) continue :method_search, 115 | else => continue :method_search, 116 | } 117 | }, 118 | else => continue :method_search, 119 | } 120 | }, 121 | else => unreachable, 122 | } 123 | } 124 | 125 | return method; 126 | } 127 | 128 | return null; 129 | } 130 | 131 | fn interpret( 132 | self: *Interpreter, 133 | class_file: *const ClassFile, 134 | method_name: []const u8, 135 | args: []primitives.PrimitiveValue, 136 | ) anyerror!primitives.PrimitiveValue { 137 | var descriptor_buf = std.ArrayList(u8).init(self.allocator); 138 | defer descriptor_buf.deinit(); 139 | 140 | var method = (try self.findMethod(class_file, method_name, args)) orelse return error.MethodNotFound; 141 | 142 | descriptor_buf.shrinkRetainingCapacity(0); 143 | try utils.formatMethod(self.allocator, class_file, method, descriptor_buf.writer()); 144 | std.log.info("{d} {d}", .{ @ptrToInt(class_file.constant_pool), @ptrToInt(class_file.constant_pool.get(class_file.this_class).class.constant_pool) }); 145 | std.log.info("method: {s} {s} ({d} attrs)", .{ class_file.constant_pool.get(class_file.this_class).class.getName().bytes, descriptor_buf.items, method.attributes.items.len }); 146 | 147 | // TODO: More specific 148 | if (std.mem.eql(u8, method.getName().bytes, "registerNatives")) { 149 | std.log.info("CALLED REGISTER NATIVES", .{}); 150 | return .void; 151 | } 152 | 153 | // TODO: See descriptors.zig 154 | for (method.attributes.items) |*att| { 155 | // var data = try att.readData(self.allocator, class_file); 156 | std.log.info((" " ** 4) ++ "method attribute: '{s}'", .{@tagName(att.*)}); 157 | 158 | switch (att.*) { 159 | .code => |code_attribute| { 160 | var stack_frame = StackFrame.init(self.allocator, class_file); 161 | defer stack_frame.deinit(); 162 | 163 | try stack_frame.local_variables.appendSlice(args); 164 | 165 | var fbs = std.io.fixedBufferStream(code_attribute.code.items); 166 | var fbs_reader = fbs.reader(); 167 | 168 | var opcode = try opcodes.Operation.decode(self.allocator, fbs_reader); 169 | 170 | while (true) { 171 | std.log.info((" " ** 8) ++ "{any}", .{opcode}); 172 | 173 | // TODO: Rearrange everything according to https://docs.oracle.com/javase/specs/jvms/se16/html/jvms-7.html 174 | switch (opcode) { 175 | // Constants 176 | // See https://docs.oracle.com/javase/specs/jvms/se16/html/jvms-7.html, subsection "Constants" for order 177 | .nop => {}, 178 | .aconst_null => try stack_frame.operand_stack.push(.{ .reference = 0 }), 179 | 180 | .iconst_m1 => try stack_frame.operand_stack.push(.{ .int = -1 }), 181 | .iconst_0 => try stack_frame.operand_stack.push(.{ .int = 0 }), 182 | .iconst_1 => try stack_frame.operand_stack.push(.{ .int = 1 }), 183 | .iconst_2 => try stack_frame.operand_stack.push(.{ .int = 2 }), 184 | .iconst_3 => try stack_frame.operand_stack.push(.{ .int = 3 }), 185 | .iconst_4 => try stack_frame.operand_stack.push(.{ .int = 4 }), 186 | .iconst_5 => try stack_frame.operand_stack.push(.{ .int = 5 }), 187 | 188 | .lconst_0 => try stack_frame.operand_stack.push(.{ .long = 0 }), 189 | .lconst_1 => try stack_frame.operand_stack.push(.{ .long = 1 }), 190 | 191 | .fconst_0 => try stack_frame.operand_stack.push(.{ .float = 0 }), 192 | .fconst_1 => try stack_frame.operand_stack.push(.{ .float = 1 }), 193 | .fconst_2 => try stack_frame.operand_stack.push(.{ .float = 2 }), 194 | 195 | .dconst_0 => try stack_frame.operand_stack.push(.{ .double = 0 }), 196 | .dconst_1 => try stack_frame.operand_stack.push(.{ .double = 1 }), 197 | 198 | .bipush => |value| try stack_frame.operand_stack.push(.{ .int = value }), 199 | .sipush => |value| try stack_frame.operand_stack.push(.{ .int = value }), 200 | 201 | .ldc => |index| { 202 | switch (stack_frame.class_file.constant_pool.get(index)) { 203 | .integer => |f| try stack_frame.operand_stack.push(.{ .int = @bitCast(primitives.int, f.bytes) }), 204 | .float => |f| try stack_frame.operand_stack.push(.{ .float = @bitCast(primitives.float, f.bytes) }), 205 | .string => |s| { 206 | var utf8 = class_file.constant_pool.get(s.string_index).utf8; 207 | var ref = try self.heap.newArray(.byte, @intCast(primitives.int, utf8.bytes.len)); 208 | var arr = self.heap.getArray(ref); 209 | 210 | std.mem.copy(i8, arr.byte.slice, @ptrCast([*]const i8, utf8.bytes.ptr)[0..utf8.bytes.len]); 211 | 212 | try stack_frame.operand_stack.push(.{ .reference = try self.new("java.lang.String", .{ref}) }); 213 | }, 214 | .class => |class| { 215 | var class_name_slashes = class.getName().bytes; 216 | var class_name = class_name_slashes; 217 | // defer self.allocator.free(class_name); 218 | 219 | try stack_frame.operand_stack.push(.{ .reference = try self.heap.newObject((try self.class_resolver.resolve(class_name)).?, self.class_resolver) }); 220 | }, 221 | else => { 222 | std.log.info("{any}", .{stack_frame.class_file.constant_pool.get(index)}); 223 | unreachable; 224 | }, 225 | } 226 | }, 227 | .ldc_w => |index| { 228 | switch (stack_frame.class_file.constant_pool.get(index)) { 229 | .integer => |f| try stack_frame.operand_stack.push(.{ .int = @bitCast(primitives.int, f.bytes) }), 230 | .float => |f| try stack_frame.operand_stack.push(.{ .float = @bitCast(primitives.float, f.bytes) }), 231 | .string => |s| { 232 | var utf8 = class_file.constant_pool.get(s.string_index).utf8; 233 | var ref = try self.heap.newArray(.byte, @intCast(primitives.int, utf8.bytes.len)); 234 | var arr = self.heap.getArray(ref); 235 | 236 | std.mem.copy(i8, arr.byte.slice, @ptrCast([*]const i8, utf8.bytes.ptr)[0..utf8.bytes.len]); 237 | 238 | try stack_frame.operand_stack.push(.{ .reference = try self.new("java.lang.String", .{ref}) }); 239 | }, 240 | else => { 241 | std.log.info("{any}", .{stack_frame.class_file.constant_pool.get(index)}); 242 | unreachable; 243 | }, 244 | } 245 | }, 246 | .ldc2_w => |index| { 247 | switch (stack_frame.class_file.constant_pool.get(index)) { 248 | .double => |d| try stack_frame.operand_stack.push(.{ .double = @bitCast(primitives.double, d.bytes) }), 249 | .long => |l| try stack_frame.operand_stack.push(.{ .long = @bitCast(primitives.long, l.bytes) }), 250 | else => unreachable, 251 | } 252 | }, 253 | 254 | // Loads 255 | // See https://docs.oracle.com/javase/specs/jvms/se16/html/jvms-7.html, subsection "Loads" for order 256 | 257 | .iinc => |iinc| stack_frame.local_variables.items[iinc.index].int += iinc.@"const", 258 | 259 | // zig fmt: off 260 | inline 261 | .iload, .iload_0, .iload_1, .iload_2, .iload_3, 262 | .fload, .fload_0, .fload_1, .fload_2, .fload_3, 263 | .dload, .dload_0, .dload_1, .dload_2, .dload_3, 264 | .aload, .aload_0, .aload_1, .aload_2, .aload_3, 265 | // zig fmt: on 266 | => |body_index, current| { 267 | const kind = comptime primitives.PrimitiveValueKind.fromSingleCharRepr(@tagName(current)[0]); 268 | const index = comptime if (@tagName(current).len < 6) body_index else std.fmt.parseInt(usize, @tagName(current)[6..], 10) catch unreachable; 269 | 270 | try stack_frame.operand_stack.push(@unionInit( 271 | primitives.PrimitiveValue, 272 | @tagName(kind), 273 | @field( 274 | stack_frame.local_variables.items[index], 275 | @tagName(kind), 276 | ), 277 | )); 278 | }, 279 | 280 | // zig fmt: off 281 | inline 282 | .istore, .istore_0, .istore_1, .istore_2, .istore_3, 283 | .fstore, .fstore_0, .fstore_1, .fstore_2, .fstore_3, 284 | .dstore, .dstore_0, .dstore_1, .dstore_2, .dstore_3, 285 | .astore, .astore_0, .astore_1, .astore_2, .astore_3, 286 | // zig fmt: on 287 | => |body_index, current| { 288 | const kind = comptime primitives.PrimitiveValueKind.fromSingleCharRepr(@tagName(current)[0]); 289 | const index = comptime if (@tagName(current).len < 7) body_index else std.fmt.parseInt(usize, @tagName(current)[7..], 10) catch unreachable; 290 | 291 | try stack_frame.setLocalVariable(index, @unionInit( 292 | primitives.PrimitiveValue, 293 | @tagName(kind), 294 | @field( 295 | stack_frame.operand_stack.pop(), 296 | @tagName(kind), 297 | ), 298 | )); 299 | }, 300 | 301 | .iadd => try stack_frame.operand_stack.push(.{ .int = stack_frame.operand_stack.pop().int + stack_frame.operand_stack.pop().int }), 302 | .imul => try stack_frame.operand_stack.push(.{ .int = stack_frame.operand_stack.pop().int * stack_frame.operand_stack.pop().int }), 303 | .isub => { 304 | var stackvals = stack_frame.operand_stack.popToStruct(struct { a: primitives.int, b: primitives.int }); 305 | try stack_frame.operand_stack.push(.{ .int = stackvals.a - stackvals.b }); 306 | }, 307 | .idiv => { 308 | var stackvals = stack_frame.operand_stack.popToStruct(struct { numerator: primitives.int, denominator: primitives.int }); 309 | try stack_frame.operand_stack.push(.{ .int = @divTrunc(stackvals.numerator, stackvals.denominator) }); 310 | }, 311 | // bitwise ops 312 | .iand => { 313 | var stackvals = stack_frame.operand_stack.popToStruct(struct { numerator: primitives.int, denominator: primitives.int }); 314 | try stack_frame.operand_stack.push(.{ .int = stackvals.numerator & stackvals.denominator }); 315 | }, 316 | .ior => { 317 | var stackvals = stack_frame.operand_stack.popToStruct(struct { numerator: primitives.int, denominator: primitives.int }); 318 | try stack_frame.operand_stack.push(.{ .int = stackvals.numerator | stackvals.denominator }); 319 | }, 320 | .ixor => { 321 | var stackvals = stack_frame.operand_stack.popToStruct(struct { numerator: primitives.int, denominator: primitives.int }); 322 | try stack_frame.operand_stack.push(.{ .int = stackvals.numerator ^ stackvals.denominator }); 323 | }, 324 | .iushr => { 325 | var stackvals = stack_frame.operand_stack.popToStruct(struct { numerator: primitives.int, denominator: primitives.int }); 326 | try stack_frame.operand_stack.push(.{ .int = stackvals.numerator >> @intCast(std.math.Log2Int(primitives.int), stackvals.denominator) }); 327 | }, 328 | 329 | .fadd => try stack_frame.operand_stack.push(.{ .float = stack_frame.operand_stack.pop().float + stack_frame.operand_stack.pop().float }), 330 | .fmul => try stack_frame.operand_stack.push(.{ .float = stack_frame.operand_stack.pop().float * stack_frame.operand_stack.pop().float }), 331 | .fsub => { 332 | var stackvals = stack_frame.operand_stack.popToStruct(struct { a: primitives.float, b: primitives.float }); 333 | try stack_frame.operand_stack.push(.{ .float = stackvals.a - stackvals.b }); 334 | }, 335 | .fdiv => { 336 | var stackvals = stack_frame.operand_stack.popToStruct(struct { numerator: primitives.float, denominator: primitives.float }); 337 | try stack_frame.operand_stack.push(.{ .float = stackvals.numerator / stackvals.denominator }); 338 | }, 339 | 340 | .dadd => try stack_frame.operand_stack.push(.{ .double = stack_frame.operand_stack.pop().double + stack_frame.operand_stack.pop().double }), 341 | .dmul => try stack_frame.operand_stack.push(.{ .double = stack_frame.operand_stack.pop().double * stack_frame.operand_stack.pop().double }), 342 | .dsub => { 343 | var stackvals = stack_frame.operand_stack.popToStruct(struct { a: primitives.double, b: primitives.double }); 344 | try stack_frame.operand_stack.push(.{ .double = stackvals.a - stackvals.b }); 345 | }, 346 | .ddiv => { 347 | var stackvals = stack_frame.operand_stack.popToStruct(struct { numerator: primitives.double, denominator: primitives.double }); 348 | try stack_frame.operand_stack.push(.{ .double = stackvals.numerator / stackvals.denominator }); 349 | }, 350 | 351 | // Conditionals / jumps 352 | // TODO: Implement all conditions (int comparison, 0 comparison, etc.) 353 | .goto => |offset| { 354 | try fbs.seekBy(offset - @intCast(i16, opcode.sizeOf())); 355 | }, 356 | 357 | // Stack 358 | // See https://docs.oracle.com/javase/specs/jvms/se16/html/jvms-7.html, subsection "Stack" for order 359 | .pop => _ = stack_frame.operand_stack.pop(), 360 | .pop2 => { 361 | var first = stack_frame.operand_stack.pop(); 362 | if (first != .double and first != .long) 363 | _ = stack_frame.operand_stack.pop(); 364 | }, 365 | .dup => try stack_frame.operand_stack.push(stack_frame.operand_stack.array_list.items[stack_frame.operand_stack.array_list.items.len - 1]), 366 | // TODO: Implement the rest of the dupe squad 367 | .swap => { 368 | var first = stack_frame.operand_stack.pop(); 369 | var second = stack_frame.operand_stack.pop(); 370 | 371 | std.debug.assert(first != .double and 372 | first != .long and 373 | second != .double and 374 | second != .long); 375 | 376 | try stack_frame.operand_stack.push(first); 377 | try stack_frame.operand_stack.push(second); 378 | }, 379 | 380 | // Conversions 381 | // See https://docs.oracle.com/javase/specs/jvms/se16/html/jvms-7.html, subsection "Conversions" 382 | inline .i2l, .i2f, .i2d, .l2i, .l2f, .l2d, .f2i, .f2l, .f2d, .d2i, .d2l, .d2f, .i2b, .i2c, .i2s => |_, current| { 383 | const from = primitives.PrimitiveValueKind.fromSingleCharRepr(comptime @tagName(current)[0]); 384 | const to = primitives.PrimitiveValueKind.fromSingleCharRepr(comptime @tagName(current)[2]); 385 | 386 | const popped = stack_frame.operand_stack.pop(); 387 | std.debug.assert(popped == from); 388 | try stack_frame.operand_stack.push(popped.cast(to)); 389 | }, 390 | 391 | // Java bad 392 | .newarray => |atype| { 393 | inline for (std.meta.fields(@TypeOf(atype))) |field| { 394 | if (atype == @intToEnum(@TypeOf(atype), field.value)) 395 | try stack_frame.operand_stack.push(.{ .reference = try self.heap.newArray(std.meta.stringToEnum(array.ArrayKind, field.name).?, stack_frame.operand_stack.pop().int) }); 396 | } 397 | }, 398 | .anewarray => { 399 | try stack_frame.operand_stack.push(.{ .reference = try self.heap.newArray(.reference, stack_frame.operand_stack.pop().int) }); 400 | }, 401 | .new => |index| { 402 | var class_name_slashes = class_file.constant_pool.get(index).class.getName().bytes; 403 | var class_name = class_name_slashes; 404 | // defer self.allocator.free(class_name); 405 | 406 | try stack_frame.operand_stack.push(.{ .reference = try self.heap.newObject((try self.class_resolver.resolve(class_name)).?, self.class_resolver) }); 407 | }, 408 | 409 | .arraylength => try stack_frame.operand_stack.push(.{ .int = self.heap.getArray(stack_frame.operand_stack.pop().reference).length() }), 410 | 411 | .iastore, .lastore, .fastore, .dastore, .aastore, .bastore, .castore, .sastore => { 412 | var value = stack_frame.operand_stack.pop(); 413 | var stackvals = stack_frame.operand_stack.popToStruct(struct { arrayref: primitives.reference, index: primitives.int }); 414 | try self.heap.getArray(stackvals.arrayref).set(stackvals.index, value); 415 | }, 416 | 417 | .iaload, .laload, .faload, .daload, .aaload, .baload, .caload, .saload => { 418 | var stackvals = stack_frame.operand_stack.popToStruct(struct { arrayref: primitives.reference, index: primitives.int }); 419 | try stack_frame.operand_stack.push(try self.heap.getArray(stackvals.arrayref).get(stackvals.index)); 420 | }, 421 | 422 | // Invoke thangs 423 | .invokestatic, .invokespecial, .invokevirtual => |index| { 424 | var methodref = stack_frame.class_file.constant_pool.get(index).methodref; 425 | var nti = methodref.getNameAndTypeInfo(); 426 | var class_info = methodref.getClassInfo(); 427 | 428 | var name = nti.getName().bytes; 429 | var class_name_slashes = class_info.getName().bytes; 430 | var class_name = class_name_slashes; 431 | // defer self.allocator.free(class_name); 432 | 433 | try self.useStaticClass(class_name); 434 | 435 | var descriptor_str = nti.getDescriptor().bytes; 436 | var method_desc = try descriptors.parseString(self.allocator, descriptor_str); 437 | 438 | var params = try self.allocator.alloc(primitives.PrimitiveValue, method_desc.method.parameters.len + if (opcode != .invokestatic) @as(usize, 1) else @as(usize, 0)); 439 | 440 | // std.log.info("{s} {s} {s} {s}", .{ class_name, name, descriptor_str, stack_frame.operand_stack.array_list.items }); 441 | var paramiiii: usize = method_desc.method.parameters.len; 442 | while (paramiiii > 0) : (paramiiii -= 1) { 443 | params[method_desc.method.parameters.len - paramiiii] = switch (method_desc.method.parameters[paramiiii - 1].*) { 444 | .byte => .{ .byte = stack_frame.operand_stack.pop().byte }, 445 | .char => .{ .char = stack_frame.operand_stack.pop().char }, 446 | 447 | .int, .boolean => .{ .int = stack_frame.operand_stack.pop().int }, 448 | .long => .{ .long = stack_frame.operand_stack.pop().long }, 449 | .short => .{ .short = stack_frame.operand_stack.pop().short }, 450 | 451 | .float => .{ .float = stack_frame.operand_stack.pop().float }, 452 | .double => .{ .double = stack_frame.operand_stack.pop().double }, 453 | 454 | .object, .array => .{ .reference = stack_frame.operand_stack.pop().reference }, 455 | .method => unreachable, 456 | 457 | else => unreachable, 458 | }; 459 | } 460 | if (opcode != .invokestatic) params[params.len - 1] = .{ .reference = stack_frame.operand_stack.pop().reference }; 461 | std.mem.reverse(primitives.PrimitiveValue, params); 462 | 463 | var return_val = try self.interpret((try self.class_resolver.resolve(class_name)).?, name, params); 464 | if (return_val != .void) 465 | try stack_frame.operand_stack.push(return_val); 466 | 467 | std.log.info("return to method: {s}", .{descriptor_buf.items}); 468 | }, 469 | .invokeinterface => |iiparams| { 470 | var methodref = stack_frame.class_file.constant_pool.get(iiparams.index).interface_methodref; 471 | var nti = methodref.getNameAndTypeInfo(); 472 | var class_info = methodref.getClassInfo(); 473 | 474 | var name = nti.getName().bytes; 475 | var class_name_slashes = class_info.getName().bytes; 476 | var class_name = class_name_slashes; 477 | // defer self.allocator.free(class_name); 478 | 479 | try self.useStaticClass(class_name); 480 | 481 | var descriptor_str = nti.getDescriptor().bytes; 482 | var method_desc = try descriptors.parseString(self.allocator, descriptor_str); 483 | 484 | var params = try self.allocator.alloc(primitives.PrimitiveValue, method_desc.method.parameters.len + 1); 485 | 486 | // std.log.info("{s} {s} {s} {s}", .{ class_name, name, descriptor_str, stack_frame.operand_stack.array_list.items }); 487 | var paramiiii: usize = method_desc.method.parameters.len; 488 | while (paramiiii > 0) : (paramiiii -= 1) { 489 | params[method_desc.method.parameters.len - paramiiii] = switch (method_desc.method.parameters[paramiiii - 1].*) { 490 | .byte => .{ .byte = stack_frame.operand_stack.pop().byte }, 491 | .char => .{ .char = stack_frame.operand_stack.pop().char }, 492 | 493 | .int, .boolean => .{ .int = stack_frame.operand_stack.pop().int }, 494 | .long => .{ .long = stack_frame.operand_stack.pop().long }, 495 | .short => .{ .short = stack_frame.operand_stack.pop().short }, 496 | 497 | .float => .{ .float = stack_frame.operand_stack.pop().float }, 498 | .double => .{ .double = stack_frame.operand_stack.pop().double }, 499 | 500 | .object, .array => .{ .reference = stack_frame.operand_stack.pop().reference }, 501 | .method => unreachable, 502 | 503 | else => unreachable, 504 | }; 505 | } 506 | if (opcode != .invokestatic) params[params.len - 1] = .{ .reference = stack_frame.operand_stack.pop().reference }; 507 | std.mem.reverse(primitives.PrimitiveValue, params); 508 | 509 | var return_val = try self.interpret(self.heap.getObject(params[0].reference).class_file, name, params); 510 | if (return_val != .void) 511 | try stack_frame.operand_stack.push(return_val); 512 | 513 | std.log.info("return to method: {s}", .{descriptor_buf.items}); 514 | }, 515 | 516 | // Return 517 | .ireturn, .freturn, .dreturn, .areturn => return stack_frame.operand_stack.pop(), 518 | .@"return" => return .void, 519 | 520 | // Comparisons 521 | // See https://docs.oracle.com/javase/specs/jvms/se16/html/jvms-7.html, subsection "Comparisons" for order 522 | .lcmp => { 523 | var stackvals = stack_frame.operand_stack.popToStruct(struct { value1: primitives.long, value2: primitives.long }); 524 | try stack_frame.operand_stack.push(.{ .int = if (stackvals.value1 == stackvals.value2) @as(primitives.int, 0) else if (stackvals.value1 > stackvals.value2) @as(primitives.int, 1) else @as(primitives.int, -1) }); 525 | }, 526 | .fcmpl, .fcmpg => { 527 | var stackvals = stack_frame.operand_stack.popToStruct(struct { value1: primitives.float, value2: primitives.float }); 528 | try stack_frame.operand_stack.push(.{ .int = if (stackvals.value1 > stackvals.value2) @as(primitives.int, 1) else if (stackvals.value1 == stackvals.value2) @as(primitives.int, 0) else @as(primitives.int, -1) }); 529 | }, 530 | .dcmpl, .dcmpg => { 531 | var stackvals = stack_frame.operand_stack.popToStruct(struct { value1: primitives.double, value2: primitives.double }); 532 | try stack_frame.operand_stack.push(.{ .int = if (stackvals.value1 > stackvals.value2) @as(primitives.int, 1) else if (stackvals.value1 == stackvals.value2) @as(primitives.int, 0) else @as(primitives.int, -1) }); 533 | }, 534 | 535 | .ifeq => |offset| { 536 | var value = stack_frame.operand_stack.pop().int; 537 | if (value == 0) { 538 | try fbs.seekBy(offset - @intCast(i16, opcode.sizeOf())); 539 | } 540 | }, 541 | .ifne => |offset| { 542 | var value = stack_frame.operand_stack.pop().int; 543 | if (value != 0) { 544 | try fbs.seekBy(offset - @intCast(i16, opcode.sizeOf())); 545 | } 546 | }, 547 | 548 | .iflt => |offset| { 549 | var value = stack_frame.operand_stack.pop().int; 550 | if (value < 0) { 551 | try fbs.seekBy(offset - @intCast(i16, opcode.sizeOf())); 552 | } 553 | }, 554 | .ifge => |offset| { 555 | var value = stack_frame.operand_stack.pop().int; 556 | if (value >= 0) { 557 | try fbs.seekBy(offset - @intCast(i16, opcode.sizeOf())); 558 | } 559 | }, 560 | .ifgt => |offset| { 561 | var value = stack_frame.operand_stack.pop().int; 562 | if (value > 0) { 563 | try fbs.seekBy(offset - @intCast(i16, opcode.sizeOf())); 564 | } 565 | }, 566 | .ifle => |offset| { 567 | var value = stack_frame.operand_stack.pop().int; 568 | if (value <= 0) { 569 | try fbs.seekBy(offset - @intCast(i16, opcode.sizeOf())); 570 | } 571 | }, 572 | 573 | .if_icmpeq => |offset| { 574 | var stackvals = stack_frame.operand_stack.popToStruct(struct { value1: primitives.int, value2: primitives.int }); 575 | if (stackvals.value1 == stackvals.value2) { 576 | try fbs.seekBy(offset - @intCast(i16, opcode.sizeOf())); 577 | } 578 | }, 579 | .if_icmpne => |offset| { 580 | var stackvals = stack_frame.operand_stack.popToStruct(struct { value1: primitives.int, value2: primitives.int }); 581 | if (stackvals.value1 != stackvals.value2) { 582 | try fbs.seekBy(offset - @intCast(i16, opcode.sizeOf())); 583 | } 584 | }, 585 | .if_icmplt => |offset| { 586 | var stackvals = stack_frame.operand_stack.popToStruct(struct { value1: primitives.int, value2: primitives.int }); 587 | if (stackvals.value1 < stackvals.value2) { 588 | try fbs.seekBy(offset - @intCast(i16, opcode.sizeOf())); 589 | } 590 | }, 591 | .if_icmpge => |offset| { 592 | var stackvals = stack_frame.operand_stack.popToStruct(struct { value1: primitives.int, value2: primitives.int }); 593 | if (stackvals.value1 >= stackvals.value2) { 594 | try fbs.seekBy(offset - @intCast(i16, opcode.sizeOf())); 595 | } 596 | }, 597 | .if_icmpgt => |offset| { 598 | var stackvals = stack_frame.operand_stack.popToStruct(struct { value1: primitives.int, value2: primitives.int }); 599 | if (stackvals.value1 > stackvals.value2) { 600 | try fbs.seekBy(offset - @intCast(i16, opcode.sizeOf())); 601 | } 602 | }, 603 | .if_icmple => |offset| { 604 | var stackvals = stack_frame.operand_stack.popToStruct(struct { value1: primitives.int, value2: primitives.int }); 605 | if (stackvals.value1 <= stackvals.value2) { 606 | try fbs.seekBy(offset - @intCast(i16, opcode.sizeOf())); 607 | } 608 | }, 609 | 610 | .if_acmpeq => |offset| { 611 | var stackvals = stack_frame.operand_stack.popToStruct(struct { value1: primitives.reference, value2: primitives.reference }); 612 | if (stackvals.value1 == stackvals.value2) { 613 | try fbs.seekBy(offset - @intCast(i16, opcode.sizeOf())); 614 | } 615 | }, 616 | .if_acmpne => |offset| { 617 | var stackvals = stack_frame.operand_stack.popToStruct(struct { value1: primitives.reference, value2: primitives.reference }); 618 | if (stackvals.value1 != stackvals.value2) { 619 | try fbs.seekBy(offset - @intCast(i16, opcode.sizeOf())); 620 | } 621 | }, 622 | 623 | // References 624 | // See https://docs.oracle.com/javase/specs/jvms/se16/html/jvms-7.html, subsection "References" for order 625 | .getstatic => |index| { 626 | var fieldref = stack_frame.class_file.constant_pool.get(index).fieldref; 627 | 628 | var nti = fieldref.getNameAndTypeInfo(); 629 | var name = nti.getName().bytes; 630 | var class_name_slashes = fieldref.getClassInfo().getName().bytes; 631 | var class_name = class_name_slashes; 632 | // defer self.allocator.free(class_name); 633 | 634 | try self.useStaticClass(class_name); 635 | 636 | var class = self.static_pool.getClass(class_name).?; 637 | try stack_frame.operand_stack.push(class.getField(name).?); 638 | }, 639 | .putstatic => |index| { 640 | var fieldref = stack_frame.class_file.constant_pool.get(index).fieldref; 641 | 642 | var nti = fieldref.getNameAndTypeInfo(); 643 | var name = nti.getName().bytes; 644 | var class_name_slashes = fieldref.getClassInfo().getName().bytes; 645 | var class_name = class_name_slashes; 646 | // defer self.allocator.free(class_name); 647 | 648 | try self.useStaticClass(class_name); 649 | 650 | var value = stack_frame.operand_stack.pop(); 651 | 652 | try (self.static_pool.getClass(class_name).?).setField(name, value); 653 | }, 654 | .getfield => |index| { 655 | var fieldref = stack_frame.class_file.constant_pool.get(index).fieldref; 656 | 657 | var nti = fieldref.getNameAndTypeInfo(); 658 | var name = nti.getName().bytes; 659 | 660 | var objectref = stack_frame.operand_stack.pop().reference; 661 | 662 | try stack_frame.operand_stack.push(self.heap.getObject(objectref).getField(name).?); 663 | }, 664 | .putfield => |index| { 665 | var fieldref = stack_frame.class_file.constant_pool.get(index).fieldref; 666 | 667 | var nti = fieldref.getNameAndTypeInfo(); 668 | var name = nti.getName().bytes; 669 | 670 | var value = stack_frame.operand_stack.pop(); 671 | var objectref = stack_frame.operand_stack.pop().reference; 672 | 673 | try self.heap.getObject(objectref).setField(name, value); 674 | }, 675 | 676 | .tableswitch => |s| { 677 | var size = 1 + s.skipped_bytes + 4 * 3 + 4 * s.jumps.len; 678 | var index = stack_frame.operand_stack.pop().int; 679 | 680 | if (index < 0 or index >= s.jumps.len) { 681 | try fbs.seekBy(s.default_offset - @intCast(i32, size)); 682 | } else { 683 | try fbs.seekBy(s.jumps[@intCast(usize, index)] - @intCast(i32, size)); 684 | } 685 | }, 686 | 687 | .lookupswitch => |s| z: { 688 | var size = 1 + s.skipped_bytes + 4 * 2 + 8 * s.pairs.len; 689 | var match = stack_frame.operand_stack.pop().int; 690 | 691 | for (s.pairs) |pair| { 692 | if (match == pair.match) { 693 | try fbs.seekBy(pair.offset - @intCast(i32, size)); 694 | break :z; 695 | } 696 | } 697 | 698 | try fbs.seekBy(s.default_offset - @intCast(i32, size)); 699 | }, 700 | 701 | else => unreachable, 702 | } 703 | opcode = opcodes.Operation.decode(self.allocator, fbs_reader) catch break; 704 | } 705 | }, 706 | inline else => |_, op| { 707 | @panic("UNHANDLED OPERATION: " ++ @tagName(op)); 708 | }, 709 | } 710 | } 711 | 712 | unreachable; 713 | } 714 | -------------------------------------------------------------------------------- /src/interpreter/Object.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const utils = @import("utils.zig"); 3 | const primitives = @import("primitives.zig"); 4 | const ClassResolver = @import("ClassResolver.zig"); 5 | 6 | const cf = @import("cf"); 7 | const descriptors = cf.descriptors; 8 | const ClassFile = cf.ClassFile; 9 | 10 | const Object = @This(); 11 | 12 | pub const ObjectFieldInfo = struct { hash: u64, kind: primitives.PrimitiveValueKind }; 13 | 14 | allocator: std.mem.Allocator, 15 | class_file: *const ClassFile, 16 | field_info: []ObjectFieldInfo, 17 | field_data_pool: []u8, 18 | 19 | // TODO: Store field info somewhere once generated! 20 | 21 | pub fn genNonStaticFieldInfo(allocator: std.mem.Allocator, arr: *std.ArrayList(ObjectFieldInfo), class_file: *const ClassFile, class_resolver: *ClassResolver) anyerror!void { 22 | if (class_file.super_class) |super| { 23 | var class_name_slashes = class_file.constant_pool.get(super).class.getName().bytes; 24 | var class_name = class_name_slashes; 25 | defer allocator.free(class_name); 26 | 27 | try genNonStaticFieldInfo(allocator, arr, (try class_resolver.resolve(class_name)).?, class_resolver); 28 | } 29 | 30 | for (class_file.fields.items) |*field| { 31 | if (field.access_flags.static) continue; 32 | 33 | var desc = try descriptors.parseString(allocator, field.getDescriptor().bytes); 34 | defer desc.deinit(allocator); 35 | 36 | try arr.append(.{ 37 | .hash = std.hash.Wyhash.hash(arr.items.len, field.getName().bytes), 38 | .kind = switch (desc.*) { 39 | .byte => .byte, 40 | .char => .char, 41 | 42 | .int, .boolean => .int, 43 | .long => .long, 44 | .short => .short, 45 | 46 | .float => .float, 47 | .double => .double, 48 | 49 | .object, .array => .reference, 50 | 51 | else => unreachable, 52 | }, 53 | }); 54 | } 55 | } 56 | 57 | pub fn initNonStatic(allocator: std.mem.Allocator, class_file: *const ClassFile, class_resolver: *ClassResolver) !Object { 58 | var arr = std.ArrayList(ObjectFieldInfo).init(allocator); 59 | try genNonStaticFieldInfo(allocator, &arr, class_file, class_resolver); 60 | return init(allocator, class_file, try arr.toOwnedSlice()); 61 | } 62 | 63 | pub fn genStaticFieldInfo(allocator: std.mem.Allocator, arr: *std.ArrayList(ObjectFieldInfo), class_file: *const ClassFile) anyerror!void { 64 | for (class_file.fields.items) |*field| { 65 | if (!field.access_flags.static) continue; 66 | 67 | var desc = try descriptors.parseString(allocator, field.getDescriptor().bytes); 68 | defer desc.deinit(allocator); 69 | 70 | try arr.append(.{ 71 | .hash = std.hash.Wyhash.hash(arr.items.len, field.getName().bytes), 72 | .kind = switch (desc.*) { 73 | .byte => .byte, 74 | .char => .char, 75 | 76 | .int, .boolean => .int, 77 | .long => .long, 78 | .short => .short, 79 | 80 | .float => .float, 81 | .double => .double, 82 | 83 | .object, .array => .reference, 84 | 85 | else => unreachable, 86 | }, 87 | }); 88 | } 89 | } 90 | 91 | pub fn initStatic(allocator: std.mem.Allocator, class_file: *const ClassFile) !Object { 92 | var arr = std.ArrayList(ObjectFieldInfo).init(allocator); 93 | try genStaticFieldInfo(allocator, &arr, class_file); 94 | return init(allocator, class_file, try arr.toOwnedSlice()); 95 | } 96 | 97 | pub fn init(allocator: std.mem.Allocator, class_file: *const ClassFile, field_info: []ObjectFieldInfo) !Object { 98 | var fields_size: usize = 0; 99 | 100 | for (field_info) |field| { 101 | fields_size += field.kind.sizeOf(); 102 | } 103 | 104 | var field_data_pool = try allocator.alloc(u8, fields_size); 105 | for (field_data_pool) |*v| v.* = 0; 106 | 107 | return Object{ 108 | .allocator = allocator, 109 | .class_file = class_file, 110 | .field_info = field_info, 111 | .field_data_pool = field_data_pool, 112 | }; 113 | } 114 | 115 | /// Caller owns memory; `self.allocator.free(class_name)`. 116 | pub fn getClassName(self: Object) ![]const u8 { 117 | var class_name_slashes = self.class_file.constant_pool.get(self.class_file.this_class).class.getName().bytes; 118 | return class_name_slashes; 119 | } 120 | 121 | pub fn deinit(self: *Object) void { 122 | self.allocator.free(self.field_data_pool); 123 | } 124 | 125 | fn getFieldIndex(self: *Object, name: []const u8) !usize { 126 | for (self.field_info) |fi, i| { 127 | if (std.hash.Wyhash.hash(i, name) == fi.hash) return i; 128 | } 129 | 130 | return error.FieldNotFound; 131 | } 132 | 133 | pub fn setField(self: *Object, name: []const u8, value: primitives.PrimitiveValue) !void { 134 | var ind = try self.getFieldIndex(name); 135 | var offset: usize = 0; 136 | 137 | var i: usize = 0; 138 | while (i < ind) : (i += 1) { 139 | offset += self.field_info[i].kind.sizeOf(); 140 | } 141 | 142 | var x = self.field_info[i].kind; 143 | inline for (std.meta.fields(primitives.PrimitiveValueKind)) |f| { 144 | if (@enumToInt(primitives.PrimitiveValueKind.void) == f.value) continue; 145 | if (@enumToInt(x) == f.value) { 146 | std.mem.copy(u8, self.field_data_pool[offset .. offset + x.sizeOf()], &std.mem.toBytes(@field(value, f.name))); 147 | return; 148 | } 149 | } 150 | 151 | unreachable; 152 | } 153 | 154 | pub fn getField(self: *Object, name: []const u8) ?primitives.PrimitiveValue { 155 | var ind = self.getFieldIndex(name) catch return null; 156 | var offset: usize = 0; 157 | 158 | var i: usize = 0; 159 | while (i < ind) : (i += 1) { 160 | offset += self.field_info[i].kind.sizeOf(); 161 | } 162 | 163 | var x = self.field_info[i].kind; 164 | inline for (std.meta.fields(primitives.PrimitiveValueKind)) |f| { 165 | if (@enumToInt(primitives.PrimitiveValueKind.void) == f.value) continue; 166 | if (@enumToInt(x) == f.value) { 167 | const k = @intToEnum(primitives.PrimitiveValueKind, f.value); 168 | return @unionInit(primitives.PrimitiveValue, f.name, std.mem.bytesToValue(k.getType(), self.field_data_pool[offset .. offset + x.sizeOf()][0..comptime k.sizeOf()])); 169 | } 170 | } 171 | 172 | unreachable; 173 | } 174 | -------------------------------------------------------------------------------- /src/interpreter/Stack.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const primitives = @import("primitives.zig"); 3 | 4 | const Self = @This(); 5 | 6 | array_list: std.ArrayList(primitives.PrimitiveValue), 7 | 8 | pub fn init(allocator: std.mem.Allocator) Self { 9 | return .{ .array_list = std.ArrayList(primitives.PrimitiveValue).init(allocator) }; 10 | } 11 | 12 | pub fn pop(self: *Self) primitives.PrimitiveValue { 13 | return self.array_list.pop(); 14 | } 15 | 16 | pub fn popToStruct(self: *Self, comptime T: type) T { 17 | var res: T = undefined; 18 | comptime var i: usize = 0; 19 | inline while (i < std.meta.fields(T).len) : (i += 1) { 20 | comptime var field = std.meta.fields(T)[std.meta.fields(T).len - 1 - i]; 21 | @field(res, field.name) = switch (field.type) { 22 | primitives.byte => self.pop().byte, 23 | primitives.short => self.pop().short, 24 | primitives.int => self.pop().int, 25 | primitives.long => self.pop().long, 26 | primitives.char => self.pop().char, 27 | 28 | primitives.float => self.pop().float, 29 | primitives.double => self.pop().double, 30 | 31 | primitives.reference => self.pop().reference, 32 | 33 | else => @compileLog("Type needs to be primitive!"), 34 | }; 35 | } 36 | return res; 37 | } 38 | 39 | pub fn push(self: *Self, value: primitives.PrimitiveValue) !void { 40 | try self.array_list.append(value); 41 | } 42 | 43 | pub fn deinit(self: *Self) void { 44 | self.array_list.deinit(); 45 | } 46 | -------------------------------------------------------------------------------- /src/interpreter/StackFrame.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const primitives = @import("./primitives.zig"); 4 | const Stack = @import("Stack.zig"); 5 | 6 | const cf = @import("cf"); 7 | const ClassFile = cf.ClassFile; 8 | 9 | const Self = @This(); 10 | 11 | local_variables: std.ArrayList(primitives.PrimitiveValue), 12 | operand_stack: Stack, 13 | class_file: *const ClassFile, 14 | 15 | pub fn init(allocator: std.mem.Allocator, class_file: *const ClassFile) Self { 16 | return .{ 17 | .local_variables = std.ArrayList(primitives.PrimitiveValue).init(allocator), 18 | .operand_stack = Stack.init(allocator), 19 | .class_file = class_file, 20 | }; 21 | } 22 | 23 | pub fn setLocalVariable(self: *Self, index: usize, value: primitives.PrimitiveValue) !void { 24 | try self.local_variables.ensureTotalCapacity(index + 1); 25 | self.local_variables.expandToCapacity(); 26 | self.local_variables.items[index] = value; 27 | } 28 | 29 | pub fn deinit(self: *Self) void { 30 | self.local_variables.deinit(); 31 | self.operand_stack.deinit(); 32 | } 33 | -------------------------------------------------------------------------------- /src/interpreter/StaticPool.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Object = @import("Object.zig"); 3 | 4 | const cf = @import("cf"); 5 | const ClassFile = cf.ClassFile; 6 | 7 | const StaticPool = @This(); 8 | 9 | allocator: std.mem.Allocator, 10 | pool: std.StringHashMap(*Object), 11 | 12 | pub fn init(allocator: std.mem.Allocator) StaticPool { 13 | return .{ .allocator = allocator, .pool = std.StringHashMap(*Object).init(allocator) }; 14 | } 15 | 16 | pub fn hasClass(self: *StaticPool, name: []const u8) bool { 17 | return self.pool.get(name) != null; 18 | } 19 | 20 | pub fn getClass(self: *StaticPool, name: []const u8) ?*Object { 21 | return if (self.pool.get(name)) |o| o else null; 22 | } 23 | 24 | pub fn addClass(self: *StaticPool, name: []const u8, class_file: *const ClassFile) !void { 25 | var o = try self.allocator.create(Object); 26 | o.* = try Object.initStatic(self.allocator, class_file); 27 | try self.pool.put(name, o); 28 | } 29 | -------------------------------------------------------------------------------- /src/interpreter/array.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const primitives = @import("primitives.zig"); 3 | 4 | pub const ArrayKind = enum { byte, short, int, long, char, float, double, reference }; 5 | pub const Array = union(ArrayKind) { 6 | byte: ArrayOf(primitives.byte), 7 | short: ArrayOf(primitives.short), 8 | int: ArrayOf(primitives.int), 9 | long: ArrayOf(primitives.long), 10 | char: ArrayOf(primitives.char), 11 | 12 | float: ArrayOf(primitives.float), 13 | double: ArrayOf(primitives.double), 14 | 15 | reference: ArrayOf(primitives.reference), 16 | 17 | pub fn init(allocator: std.mem.Allocator, kind: ArrayKind, count: primitives.int) !Array { 18 | inline for (std.meta.fields(ArrayKind)) |field| { 19 | if (@enumToInt(kind) == field.value) { 20 | return @unionInit(Array, field.name, try ArrayOf(@field(primitives, field.name)).init(allocator, count)); 21 | } 22 | } 23 | 24 | unreachable; 25 | } 26 | 27 | pub fn set(self: Array, index: primitives.int, value: primitives.PrimitiveValue) !void { 28 | inline for (std.meta.fields(ArrayKind)) |field| { 29 | if (@enumToInt(std.meta.activeTag(self)) == field.value) { 30 | return @field(self, field.name).set(index, @field(value, field.name)); 31 | } 32 | } 33 | 34 | unreachable; 35 | } 36 | 37 | pub fn get(self: Array, index: primitives.int) !primitives.PrimitiveValue { 38 | inline for (std.meta.fields(ArrayKind)) |field| { 39 | if (@enumToInt(std.meta.activeTag(self)) == field.value) { 40 | return @unionInit(primitives.PrimitiveValue, field.name, try @field(self, field.name).get(index)); 41 | } 42 | } 43 | 44 | unreachable; 45 | } 46 | 47 | pub fn length(self: Array) primitives.int { 48 | inline for (std.meta.fields(ArrayKind)) |field| { 49 | if (@enumToInt(std.meta.activeTag(self)) == field.value) { 50 | return @field(self, field.name).length(); 51 | } 52 | } 53 | 54 | unreachable; 55 | } 56 | }; 57 | 58 | /// Create an array of type `T`. T should be a primitive. 59 | pub fn ArrayOf(comptime T: type) type { 60 | return struct { 61 | const Self = @This(); 62 | 63 | slice: []T, 64 | 65 | pub fn init(allocator: std.mem.Allocator, count: primitives.int) !Self { 66 | if (count < 0) return error.NegativeArraySize; 67 | 68 | var slice = try allocator.alloc(T, @intCast(usize, try std.math.absInt(count))); 69 | return Self{ .slice = slice }; 70 | } 71 | 72 | pub fn set(self: Self, index: primitives.int, value: T) !void { 73 | if (index < 0) return error.NegativeIndex; 74 | self.slice[@intCast(usize, try std.math.absInt(index))] = value; 75 | } 76 | 77 | pub fn get(self: Self, index: primitives.int) !T { 78 | if (index < 0) return error.NegativeIndex; 79 | return self.slice[@intCast(usize, try std.math.absInt(index))]; 80 | } 81 | 82 | pub fn length(self: Self) primitives.int { 83 | return @intCast(primitives.int, self.slice.len); 84 | } 85 | 86 | pub fn deinit(self: Self, allocator: std.mem.Allocator) void { 87 | allocator.free(self.slice); 88 | } 89 | }; 90 | } 91 | -------------------------------------------------------------------------------- /src/interpreter/primitives.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub const void_value = void; 4 | 5 | pub const byte = i8; 6 | pub const short = i16; 7 | pub const int = i32; 8 | pub const long = i64; 9 | pub const char = u16; 10 | 11 | pub const float = f32; 12 | pub const double = f64; 13 | 14 | pub const reference = usize; 15 | pub const returnAddress = usize; 16 | 17 | pub const PrimitiveValueKind = enum(u4) { 18 | void, 19 | 20 | byte, 21 | short, 22 | int, 23 | long, 24 | char, 25 | 26 | float, 27 | double, 28 | 29 | reference, 30 | returnAddress, 31 | 32 | pub fn fromSingleCharRepr(c: u8) PrimitiveValueKind { 33 | return switch (c) { 34 | 'i' => .int, 35 | 'l' => .long, 36 | 'f' => .float, 37 | 'd' => .double, 38 | 'a' => .reference, 39 | else => @panic("Invalid repr."), 40 | }; 41 | } 42 | 43 | pub fn sizeOf(self: PrimitiveValueKind) usize { 44 | comptime var i: usize = 0; 45 | inline for (std.meta.fields(PrimitiveValue)) |f| { 46 | if (@enumToInt(self) == i) { 47 | return @sizeOf(f.type); 48 | } 49 | i += 1; 50 | } 51 | 52 | unreachable; 53 | } 54 | 55 | pub fn getType(comptime self: PrimitiveValueKind) type { 56 | comptime var i: usize = 0; 57 | inline for (std.meta.fields(PrimitiveValue)) |f| { 58 | if (@enumToInt(self) == i) { 59 | return f.type; 60 | } 61 | i += 1; 62 | } 63 | 64 | unreachable; 65 | } 66 | }; 67 | 68 | pub const PrimitiveValue = union(PrimitiveValueKind) { 69 | void: void_value, 70 | 71 | byte: byte, 72 | short: short, 73 | int: int, 74 | long: long, 75 | char: char, 76 | 77 | float: float, 78 | double: double, 79 | 80 | reference: reference, 81 | returnAddress: returnAddress, 82 | 83 | pub fn fromNative(value: anytype) PrimitiveValue { 84 | return switch (@TypeOf(value)) { 85 | void => .void, 86 | 87 | byte => .{ .byte = value }, 88 | short => .{ .short = value }, 89 | int => .{ .int = value }, 90 | long => .{ .long = value }, 91 | char => .{ .char = value }, 92 | 93 | float => .{ .float = value }, 94 | double => .{ .double = value }, 95 | 96 | reference => .{ .reference = value }, 97 | 98 | else => @compileError("Invalid Java primitive type! (Is what you're inputting an int? You might want to @as or @intCast it!)"), 99 | }; 100 | } 101 | 102 | pub fn toBool(self: *PrimitiveValue) bool { 103 | return self.int == 1; 104 | } 105 | 106 | pub fn isNull(self: *PrimitiveValue) bool { 107 | return self.reference == 0; 108 | } 109 | 110 | pub fn cast(from: PrimitiveValue, to: PrimitiveValueKind) PrimitiveValue { 111 | return switch (from) { 112 | .int => |i| switch (to) { 113 | .long => .{ .long = @intCast(long, i) }, 114 | .float => .{ .float = @intToFloat(float, i) }, 115 | .double => .{ .double = @intToFloat(double, i) }, 116 | 117 | .byte => .{ .byte = @intCast(byte, i) }, 118 | .char => .{ .char = @intCast(char, i) }, 119 | .short => .{ .short = @intCast(short, i) }, 120 | else => unreachable, 121 | }, 122 | .long => |l| switch (to) { 123 | .int => .{ .int = @intCast(int, l) }, 124 | .float => .{ .float = @intToFloat(float, l) }, 125 | .double => .{ .double = @intToFloat(double, l) }, 126 | else => unreachable, 127 | }, 128 | .float => |f| switch (to) { 129 | .int => .{ .int = @floatToInt(int, f) }, 130 | .long => .{ .long = @floatToInt(long, f) }, 131 | .double => .{ .double = @floatCast(double, f) }, 132 | else => unreachable, 133 | }, 134 | .double => |d| switch (to) { 135 | .int => .{ .int = @floatToInt(int, d) }, 136 | .long => .{ .long = @floatToInt(long, d) }, 137 | .float => .{ .float = @floatCast(float, d) }, 138 | else => unreachable, 139 | }, 140 | else => unreachable, 141 | }; 142 | } 143 | }; 144 | -------------------------------------------------------------------------------- /src/interpreter/utils.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const cf = @import("cf"); 4 | const MethodInfo = cf.MethodInfo; 5 | const ClassFile = cf.ClassFile; 6 | const descriptors = cf.descriptors; 7 | 8 | pub fn formatMethod(allocator: std.mem.Allocator, _: *const ClassFile, method: *const MethodInfo, writer: anytype) !void { 9 | if (method.access_flags.public) _ = try writer.writeAll("public "); 10 | if (method.access_flags.private) _ = try writer.writeAll("private "); 11 | if (method.access_flags.protected) _ = try writer.writeAll("protected "); 12 | 13 | if (method.access_flags.native) _ = try writer.writeAll("native "); 14 | if (method.access_flags.static) _ = try writer.writeAll("static "); 15 | if (method.access_flags.abstract) _ = try writer.writeAll("abstract "); 16 | if (method.access_flags.final) _ = try writer.writeAll("final "); 17 | 18 | var desc = method.getDescriptor().bytes; 19 | var fbs = std.io.fixedBufferStream(desc); 20 | 21 | // Write human readable return descriptor 22 | var descriptor = try descriptors.parse(allocator, fbs.reader()); 23 | try descriptor.method.return_type.humanStringify(writer); 24 | 25 | // Write human readable parameters descriptor 26 | try writer.writeByte(' '); 27 | _ = try writer.writeAll(method.getName().bytes); 28 | try writer.writeByte('('); 29 | for (descriptor.method.parameters) |param, i| { 30 | try param.humanStringify(writer); 31 | if (i + 1 != descriptor.method.parameters.len) _ = try writer.writeAll(", "); 32 | } 33 | try writer.writeByte(')'); 34 | } 35 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | // const primitives = @import("interpreter/primitives.zig"); 3 | const Interpreter = @import("interpreter/Interpreter.zig"); 4 | const ClassResolver = @import("interpreter/ClassResolver.zig"); 5 | // In order to build, you first must create src/conf.zig with contents 6 | // `pub const conf = .{.javastd_path = "..."};` 7 | // const conf = @import("conf.zig").conf; 8 | 9 | pub fn main() anyerror!void { 10 | const allocator = std.heap.page_allocator; 11 | var stdout = std.io.getStdOut().writer(); 12 | 13 | var classpath = [_][]const u8{ 14 | // try std.fs.cwd().openDir("test/src", .{ .access_sub_paths = true, .iterate = true }), 15 | "C:\\Programming\\Zig\\jaz\\test\\src", 16 | // How do I find this path, you may ask 17 | // 1. Find your jmods folder (for me, it's located at C:/Program Files/Java/jdk-16.0.1/jmods) 18 | // 2. Open `java.base.jmod` with a dearchiving tool 19 | // 3. Plop the content of `classes` (excluding module_info) into a new directory 20 | // 4. Set the string below to the path of that new directory 21 | "C:/Programming/Garbo/javastd", 22 | // try std.fs.openDirAbsolute(conf.javastd_path, .{ .access_sub_paths = true, .iterate = true }), 23 | }; 24 | 25 | var class_resolver = try ClassResolver.init(allocator, &classpath); 26 | // std.log.info("{any}", .{class_resolver.resolve("jaztest/Benjami/ArrayList")}); 27 | var interpreter = Interpreter.init(allocator, &class_resolver); 28 | 29 | // var ben = try interpreter.new("jaztest.Benjamin", .{}); 30 | 31 | try stdout.print("\n\n\n--- OUTPUT ---\n\n\n", .{}); 32 | try stdout.print("Ben's Awesomeness: {any}\n", .{try interpreter.call("jaztest/Benjamin/main", .{})}); 33 | try stdout.print("\n\n\n--- END OUTPUT ---\n\n\n", .{}); 34 | } 35 | 36 | test { 37 | std.testing.refAllDecls(@This()); 38 | } 39 | -------------------------------------------------------------------------------- /test/src/jaztest/Benjamin.java: -------------------------------------------------------------------------------- 1 | package jaztest; 2 | 3 | public class Benjamin { 4 | public static void main() { 5 | System.out.println("slay queen"); 6 | } 7 | } -------------------------------------------------------------------------------- /test/src/jaztest/IZiguana.java: -------------------------------------------------------------------------------- 1 | package jaztest; 2 | 3 | public interface IZiguana { 4 | public int getAwesomeness(); 5 | public float getPoggersiness(); 6 | } --------------------------------------------------------------------------------