commit f12d41e580c30040ea2bc9afaf6b2b0ecf97c692 Author: ktkk Date: Thu Jun 18 15:34:59 2026 +0000 Initial commit diff --git a/Main.class b/Main.class new file mode 100644 index 0000000..42666a3 Binary files /dev/null and b/Main.class differ diff --git a/Main.java b/Main.java new file mode 100644 index 0000000..4d7481b --- /dev/null +++ b/Main.java @@ -0,0 +1,19 @@ +import java.util.concurrent.*; + +public class Main implements Runnable, Callable { + private static final String message = "Hello, World"; + private static final int number = 42; + private static final double funnyNumber = 6.9; + + public static void main() { + System.out.println(message); + } + + public void run() { + Main.main(); + } + + public Integer call() { + return number; + } +} diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..2da79e0 --- /dev/null +++ b/build.zig @@ -0,0 +1,36 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const exe_mod = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + const exe = b.addExecutable(.{ + .name = "zig_jvm", + .root_module = exe_mod, + }); + + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + + const exe_unit_tests = b.addTest(.{ + .root_module = exe_mod, + }); + + const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); + + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_exe_unit_tests.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..fb89eda --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,13 @@ +.{ + .name = .zig_jvm, + .version = "0.0.0", + .fingerprint = 0x4dd537434033c2ad, // Changing this has security and trust implications. + .minimum_zig_version = "0.14.0-dev.3462+edabcf619", + .dependencies = .{ + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..bc7dbb5 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1781577229, + "narHash": "sha256-lrp67w8AulE9Ks53n27I45ADSzbOCn4H+CNW1Ck8B+8=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "567a49d1913ce81ac6e9582e3553dd90a955875f", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..d83cf0f --- /dev/null +++ b/flake.nix @@ -0,0 +1,40 @@ +{ + description = "A Java class file parser written in Zig"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { + nixpkgs, + flake-utils, + ... + }: + flake-utils.lib.eachDefaultSystem ( + system: let + pkgs = nixpkgs.legacyPackages.${system}; + nativeBuildInputs = with pkgs; [ + zig + zls + ]; + + buildInputs = with pkgs; []; + in { + devShells.default = pkgs.mkShell { inherit nativeBuildInputs buildInputs; }; + + packages.default = pkgs.stdenv.mkDerivation { + pname = "zig-class-parser"; + version = "0.0.0"; + src = ./.; + + nativeBuildInputs = + nativeBuildInputs + ++ [ + pkgs.zig.hook + ]; + inherit buildInputs; + }; + } + ); +} diff --git a/src/Class.zig b/src/Class.zig new file mode 100644 index 0000000..3e6bacd --- /dev/null +++ b/src/Class.zig @@ -0,0 +1,1735 @@ +const std = @import("std"); + +allocator: std.mem.Allocator, + +magic: u32, +minor_version: u16, +major_version: u16, +constant_pool: []?CpInfo, +access_flags: u16, +this_class: u16, +super_class: u16, +interfaces: []u16, +fields: []FieldInfo, +methods: []MethodInfo, +attributes: []AttributeInfo, + +const Self = @This(); + +const ParseError = std.Io.Reader.Error || std.mem.Allocator.Error || error { + InvalidMagicNumber, + InvalidTag, +}; + +const ResolveError = error { + InvalidConstantPoolIndex, + InvalidConstantType, +}; + +pub fn parse(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError!Self { + const magic = try input.takeInt(u32, .big); + if (magic != 0xCAFEBABE) { + return ParseError.InvalidMagicNumber; + } + + const minor_version = try input.takeInt(u16, .big); + const major_version = try input.takeInt(u16, .big); + + const constant_pool = try parseConstantPool(input, allocator); + errdefer { + for (constant_pool) |*cp_info| { + if (cp_info.*) |*c| c.deinit(allocator); + } + allocator.free(constant_pool); + } + + const access_flags = try input.takeInt(u16, .big); + + const this_class = try input.takeInt(u16, .big); + const super_class = try input.takeInt(u16, .big); + + const interfaces = try parseInterfaces(input, allocator); + errdefer allocator.free(interfaces); + + const fields = try parseFields(input, allocator); + errdefer { + for (fields) |*field| { + field.deinit(allocator); + } + allocator.free(fields); + } + + const methods = try parseMethods(input, allocator); + errdefer { + for (methods) |*method| { + method.deinit(allocator); + } + allocator.free(methods); + } + + const attributes = try parseAttributes(input, allocator); + errdefer { + for (attributes) |*attribute| { + attribute.deinit(allocator); + } + allocator.free(attributes); + } + + return .{ + .allocator = allocator, + .magic = magic, + .minor_version = minor_version, + .major_version = major_version, + .constant_pool = constant_pool, + .access_flags = access_flags, + .this_class = this_class, + .super_class = super_class, + .interfaces = interfaces, + .fields = fields, + .methods = methods, + .attributes = attributes, + }; +} + +fn parseConstantPool(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError![]?CpInfo { + const constant_pool_size = try input.takeInt(u16, .big); + const constant_pool = try allocator.alloc(?CpInfo, constant_pool_size - 1); + errdefer allocator.free(constant_pool); + + var i: usize = 0; + while (i < constant_pool_size - 1) { + defer i += 1; + + const cp_info = try CpInfo.parse(input, allocator); + constant_pool[i] = cp_info; + + switch (cp_info) { + .long, .double => { + i += 1; + constant_pool[i] = null; + }, + else => {}, + } + } + + return constant_pool; +} + +fn parseInterfaces(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError![]u16 { + const interfaces_count = try input.takeInt(u16, .big); + const interfaces = try allocator.alloc(u16, interfaces_count); + errdefer allocator.free(interfaces); + + for (0..interfaces_count) |i| { + interfaces[i] = try input.takeInt(u16, .big); + } + + return interfaces; +} + +fn parseFields(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError![]FieldInfo { + const fields_count = try input.takeInt(u16, .big); + const fields = try allocator.alloc(FieldInfo, fields_count); + errdefer allocator.free(fields); + + for (0..fields_count) |i| { + fields[i] = try FieldInfo.parse(input, allocator); + } + + return fields; +} + +fn parseMethods(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError![]MethodInfo { + const methods_count = try input.takeInt(u16, .big); + const methods = try allocator.alloc(MethodInfo, methods_count); + errdefer allocator.free(methods); + + for (0..methods_count) |i| { + methods[i] = try MethodInfo.parse(input, allocator); + } + + return methods; +} + +fn parseAttributes(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError![]AttributeInfo { + const attributes_count = try input.takeInt(u16, .big); + const attributes = try allocator.alloc(AttributeInfo, attributes_count); + errdefer allocator.free(attributes); + + for (0..attributes_count) |i| { + attributes[i] = try AttributeInfo.parse(input, allocator); + } + + return attributes; +} + +pub fn deinit(self: *Self) void { + self.freeConstantPool(); + self.freeInterfaces(); + self.freeFields(); + self.freeMethods(); + self.freeAttributes(); +} + +fn freeConstantPool(self: *Self) void { + for (self.constant_pool) |*cp_info| { + if (cp_info.*) |*c| c.deinit(self.allocator); + } + self.allocator.free(self.constant_pool); +} + +fn freeInterfaces(self: *Self) void { + self.allocator.free(self.interfaces); +} + +fn freeFields(self: *Self) void { + for (self.fields) |*field_info| { + field_info.deinit(self.allocator); + } + self.allocator.free(self.fields); +} + +fn freeMethods(self: *Self) void { + for (self.methods) |*method_info| { + method_info.deinit(self.allocator); + } + self.allocator.free(self.methods); +} + +fn freeAttributes(self: *Self) void { + for (self.attributes) |*attr| { + attr.deinit(self.allocator); + } + self.allocator.free(self.attributes); +} + +pub fn jsonStringify(self: *const Self, jws: *std.json.Stringify) !void { + try jws.beginObject(); + + inline for (std.meta.fields(Self)) |field| { + if (comptime std.mem.eql(u8, field.name, "allocator")) continue; + try jws.objectField(field.name); + try jws.write(@field(self, field.name)); + } + + try jws.endObject(); +} + +pub fn format(self: *const Self, w: *std.Io.Writer) std.Io.Writer.Error!void { + const depth = 2; + var indent: u8 = 0; + + try w.print("magic: 0x{X}\n", .{ self.magic }); + + try w.print("version: {d}.{d}\n", .{ self.major_version, self.minor_version }); + + const this_class = self.thisClass() catch return error.WriteFailed; + _ = try w.write("this class:\n"); + try self.formatCpInfo(w, depth, indent + 1, &.{ .class = this_class }); + + if (self.superClass() catch return error.WriteFailed) |super_class| { + _ = try w.write("super class:\n"); + try self.formatCpInfo(w, depth, indent + 1, &.{ .class = super_class }); + } + + _ = try w.write("interfaces:\n"); + for (self.interfaces, 0..) |interface, i| { + indent += 1; + defer indent -= 1; + + if (interface >= self.constant_pool.len) return error.WriteFailed; + const cp_info = self.constant_pool[interface - 1]; + if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .class) return error.WriteFailed; + + try w.splatByteAll(' ', depth * indent); + try w.print("{d}:\n", .{ i }); + try self.formatCpInfo(w, depth, indent + 1, &cp_info.?); + } + + _ = try w.write("fields:\n"); + for (self.fields, 0..) |field, i| { + indent += 1; + defer indent -= 1; + + try w.splatByteAll(' ', depth * indent); + try w.print("{d}:\n", .{ i }); + + indent += 1; + defer indent -= 1; + + try w.splatByteAll(' ', depth * indent); + try w.print("flags: {d}\n", .{ field.access_flags }); + + try w.splatByteAll(' ', depth * indent); + _ = try w.write("name:\n"); + const field_name = field.name(self.constant_pool) catch return error.WriteFailed; + try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = field_name }); + + try w.splatByteAll(' ', depth * indent); + _ = try w.write("descriptor:\n"); + const field_descriptor = field.descriptor(self.constant_pool) catch return error.WriteFailed; + try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = field_descriptor }); + + try w.splatByteAll(' ', depth * indent); + _ = try w.write("attributes:\n"); + for (field.attributes, 0..) |*attribute, j| { + indent += 1; + defer indent -= 1; + + try w.splatByteAll(' ', depth * indent); + try w.print("{d}:\n", .{ j }); + + try self.formatAttribute(w, depth, indent + 1, attribute); + } + } + + _ = try w.write("methods:\n"); + for (self.methods, 0..) |method, i| { + indent += 1; + defer indent -= 1; + + try w.splatByteAll(' ', depth * indent); + try w.print("{d}:\n", .{ i }); + + indent += 1; + defer indent -= 1; + + try w.splatByteAll(' ', depth * indent); + _ = try w.write("name:\n"); + const method_name = method.name(self.constant_pool) catch return error.WriteFailed; + try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = method_name }); + + try w.splatByteAll(' ', depth * indent); + _ = try w.write("descriptor:\n"); + const method_descriptor = method.descriptor(self.constant_pool) catch return error.WriteFailed; + try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = method_descriptor }); + + try w.splatByteAll(' ', depth * indent); + _ = try w.write("attributes:\n"); + for (method.attributes, 0..) |*attribute, j| { + indent += 1; + defer indent -= 1; + + try w.splatByteAll(' ', depth * indent); + try w.print("{d}:\n", .{ j }); + + try self.formatAttribute(w, depth, indent + 1, attribute); + } + } + + _ = try w.write("attributes:\n"); + for (self.attributes, 0..) |*attribute, i| { + indent += 1; + defer indent -= 1; + + try w.splatByteAll(' ', depth * indent); + try w.print("{d}:\n", .{ i }); + + try self.formatAttribute(w, depth, indent + 1, attribute); + } +} + +fn formatAttribute(self: *const Self, w: *std.Io.Writer, depth: usize, indent: u8, attribute: *const AttributeInfo) std.Io.Writer.Error!void { + try w.splatByteAll(' ', depth * indent); + _ = try w.write("name:\n"); + const attribute_name = attribute.attributeName(self.constant_pool) catch return error.WriteFailed; + try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = attribute_name }); + + var predefined = attribute.resolve(self.allocator, self.constant_pool) catch return error.WriteFailed; + if (predefined) |*p| { + defer p.deinit(self.allocator); + + try w.splatByteAll(' ', depth * indent); + _ = try w.write("predefined: yes\n"); + + try w.splatByteAll(' ', depth * indent); + try w.print("type: {s}\n", .{ @tagName(p.*) }); + + switch (p.*) { + .constant_value => |attr| { + const constant_value = attr.constantValue(self.constant_pool) catch return error.WriteFailed; + try self.formatCpInfo(w, depth, indent + 1, &constant_value); + }, + .code => |attr| { + try w.splatByteAll(' ', depth * indent); + try w.print("max_stack: {d}\n", .{ attr.max_stack }); + + try w.splatByteAll(' ', depth * indent); + try w.print("max_locals: {d}\n", .{ attr.max_locals }); + + // TODO: Properly parse and format instructions + }, + //.stack_map_table, + //.exceptions, + //.inner_classes, + //.enclosing_method, + .synthetic => {}, + .signature => |attr| { + const signature = attr.signature(self.constant_pool) catch return error.WriteFailed; + try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = signature }); + }, + .source_file => |attr| { + const source_file = attr.sourceFile(self.constant_pool) catch return error.WriteFailed; + try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = source_file }); + }, + //.source_debug_extension, + //.line_number_table, + //.local_variable_table, + //.local_variable_type_table, + //.deprecated, + //.runtime_visible_annotations, + //.runtime_invisible_annotations, + //.runtime_visible_parameter_annotations, + //.runtime_invisible_parameter_annotations, + //.annotation_default, + //.bootstrap_methods, + else => {}, + } + } else { + try w.splatByteAll(' ', depth * indent); + _ = try w.write("predefined: no\n"); + } +} + +fn formatCpInfo(self: *const Self, w: *std.Io.Writer, depth: usize, indent: u8, cp_info: *const CpInfo) std.Io.Writer.Error!void { + try w.splatByteAll(' ', depth * indent); + try w.print("constant_value_type: {s}\n", .{ @tagName(cp_info.*) }); + + switch (cp_info.*) { + .class => |class| { + try w.splatByteAll(' ', depth * indent); + _ = try w.write("name:\n"); + const class_name = class.name(self.constant_pool) catch return error.WriteFailed; + try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = class_name }); + }, + .field_ref => |field_ref| { + try w.splatByteAll(' ', depth * indent); + _ = try w.write("class:\n"); + const class = field_ref.class(self.constant_pool) catch return error.WriteFailed; + try self.formatCpInfo(w, depth, indent + 1, &.{ .class = class }); + + try w.splatByteAll(' ', depth * indent); + _ = try w.write("name_and_type:\n"); + const name_and_type = field_ref.nameAndType(self.constant_pool) catch return error.WriteFailed; + try self.formatCpInfo(w, depth, indent + 1, &.{ .name_and_type = name_and_type }); + }, + .method_ref => |method_ref| { + try w.splatByteAll(' ', depth * indent); + _ = try w.write("class:\n"); + const class = method_ref.class(self.constant_pool) catch return error.WriteFailed; + try self.formatCpInfo(w, depth, indent + 1, &.{ .class = class }); + + try w.splatByteAll(' ', depth * indent); + _ = try w.write("name_and_type:\n"); + const name_and_type = method_ref.nameAndType(self.constant_pool) catch return error.WriteFailed; + try self.formatCpInfo(w, depth, indent + 1, &.{ .name_and_type = name_and_type }); + }, + .interface_method_ref => |interface_method_ref| { + try w.splatByteAll(' ', depth * indent); + _ = try w.write("class:\n"); + const class = interface_method_ref.class(self.constant_pool) catch return error.WriteFailed; + try self.formatCpInfo(w, depth, indent + 1, &.{ .class = class }); + + try w.splatByteAll(' ', depth * indent); + _ = try w.write("name_and_type:\n"); + const name_and_type = interface_method_ref.nameAndType(self.constant_pool) catch return error.WriteFailed; + try self.formatCpInfo(w, depth, indent + 1, &.{ .name_and_type = name_and_type }); + }, + .string => |string| { + const s = string.string(self.constant_pool) catch return error.WriteFailed; + try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = s }); + }, + .integer => |integer| { + try w.splatByteAll(' ', depth * indent); + try w.print("integer: {d}\n", .{ integer.bytes }); + }, + .float => |float| { + const f: f32 = @bitCast(float.bytes); + + try w.splatByteAll(' ', depth * indent); + try w.print("float: {d}\n", .{ f }); + }, + .long => |long| { + const l: u64 = (@as(u64, long.high_bytes) << 32) | long.low_bytes; + + try w.splatByteAll(' ', depth * indent); + try w.print("long: {d}\n", .{ l }); + }, + .double => |double| { + const l: u64 = (@as(u64, double.high_bytes) << 32) | double.low_bytes; + const d: f64 = @bitCast(l); + + try w.splatByteAll(' ', depth * indent); + try w.print("double: {d}\n", .{ d }); + }, + .name_and_type => |name_and_type| { + try w.splatByteAll(' ', depth * indent); + _ = try w.write("name:\n"); + const name = name_and_type.name(self.constant_pool) catch return error.WriteFailed; + try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = name }); + + try w.splatByteAll(' ', depth * indent); + _ = try w.write("descriptor:\n"); + const descriptor = name_and_type.descriptor(self.constant_pool) catch return error.WriteFailed; + try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = descriptor }); + }, + .utf8 => |utf8| { + try w.splatByteAll(' ', depth * indent); + try w.print("bytes: \"{s}\"\n", .{ utf8.bytes }); + }, + .method_handle => |method_handle| { + try w.splatByteAll(' ', depth * indent); + _ = try w.write("reference:\n"); + const reference = method_handle.reference(self.constant_pool) catch return error.WriteFailed; + try self.formatCpInfo(w, depth, indent + 1, &reference); + }, + .method_type => |method_type| { + try w.splatByteAll(' ', depth * indent); + _ = try w.write("descriptor:\n"); + const descriptor = method_type.descriptor(self.constant_pool) catch return error.WriteFailed; + try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = descriptor }); + }, + .invoke_dynamic => |invoke_dynamic| { + try w.splatByteAll(' ', depth * indent); + _ = try w.write("name_and_type:\n"); + const name_and_type = invoke_dynamic.nameAndType(self.constant_pool) catch return error.WriteFailed; + try self.formatCpInfo(w, depth, indent + 1, &.{ .name_and_type = name_and_type }); + }, + } +} + +pub fn thisClass(self: *const Self) ResolveError!CpInfo.Class { + const this_class = self.this_class - 1; + if (this_class >= self.constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = self.constant_pool[this_class]; + if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .class) return ResolveError.InvalidConstantType; + return cp_info.?.class; +} + +pub fn superClass(self: *const Self) ResolveError!?CpInfo.Class { + // TODO: Determine whether this is an interface or a regular class. + // When this is an interface, assert that the super class must be Object. + + if (self.super_class == 0) { + const this_class = try self.thisClass(); + const this_class_name = try this_class.name(self.constant_pool); + if (!std.mem.eql(u8, this_class_name.bytes, "Object")) return ResolveError.InvalidConstantType; + return null; + } else { + const super_class = self.super_class - 1; + if (super_class >= self.constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = self.constant_pool[super_class]; + if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .class) return ResolveError.InvalidConstantType; + // TODO: Assert that superclass does not have ACC_FINAL flag set. + return cp_info.?.class; + } +} + +pub const CpInfo = union(CpInfo.Tag) { + class: Class, + field_ref: FieldRef, + method_ref: MethodRef, + interface_method_ref: InterfaceMethodRef, + string: String, + integer: Integer, + float: Float, + long: Long, + double: Double, + name_and_type: NameAndType, + utf8: Utf8, + method_handle: MethodHandle, + method_type: MethodType, + invoke_dynamic: InvokeDynamic, + + pub const Tag = enum(u8) { + class = 7, + field_ref = 9, + method_ref = 10, + interface_method_ref = 11, + string = 8, + integer = 3, + float = 4, + long = 5, + double = 6, + name_and_type = 12, + utf8 = 1, + method_handle = 15, + method_type = 16, + invoke_dynamic = 18, + }; + + pub const Class = struct { + name_index: u16, + + pub fn name(self: *const Class, constant_pool: []const ?CpInfo) ResolveError!Utf8 { + const name_index = self.name_index - 1; + if (name_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = constant_pool[name_index]; + if (cp_info == null or @as(Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType; + return cp_info.?.utf8; + } + }; + + pub const FieldRef = struct { + class_index: u16, + name_and_type_index: u16, + + pub fn class(self: *const FieldRef, constant_pool: []const ?CpInfo) ResolveError!Class { + const class_index = self.class_index - 1; + if (class_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = constant_pool[class_index]; + if (cp_info == null or @as(Tag, cp_info.?) != .class) return ResolveError.InvalidConstantType; + return cp_info.?.class; + } + + pub fn nameAndType(self: *const FieldRef, constant_pool: []const ?CpInfo) ResolveError!NameAndType { + const name_and_type_index = self.name_and_type_index - 1; + if (name_and_type_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = constant_pool[name_and_type_index]; + if (cp_info == null or @as(Tag, cp_info.?) != .name_and_type) return ResolveError.InvalidConstantType; + return cp_info.?.name_and_type; + } + }; + + pub const MethodRef = struct { + class_index: u16, + name_and_type_index: u16, + + pub fn class(self: *const MethodRef, constant_pool: []const ?CpInfo) ResolveError!Class { + const class_index = self.class_index - 1; + if (class_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = constant_pool[class_index]; + if (cp_info == null or @as(Tag, cp_info.?) != .class) return ResolveError.InvalidConstantType; + return cp_info.?.class; + } + + pub fn nameAndType(self: *const MethodRef, constant_pool: []const ?CpInfo) ResolveError!NameAndType { + const name_and_type_index = self.name_and_type_index - 1; + if (name_and_type_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = constant_pool[name_and_type_index]; + if (cp_info == null or @as(Tag, cp_info.?) != .name_and_type) return ResolveError.InvalidConstantType; + return cp_info.?.name_and_type; + } + }; + + pub const InterfaceMethodRef = struct { + class_index: u16, + name_and_type_index: u16, + + pub fn class(self: *const InterfaceMethodRef, constant_pool: []const ?CpInfo) ResolveError!Class { + const class_index = self.class_index - 1; + if (class_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = constant_pool[class_index]; + if (cp_info == null or @as(Tag, cp_info.?) != .class) return ResolveError.InvalidConstantType; + return cp_info.?.class; + } + + pub fn nameAndType(self: *const InterfaceMethodRef, constant_pool: []const ?CpInfo) ResolveError!NameAndType { + const name_and_type_index = self.name_and_type_index - 1; + if (name_and_type_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = constant_pool[name_and_type_index]; + if (cp_info == null or @as(Tag, cp_info.?) != .name_and_type) return ResolveError.InvalidConstantType; + return cp_info.?.name_and_type; + } + }; + + pub const String = struct { + string_index: u16, + + pub fn string(self: *const String, constant_pool: []const ?CpInfo) ResolveError!Utf8 { + const string_index = self.string_index - 1; + if (string_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = constant_pool[string_index]; + if (cp_info == null or @as(Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType; + return cp_info.?.utf8; + } + }; + + pub const Integer = struct { + bytes: u32, + }; + + pub const Float = struct { + bytes: u32, + }; + + pub const Long = struct { + high_bytes: u32, + low_bytes: u32, + }; + + pub const Double = struct { + high_bytes: u32, + low_bytes: u32, + }; + + pub const NameAndType = struct { + name_index: u16, + descriptor_index: u16, + + pub fn name(self: *const NameAndType, constant_pool: []const ?CpInfo) ResolveError!Utf8 { + const name_index = self.name_index - 1; + if (name_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = constant_pool[name_index]; + if (cp_info == null or @as(Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType; + return cp_info.?.utf8; + } + + pub fn descriptor(self: *const NameAndType, constant_pool: []const ?CpInfo) ResolveError!Utf8 { + const descriptor_index = self.descriptor_index - 1; + if (descriptor_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = constant_pool[descriptor_index]; + if (cp_info == null or @as(Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType; + return cp_info.?.utf8; + } + }; + + pub const Utf8 = struct { + bytes: []const u8, + }; + + pub const MethodHandle = struct { + reference_kind: Kind, + reference_index: u16, + + pub const Kind = enum(u8) { + get_field = 1, + get_static = 2, + put_field = 3, + put_static = 4, + invoke_virtual = 5, + invoke_static = 6, + invoke_special = 7, + new_invoke_special = 8, + invoke_interface = 9, + }; + + pub fn reference(self: *const MethodHandle, constant_pool: []const ?CpInfo) ResolveError!CpInfo { + const reference_index = self.reference_index - 1; + if (reference_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = constant_pool[reference_index]; + return switch (self.reference_kind) { + .get_field, .get_static, .put_field, .put_static => blk: { + if (cp_info == null or @as(Tag, cp_info.?) != .field_ref) break :blk ResolveError.InvalidConstantType; + break :blk cp_info.?; + }, + .invoke_virtual, .invoke_static, .invoke_special, .new_invoke_special => blk: { + if (cp_info == null or @as(Tag, cp_info.?) != .method_ref) break :blk ResolveError.InvalidConstantType; + break :blk cp_info.?; + }, + .invoke_interface => blk: { + if (cp_info == null or @as(Tag, cp_info.?) != .interface_method_ref) break :blk ResolveError.InvalidConstantType; + break :blk cp_info.?; + }, + }; + } + }; + + pub const MethodType = struct { + descriptor_index: u16, + + pub fn descriptor(self: *const MethodType, constant_pool: []const ?CpInfo) ResolveError!Utf8 { + const descriptor_index = self.descriptor_index - 1; + if (descriptor_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = constant_pool[descriptor_index]; + if (cp_info == null or @as(Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType; + return cp_info.?.utf8; + } + }; + + pub const InvokeDynamic = struct { + bootstrap_method_attr_index: u16, + name_and_type_index: u16, + + pub fn nameAndType(self: *const InvokeDynamic, constant_pool: []const ?CpInfo) ResolveError!NameAndType { + const name_and_type_index = self.name_and_type_index - 1; + if (name_and_type_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = constant_pool[name_and_type_index]; + if (cp_info == null or @as(Tag, cp_info.?) != .name_and_type) return ResolveError.InvalidConstantType; + return cp_info.?.name_and_type; + } + }; + + pub fn parse(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError!CpInfo { + const tag_byte = try input.takeByte(); + const tag = std.enums.fromInt(Tag, tag_byte) orelse return ParseError.InvalidTag; + + return switch (tag) { + .class => .{ + .class = .{ + .name_index = try input.takeInt(u16, .big), + }, + }, + .field_ref => .{ + .field_ref = .{ + .class_index = try input.takeInt(u16, .big), + .name_and_type_index = try input.takeInt(u16, .big), + }, + }, + .method_ref => .{ + .method_ref = .{ + .class_index = try input.takeInt(u16, .big), + .name_and_type_index = try input.takeInt(u16, .big), + }, + }, + .interface_method_ref => .{ + .interface_method_ref = .{ + .class_index = try input.takeInt(u16, .big), + .name_and_type_index = try input.takeInt(u16, .big), + }, + }, + .string => .{ + .string = .{ + .string_index = try input.takeInt(u16, .big), + }, + }, + .integer => .{ + .integer = .{ + .bytes = try input.takeInt(u32, .big), + }, + }, + .float => .{ + .integer = .{ + .bytes = try input.takeInt(u32, .big), + }, + }, + .long => .{ + .long = .{ + .high_bytes = try input.takeInt(u32, .big), + .low_bytes = try input.takeInt(u32, .big), + }, + }, + .double => .{ + .double = .{ + .high_bytes = try input.takeInt(u32, .big), + .low_bytes = try input.takeInt(u32, .big), + }, + }, + .name_and_type => .{ + .name_and_type = .{ + .name_index = try input.takeInt(u16, .big), + .descriptor_index = try input.takeInt(u16, .big), + }, + }, + .utf8 => blk: { + const length = try input.takeInt(u16, .big); + const bytes = try input.readAlloc(allocator, length); + + break :blk .{ + .utf8 = .{ + .bytes = bytes, + }, + }; + }, + .method_handle => blk: { + const kind_byte = try input.takeByte(); + const kind = std.enums.fromInt(MethodHandle.Kind, kind_byte) orelse return ParseError.InvalidTag; + + break :blk .{ + .method_handle = .{ + .reference_kind = kind, + .reference_index = try input.takeInt(u16, .big), + }, + }; + }, + .method_type => .{ + .method_type = .{ + .descriptor_index = try input.takeInt(u16, .big), + }, + }, + .invoke_dynamic => .{ + .invoke_dynamic = .{ + .bootstrap_method_attr_index = try input.takeInt(u16, .big), + .name_and_type_index = try input.takeInt(u16, .big), + }, + }, + }; + } + + pub fn deinit(self: *CpInfo, allocator: std.mem.Allocator) void { + switch (self.*) { + .utf8 => |info| allocator.free(info.bytes), + else => {}, + } + } +}; + +pub const FieldInfo = struct { + access_flags: u16, + name_index: u16, + descriptor_index: u16, + attributes: []AttributeInfo, + + pub fn parse(input: *std.Io.Reader, allocator: std.mem.Allocator) !FieldInfo { + const access_flags = try input.takeInt(u16, .big); + const name_index = try input.takeInt(u16, .big); + const descriptor_index = try input.takeInt(u16, .big); + const attributes = try parseAttributes(input, allocator); + return .{ + .access_flags = access_flags, + .name_index = name_index, + .descriptor_index = descriptor_index, + .attributes = attributes, + }; + } + + pub fn deinit(self: *FieldInfo, allocator: std.mem.Allocator) void { + for (self.attributes) |*attr_info| { + attr_info.deinit(allocator); + } + allocator.free(self.attributes); + } + + pub fn name(self: *const FieldInfo, constant_pool: []const ?CpInfo) ResolveError!CpInfo.Utf8 { + const name_index = self.name_index - 1; + if (name_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = constant_pool[name_index]; + if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType; + return cp_info.?.utf8; + } + + pub fn descriptor(self: *const FieldInfo, constant_pool: []const ?CpInfo) ResolveError!CpInfo.Utf8 { + const descriptor_index = self.descriptor_index - 1; + if (descriptor_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = constant_pool[descriptor_index]; + if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType; + return cp_info.?.utf8; + } +}; + +pub const MethodInfo = struct { + access_flags: u16, + name_index: u16, + descriptor_index: u16, + attributes: []AttributeInfo, + + pub fn parse(input: *std.Io.Reader, allocator: std.mem.Allocator) !MethodInfo { + const access_flags = try input.takeInt(u16, .big); + const name_index = try input.takeInt(u16, .big); + const descriptor_index = try input.takeInt(u16, .big); + const attributes = try parseAttributes(input, allocator); + return .{ + .access_flags = access_flags, + .name_index = name_index, + .descriptor_index = descriptor_index, + .attributes = attributes, + }; + } + + pub fn deinit(self: *MethodInfo, allocator: std.mem.Allocator) void { + for (self.attributes) |*attr_info| { + attr_info.deinit(allocator); + } + allocator.free(self.attributes); + } + + pub fn name(self: *const MethodInfo, constant_pool: []const ?CpInfo) ResolveError!CpInfo.Utf8 { + const name_index = self.name_index - 1; + if (name_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = constant_pool[name_index]; + if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType; + return cp_info.?.utf8; + } + + pub fn descriptor(self: *const MethodInfo, constant_pool: []const ?CpInfo) ResolveError!CpInfo.Utf8 { + const descriptor_index = self.descriptor_index - 1; + if (descriptor_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = constant_pool[descriptor_index]; + if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType; + return cp_info.?.utf8; + } +}; + +pub const AttributeInfo = struct { + attribute_name_index: u16, + info: []u8, + + pub const Predefined = union(enum) { + constant_value: ConstantValue, + code: Code, + stack_map_table: StackMapTable, + exceptions: Exceptions, + inner_classes: InnerClasses, + enclosing_method: EnclosingMethod, + synthetic: Synthetic, + signature: Signature, + source_file: SourceFile, + source_debug_extension: SourceDebugExtension, + line_number_table: LineNumberTable, + local_variable_table: LocalVariableTable, + local_variable_type_table: LocalVariableTypeTable, + deprecated: Deprecated, + runtime_visible_annotations: RuntimeVisibleAnnotations, + runtime_invisible_annotations: RuntimeInvisibleAnnotations, + runtime_visible_parameter_annotations: RuntimeVisibleParameterAnnotations, + runtime_invisible_parameter_annotations: RuntimeInvisibleParameterAnnotations, + annotation_default: AnnotationDefault, + bootstrap_methods: BootstrapMethods, + + pub const ConstantValue = struct { + constant_value_index: u16, + + pub fn constantValue(self: *const ConstantValue, constant_pool: []const ?CpInfo) ResolveError!CpInfo { + const constant_value_index = self.constant_value_index - 1; + if (constant_value_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = constant_pool[constant_value_index]; + if (cp_info == null) return ResolveError.InvalidConstantType; + + return cp_info.?; + } + }; + + pub const Code = struct { + max_stack: u16, + max_locals: u16, + code: []u8, + exception_table: []ExceptionHandler, + attributes: []AttributeInfo, + + pub const ExceptionHandler = struct { + start_pc: u16, + end_pc: u16, + handler_pc: u16, + catch_type: u16, + }; + }; + + pub const StackMapTable = struct { + entries: []StackMapFrame, + + pub const StackMapFrame = union(enum) { + // 0-63 + same_frame, + // 64-127 + same_locals_1_stack_item_frame: struct { + stack: [1]VerificationTypeInfo, + }, + // 247 + same_locals_1_stack_item_frame_extended: struct { + offset_delta: u16, + stack: [1]VerificationTypeInfo, + }, + // 248-250 + chop_frame: struct { + offset_delta: u16, + }, + // 251 + same_frame_extended: struct { + offset_delta: u16, + }, + // 252-254 + append_frame: struct { + offset_delta: u16, + locals: []const VerificationTypeInfo, + }, + // 255 + full_frame: struct { + offset_delta: u16, + locals: []const VerificationTypeInfo, + stack: []const VerificationTypeInfo, + }, + + pub fn parse(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError!StackMapFrame { + const frame_type = try input.takeByte(); + return switch (frame_type) { + 0...63 => .same_frame, + 64...127 => .{ + .same_locals_1_stack_item_frame = .{ + .stack = .{ try .parse(input) }, + }, + }, + 247 => .{ + .same_locals_1_stack_item_frame_extended = .{ + .offset_delta = try input.takeInt(u16, .big), + .stack = .{ try .parse(input) }, + }, + }, + 248...250 => .{ + .chop_frame = .{ + .offset_delta = try input.takeInt(u16, .big), + }, + }, + 252...254 => blk: { + const offset_delta = try input.takeInt(u16, .big); + + const length = frame_type - 251; + const locals = try allocator.alloc(Predefined.StackMapTable.VerificationTypeInfo, length); + errdefer allocator.free(locals); + for (locals) |*local| { + local.* = try .parse(input); + } + + break :blk .{ + .append_frame = .{ + .offset_delta = offset_delta, + .locals = locals, + }, + }; + }, + 255 => blk: { + const offset_delta = try input.takeInt(u16, .big); + + const number_of_locals = try input.takeInt(u16, .big); + const locals = try allocator.alloc(Predefined.StackMapTable.VerificationTypeInfo, number_of_locals); + errdefer allocator.free(locals); + for (locals) |*local| { + local.* = try .parse(input); + } + + const number_of_stack_items = try input.takeInt(u16, .big); + const stack = try allocator.alloc(Predefined.StackMapTable.VerificationTypeInfo, number_of_stack_items); + errdefer allocator.free(stack); + for (stack) |*stack_item| { + stack_item.* = try .parse(input); + } + + break :blk .{ + .full_frame = .{ + .offset_delta = offset_delta, + .locals = locals, + .stack = stack, + }, + }; + }, + else => unreachable, + }; + } + + pub fn deinit(self: *StackMapFrame, allocator: std.mem.Allocator) void { + switch (self.*) { + .append_frame => |frame| allocator.free(frame.locals), + .full_frame => |frame| { + allocator.free(frame.locals); + allocator.free(frame.stack); + }, + else => {}, + } + } + }; + + pub const VerificationTypeInfo = union(VerificationTypeInfo.Tag) { + top, + integer, + float, + double, + long, + null, + uninitialized_this, + object: struct { + cpool_index: u16, + }, + uninitialized: struct { + cpool_index: u16, + }, + + pub const Tag = enum(u8) { + top = 0, + integer = 1, + float = 2, + double = 3, + long = 4, + null = 5, + uninitialized_this = 6, + object = 7, + uninitialized = 8, + }; + + pub fn parse(input: *std.Io.Reader) ParseError!VerificationTypeInfo { + const tag_byte = try input.takeByte(); + const tag = std.enums.fromInt(Predefined.StackMapTable.VerificationTypeInfo.Tag, tag_byte) orelse return ParseError.InvalidTag; + + return switch (tag) { + .top => .top, + .integer => .integer, + .float => .float, + .double => .double, + .long => .long, + .null => .null, + .uninitialized_this => .uninitialized_this, + .object => .{ + .object = .{ + .cpool_index = try input.takeInt(u16, .big), + }, + }, + .uninitialized => .{ + .uninitialized = .{ + .cpool_index = try input.takeInt(u16, .big), + }, + }, + }; + } + }; + }; + + pub const Exceptions = struct { + exception_index_table: []u16, + }; + + pub const InnerClasses = struct { + classes: []InnerClass, + + pub const InnerClass = struct { + inner_class_info_index: u16, + outer_class_info_index: u16, + inner_name_index: u16, + inner_class_access_flags: u16, + }; + }; + + pub const EnclosingMethod = struct { + class_index: u16, + method_index: u16, + }; + + pub const Synthetic = struct { }; + + pub const Signature = struct { + signature_index: u16, + + pub fn signature(self: *const Signature, constant_pool: []const ?CpInfo) ResolveError!CpInfo.Utf8 { + const signature_index = self.signature_index - 1; + if (signature_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = constant_pool[signature_index]; + if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType; + return cp_info.?.utf8; + } + }; + + pub const SourceFile = struct { + source_file_index: u16, + + pub fn sourceFile(self: *const SourceFile, constant_pool: []const ?CpInfo) ResolveError!CpInfo.Utf8 { + const source_file_index = self.source_file_index - 1; + if (source_file_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = constant_pool[source_file_index]; + if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType; + return cp_info.?.utf8; + } + }; + + pub const SourceDebugExtension = struct { + debug_extension: []u8, + }; + + pub const LineNumberTable = struct { + line_number_table: []const LineNumber, + + pub const LineNumber = struct { + start_pc: u16, + line_number: u16, + }; + }; + + pub const LocalVariableTable = struct { + local_variable_table: []LocalVariable, + + pub const LocalVariable = struct { + start_pc: u16, + length: u16, + name_index: u16, + descriptor_index: u16, + index: u16, + }; + }; + + pub const LocalVariableTypeTable = struct { + local_variable_type_table: []LocalVariableType, + + pub const LocalVariableType = struct { + start_pc: u16, + length: u16, + name_index: u16, + signature_index: u16, + index: u16, + }; + }; + + pub const Deprecated = struct { }; + + pub const ElementValue = union(enum) { + // B, C, D, F, I, J, S, Z, s + const_value_index: u16, + // e + enum_const_value: struct { + type_name_index: u16, + const_name_index: u16, + }, + // c + class_info_index: u16, + // @ + annotation_value: Annotation, + // [ + array_value: struct { + values: []ElementValue, + }, + + pub fn parse(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError!ElementValue { + const tag = try input.takeByte(); + return switch (tag) { + 'B', 'C', 'D', 'F', 'I', 'J', 'S', 'Z', 's' => .{ + .const_value_index = try input.takeInt(u16, .big), + }, + 'e' => .{ + .enum_const_value = .{ + .type_name_index = try input.takeInt(u16, .big), + .const_name_index = try input.takeInt(u16, .big), + }, + }, + 'c' => .{ + .class_info_index = try input.takeInt(u16, .big), + }, + '@' => .{ + .annotation_value = try .parse(input, allocator), + }, + '[' => .{ + .array_value = blk: { + const num_values = try input.takeInt(u16, .big); + const values = try allocator.alloc(ElementValue, num_values); + errdefer allocator.free(values); + for (values) |*value| { + value.* = try .parse(input, allocator); + } + + break :blk .{ + .values = values, + }; + }, + }, + else => unreachable, + }; + } + + pub fn deinit(self: *ElementValue, allocator: std.mem.Allocator) void { + switch (self.*) { + .annotation_value => |element_value| element_value.deinit(allocator), + .array_value => |element_value| { + for (element_value.values) |value| { + value.deinit(allocator); + } + allocator.free(element_value.values); + }, + else => {}, + } + } + }; + + pub const Annotation = struct { + type_index: u16, + element_value_pairs: []ElementValuePair, + + pub const ElementValuePair = struct { + element_name_index: u16, + value: ElementValue, + }; + + pub fn parse(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError!Annotation { + const type_index = try input.takeInt(u16, .big); + + const num_element_value_pairs = try input.takeInt(u16, .big); + const element_value_pairs = try allocator.alloc(ElementValuePair, num_element_value_pairs); + errdefer allocator.free(element_value_pairs); + for (element_value_pairs) |*element_value_pair| { + element_value_pair.* = .{ + .element_name_index = try input.takeInt(u16, .big), + .value = try .parse(input, allocator), + }; + } + + return .{ + .type_index = type_index, + .element_value_pairs = element_value_pairs, + }; + } + + pub fn deinit(self: *Annotation, allocator: std.mem.Allocator) void { + for (self.element_value_pairs) |*element_value_pair| { + element_value_pair.value.deinit(allocator); + } + allocator.free(self.element_value_pairs); + } + }; + + pub const RuntimeVisibleAnnotations = struct { + annotations: []Annotation, + }; + + pub const RuntimeInvisibleAnnotations = struct { + annotations: []Annotation, + }; + + pub const ParameterAnnotation = struct { + annotations: []Annotation, + }; + + pub const RuntimeVisibleParameterAnnotations = struct { + parameter_annotations: []ParameterAnnotation, + }; + + pub const RuntimeInvisibleParameterAnnotations = struct { + parameter_annotations: []ParameterAnnotation, + }; + + pub const AnnotationDefault = struct { + default_value: ElementValue, + }; + + pub const BootstrapMethods = struct { + bootstrap_methods: []BootstrapMethod, + + pub const BootstrapMethod = struct { + bootstrap_method_ref: u16, + bootstrap_arguments: []u16, + }; + }; + + pub fn deinit(self: *Predefined, allocator: std.mem.Allocator) void { + switch (self.*) { + .code => |attribute| { + allocator.free(attribute.code); + allocator.free(attribute.exception_table); + for (attribute.attributes) |*attr| { + attr.deinit(allocator); + } + allocator.free(attribute.attributes); + }, + .stack_map_table => |attribute| { + for (attribute.entries) |*entry| { + entry.deinit(allocator); + } + allocator.free(attribute.entries); + }, + .exceptions => |attribute| allocator.free(attribute.exception_index_table), + .inner_classes => |attribute| allocator.free(attribute.classes), + .source_debug_extension => |attribute| allocator.free(attribute.debug_extension), + .line_number_table => |attribute| allocator.free(attribute.line_number_table), + .local_variable_table => |attribute| allocator.free(attribute.local_variable_table), + .local_variable_type_table => |attribute| allocator.free(attribute.local_variable_type_table), + .runtime_visible_annotations => |attribute| allocator.free(attribute.annotations), + .runtime_invisible_annotations => |attribute| allocator.free(attribute.annotations), + .runtime_visible_parameter_annotations => |attribute| { + for (attribute.parameter_annotations) |parameter_annotation| { + allocator.free(parameter_annotation.annotations); + } + allocator.free(attribute.parameter_annotations); + }, + .runtime_invisible_parameter_annotations => |attribute| { + for (attribute.parameter_annotations) |parameter_annotation| { + allocator.free(parameter_annotation.annotations); + } + allocator.free(attribute.parameter_annotations); + }, + .bootstrap_methods => |attribute| { + for (attribute.bootstrap_methods) |bootstrap_method| { + allocator.free(bootstrap_method.bootstrap_arguments); + } + allocator.free(attribute.bootstrap_methods); + }, + else => {}, + } + } + }; + + pub fn parse(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError!AttributeInfo { + const attribute_name_index = try input.takeInt(u16, .big); + const attribute_length = try input.takeInt(u32, .big); + const info = try input.readAlloc(allocator, attribute_length); + return .{ + .attribute_name_index = attribute_name_index, + .info = info, + }; + } + + pub fn deinit(self: *AttributeInfo, allocator: std.mem.Allocator) void { + allocator.free(self.info); + } + + pub fn attributeName(self: *const AttributeInfo, constant_pool: []const ?CpInfo) ResolveError!CpInfo.Utf8 { + const attribute_name_index = self.attribute_name_index - 1; + if (attribute_name_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; + const cp_info = constant_pool[attribute_name_index]; + if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType; + return cp_info.?.utf8; + } + + pub fn resolve(self: *const AttributeInfo, allocator: std.mem.Allocator, constant_pool: []const ?CpInfo) (ResolveError || ParseError)!?Predefined { + const attribute_name = try self.attributeName(constant_pool); + + var r: std.Io.Reader = .fixed(self.info); + + const eql = std.mem.eql; + if (eql(u8, attribute_name.bytes, "ConstantValue")) { + return .{ + .constant_value = .{ + .constant_value_index = try r.takeInt(u16, .big), + }, + }; + } else if (eql(u8, attribute_name.bytes, "Code")) { + const max_stack = try r.takeInt(u16, .big); + const max_locals = try r.takeInt(u16, .big); + + const code_length = try r.takeInt(u32, .big); + const code = try r.readAlloc(allocator, code_length); + errdefer allocator.free(code); + + const exception_table_length = try r.takeInt(u16, .big); + const exception_table = try allocator.alloc(Predefined.Code.ExceptionHandler, exception_table_length); + errdefer allocator.free(exception_table); + for (exception_table) |*exception_handler| { + exception_handler.* = .{ + .start_pc = try r.takeInt(u16, .big), + .end_pc = try r.takeInt(u16, .big), + .handler_pc = try r.takeInt(u16, .big), + .catch_type = try r.takeInt(u16, .big), + }; + } + + const attribute_count = try r.takeInt(u16, .big); + const attributes = try allocator.alloc(AttributeInfo, attribute_count); + errdefer allocator.free(attributes); + for (attributes) |*attribute| { + attribute.* = try .parse(&r, allocator); + } + + return .{ + .code = .{ + .max_stack = max_stack, + .max_locals = max_locals, + .code = code, + .exception_table = exception_table, + .attributes = attributes, + }, + }; + } else if (eql(u8, attribute_name.bytes, "StackMapTable")) { + const number_of_entries = try r.takeInt(u16, .big); + const entries = try allocator.alloc(Predefined.StackMapTable.StackMapFrame, number_of_entries); + errdefer allocator.free(entries); + for (entries) |*entry| { + entry.* = try .parse(&r, allocator); + } + + return .{ + .stack_map_table = .{ + .entries = entries, + }, + }; + } else if (eql(u8, attribute_name.bytes, "Exceptions")) { + const number_of_exceptions = try r.takeInt(u16, .big); + const exception_index_table = try allocator.alloc(u16, number_of_exceptions); + errdefer allocator.free(exception_index_table); + for (exception_index_table) |*exception_index| { + exception_index.* = try r.takeInt(u16, .big); + } + + return .{ + .exceptions = .{ + .exception_index_table = exception_index_table, + }, + }; + } else if (eql(u8, attribute_name.bytes, "InnerClasses")) { + const number_of_classes = try r.takeInt(u16, .big); + const classes = try allocator.alloc(Predefined.InnerClasses.InnerClass, number_of_classes); + errdefer allocator.free(classes); + for (classes) |*class| { + class.* = .{ + .inner_class_info_index = try r.takeInt(u16, .big), + .outer_class_info_index = try r.takeInt(u16, .big), + .inner_name_index = try r.takeInt(u16, .big), + .inner_class_access_flags = try r.takeInt(u16, .big), + }; + } + + return .{ + .inner_classes = .{ + .classes = classes, + }, + }; + } else if (eql(u8, attribute_name.bytes, "EnclosingMethod")) { + return .{ + .enclosing_method = .{ + .class_index = try r.takeInt(u16, .big), + .method_index = try r.takeInt(u16, .big), + }, + }; + } else if (eql(u8, attribute_name.bytes, "Synthetic")) { + return .{ + .synthetic = .{}, + }; + } else if (eql(u8, attribute_name.bytes, "Signature")) { + return .{ + .signature = .{ + .signature_index = try r.takeInt(u16, .big), + }, + }; + } else if (eql(u8, attribute_name.bytes, "SourceFile")) { + return .{ + .source_file = .{ + .source_file_index = try r.takeInt(u16, .big), + }, + }; + } else if (eql(u8, attribute_name.bytes, "SourceDebugExtension")) { + return .{ + .source_debug_extension = .{ + .debug_extension = try allocator.dupe(u8, self.info), + }, + }; + } else if (eql(u8, attribute_name.bytes, "LineNumberTable")) { + const line_number_table_length = try r.takeInt(u16, .big); + const line_number_table = try allocator.alloc(Predefined.LineNumberTable.LineNumber, line_number_table_length); + errdefer allocator.free(line_number_table); + for (line_number_table) |*line_number| { + line_number.* = .{ + .start_pc = try r.takeInt(u16, .big), + .line_number = try r.takeInt(u16, .big), + }; + } + + return .{ + .line_number_table = .{ + .line_number_table = line_number_table, + }, + }; + } else if (eql(u8, attribute_name.bytes, "LocalVariableTable")) { + const local_variable_table_length = try r.takeInt(u16, .big); + const local_variable_table = try allocator.alloc(Predefined.LocalVariableTable.LocalVariable, local_variable_table_length); + errdefer allocator.free(local_variable_table); + for (local_variable_table) |*local_variable| { + local_variable.* = .{ + .start_pc = try r.takeInt(u16, .big), + .length = try r.takeInt(u16, .big), + .name_index = try r.takeInt(u16, .big), + .descriptor_index = try r.takeInt(u16, .big), + .index = try r.takeInt(u16, .big), + }; + } + + return .{ + .local_variable_table = .{ + .local_variable_table = local_variable_table, + }, + }; + } else if (eql(u8, attribute_name.bytes, "LocalVariableTypeTable")) { + const local_variable_type_table_length = try r.takeInt(u16, .big); + const local_variable_type_table = try allocator.alloc(Predefined.LocalVariableTypeTable.LocalVariableType, local_variable_type_table_length); + errdefer allocator.free(local_variable_type_table); + for (local_variable_type_table) |*local_variable_type| { + local_variable_type.* = .{ + .start_pc = try r.takeInt(u16, .big), + .length = try r.takeInt(u16, .big), + .name_index = try r.takeInt(u16, .big), + .signature_index = try r.takeInt(u16, .big), + .index = try r.takeInt(u16, .big), + }; + } + + return .{ + .local_variable_type_table = .{ + .local_variable_type_table = local_variable_type_table, + }, + }; + } else if (eql(u8, attribute_name.bytes, "Deprecated")) { + return .{ + .deprecated = .{}, + }; + } else if (eql(u8, attribute_name.bytes, "RuntimeVisibleAnnotations")) { + const num_annotations = try r.takeInt(u16, .big); + const annotations = try allocator.alloc(Predefined.Annotation, num_annotations); + errdefer allocator.free(annotations); + for (annotations) |*annotation| { + annotation.* = try .parse(&r, allocator); + } + + return .{ + .runtime_visible_annotations = .{ + .annotations = annotations, + }, + }; + } else if (eql(u8, attribute_name.bytes, "RuntimeInvisibleAnnotations")) { + const num_annotations = try r.takeInt(u16, .big); + const annotations = try allocator.alloc(Predefined.Annotation, num_annotations); + errdefer allocator.free(annotations); + for (annotations) |*annotation| { + annotation.* = try .parse(&r, allocator); + } + + return .{ + .runtime_invisible_annotations = .{ + .annotations = annotations, + }, + }; + } else if (eql(u8, attribute_name.bytes, "RuntimeVisibleParameterAnnotations")) { + const num_parameters = try r.takeByte(); + const parameter_annotations = try allocator.alloc(Predefined.ParameterAnnotation, num_parameters); + errdefer allocator.free(parameter_annotations); + for (parameter_annotations) |*parameter_annotation| { + const num_annotations = try r.takeInt(u16, .big); + const annotations = try allocator.alloc(Predefined.Annotation, num_annotations); + errdefer allocator.free(annotations); + for (annotations) |*annotation| { + annotation.* = try .parse(&r, allocator); + } + + parameter_annotation.* = .{ + .annotations = annotations, + }; + } + + return .{ + .runtime_visible_parameter_annotations = .{ + .parameter_annotations = parameter_annotations, + }, + }; + } else if (eql(u8, attribute_name.bytes, "RuntimeInvisibleParameterAnnotations")) { + const num_parameters = try r.takeByte(); + const parameter_annotations = try allocator.alloc(Predefined.ParameterAnnotation, num_parameters); + errdefer allocator.free(parameter_annotations); + for (parameter_annotations) |*parameter_annotation| { + const num_annotations = try r.takeInt(u16, .big); + const annotations = try allocator.alloc(Predefined.Annotation, num_annotations); + errdefer allocator.free(annotations); + for (annotations) |*annotation| { + annotation.* = try .parse(&r, allocator); + } + + parameter_annotation.* = .{ + .annotations = annotations, + }; + } + + return .{ + .runtime_invisible_parameter_annotations = .{ + .parameter_annotations = parameter_annotations, + }, + }; + } else if (eql(u8, attribute_name.bytes, "AnnotationDefault")) { + unreachable; + } else if (eql(u8, attribute_name.bytes, "BootstrapMethods")) { + const num_bootstrap_methods = try r.takeInt(u16, .big); + const bootstrap_methods = try allocator.alloc(Predefined.BootstrapMethods.BootstrapMethod, num_bootstrap_methods); + errdefer allocator.free(bootstrap_methods); + for (bootstrap_methods) |*bootstrap_method| { + const bootstrap_method_ref = try r.takeInt(u16, .big); + + const num_bootstrap_arguments = try r.takeInt(u16, .big); + const bootstrap_arguments = try allocator.alloc(u16, num_bootstrap_arguments); + errdefer allocator.free(bootstrap_arguments); + for (bootstrap_arguments) |*bootstrap_argument| { + bootstrap_argument.* = try r.takeInt(u16, .big); + } + + bootstrap_method.* = .{ + .bootstrap_method_ref = bootstrap_method_ref, + .bootstrap_arguments = bootstrap_arguments, + }; + } + + return .{ + .bootstrap_methods = .{ + .bootstrap_methods = bootstrap_methods, + }, + }; + } else { + return null; + } + } +}; + diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..df76872 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,49 @@ +const std = @import("std"); + +const Class = @import("Class.zig"); + +pub fn main(init: std.process.Init) !void { + const io = init.io; + const allocator = init.gpa; + + var args = try init.minimal.args.iterateAllocator(init.arena.allocator()); + defer args.deinit(); + + var stderr_writer: std.Io.File.Writer = .init(.stderr(), io, &.{}); + const stderr = &stderr_writer.interface; + + var stdout_buf: [1024]u8 = undefined; + var stdout_writer: std.Io.File.Writer = .init(.stdout(), io, &stdout_buf); + const stdout = &stdout_writer.interface; + + std.debug.assert(args.skip()); + const file_path = if (args.next()) |fp| fp else { + try stderr.writeAll("No file argument provided.\n"); + return; + }; + + const file = std.Io.Dir.cwd().openFile(io, file_path, .{}) catch |err| switch (err) { + error.FileNotFound => { + try stderr.writeAll("File not found.\n"); + return; + }, + error.IsDir => { + try stderr.writeAll("Path is a directory.\n"); + return; + }, + else => return err, + }; + defer file.close(io); + + var file_buf: [1024]u8 = undefined; + var file_reader = file.reader(io, &file_buf); + const reader = &file_reader.interface; + + var class = try Class.parse(reader, allocator); + defer class.deinit(); + + try stdout.print("{f}", .{ class }); + + try stdout.flush(); +} + diff --git a/zig-out/bin/zig_jvm b/zig-out/bin/zig_jvm new file mode 100755 index 0000000..85fa921 Binary files /dev/null and b/zig-out/bin/zig_jvm differ