2012. december 30., vasárnap

Reflection - a bajkeverő csodagyerek

Albert Hoffmann szavai csengnek a fülemben, amikor a Reflection-re gondolok: "bajkeverő csodagyerek". Egyfelől milyen hasznos, hogy futásidőben példányosítani lehet osztályokat, hogy feltérképezhetjük őket, hogy módisíthatjuk őket, stb. Aki dolgozott már modern webes keretrendszerrel az tisztában van a Reflection minden előnyével, hisz ez a legegyszerűbb módja feltölteni egy objektumot a kéréssel érkező paramétereknek, vagy egy proxy segítségével megváltoztatni egy adattag viselkedését. Ilyen értelemben elképzelhetetlen lenne a (mai értelemben vett) Java EE terjedése, mert Reflection nélkül még mindig a kőkörban élnénk.
RequestParams rp = new RequestParams(); 
final Field[] fields = rp.getClass().getDeclaredFields();
for (final Field field : fields) {
 firstChar = String.valueOf(field.getName().charAt(0));
 methodName = "set" + field.getName().replaceFirst(firstChar, firstChar.toUpperCase());
 try {
  method = rp.getClass().getMethod(methodName, String.class);
  method.invoke(rp, request.getParameter(field.getName()));
 } catch (final SecurityException e) {
 } catch (final NoSuchMethodException e) {
 } catch (final IllegalArgumentException e) {
 } catch (final IllegalAccessException e) {
 } catch (final InvocationTargetException e) {
 }
}
A példa jól szemlélteti, hogy pofon egyszerű feltölteni a kapott paraméterekkel egy objektumot, újabb paraméterek létrehozásakor nem kell setterekkel bajlódni, ráadásul a kód teljesen hordozható, így bármely osztályra dinamikusan alkalmazható (a példa csak String-eket kezel az egyszerűség kedvéért).
Itt merül fel a kérdés, hogy a Reflection nem egy kétélű fegyver? Mivel mondhatni teljes hozzáférést biztosít objektumjaink belső működéséhez és felépítéséhez, nem lehet ezt rosszra is használni? A válasz egyértelmű IGEN! Daily WTF-en találtam ezt a kedves kis szösszenetet:
class ValueMunger extends Thread {
    public void run() {
        while(true) {
            munge();
            try { sleep(1000); } catch (Throwable t) { }
        }
    }
    
    public void munge() {
        try {
            Field field = Integer.class.getDeclaredField( "value" );
            field.setAccessible( true );
            for(int i = -127; i <= 128; i++)
            field.setInt( 
                Integer.valueOf(i),
                // either the same (90%), +1 (10%), or 42 (1%)
                Math.random() < 0.9 ? i : Math.random() < 0.1 ? 42 : i+1  );
        } catch (Throwable t) { ; }
    }

}
A fenti kódrészlet az Integer wrapper cachet-t zagyválja egy kicsit össze, időnként nem a valós értéket kapjuk vissza, hanem az élet értelmét, ami üzleti kritikus alkalmazásokban igen nagy probléma, nem szeretnénk egy pészmékerben ilyesmi kóddal találkozni, de azt sem szeretnénk, ha a banki alkalmazás viccelné meg a bankszámlánkat időnként. A final mezőink, a private tagjaink sincsenek biztonságban, ahogy az Enum-jaink sem, és ezen a ponton a végtelenségi lehetne sorolni az ártalmas és vicces példákat, ugyanis kijelenthető, hogy (Sun/Oracle) Java verziótól függően szinte mindenhez hozzá lehet férni Reflection API segítségével, nincs menekvés.
Ártalmas kódot hagyhat hátra kilépő munkatárs, tölthetünk le ártatlanul egy külső osztálykönyvtárral, szóval több forrásból is beszerezhetőek, de vajon mit lehet tenni ellenük? Az egyetlen gyógyszer a tudatos és körültekintő felhasználás.

  • Használjunk statikus kódelemzőt, és szigorúan tartsuk számon azokat a részeket, ahol az alkalmazásunk Reflection API-t használ.
  • Használjunk nyílt forrású könyvtárakat, és fordítsuk magunk (kódelemzés után).
  • A letöltött osztálykönyvtárak hitelességét minden lehetséges módon ellenőrizzük, ugyanis koránt sem biztos, hogy senki nem törte fel kedvenc keretrendszerünk weboldalát, és cserélte ott le a letölthető jar-okat saját módosított verziójára (jó lenne a Linux repository-khoz hasonló központosított megoldás).
  • Csak a készítő által aláírt osztályokat, és jar-okat használjunk, ha külső forrásra kell támaszkodnunk.
  • Minimalizáljuk a külső forrásokat, ne használjunk két külön osztálykönyvtárat közel ugyanarra a feladatra.
Ha van még ötletetek, milyen módszerekkel védhetjük még a hátsónkat, kérlek ne tartsátok vissza magatokat, és írjátok meg, nekem hirtelen ennyi jutott eszembe. A bejegyzésben taglalt téma nem újkeletű, de azt hiszem sosem lehet róla eleget beszélni.