Files
2026-03-30 19:20:11 +00:00

299 lines
11 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "classfile.h"
static uint8_t read_u1(FILE *f) {
return (uint8_t)fgetc(f);
}
static uint16_t read_u2(FILE *f) {
uint16_t a = read_u1(f), b = read_u1(f);
return (a << 8) | b;
}
static uint32_t read_u4(FILE *f) {
uint32_t a = read_u2(f), b = read_u2(f);
return (a << 16) | b;
}
static uint64_t read_u8(FILE *f) {
uint64_t a = read_u4(f), b = read_u4(f);
return (a << 32) | b;
}
static void skip_attr(FILE *f) {
uint32_t len = read_u4(f);
fseek(f, len, SEEK_CUR);
}
static void parse_code_attr(FILE *f, CodeAttr *code) {
read_u4(f); /* attribute_length (ignored) */
code->max_stack = read_u2(f);
code->max_locals = read_u2(f);
code->code_len = read_u4(f);
code->code = malloc(code->code_len);
fread(code->code, 1, code->code_len, f);
code->exception_table_len = read_u2(f);
code->exception_table = NULL;
if (code->exception_table_len > 0) {
code->exception_table = malloc(
code->exception_table_len * sizeof(ExceptionHandler));
for (int i = 0; i < code->exception_table_len; i++) {
code->exception_table[i].start_pc = read_u2(f);
code->exception_table[i].end_pc = read_u2(f);
code->exception_table[i].handler_pc = read_u2(f);
code->exception_table[i].catch_type = read_u2(f);
}
}
/* skip Code sub-attributes (LineNumberTable, LocalVariableTable …) */
uint16_t attr_count = read_u2(f);
for (int i = 0; i < attr_count; i++) {
read_u2(f); /* attribute_name_index */
skip_attr(f);
}
}
static int parse_constant_pool(FILE *f, ClassFile *cf) {
cf->cp = calloc(cf->cp_count, sizeof(CPEntry));
if (!cf->cp) return 0;
/* spec: index starts at 1; long/double consume two slots */
for (int i = 1; i < cf->cp_count; i++) {
CPEntry *e = &cf->cp[i];
e->tag = read_u1(f);
switch (e->tag) {
case CP_UTF8: {
e->utf8.len = read_u2(f);
e->utf8.bytes = malloc(e->utf8.len + 1);
fread(e->utf8.bytes, 1, e->utf8.len, f);
e->utf8.bytes[e->utf8.len] = '\0';
break;
}
case CP_INTEGER:
e->integer.value = (int32_t)read_u4(f);
break;
case CP_FLOAT: {
uint32_t raw = read_u4(f);
memcpy(&e->flt.value, &raw, 4);
break;
}
case CP_LONG:
e->lng.value = (int64_t)read_u8(f);
i++; /* long takes two CP slots */
break;
case CP_DOUBLE: {
uint64_t raw = read_u8(f);
memcpy(&e->dbl.value, &raw, 8);
i++; /* double takes two CP slots */
break;
}
case CP_CLASS:
case CP_STRING:
e->cls_or_str.index = read_u2(f);
break;
case CP_FIELDREF:
case CP_METHODREF:
case CP_IFACE_METHODREF:
e->ref.class_index = read_u2(f);
e->ref.name_and_type_index = read_u2(f);
break;
case CP_NAME_AND_TYPE:
e->nat.name_index = read_u2(f);
e->nat.descriptor_index = read_u2(f);
break;
default:
fprintf(stderr, "classfile: unknown CP tag %d at index %d\n",
e->tag, i);
return 0;
}
}
return 1;
}
/* ── parse fields ───────────────────────────────────────────────────────── */
static int parse_fields(FILE *f, ClassFile *cf) {
cf->fields = calloc(cf->fields_count, sizeof(Field));
if (!cf->fields && cf->fields_count > 0) return 0;
for (int i = 0; i < cf->fields_count; i++) {
cf->fields[i].access_flags = read_u2(f);
cf->fields[i].name_index = read_u2(f);
cf->fields[i].descriptor_index = read_u2(f);
/* skip field attributes (ConstantValue, etc.) */
uint16_t ac = read_u2(f);
for (int j = 0; j < ac; j++) {
read_u2(f);
skip_attr(f);
}
}
return 1;
}
/* ── parse methods ──────────────────────────────────────────────────────── */
static int parse_methods(FILE *f, ClassFile *cf) {
cf->methods = calloc(cf->methods_count, sizeof(Method));
if (!cf->methods && cf->methods_count > 0) return 0;
for (int i = 0; i < cf->methods_count; i++) {
Method *m = &cf->methods[i];
m->access_flags = read_u2(f);
m->name_index = read_u2(f);
m->descriptor_index = read_u2(f);
m->has_code = 0;
uint16_t ac = read_u2(f);
for (int j = 0; j < ac; j++) {
uint16_t attr_name_idx = read_u2(f);
const char *attr_name = cp_utf8(cf, attr_name_idx);
if (strcmp(attr_name, "Code") == 0) {
parse_code_attr(f, &m->code);
m->has_code = 1;
} else {
skip_attr(f);
}
}
}
return 1;
}
/* ── public: load a .class file ─────────────────────────────────────────── */
ClassFile *classfile_load(const char *path) {
FILE *f = fopen(path, "rb");
if (!f) {
perror(path);
return NULL;
}
uint32_t magic = read_u4(f);
if (magic != 0xCAFEBABE) {
fprintf(stderr, "%s: not a class file (magic=0x%08X)\n", path, magic);
fclose(f);
return NULL;
}
ClassFile *cf = calloc(1, sizeof(ClassFile));
cf->minor_version = read_u2(f);
cf->major_version = read_u2(f);
/* constant pool */
cf->cp_count = read_u2(f);
if (!parse_constant_pool(f, cf)) goto fail;
cf->access_flags = read_u2(f);
cf->this_class = read_u2(f);
cf->super_class = read_u2(f);
/* interfaces */
cf->interfaces_count = read_u2(f);
cf->interfaces = malloc(cf->interfaces_count * sizeof(uint16_t));
for (int i = 0; i < cf->interfaces_count; i++)
cf->interfaces[i] = read_u2(f);
/* fields */
cf->fields_count = read_u2(f);
if (!parse_fields(f, cf)) goto fail;
/* methods */
cf->methods_count = read_u2(f);
if (!parse_methods(f, cf)) goto fail;
/* skip class-level attributes (SourceFile, etc.) */
uint16_t ac = read_u2(f);
for (int i = 0; i < ac; i++) {
read_u2(f);
skip_attr(f);
}
fclose(f);
return cf;
fail:
fclose(f);
classfile_free(cf);
return NULL;
}
/* ── public: find a method by name + descriptor ─────────────────────────── */
Method *classfile_find_method(ClassFile *cf, const char *name, const char *descriptor) {
for (int i = 0; i < cf->methods_count; i++) {
const char *n = cp_utf8(cf, cf->methods[i].name_index);
const char *d = cp_utf8(cf, cf->methods[i].descriptor_index);
if (strcmp(n, name) == 0 && strcmp(d, descriptor) == 0)
return &cf->methods[i];
}
return NULL;
}
/* ── public: free everything ────────────────────────────────────────────── */
void classfile_free(ClassFile *cf) {
if (!cf) return;
if (cf->cp) {
for (int i = 1; i < cf->cp_count; i++)
if (cf->cp[i].tag == CP_UTF8)
free(cf->cp[i].utf8.bytes);
free(cf->cp);
}
if (cf->interfaces) free(cf->interfaces);
if (cf->fields) free(cf->fields);
if (cf->methods) {
for (int i = 0; i < cf->methods_count; i++) {
if (cf->methods[i].has_code) {
free(cf->methods[i].code.code);
free(cf->methods[i].code.exception_table);
}
}
free(cf->methods);
}
free(cf);
}
/* ── public: debug dump ─────────────────────────────────────────────────── */
void classfile_dump(const ClassFile *cf) {
printf("Class file version: %d.%d\n",
cf->major_version, cf->minor_version);
printf("This class : %s\n",
cp_utf8(cf, cf->cp[cf->this_class].cls_or_str.index));
printf("Super class: %s\n",
cf->super_class
? cp_utf8(cf, cf->cp[cf->super_class].cls_or_str.index)
: "(none)");
printf("\nConstant pool (%d entries):\n", cf->cp_count - 1);
for (int i = 1; i < cf->cp_count; i++) {
CPEntry *e = &cf->cp[i];
printf(" #%-3d ", i);
switch (e->tag) {
case CP_UTF8: printf("Utf8 \"%s\"\n", e->utf8.bytes); break;
case CP_INTEGER: printf("Integer %d\n", e->integer.value); break;
case CP_FLOAT: printf("Float %f\n", e->flt.value); break;
case CP_LONG: printf("Long %lld\n", (long long)e->lng.value); break;
case CP_DOUBLE: printf("Double %f\n", e->dbl.value); break;
case CP_CLASS: printf("Class #%d\n", e->cls_or_str.index); break;
case CP_STRING: printf("String #%d\n", e->cls_or_str.index); break;
case CP_FIELDREF:
printf("Fieldref #%d.#%d\n",
e->ref.class_index, e->ref.name_and_type_index); break;
case CP_METHODREF:
printf("Methodref #%d.#%d\n",
e->ref.class_index, e->ref.name_and_type_index); break;
case CP_NAME_AND_TYPE:
printf("NameAndType #%d:#%d\n",
e->nat.name_index, e->nat.descriptor_index); break;
default: printf("(tag %d)\n", e->tag);
}
}
printf("\nMethods (%d):\n", cf->methods_count);
for (int i = 0; i < cf->methods_count; i++) {
Method *m = &cf->methods[i];
printf(" %s %s [code=%s, max_stack=%d, max_locals=%d, len=%d]\n",
cp_utf8(cf, m->name_index),
cp_utf8(cf, m->descriptor_index),
m->has_code ? "yes" : "no",
m->has_code ? m->code.max_stack : 0,
m->has_code ? m->code.max_locals : 0,
m->has_code ? (int)m->code.code_len : 0);
}
}