Hlavní navigace

Pohled pod kapotu JVM – změna přístupových práv k metodám a atributům libovolné třídy

30. 7. 2013
Doba čtení: 21 minut

Sdílet

V dnešní části seriálu o jazyce Java si ukážeme jeden ze způsobů využití nástroje Javassist. Řekneme si totiž, jak je možné změnit přístupová práva k metodám a atributům libovolné třídy. Jedná se sice o poměrně jednoduchou problematiku, která ale ukazuje sílu a současně i snadnost použití Javassistu.

Obsah

1. Pohled pod kapotu JVM – změna přístupových práv k metodám a atributům libovolné třídy

2. Zdrojový kód testovací třídy Test

3. Změna přístupových práv ke statické metodě libovolné třídy

4. Změna přístupových práv ke statickému atributu libovolné třídy

5. Metoda pro zjištění, zda skutečně máme přístup ke všem statickým atributům i statickým metodám třídy

6. Metoda setStaticIntField()

7. Metoda invokeStaticMethod()

8. Kompletní zdrojový kód demonstračního příkladu ClassModification1

9. Spuštění demonstračního příkladu bez změny přístupových práv

10. Spuštění demonstračního příkladu s povolením změn přístupových práv ve třídě Test

11. Původní struktura bajtkódu třídy Test

12. Nová struktura bajtkódu třídy Test

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

14. Odkazy na Internetu

1. Pohled pod kapotu JVM – změna přístupových práv k metodám a atributům libovolné třídy

V několika předchozích částech seriálu o programovacím jazyce Java i o virtuálním stroji Javy jsme si ukázali, jak lze využít nástroj Javassist pro tvorbu nových tříd, popř. pro výpis obsahu libovolné třídy, včetně dekódování instrukcí bajtkódu tvořících těla jednotlivých metod zkoumaných tříd. Ovšem Javassist lze využít i k dalším operacím. Jedním z poměrně častých požadavků – například při vytváření testů – je mít možnost zavolat libovolnou metodu zvolené třídy, a to i metodu privátní (obecně metodu, k níž nemáme práva přístupu). Podobně je někdy nutné přímo přistoupit k privátnímu atributu třídy. Tuto problematiku lze řešit více způsoby. První způsob využívá reflexi (Reflection API), především pak metodu java.lang.reflect.Accessi­bleObject.setAccessible() pro nastavení přístupu i k privátním metodám/atributům. Tento způsob však dokáže zajistit přístup k metodě/atributu pouze v rámci běžícího virtuálního stroje Javy, zatímco původní podoba třídy zůstane nezměněna, což mj. znamená, že nebude možné přímo přistoupit k privátnímu atributu nebo metodě ve zdrojovém kódu – to nedovolí překladač javac.

Druhý způsob, kterým se dnes budeme zabývat, je flexibilnější a také obecnější. Spočívá v tom, že se upravovaná třída načte do virtuálního stroje Javy a následně se její metody a/nebo atributy změní s využitím nástroje Javassist. Poté je možné buď takto změněnou třídu přímo použít, popř. je navíc možné změněnou třídu, přesněji řečeno její bajtkód, uložit na disk do souboru s koncovkou .class. Oba způsoby lze přitom použít i v těch případech, kdy nemáme k dispozici zdrojový kód modifikovaných tříd, což je v ne-OS světě poměrně častý problém, protože mnohdy mají vývojáři k dispozici pouze soubory .class nebo Javovský archiv .jar. S využitím Javassistu lze i v těchto případech například opravit chyby v nějaké metodě či provádět již výše zmíněné testování (možná je to zvláštní, ale v mnoha případech vůbec nedojde k porušení licence, zejména tehdy, pokud se změněný bajtkód neuloží do nového .class a dále nešíří).

2. Zdrojový kód testovací třídy Test

Ještě než si ukážeme konkrétní postupy, jak v nástroji Javassist zajistit změnu přístupových práv k metodám a atributům, podíváme se na zdrojový kód testovací třídy nazvané Test. V této třídě je deklarována čtveřice statických metod foo(), bar(), printX() a printY() i dvojice statických celočíselných atributů x a y. Důležité je, že první z těchto atributů je veřejný a druhý privátní, což znamená, že přístup ke druhému atributu je kontrolován jak překladačem (ten nám nedovolí přeložit kód, který k atributu Test.y přistupuje mimo jeho třídu), tak i v čase běhu aplikace. Podobné omezení platí i v případě metod třídy Test, konkrétně u metody Test.bar(), která je privátní a tudíž nepřístupná vně této třídy.

Poznámka: to, že jak metody, tak i atributy jsou statické, znamená, že můžeme třídu testovat, aniž by bylo nutné vytvářet její instance. Nicméně je velmi jednoduché testovací třídu i dále popsaný demonstrační příklad upravit tak, aby používal nestatické (instanční) metody a atributy.

Úplný zdrojový kód testovací třídy Test je vypsán pod tímto odstavcem:

/**
 * Testovaci trida, ktera bude modifikovana nastrojem Javassist.
 */
public class Test {
 
    public static int x;
    private static int y;
 
    public static void foo() {
        System.out.println("Method 'foo' called!");
    }
 
    private static void bar() {
        System.out.println("Method 'bar' called!");
    }
 
    public static void printX() {
        System.out.println("Field Test.x = " + x);
    }
 
    public static void printY() {
        System.out.println("Field Test.y = " + y);
    }
}

3. Změna přístupových práv ke statické metodě libovolné třídy

Podívejme se nyní, jak je snadné změnit přístupová práva ke statické metodě třídy. V případě použití nástroje Javassist je nejdříve nutné pro danou třídu získat instanci typu CtClass s využitím class poolu:

        // ziskat vychozi class pool
        ClassPool pool = ClassPool.getDefault();
 
        // objekt predstavujici menenou tridu
        CtClass testClass;
 
        // ziskat objekt predstavujici tridu Test
        // (provede se vyhledani a nacteni souboru Test.class)
        testClass = pool.get("Test");

Jakmile již máme získanou instanci typu CtClass, lze změnit modifikátory libovolné metody. Následující kód mění modifikátory metody Test.bar() z kombinace STATIC+PRIVATE na požadovanou kombinaci STATIC+PUBLIC:

    /**
     * Modifikace metody Test.bar() - zmena pristupovych prav.
     * 
     * @param testClass
     *            testovaci (modifikovana) trida.
     * @throws NotFoundException
     *             vyvolana v pripade, ze metoda neni nalezena.
     */
    private static void modifyMethodBar(CtClass testClass) throws NotFoundException {
        CtMethod method = testClass.getDeclaredMethod("bar");
        method.setModifiers(Modifier.STATIC + Modifier.PUBLIC);
    }

Upozornění: problém může nastat v případě přidání či odstranění modifikátoru STATIC, neboť se změní volací konvence této metody a dokonce i instrukce bajtkódu provádějícího její volání (invokestatic versus invokevirtual).

4. Změna přístupových práv ke statickému atributu libovolné třídy

Stejně snadná je i změna přístupových práv ke statickému či nestatickému atributu třídy. V následujícím kódu jsou modifikátory atributu Test.y změněny z kombinace STATIC+PRIVATE na kombinaci STATIC+PUBLIC:

    /**
     * Modifikace atributu Test.y - zmena pristupovych prav.
     * 
     * @param testClass
     *            testovaci (modifikovana) trida.
     * @throws NotFoundException
     *             vyvolana v pripade, ze atribut neni nalezen.
     */
    private static void modifyFieldY(CtClass testClass) throws NotFoundException {
        CtField field = testClass.getDeclaredField("y");
        field.setModifiers(Modifier.STATIC + Modifier.PUBLIC);
    }

Upozornění: i pokud odstraníte modifikátor FINAL, nemusí to znamenat, že bude jednoduše možné změnit hodnotu tohoto atributu, která se projeví v celém programu. Je tomu tak z toho důvodu, že se například kombinace STATIC+FINAL považuje za symbolickou konstantu a v kódu se tedy nemusí provádět explicitní čtení tohoto atributu. Ostatně si to můžeme snadno vyzkoušet. Třída:

public class T {
    static {
        System.out.println(Math.PI);
        System.exit(0);
    }
}

se přeloží takto:

Compiled from "T.java"
public class T extends java.lang.Object{
public T();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return
 
static {};
  Code:
   0:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   ldc2_w  #3;         //double 3.141592653589793d
   6:   invokevirtual   #5; //Method java/io/PrintStream.println:(D)V
   9:   iconst_0
   10:  invokestatic    #6; //Method java/lang/System.exit:(I)V
   13:  return

}

Povšimněte si především faktu, že se namísto čtení atributu Math.PI skutečně přímo používá konstanta uložená na constant poolu.

5. Metoda pro zjištění, zda skutečně máme přístup ke všem statickým atributům i statickým metodám třídy

Zjištění, zda skutečně máme povolený přístup ke všem statickým atributům i statickým metodám testovací třídy Test, můžeme provést dvěma způsoby. Buď lze vytvořit samostatnou třídu, při jejímž překladu i spuštění se využije modifikovaný bajtkód třídy Test (připomeňme si, že při překladu se kontroluje, zda jsou atributy a metody tříd dostupné), nebo lze modifikovanou třídu Test použít v tom samém virtuálním stroji Javy, kde byl spuštěn nástroj Javassist. Tento druhý způsob je sice zajímavější, ale i poněkud složitější, neboť je nutné použít Reflection API. Podívejme se nejprve na zdrojový kód metody nazvané checkAccess(), v níž se nejprve převede objekt typu CtClass na instanci java.lang.Class. Ihned poté je již možné použít Reflection API, které je dostupné v každém běhovém prostředí Javy od verze 1.1:

    /**
     * Zjisteni, zda skutecne mame pristup ke vsem statickym metodam i statickym
     * atributum tridy.
     * 
     * @param testClass
     *            testovaci (modifikovana) trida.
     * @throws CannotCompileException
     *             muze byt vyhozena v prubehu prevodu CtClass na Class
     */
    @SuppressWarnings("unchecked")
    private static void checkAccess(CtClass testClass) throws CannotCompileException {
        Class testClassKlass = testClass.toClass();
 
        // otestovani pristupu ke vsem statickym metodam tridy
        invokeStaticMethod(testClassKlass, "foo");
        invokeStaticMethod(testClassKlass, "bar");
        invokeStaticMethod(testClassKlass, "printX");
        invokeStaticMethod(testClassKlass, "printY");
 
        // otestovani pristupu ke vsem statickym atributum tridy
        setStaticIntField(testClassKlass, "x", 42);
        setStaticIntField(testClassKlass, "y", 6502);
 
        // vypis novych hodnot statickych atributu
        invokeStaticMethod(testClassKlass, "printX");
        invokeStaticMethod(testClassKlass, "printY");
    }

6. Metoda setStaticIntField()

V metodě checkAccess() popsané v předchozí kapitole se mj. volá i další uživatelská metoda nazvaná setStaticIntField(). Jak již název této metody napovídá, je možné ji použít pro změnu hodnoty statického atributu typu int. S využitím Reflection API se tato operace rozděluje na dvě části: získání objektu typu Field představujícího obraz atributu a následné nastavení nové hodnoty atributu s využitím Field.set() popř. Field.setInt(). Této metodě je nutné předat instanci třídy, pro níž se má atribut nastavit (u statických atributů je zde namísto reference předána hodnota null) a ve druhém parametru se předává buď instance třídy Integer u metody Field.set() nebo přímo celočíselná hodnota u metody Field.setInt(). Při nastavování hodnoty atributu mohou nastat výjimky různých typů – atribut nebyl nalezen, není k němu dovolen přístup, nastavuje se špatná hodnota atd.:

    /**
     * Nastaveni statickeho atributu typu int na vybranou hodnotu.
     * 
     * @param anyClass
     *            testovaci (modifikovana) trida.
     * @param fieldName
     *            jmeno statickeho atributu, jehoz hodnota se ma zmenit
     * @param fieldNewValue
     *            pozadovana nova hodnota atributu
     */
    private static void setStaticIntField(Class anyClass, String fieldName, int fieldNewValue) {
        try {
            Field field = anyClass.getField(fieldName);
            field.set(null, fieldNewValue);
        }
        catch (SecurityException e) {
            e.printStackTrace();
        }
        catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        catch (IllegalArgumentException e) {
            e.printStackTrace();
        }
        catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

7. Metoda invokeStaticMethod()

Zavolání (statické) metody s využitím Reflection API se provádí stejným dvoukrokovým přístupem jako změna hodnoty atributu. Nejdříve je nutné získat objekt typu Method představující volanou metodu. Následně se zavolá Method.invoke(), které se v prvním parametru předá instance třídy (u statických tříd zde může být null) a v dalších parametrech pak případné parametry volané metody. Všechny metody z testovací třídy Test jsou bezparametrické, takže jejich volání lze provést velmi jednoduše způsobem, který je ukázán v kódu pod tímto odstavcem:

    /**
     * Zavolani vybrane staticke metody
     * 
     * @param anyClass
     *            trida, v niz je staticka metoda deklarovana
     * @param methodName
     *            jmeno staticke metody, ktera se ma spustit
     */
    @SuppressWarnings("unchecked")
    private static void invokeStaticMethod(Class anyClass, String methodName) {
        try {
            Method method = anyClass.getMethod(methodName);
            method.invoke(null);
        }
        catch (SecurityException e) {
            e.printStackTrace();
        }
        catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        catch (IllegalArgumentException e) {
            e.printStackTrace();
        }
        catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

Poznámka: zachycení všech možných výjimek je prováděno ihned v uživatelské metodě invokeStaticMethod() z toho důvodu, aby demonstrační program mohl po vzniku výjimky pokračovat dále.

8. Kompletní zdrojový kód demonstračního příkladu ClassModification1

Veškeré uživatelské metody popsané výše jsou součástí dnešního prvního a současně i jediného demonstračního příkladu nazvaného ClassModification1. V tomto příkladu se provede trojice operací. Nejdříve se načte soubor Test.class a získá se z něj hodnota typu CtClass. Následně se struktura celé této třídy vypíše na standardní výstup s využitím stejného postupu, s jakým jsme se seznámili již v předchozích částech tohoto seriálu – zpětným přečtením kódu každé metody s následnou iterací přes získaný kód a výpisem symbolických jmen jednotlivých instrukcí. Poté dojde ke změně modifikátorů metody Test.bar() i atributu y. Posléze se právě změněná třída Test načte do JVM a všechny čtyři její statické metody foo(), bar(), printX()printY() se spustí. Dojde i ke změně hodnoty obou statických atributů této třídy. Následuje výpis celého zdrojového kódu tohoto demonstračního příkladu:

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
 
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.CodeIterator;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.Mnemonic;
 
 
 
/**
 * Test moznosti nastroje Javassist - zmena modifikatoru metod a atributu
 * testovaci tridy Test. Jakmile dojde ke zmene vsech metod a atributu
 * na "public" (verejne), jsou zmeneny hodnoty obou atributu a spusteny
 * vsechny metody testovaci tridy.
 *
 * @author Pavel Tisnovsky
 */
public class ClassModification1 {
 
    /**
     * Jmeno testovaci tridy.
     */
    private static final String TEST_CLASS_NAME = "Test";
 
    /**
     * Vypis struktury vybrane metody.
     * 
     * @param modifiedClass
     *            predstavuje vytvarenou ci modifikovanou 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 modifiedClass, String methodName) throws NotFoundException, BadBytecode {
        System.out.println("Method '" + methodName + "' structure:");
        CtMethod method = modifiedClass.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);
            System.out.println("        " + Mnemonic.OPCODE[opcode]);
        }
    }
 
    /**
     * Vypis struktury vybranych metod z modifikovane tridy.
     * 
     * @param modifiedClass
     *            predstavuje vytvarenou ci modifikovanou tridu
     * @throws NotFoundException
     *             vyhozena, pokud metoda nebyla nalezena
     * @throws BadBytecode 
     */
    private static void printMethodStructures(CtClass modifiedClass) throws NotFoundException, BadBytecode {
        printMethodStructure(modifiedClass, "foo");
        printMethodStructure(modifiedClass, "bar");
        printMethodStructure(modifiedClass, "printX");
        printMethodStructure(modifiedClass, "printY");
    }
 
    /**
     * Modifikace metody Test.bar() - zmena pristupovych prav.
     * 
     * @param testClass
     *            testovaci (modifikovana) trida.
     * @throws NotFoundException
     *             vyvolana v pripade, ze metoda neni nalezena.
     */
    private static void modifyMethodBar(CtClass testClass) throws NotFoundException {
        CtMethod method = testClass.getDeclaredMethod("bar");
        method.setModifiers(Modifier.STATIC + Modifier.PUBLIC);
    }
 
    /**
     * Modifikace atributu Test.y() - zmena pristupovych prav.
     * 
     * @param testClass
     *            testovaci (modifikovana) trida.
     * @throws NotFoundException
     *             vyvolana v pripade, ze atribut neni nalezen.
     */
    private static void modifyFieldY(CtClass testClass) throws NotFoundException {
        CtField field = testClass.getDeclaredField("y");
        field.setModifiers(Modifier.STATIC + Modifier.PUBLIC);
    }
 
    /**
     * Zjisteni, zda skutecne mame pristup ke vsem statickym metodam i statickym
     * atributum tridy.
     * 
     * @param testClass
     *            testovaci (modifikovana) trida.
     * @throws CannotCompileException
     *             muze byt vyhozena v prubehu prevodu CtClass na Class
     */
    @SuppressWarnings("unchecked")
    private static void checkAccess(CtClass testClass) throws CannotCompileException {
        Class testClassKlass = testClass.toClass();
 
        // otestovani pristupu ke vsem statickym metodam tridy
        invokeStaticMethod(testClassKlass, "foo");
        invokeStaticMethod(testClassKlass, "bar");
        invokeStaticMethod(testClassKlass, "printX");
        invokeStaticMethod(testClassKlass, "printY");
 
        // otestovani pristupu ke vsem statickym atributum tridy
        setStaticIntField(testClassKlass, "x", 42);
        setStaticIntField(testClassKlass, "y", 6502);
 
        // vypis novych hodnot statickych atributu
        invokeStaticMethod(testClassKlass, "printX");
        invokeStaticMethod(testClassKlass, "printY");
    }
 
    /**
     * Nastaveni statickeho atributu typu int na vybranou hodnotu.
     * 
     * @param anyClass
     *            testovaci (modifikovana) trida.
     * @param fieldName
     *            jmeno statickeho atributu, jehoz hodnota se ma zmenit
     * @param fieldNewValue
     *            pozadovana nova hodnota atributu
     */
    private static void setStaticIntField(Class anyClass, String fieldName, int fieldNewValue) {
        try {
            Field field = anyClass.getField(fieldName);
            field.set(null, fieldNewValue);
        }
        catch (SecurityException e) {
            e.printStackTrace();
        }
        catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        catch (IllegalArgumentException e) {
            e.printStackTrace();
        }
        catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * Zavolani vybrane staticke metody
     * 
     * @param anyClass
     *            trida, v niz je staticka metoda deklarovana
     * @param methodName
     *            jmeno staticke metody, ktera se ma spustit
     */
    @SuppressWarnings("unchecked")
    private static void invokeStaticMethod(Class anyClass, String methodName) {
        try {
            Method method = anyClass.getMethod(methodName);
            method.invoke(null);
        }
        catch (SecurityException e) {
            e.printStackTrace();
        }
        catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        catch (IllegalArgumentException e) {
            e.printStackTrace();
        }
        catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * Spusteni modifikatoru tridy.
     *
     * @param args nevyuzito
     */
    public static void main(String[] args) {
        // ziskat vychozi class pool
        ClassPool pool = ClassPool.getDefault();
 
        // objekt predstavujici menenou tridu
        CtClass testClass;
 
        try {
            // ziskat objekt predstavujici tridu Test
            testClass = pool.get(TEST_CLASS_NAME);
 
            // vypis puvodni struktury tridy Test
            System.out.println("Original class structure:\n");
            printMethodStructures(testClass);
 
            // modifikace puvodne privatni metody
            modifyMethodBar(testClass);
 
            // modifikace puvodne privatniho atributu
            modifyFieldY(testClass);
 
            // vypis zmenene struktury tridy Test
            System.out.println("Modified class structure:\n");
            printMethodStructures(testClass);
 
            // ulozeni bajtkodu tridy na disk
            testClass.writeFile();
 
            // a otestovani, zda mame skutecne pristup ke vsem atributum
            checkAccess(testClass);
        }
        catch (NotFoundException e) {
            e.printStackTrace();
        }
        catch (BadBytecode e) {
            e.printStackTrace();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        catch (CannotCompileException e) {
            e.printStackTrace();
        }
        catch (SecurityException e) {
            e.printStackTrace();
        }
        catch (IllegalArgumentException e) {
            e.printStackTrace();
        }
    }
 
}

9. Spuštění demonstračního příkladu bez změny přístupových práv

Podívejme se nyní, co se stane v případě, že v demonstračním příkladu NEprovedeme změnu přístupových práv, tj. když nedojde k zavolání uživatelských metod modifyMethodBar() a modifyFieldY() (volání bylo zakomentováno). V tomto případě se samozřejmě korektně vypíše struktura třídy Test, ovšem při volání metody Test.bar() či při pokusu o změnu atributu Test.y dojde k vyhození (a následnému zachycení) výjimky:

Original class structure:
 
Method 'foo' structure:
    real name:    foo
    descriptor:   ()V
    access flags: public static
    method body:
        getstatic
        ldc
        invokevirtual
        return
 
Method 'bar' structure:
    real name:    bar
    descriptor:   ()V
    access flags: private static
    method body:
        getstatic
        ldc
        invokevirtual
        return
 
Method 'printX' structure:
    real name:    printX
    descriptor:   ()V
    access flags: public static
    method body:
        getstatic
        new
        dup
        ldc
        invokespecial
        getstatic
        invokevirtual
        invokevirtual
        invokevirtual
        return
 
Method 'printY' structure:
    real name:    printY
    descriptor:   ()V
    access flags: public static
    method body:
        getstatic
        new
        dup
        ldc
        invokespecial
        getstatic
        invokevirtual
        invokevirtual
        invokevirtual
        return
Modified class structure:
 
Method 'foo' structure:
    real name:    foo
    descriptor:   ()V
    access flags: public static
    method body:
        getstatic
        ldc
        invokevirtual
        return
 
Method 'bar' structure:
    real name:    bar
    descriptor:   ()V
    access flags: private static
    method body:
        getstatic
        ldc
        invokevirtual
        return
 
Method 'printX' structure:
    real name:    printX
    descriptor:   ()V
    access flags: public static
    method body:
        getstatic
        new
        dup
        ldc
        invokespecial
        getstatic
        invokevirtual
        invokevirtual
        invokevirtual
        return
 
Method 'printY' structure:
    real name:    printY
    descriptor:   ()V
    access flags: public static
    method body:
        getstatic
        new
        dup
        ldc
        invokespecial
        getstatic
        invokevirtual
        invokevirtual
        invokevirtual
        return
Method 'foo' called!
Field Test.x = 0
Field Test.y = 0
java.lang.NoSuchMethodException: Test.bar()
        at java.lang.Class.getMethod(Class.java:1605)
        at ClassModification1.invokeStaticMethod(ClassModification1.java:192)
        at ClassModification1.checkAccess(ClassModification1.java:139)
        at ClassModification1.main(ClassModification1.java:246)
java.lang.NoSuchFieldException: y
        at java.lang.Class.getField(Class.java:1520)
        at ClassModification1.setStaticIntField(ClassModification1.java:164)
        at ClassModification1.checkAccess(ClassModification1.java:145)
        at ClassModification1.main(ClassModification1.java:246)
Field Test.x = 42
Field Test.y = 0

10. Spuštění demonstračního příkladu s povolením změn přístupových práv ve třídě Test

Pokud se demonstrační příklad ClassModification1 spustí bez dalších změn, dojde ke korektní úpravě přístupových práv k metodě Test.bar() i k atributu Test.y; což znamená, že při další práci se třídou Test nebudou vyhazovány žádné výjimky. O tom se můžeme ostatně snadno přesvědčit:

Original class structure:
 
Method 'foo' structure:
    real name:    foo
    descriptor:   ()V
    access flags: public static
    method body:
        getstatic
        ldc
        invokevirtual
        return
 
Method 'bar' structure:
    real name:    bar
    descriptor:   ()V
    access flags: private static
    method body:
        getstatic
        ldc
        invokevirtual
        return
 
Method 'printX' structure:
    real name:    printX
    descriptor:   ()V
    access flags: public static
    method body:
        getstatic
        new
        dup
        ldc
        invokespecial
        getstatic
        invokevirtual
        invokevirtual
        invokevirtual
        return
 
Method 'printY' structure:
    real name:    printY
    descriptor:   ()V
    access flags: public static
    method body:
        getstatic
        new
        dup
        ldc
        invokespecial
        getstatic
        invokevirtual
        invokevirtual
        invokevirtual
        return
Modified class structure:
 
Method 'foo' structure:
    real name:    foo
    descriptor:   ()V
    access flags: public static
    method body:
        getstatic
        ldc
        invokevirtual
        return
 
Method 'bar' structure:
    real name:    bar
    descriptor:   ()V
    access flags: public static
    method body:
        getstatic
        ldc
        invokevirtual
        return
 
Method 'printX' structure:
    real name:    printX
    descriptor:   ()V
    access flags: public static
    method body:
        getstatic
        new
        dup
        ldc
        invokespecial
        getstatic
        invokevirtual
        invokevirtual
        invokevirtual
        return
 
Method 'printY' structure:
    real name:    printY
    descriptor:   ()V
    access flags: public static
    method body:
        getstatic
        new
        dup
        ldc
        invokespecial
        getstatic
        invokevirtual
        invokevirtual
        invokevirtual
        return
Method 'foo' called!
Method 'bar' called!
Field Test.x = 0
Field Test.y = 0
Field Test.x = 42
Field Test.y = 6502

11. Původní struktura bajtkódu třídy Test

Zajímavé bude zjistit, jakým způsobem demonstrační příklad ClassModification1 změnil bajtkód testovací třídy Test. Aby byly změny jasně patrné, zjistíme nejdříve původní (nemodifikovanou) strukturu této třídy:

javac Test.java
javap -c -private Test

Dostaneme následující výstup (zvýrazněna jsou jména a přístupová práva metod i atributů):

Compiled from "Test.java"
public class Test extends java.lang.Object{
 
public static int x;
 
private static int y;
 
public Test();
  Code:
   0:   aload_0
   1:   invokespecial   #12; //Method java/lang/Object."<init>":()V
   4:   return
 
public static void foo();
  Code:
   0:   getstatic       #23; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   ldc     #25;         //String Method 'foo' called!
   5:   invokevirtual   #31; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   8:   return
 
private static void bar();
  Code:
   0:   getstatic       #23; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   ldc     #34;         //String Method 'bar' called!
   5:   invokevirtual   #31; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   8:   return
 
public static void printX();
  Code:
   0:   getstatic       #23; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   new     #37;         //class java/lang/StringBuilder
   6:   dup
   7:   ldc     #39;         //String Field Test.x = 
   9:   invokespecial   #41; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
   12:  getstatic       #43; //Field x:I
   15:  invokevirtual   #47; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   18:  invokevirtual   #51; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   21:  invokevirtual   #31; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   24:  return
 
public static void printY();
  Code:
   0:   getstatic       #23; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   new     #37;         //class java/lang/StringBuilder
   6:   dup
   7:   ldc     #54;         //String Field Test.y = 
   9:   invokespecial   #41; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
   12:  getstatic       #56; //Field y:I
   15:  invokevirtual   #47; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   18:  invokevirtual   #51; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   21:  invokevirtual   #31; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   24:  return
 
}
 

12. Nová struktura bajtkódu třídy Test

Nová struktura bajtkódu třídy Test vypadá po jeho modifikaci a uložení nástrojem Javassist následovně:

root_podpora

Compiled from "Test.java"
public class Test extends java.lang.Object{
 
public static int x;
 
public static int y;
 
public Test();
  Code:
   0:   aload_0
   1:   invokespecial   #12; //Method java/lang/Object."<init>":()V
   4:   return
 
public static void foo();
  Code:
   0:   getstatic       #23; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   ldc     #25;         //String Method 'foo' called!
   5:   invokevirtual   #31; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   8:   return
 
public static void bar();
  Code:
   0:   getstatic       #23; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   ldc     #34;         //String Method 'bar' called!
   5:   invokevirtual   #31; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   8:   return
 
public static void printX();
  Code:
   0:   getstatic       #23; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   new     #37;         //class java/lang/StringBuilder
   6:   dup
   7:   ldc     #39;         //String Field Test.x = 
   9:   invokespecial   #41; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
   12:  getstatic       #43; //Field x:I
   15:  invokevirtual   #47; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   18:  invokevirtual   #51; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   21:  invokevirtual   #31; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   24:  return
 
public static void printY();
  Code:
   0:   getstatic       #23; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   new     #37;         //class java/lang/StringBuilder
   6:   dup
   7:   ldc     #54;         //String Field Test.y = 
   9:   invokespecial   #41; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
   12:  getstatic       #56; //Field y:I
   15:  invokevirtual   #47; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   18:  invokevirtual   #51; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   21:  invokevirtual   #31; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   24:  return
 
}

Pro lepší čitelnost lze rozdíly mezi oběma strukturami získat nástrojem diff (použit je unifikovaný výstup, který je podle mého názoru nejčitelnější):

--- Test1.out   Sat Jul 27 22:24:03 2013
+++ Test2.out   Sat Jul 27 22:24:20 2013
@@ -2,7 +2,7 @@
 public class Test extends java.lang.Object{
 public static int x;
 
-private static int y;
+public static int y;
  
 public Test();
   Code:
@@ -17,7 +17,7 @@
    5:  invokevirtual   #31; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
    8:  return
  
-private static void bar();
+public static void bar();
   Code:
    0:  getstatic   #23; //Field java/lang/System.out:Ljava/io/PrintStream;
    3:  ldc #34; //String Method 'bar' called!

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

Následuje – v tomto seriálu již tradiční – kapitola s odkazy na zdrojové kódy. Dnes popsaný demonstrační příklad je společně s dalšími pomocnými skripty 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 těchto zdrojových kódů:

14. Odkazy na Internetu

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

Byl pro vás článek přínosný?