/*
 * Nastroj pro vypis vsech udaju ulozenych v constant poolu v souborech .class
 *
 * Pavel Tisnovsky 2011
 *
 * Preklad;
 * gcc -ansi -Wall decompiler.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>



/* Odkomentujte pro zakaz tisku retezcovych konstant */
/*#define DONT_PRINT_STRING_ENTRIES*/



/*
 * Typy zaznamu ulozenych v constant poolu.
 * Viz dokumentace k JVM.
 */
enum
{
    CONSTANT_Class = 7,
    CONSTANT_Fieldref = 9,
    CONSTANT_Methodref = 10,
    CONSTANT_InterfaceMethodref = 11,
    CONSTANT_String = 8,
    CONSTANT_Integer = 3,
    CONSTANT_Float = 4,
    CONSTANT_Long = 5,
    CONSTANT_Double = 6,
    CONSTANT_NameAndType = 12,
    CONSTANT_Utf8 = 1
};



/*
 * Struktura reprezentujici jeden zaznam v constant poolu.
 * (nektere polozky by bylo mozne sloucit a zmensit tak pametove naroky za cenu vetsi necitelnosti)
 */
typedef struct
{
    uint8_t  tag;                /* typ zaznamu */
    uint16_t class_index;        /* odkaz na jmeno tridy */
    uint16_t name_type_index;    /* odkaz na typ metody/atributu... */
    uint16_t name_index;         /* odkaz na jmeno metody/atributu... */
    uint16_t descriptor_index;   /* odkaz na identifikaci typu */
    uint16_t string_length;      /* delka retezcoveho literalu */
    char     *string;            /* retezec (v programu se ukoncuje nulou, v constant poolu ma zadanou delku) */
    union                        /* konstanta o velikosti 4 bajty */
    {                            /* zjednoduseni pres unii, aby bylo mozne jednoduse provadet prevody */
        uint32_t read_value;     /* platne pro little endian procesory!!! */
        int32_t  int_value;      
        float    float_value;    
    } four_bytes;                
    union                        /* konstanta o velikosti 8 bajtu */
    {                            /* zjednoduseni pres unii, aby bylo mozne jednoduse provadet prevody */
        struct {
            uint32_t lower_word; /* platne pro little endian procesory */
            uint32_t higher_word;
        } read_values;
        int64_t    long_value;
        double     double_value;
    } eight_bytes;
} PoolEntry;



/*
 * Pole obsahujici cely constant pool.
 */
PoolEntry* pool_entries = NULL;



/*
 * Funkce zavolana v pripade, ze pri cteni ze souboru dojde k chybe.
 */
void read_error(FILE *fin)
{
    printf("Chyba pri cteni bajtu na offsetu %ld\n", ftell(fin));
    if (fclose(fin))
    {
        puts("... a uzavreni souboru taky selhalo!");
    }
    exit(1);
}



/*
 * Precteni jednoho bajtu ze souboru s kontrolou, zda nedoslo k chybe.
 */
uint8_t read_byte(FILE *fin)
{
    int i = fgetc(fin);
    if (i == EOF)
    {
        read_error(fin);
    }
    return i;
}



/*
 * Precteni dvou bajtu ze souboru s kontrolou, zda nedoslo k chybe.
 * V souborech .class se pouziva poradi bajtu typu big endian.
 */
uint16_t read_two_bytes(FILE *fin)
{
    int i1 = read_byte(fin);
    int i2 = read_byte(fin);
    return (i1 << 8) | (i2);
}



/*
 * Precteni ctyr bajtu ze souboru s kontrolou, zda nedoslo k chybe.
 * V souborech .class se pouziva poradi bajtu typu big endian.
 */
uint32_t read_four_bytes(FILE *fin)
{
    int i1 = read_byte(fin);
    int i2 = read_byte(fin);
    int i3 = read_byte(fin);
    int i4 = read_byte(fin);
    return (i1 << 24) | (i2 << 16) | (i3 << 8) | (i4);
}



/*
 * Otevreni souboru .class se zakladni kontrolou, zda nedoslo k chybe.
 */
FILE* open_class_file(const char *file_name)
{
    FILE *fin;

    fin = fopen(file_name, "rb");
    if (!fin)
    {
        puts("Chyba pri otevirani souboru");
        exit(1);
    }
    return fin;
}



/*
 * Nacteni a zobrazeni magicke konstanty.
 */
void process_magic_number(FILE *fin)
{
    uint32_t magic = read_four_bytes(fin);
    printf("Magicka konstanta: %08x\n", magic);
    if (magic != 0xcafebabe)
    {
        puts("Spatne magicka konstanta (jedna se skutecne o .class?)");
        fclose(fin);
        exit(1);
    }
}



/*
 * Nacteni a zobrazeni verze .class souboru.
 */
void process_class_version(FILE *fin)
{
    uint16_t minor_version;
    uint16_t major_version;

    minor_version = read_two_bytes(fin);
    major_version = read_two_bytes(fin);
    printf("Cislo verze:       %d\n", major_version);
    printf("Cislo podverze:    %d\n", minor_version);
}



/*
 * Nacteni jednoho zaznamu z constant poolu. Funkce vraci 1 nebo 2
 * podle toho, zda ma byt dalsi zaznam umisten ihned za prectenym
 * zaznamem, nebo ma byt za prectenym zaznamem "mezera" (to kvuli
 * osmibajtovym konstantam, ktere "genialne" obsazuji dva sloty)
 */
int read_constant_pool_entry(FILE *fin, PoolEntry *pool_entry)
{
    uint8_t  tag;
    uint32_t length;
    uint32_t i;

    tag = read_byte(fin);            /* nejdulezitejsi informace - typ zaznamu */
    pool_entry->tag = tag;

    switch (tag)
    {
        case CONSTANT_Class:         /* tento zaznam obsahuje jednu referenci */
            pool_entry->class_index = read_two_bytes(fin);
            break;
        case CONSTANT_Fieldref:      /* tento zaznam obsahuje dve reference */
        case CONSTANT_Methodref:
        case CONSTANT_InterfaceMethodref:
            pool_entry->class_index = read_two_bytes(fin);
            pool_entry->name_type_index = read_two_bytes(fin);
            break;
        case CONSTANT_String:        /* tento tento zaznam obsahuje jednu referenci */
            pool_entry->class_index = read_two_bytes(fin);
            break;
        case CONSTANT_Integer:       /* tento zaznam obsahuje ctyrbajtovou konstantu */
        case CONSTANT_Float:
            pool_entry->four_bytes.read_value = read_four_bytes(fin);
            break;
        case CONSTANT_Long:          /* tento zaznam obsahuje osmibajtovou konstantu */
        case CONSTANT_Double:        /* navic musi byt za zaznamem jedno volne misto */
            pool_entry->eight_bytes.read_values.higher_word = read_four_bytes(fin);
            pool_entry->eight_bytes.read_values.lower_word = read_four_bytes(fin);
            return 2;
            break;
        case CONSTANT_NameAndType:   /* tento zaznam obsahuje dve reference */
            pool_entry->name_index = read_two_bytes(fin);
            pool_entry->descriptor_index = read_two_bytes(fin);
            break;
        case CONSTANT_Utf8:          /* string v kodovani UTF-8 */
            length = read_two_bytes(fin);
            pool_entry->string_length = length;
            pool_entry->string = (char*)malloc(sizeof(char) * (length+1));
            for (i = 0; i < length; i++) /* nacteme cely retezec... */
            {
                pool_entry->string[i] = read_byte(fin);
            }
            pool_entry->string[i] = 0; /* ... a ukoncime ho nulou pro snadny tisk v printf */
            break;
        default:
            printf("Neznamy tag precteny z const. poolu: %d\n", tag);
            break;
    }
    return 1;
}



/*
 * Tisk zaznamu typu "Class".
 */
void print_class_info(PoolEntry *pool_entry)
{
    /* pouziti reference na jmeno tridy */
    char *class_name = pool_entries[pool_entry->class_index-1].string;
    printf("Class           %3d        %s", pool_entry->class_index, class_name);
}



/*
 * Tisk zaznamu typu "Field reference"
 */
void print_field_ref(PoolEntry *pool_entry)
{
    int class_index = pool_entry->class_index;
    int name_type_index = pool_entry->name_type_index;

    /* ziskani jmena tridy pres jednu referenci */
    char *class_name = pool_entries[pool_entries[class_index - 1].class_index-1].string;

    /* ziskani typu a jmena atributu pres dve reference */
    char *name = pool_entries[pool_entries[name_type_index - 1].name_index-1].string;
    char *descriptor = pool_entries[pool_entries[name_type_index - 1].descriptor_index-1].string;
    printf("FieldRef        %3d %3d    %s.%s:%s", class_index, name_type_index, class_name, name, descriptor);
}



/*
 * Tisk zaznamu typu "Method reference"
 */
void print_method_ref(PoolEntry *pool_entry)
{
    int class_index = pool_entry->class_index;
    int name_type_index = pool_entry->name_type_index;

    /* ziskani jmena tridy pres jednu referenci */
    char *class_name = pool_entries[pool_entries[class_index - 1].class_index-1].string;

    /* ziskani typu a jmena atributu pres dve reference */
    char *name = pool_entries[pool_entries[name_type_index - 1].name_index-1].string;
    char *descriptor = pool_entries[pool_entries[name_type_index - 1].descriptor_index-1].string;
    printf("MethodRef       %3d %3d    %s.%s%s", class_index, name_type_index, class_name, name, descriptor);
}



/*
 * Tisk zaznamu typu "Interface method reference"
 */
void print_interface_method_ref(PoolEntry *pool_entry)
{
    int class_index = pool_entry->class_index;
    int name_type_index = pool_entry->name_type_index;

    /* ziskani jmena tridy pres jednu referenci */
    char *class_name = pool_entries[pool_entries[class_index - 1].class_index-1].string;

    /* ziskani typu a jmena atributu pres dve reference */
    char *name = pool_entries[pool_entries[name_type_index - 1].name_index-1].string;
    char *descriptor = pool_entries[pool_entries[name_type_index - 1].descriptor_index-1].string;
    printf("IfaceMethodRef  %3d %3d    %s.%s%s", class_index, name_type_index, class_name, name, descriptor);
}



/*
 * Tisk zaznamu typu "String"
 */
void print_string_const(PoolEntry *pool_entry)
{
    /* pouziti reference na obsah retezce */
    char *string = pool_entries[pool_entry->class_index-1].string;
    printf("String const    %3d      \"%s\"", pool_entry->class_index, string);
}



/*
 * Tisk zaznamu typu "Integer constant"
 */
void print_integer_const(PoolEntry *pool_entry)
{
    printf("%-26s %d", "Integer", pool_entry->four_bytes.int_value);
}



/*
 * Tisk zaznamu typu "Float constant"
 */
void print_float_const(PoolEntry *pool_entry)
{
    printf("%-26s %f", "Float", pool_entry->four_bytes.float_value);
}



/*
 * Tisk zaznamu typu "Long constant"
 */
void print_long_const(PoolEntry *pool_entry)
{
    printf("%-26s %lld", "Long", pool_entry->eight_bytes.long_value);
}



/*
 * Tisk zaznamu typu "Double constant"
 */
void print_double_const(PoolEntry *pool_entry)
{
    printf("%-26s %f", "Double", pool_entry->eight_bytes.double_value);
}



/*
 * Tisk zaznamu typu "Type and name"
 */
void print_type_and_name(PoolEntry *pool_entry)
{
    /* zjistit oba odkazy */
    int name_index = pool_entry->name_index;
    int descriptor_index = pool_entry->descriptor_index;

    /* z odkazu ziskat oba retezce */
    char *name_string = pool_entries[name_index-1].string;
    char *descriptor_string = pool_entries[descriptor_index-1].string;
    printf("Name and type   %3d %3d    %s  %s", name_index, descriptor_index, name_string, descriptor_string);
}



/*
 * Tisk zaznamu typu "UTF-8 string"
 */
void print_utf8_string(PoolEntry *pool_entry)
{
    printf("String                     \"%s\"", pool_entry->string);
}



/*
 * Tisk hodnoty jednoho zaznamu precteneho z constant poolu.
 */
void print_constant_pool_entry(int item_index, PoolEntry *pool_entry)
{
#ifdef DONT_PRINT_STRING_ENTRIES
    if (pool_entry->tag != CONSTANT_Utf8) {
#endif
    printf("%3d  %3d  ", item_index+1, pool_entry->tag);
#ifdef DONT_PRINT_STRING_ENTRIES
    }
#endif

    switch (pool_entry->tag)
    {
        case CONSTANT_Class:
            print_class_info(pool_entry);
            break;
        case CONSTANT_Fieldref:
            print_field_ref(pool_entry);
            break;
        case CONSTANT_Methodref:
            print_method_ref(pool_entry);
            break;
        case CONSTANT_InterfaceMethodref:
            print_interface_method_ref(pool_entry);
            break;
        case CONSTANT_String:
            print_string_const(pool_entry);
            break;
        case CONSTANT_Integer:
            print_integer_const(pool_entry);
            break;
        case CONSTANT_Float:
            print_float_const(pool_entry);
            break;
        case CONSTANT_Long:
            print_long_const(pool_entry);
            break;
        case CONSTANT_Double:
            print_double_const(pool_entry);
            break;
        case CONSTANT_NameAndType:
            print_type_and_name(pool_entry);
            break;
        case CONSTANT_Utf8:
#ifndef DONT_PRINT_STRING_ENTRIES
            print_utf8_string(pool_entry);
#endif
            break;
        default:
            printf("Neznamy tag");
            break;
    }
#ifdef DONT_PRINT_STRING_ENTRIES
    if (pool_entry->tag != CONSTANT_Utf8) {
#endif
    putchar('\n');
#ifdef DONT_PRINT_STRING_ENTRIES
    }
#endif
}



/*
 * Alokace pameti pro ulozeni informaci prectenych z constant poolu s inicializaci.
 */
void create_constant_pool(int length)
{
    int i;
    /* alokace pameti pro cele dynamicke pole */
    pool_entries = (PoolEntry*) malloc(length * sizeof(PoolEntry));
    /* inicializace vsech dulezitych polozek ve strukture */
    for (i = 0; i < length; i++)
    {
        PoolEntry *pool_entry = pool_entries + i;
        pool_entry->tag = 0;
        pool_entry->class_index = 0;
        pool_entry->name_type_index = 0;
        pool_entry->name_index = 0;
        pool_entry->descriptor_index = 0;
        pool_entry->string_length = 0;
        pool_entry->string = NULL;
    }
}



/*
 * Dealokace pameti s informacemi prectenymi z constant poolu.
 */
void delete_constant_pool(int length)
{
    int i;
    /* dulezite je dealokovat vsechny retezce */
    for (i = 0; i < length; i++)
    {
        PoolEntry *pool_entry = pool_entries + i;
        if (pool_entry->string)
        {
            free(pool_entry->string);
        }
    }
    /* a nasledne vlastni pole */
    free(pool_entries);
}



/*
 * Nacteni vsech zaznamu z constant poolu a nasledne tisk jeho obsahu.
 * Tuto operaci je nutne rozdelit na dve casti, jinak by nebylo mozne
 * provadet "referencovani" mezi jednotlivymi polozkami (jedna se mnohdy
 * o dopredne reference).
 */
void process_constant_pool(FILE *fin)
{
    uint16_t constant_pool_count;
    int i;

    /* pocet polozek v constant poolu */
    constant_pool_count = read_two_bytes(fin);
    constant_pool_count--;
    printf("\nVelikost const. poolu: %d prvku\n", constant_pool_count);

    /* alokace pameti + inicializace */
    create_constant_pool(constant_pool_count);

    /* nacteni prvku je nutne provadet ve while, kvuli ne zcela
     * inteligentnimu zpusobu ulozeni konstant typu long a double.
     */
    i = 0;
    while (i < constant_pool_count)
    {
        i += read_constant_pool_entry(fin, pool_entries+i);
    }

    /* tisk polozek */
    for (i = 0; i < constant_pool_count; i++)
    {
        print_constant_pool_entry(i, pool_entries+i);
    }

    /* rucni GC :-) */
    delete_constant_pool(constant_pool_count);
}



/*
 * Zpracovani constant poolu ulozeneho v souboru .class
 */
void process_whole_class(FILE *fin)
{
    process_magic_number(fin);
    process_class_version(fin);
    process_constant_pool(fin);
}



/*
 * Vstup do programu.
 * V prvnim argumentu je ocekavano jmeno .class souboru
 */
int main(int argc, char** argv)
{
    FILE *fin;
    if (argc != 2)
    {
        puts("Pouziti: a.out trida.class");
        return 1;
    }
    fin = open_class_file(argv[1]);
    process_whole_class(fin);
    if (fclose(fin))
    {
        puts("Chyba pri zavirani souboru");
        return 1;
    }
    return 0;
}

/* finito */