const std = @import("std"); const EnumFlags = @import("EnumFlags.zig").EnumFlags; allocator: std.mem.Allocator, magic: u32, minor_version: u16, major_version: u16, constant_pool: []?CpInfo, access_flags: ClassAccessFlags, 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: ClassAccessFlags = .{ .mask = 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 }); try w.print("flags: {f}\n", .{ self.access_flags }); const this_class = self.thisClass() catch return error.WriteFailed; _ = try w.write("this class:\n"); try this_class.format(w, depth, indent + 1, self.constant_pool); if (self.superClass() catch return error.WriteFailed) |super_class| { _ = try w.write("super class:\n"); try super_class.format(w, depth, indent + 1, self.constant_pool); } _ = 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 cp_info.?.format(w, depth, indent + 1, self.constant_pool); } _ = 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 }); try field.format(w, depth, indent + 1, self.allocator, self.constant_pool); } _ = 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 }); try method.format(w, depth, indent + 1, self.allocator, self.constant_pool); } _ = 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 attribute.format(w, depth, indent + 1, self.allocator, self.constant_pool); } } pub fn thisClass(self: *const Self) ResolveError!CpInfo { 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.?; } pub fn superClass(self: *const Self) ResolveError!?CpInfo { // 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()).class; const this_class_name = (try this_class.name(self.constant_pool)).utf8; 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.?; } } 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!CpInfo { 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.?; } }; pub const FieldRef = struct { class_index: u16, name_and_type_index: u16, pub fn class(self: *const FieldRef, constant_pool: []const ?CpInfo) ResolveError!CpInfo { 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.?; } pub fn nameAndType(self: *const FieldRef, constant_pool: []const ?CpInfo) ResolveError!CpInfo { 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.?; } }; pub const MethodRef = struct { class_index: u16, name_and_type_index: u16, pub fn class(self: *const MethodRef, constant_pool: []const ?CpInfo) ResolveError!CpInfo { 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.?; } pub fn nameAndType(self: *const MethodRef, constant_pool: []const ?CpInfo) ResolveError!CpInfo { 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.?; } }; pub const InterfaceMethodRef = struct { class_index: u16, name_and_type_index: u16, pub fn class(self: *const InterfaceMethodRef, constant_pool: []const ?CpInfo) ResolveError!CpInfo { 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.?; } pub fn nameAndType(self: *const InterfaceMethodRef, constant_pool: []const ?CpInfo) ResolveError!CpInfo { 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.?; } }; pub const String = struct { string_index: u16, pub fn string(self: *const String, constant_pool: []const ?CpInfo) ResolveError!CpInfo { 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.?; } }; 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!CpInfo { 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.?; } pub fn descriptor(self: *const NameAndType, constant_pool: []const ?CpInfo) ResolveError!CpInfo { 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.?; } }; 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!CpInfo { 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.?; } }; 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!CpInfo { 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.?; } }; 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 fn format(self: *const CpInfo, w: *std.Io.Writer, depth: usize, indent: u8, constant_pool: []const ?CpInfo) std.Io.Writer.Error!void { try w.splatByteAll(' ', depth * indent); try w.print("constant_value_type: {s}\n", .{ @tagName(self.*) }); switch (self.*) { .class => |class| { try w.splatByteAll(' ', depth * indent); _ = try w.write("name:\n"); const class_name = class.name(constant_pool) catch return error.WriteFailed; try class_name.format(w, depth, indent + 1, constant_pool); }, .field_ref => |field_ref| { try w.splatByteAll(' ', depth * indent); _ = try w.write("class:\n"); const class = field_ref.class(constant_pool) catch return error.WriteFailed; try class.format(w, depth, indent + 1, constant_pool); try w.splatByteAll(' ', depth * indent); _ = try w.write("name_and_type:\n"); const name_and_type = field_ref.nameAndType(constant_pool) catch return error.WriteFailed; try name_and_type.format(w, depth, indent + 1, constant_pool); }, .method_ref => |method_ref| { try w.splatByteAll(' ', depth * indent); _ = try w.write("class:\n"); const class = method_ref.class(constant_pool) catch return error.WriteFailed; try class.format(w, depth, indent + 1, constant_pool); try w.splatByteAll(' ', depth * indent); _ = try w.write("name_and_type:\n"); const name_and_type = method_ref.nameAndType(constant_pool) catch return error.WriteFailed; try name_and_type.format(w, depth, indent + 1, constant_pool); }, .interface_method_ref => |interface_method_ref| { try w.splatByteAll(' ', depth * indent); _ = try w.write("class:\n"); const class = interface_method_ref.class(constant_pool) catch return error.WriteFailed; try class.format(w, depth, indent + 1, constant_pool); try w.splatByteAll(' ', depth * indent); _ = try w.write("name_and_type:\n"); const name_and_type = interface_method_ref.nameAndType(constant_pool) catch return error.WriteFailed; try name_and_type.format(w, depth, indent + 1, constant_pool); }, .string => |string| { const s = string.string(constant_pool) catch return error.WriteFailed; try s.format(w, depth, indent + 1, constant_pool); }, .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(constant_pool) catch return error.WriteFailed; try name.format(w, depth, indent + 1, constant_pool); try w.splatByteAll(' ', depth * indent); _ = try w.write("descriptor:\n"); const descriptor = name_and_type.descriptor(constant_pool) catch return error.WriteFailed; try descriptor.format(w, depth, indent + 1, constant_pool); }, .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.print("kind: {t}\n", .{ method_handle.reference_kind }); try w.splatByteAll(' ', depth * indent); _ = try w.write("reference:\n"); const reference = method_handle.reference(constant_pool) catch return error.WriteFailed; try reference.format(w, depth, indent + 1, constant_pool); }, .method_type => |method_type| { try w.splatByteAll(' ', depth * indent); _ = try w.write("descriptor:\n"); const descriptor = method_type.descriptor(constant_pool) catch return error.WriteFailed; try descriptor.format(w, depth, indent + 1, constant_pool); }, .invoke_dynamic => |invoke_dynamic| { try w.splatByteAll(' ', depth * indent); _ = try w.write("name_and_type:\n"); const name_and_type = invoke_dynamic.nameAndType(constant_pool) catch return error.WriteFailed; try name_and_type.format(w, depth, indent + 1, constant_pool); }, } } }; pub const ClassFlags = enum(u16) { public = 0x0001, final = 0x0010, super = 0x0020, interface = 0x0200, abstract = 0x0400, synthetic = 0x1000, annotation = 0x2000, @"enum" = 0x4000, }; pub const ClassAccessFlags = EnumFlags(ClassFlags); pub const FieldInfo = struct { access_flags: FieldAccessFlags, name_index: u16, descriptor_index: u16, attributes: []AttributeInfo, pub fn parse(input: *std.Io.Reader, allocator: std.mem.Allocator) !FieldInfo { const access_flags: FieldAccessFlags = .{ .mask = 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 format(self: *const FieldInfo, w: *std.Io.Writer, depth: usize, indent: u8, allocator: std.mem.Allocator, constant_pool: []const ?CpInfo) std.Io.Writer.Error!void { try w.splatByteAll(' ', depth * indent); try w.print("flags: {f}\n", .{ self.access_flags }); try w.splatByteAll(' ', depth * indent); _ = try w.write("name:\n"); const field_name = self.name(constant_pool) catch return error.WriteFailed; try field_name.format(w, depth, indent + 1, constant_pool); try w.splatByteAll(' ', depth * indent); _ = try w.write("descriptor:\n"); const field_descriptor = self.descriptor(constant_pool) catch return error.WriteFailed; try field_descriptor.format(w, depth, indent + 1, constant_pool); try w.splatByteAll(' ', depth * indent); _ = try w.write("attributes:\n"); var attr_indent = indent; for (self.attributes, 0..) |*attribute, j| { attr_indent += 1; defer attr_indent -= 1; try w.splatByteAll(' ', depth * attr_indent); try w.print("{d}:\n", .{ j }); try attribute.format(w, depth, attr_indent + 1, allocator, constant_pool); } } pub fn name(self: *const FieldInfo, constant_pool: []const ?CpInfo) ResolveError!CpInfo { 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.?; } pub fn descriptor(self: *const FieldInfo, constant_pool: []const ?CpInfo) ResolveError!CpInfo { 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.?; } }; pub const FieldFlags = enum(u16) { public = 0x0001, private = 0x0002, protected = 0x0004, static = 0x0008, final = 0x0010, @"volatile" = 0x0040, transient = 0x0080, synthetic = 0x1000, @"enum" = 0x4000, }; pub const FieldAccessFlags = EnumFlags(FieldFlags); pub const MethodInfo = struct { access_flags: MethodAccessFlags, name_index: u16, descriptor_index: u16, attributes: []AttributeInfo, pub fn parse(input: *std.Io.Reader, allocator: std.mem.Allocator) !MethodInfo { const access_flags: MethodAccessFlags = .{ .mask = 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 format(self: *const MethodInfo, w: *std.Io.Writer, depth: usize, indent: u8, allocator: std.mem.Allocator, constant_pool: []const ?CpInfo) std.Io.Writer.Error!void { try w.splatByteAll(' ', depth * indent); try w.print("flags: {f}\n", .{ self.access_flags }); try w.splatByteAll(' ', depth * indent); _ = try w.write("name:\n"); const method_name = self.name(constant_pool) catch return error.WriteFailed; try method_name.format(w, depth, indent + 1, constant_pool); try w.splatByteAll(' ', depth * indent); _ = try w.write("descriptor:\n"); const method_descriptor = self.descriptor(constant_pool) catch return error.WriteFailed; try method_descriptor.format(w, depth, indent + 1, constant_pool); try w.splatByteAll(' ', depth * indent); _ = try w.write("attributes:\n"); var attr_indent = indent; for (self.attributes, 0..) |*attribute, j| { attr_indent += 1; defer attr_indent -= 1; try w.splatByteAll(' ', depth * attr_indent); try w.print("{d}:\n", .{ j }); try attribute.format(w, depth, attr_indent + 1, allocator, constant_pool); } } pub fn name(self: *const MethodInfo, constant_pool: []const ?CpInfo) ResolveError!CpInfo { 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.?; } pub fn descriptor(self: *const MethodInfo, constant_pool: []const ?CpInfo) ResolveError!CpInfo { 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.?; } }; pub const MethodFlags = enum(u16) { public = 0x0001, private = 0x0002, protected = 0x0004, static = 0x0008, final = 0x0010, synchronized = 0x0020, bridge = 0x0040, varargs = 0x0080, native = 0x0100, abstract = 0x0400, strict = 0x0800, synthetic = 0x1000, }; pub const MethodAccessFlags = EnumFlags(MethodFlags); 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: InnerClassAccessFlags, pub fn format(self: *const InnerClass, w: *std.Io.Writer, depth: usize, indent: u8, constant_pool: []const ?CpInfo) std.Io.Writer.Error!void { try w.splatByteAll(' ', depth * indent); try w.print("flags: {f}\n", .{ self.inner_class_access_flags }); const inner_class = self.innerClass(constant_pool) catch return error.WriteFailed; try w.splatByteAll(' ', depth * indent); _ = try w.write("inner_class:\n"); try inner_class.format(w, depth, indent + 1, constant_pool); const outer_class = self.outerClass(constant_pool) catch return error.WriteFailed; if (outer_class) |oc| { try w.splatByteAll(' ', depth * indent); _ = try w.write("outer_class:\n"); try oc.format(w, depth, indent + 1, constant_pool); } const inner_name = self.innerName(constant_pool) catch return error.WriteFailed; if (inner_name) |in| { try w.splatByteAll(' ', depth * indent); _ = try w.write("inner_name:\n"); try in.format(w, depth, indent + 1, constant_pool); } } pub fn innerClass(self: *const InnerClass, constant_pool: []const ?CpInfo) ResolveError!CpInfo { const inner_class_info_index = self.inner_class_info_index - 1; if (inner_class_info_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; const cp_info = constant_pool[inner_class_info_index]; if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .class) return ResolveError.InvalidConstantType; return cp_info.?; } pub fn outerClass(self: *const InnerClass, constant_pool: []const ?CpInfo) ResolveError!?CpInfo { if (self.outer_class_info_index == 0) return null; const outer_class_info_index = self.outer_class_info_index - 1; if (outer_class_info_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; const cp_info = constant_pool[outer_class_info_index]; if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .class) return ResolveError.InvalidConstantType; return cp_info.?; } pub fn innerName(self: *const InnerClass, constant_pool: []const ?CpInfo) ResolveError!?CpInfo { if (self.inner_name_index == 0) return null; const inner_name_index = self.inner_name_index - 1; if (inner_name_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; const cp_info = constant_pool[inner_name_index]; if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType; return cp_info.?; } }; pub const InnerClassFlags = enum(u16) { public = 0x0001, private = 0x0002, protected = 0x0004, static = 0x0008, final = 0x0010, interface = 0x0200, abstract = 0x0400, synthetic = 0x1000, annotation = 0x2000, @"enum" = 0x4000, }; pub const InnerClassAccessFlags = EnumFlags(InnerClassFlags); }; pub const EnclosingMethod = struct { class_index: u16, method_index: u16, pub fn class(self: *const EnclosingMethod, constant_pool: []const ?CpInfo) ResolveError!CpInfo { 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(CpInfo.Tag, cp_info.?) != .class) return ResolveError.InvalidConstantType; return cp_info.?; } pub fn method(self: *const EnclosingMethod, constant_pool: []const ?CpInfo) ResolveError!?CpInfo { if (self.method_index == 0) return null; const method_index = self.method_index - 1; if (method_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex; const cp_info = constant_pool[method_index]; if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .name_and_type) return ResolveError.InvalidConstantType; return cp_info.?; } }; pub const Synthetic = struct { }; pub const Signature = struct { signature_index: u16, pub fn signature(self: *const Signature, constant_pool: []const ?CpInfo) ResolveError!CpInfo { 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.?; } }; pub const SourceFile = struct { source_file_index: u16, pub fn sourceFile(self: *const SourceFile, constant_pool: []const ?CpInfo) ResolveError!CpInfo { 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.?; } }; 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| { for (attribute.annotations) |*annotation| { annotation.deinit(allocator); } allocator.free(attribute.annotations); }, .runtime_invisible_annotations => |attribute| { for (attribute.annotations) |*annotation| { annotation.deinit(allocator); } allocator.free(attribute.annotations); }, .runtime_visible_parameter_annotations => |attribute| { for (attribute.parameter_annotations) |parameter_annotation| { for (parameter_annotation.annotations) |*annotation| { annotation.deinit(allocator); } allocator.free(parameter_annotation.annotations); } allocator.free(attribute.parameter_annotations); }, .runtime_invisible_parameter_annotations => |attribute| { for (attribute.parameter_annotations) |parameter_annotation| { for (parameter_annotation.annotations) |*annotation| { annotation.deinit(allocator); } 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 format(self: *const AttributeInfo, w: *std.Io.Writer, depth: usize, indent: u8, allocator: std.mem.Allocator, constant_pool: []const ?CpInfo) std.Io.Writer.Error!void { try w.splatByteAll(' ', depth * indent); _ = try w.write("name:\n"); const attribute_name = self.attributeName(constant_pool) catch return error.WriteFailed; try attribute_name.format(w, depth, indent + 1, constant_pool); var predefined = self.resolve(allocator, constant_pool) catch return error.WriteFailed; if (predefined) |*p| { defer p.deinit(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(constant_pool) catch return error.WriteFailed; try constant_value.format(w, depth, indent + 1, constant_pool); }, .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 }); try w.splatByteAll(' ', depth * indent); _ = try w.write("code:\n"); var instr_indent = indent; for (attr.code, 0..) |instr, i| { instr_indent += 1; defer instr_indent -= 1; try w.splatByteAll(' ', depth * instr_indent); try w.print("{d}: 0x{x}\n", .{ i, instr }); } //try w.splatByteAll(' ', depth * indent); //_ = try w.write("exception_table:\n"); //var exception_indent = indent; //for (attr.exception_table, 0..) |*exception, i| { // exception_indent += 1; // defer exception_indent -= 1; // try w.splatByteAll(' ', depth * exception_indent); // try w.print("{d}:\n", .{ i }); // try self.formatExceptionHandler(w, depth, exception_indent + 1, exception); //} try w.splatByteAll(' ', depth * indent); _ = try w.write("attributes:\n"); var attr_indent = indent; for (attr.attributes, 0..) |*a, i| { attr_indent += 1; defer attr_indent -= 1; try w.splatByteAll(' ', depth * attr_indent); try w.print("{d}:\n", .{ i }); try a.format(w, depth, attr_indent + 1, allocator, constant_pool); } }, //.stack_map_table, .exceptions => |attr| { try w.splatByteAll(' ', depth * indent); _ = try w.write("exceptions:\n"); var exception_indent = indent; for (attr.exception_index_table, 0..) |exception, i| { exception_indent += 1; defer exception_indent -= 1; if (exception >= constant_pool.len) return error.WriteFailed; const cp_info = constant_pool[exception - 1]; if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .class) return error.WriteFailed; try w.splatByteAll(' ', depth * exception_indent); try w.print("{d}:\n", .{ i }); try cp_info.?.format(w, depth, exception_indent + 1, constant_pool); } }, .inner_classes => |attr| { try w.splatByteAll(' ', depth * indent); _ = try w.write("classes:\n"); var class_indent = indent; for (attr.classes, 0..) |*class, i| { class_indent += 1; defer class_indent -= 1; try w.splatByteAll(' ', depth * class_indent); try w.print("{d}:\n", .{ i }); try class.format(w, depth, class_indent + 1, constant_pool); } }, .enclosing_method => |attr| { const class = attr.class(constant_pool) catch return error.WriteFailed; try w.splatByteAll(' ', depth * indent); _ = try w.write("class:\n"); try class.format(w, depth, indent + 1, constant_pool); const method = attr.method(constant_pool) catch return error.WriteFailed; if (method) |m| { try w.splatByteAll(' ', depth * indent); _ = try w.write("method:\n"); try m.format(w, depth, indent + 1, constant_pool); } }, .synthetic => {}, .signature => |attr| { const signature = attr.signature(constant_pool) catch return error.WriteFailed; try signature.format(w, depth, indent + 1, constant_pool); }, .source_file => |attr| { const source_file = attr.sourceFile(constant_pool) catch return error.WriteFailed; try source_file.format(w, depth, indent + 1, constant_pool); }, //.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"); } } pub fn attributeName(self: *const AttributeInfo, constant_pool: []const ?CpInfo) ResolveError!CpInfo { 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.?; } 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)).utf8; 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 = .{ .mask = 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; } } };