Hlavní navigace

Pohled pod kapotu JVM – tvorba programových smyček s využitím nástroje Javassist

Pavel Tišnovský 27. 8. 2013

V dnešní části seriálu o programovacím jazyce Java i o virtuálním stroji Javy si ukážeme, jak lze s využitím nástroje Javassist tvořit bajtkód metod obsahujících programové smyčky. Uvidíme, že nejproblematičtějším úkolem je v tomto případě výpočet cílů nepodmíněných i podmíněných skoků.

Obsah

1. Pohled pod kapotu JVM – tvorba programových smyček s využitím nástroje Javassist

2. Ukázky různých způsobů generování bajtkódu počítané programové smyčky for

3. Generování metody s počítanou programovou smyčkou for v Javassistu

4. Výpočet cílů podmíněných a nepodmíněných skoků

5. Tvorba bajtkódu programové smyčky – složitější varianta

6. Tvorba bajtkódu programové smyčky – jednodušší varianta

7. Vylepšený výpis struktur bajtkódů obou testovacích metod loopTest1()loopTest2()

8. Úplný zdrojový kód demonstračního příkladu ClassGenerationTest9

9. Výstup demonstračního příkladu ClassGenerationTest9

10. Bajtkód třídy GeneratedClass9

11. Repositář se zdrojovými kódy dnešního demonstračního příkladu ClassGenerationTest9

12. Odkazy na Internetu

1. Pohled pod kapotu JVM – tvorba programových smyček s využitím nástroje Javassist

V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy si ukážeme, jak je možné nástroj Javassist využít pro generování bajtkódu metod, v jejichž těle se nachází počítané programové smyčky či běžné smyčky s podmínkou testovanou na začátku či na konci každé iterace. Navážeme tak na předchozí část, v níž jsme „pouze“ upravili již existující bajtkód metody s programovou smyčkou. V současné verzi Javassistu sice není tvorba programových smyček zcela jednoduchá, ovšem uvidíme, že i přes některá omezení, spočívající v nutnosti použít nízkoúrovňové operace, je Javassist možné k této činnosti využít. Jen pro úplnost si připomeňme, jaké instrukce se v bajtkódu virtuálního stroje Javy používají v případě potřeby provést nepodmíněný či podmíněný skok. Tabulka zobrazená pod tímto odstavcem vznikla sloučením tabulek popsaných v předchozí části tohoto seriálu:

# Instrukce Opkód Operandy Popis
1 goto 0×A7 highbyte, lowbyte přímý skok na adresu uloženou v dvojici operandů: highbyte*256+lowbyte
2 goto_w 0×C8 byte1,byte2,byte3 byte4 přímý skok na adresu uloženou ve čtveřici operandů: byte1*224+byte2*216+byte3*28+by­te4
3 ifeq 0×99 highbyte, lowbyte TOS=0 skok na lokální adresu highbyte*256+lowbyte při splnění podmínky
4 ifne 0×9A highbyte, lowbyte TOS≠0 skok na lokální adresu highbyte*256+lowbyte při splnění podmínky
5 iflt 0×9B highbyte, lowbyte TOS<0 skok na lokální adresu highbyte*256+lowbyte při splnění podmínky
6 ifge 0×9C highbyte, lowbyte TOS≥0 skok na lokální adresu highbyte*256+lowbyte při splnění podmínky
7 ifgt 0×9D highbyte, lowbyte TOS>0 skok na lokální adresu highbyte*256+lowbyte při splnění podmínky
8 ifle 0×9E highbyte, lowbyte TOS≤0 skok na lokální adresu highbyte*256+lowbyte při splnění podmínky
9 if_icmpeq 0×9F highbyte, lowbyte value1=value2 skok na adresu highbyte*256+lowbyte při splnění podmínky
10 if_icmpne 0×A0 highbyte, lowbyte value1≠value2 skok na adresu highbyte*256+lowbyte při splnění podmínky
11 if_icmplt 0×A1 highbyte, lowbyte value1<value2 skok na adresu highbyte*256+lowbyte při splnění podmínky
12 if_icmpge 0×A2 highbyte, lowbyte value1≥value2 skok na adresu highbyte*256+lowbyte při splnění podmínky
13 if_icmpgt 0×A3 highbyte, lowbyte value1>value2 skok na adresu highbyte*256+lowbyte při splnění podmínky
14 if_icmple 0×A4 highbyte, lowbyte value1≤value2 skok na adresu highbyte*256+lowbyte při splnění podmínky

Jak si řekneme v navazujícím textu, jsou adresy cílů skoků uvedeny relativně vůči aktuálnímu indexu instrukce, což je velmi důležité při výpočtu cíle skoku.

2. Ukázky různých způsobů generování bajtkódu počítané programové smyčky for

Nyní se dostáváme k poměrně důležité problematice týkající se způsobu překladu zdrojových kódů Javy do bajtkódu. Obecně je možné říci, že se překladače Javy nesnaží o provádění žádných zásadních optimalizací, protože výsledný bajtkód není ve většině případů interpretován, ale následně přeložen do nativního zdrojového kódu pomocí JIT překladače. I z tohoto důvodu je většinou generovaný bajtkód poměrně snadno čitelný a dobře odráží původní algoritmus napsaný přímo v Javě. Ovšem určitý problém spočívá v tom, že i přes přesnou definici syntaxe a sémantiky programovacího jazyka Java (Java Language Specification – JLS) i přesnou specifikaci bajtkódu a významu jednotlivých instrukcí není v žádné normě popsáno, jakým způsobem se mají jednotlivé bloky zdrojového kódu zkompilovat do sekvence instrukcí JVM. To jinými slovy znamená, že každý překladač Javy může generovat odlišné sekvence instrukcí a přitom bude stále splňovat jak JLS, tak i JVM Specification (na tuto vlastnost se nelze dívat jako na nedostatek, ale spíše na svobodu volby tvůrců překladačů).

Ukažme si však konkrétní příklad, například jednoduchou počítanou smyčku for:

/**
 * Testovaci trida.
 */
public class LoopTest {
 
    private static void loopTest1() {
        int i = 0;
        while (i < 10) {
            System.out.println("Hello world!");
            i++;
        }
    }
 
}

Překladač javac vytvoří následující sekvenci instrukcí, v níž se test počitadla smyčky na koncovou hodnotu provádí v instrukcích 2, 3 a 5, zatímco na konci smyčky můžeme nalézt nepodmíněný skok goto:

private static void loopTest1();
  Code:
   0:           iconst_0
   1:           istore_0
   2:           iload_0
   3:           bipush          10
   5:           if_icmpge       22
   8:           getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   11:          ldc             #3; //String Hello world!
   13:          invokevirtual   #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   16:          iinc            0, 1
   19:          goto            2
   22:          return

Naproti tomu ecj (záleží ovšem na verzi) vytvoří poněkud odlišný kód, v němž je test na hodnotu počitadla uveden na konci smyčky, což ale znamená, že ihned na počátku (před první iterací) je nutné se pomocí nepodmíněného skoku na tento test přesunout:

private static void loopTest1();
  Code:
   0:           iconst_0
   1:           istore_0
   2:           goto            16
   5:           getstatic       #20; //Field java/lang/System.out:Ljava/io/PrintStream;
   8:           ldc             #22; //String Hello world!
   10:          invokevirtual   #28; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   13:          iinc            0, 1
   16:          iload_0
   17:          bipush          10
   19:          if_icmplt       5
   22:          return

Povšimněte si, že v obou případech má bajtkód délku 23 bajtů a obsahuje jeden nepodmíněný skok goto a jeden skok podmíněný, a to buď if_icmpge nebo k němu inverzní test if_icmplt.

3. Generování metody s počítanou programovou smyčkou for v Javassistu

V této kapitole i v kapitolách následujících jsou popsány některé důležité metody, které jsou součástí demonstračního příkladu nazvaného ClassGenerationTest9. Tento příklad po svém spuštění vytvoří bajtkód nové třídy GeneratedClass9 se statickými metodami main(), loopTest1()loopTest2(). Obě zmíněné metody loopTest*() po svém zavolání vytisknou deset řádků obsahujících shodný textový řetězec. Bajtkódy těchto metod jsou ve skutečnosti prakticky shodné, ovšem pro jejich vytvoření použijeme nejdříve složitější postup s nízkoúrovňovými operacemi a u druhé metody pak postup, v němž se bude používat vyšší úroveň abstrakce. Bajtkódy metod jsou stále generovány stejným způsobem – nejprve se vytvoří vlastní záznam o metodě, včetně údajů o jejích příznacích, návratovém typu i typu jednotlivých parametrů a posléze se každé metodě přiřadí atribut „CODE“ s vlastním bajtkódem.

Vytvoření metody loopTest1():

    /**
     * Vytvoreni staticke metody loopTest1() bez navratove hodnoty.
     * Instrukce tvorici telo metody jsou vytvoreny s vyuzitim tridy Bytecode.
     *
     * @param generatedClass
     *            predstavuje vytvarenou tridu
     * @throws CannotCompileException
     *             vyhozena v pripade chyby ve zdrojovem kodu
     */
    private static void constructMethodLoopTest1(CtClass generatedClass) throws CannotCompileException {
        MethodInfo methodInfo = prepareMethod(generatedClass, "loopTest1");
 
        // vytvoreni tela metody
        ConstPool constPool = methodInfo.getConstPool();
        Bytecode bytecode = generateBytecodeForMethodLoopTest1(constPool);
        addCodeAttributeToGeneratedMethod(methodInfo, bytecode);
    }

Vytvoření metody loopTest2() je prakticky shodné:

    /**
     * Vytvoreni staticke metody loopTest2() bez navratove hodnoty.
     * Instrukce tvorici telo metody jsou vytvoreny s vyuzitim tridy Bytecode.
     *
     * @param generatedClass
     *            predstavuje vytvarenou tridu
     * @throws CannotCompileException
     *             vyhozena v pripade chyby ve zdrojovem kodu
     */
    private static void constructMethodLoopTest2(CtClass generatedClass) throws CannotCompileException {
        MethodInfo methodInfo = prepareMethod(generatedClass, "loopTest2");
 
        // vytvoreni tela metody
        ConstPool constPool = methodInfo.getConstPool();
        Bytecode bytecode = generateBytecodeForMethodLoopTest2(constPool);
        addCodeAttributeToGeneratedMethod(methodInfo, bytecode);
    }

Uživatelská metoda prepareMethod() je taktéž shodná pro vytvoření loopTest1()loopTest2(), protože se v ní nastavují shodné modifikátory, stejný návratový typ (void) i stejné typy parametrů (nulový počet):

    /**
     * Priprava bajtkodu metody.
     * 
     * @param generatedClass
     *            predstavuje vytvarenou tridu
     * @param methodName
     *            jmeno vytvarene metody
     * @throws CannotCompileException
     *             vyhozena v pripade chyby ve zdrojovem kodu
     */
    private static MethodInfo prepareMethod(CtClass generatedClass, String methodName) throws CannotCompileException {
        CtClass returnType = CtClass.voidType;
        CtClass[] parameterTypes = {};
 
        // u metody je nutne znat jeji jmeno, navratovou hodnotu i typy parametru
        CtMethod loopTestMethod = new CtMethod(returnType, methodName, parameterTypes, generatedClass);
 
        // zmena modifikatoru
        loopTestMethod.setModifiers(Modifier.STATIC | Modifier.PUBLIC);
 
        // telo metody
        generatedClass.addMethod(loopTestMethod);
 
        MethodInfo methodInfo = loopTestMethod.getMethodInfo();
        return methodInfo;
    }

Přiřazení atributu „CODE“ k vygenerované metodě je velmi jednoduchý úkon:

    /**
     * Pridani atributu "CODE" k vygenerovane metode.
     *
     * @param methodInfo
     * @param bytecode
     */
    private static void addCodeAttributeToGeneratedMethod(MethodInfo methodInfo, Bytecode bytecode) {
        CodeAttribute codeAttribute = bytecode.toCodeAttribute();
 
        methodInfo.setCodeAttribute(codeAttribute);
    }

4. Výpočet cílů podmíněných a nepodmíněných skoků

Jak jsme si již ukázali ve druhé kapitole, nevyhneme se při implementaci počítaných programových smyček použití nepodmíněných i podmíněných skoků, u nichž je nutné vhodným způsobem vypočítat jejich cíl, tj. adresu, na kterou se má skok provést. Připomeňme si, že instrukce jsou adresovány v každé metodě od nuly, protože ve virtuálním stroji Javy neexistuje přímo adresovatelný globální adresový prostor. Navíc jsou cíle skoků zapsány ve formě offsetů, tj. relativních adres, které je nutné nejprve připočíst k adrese zpracovávané instrukce, abychom získali adresu absolutní (přesněji řečeno absolutní v rámci lokálního adresního prostoru metody). Jedním z problémů, které musíme vyřešit, je tedy výpočet již zmíněného offsetu. K tomu nám dopomůže metoda Bytecode.currentPc() vracející index právě zapsané instrukce. Tento index lze uložit do pomocné proměnné a následně ho na vhodném místě využít pro výpočet offsetu.

Dále musíme využít dvě nové metody: Bytecode.addGap(počet) pro vložení neinicializovaných bajtů do bajtkódu a metodu Bytecode.write(index, data) pro zápis hodnoty bajtu do bajtkódu na adresu určenou indexem. Díky existenci této metody je možné se vrátit k oblasti vyplněné pomocí Bytecode.addGap() a uložit na toto místo vypočtenou adresu cíle skoku.

Podívejme se nyní na způsob vložení instrukce GOTO provádějící dopředný skok. Při dopředném skoku ještě nemusíme přesně vědět hodnotu offsetu, to však není nutné. Nejprve vložíme operační kód instrukce GOTO, za níž ponecháme dva volné bajty. Současně si zapamatujeme index instrukce GOTO (to pro výpočet offsetu) i index prvního volného bajtu:

        // za instrukci GOTO nasleduje 16bitovy cil skoku
        // - tyto dva bajty prozatim preskocime
        // - a zapamatujeme si jejich index pro pozdejsi inicializaci
        int gotoInstructionIndex = bytecode.currentPc();
        bytecode.addOpcode(Opcode.GOTO);
        int gotoOperandIndex = bytecode.currentPc();
        bytecode.addGap(2);

Na místě, kam má skok směřovat, opět získáme hodnotu indexu:

        // ted jiz vime, ze sem bude smerovat cil skoku
        int gotoDestinationPC = bytecode.currentPc();

Na závěr generování bajtkódu již snadno vypočteme offset a zapíšeme jeho dva bajty do výplně za instrukcí GOTO. Připomeňme si, že offset může být jak kladný (dopředný skok), tak i záporný (skok zpět):

        // nyni je jiz mozne vyplnit cilovou adresu skoku
        int offset = gotoDestinationPC - gotoInstructionIndex;
        bytecode.write(gotoOperandIndex, offset >> 8);
        bytecode.write(gotoOperandIndex+1, offset);

5. Tvorba bajtkódu programové smyčky – složitější varianta

V této chvíli již máme všechny potřebné znalosti nutné pro vytvoření bajtkódu metod loopTest1()loopTest2(). Metoda loopTest1() bude (zejména kvůli studijním účelům) vytvořena složitějším způsobem, v němž jsou použity především nízkoúrovňové operace, které přímo odpovídají jedenácti instrukcím bajtkódu, s jejichž pomocí má být metoda vytvořena:

   0:           iconst_0
   1:           istore_0
   2:           goto            16
   5:           getstatic       #14; //Field java/lang/System.out:Ljava/io/PrintStream;
   8:           ldc             #16; //String Hello
   10:          invokevirtual   #22; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   13:          iinc            0, 1
   16:          iload_0
   17:          bipush          10
   19:          if_icmplt       5
   22:          return

Povšimněte si, jakým způsobem je uvedených jedenáct instrukcí generováno. Jedinou problematičtější část tvoří výpočet cílů skoků:

    /**
     * Vytvoreni bajtkodu predstavujiciho sekvenci instrukci pro metodu int
     * loopTest1().
     * 
     * @param constPool
     *            tabulka konstant pouzivana metodou
     * @return bajtkod predstavujici sekvenci instrukci pro metodu int foo()
     */
    private static Bytecode generateBytecodeForMethodLoopTest1(ConstPool constPool)
    {
        // bajtkod by mel odpovidat nasledujicimu kodu
        //         for (int i = 0; i < 10; i++) {
        //             System.out.println("Hello");
        //         }
 
        // coz zhruba odpovida sekvenci instrukci
        //        0:   iconst_0
        //        1:   istore_0
        //        2:   goto    16
        //        5:   getstatic       #20; //Field java/lang/System.out:Ljava/io/PrintStream;
        //        8:   ldc             #42; //String Hello world!
        //        10:  invokevirtual   #28; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
        //        13:  iinc    0, 1
        //        16:  iload_0
        //        17:  bipush  10
        //        19:  if_icmplt       5
        //        22:  return
 
        final int stackSize = 1;
        final int localVars = 1;
        Bytecode bytecode = new Bytecode(constPool, stackSize, localVars);
        bytecode.addOpcode(Opcode.ICONST_0);
        bytecode.addOpcode(Opcode.ISTORE_0);
 
        // za instrukci GOTO nasleduje 16bitovy cil skoku
        // - tyto dva bajty prozatim preskocime
        // - a zapamatujeme si jejich index pro pozdejsi inicializaci
        int gotoInstructionIndex = bytecode.currentPc();
        bytecode.addOpcode(Opcode.GOTO);
        int gotoOperandIndex = bytecode.currentPc();
        bytecode.addGap(2);
 
        // v teto promenne bude ulozen index prvni instrukce smycky
        int loopStartIndex = bytecode.currentPc();
        bytecode.addGetstatic("java.lang.System", "out", "Ljava/io/PrintStream;");
        bytecode.addLdc("Hello");
        bytecode.addInvokevirtual("java.io.PrintStream", "println", "(Ljava/lang/String;)V");
 
        // zvyseni hodnoty pocitadla o jednicku
        bytecode.addOpcode(Opcode.IINC);
        bytecode.add(0, 1);
        // ted jiz vime, ze sem bude smerovat cil skoku
        int gotoDestinationPC = bytecode.currentPc();
        bytecode.addOpcode(Opcode.ILOAD_0);
        bytecode.addOpcode(Opcode.BIPUSH);
        bytecode.addOpcode(10);
        int currentPC = bytecode.currentPc();
 
        // na konci smycky je umisten podmineny skok
        bytecode.addOpcode(Opcode.IF_ICMPLT);
        // vypocet cile podmineneho skoku -> zacatek smycky
        bytecode.addIndex(loopStartIndex - currentPC);
 
        // instrukce Return
        bytecode.addOpcode(Opcode.RETURN);
 
        // nyni je jiz mozne vyplnit cilovou adresu skoku
        int offset = gotoDestinationPC - gotoInstructionIndex;
        bytecode.write(gotoOperandIndex, offset >> 8);
        bytecode.write(gotoOperandIndex+1, offset);
 
        // finito
        return bytecode;
    }

Podmíněný skok if_icmplt je prováděn směrem vzad, tudíž není nutné pro cíl skoku rezervovat žádné bajty ani do nich zpětně zapisovat pomocí Bytecode.write(). Namísto toho je možné využít praktičtější metodu Bytecode.addIndex() akceptující šestnáctibitový index.

6. Tvorba bajtkódu programové smyčky – jednodušší varianta

Jak jsme si již naznačili v předchozích kapitolách, je bajtkód metody loopTest2() sice shodný s bajtkódem metody loopTest1(), ovšem při jeho generování jsou využity poněkud abstraktnější operace. Týká se to zejména náhrady volání Bytecode.addOpcode(Opcode.ICONST0) apod. za obecnější Bytecode.addIconst(0). Nástroj Javassist sám na základě parametru rozhodne, jaká instrukce se použije, což platí i pro Bytecode.addIconst(10), což se přeloží na instrukci BIPUSH 10. Podobně je zjednodušena tvorba bajtkódu pro volání metody System.out.println() či pro návrat z metody instrukcí RETURN. Výpočet cílů skoků však není zjednodušen a stále je nutné používat pomocné proměnné s uloženými indexy instrukcí a/nebo jejich operandů:

    /**
     * Vytvoreni bajtkodu predstavujiciho sekvenci instrukci pro metodu int
     * loopTest1().
     * 
     * @param constPool
     *            tabulka konstant pouzivana metodou
     * @return bajtkod predstavujici sekvenci instrukci pro metodu int foo()
     */
    private static Bytecode generateBytecodeForMethodLoopTest2(ConstPool constPool)
    {
        // bajtkod by mel odpovidat nasledujicimu kodu
        //         for (int i = 0; i < 10; i++) {
        //             System.out.println("world!");
        //         }
 
        // coz zhruba odpovida sekvenci instrukci
        //        0:   iconst_0
        //        1:   istore_0
        //        2:   goto    16
        //        5:   getstatic       #20; //Field java/lang/System.out:Ljava/io/PrintStream;
        //        8:   ldc             #42; //String Hello world!
        //        10:  invokevirtual   #28; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
        //        13:  iinc    0, 1
        //        16:  iload_0
        //        17:  bipush  10
        //        19:  if_icmplt       5
        //        22:  return
 
        final int stackSize = 1;
        final int localVars = 1;
        Bytecode bytecode = new Bytecode(constPool, stackSize, localVars);
        bytecode.addIconst(0);
        bytecode.addIstore(0);
 
        // za instrukci GOTO nasleduje 16bitovy cil skoku
        // - tyto dva bajty prozatim preskocime
        // - a zapamatujeme si jejich index pro pozdejsi inicializaci
        int gotoInstructionIndex = bytecode.currentPc();
        bytecode.addOpcode(Opcode.GOTO);
        int gotoOperandIndex = bytecode.currentPc();
        bytecode.addGap(2);
 
        // v teto promenne bude ulozen index prvni instrukce smycky
        int loopStartIndex = bytecode.currentPc();
        bytecode.addPrintln("world!");
 
        // zvyseni hodnoty pocitadla o jednicku
        bytecode.addOpcode(Opcode.IINC);
        bytecode.add(0, 1);
        // ted jiz vime, ze sem bude smerovat cil skoku
        int gotoDestinationPC = bytecode.currentPc();
        bytecode.addIload(0);
        // pro hodnotu 10 se vygeneruje instrukce BIPUSH 10
        bytecode.addIconst(10);
 
        // tuto hodnotu potrebujeme pro vypocet cile podmineneho skoku
        // (pocita se relativne vuci teto instrukci)
        int currentPC = bytecode.currentPc();
 
        // na konci smycky je umisten podmineny skok
        bytecode.addOpcode(Opcode.IF_ICMPLT);
        // vypocet cile podmineneho skoku -> zacatek smycky
        bytecode.addIndex(loopStartIndex - currentPC);
 
        // instrukce Return
        bytecode.addReturn(CtClass.voidType);
 
        // nyni je jiz mozne vyplnit cilovou adresu skoku
        int offset = gotoDestinationPC - gotoInstructionIndex;
        bytecode.write(gotoOperandIndex, offset >> 8);
        bytecode.write(gotoOperandIndex+1, offset);
 
        // finito
        return bytecode;
    }

7. Vylepšený výpis struktur bajtkódů obou testovacích metod loopTest1()loopTest2()

Součástí dnešního demonstračního příkladu bude i výpis bajtkódu všech vygenerovaných metod. Na rozdíl od nástroje javap však budeme pro větší názornost potřebovat, aby se kromě mnemotechnických kódů jednotlivých instrukcí zobrazily i hexadecimální hodnoty bajtů tvořících jak kód instrukce, tak i její operand či operandy. Díky tomu si budeme moci lépe ukázat, jak jsou zakódovány cíle nepodmíněných i podmíněných skoků, například:

03                      iconst_0
3b                      istore_0
a7 00 0e                goto
b2 00 0e                getstatic
12 10                   ldc
b6 00 16                invokevirtual
84 00 01                iinc
1a                      iload_0
10 0a                   bipush
a1 ff f2                if_icmplt
b1                      return

Uživatelská metoda printMethodStructures() je ve skutečnosti velmi jednoduchá, protože pro všechny tři zkoumané metody zavolá další uživatelskou metodu printMethodStructure():

    /**
     * Vypis struktury vybranych metod z generovane tridy.
     * 
     * @param generatedClass
     *            predstavuje vytvarenou tridu
     * @throws NotFoundException
     *             vyhozena, pokud metoda nebyla nalezena
     * @throws BadBytecode 
     */
    private static void printMethodStructures(CtClass generatedClass) throws NotFoundException, BadBytecode {
        printMethodStructure(generatedClass, "main");
        printMethodStructure(generatedClass, "loopTest1");
        printMethodStructure(generatedClass, "loopTest2");
    }

V uživatelské metodě nazvané printMethodStructure() se nejprve získá instance třídy CtMethod představující obraz zkoumané metody. Následně se přes volání CtMethod.getMethodInfo() získá instance třídy MethodInfo obsahující, jak již název této třídy napovídá, informace o zkoumané metodě. My využijeme následující čtveřici getterů, které jsou ve třídě MethodInfo deklarovány:

# Metoda Popis
1 javassist.bytecode.MethodInfo.getName() vrátí jméno metody
2 javassist.bytecode.MethodIn­fo.getDescriptor() vrátí deskriptor metody (část její signatury)
3 javassist.bytecode.MethodIn­fo.getAccessFlags() vrátí přístupová práva a další modifikátory metody (STATIC…)
4 javassist.bytecode.MethodIn­fo.getCodeAttribute() vrátí atribut metody reprezentující její tělo

V následujícím úryvku zdrojového kódu si povšimněte, jak lze velmi snadno převést modifikátory metody na řetězec s využitím Modifier.toString():

    /**
     * Vypis struktury vybrane metody.
     * 
     * @param generatedClass
     *            predstavuje vytvarenou tridu
     * @param methodName
     *            jmeno metody, jejiz struktura se ma vypsat
     * @throws NotFoundException
     *             vyhozena, pokud metoda nebyla nalezena
     * @throws BadBytecode
     *             vyhozena, pokud se nalezne neplatna instrukce v bytekodu
     */
    private static void printMethodStructure(CtClass generatedClass, String methodName) throws NotFoundException, BadBytecode {
        System.out.println("Method '" + methodName + "' structure:");
        CtMethod method = generatedClass.getDeclaredMethod(methodName);
        if (method == null) {
            System.out.println("   not found!");
            return;
        }
        MethodInfo methodInfo = method.getMethodInfo();
        System.out.println("    real name:    " + methodInfo.getName());
        System.out.println("    descriptor:   " + methodInfo.getDescriptor());
        System.out.println("    access flags: " + Modifier.toString(methodInfo.getAccessFlags()));
        System.out.println("    method body:");
        printMethodBody(methodInfo);
        System.out.println();
    }

Podívejme se nyní na další uživatelskou metodu nazvanou printMethodBody(), která vlastně představuje ústřední část našeho jednoduchého „disassembleru“. V této metodě se prochází přes jednotlivé instrukce bajtkódu (metoda CodeIterator.next() může přeskočit o více než jeden bajt) a následně jsou vypsány mnemotechnické zkratky všech přečtených instrukcí. My ovšem potřebujeme vypsat i hexadecimální hodnoty všech bajtů tvořících jednu instrukci. Kvůli tomu je vypočten index následující instrukce a následně se ve vložené programové smyčce prochází všemi indexy mezi současně zpracovávanou instrukcí a instrukcí následující. Hodnota každého bajtu uloženého na tomto indexu je vypsána a současně se i sníží hodnota pomocné proměnné spaces použité pro zarovnání názvů operačních kódů instrukcí:

    /**
     * Vypis instrukci tvoricich telo vybrane metody.
     *
     * @throws NotFoundException
     *             vyhozena, pokud metoda nebyla nalezena
     * @throws BadBytecode
     *             vyhozena, pokud se nalezne neplatna instrukce v bytekodu
     */
    private static void printMethodBody(MethodInfo methodInfo) throws BadBytecode {
        CodeAttribute ca = methodInfo.getCodeAttribute();
        CodeIterator iterator = ca.iterator();
        while (iterator.hasNext()) {
            int index = iterator.next();
            int opcode = iterator.byteAt(index);
            int nextIndex = iterator.lookAhead();
            int spaces = 16;
            for (int i = index; i < nextIndex; i++) {
                System.out.format("%02x ", iterator.byteAt(i));
                spaces-=3;
            }
            for (int i = 0; i < spaces; i++) {
                System.out.print(' ');
            }
            System.out.println("        " + Mnemonic.OPCODE[opcode]);
        }
    }

8. Úplný zdrojový kód demonstračního příkladu ClassGenerationTest9

V této kapitole bude uveden výpis úplného zdrojového kódu dnešního demonstračního příkladu pojmenovaného ClassGenerationTest9. Po spuštění tohoto příkladu se nejdříve vytvoří bajtkód třídy GeneratedClass9, která bude obsahovat trojici statických metod main(), loopTest1()loopTest2(), přičemž bajtkód metody loopTest1() je vytvořen na základě algoritmu uvedeného v páté kapitole a bajtkód metody loopTest2() byl vytvořen algoritmem popsaným v kapitole šesté. Po vytvoření celé třídy se následně vypíše i její struktura, a to včetně hexadecimálního výpisu bajtů tvořících operační kódy i operandy jednotlivých instrukcí bajtkódu (viz též předchozí kapitolu):

import java.io.IOException;
 
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.Bytecode;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.CodeIterator;
import javassist.bytecode.ConstPool;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.Mnemonic;
import javassist.bytecode.Opcode;
 
 
 
/**
 * Test moznosti nastroje Javassist - vygenerovani jednoduche tridy
 * s metodou main a nekolika dalsimi metodami obsahujicimi programove smycky.
 *
 * @author Pavel Tisnovsky
 */
public class ClassGenerationTest9 {
 
    /**
     * Jmeno vygenerovane tridy.
     */
    private static final String GENERATED_CLASS_NAME = "GeneratedClass9";
 
    /**
     * Zdrojovy kod metody main(), ktery bude nasledne zkompilovan
     * do bajtkodu a zakomponovan do vytvorene tridy.
     */
    private static final String MAIN_METHOD_SOURCE_TEXT =
        "public static void main(String[] args)" +
        "{" +
        "    loopTest1();" +
        "    loopTest2();" +
        "}";
 
    /**
     * Vytvoreni metody main() z jejiho zdrojoveho kodu.
     * 
     * @param generatedClass
     *            predstavuje vytvarenou tridu
     * @throws CannotCompileException
     *             vyhozena v pripade chyby ve zdrojovem kodu
     */
    private static void addMethodMain(CtClass generatedClass) throws CannotCompileException {
        CtMethod methodMain = CtMethod.make(MAIN_METHOD_SOURCE_TEXT, generatedClass);
        generatedClass.addMethod(methodMain);
    }
 
    /**
     * Vytvoreni staticke metody loopTest1() bez navratove hodnoty.
     * Instrukce tvorici telo metody jsou vytvoreny s vyuzitim tridy Bytecode.
     *
     * @param generatedClass
     *            predstavuje vytvarenou tridu
     * @throws CannotCompileException
     *             vyhozena v pripade chyby ve zdrojovem kodu
     */
    private static void constructMethodLoopTest1(CtClass generatedClass) throws CannotCompileException {
        MethodInfo methodInfo = prepareMethod(generatedClass, "loopTest1");
 
        // vytvoreni tela metody
        ConstPool constPool = methodInfo.getConstPool();
        Bytecode bytecode = generateBytecodeForMethodLoopTest1(constPool);
        addCodeAttributeToGeneratedMethod(methodInfo, bytecode);
    }
 
    /**
     * Vytvoreni staticke metody loopTest2() bez navratove hodnoty.
     * Instrukce tvorici telo metody jsou vytvoreny s vyuzitim tridy Bytecode.
     *
     * @param generatedClass
     *            predstavuje vytvarenou tridu
     * @throws CannotCompileException
     *             vyhozena v pripade chyby ve zdrojovem kodu
     */
    private static void constructMethodLoopTest2(CtClass generatedClass) throws CannotCompileException {
        MethodInfo methodInfo = prepareMethod(generatedClass, "loopTest2");
 
        // vytvoreni tela metody
        ConstPool constPool = methodInfo.getConstPool();
        Bytecode bytecode = generateBytecodeForMethodLoopTest2(constPool);
        addCodeAttributeToGeneratedMethod(methodInfo, bytecode);
    }
 
    /**
     * Pridani atributu "CODE" k vygenerovane metode.
     *
     * @param methodInfo
     * @param bytecode
     */
    private static void addCodeAttributeToGeneratedMethod(MethodInfo methodInfo, Bytecode bytecode) {
        CodeAttribute codeAttribute = bytecode.toCodeAttribute();
 
        methodInfo.setCodeAttribute(codeAttribute);
    }
 
    /**
     * Priprava bajtkodu metody.
     * 
     * @param generatedClass
     *            predstavuje vytvarenou tridu
     * @param methodName
     *            jmeno vytvarene metody
     * @throws CannotCompileException
     *             vyhozena v pripade chyby ve zdrojovem kodu
     */
    private static MethodInfo prepareMethod(CtClass generatedClass, String methodName) throws CannotCompileException {
        CtClass returnType = CtClass.voidType;
        CtClass[] parameterTypes = {};
 
        // u metody je nutne znat jeji jmeno, navratovou hodnotu i typy parametru
        CtMethod loopTestMethod = new CtMethod(returnType, methodName, parameterTypes, generatedClass);
 
        // zmena modifikatoru
        loopTestMethod.setModifiers(Modifier.STATIC | Modifier.PUBLIC);
 
        // telo metody
        generatedClass.addMethod(loopTestMethod);
 
        MethodInfo methodInfo = loopTestMethod.getMethodInfo();
        return methodInfo;
    }
 
    /**
     * Vytvoreni bajtkodu predstavujiciho sekvenci instrukci pro metodu int
     * loopTest1().
     * 
     * @param constPool
     *            tabulka konstant pouzivana metodou
     * @return bajtkod predstavujici sekvenci instrukci pro metodu int foo()
     */
    private static Bytecode generateBytecodeForMethodLoopTest1(ConstPool constPool)
    {
        // bajtkod by mel odpovidat nasledujicimu kodu
        //         for (int i = 0; i < 10; i++) {
        //             System.out.println("Hello");
        //         }
 
        // coz zhruba odpovida sekvenci instrukci
        //        0:   iconst_0
        //        1:   istore_0
        //        2:   goto    16
        //        5:   getstatic       #20; //Field java/lang/System.out:Ljava/io/PrintStream;
        //        8:   ldc     #42; //String Hello world!
        //        10:  invokevirtual   #28; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
        //        13:  iinc    0, 1
        //        16:  iload_0
        //        17:  bipush  10
        //        19:  if_icmplt       5
        //        22:  return
 
        final int stackSize = 1;
        final int localVars = 1;
        Bytecode bytecode = new Bytecode(constPool, stackSize, localVars);
        bytecode.addOpcode(Opcode.ICONST_0);
        bytecode.addOpcode(Opcode.ISTORE_0);
 
        // za instrukci GOTO nasleduje 16bitovy cil skoku
        // - tyto dva bajty prozatim preskocime
        // - a zapamatujeme si jejich index pro pozdejsi inicializaci
        int gotoInstructionIndex = bytecode.currentPc();
        bytecode.addOpcode(Opcode.GOTO);
        int gotoOperandIndex = bytecode.currentPc();
        bytecode.addGap(2);
 
        // v teto promenne bude ulozen index prvni instrukce smycky
        int loopStartIndex = bytecode.currentPc();
        bytecode.addGetstatic("java.lang.System", "out", "Ljava/io/PrintStream;");
        bytecode.addLdc("Hello");
        bytecode.addInvokevirtual("java.io.PrintStream", "println", "(Ljava/lang/String;)V");
 
        // zvyseni hodnoty pocitadla o jednicku
        bytecode.addOpcode(Opcode.IINC);
        bytecode.add(0, 1);
        // ted jiz vime, ze sem bude smerovat cil skoku
        int gotoDestinationPC = bytecode.currentPc();
        bytecode.addOpcode(Opcode.ILOAD_0);
        bytecode.addOpcode(Opcode.BIPUSH);
        bytecode.addOpcode(10);
        int currentPC = bytecode.currentPc();
 
        // na konci smycky je umisten podmineny skok
        bytecode.addOpcode(Opcode.IF_ICMPLT);
        // vypocet cile podmineneho skoku -> zacatek smycky
        bytecode.addIndex(loopStartIndex - currentPC);
 
        // instrukce Return
        bytecode.addOpcode(Opcode.RETURN);
 
        // nyni je jiz mozne vyplnit cilovou adresu skoku
        int offset = gotoDestinationPC - gotoInstructionIndex;
        bytecode.write(gotoOperandIndex, offset >> 8);
        bytecode.write(gotoOperandIndex+1, offset);
 
        // finito
        return bytecode;
    }
 
    /**
     * Vytvoreni bajtkodu predstavujiciho sekvenci instrukci pro metodu int
     * loopTest1().
     * 
     * @param constPool
     *            tabulka konstant pouzivana metodou
     * @return bajtkod predstavujici sekvenci instrukci pro metodu int foo()
     */
    private static Bytecode generateBytecodeForMethodLoopTest2(ConstPool constPool)
    {
        // bajtkod by mel odpovidat nasledujicimu kodu
        //         for (int i = 0; i < 10; i++) {
        //             System.out.println("world!");
        //         }
 
        // coz zhruba odpovida sekvenci instrukci
        //        0:   iconst_0
        //        1:   istore_0
        //        2:   goto    16
        //        5:   getstatic       #20; //Field java/lang/System.out:Ljava/io/PrintStream;
        //        8:   ldc     #42; //String Hello world!
        //        10:  invokevirtual   #28; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
        //        13:  iinc    0, 1
        //        16:  iload_0
        //        17:  bipush  10
        //        19:  if_icmplt       5
        //        22:  return
 
        final int stackSize = 1;
        final int localVars = 1;
        Bytecode bytecode = new Bytecode(constPool, stackSize, localVars);
        bytecode.addIconst(0);
        bytecode.addIstore(0);
 
        // za instrukci GOTO nasleduje 16bitovy cil skoku
        // - tyto dva bajty prozatim preskocime
        // - a zapamatujeme si jejich index pro pozdejsi inicializaci
        int gotoInstructionIndex = bytecode.currentPc();
        bytecode.addOpcode(Opcode.GOTO);
        int gotoOperandIndex = bytecode.currentPc();
        bytecode.addGap(2);
 
        // v teto promenne bude ulozen index prvni instrukce smycky
        int loopStartIndex = bytecode.currentPc();
        bytecode.addPrintln("world!");
 
        // zvyseni hodnoty pocitadla o jednicku
        bytecode.addOpcode(Opcode.IINC);
        bytecode.add(0, 1);
        // ted jiz vime, ze sem bude smerovat cil skoku
        int gotoDestinationPC = bytecode.currentPc();
        bytecode.addIload(0);
        // pro hodnotu 10 se vygeneruje instrukce BIPUSH 10
        bytecode.addIconst(10);
 
        // tuto hodnotu potrebujeme pro vypocet cile podmineneho skoku
        // (pocita se relativne vuci teto instrukci)
        int currentPC = bytecode.currentPc();
 
        // na konci smycky je umisten podmineny skok
        bytecode.addOpcode(Opcode.IF_ICMPLT);
        // vypocet cile podmineneho skoku -> zacatek smycky
        bytecode.addIndex(loopStartIndex - currentPC);
 
        // instrukce Return
        bytecode.addReturn(CtClass.voidType);
 
        // nyni je jiz mozne vyplnit cilovou adresu skoku
        int offset = gotoDestinationPC - gotoInstructionIndex;
        bytecode.write(gotoOperandIndex, offset >> 8);
        bytecode.write(gotoOperandIndex+1, offset);
 
        // finito
        return bytecode;
    }
 
    /**
     * Vytvoreni tridy s metodou main().
     * 
     * @throws CannotCompileException
     *             vyhozena v pripade chyby ve zdrojovem kodu metody main()
     * @throws IOException
     *             pokud dojde k chybe pri zapisu bajtkodu na disk
     * @throws NotFoundException
     *             pokud dojde k chybe pri zapisu bajtkodu na disk
     */
    private static CtClass generateClass() throws CannotCompileException, NotFoundException, IOException {
        // ziskat vychozi class pool
        ClassPool pool = ClassPool.getDefault();
 
        // vytvoreni nove verejne tridy
        CtClass generatedClass = pool.makeClass(GENERATED_CLASS_NAME);
 
        // konstrukce nove metody loopTest1()
        constructMethodLoopTest1(generatedClass);
 
        // konstrukce nove metody loopTest2()
        constructMethodLoopTest2(generatedClass);
 
        // pridani metody do teto tridy
        addMethodMain(generatedClass);
 
        // ulozeni bajtkodu na disk
        generatedClass.writeFile();
 
        return generatedClass;
    }
 
    /**
     * Vypis struktury vybrane metody.
     * 
     * @param generatedClass
     *            predstavuje vytvarenou tridu
     * @param methodName
     *            jmeno metody, jejiz struktura se ma vypsat
     * @throws NotFoundException
     *             vyhozena, pokud metoda nebyla nalezena
     * @throws BadBytecode
     *             vyhozena, pokud se nalezne neplatna instrukce v bytekodu
     */
    private static void printMethodStructure(CtClass generatedClass, String methodName) throws NotFoundException, BadBytecode {
        System.out.println("Method '" + methodName + "' structure:");
        CtMethod method = generatedClass.getDeclaredMethod(methodName);
        if (method == null) {
            System.out.println("   not found!");
            return;
        }
        MethodInfo methodInfo = method.getMethodInfo();
        System.out.println("    real name:    " + methodInfo.getName());
        System.out.println("    descriptor:   " + methodInfo.getDescriptor());
        System.out.println("    access flags: " + Modifier.toString(methodInfo.getAccessFlags()));
        System.out.println("    method body:");
        printMethodBody(methodInfo);
        System.out.println();
    }
 
    /**
     * Vypis instrukci tvoricich telo vybrane metody.
     *
     * @throws NotFoundException
     *             vyhozena, pokud metoda nebyla nalezena
     * @throws BadBytecode
     *             vyhozena, pokud se nalezne neplatna instrukce v bytekodu
     */
    private static void printMethodBody(MethodInfo methodInfo) throws BadBytecode {
        CodeAttribute ca = methodInfo.getCodeAttribute();
        CodeIterator iterator = ca.iterator();
        while (iterator.hasNext()) {
            int index = iterator.next();
            int opcode = iterator.byteAt(index);
            int nextIndex = iterator.lookAhead();
            int spaces = 16;
            for (int i = index; i < nextIndex; i++) {
                System.out.format("%02x ", iterator.byteAt(i));
                spaces-=3;
            }
            for (int i = 0; i < spaces; i++) {
                System.out.print(' ');
            }
            System.out.println("        " + Mnemonic.OPCODE[opcode]);
        }
    }
 
    /**
     * Vypis struktury vybranych metod z generovane tridy.
     * 
     * @param generatedClass
     *            predstavuje vytvarenou tridu
     * @throws NotFoundException
     *             vyhozena, pokud metoda nebyla nalezena
     * @throws BadBytecode 
     */
    private static void printMethodStructures(CtClass generatedClass) throws NotFoundException, BadBytecode {
        printMethodStructure(generatedClass, "main");
        printMethodStructure(generatedClass, "loopTest1");
        printMethodStructure(generatedClass, "loopTest2");
    }
 
    /**
     * Spusteni generatoru tridy.
     *
     * @param args nevyuzito
     */
    public static void main(String[] args) {
        System.out.println("class generation begin: " + GENERATED_CLASS_NAME);
        try {
            CtClass generatedClass = generateClass();
            // dulezite - generovana trida nesmi byt "zmrazena"
            generatedClass.defrost();
            printMethodStructures(generatedClass);
        }
        catch (CannotCompileException e) {
            e.printStackTrace();
        }
        catch (NotFoundException e) {
            e.printStackTrace();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        catch (BadBytecode e) {
            e.printStackTrace();
        }
        System.out.println("class generation end: " + GENERATED_CLASS_NAME);
    }
 
}

9. Výstup demonstračního příkladu ClassGenerationTest9

Podívejme se nyní na to, jak vypadá výstup dnešního demonstračního příkladu ClassGenerationTest9. Asi nejzajímavější informací je způsob uložení cílů skoků ihned za operačními kódy instrukcí gotoif_icmplt. Povšimněte si, že se skutečně jedná o dvoubajtovou celočíselnou hodnotu se znaménkem. To například znamená, že sekvence bajtů a7 00 0e značí nepodmíněný skok (goto) o čtrnáct bajtů směrem dopředu, zatímco sekvence bajtů a1 ff f2 je podmíněný skok (konkrétně if_icmplt) o čtrnáct bajtů směrem dozadu (oba offsety jsou vypočteny od začátku příslušné instrukce):

class generation begin: GeneratedClass9
Method 'main' structure:
    real name:    main
    descriptor:   ([Ljava/lang/String;)V
    access flags: public static
    method body:
b8 00 21                invokestatic
b8 00 23                invokestatic
b1                      return
 
Method 'loopTest1' structure:
    real name:    loopTest1
    descriptor:   ()V
    access flags: public static
    method body:
03                      iconst_0
3b                      istore_0
a7 00 0e                goto
b2 00 0e                getstatic
12 10                   ldc
b6 00 16                invokevirtual
84 00 01                iinc
1a                      iload_0
10 0a                   bipush
a1 ff f2                if_icmplt
b1                      return
 
Method 'loopTest2' structure:
    real name:    loopTest2
    descriptor:   ()V
    access flags: public static
    method body:
03                      iconst_0
3b                      istore_0
a7 00 0e                goto
b2 00 1b                getstatic
12 1d                   ldc
b6 00 16                invokevirtual
84 00 01                iinc
1a                      iload_0
10 0a                   bipush
a1 ff f2                if_icmplt
b1                      return
 
class generation end: GeneratedClass9
 

10. Bajtkód třídy GeneratedClass9

Podívejme se nyní na způsob, jakým je bajtkód třídy GeneratedClass9 vypsán standardním nástrojem javap. Vzhledem k tomu, že budeme chtít vidět výpis bajtkódu jednotlivých metod, a to včetně metod soukromých, je nutné nástroj javap spustit následujícím způsobem:

javap -c -private GeneratedClass9

Nástroj javap nám však ve své současné verzi nedokáže vypsat i hexadecimální hodnoty bajtů tvořících operační kódy a operandy instrukcí, takže u skoků budeme vidět pouze absolutní adresy (samozřejmě platné v rámci těla metody):

Compiled from "GeneratedClass9.java"
public class GeneratedClass9 extends java.lang.Object{
 
public static void loopTest1();
  Code:
   0:           iconst_0
   1:           istore_0
   2:           goto            16
   5:           getstatic       #14; //Field java/lang/System.out:Ljava/io/PrintStream;
   8:           ldc             #16; //String Hello
   10:          invokevirtual   #22; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   13:          iinc            0, 1
   16:          iload_0
   17:          bipush          10
   19:          if_icmplt       5
   22:          return
 
public static void loopTest2();
  Code:
   0:           iconst_0
   1:           istore_0
   2:           goto            16
   5:           getstatic       #27; //Field java/lang/System.err:Ljava/io/PrintStream;
   8:           ldc             #29; //String world!
   10:          invokevirtual   #22; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   13:          iinc            0, 1
   16:          iload_0
   17:          bipush          10
   19:          if_icmplt       5
   22:          return
 
public static void main(java.lang.String[]);
  Code:
   0:           invokestatic    #33; //Method loopTest1:()V
   3:           invokestatic    #35; //Method loopTest2:()V
   6:           return
 
public GeneratedClass9();
  Code:
   0:           aload_0
   1:           invokespecial   #38; //Method java/lang/Object."":()V
   4:           return
 
}

11. Repositář se zdrojovými kódy dnešního demonstračního příkladu ClassGenerationTest9

Následuje – v tomto seriálu již tradiční – kapitola s odkazy na zdrojové kódy. Dnes popsaný demonstrační příklad ClassGenerationTest9 je uložen do Mercurial repositáře dostupného na adrese http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/. V následující tabulce najdete odkazy na prozatím nejnovější verze tohoto zdrojového kódu:

# Zdrojový soubor/skript Umístění souboru v repositáři
1 ClassGenerationTest9.java http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/file/c4872d13d7c1/ja­vassist/ClassGenerationTes­t9/ClassGenerationTest9.ja­va

12. Odkazy na Internetu

  1. GOTO instruction
    http://www.vmth.ucdavis.e­du/incoming/Jasmin/ref-_goto.html
  2. Open Source ByteCode Libraries in Java
    http://java-source.net/open-source/bytecode-libraries
  3. IF_ICMPLT instruction
    http://www.vmth.ucdavis.e­du/incoming/Jasmin/ref–29.html
  4. IFEQ instruction
    http://www.vmth.ucdavis.e­du/incoming/Jasmin/ref-_ifeq.html
  5. ASM Home page
    http://asm.ow2.org/
  6. Seznam nástrojů využívajících projekt ASM
    http://asm.ow2.org/users.html
  7. ObjectWeb ASM (Wikipedia)
    http://en.wikipedia.org/wi­ki/ObjectWeb_ASM
  8. Java Bytecode BCEL vs ASM
    http://james.onegoodcooki­e.com/2005/10/26/java-bytecode-bcel-vs-asm/
  9. BCEL Home page
    http://commons.apache.org/bcel/
  10. Byte Code Engineering Library (před verzí 5.0)
    http://bcel.sourceforge.net/
  11. Byte Code Engineering Library (verze >= 5.0)
    http://commons.apache.org/pro­per/commons-bcel/
  12. BCEL Manual
    http://commons.apache.org/bcel/ma­nual.html
  13. Byte Code Engineering Library (Wikipedia)
    http://en.wikipedia.org/wiki/BCEL
  14. BCEL Tutorial
    http://www.smfsupport.com/sup­port/java/bcel-tutorial!/
  15. Bytecode Engineering
    http://book.chinaunix.net/spe­cial/ebook/Core_Java2_Volu­me2AF/0131118269/ch13lev1sec6­.html
  16. Bytecode Outline plugin for Eclipse (screenshoty + info)
    http://asm.ow2.org/eclipse/index.html
  17. Javassist
    http://www.jboss.org/javassist/
  18. Byteman
    http://www.jboss.org/byteman
  19. Java programming dynamics, Part 7: Bytecode engineering with BCEL
    http://www.ibm.com/develo­perworks/java/library/j-dyn0414/
  20. The JavaTM Virtual Machine Specification, Second Edition
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/VMSpec­TOC.doc.html
  21. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  22. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  23. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  24. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  25. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  26. aspectj (Eclipse)
    http://www.eclipse.org/aspectj/
  27. Aspect-oriented programming (Wikipedia)
    http://en.wikipedia.org/wi­ki/Aspect_oriented_program­ming
  28. AspectJ (Wikipedia)
    http://en.wikipedia.org/wiki/AspectJ
  29. EMMA: a free Java code coverage tool
    http://emma.sourceforge.net/
  30. Cobertura
    http://cobertura.sourceforge.net/
  31. jclasslib bytecode viewer
    http://www.ej-technologies.com/products/jclas­slib/overview.html
Našli jste v článku chybu?
Root.cz: 250 Mbit/s po telefonní lince, když máte štěstí

250 Mbit/s po telefonní lince, když máte štěstí

Podnikatel.cz: Víme první výsledky doby odezvy #EET

Víme první výsledky doby odezvy #EET

Měšec.cz: Kdy vám stát dá na stěhování 50 000 Kč?

Kdy vám stát dá na stěhování 50 000 Kč?

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

Vitalia.cz: Znáte „černý detox“? Ani to nezkoušejte

Znáte „černý detox“? Ani to nezkoušejte

Lupa.cz: Proč firmy málo chrání data? Chovají se logicky

Proč firmy málo chrání data? Chovají se logicky

Podnikatel.cz: Babiše přesvědčila 89letá podnikatelka?!

Babiše přesvědčila 89letá podnikatelka?!

Vitalia.cz: Chtějí si léčit kvasinky. Lék je jen v Německu

Chtějí si léčit kvasinky. Lék je jen v Německu

Vitalia.cz: Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

Vitalia.cz: Mondelez stahuje rizikovou čokoládu Milka

Mondelez stahuje rizikovou čokoládu Milka

Měšec.cz: Jak vymáhat výživné zadarmo?

Jak vymáhat výživné zadarmo?

Lupa.cz: Google měl výpadek, nejel Gmail ani YouTube

Google měl výpadek, nejel Gmail ani YouTube

Podnikatel.cz: Chtějte údaje k dani z nemovitostí do mailu

Chtějte údaje k dani z nemovitostí do mailu

Podnikatel.cz: EET: Totálně nezvládli metodologii projektu

EET: Totálně nezvládli metodologii projektu

DigiZone.cz: Sony KD-55XD8005 s Android 6.0

Sony KD-55XD8005 s Android 6.0

Vitalia.cz: Paštiky plné masa ho zatím neuživí

Paštiky plné masa ho zatím neuživí

Lupa.cz: Insolvenční řízení kvůli cookies? Vítejte v ČR

Insolvenční řízení kvůli cookies? Vítejte v ČR

Vitalia.cz: Dáte si jahody s plísní?

Dáte si jahody s plísní?

120na80.cz: Rakovina oka. Jak ji poznáte?

Rakovina oka. Jak ji poznáte?

Vitalia.cz: Často čůrá a má žízeň? Příznaky dětské cukrovky

Často čůrá a má žízeň? Příznaky dětské cukrovky