Univerzita Karlova v Praze. Matematicko-fyzikální fakulta DIPLOMOVÁ PRÁCE. Bc. Tomáš Šafařík. Preprocesor Java bytecode pro verifikační nástroje

Rozměr: px
Začít zobrazení ze stránky:

Download "Univerzita Karlova v Praze. Matematicko-fyzikální fakulta DIPLOMOVÁ PRÁCE. Bc. Tomáš Šafařík. Preprocesor Java bytecode pro verifikační nástroje"

Transkript

1 Univerzita Karlova v Praze Matematicko-fyzikální fakulta DIPLOMOVÁ PRÁCE Bc. Tomáš Šafařík Preprocesor Java bytecode pro verifikační nástroje Katedra distribuovaných a spolehlivých systémů Vedoucí diplomové práce: RNDr. Pavel Parízek, Ph.D. Studijní program: Informatika Studijní obor: Softwarové systémy Praha 2016

2 Prohlašuji, že jsem tuto diplomovou práci vypracoval samostatně a výhradně s použitím citovaných pramenů, literatury a dalších odborných zdrojů. Beru na vědomí, že se na moji práci vztahují práva a povinnosti vyplývající ze zákona č. 121/2000 Sb., autorského zákona v platném znění, zejména skutečnost, že Univerzita Karlova v Praze má právo na uzavření licenční smlouvy o užití této práce jako školního díla podle 60 odst. 1 autorského zákona. V Praze dne 12. května 2016 Tomáš Šafařík

3 Na tomto místě bych rád poděkoval mému vedoucímu panu RNDr. Pavlu Parízkovi, Ph.D. za to, že mi umožnil vypracovat tuto diplomovou práci. Také bych mu chtěl poděkovat za způsob jejího vedení, kdy kdykoli jsem potřeboval, tak si na mě udělal čas a trpělivě zodpovídal mé otázky. Jeho pomoc byla také neocenitelná při psaní tohoto textu, kdy mi radil jak po odborné, tak formální stránce. Za vysoce nadstandardní považuji to, že si celý text před jeho odevzdáním přečetl a zhodnotil ho. Bez jeho všestranné pomoci by jistě tato práce nevznikla.

4 Název práce: Preprocesor Java bytecode pro verifikační nástroje Autor: Bc. Tomáš Šafařík Katedra / Ústav: Katedra distribuovaných a spolehlivých systémů Vedoucí diplomové práce: RNDr. Pavel Parízek, Ph.D. Abstrakt: Nástroje J2BP a PANDA umožňují verifikovat zkompilované Java programy. V současné době tyto nástroje nejsou schopny správně zpracovat programy s určitými sekvencemi instrukcí JVM bytecodu. Tyto sekvence instrukcí jsme popsali a navrhli jejich transformace. Na základě těchto návrhů jsme implementovali novou aplikaci BytecodeTransformer. Tato aplikace transformuje zkompilované Java programy a nahrazuje v nich problematické sekvence instrukcí bytecodu. Díky tomu se tedy rozšířila množina programů, které nástroje J2BP a PANDA dokážou verifikovat. Dále jsme vyhodnotili aplikaci BytecodeTransformer prostřednictvím našich i cizích Java programů. Tyto testy ukázaly správnou funkcionalitu implementované aplikace. Klíčová slova: Java, JVM, bytecode, transformace kódu, verifikace programů

5 Title: Java Bytecode Preprocessor for Program Verification Tools Author: Bc. Tomáš Šafařík Department: Department of Distributed and Dependable Systems Supervisor: RNDr. Pavel Parízek, Ph.D. Abstract: Both J2BP and PANDA tools verify compiled Java programs. By now, these tools are not able to process some programs with specific JVM bytecode instruction sequences in the correct way. We described these instruction sequences and proposed their transformations. We developed the new application, called BytecodeTransformer, based on these propositions. This application transforms compiled Java programs and replaces the problematic instruction sequences with some others. Usage of BytecodeTransformer enlarges the set of programs that can be verified by both J2BP and PANDA. We also evaluated BytecodeTransformer on several Java programs, including own tests and well-known open-source programs. These tests demonstrated the correct functionality of BytecodeTransformer. Keywords: Java, JVM, bytecode, code transformation, program verification

6 Obsah 1 Úvod Struktura následujícího textu Základní používané principy a nástroje Predikátová abstrakce Java Path Finder J2BP PANDA Java virtual machine a bytecode Kostra aplikace Požadavky na aplikaci Základní rozhodnutí pro implementaci aplikace Výběr knihovny pro manipulaci s bytecodem a její použití Základní návrh aplikace Třídy a rozhraní pro implementaci pluginů Abstraktní a pomocné implementace pluginů Příklad implementace jednoduchého pluginu Infrastruktura aplikace Další pomocná rozhraní a třídy Použití aplikace Transformace bytecodu Název lokální proměnné Umístění lokální proměnné Název instanční proměnné Ternární operátor Nahrazení hromadných metod Nahrazení metody entryset z rozhraní java.util.map Inicializace pole Transformace vícedimenzionálního pole Úprava výrazu assert Omezení pravé strany příkazu přiřazení Testy a jejich zhodnocení Statistiky aplikace BytecodeTransformer Projekty J2BPExamples a PandaExamples Knihovna guava-testlib

7 5.4 Knihovna guava Zhodnocení popsaných testů Zhodnocení práce Možná vylepšení implementované aplikace Závěr Seznam použité literatury Přílohy

8 1 Úvod Formální verifikace programu je snaha dokázat platnost určitých tvrzení o chování programu. Takové tvrzení může být například, že program neobsahuje deadlock nebo že proměnná x má hodnotu vždy větší než 0 nebo že program neskončí vyhozením výjimky. Verifikační nástroje tedy obvykle pracují tak, že načtou v určité formě verifikovaný program a tvrzení, která mají být ověřena. Verifikovaný program může být například zadán ve formě zdrojového kódu vyššího programovacího jazyka jako je Java nebo C++ nebo pomocí zkompilovaného programu. Ověřovaná tvrzení mohou byt zadána v nějakém standardním formalismu jako například LTL 1, CTL 2, případně v podobě invariantů (assertů), které mají platit na určité pozici v programu. Výstupem verifikačního nástroje je hlášení, zda verifikovaný program daná tvrzení splňuje, a když ne, tak jakým způsobem je porušuje. V některých případech je ve verifikačních nástrojích velmi složité implementovat ověření určitých programových konstrukcí. V těchto případech je možné se uchýlit k náhradní strategii a pokusit se tyto konstrukce transformovat tak, aby výsledný transformovaný program již bylo možné ověřit. Mezi takové nástroje patří i J2BP [1] [2] a PANDA [3] [4]. Oba tyto nástroje dokážou ověřit vstupní program zadaný pomocí Java programu zkompilovaného do JVM bytecodu (dále již jen bytecode). Problém jim ale činí některé sekvence instrukcí bytecodu, které jsou generovány překladačem javac pro určité konstrukce v jazyku Java. Tato práce si klade za cíl popsat tyto sekvence instrukcí generované překladačem javac, které nedokážou nástroje J2BP nebo PANDA korektně zpracovat. Dalším cílem je navrhnout a implementovat transformace těchto sekvencí tak, aby po jejich provedení bylo již možné výsledný program korektně zpracovat uvedenými nástroji. Navržené transformace přirozeně nesmí změnit sémantiku transformovaného programu. Posledním cílem potom je otestovat korektnost implementovaných transformací v tom smyslu, že výsledný kód bude validním programem, který dokáže zpracovat Java Virtual Machine (dále již jen JVM), že se nezmění sémantika 1 Linear time logic 2 Computation tree logic 3

9 transformovaného programu a že daná transformace opravdu odstraní posloupnosti instrukcí, které nedokázaly nástroje J2BP nebo PANDA zpracovat. Výstupem této práce je tedy aplikace pojmenovaná BytecodeTransformer, která vykonává dané transformace. Aplikace je implementována tak, aby ji bylo možné v budoucnu snadno rozšířit o další transformace. Kromě této aplikace vznikly i skripty na její jednoduché spouštění a její programátorská dokumentace. Kromě zmíněné aplikace je výstupem i následující text, ve kterém popíšeme a obhájíme API vytvořené aplikace. Také v ní přiblížíme a zdůvodníme implementované transformace. Implementované transformace by tedy měly rozšířit množinu programů, které budou nástroje J2BP a PANDA schopny verifikovat. Kromě těchto transformací také vznikne kostra aplikace a některé pomocné třídy, díky kterým bude možné snadno naprogramovat další transformace, které nebudou součástí implementované aplikace. Dále následující text přinese některé související informace například o výběru vhodné knihovny pro manipulaci s bytecodem nebo o JVM a její funkčnosti. 1.1 Struktura následujícího textu V druhé kapitole budou nejdříve popsány nástroje, pro které budou implementovány transformace, a potom v ní podrobněji popíšeme Java Virtual Machine a bytecode, který interpretuje. To se bude hodit v následujících kapitolách pro pochopení ukázek bytecodu. Ve třetí kapitole popíšeme a zdůvodníme kostru aplikace, která bude sloužit jako základní platforma pro programování jednotlivých transformací. V této kapitole také zdůvodním výběr knihovny pro manipulaci s bytecodem. Ve čtvrté kapitole postupně popíšeme implementované transformace. Pro každou transformaci bude vysvětleno, jaký problém řeší, jaké byly možnosti implementace a proč nakonec byla zvolena daná implementace. V páté kapitole popíšeme různé druhy testů, které ověří výslednou aplikaci. A konečně v šesté kapitole zhodnotíme práci a její přínosy a popíšeme její možná rozšíření. 4

10 2 Základní používané principy a nástroje V této kapitole budou popsány nástroje J2BP a PANDA, pro které mají být implementovány transformace bytecodu. Také budou stručně popsány myšlenky a programy, na kterých tyto nástroje stojí tedy verifikace programů na základě predikátové abstrakce a Java Path Finder. Nakonec bude podrobněji popsána JVM a její instrukční sada. 2.1 Predikátová abstrakce V této sekci popíšeme principy, na kterých jsou postaveny nástroje J2BP a PANDA. To nám pomůže porozumět způsobu, jakým se tyto nástroje používají. To se bude hodit v kapitole o testování. Jedním ze způsobu jak verifikovat program je kontrola, že každý stav, do kterého se tento program může dostat, splňuje zadaná tvrzení. To znamená, že je potřeba ověřit kontrolovaný program pro každý možný uživatelský vstup, pro každou kombinaci proložení vláken, atp. Stav aplikace se obvykle skládá z aktuálního ohodnocení proměnných a z pozice právě vykonávaných instrukcí (pro více vláken). Problémem tohoto postupu je to, že množství stavů, které je potřeba ověřit, je tak ohromné, že verifikaci často nelze provést v rozumném čase. Tento problém se nazývá state explosion problem. Vzniklo několik způsobů jak tento problém řešit a jedním z nich je predikátová abstrakce [5] [6] [2], kterou právě nástroje J2BP a PANDA používají. Predikátová abstrakce se snaží množství stavů omezit tak, že z ověřovaného programu vytvoří tzv. boolean program, který je verifikován namísto původního. Tento program se skládá ze stejných programovacích konstruktů jako originální program, ale původní proměnné jsou nahrazeny novými proměnnými s typem boolean. Tyto nové proměnné reprezentují pravdivostní ohodnocení predikátů, které jsou definovány nad původními proměnnými. Například v následujícím kódu nás pro určení toho, zda bude vyhozena výjimka, nemusí zajímat každé ohodnocení proměnné x, ale pouze to, zda proměnná x nabírá hodnotu 0. x=0; if (x==0) { throw new Exception(); } else { dosomething(); } 5

11 Tento program lze přeložit do boolean programu prostřednictvím proměnné b s typem boolean reprezentující predikát x==0 přibližně takto: b = true; if (b) { showerrortrace; } else { dosomething(); } Takto je tedy možné významně omezit počet stavů, které je nutné zkontrolovat. Tento způsob verifikace, ale také přináší problémy, které jsou v současné době intenzivně studovány. Mezi ně například patří, jak co nejlépe generovat predikáty, aby co možná nejvěrněji popisovaly chování původního programu, ale zároveň nedošlo ke state explosion problému. S touto otázkou přímo souvisí problematika tzv. spurious errors, kdy dané predikáty, které jsou příliš hrubé, nedokážou odlišit chybový stav, který nemůže nastat, od validního nechybového stavu. Tato problematika je obvykle řešena generováním zjemňujících nebo také zpřesňujících predikátů v průběhu verifikace programu. 2.2 Java Path Finder Nástroj Java Path Finder (dále již jen JPF) [7] se skládá z mnoha různých částí (často volitelných) a slouží k verifikaci programů zkompilovaných do bytecodu. JPF je možné konfigurovat velkým množstvím různých způsobů a vzniklo pro něj mnoho rozšíření. Základem JPF je komponenta JPF-Core, což je vlastně JVM implementovaná v Javě s několika speciálními vlastnostmi. Nejdůležitější z těchto vlastností je, že se JPF snaží vykonat program každým možným způsobem, tj. v okamžiku, kdy je možné ve vykonávání programu pokračovat více způsoby (například různé vstupy od uživatele, prokládání vláken), bude každé takové pokračování vyzkoušeno. V případě, že se v průběhu vykonávání programu narazí na chybu, případně bude porušena verifikační podmínka zadaná uživatelem, bude o této události vygenerována chybová zpráva s informací, jakým postupem k dané události došlo. Při tomto přístupu dochází k již popsanému state explosion problému. JPF řeší tento problém na základě toho, že si pamatuje stavy, které již zpracoval, a znovu je nevyhodnocuje (tzv. state matching) a na základě tzv. backtrackingu, kdy je schopen se vrátit ve stavu vykonávaného programu o určitý počet instrukcí zpět. 6

12 2.3 J2BP J2BP [1] [2] je nástroj pro generování boolean programů ze zkompilovaných Java programů (z bytecodu). Tyto programy by měly být naprogramovány v Javě ve verzi 6 a zkompilovány překladačem javac z Java Development Kit (dále již jen JDK) verze 6. Důvodem je, že právě pro tyto verze Javy a JDK byl nástroj J2BP vyvíjen a testován. Nicméně obvykle by mělo být možné verifikovat i programy v Javě ve verzi 7 a/nebo zkompilované překladačem z vyšší verze JDK. Výsledný boolean program je generován opět ve formě bytecodu a může být verifikován pomocí již popsaného nástroje Java Path Finder. J2BP je naprogramován v Javě (ve verzi 6) a ve Scale, používá ke svému běhu knihovnu Wala a SMT solver Yices a může být spouštěn na OS Linux 3. Vstupem nástroje J2BP jsou následující informace: 1. Třída, která obsahuje metodu main verifikovaného programu. 2. Seznam tříd, ze kterých se verifikovaný program skládá. 3. Textový soubor s vyhodnocovanými predikáty. 4. Textový soubor s vlastnostmi (properties). 5. Adresář, kam bude generován výsledný boolean program. Podle údajů ze souboru s predikáty budou generovány proměnné v boolean programu. V budoucnu bude možné tyto predikáty odvozovat i automaticky. Velmi podobný formát jako soubor s predikáty má soubor s vlastnostmi, který udává formule, které musí v určitém místě programu platit. V současné době nástroj J2BP nepodporuje určité konstrukce, jako například instrukce generované pro ternární operátor nebo tzv. hromadné metody na kolekcích například metodu addall. Podrobnější informace o nástroji J2BP lze získat na jeho domovské stránce PANDA Nástroj PANDA [3] [4] je implementován jako rozšíření pro Java Path Finder. Jeho účelem je přidat do JPF podporu pro predikátovou abstrakci. PANDA se tedy spouští prostřednictvím JPF s vhodnou konfigurací na OS Linux 4. K běhu takto nakonfigurovaného JPF je potřeba Java 7 a SMT solvery Z3 a CVC4. Kromě toho je 3 Vzhledem k použitým programovacím jazykům by převedení na jiný OS nebylo složité 4 Ze stejných důvodů jako u nástroje J2BP by převedení na jiný OS nebylo složité 7

13 možné pomocí externího souboru specifikovat množinu iniciálních predikátů. Po běhu JPF s Pandou je oznámeno, zda verifikovaný program obsahuje nějaké chyby či zda byla porušena nějaká ze zadaných podmínek. Nástroj PANDA byl testován pro verifikaci programu implementovaných v Javě ve verzi 7 a zkompilováných překladačem javac z JDK verze 7. Nicméně obvykle by mělo být možné verifikovat i programy zkompilované překladačem z vyšší verze JDK (tedy 8). V současné době nástroj PANDA nepodporuje určité konstrukce, jako například instrukci bytecodu multidimarray nebo stejné názvy lokálních proměnných. Podrobnější informace o nástroji PANDA lze získat na jeho domovské stránce Java virtual machine a bytecode Cílem této kapitoly je popsat základní principy JVM a kódu (bytecode), který zpracovává a který je uložen v class souborech tak, aby čtenář porozuměl příkladům uvedeným dále v textu práce. Zaměříme se na JVM ve verzi 7 [8], protože nástroj PANDA byl vyvíjen pro JDK verze 7 (verze jazyka Java a překladače). Nástroj J2BP je sice určen pro JDK ve verzi 6, ale JVM obou verzí jsou ale relativně podobné. V případě potřeby podrobnějších informací je možné nahlédnout do detailní 5 specifikace JVM nebo do některých zdrojů na internetu [9] [10] [11]. Později, v kapitolách popisující jednotlivé transformace, doplníme podrobnější informace, které se budou hodit pro danou situaci, ale pro všeobecný přehled v této části by byly příliš detailní. Informace z této kapitoly také využijeme při výběru vhodné knihovny pro manipulaci s bytecodem Formát class souboru JVM může pracovat ve dvou módech: buď interpretuje kód v class souboru nebo jej pomocí JIT (Just In Time) překladače přeloží do nativního kódu a ten spustí. Class soubor odpovídá právě jedné třídě v jazyku Java, takže pokud jeden soubor se zdrojovým kódem v Javě (přípona *.java) obsahuje více tříd, tak z něho při překladu vznikne více class souborů. Název class souboru musí přesně odpovídat reprezentované třídě. Class soubor má jasně stanovenu binární podobu. Může se skládat ze strukturovaných položek, konstant nebo polí. Pole je vždy uvozeno 5 Specifikace JVM obsahuje více než 600 stránek 8

14 konstantou o velikosti 2 byty určující počet jeho položek. Nakonec se dá každý class soubor rozložit na konstanty, které mají rozsah 1, 2 nebo 4 byty. Více-bytové položky jsou reprezentovány konvencí Big Endian. Tabulka 1 ukazuje základní členění bytecodu. Název položky Velikost v bytech Poznámka Magická konstanta 4 Konstanta 0xCAFEBABE Hlavní (major) verze 2 Např. bytecode pro JVM 7 má hlavní verzi určenou konstantou 51 Menší (minor) verze 2 Menší verze bytecodu Seznam konstant 2 + velikost Tzv. constant pool pole Příznaky třídy 2 Kombinace z hodnot: public, final, super, interface, abstract, synthetic, annotation a enum Jméno třídy 2 Odkaz na konstantu typu Class Jméno předka 2 Odkaz na konstantu typu Class Implementovaná rozhraní * počet Odkazy na konstanty typu Class položek pole Instanční a statické proměnné 2 + velikost pole Informace o instančních a statických proměnných Instanční a statické metody 2 + velikost pole Informace o instančních a statických metodách Další atributy 2 + velikost pole Tab. 1 Základní členění class souboru Rozšiřující informace o třídě, např. atributy: InnerClasses, EnclosingMethod, a další Velmi důležitou součástí class souboru je seznam konstant (constant pool). Zde jsou umístěny téměř všechny konstanty používané v rámci class souboru 6. Na tyto konstanty se odkazují další položky bytecodu (viz například Tab. 1). Seznam konstant se skládá z položek vždy reprezentující jednu konstantu. Konstantě je přiřazen tag (v podstatě se jedná o typ konstanty) a jedna až dvě hodnoty. Typy konstant jsou popsány v tabulce 2. Typ (tag) Parametr 1 Parametr 2 Poznámka Integer, Float, Long, Double Hodnota konstanty (4 nebo 2 * 4 bytů) Není Numerické konstanty 6 Výjimkou je několik málo instrukcí, které mají konstantu určenou přímo jako operand 9

15 Typ (tag) Parametr 1 Parametr 2 Poznámka Utf8 Délka řetězce Byty řetězce Reprezentace řetězcové konstanty String Odkaz na konstantu Není Odkaz na řetězec Utf8 Class Odkaz na konstantu Utf8 Není Reprezentace třídy NameAndType Fieldref Methodref, InterfaceMethodref Odkaz na konstantu Utf8 Odkaz na konstantu Utf8 (jméno třídy) Odkaz na konstantu Utf8 (jméno třídy) Odkaz na konstantu Utf8 Odkaz na konstantu NameAndType (proměnná) Odkaz na konstantu NameAndType (metoda) Tab. 2 Typy konstant v seznamu konstant Pomocná konstanta pro jméno a typ Reprezentace instanční nebo statické proměnné Reprezentace metody V tabulce 2 jsme viděli v konstantě s typem NameAndType použití řetězce k zakódování typu proměnné nebo metody. Typ primitivní proměnné je zakódován pomocí jednoho ze znaků: B (byte), C (char), D (double), F (float), I (int), J (long), S (short), Z (boolean). Proměnná typu reference se zakóduje pomocí posloupnosti znaku L, jména třídy a nakonec znaku ;. Složky balíčků jsou v názvu třídy odděleny znakem /. Pro zakódování reference na pole je použit znak [ následovaný kódem typu elementů pole. Metoda se kóduje následujícím předpisem: ([KodTypParametru1[KodTypuParametru2[ ]]])KodTypuNavratoveHodnoty Pro zakódování návratového typu void (pro procedury) se používá znak V. V dalším textu se pro zakódované typy proměnných používá výraz deskriptor. Uveďme několik příkladů: int i; //deskriptor: I int[][] intarray; //deskriptor: [[I String str; //deskriptor: Ljava/lang/String; String[] stringarray; //deskriptor: [Ljava/lang/String; void method1(){...} //deskriptor: ()V String method2(int[] a, String b) {...} //deskriptor: ([ILjava/lang/String;)Ljava/lang/String; 10

16 Informace o statických a instančních proměnných třídy jsou rozčleněny do čtyř částí, kde všechny až na poslední jsou dvoubytové konstanty: 1. Příznak - (jedna z přípustných kombinací hodnot public, private, protected, static, final, volatile, transient, synthetic, enum). 2. Jméno proměnné - odkaz na konstantu typu Utf8. 3. Deskriptor proměnné - odkaz na konstantu typu Utf8. 4. Pole dalších atributů - doplňující datové položky - například ConstantValue (u konstant), Signature (u proměnných s generickým parametrem) a další. Nakonec popíšeme obsah položky reprezentující metodu. Ta je podobně jako u proměnných rozčleněna do čtyř částí, kde až na poslední jsou všechny dvoubytové konstanty: 1. Příznaky - (jedna z přípustných kombinací hodnot public, private, protected, static, final, synchronized, bridge, varargs, native, abstract, strict, synthetic). 2. Jméno metody - odkaz na konstantu typu Utf8. 3. Deskriptor metody (typu metody s formátem popsaným již v textu dříve) - odkaz na konstantu typu Utf8. 4. Pole atributů - doplňující datové položky metody - například Code (obsahuje instrukce metody, bude podrobněji popsáno v kapitole o JVM), LineNumberTable, LocalVariableTypeTable (oboje obsahují ladící informace o lokálních proměnných) a další Interpretace bytecodu Při spuštění programu JVM vytvoří, slinkuje a inicializuje třídu obsahující statickou veřejnou metodu main(ljava/lang/string;)v 7. Poté je spuštěn kód v metodě main, který určuje jakýkoli další kód, který bude zpracován. Při zpracování dalšího kódu se mohou načítat další třídy (uložené v class souborech). V podstatě 8 každá instrukce mění některou z paměťových oblastí spravovaných prostřednictvím JVM. Těchto paměťových oblastí je pět: 7 Kde tuto metodu hledat je implementačně specifické 8 Vzato do důsledku, tak každá instrukce změní minimálně obsah čítače instrukcí 11

17 1. Registr PC tj. čítač instrukcí, každé vlákno má svůj vlastní, obsahuje odkaz na právě zpracovávanou instrukci. 2. Zásobník (Stack) - pro každé vlákno jeden, pro každé volání metody obsahuje zásobníkový rámec (Stack frame), ve kterém je vložen zásobník operandů. 3. Halda (Heap) - obsahuje data objektů, spravována pomocí garbage collectoru, sdílena všemi vlákny. 4. Method area - obsahuje data class souborů jako například runtime constant pool, instrukce metod, atributy, atd. 5. Zásobník volání nativních metod - vlastní pro každé vlákno. Na začátku zásobníkového rámce je uloženo pole lokálních proměnných. Na začátku tohoto pole jsou uloženy parametry volané metody, v případě, že se jedná o instanční metodu, tak je v první lokální proměnné na indexu 0 uložena hodnota this. Za parametry následují hodnoty lokálních proměnných použitých v metodě na úrovni bytecodu. Tyto lokální proměnné neodpovídají těm ve zdrojovém kódu, například pro dvě různé lokální proměnné v zdrojovém kódu může být použita stejná pozice v poli lokálních proměnných. Instrukce přistupují k hodnotám v poli lokálních proměnných pomocí indexů. Zásobníkový rámec také obsahuje zásobník operandů, který ale dle specifikace nemusí přímo navazovat na pole s lokálními proměnnými. Obrázek 1 znázorňuje možnou strukturu zásobníku. Obr. 1 Zásobníkový rámec Zásobník operandů je při startu metody prázdný a v průběhu zpracovávání kódu s ním většina instrukcí nějakým způsobem pracuje (přidává/odebírá hodnoty, provádí výpočty...). Instrukce pracující se zásobníkem vždy pracují s jeho vrcholem, případně s hodnotou konstantně vzdálenou od vrcholu. Z toho vyplývá, že JVM je implementována jako zásobníkový stroj. 12

18 2.5.3 Instrukce bytecodu JVM interně více či méně podporuje deset typů proměnných: prvních osm odpovídá primitivním typům v Javě (byte, char, short, int, long, float, double a boolean). Dalším typem je reference (v Javě se jedná o jakoukoli referenci na objekt či pole). Posledním typem, se kterým pracuje JVM, je returnaddress. ReturnAddress reprezentuje odkaz na pozici instrukce. Tento typ byl používán v instrukcích jsr, jsr_w a ret, které by se ale od verze jazyka Java SE 6 (verze bytecodu 50 a vyšší) již neměly nadále používat. Typy long a double zabírají v poli lokálních proměnných dvě paměťové pozice (sloty). Všechny ostatní typy JVM zabírají jednu paměťovou pozici (slot). Jak již bylo zmíněno dříve, tak jsou instrukce uloženy v atributu Code v záznamech o metodách. Instrukce je identifikována konstantou velikosti 1 byte, za kterou může následovat několik operandů. Operand může být buď celočíselná konstanta, nebo reference do seznamu konstant. JVM pro většinu instrukcí, které pracují s polem lokálních proměnných nebo zásobníkem operandů, kontroluje typ hodnoty uložený na dané paměťové pozici. Takže například pro instrukci iadd, která sečte dvě hodnoty uložené na vrcholu zásobníku, se zkontroluje, že se jedná skutečně o hodnoty typu int. Typová kontrola je většinou naznačena v názvu instrukce - podle podporovaného typu obsahuje instrukce jeden nebo více ze znaků: b - byte, s - short, i - int, l - long, f - float, d - double, c - char, a - reference. Většina instrukcí podporuje ovšem pouze tzv. výpočtové typy, tj. int, long, float, double, reference a returnaddres, kde ostatní nepodporované typy (boolean, byte, short, char) jsou reprezentovány pomocí hodnot typu int. Pouze výpočtovými typy jsou také označovány aktuální hodnoty v paměťových pozicích v poli lokálních proměnných a na zásobníku operandů. Takže pokud je například vykonána konverze z typu int na typ byte pomocí instrukce i2b 9, tak z pohledu JVM je na vrcholu zásobníku operandů stále uložena hodnota typu int. V tabulce 3 zjednodušeně představíme instrukční soubor JVM. V sloupečku Typ jsou uvedeny typové varianty instrukce, které nahrazují znak X v názvech instrukcí v sloupečku Instrukce. Například pseudoinstrukce Xadd zastupuje varianty iadd, ladd, fadd, dadd. Sloupeček Zás. (Zásobník) popisuje, co daná skupina 9 JVM vezme z vrcholu zásobníku operandů hodnotu typu int, ořízne ji na hodnotu typu byte a výsledek uloží na vrchol zásobníku 13

19 instrukcí provede se zásobníkem operandů. Například údaj 2 1 značí, že instrukce vyzvedne ze zásobníku dvě hodnoty, provede operaci a výsledek uloží na vrchol zásobníku. Někdy může být místo číslovky použit znak?. To značí, že daná skupina instrukcí může používat nebo ukládat na zásobník operandů různý počet hodnot v závislosti na konkrétní instrukci nebo dokonce kontextu, ve kterém je daná instrukce použita. Sloupeček s názvem Op. instr. značí typ operandu instrukce (ten je zakódován přímo v instrukci). Pro tento sloupeček jsou použity následující zkratky I index do pole lokálních proměnných, K číselná konstanta, O offset pro skok, R odkaz (reference) do seznamu konstant, N - žádný operand a? různé operandy. Kategorie Instrukce Typ Zás. Op. instr. Popis Načtení hodnoty lokální proměnné na zásobník Xload I, L, F, D, A 0 1 I Načte proměnnou z pole lokálních proměnných na daném indexu na zásobník (existují i varianty se zakódovaným indexem např.: iload_0, ) Uložení hodnoty ze zásobníku do lokální proměnné Načtení konstanty na zásobník Binární aritmetická operace Xstore I, L, F, D, A bipush, sipush, ldc, ldc_w, ldc2_w, aconst_null, iconst_m1, iconst_<i>, lconst_<l>, fconst_<f>, dconst_<d> Xadd, Xsub, Xmul, Xdiv, Xrem I, L, F, D 1 0 I Uloží hodnotu ze zásobníku do pole lokálních proměnných na daný index (existují i varianty se zakódovaným indexem např.: astore_0, ) 0 1 K nebo R nebo N Načte na zásobník konstantu. Konstanta je určena operandem nebo odkazem do seznamu konstant nebo zakódována v instrukci 2 1 N Provede binární aritmetickou operaci (+, -, *, /, zbytek) s hodnotami na zásobníku a výsledek uloží na zásobník 14

20 Kategorie Instrukce Typ Zás. Op. instr. Popis Negace Xneg I, L, F, D 1 1 N Zneguje hodnotu na vrcholu zásobníku a uloží Bitové operace Xshl, Xshr, Xushl, Xusr, Xor, Xand, Xxor I, L 2 1 N Provede binární bitovou operaci (posun (levý, pravý; se znamínkem nebo bez), or, and, xor ) Inkrementace Xinc I 0 0 I, K Inkrementuje lokální proměnou o konstantu v rozsahu byte Porovnání Xcmpg, Xcmpl F, D Konverze Vytvoření objektu Manipulace s instanční nebo statickou proměnnou Načtení hodnoty z pole i2b, i2s, i2l, i2f, i2d, l2i, l2f, l2d, f2i, f2l, f2d, d2i, d2l, d2f, new, newarray, anewarray, multianewarray getfield, putfield, getstatic, putstatic, Xaload B, S, L, I, F, D, C, A 2 1 N Porovná hodnoty a výsledek 0/1 uloží na zásobník 1 1 N Provede konverzi a výsledek uloží na zásobník? 1? První instrukce vytvoří objekt, druhá pole primitivních typů, třetí pole referencí, čtvrtá multidimenzionální pole, referenci je vždy uložena na zásobník?? R Načte nebo uloží na zásobník instanční nebo statickou proměnnou 2 1 N Načte na zásobník hodnotu z pole z daného indexu 15

21 Kategorie Instrukce Typ Zás. Op. instr. Uložení hodnoty do pole Manipulace se zásobníkem Skok podle hodnoty Porovnání a skok Volání metody Návrat hodnoty z metody Ostatní instrukce pro toku řízení Ostatní Xastore B, S, L, I, F, D, C, A pop, pop2, dup, dup2, dup_x1, dup2_x1, dup_x2, dup2_x2, swap ifeq, ifne, iflt, ifle, ifgt, ifge, ifnull, ifnonnull if_icmpeq, if_icmpne, if_icmplt, if_icmple, if_icmpgt, if_icmpge, if_acmpeq, if_acmpne, invokevirtual, invokeinterface, invokespecial, invokestatic, invokedynamic, Xreturn I, L, F, D return, athrow, goto, goto_w, tableswitch, lookupswitch, jsr, jsr_w, ret arraylength, instanceof, checkcast, monitorenter, monitorexit, nop, wide Tab. 3 Přehled instrukcí bytecodu Popis 3 0 N Uloží ze zásobníku hodnotu do pole na daný index?? N Manipulace se zásobníkem 1 0 O Podle hodnoty na zásobníku (=0,!=0, <0,<=0, >0, >=0, =null,!=null) vykoná skok 2 0 O Porovná dvě hodnoty zásobníku a podle výsledku vykoná skok?? R,? Zavolá (virtuální, z rozhraní, speciální, statickou nebo dynamickou) metodu 1 0 N Vrátí hodnotu z metody??? Návrat z metody, vyhození výjimky, skok podle offsetu, skok podle hodnoty v tabulce a další??? Zjištění délky pole, zjištění typu objektu, kontrola typu objektu, synchronizace a další 16

22 Instrukce v tabulce 3 jsou sice popsány jen v náznacích, ale i přesto tato tabulka dává základní představu o možnostech instrukční sady JVM. Některé instrukce ještě budou podrobněji popsány v kapitolách o transformacích Atribut StackMapFrame Na závěr je potřeba ještě zmínit problematiku atributu StackMapFrame. Atribut StackMapFrame, který je obsažen v informacích o metodách, obsahuje údaje o typech hodnot uložených v poli lokálních proměnných a na zásobníku operandů v určitém okamžiku při vykonávání bytecodu. Tato informace je generována pro každou instrukci, jež je cílem skoku, případně pro instrukci bezprostředně za instrukcí nepodmíněného skoku (goto,...). Paměťová pozice může být označena typem neinicializované hodnoty nebo výpočetním typem. V případě reference je tato informace ještě rozšířena o typ reference (například java/lang/string). Atribut StackMapFrame je používán pro urychlení verifikace bytecodu. Při ní se ověřuje, že každá instrukce pracuje s hodnotami (na zásobníku operandů a v poli lokálních proměnných), které mají správné typy. Tyto typy se určují dynamicky při startu aplikace pomocí analýzy datového toku. Kvůli zpětným skokům musela být tato analýza dříve implementována jako víceprůchodová, což mohlo být časově velmi náročné. Právě z tohoto důvodu byl přidán atribut StackMapFrame, který zamezí nutnosti více průchodů při určování typu hodnot uložených v poli lokálních proměnných a na zásobníku operandů při verifikaci bytecodu. Překladač by měl tyto informace povinně generovat od verze Javy 7, což se v komunitě nesetkalo vždy s kladnou odezvou [12] [13] [14], protože generování nového atributu bylo nutné naimplementovat i do knihoven manipulujících s bytecodem (pro případy, kdy manipulace rozbila původní, překladačem vygenerovaný atribut). Tyto knihovny se tak implementačně zkomplikovaly. Aby byla situace ještě horší, tak je někdy potřeba určit pro dvě třídy nejbližší společnou nadtřídu, ale k tomu knihovna na manipulaci bytecodu nemusí mít informace (například pokud se neupravuje bytecode knihovny, která obsahuje předky transformované třídy). Existuje způsob jak se bez atributu StackMapFrame obejít pro JVM při spuštění programu nastavit volbu --XX:-UseSplitVerifier (pro bytecode ve verzi 51, tedy pro Javu 7) nebo -noverify (pro bytecode ve verzi 52 a vyšší, tedy pro Javu 8 a vyšší) [15]. Volba --XX:-UseSplitVerifier způsobí, že bytecode bude před 17

23 zpracováním plně verifikován, jenom verifikace může být pomalejší, protože bude nutné dopočítat informace, které by se jinak nalézaly v atributu StackMapFrame Volba -noverify způsobí, že se bytecode před interpretací nebude vůbec verifikovat, což není doporučováno [16]. V tomto případě se může stát, že nějaká transformace bude generovat nevalidní bytecode, ale transformovaný program při běhu nespadne, ale bude se chovat neočekávaně. V naší aplikaci se budeme snažit generovat korektní atribut StackMapFrame. Je sice pravda, že implementované transformace budou zaměřeny pouze na bytecode, který je generován pro Javu 6 a 7, ale tím, že vyřešíme správné generování tohoto atributu, tak budeme moci transformovat i programy v Javě 8. To se může hodit v budoucnu při rozvoji programu, kdy bude potřeba transformovat programy ve vyšších verzích Javy. Většina transformací by totiž měla fungovat i pro Javu 8, protože způsob generování bytecodu se většinou nemění. Informace výše se nám tedy budou hodit při výběru vhodné knihovny na manipulaci s bytecodem a budeme na ně muset myslet i při implementaci transformací, kdy bude někdy nutné atribut StackMapFrame přepočítat. 18

24 3 Kostra aplikace Před tím než bylo možné začít implementovat jednotlivé transformace, tak bylo nutné vytvořit kostru aplikace, ve které by se transformace programovaly. Tato kostra ale byla již užitečná v okamžiku analýzy požadavků na transformace, protože v ní bylo možné snadno naprogramovat různé experimenty s bytecodem. Právě tuto kostru si tato kapitola klade za cíl popsat a zdůvodnit ji. Kromě toho v této kapitole také popíšeme výběr knihovny na manipulaci s bytecodem, kterou budeme používat v implementaci jednotlivých transformací. Analýza a implementace dílčích transformací bude popsána až v následující kapitole. 3.1 Požadavky na aplikaci Ve spolupráci s vedoucím práce jsme na základě zkoumání zdrojových kódů a experimentů s nástroji J2BP a PANDA postupně identifikovali požadavky na transformace (problematické sekvence bytecodu). Tyto požadavky byly většinou určeny konstrukcí v jazyce Java, která se do bytecodu přeložila takovým způsobem, že jej nástroj J2BP nebo PANDA nedokázaly korektně zpracovat. Tabulka 4 tyto konstrukce shrnuje. Název Nástroj Detaily/Příklad Názvy instančních proměnných PANDA Všechny instanční proměnné musí mít v hierarchii tříd unikátní názvy Umístění lokálních proměnných PANDA, J2BP Každá lokální proměnná musí být umístěna v unikátním paměťovém slotu Inicializace pole PANDA Není podporován konstrukt inicializace pole například: int[] data = new int[]{1,2}; Vícerozměrné pole PANDA Vícerozměrné pole se musí inicializovat po jednotlivých dimenzích Hromadné metody J2BP Některé hromadné metody nejsou podporovány např.: java.util.collection:addall( ) Assert J2PB, Odstranění kódu testujícího, že jsou PANDA povoleny asserty (volba -ea) 19

25 Název Nástroj Detaily/Příklad Ternární operátor J2BP Nefunguje ternární operátor varianta s ifelse funguje Omezení pravé strany PANDA Nefungují přiřazení jako např.: x= ++a; přiřazení nebo x = a=100; Další, zatím neznámá transformace??? Tab. 4 Požadavky na transformace V tabulce 4 není potřeba do detailu rozumět požadavkům na jednotlivé transformace ty budou podrobně popsány v dalších kapitolách. Tato tabulka má ovšem ilustrovat, že na první pohled jsou transformace navzájem nezávislé. Dále je vidět, že se mohou vyskytnout další požadavky na zatím neznámé transformace, a to jak v průběhu práce, tak i po jejím dokončení. Co naopak z tabulky není možné určit, je, že se nástroje PANDA i J2BP se i nadále vyvíjejí, takže je možné, že v budoucnu nebude nutné některé transformace provádět. Z těchto faktů lze odvodit, že by mělo být možné snadno konfigurovat (bez programového zásahu do aplikace), jaké transformace budou spuštěny, a že by aplikace měla být snadno rozšiřitelná i o další transformace (které by případně implementoval někdo jiný, než autor této práce). Dalším požadavkem je, aby aplikaci bylo možné snadno automaticky spouštět tak, aby její spuštění mohlo být zařazeno do skriptů spouštějící nástroje PANDA nebo J2BP. Dále je nutné, aby výsledná aplikace pracovala pod operačním systémem Linux, protože právě pro něj jsou aplikace PANDA a J2BP primárně vyvíjeny. Naopak ne úplně důležitým požadavkem (samozřejmě v rozumných mezích ) je výkonnost aplikace. To plyne z toho, že verifikace pomocí predikátové abstrakce je poměrně náročná na zdroje, takže zkoumané programy jsou spíše menšího rozsahu (v řádů desítek kb přeloženého kódu). Všechny tyto výše popsané požadavky byly vzaty do úvahy při vytváření kostry aplikace. 3.2 Základní rozhodnutí pro implementaci aplikace Jako platforma pro vývoj aplikace byl zvolen jazyk Java 8 a její ekosystém. Toto rozhodnutí přirozeně vyplývá jednak z toho, že nástroje PANDA, J2BP a JPF jsou postaveny na platformě Java. Dalším důvodem je, že výsledná aplikace by měla 20

26 upravovat bytecode pro JVM a pro tyto účely existuje několik knihoven implementovaných v jazyce Java a spolehlivě používaných v mnoha rozsáhlých aplikacích. Další výhodou je, že výsledná aplikace bude multiplatformní, takže v případě, že nástroje PANDA nebo J2BP budou přeneseny na jiný operační systém než Linux, tak bude možné transformační aplikaci bez úpravy rovnou začít používat. Multiplatformnost Javy také pomůže při vývoji nové aplikace. Vzhledem k tomu, že jediným požadavkem na ovládání aplikace bylo snadné automatické spouštění, tak je vytvořená aplikace zapínána z příkazové řádky za pomoci vstupních parametrů a neobsahuje žádné grafické uživatelské rozhraní. Pro usnadnění použití byla vytvořena sada spouštěcích skriptů pro OS Linux i Windows. Tyto skripty umožňují spouštět například jednotlivé transformace samostatně, což je vhodné pro testování, nebo množinu transformací potřebných pouze pro nástroj PANDA (tyto transformace se liší od transformací potřebných pro nástroj J2BP). 3.3 Výběr knihovny pro manipulaci s bytecodem a její použití V následujících sekcích popíšeme výběr knihovny pro manipulaci s bytecodem a zdůvodníme způsob jejího použití. Tyto informace se budou hodit v dalším textu, protože některé popisované třídy budou založeny na konstruktech z vybrané knihovny Výběr knihovny I když je kostra aplikace implementovaná, jak uvidíme později, tak, že umožňuje použití více libovolných knihoven pro manipulaci s bytecodem, tak omezením jejich množství, v ideálním případě na jednu, umožní rychlejší vývoj jednotlivých transformací. To je způsobeno tím, že se nebude potřeba podrobně seznamovat s API více knihoven. Použití jediné knihovny také umožní nad ní vyvinout pomocné komponenty a různé utilitární metody, které budou znovupoužitelné ve více transformacích. Při prozkoumávání internetu a při diskuzích s vedoucím práce bylo nalezeno několik knihoven, které by mohly být na první pohled vhodné pro účely vyvíjené aplikace. Dalším krokem bylo vybrat jednu z nich. Pokud by měl být tento krok proveden úplně do detailu, tak by možná pracností vydal na celou diplomovou práci, takže bylo potřeba stanovit několik základních kritérií, podle kterých budou knihovny hodnoceny. Zvolili jsme tato kritéria: 21

27 1. Podpora manipulací na úrovni jednotlivých instrukcí a základních elementů bytecodu. 2. Vyspělost a použití knihovny v dalších projektech. 3. Dostupnost dokumentace. 4. Podpora atributu StackMapFrame (viz sekce 2.5.4). 5. Přehlednost a jednoduchost API. 6. Podpora pro hlubší analýzu bytecodu. 7. Open source licence. První kritérium zaručuje, že bude možné pomocí knihovny vygenerovat libovolnou posloupnost instrukcí a případně upravit jakoukoli část bytecodu. To je základní vlastnost, kterou by knihovna měla nabízet. Tato flexibilita bude potřebná pro většinu transformací, kde bude potřeba nahradit určitou posloupnost instrukcí za jinou, ale sémanticky ekvivalentní. Toto kritérium z výběru odstraňuje všechny vysoko-úrovňové knihovny (například aspektové knihovny nebo knihovny, kde se vkládá kód definovaný pomocí jazyka Java). Druhé kritérium by mělo zaručit, že knihovna bude dostatečně ověřena použitím v jiných aplikacích, což většinou znamená, že neobsahuje příliš mnoho chyb a existující chyby jsou autory řešeny. Přehledná a dostatečně podrobná dokumentace ulehčuje použití knihovny, navíc ji je možné díky ní rychle zhodnotit. Hodnotu atributu StackMapFrame by bylo sice možné v případě potřeby generovat ručně, nicméně se jedná o velmi technickou a netriviální záležitost, která rozhodně není součástí této práce, takže se budeme snažit vybrat knihovnu, která tento atribut podporuje. Přehledné a intuitivní API knihovny zjednodušuje její použití a zpřehledňuje výsledný kód. V některých transformacích bude potřeba nad kódem provést nějakou netriviální analýzu, například vytvořit graf toku řízení, případně určit typy dat v paměťových pozicích. V ideálním případě by vybraná knihovna měla mít tuto funkčnost vestavěnu nebo ji bude alespoň podporovat. Také potřebujeme, aby knihovna byla volně dostupná a šiřitelná s projektem. Při výběru byly posuzovány následující knihovny: Polyglot [17], ExtendJ [18], Soot [19], BCEL [20], Javassist [21] a ASM [22]. Polyglot a ExtendJ jsou knihovny, které umožňují vytvářet vlastní překladače, případně je rozšiřovat. Nad oběma knihovnami je implementován překladač Javy. Při překladu je možné provádět různé pokročilé analýzy a transformace kódu. Problémem je, že pro žádnou z těchto knihoven se nepodařilo nalézt frontendovou 22

28 komponentu, která by zpracovávala bytecode. Polyglot se navíc obvykle používá tak, že se generuje zdrojový kód v Javě a ten je potom zkompilován standardním překladačem. K použití těchto knihoven v projektu by bylo nutné buď naprogramovat frontend pro parsování class souborů nebo přehodnotit použití vytvářené aplikace tak, aby neupravovala soubory s bytecodem, ale přímo ze zdrojových souborů Javy generovala správný bytecode. První varianta by celou práci značně rozšířila mimo její zamýšlené hranice. Druhá varianta by způsobila závislost nástrojů PANDA a J2BP na spíše experimentálním překladači Javy namísto standardně používaného překladače javac. Z těchto důvodu nebyly tyto knihovny použity. Knihovna Soot je určena pro velmi pokročilé analýzy a optimalizace bytecodu. Podporuje kód na několika úrovních abstrakce, jako například tříadresový kód SSA nebo zásobníkový kód Baf. Podle dokumentace a příkladů užití by se možná dala použít v naší aplikaci (na úrovni kódu Baf), ale není k tomu primárně určena. Standardně se Soot používá tak, že se z bytecodu vygeneruje reprezentace kódu v nějaké vyšší formě abstrakce (například SSA), nad tímto kódem se provedou pokročilé analýzy a podle nich je potom transformován. Vysokoúrovňový kód se potom převádí na kód s nižší úrovní abstrakce, až se nakonec převede na bytecode. Na tyto úkoly se také soustředí tutoriály popisující knihovnu. Z těchto důvodu nakonec nebyla knihovna Soot vybrána. BCEL je jedna z klasických knihoven uřčena k manipulaci bytecodu. Podle dokumentace nabízí API k přímé práci s instrukcemi. Byla také použita v několika projektech jako Xalan nebo FindBugs. BCEL je distribuován pod licencí Apache I a její dokumentace je poměrně podrobná. Důvodem, proč nakonec nebyla vybrána, je, že poslední oficiálně uvolněná verze 5.2 je z 1. února 2006 a sami autoři na stránkách projektu radí použít knihovnu ASM. Javassist nabízí pro manipulaci s bytecodem API na dvou úrovních. Vyšší úroveň je úroveň jazyka Javy, kdy je vložený kód přeložen interním překladačem do bytecodu a vložen do upravovaného bytecodu. Dále je možné upravovat bytecode přímo na úrovni bytecodu. Na úrovni bytecodu se pracuje pomocí třídy javassist.bytecode.codeiterator přímo s binární reprezentací bytecodu. Uvádíme dva příklady převzaté z dokumentace knihovny Javaassist: 23

29 //Příklad 1 CodeIterator ci =... ; while (ci.hasnext()) { int index = ci.next(); int op = ci.byteat(index); System.out.println(Mnemonic.OPCODE[op]); } // // Příklad 2 ConstPool cp =...; // constant pool table Bytecode b = new Bytecode(cp, 1, 0); b.addiconst(3); b.addreturn(ctclass.inttype); CodeAttribute ca = b.tocodeattribute(); V prvním příkladu se vypíšou instrukce obsažené v bytecodu. Všimněme si, že se zde pracuje přímo s konstantou reprezentující kód instrukce. V druhém příkladu je vytvořen bytecode odpovídající kódu, který vrátí z metody konstantu 3. Knihovna Javassist má přehlednou a relativně podrobnou dokumentaci [23]. Také obsahuje podporu pro generování atributu StackMapFrame. Podle Javadocu jsou ve zdrojových kódech obsaženy i třídy na zkonstruování grafu toku řízení, ale v dokumentaci není tato funkčnost popsána. Poslední verze knihovny byla uvolněna ve verzi GA pod licencí Apache, pochází z 25. června 2015 a je využívána takovými projekty jako Hibernate nebo EhCache 10. Nakonec přestavíme knihovnu ASM. Tato knihovna podporuje úpravy bytecodu pomocí dvou různých API. První API, založené na událostech, definuje abstraktní třídy, jejichž některé metody jsou v průběhu parsování souboru s bytecodem volány. Uživatel implementuje tyto metody a reaguje na události typu například navštívena instrukce multidimarray tato událost odpovídá metodě void visitmultianewarrayinsn(string desc, int dims). Reakcí na takovou událost může být například volání stejné metody na další implementaci stejné abstraktní třídy. To určí, že se má daná instrukce při zpracování bytecodu zatím zachovat. Další implementace abstraktní třídy může buď zapisovat výsledný bytecode (tato implementace je dodána knihovnou) nebo provádět další úpravu zpracovávaného bytecodu (například tím, že se zavolá metoda pro jinou instrukci). Toto zpracování bytecodu připomíná parsování XML za pomocí SAX parseru. Druhým API pro transformaci bytecodu je tzv. Tree API. V něm je bytecode reprezentován za pomocí objektů. Například třída MethodNode reprezentuje jednu 10 Podle použití knihovny Javassit na 24

30 metodu. Z ní je možné získat seznam instrukcí za pomoci veřejné instanční proměnné instructions s typem InsnList. Potom je například do této proměnné možné přidat nové instrukce, které se objeví ve výsledném bytecodu. Uveďme opět příklad: MethodNode mn = new MethodNode(); mn.instructions.add(new LdcInsnNode(3)); mn.instructions.add(new InsnNode(Opcodes.IRETURN)); Tento kód podobně jako u knihovny Javassist vygeneruje bytecode, který vrátí z metody konstantu 3. Všimněme si, že zde jsou i jednotlivé instrukce reprezentovány třídami. Tree API tedy připomíná zpracování XML za pomoci DOM objektu. Knihovna ASM má přehlednou a velmi podrobnou dokumentaci včetně části o analýzách bytecodu 11 [10]. Také obsahuje podporu pro generování atributu StackMapFrame. Poslední verze knihovny byla uvolněna pod licencí BSD, pochází z 15. května 2015 ve verzi a je využívána takovými projekty jako Lucene, Jetty, a PMD 12 nebo také pro generování kódu pro lambda výrazy v Javě 8 [24]. Knihovny Javassist a ASM na první pohled splnily všechny na ně kladené požadavky. Pro použití v aplikaci nakonec byla vybrána knihovna ASM, protože má lepší dokumentaci a práce s bytecodem prostřednictvím Tree API se ukázala jako jednodušší a intuitivnější než přímá manipulace s bytecodem v knihovně Javassist Způsob použití knihovny ASM Jak již bylo uvedeno, tak knihovna ASM nabízí dvě různá API. První tzv. Core API je podobné zpracování XML pomocí SAX parseru. Uživatel zřetězí třídy, které reagují na události při zpracovávání bytecodu. Poslední třída v takovém řetězci obvykle ukládá výsledný bytecode. Obrázek 2 převzatý z dokumentace knihovny ASM ukazuje průběh takového zřetězení tříd, které má za úkol změnit verzi bytecodu. 11 Dokumentace ASM obsahuje 148 stránek 12 Podle použití knihovny ASM na 25

31 Obr. 2 Příklad transformace za pomoci Core API knihovny ASM Práce s Tree API připomíná zpracování DOM objektu při manipulaci s XML. Základní třídou je v tomto pojetí ClassNode 13 reprezentující jednu třídu. Třída ClassNode mimo jiné obsahuje seznam instancí tříd MethodNode reprezentující jednotlivé metody třídy. Třída MethodNode obsahuje seznam instrukcí, což jsou potomci třídy AbstractInsnNode. Podobným způsobem jsou reprezentovány všechny položky bytecodu. Výhodou Core API oproti Tree API je, že je rychlejší 14 a má menší paměťovou náročnost. Nevýhodou ovšem je, že je nutné implementovat některé složitější transformace jako dvouprůchodové. V prvním průchodu jsou zapamatovány potřebné informace a až v druhém průchodu je pozměněn upravovaný program. Dále API pro analýzu kódu pracuje s Tree API. Z tohoto důvodu budou všechny transformace implementovány pomocí Tree API (tedy pokud se neukáže, že pro nějakou transformaci je potřeba nějaká sofistikovanější knihovna). Toto rozhodnutí také umožní vytvoření komponent, které budou pomáhat při využití knihovny ASM. 3.4 Základní návrh aplikace Aplikace je navržena jako kontejner na spouštění tzv. pluginů. Plugin je třída, kde je implementována určitá atomická akce nad transformovaným programem (bytecodem). Tato akce může byt například načtení nebo uložení bytecodu nebo provedení určité transformace. Spuštěné pluginy lze zřetězit tak, aby byla provedena 13 Všechny zde uvedené třídy jsou z balíčku org.objectweb.asm.tree 14 Podle autorů knihovny je Tree API o cca o 25% pomalejší než Core API 26

32 určitá posloupnost akcí. Při spuštění aplikace je nutné určit jaké pluginy se mají vykonat. Tímto způsobem je možné zapínat a vypínat pluginy. Tento návrh vychází z modifikace návrhového vzoru Zřetězení zodpovědnosti (Chain of responsibilities) [25]. Návrhový vzor Zřetězení zodpovědnosti se skládá z několika objektů (procesorů), které obsahují kód, který nějakým způsobem upravuje nebo využívá vstupní objekt (požadavek). Zpracování požadavku je zakončeno rozhodnutím, zda má být požadavek zpracován dalším procesorem nebo má být jeho zpracování ukončeno. Existuje několik modifikací tohoto návrhového vzoru. Jeden ze způsobu implementace je ten, že požadavek je zpracován pouze jedním procesorem, podle typu úlohy a/nebo pořadí daného procesoru v řetězci. Takto je například implementováno zpracování výjimek v jazyce Java (try-catch blok). Další variací je například implementace, kdy se v procesoru na základě typu úlohy vybírá další procesor z několika možných. Takto je možné implementovat větvení ve zpracování úlohy. V souvislosti s tímto návrhem aplikace bylo učiněno několik rozhodnutí, které v následujících odstavcích popíšeme. Rozhodnutí 1. V naší aplikaci plugin odpovídá jednomu procesoru. Tento procesor by v ideálním případě neměl jakýmkoli způsobem záviset na dalších pluginech. To znamená, že by neměl mimo jiné rozhodovat, jaký další plugin bude proveden. Z tohoto tedy vyplývá hlavní rozdíl oproti klasickému návrhovému vzoru Zřetězení zodpovědnosti, kdy je řetězec procesorů určován samotnými procesory. V našem případě bude tento řetězec určován řídícím objektem, který jej získá ze vstupní konfigurace (od uživatele z příkazové řádky). Tento návrh by také bylo možné popsat jako zřetězení strategií, případně v architektonické rovině tomuto návrhu odpovídá návrhový vzor Pipes and filters. Představený návrh tedy splňuje základní požadavky na aplikaci jednotlivé transformace je možné v případě potřeby zapínat a vypínat a bude možné naprogramovat další transformace bez potřeby zasahovat do stávajícího kódu. Rozhodnutí 2. Další důležité rozhodnutí spočívá v tom, jak vlastně bude vypadat zpracovávaná úloha jaké informace se budou mezi jednotlivými pluginy předávat. V zásadě existují dvě možnosti - nezpracovaný bytecode v binární podobě (typicky pole bytů), anebo bytecode v nějaké reprezentaci poplatné použité knihovně na zpracování bytecodu (například Tree API knihovny ASM). Obě dvě řešení mají své výhody i 27

33 nevýhody. První volba umožňuje přidání procesoru, který bude používat vlastní knihovnu na zpracování bytecodu, aniž by bylo nutné zasahovat do jádra aplikace. Nevýhodou tohoto řešení je, že by bylo nutné v každém pluginu deserializovat a serializovat zpracovávaný bytecode, což bude vyžadovat zdroje navíc (především čas). Drobnou výhodou může být, že v případě, kdy knihovna pro manipulaci s bytecodem podporuje kontrolu validity vygenerovaného kódu, tak bude případná chyba zachycena okamžitě po doběhnutí pluginu (ne až na konci celého řetězce), takže půjde lépe rozpoznat, který plugin způsobil chybu. Druhá volba má výhody a nevýhody dány přesně opačně než první volba. To znamená, že v případě potřeby bude problematické použít jinou knihovnu na manipulaci s bytecodem než vybranou, zato časově bude zpracování bytocodu vycházet lépe - vypadne řetězec seriliazací a deserializací. I když na začátku byla vybrána knihovna na transformace bytecodu, tak nebylo úplně jisté, že bude stačit na všechny v budoucnu požadované transformace, takže byla zvolena první možnost. Rozhodnutí 3. Dále je potřeba určit jaká část bytecodu se bude v jednom okamžiku předkládat na zpracování jednomu pluginu. Může se jednat například o bytecode odpovídající jedné třídě nebo třídám v jednom balíčku (package) nebo úplně všem třídám zpracovávaného programu. Toto rozhodnutí je jednoduché, protože už z předběžných požadavků na pluginy lze odvodit, že bude nutné implementovat transformace, které budou muset program transformovat v jednom kroku. Například plugin na transformaci názvů instančních proměnných bude nutné spouštět nad celým programem jako celkem (nebude možné například transformovat jednotlivé třídy nezávisle na ostatních). Z výše popsaného tedy vyplývá, že pluginu na zpracování bude předáván bytecode celého transformovaného programu a že metoda na zpracování bude volána pouze jednou v celém běhu transformačního programu. Nyní již máme všechny informace, abychom mohli znázornit, jak bude vypadat typický průběh aplikace. Obrázek 3 ukazuje aplikaci, která spouští čtyři pluginy. První a čtvrtý plugin načítá a ukládá bytecode. Druhý a třetí plugin provádí jeho transformaci. Obr. 3 Ukázka možného běhu aplikace 28

34 Rozhodnutí 4. Neméně důležité rozhodnutí je, jak bude uživatel identifikovat nebo určovat pluginy, které mají být spuštěny. Zde se opět nabízí několik možností. Nejjednodušší je, aby je uživatel zvenčí při spouštění aplikace určoval podle názvu třídy včetně balíčku. Výhodou tohoto řešení je, že není nutné třídu implementující plugin nijak označovat případně registrovat. Nevýhodou je, že konfigurace například na příkazové řádce bude dlouhá a nepřehledná a v případě refaktoringu názvu a/nebo umístění pluginu bude nutné měnit spouštěcí skripty. Další možností je registrace pluginu - typicky programově nebo přes nějaký konfigurační soubor. Nevýhoda tohoto řešení spočívá v tom, že programátor často zapomene nový plugin zaregistrovat. Dále tato registrace přináší množství opakujícího se (boilerplate) kódu. Proto nakonec byla zvolena konfigurace pomocí jež obsahuje atribut pro identifikaci pluginu. Občas se bude hodit dodat pluginu parametry od uživatele zvenčí. Pro tyto účely bylo potřeba vytvořit mechanismus, který by určoval informace o parametrech, jejich typech, o jejich povinném vyplnění, atd. I tato problematika byla vyřešena na základě anotací - pomocí 3.5 Třídy a rozhraní pro implementaci pluginů V následujících sekcích představíme základní třídy, rozhraní a anotace potřebné pro implementaci pluginů Rozhraní Producer, Transformer a Consumer Třída pluginu pro transformaci bytecodu musí implementovat právě jedno z těchto rozhraní: Producer, Transformer, nebo Consumer. Všechny tyto tři rozhraní dědí od dalšího rozhraní se jménem LifeCycle. UML diagram na obrázku 4 ukazuje nejdůležitější metody těchto rozhraní. 29

35 Obr. 4 Rozhraní pro implementaci pluginu Každá metoda pluginu tedy pracuje s rozhraním PluginContext. Toto rozhraní mimo jiné poskytuje přístup ke vstupním parametrům pluginu nebo k řetězci identifikující plugin, což se hodí při vytváření nových unikátních jmen v bytecodu. Toto rozhraní také zajišťuje, že pokud bude nutné někdy v budoucnu přidat metodám z pluginu nějaký parametr, tak nebude nutné měnit signaturu těchto metod, protože se nový parametr přidá do rozhraní PluginContext. Metoda init z rozhraní LifeCycle umožňuje provést inicializační kód před tím, než bude zavolána jedna ze základních metod pluginu tedy jedna z metod: produce, transform, nebo consume. Každá z těchto tří metod pracuje se seznamem tříd ClassPathItem. Tato třída reprezentuje jeden soubor na classpath určený k transformaci. Typickým úkolem implementace metody produce z rozhraní Producer je načíst (produkovat) soubory, ze kterých se skládá transformovaný program. Rozhraní Producer má v současné době pouze jedinou implementaci třídu ClassPathReader. Metoda transform rozhraní Transformer je základem každého transformačního pluginu. Úkolem každé implementace této metody je nějakým způsobem zpracovat seznam instancí tříd ClassPathItem a vrátit jej. Zpracovat typicky znamená provést nějakou manipulaci s bytecodem a/nebo přidat či ubrat instance třídy ClassPathItem do výsledného seznamu. Metoda consume z rozhraní Consumer slouží k závěrečnému zpracování (konzumaci) seznamu instancí třídy ClassPathItem. V současné době existuje jediná implementace tohoto rozhraní třída ClassPathWriter, která uloží soubory reprezentované pomocí seznamu instancí tříd ClassPathItem na disk jako soubory. 30

36 3.5.2 Třída ClassPathItem Transformovaný program se podobně jako každý jiný program v jazyce Java může skládat z adresářů obsahující soubory (především s příponou class) a z jar souborů, což jsou archivy obsahující další soubory. Třída ClassPathItem reprezentuje jeden soubor z adresáře nebo z jar souboru, ze kterých se skládá transformovaný program. UML diagram na obrázku 5 ukazuje strukturu této třídy a s ní souvisejících tříd. Obr. 5 Třída ClassPathItem a související třídy Pomocí metod getcontent a setcontent je možné získat nebo nastavit binární obsah reprezentovaného souboru. Metody getclasspathfile a setclasspathfile zpřístupňují informace o umístění souboru v rámci classpath transformovaného programu. K tomu slouží třída ClassPathFile, která nabízí informaci o jménu souboru (metoda getname) a o umístění v rámci kontejneru (metoda getdir). Metoda getdir vrací řetězec, ze kterého lze získat informace o balíčku, kde je soubor umístěn. Jediný rozdíl je, že jako separátor je namísto znaku. použit znak /. Kontejner reprezentovaný třídou ClassPathContainer identifikuje objekt na classpath, ze kterého soubor prochází. Instanci třídy ClassPathContainer lze získat pomocí metody getcontainer. Kontejner může být buď typu adresář, nebo jar soubor. Typ kontejneru lze získat pomocí metody gettype na třídě ClassPathContainer. Metoda getfile vrátí instanci třídy java.io.file ukazující na soubor případně adresář reprezentovaný kontejnerem. Pro práci s třídou ClassPathItem je nutné uvést ještě dvě upozornění. V případě, že se aktualizuje binární obsah třídy, které bylo změněno jméno a/nebo balíček, tak je nutné také aktualizovat informace o umístění souboru pomocí metody setclasspathfile. V případě, že je v rámci transformace vytvářena nová třída, tak je možné nechat informaci o kontejneru nevyplněnou kontejner bude určen automaticky až při ukládání souborů na disk. 31

37 3.5.3 Jak již bylo uvedeno, tak plugin musí být označen Tato anotace má jediný atribut - name. Tento atribut identifikuje plugin tak, aby mohl být použit z venčí z příkazové řádky. Kromě toho je nutné do balíčku, který obsahuje nějaký plugin nebo obsahuje podbalíček s pluginem, umístit soubor package-info.java s Například ve stávajícím projektu se všechny pluginy nacházejí v podbalíčcích balíčku cz.cuni.mff.d3s.bytecodetransformer. plugins, proto je v tomto balíčku umístěn soubor package-info.java s Toto označování balíčku bylo vytvořeno proto, aby při dohledávání pluginů nebylo nutné načítat každou třídu na classpath, ale pouze ty, které budou ve správně označeném balíčku nebo podbalíčku. Při měření času se ukázalo, že to urychlilo načítání informací o pluginech cca. 20x Anotace a třídy pro vstupní parametry pluginu Pro definici vstupních parametrů pluginu se používá UML diagram na obrázku 6 ukazuje atributy a s ní souvisejících tříd a rozhraní. Obr. 6 a související třídy a rozhraní Každá tato anotace určuje jeden vstupní parametr. Atribut name identifikuje jméno parametru, nepovinný atribut defaultvalue se užívá pro určení standardní hodnoty parametru (pokud hodnota není zadána uživatelem). Nepovinný atribut mandatory se standardní hodnotou false určuje, zda je vstupní parametr povinný. Posledním atributem je converter s typem Class<? extends InputParameterConverter <?>>. Tento atribut tedy přijímá typ třídy implementující rozhraní InputParameterConverter. Toto rozhraní má jedinou metodu apply, jejímž 15 Měřeno na procesoru Core I7 s SSD diskem. Čas s vycházel přibližně na 100ms, kdežto bez na 2000ms! 32

38 úkolem je zkonvertovat vstupní parametr typu java.lang.string na výstupní hodnotu určenou generickým typem. Implementace tohoto rozhraní jsou například EmptyInputParamConverter, která vlastně žádnou konverzi neprovádí, pouze vrátí hodnotu, kterou dostane jako parametr. Typ této třídy je použit jako standardní hodnota pro atribut converter. Další implementací je například třída BooleanInputParamConverter, která vstupní hodnotu zkonvertuje na boolean. V případě potřeby je možné doprogramovat další konvertory. Implementace konvertorů musí splňovat dvě podmínky musí mít bezparametrický konstruktor a metoda apply by neměla mít žádné vedlejší efekty, protože jedna instance konvertoru může být použita na konverzi více vstupních parametrů Poslední zajímavou anotací Slouží k zachycení informace, že na classpath je soubor s nápovědou pro plugin, která může být zobrazena uživateli. Anotace nabízí dva atributy packagename a filename. Atribut packagename určuje balíček, kde se hledá soubor s nápovědou. Pokud není tento atribut uveden, tak se standardně hledá v balíčku, ve kterém je obsažena anotovaná třída pluginu. Atribut filename určuje jméno souboru s nápovědou, standardní hodnota je nastavena na help.txt. Takže obvykle stačí plugin označit prázdnou a do stejného balíčku umístit soubor help.txt s nápovědou. 3.6 Abstraktní a pomocné implementace pluginů V této sekci podrobněji představíme pluginy pro načítání a ukládání bytecodu a abstraktní implementace pluginů, které se budou hodit pro implementace transformačních pluginů. UML diagram na obrázku 7 zobrazuje jejich strukturu. Obr. 7 Hierarchie abstraktních a pomocných pluginů 33

39 3.6.1 Plugin ClassPathReader Plugin ClassPathReader implementovaný stejně pojmenovanou třídou slouží k načtení souborů tvořící program k transformaci. K určení transformovaného programu se používá vstupní parametr classpath, který má podobný formát jako parametr -cp programu java (nepodporuje ovšem zástupný znak * ). Parametr classpath se skládá z adresářů obsahující *.class soubory a z jar souborů oddělených separátorem. Separátor je na OS Linux určen znakem : zatímco na OS Windows je používán znak ;. Typicky se bude tento parametr používat s hodnotou ukazující na jediný adresář obsahující class soubory transformovaného programu. ClassPathReader má ještě další vstupní parametr extraclasspath. Ten má stejný formát jako parametr classpath, ale používá se k určení tříd, které nemají být transformovány, ale použity ke generování atributu StackMapFrame. Jedná se tedy typicky o třídy, které transformovaný program používá k běhu, ale nejsou součástí transformace. V produkčním použití se tento parametr většinou nebude nastavovat, protože chceme transformovat celý program. Nicméně tento parametr se bude hodit pro použití při testování, kdy bude potřeba transformovat nějakou knihovnu tvořenou jedním jar souborem, ale další knihovny, na kterých tento jar soubor závisí, transformovat chtít nebudeme (například kvůli rychlosti, omezení testu jenom na jednu knihovnu, atd.) Plugin ClassPathWriter Plugin ClassPathWriter implementovaný stejně pojmenovanou třídou slouží k uložení souboru tvořící transformovaný program. K určení umístění transformovaného programu se používá povinný vstupní parametr ouputdir. Tento parametr slouží k zadání adresáře, kam bude umístěn transformovaný program. V tomto adresáři budou vytvořeny dva podadresáře classes a jars. Adresář classes bude obsahovat soubory, které byly načteny pro transformaci z adresářů. Adresář jars bude obsahovat jar soubory s již transformovaným obsahem. Další nepovinný parametr - jarforgeneratedfiles určuje jar soubor, do kterého se mají vložit soubory vytvořené v průběhu transformace. Pokud není tento jar soubor určen, tak tyto nově vytvořené soubory budou uloženy v adresáři classes. Tento parametr se opět bude hodit především pro testování. 34

40 3.6.3 Abstraktní implementace tříd pro transformační pluginy K transformaci pluginu je možné namísto rozhraní Transformer použít jednu z abstraktních tříd. Třída AbstractClassPathItemTransfomer se používá k transformacím, kde je možné nezávisle transformovat pouze obsah reprezentovaný jednou instancí třídy ClassPathItem. K tomu slouží abstraktní metoda transformclasspathitem, která dostane jednu instanci třídy ClassPathItem, kterou nějak upraví nebo vytvoří další instance této třídy a ty vrátí jako seznam. Metoda istransformableclasspathitem slouží k určení, zda se pro danou instanci třídy ClassPathItem bude vůbec volat metoda transformclasspathitem. Základní implementace této metody určuje, že se metoda transformclasspathitem bude volat pouze pro instance třídy ClassPathItem obsahující class soubor (soubor s bytecodem). Toto chování ale může být na potomcích přepsáno. Od třídy AbstractClassPathItemTransfomer je odvozena další abstraktní třída AbstractAsmClassNodeTransformer. Ta má metodu transformclasspathitem implementovánu tak, že z bytecodu získaného ze třídy ClassPathItem vytvoří instanci třídy org.objectweb.asm.tree.classnode (z knihovny ASM), předá ji jako parametr abstraktní metodě createclasstransformer, která vrátí instanci třídy implementující rozhraní LocalTransformer. Na tomto rozhraní je potom zavolána bezparametrická metoda transform. A nakonec je z instance třídy ClassNode opět vytvořen bytecode a ten je nastaven instanci třídy ClassPathItem. Třída implementující rozhraní LocalTransformer by tedy typicky měla provést transformaci bytecodu jedné třídy, který je reprezentován instancí třídy ClassNode. Tento mechanismus zaručuje, že transformace, která nezávisle upravuje bytecode jedné třídy, se bude moci soustředit pouze na vlastní transformaci a ne například na kód, který by vytvářel z bytecodu instanci třídy ClassNode nebo iteroval přes soubory určené k transformaci. Abstraktní třída AbstractAsmMethodNodeTransformer, která je odvozena od třídy AbstractAsmClassNodeTransformer, také funguje na podobném principu. Jediným rozdílem je, že její abstraktní metoda createmethodtransformer bere jako parametr instanci třídy org.objectweb.asm.tree.methodnode. Z toho vyplývá, že by ji jako předka měly používat transformace, které transformují nezávisle bytecode jedné metody. 35

41 3.7 Příklad implementace jednoduchého pluginu V této sekci ukážeme jak implementovat jednoduchou transformaci. Úkolem je vytvořit transformaci, která za každou instrukci iadd vloží určitý počet instrukcí nop. Tento počet bude určen vstupním parametrem nopcount. Zadání je záměrně triviální, aby výsledný kód byl co možná nejkratší. Ze zadání je vidět, že transformace může nezávisle pracovat nad bytecodem jedné metody. Proto jako předka pro základní třídu s transformací použijeme třídu AbstractAsmMethodNodeTransformer. Kód bude vypadat = = "nopcount", defaultvalue = "1", mandatory = true, converter public class ExampleTransformer extends AbstractAsmMethodNodeTransformer protected LocalTransformer createmethodtransformer( MethodContext methodctx, MethodNode methodnode) { } //pro kazdou transformovanou metodu //vrat novou instanci transformace return new ExampleMethodNodeTransformer( methodctx, methodnode); } Z kódu vidíme, že je nutné implementovat jedinou metodu createmethodtransformer a ta dělá jedinou věc a to, že vrátí novou instanci třídy ExampleMethodNodeTransformer, kde je implementována transformace bytecodu jedné metody. Kromě toho anotace nad třídou říkají, že se plugin jmenuje ExampleTransformer, přijímá jeden povinný parametr typu java.lang.integer se jménem nopcount a standardní hodnotou 1. Kromě toho má plugin přiřazenu nápovědu v souboru help.txt ve stejném balíčku jako je umístěna třída ExampleMethodNodeTransformer. V balíčku nebo v nějakém nadbalíčku s pluginem je nutné umístit soubor package-info.java, aby byl plugin kontejnerem dohledán. Soubor package-info.java bude mít tento obsah: 36

42 @PluginPackage package cz.cuni.mff.d3s.bytecodetransformer.example; import cz.cuni.mff.d3s.bytecodetransformer.app.annotation.pluginpackage; Dále ukážeme kód třídy ExampleMethodNodeTransformer, kde je implementována vlastní transformace bytecodu. Ta si zapamatuje vstupní parametry z konstruktoru a v metodě transform provede vlastní transformaci. public class ExampleMethodNodeTransformer implements LocalTransformer { //context transformace private final MethodContext ctx; //transformovaná metoda private final MethodNode methodnode; public ExampleMethodNodeTransformer( MethodContext ctx, MethodNode methodnode) { } this.ctx = ctx; this.methodnode = public void transform() { //projdi instrukce transformovane metody for (ListIterator<AbstractInsnNode> it = methodnode.instructions.iterator(); it.hasnext();) { final AbstractInsnNode instr = it.next(); //pokud je instrukce IADD if (instr.getopcode() == Opcodes.IADD) { //tak za ni pridame pozadovany pocet instrukci NOP for (int i = 0; i < ctx.getinputparam(integer.class, "nopcount"); i++) { } } } } } it.add(new InsnNode(Opcodes.NOP)); Všechny soubory tvořící uvedený příklad se nacházejí v balíčku cz.cuni.mff.d3s.bytecodetransformer.example. 37

43 3.8 Infrastruktura aplikace Funkčnost aplikace BytecodeTransformer je rozdělena do několika základních infrastrukturních komponent. Každá tato komponenta je reprezentována jedním rozhraním. Instance tříd implementující tyto rozhraní lze získat na jediném místě - v příslušných statických metodách třídy ApplicationFactory. Tyto metody také vždy jako typ vracejí rozhraní. To zajišťuje, že v případě potřeby zaměnit implementaci komponenty za jinou bude dotčena jediná část kódu příslušná statická metoda v třídě ApplicationFactory. Mezi základní rozhraní patří: PluginResolver, PluginParamBuilder, PluginContainer a PluginHelpResolver. Každé toto rozhraní má v současné době jedinou implementaci. UML diagram na obrázku 8 tyto rozhraní a jejich implementace představuje. Obr. 8 Infrastrukturní komponenty aplikace (pro jednoduchost bez generik) Implementace rozhraní PluginResolver nabízí metody pro získání typů nebo instancí pluginů podle identifikace pluginu. V současné době existuje jediná implementace této komponenty AnnotationPluginResolver. Ta využívá již popsané ke skenování classpath a určení všech dostupných pluginů. Další základní komponenta PluginParamBuilder nabízí jedinou metodu buildparams, která jako parametry dostane seznam identifikací pluginů a pro každou identifikaci pluginu mapu jeho parametrů. Úkolem této metody je ověřit a případně nějak upravit tyto parametry a výsledek vrátit pomocí instance třídy 38

44 implementující rozhraní PluginsParamContainer. Toto rozhraní poskytuje přístup k jednotlivým parametrům spouštěných pluginů. Rozhraní PluginParamBuilder má jedinou implementující třídu AnnotationPluginParamBuilder, která zpracovává Tato implementace používá funkčnost z rozhraní PluginResolver. Rozhraní PluginContainer s jedinou metodou run spouští požadované pluginy. Požadavky na spouštěné pluginy jsou zadány pomocí seznamu identifikací pluginů a mapy parametrů pro každý spouštěný plugin. Pro rozhraní PluginContainer existuje jediná implementace třída SimplePluginContainer. Tato třída funguje tak, že nejdříve za pomoci implementace rozhraní PluginParamBuilder získá informace o vstupních parametrech pluginů. Potom z implementace rozhraní PluginResolver získá instance pluginů, které mají být spuštěny. Nakonec nad každou z instancí pluginu nejdříve zavolá metody init, potom jednu z metod produce, transform, nebo consume a nakonec zavolá metody destroy. Nakonec popíšeme rozhraní PluginHelpResolver. To nabízí jedinou metodu gethelpmsgforplugin. Jediná implementace tohoto rozhraní třída AnnotationPluginHelpResolver získá jako parametr identifikaci pluginu, pro který má být vrácena nápověda. Tento plugin musí být označen pomocí Z této anotace jsou získány informace o umístění a názvu soboru s nápovědou. Obsah tohoto souboru je načten a vrácen jako návratová hodnota. 3.9 Další pomocná rozhraní a třídy Kromě doposud popsaných rozhraní a tříd existují další, které mají usnadnit implementaci transformací. V této sekci popíšeme bez bližších podrobností nejdůležitější z nich. K nim patří třída PatchSet, do které lze uložit informace o transformaci, která má být provedena (například nahrazení jedné instrukce blokem jiných, atp.), ale vykonání této transformace je odloženo až do zavolání metody applypatch. To se hodí v okamžiku, kdy provádíme transformaci podle analýzy, jejímž výsledkem jsou indexy do pole instrukcí, které by se přidáním nebo odstraněním instrukcí zneplatnily. Příkladem takové analýzy je třída DataFlowAnalyzer. Ta vrací seznam základních bloků transformované metody (instance třídy BasicBlock). Jeden základní blok je určen indexy do seznamu instrukcí. 39

45 Další užitečnou třídou, respektive rozhraním, je třída AsmLocalVarAllocator implementující rozhraní LocalVarAllocator. Ta slouží k alokaci nových lokálních proměnných s unikátními názvy v atributech LocalVariableTable a LocalVariableTypeTable. Tyto atributy bytecodu shrnují informace o lokálních proměnných pro účely ladění. Tyto informace jsou důležité pro správnou funkčnost nástrojů J2BP a PANDA. Výčtový typ Instructions slouží k získání určitých informací o instrukci například její kategorii, o počtu operandů, které musejí být před jejím vykonáním umístěny na zásobník, nebo její textovou reprezentaci vhodnou pro ladění a logování. Programátor nových transformací může použít dvě základní výjimky: UserException a ProgramException. UserException by měla být vyhazována v okamžiku, kdy je chyba způsobena uživatelem aplikace například špatně zadaný parametr při spuštění aplikace. ProgramException slouží k vyhození v případě, že chyba je způsobena přímo aplikací například neočekávaný stav ve switch bloku. Do této výjimky je také možné zabalit a potom vyhodit zachycenou neočekávanou kontrolovanou výjimku například java.io.ioexception. Kromě výše popsaných tříd vzniklo několik utilitárních tříd jako například AsmUtils, ClassPathUtils, AssertUtils, atd Použití aplikace V následujících sekcích popíšeme, jak spustit aplikaci a které volby k tomu můžeme použít Jak spustit aplikaci Adresář obsahující aplikaci se skládá ze dvou podadresářů: lib a bin. V adresáři lib jsou obsaženy všechny soubory, které musí být při spuštění aplikace na classpath. Základním souborem v adresáři lib je BytecodeTransformer.jar, což je spustitelný jar soubor obsahující zkompilovanou aplikaci a informace o závislostech. Aplikace tedy muže být spuštěna pomocí Javy 8 následujícím příkazem: java -jar BytecodeTransformer.jar -param1 -param2 -paramn Například následující příkaz provede transformaci ternárního operátoru, kde se transformovaný program nachází v adresáři sourcedir a výsledek bude uložen do adresáře outputdir : 40

46 java -jar BytecodeTransformer.jar -plugins "ClassPathReader;TernaryTransformer;ClassPathWriter" -VClassPathReader.classpath=sourceDir -VClassPathWriter.ouputDir=outputDir Kromě tohoto způsobu lze spustit aplikaci pro vykonání transformace jednodušeji pomocí skriptu v adresáři bin. Ten obsahuje skripty jak pro OS Linux tak pro OS Windows. Byl vytvořen skript pro spuštění každé transformace zvlášť, případně pro spuštění sady transformací pro nástroj J2BP ( run_j2bp.sh nebo run_j2bp.cmd ) nebo PANDA ( run_panda.sh nebo run_panda.cmd ), případně pro spuštění úplně všech transformací ( run_all.sh nebo run_all.cmd ). Každá z těchto transformací bere dva parametry: classpath s programem k transformaci a adresář, kde bude umístěn výsledný program po transformaci. Například následující příkaz spustí transformaci ternárního operátoru stejně jako příkaz prezentovaný výše. run_ternarytransformation.cmd sourcedir outputdir Výše popsané skripty se pokusí dohledat Javu ve verzi 8 následujícím postupem: nejdříve se zkusí systémová proměnná BCT_JAVA_HOME 16, která by měla obsahovat adresář s instalací Javy (například: C:\Program Files\Java\jre1.8.0_40 ), potom se zkusí standardní systémová proměnná JAVA_HOME a nakonec se vyzkouší samotný příkaz java. Pokud ani pomocí jednoho z těchto umístění nelze spustit Javu ve verzi 8, tak je zahlášena chyba. Toto řešení umožňuje mít například na path nebo v proměnné JAVA_HOME Javu v jiné verzi než Volby pro spuštění aplikace Aplikaci je možné spouštět následujícími způsoby: 1. -plugins pluginname1;pluginname2;pluginnamen -VpluginName1.paramName1=value1 -VpluginName1.paramName2=value2 -VpluginName2.paramName1=value3... [-statistics] 2. -availableplugins 3. -pluginhelp pluginname 16 BCT je zkratka pro BytecodeTransformer 41

47 4. -help První a nejdůležitější volba -plugins spouští vybrané pluginy, které jsou zadány pomocí jmen oddělených znakem ;. Každý zadaný plugin může přijímat vstupní parametry. Tyto parametry jsou zadávány pomocí volby -V, za kterou následuje identifikace pluginu, potom znak = a nakonec hodnota vstupního parametru. Jako příklad poslouží spouštění ternárního operátoru výše. Jako nepovinný parametr může být použit -statistics, který vypíše statistiky o provedených transformacích. Bezparametrická volba -availableplugins slouží k vypsání pluginů dostupných v aplikaci. Volba -pluginhelp, která jako parametr bere jméno pluginu, vypíše informace o zadaném pluginu. Volba -help vypíše nápovědu k použití aplikace. 42

48 4 Transformace bytecodu V následujících sekcích budou popsány transformace bytecodu implementované v rámci této práce (viz Tab. 5). Každá sekce má pro přehlednost podobnou strukturu. Nejdříve jsou uvedeny informace a příklady jevu v bytecodu a/nebo v Javě, který by měl být transformován. Tyto příklady jsou obvykle podle nástroje, pro který je určena daná transformace, uvedeny buď v Javě 6 a v bytecodu generovaného překladačem z JDK verze 6 (pro nástroj J2BP), nebo v Javě 7 a v bytecodu generovaného překladačem z JDK 7 (pro nástroj PANDA). Poté obvykle následuje podsekce o důvodech a cílech transformace. V další podsekci je pak standardně uveden a zdůvodněn postup implementované transformace. Poslední podsekce se většinou zabývá implementačními detaily, jako například v jakém balíčku a třídách je transformace implementována anebo jaké speciální třídy byly při implementaci použity. V některých případech je sekce ještě doplněna o informace, jak by bylo možné transformaci vylepšit nebo rozšířit. Transformace Sekce Transformační třída Nástroj Název lokální 4.1 UniqueLocalVarNameTransformer PANDA proměnné Umístění lokální 4.2 UniqueLocalVarSlotTransformer PANDA, proměnné J2BP Název instanční 4.3 UniqueFieldNameTransformer PANDA proměnné Ternární operátor 4.4 TernaryTransformer J2BP Nahrazení hromadných metod 4.5 Více různých tříd J2BP Nahrazení metody 4.6 EntrySetMethodTransformer J2BP entryset Inicializace pole 4.7 ArrayInitTransformer PANDA Vícedimenzionální pole 4.8 MultiDimArrayTransformer PANDA Úprava výrazu assert 4.9 AssertTransformer PANDA, J2BP Omezení pravé strany přiřazení 4.10 RightHandSideTransformer PANDA Tab. 5 Přehled implementovaných transformací 43

49 UML diagram na obrázku 9 zobrazuje hierarchii všech transformační tříd včetně abstraktních předků a rozhraní. 4.1 Název lokální proměnné Obr. 9 Hierarchie transformačních tříd První transformace, kterou popíšeme, se týká unikátnosti názvu lokálních proměnných Názvy lokálních proměnných v Javě V jazyce Java mohou mít dvě různé lokální (v rámci metody) proměnné stejný název, pokud se nepřekrývá jejich rozsah platnosti [26]. Například v této jednoduché metodě se vyskytují tři různé lokální proměnné s jediným názvem xxx: public static void testexamplevariablename() { { int xxx = 10; } { double xxx = 2.71; } { List<String> xxx = null; } } 44

50 4.1.2 Názvy lokálních proměnných v bytecodu Až do verze JVM 7 (včetně) se názvy lokálních proměnných vyskytovaly v bytecodu pouze v atributech LocalVariableTable a LocalVariableTypeTable [27]. Oba tyto atributy jsou nepovinné a slouží pro účely ladění překladač je vygeneruje na požádání pomocí volby -g [28]. Atribut LocalVariableTable slouží k určení hodnoty lokální proměnné tj. obsahuje informace o umístění, názvu, deskriptoru a rozsahu platnosti dané lokální proměnné. Platnost je určena pomocí položek Start a Length, kde Start ukazuje na první instrukci, na které má proměnná již přiřazenu hodnotu a výraz Start + Length udává pozici první instrukce, kde není hodnota proměnné definována. Atribut LocalVariableTypeTable byl přidán až ve verzi JVM 5 a obsahuje informace o lokálních proměnných s generickým parametrem. Informace jsou stejné jako v případě atributu LocalVariableTable jen s tím rozdílem, že se pro proměnnou uchovává její signatura namísto deskriptoru. Pro metodu testexamplevariablename uvedenou výše bude bytecode obsahovat tyto atributy LocalVariableTable a LocalVariableTypeTable: LocalVariableTable: Start Length Slot Name Signature xxx I xxx D xxx Ljava/util/List; LocalVariableTypeTable: Start Length Slot Name Signature xxx Ljava/util/List<Ljava/lang/String;>; Cíle transformace Popisovaná transformace by měla zajistit, že každá lokální proměnná v rámci metody bude mít unikátní název. To je potřeba, protože nástroj PANDA považuje různé, ale stejně pojmenované lokální proměnné za jednu. To je problematické v okamžiku, kdy se v bytecodu vyskytnou dvě stejně pojmenované různé lokální proměnné - jedna s primitivním typem (například int) a druhá s typem reference (například java.lang.object). Pokus o verifikaci dříve uvedené metody testexamplevariablename skončí následující chybovou hláškou, která vyjadřuje konflikt mezi předchozí a novou typovou informací: 45

51 Current instruction [first step = false]: 8: ASTORE 'List<String> xxx = null;' in method: UniqueLocalVarName_1.testExampleVariableName()V java.lang.classcastexception: gov.nasa.jpf.abstraction.state.universe.primitivelocalvariable cannot be cast to gov.nasa.jpf.abstraction.state.universe.structuredlocalvariable Postup transformace Nejjednodušší, ale naivní algoritmus je projít řádky výše uvedených atributů a na každém řádku zajistit unikátní název lokální proměnné (samozřejmě konzistentně v obou dvou atributech). Tento postup ale nebude fungovat, protože díky rozdělenému rozsahu platnosti může být jedna lokální proměnná uvedena na více místech atributů. Napřiklad pro kód: public static void testexamplevariablename2() { boolean isone; if (somestaticvalue == 1) { isone = true; } else { isone = false; } } bude atribut LocalVariableTable obsahovat dva záznamy pro jednu lokální proměnnou isone: Start Length Slot Name Signature isone Z isone Z Pohledem na bytecode metody testexamplevariablename2, si snadno uvědomíme, že výše uvedený atribut odpovídá specifikaci: 0: getstatic #8 // Field somestaticvalue:i 3: iconst_1 4: if_icmpne 12 7: iconst_1 8: istore_0 9: goto 14 12: iconst_0 13: istore_0 14: return Další možností je přejmenovat pouze lokální proměnné, které mají stejný název, ale liší se buď v umístění (slot) nebo v signatuře (nebo v obou). Tento postup sice neodliší různé lokální proměnné se stejným jménem, signaturou a umístěním, ale to nevadí, protože takovéto proměnné lze považovat za jednu. Liší se pouze v tom, že v atributech LocalVariableTable a LocalVariableTypeTable může být jejich 46

52 rozsah platnosti rozdělen na více nesouvisejících částí ale to nástroji PANDA nevadí. Možná by šlo z bytecodu zrekonstruovat i různé původní lokální proměnné se stejným názvem, signaturou a umístěním jenže pro to by bylo potřeba vytvořit graf toku řízení a tím by se tato jednoduchá transformace velmi zbytečně zkomplikovala Implementační detaily Transformace je implementována ve třídách v balíčku cz.cuni.mff.d3s. bytecodetransformer.plugins.localvarname. Třída označena je pojmenovaná UniqueLocalVarNameTransformer (stejně jako plugin). Z popisu algoritmu popsaného v předchozí sekci je vidět, že se jedná o transformaci, která je lokální pro jednu metodu (upravuje obsah jedné metody nezávisle na ostatním kódu). Z tohoto důvodu je třída UniqueLocalVarNameTransformer děděna od třídy AbstractAsmMethodNodeTransformer, takže jejím úkolem je pro každou transformovanou metodu, která je reprezentována instancí třídy MethodNode z knihovny ASM, vytvořit transformační třídu. Tato transformační třída je pojmenována UniqueLocalVarNameMethodTransformer. V ní je implementován výše popsaný algoritmus. 4.2 Umístění lokální proměnné I v této sekci se budeme zabývat problematikou lokálních proměnných tentokrát jejich umístěním ve slotech, které by mělo být unikátní Umístění lokálních proměnných v bytecodu JVM umisťuje lokální proměnné a parametry metod do tzv. slotů v zásobníkových rámcích. Každá lokální proměnná je v rámci bytecodu identifikována pomocí konstantního indexu slotu, který je určen kompilátorem při překladu. Sloty jsou indexovány od 0, kde 0 připadá na hodnotu this v případě instanční metody, další sloty jsou vzestupně obsazovány parametry metody a nakonec přichází na řadu lokální proměnné. Se sloty, kde jsou umístěny lokální proměnné, dokáže manipulovat pouze omezený počet instrukcí. Tyto instrukce lze rozdělit do několika typů popsaných v tabulce 6. 47

53 Typ Instrukce Popis Načtení Uložení aload, iload, lload, fload, Načte hodnotu ze slotu na dload + varianty s konstantním vrchol zásobníku operandů slotem (například iload_0, ) astore, istore, lstore, fstore, Uloží hodnotu z vrcholu dstore + varianty s konstantním zásobníků operandů do slotu slotem (například istore_0, ) Inkrementace iinc Přičte k lokální proměnné hodnotu konstanty Skok na adresu ret Skočí na adresu uloženou ve slotu (nepoužívá se od JVM 6) Tab. 6 Instrukce pracující se sloty lokálních proměnných Hodnoty lokálních proměnných s typem long nebo double zabírají dva sloty tj. slot bezprostředně následující po takové lokální proměnné nesmí být přiřazen žádné jiné lokální proměnné. Dvě lokální proměnné mohou být přiřazeny do stejného slotu, pokud se jejich rozsah platnosti nepřekrývá Cíle transformace Transformace by měla zajistit, že každá lokální proměnná, která je uvedena v atributu LocalVariableTable a LocalVariableTypeTable, bude umístěna v unikátním slotu. To se bude hodit především pro nástroj J2BP, který nedokáže korektně odlišit dvě různé lokální proměnné v jednom slotu. Při transformaci si bude nutné dát pozor na to, aby se nezměnilo umístění parametrů metody, protože by přestalo fungovat předávání parametrů takovým metodám Postup transformace Základem pro práci algoritmu jsou informace z atributů LocalVariableTable a LocalVariableTypeTable, které byly popsány v sekci 0. Vycházet z těchto dvou atributů je možné díky tomu, že je nástroj J2BP potřebuje pro korektní funkčnost. Postup transformace je možné popsat v následujících krocích: 1. Pomocí atributu LocalVariableTable jsou určeny lokální proměnné, jejichž sloty kolidují. Pokud žádné lokální proměnné nekolidují, tak je transformace ukončena. 48

54 2. Potom je určeno mapování mezi instrukcemi a kolidujícími proměnnými, jejichž platnost začíná nebo končí na dané instrukci. 3. V dalším kroku se postupně procházejí instrukce a pomocí mapování z předchozího kroku se určuje, které lokální proměnné jsou na dané instrukci živé. Pokud na instrukci začíná poprvé platnost nějaké lokální proměnné, tak se určí, zda její slot byl již obsazen jinou proměnnou na nějaké předchozí instrukci. Pokud ano, tak je této proměnné přiřazen nový slot. V opačném případě je uložena informace, že je zatím neobsazený slot lokální proměnné již obsazen. Dále se právě zpracovávané instrukci aktualizuje instrukční operand se slotem lokální proměnné (pokud tento operand instrukce má). 4. Nakonec se aktualizují informace o umístění lokálních proměnných v atributech LocalVariableTable a LocalVariableTypeTable. Na závěr je potřeba zmínit, že transformace, tak jak je popsána, nezmění umístění parametrů metody, protože parametry metody jsou platné přes všechny instrukce transformované metody, takže s nimi nemůže kolidovat žádná jiná lokální proměnná Implementační detaily Transformace je implementována ve třídách, které se nalézají v balíčku cz.cuni. mff.d3s.bytecodetransformer.plugins.localvarslot. Třída označena je pojmenovaná UniqueLocalVarSlotTransformer. Atributu name této je přiřazena hodnota UniqueLocalVarSlotTransformer. Z popisu algoritmu popsaného v předchozí podsekci je vidět, že se jedná o transformaci, která je lokální pro jednu metodu, protože je upravován obsah jedné metody nezávisle na ostatních metodách. Z tohoto důvodu je třída UniqueLocalVarSlotTransformer děděna od třídy AbstractAsmMethodNode Transformer. Třída, ve které je implementována transformace jedné metody, se jmenuje UniqueLocalVarSlotMethodTransformer. V ní je implementován výše popsaný algoritmus. 4.3 Název instanční proměnné V této sekci popíšeme transformaci, která zajistí unikátnost názvů instančních proměnných v rámci hierarchie dědičnosti tříd. Instanční proměnné také bývají označovány jako instanční atributy nebo fieldy. My se ovšem budeme v dalším textu držet názvu instanční proměnné, protože ho považujeme za nejpřesnější. 49

55 4.3.2 Instanční proměnné v bytecodu V bytecodu pracují s instančními proměnnými tříd pouze dvě instrukce putfield a getfield. Obě dvě instrukce mají jen jeden instrukční operand odkaz na konstantu typu field reprezentující proměnnou, se kterou pracují. Instrukce putfield nastavuje hodnotu instanční proměnné. K tomu tato instrukce použije dva operandy na zásobníku první reprezentuje hodnotu, která se má uložit do instanční proměnné, a druhý operand je reference na instanci objektu, ve které se nalézá daná instanční proměnná. Instrukce getfield načte na vrchol zásobníku hodnotu nalézající se v instanční proměnné. K tomu použije z vrcholu zásobníku referenci na instanci objektu, kde se nalézá daná instanční proměnná Cíle transformace Transformace by měla zajistit, že v rámci jedné hierarchie tříd budou mít instanční proměnné unikátní názvy. To znamená, že pokud se v jedné konkrétní třídě vyskytuje proměnná s určitým názvem, tak tento název nesmí být použit pro žádnou instanční proměnnou v jakémkoli potomkovi nebo předkovi této konkrétní třídy. Tento požadavek vychází z toho, že nástroj PANDA nedokáže v určitých kontextech odlišit stejně pojmenované instanční proměnné v různých třídách v jedné hierarchii dědičnosti. Příkladem takového kódu může být třída FieldName_1 v balíčku panda.fieldname.verification v projektu PandaExamples, který je součástí této práce Postup transformace Na rozdíl od ostatních transformací tato musí zohlednit informace ze všech tříd, aby byla schopna rozpoznat instanční proměnné, které je potřeba přejmenovat. Transformaci je možné popsat následujícím postupem: 1. Nejdříve je nutné vytvořit hierarchii dědičnosti tříd. Tento krok se týká pouze tříd určených k transformaci. Výsledkem této fáze je seznam acyklických neorientovaných grafů (stromů), které reprezentují hierarchii dědičnosti. Jako kořen stromu je určena třída, která má za předka třídu, která není určena k transformaci - to může být například třída ze standardní knihovny. 2. V dalším kroku je potřeba podle vytvořené hierarchie dědičnosti určit instanční proměnné pro přejmenování. Při rozhodování, zda přejmenovat proměnnou v předkovi nebo v potomkovi, byla jako vhodnější určena 50

56 varianta přejmenovávat instanční proměnné v potomkovi, protože tato úprava ovlivní menší počet tříd. Výsledkem druhého kroku je tedy seznam přesně určených instančních proměnných k přejmenování. 3. V dalším kroku je potřeba stanovit nové názvy pro proměnné určené k přejmenování. Pro účely ladění se ukázalo, že je vhodné, aby nové jméno bylo odvozeno od původního jména proměnné. Dále je nutné zkontrolovat, zda nové jméno nebude kolidovat s jiným jménem proměnné v hierarchii dědičnosti (ať s původním nebo s nově vytvořeným). V rámci tohoto kroku se také provede přejmenování instančních proměnných v rámci transformovaných tříd. 4. Nakonec je potřeba projít instrukce všech metod a upravit názvy přejmenovaných instančních proměnných. Tento krok se týká pouze instrukcí putfield a getfield Problémy a možná vylepšení transformace Z popisu je jasné, že tato transformace může změnit sémantiku programu. Například přestane fungovat kód, který manipuluje s přejmenovanou instanční proměnnou pomocí reflexe. Nicméně bez této úpravy by nástroj PANDA tento kód stejně nezpracoval korektně, takže transformace v tomto ohledu nic nepokazí. Dále může popsaná transformace mít vliv na vstupní predikáty, kde může být původní jméno proměnné použito v nějakém výrazu. Transformace v současné podobě zkoumá pouze instanční proměnné ve třídách, které se mají transformovat. Tj. neberou se do úvahy třídy ze standardní knihovny nebo z knihoven, které byly použity v transformovaném programu, ale samy nejsou určeny k transformaci. Jako příklad této situace může sloužit uživatelova třída, která dědí od třídy ze standardní knihovny, a obě dvě tyto třídy mají deklarovánu instanční proměnnou se stejným názvem. Transformace potom nezmění jméno dané instanční proměnné v uživatelově třídě. Druhou možnost lze spíše považovat za chybu uživatele, protože by pro správnou funkčnost měl být transformován celý program včetně všech knihoven. První možnost nebyla záměrně implementována, protože se jedná o poměrně vzácný jev a celá transformace by se tím poměrně zkomplikovala. Bylo by nutné řešit otázky jako například, kde vzít standardní knihovnu, která by odpovídala verze Javy, ve které byl vyvíjen transformovaný program. 51

57 4.3.6 Implementační detaily Třídy, ze kterých se skládá transformace, jsou umístěny v balíčku cz.cuni.mff.d3s.bytecodetransformer.plugins.fieldname. Základní transformační třída se jmenuje UniqueFieldNameTransformer. Tato třída je tedy označena se jménem (atribut name) UniqueFieldName Transformer. V metodě transform třídy UniqueFieldNameTransformer se nejdříve z bytecodu každé transformované třídy vytvoří instance třídy ClassNode (třída z knihovny ASM reprezentující bytecode jedné třídy). Z nich se potom pomocí metody createrootclassnodelist vytvoří seznam instancí tříd, jež reprezentují kořeny stromů hierarchií dědičností transformovaných tříd. Tato metoda tedy odpovídá kroku jedna z popisovaného algoritmu. Vrcholy stromů jsou reprezentovány pomocí třídy ClassNodeWrapper. Tato třída zapouzdřuje (návrhový vzor adaptér) instanci třídy ClassNode. Třída ClassNodeWrapper obsahuje metody k určení předka (metoda getsuperclass) a potomků (metoda getsubclasses) reprezentované třídy. Dále je možné na této třídě nalézt metody pro usnadnění práce při přejmenování instanční proměnných tj. metody jako například isfieldnameusedinhierarchy, renamefield, atp. V dalším kroku se pro každou hierarchii dědičnosti určí seznam instančních proměnných, které je potřeba přejmenovat, a zároveň se pro tyto proměnné určí nová jména. Metoda determinefieldsforrenaming z třídy UniqueFieldName Transformer, která jako parametr bere instanci třídy ClassNodeWrapper reprezentující kořen hierarchie dědičnosti, určí, které instanční proměnné je potřeba přejmenovat. Instanční proměnné určené k přejmenování je reprezentována třídou FieldWrapper. Metoda renamefield z třídy UniqueFieldNameTransformer těmto instančním proměnným nastaví nová jména. V dalším kroku se potom projdou instrukce bytecodu ze všech transformovaných tříd a příslušné instrukce putfield a getfield jsou aktualizovány. Nakonec se provede serializace transformovaného bytecodu. 4.4 Ternární operátor V této sekci popíšeme transformaci kódu, který vzniká především (ale ne jen) při překladu ternárního operátoru. Tato transformace se bude hodit pro nástroj J2BP. 52

58 4.4.1 Překlad ternárního operátoru do bytecodu Ternární operátor se skládá ze tří výrazů. První z nich se musí vyhodnotit na hodnotu typu boolean a podle této hodnoty se rozhodne, zda se vyhodnotí druhý (pokud první výraz nabírá hodnotu true) nebo třetí výraz (pokud první výraz nabírá hodnotu false). Druhý a třetí výraz se musí vyhodnotit na stejný typ. Výsledkem ternárního operátoru je tedy hodnota, která musí být nějak zpracována například přiřazena do proměnné nebo použita jako parametr volání metody tzv. lvalue. Následující kód ukazuje jednoduché použití ternárního operátoru: int x = 0; int value = x == 0? 0 : 1; Ternární operátor se překládá tak, že se nejdříve vyhodnotí první výraz a podle jeho výsledku se buď plynule přejde na vyhodnocení druhého výrazu, jehož výsledek je zanechán na vrcholu zásobníku, a po něm následuje skok na konec ternárního operátoru, nebo se skočí na vyhodnocení třetího výrazu, jehož výsledek je zanechán na vrcholu zásobníku, a po něm se plynule přejde na konec ternárního operátoru. Následuje překlad druhé řádky z kódu výše: 2: iload_0 //nacteni hodnoty z promenne x na zadobnik 3: ifne 10 //skok pokud nactena hodnota není 0 6: iconst_0 //vlozeni hodnoty 0 na zasobnik 7: goto 11 //skok na konec ternarniho operatoru 10: iconst_1 //vlozeni hodnoty 0 na zasobnik 11: istore_1 //ulozeni vysledku ternarniho op. do prom. value V případě, kdy jeden nebo více z výrazů obsahuje další vnořený ternární operátor, tak se tyto ternární operátory postupně vyhodnocují tak, že výsledkem nejvnitřnější větve je vyhodnocení (neternárního) výrazu a skok na pozici konce celého (hlavního) ternárního operátoru. Pokud se jedná o poslední výraz v rámci hlavního ternárního operátoru, tak nenásleduje žádná instrukce. Například pro následující ternární operátor: 53

59 x == 0? (y == 0? 0 : 1) : (y == 0? 2 : 3) je vytvořen kód, který odpovídá grafu toku řízení na obrázku 10. Obr. 10 Graf toku řízení ternárního operátoru Skrytý ternární operátor Jako skrytý ternární operátor označujeme kód, který vzniká při vyhodnocení výrazů, jehož výsledkem je hodnota typu boolean. Hodnota typu boolean je v bytecodu vnitřně reprezentována pomocí hodnot 0 nebo 1. Proto se při překladu výrazu typu boolean používá kód, který odpovídá bytecodu generovanému pro ternární operátor, jehož výsledkem je jedna z hodnot 0 nebo 1. Následující výpis kódu na prvním řádku ukazuje výraz, jehož výsledkem je hodnota typu boolean, a na druhém řádku je zobrazen kód v Javě, který by se přeložil stejně jako výraz na prvním řádku. boolean result = number < 10; int result = number < 10? 1 : 0; Cíle transformace Při testech a analýze nástroje J2BP se ukázalo, že má problém korektně zpracovat kód, který je generován pro ternární operátor kvůli skoku, jež následuje po uložení výsledné hodnoty na zásobník. Jako nejvhodnější způsob transformace se ukázal 54

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007 Úvod do programovacích jazyků (Java) Michal Krátký 1 Katedra informatiky VŠB Technická univerzita Ostrava Úvod do programovacích jazyků (Java), 2006/2007 c 2006 Michal Krátký Úvod do programovacích jazyků

Více

Úvod do programovacích jazyků (Java)

Úvod do programovacích jazyků (Java) Úvod do programovacích jazyků (Java) Michal Krátký Katedra informatiky VŠB Technická univerzita Ostrava Úvod do programovacích jazyků (Java), 2007/2008 c 2006 2008 Michal Krátký Úvod do programovacích

Více

1. Programování proti rozhraní

1. Programování proti rozhraní 1. Programování proti rozhraní Cíl látky Cílem tohoto bloku je seznámení se s jednou z nejdůležitější programátorskou technikou v objektově orientovaném programování. Tou technikou je využívaní rozhraní

Více

Úvod do programovacích jazyků (Java)

Úvod do programovacích jazyků (Java) Úvod do programovacích jazyků (Java) Michal Krátký Katedra informatiky VŠB Technická univerzita Ostrava Úvod do programovacích jazyků (Java), 2007/2008 c 2006 2008 Michal Krátký Úvod do programovacích

Více

TÉMATICKÝ OKRUH Softwarové inženýrství

TÉMATICKÝ OKRUH Softwarové inženýrství TÉMATICKÝ OKRUH Softwarové inženýrství Číslo otázky : 24. Otázka : Implementační fáze. Postupy při specifikaci organizace softwarových komponent pomocí UML. Mapování modelů na struktury programovacího

Více

14.4.2010. Obsah přednášky 7. Základy programování (IZAPR) Přednáška 7. Parametry metod. Parametry, argumenty. Parametry metod.

14.4.2010. Obsah přednášky 7. Základy programování (IZAPR) Přednáška 7. Parametry metod. Parametry, argumenty. Parametry metod. Základy programování (IZAPR) Přednáška 7 Ing. Michael Bažant, Ph.D. Katedra softwarových technologií Kancelář č. 229, Náměstí Čs. legií Michael.Bazant@upce.cz Obsah přednášky 7 Parametry metod, předávání

Více

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007 Úvod do programovacích jazyků (Java) Michal Krátký Katedra informatiky VŠB Technická univerzita Ostrava Úvod do programovacích jazyků (Java), 2006/2007 c 2006 Michal Krátký Úvod do programovacích jazyků

Více

Výčtový typ strana 67

Výčtový typ strana 67 Výčtový typ strana 67 8. Výčtový typ V této kapitole si ukážeme, jak implementovat v Javě statické seznamy konstant (hodnot). Příkladem mohou být dny v týdnu, měsíce v roce, planety obíhající kolem slunce

Více

Abstraktní datové typy: zásobník

Abstraktní datové typy: zásobník Abstraktní datové typy: zásobník doc. Ing. Miroslav Beneš, Ph.D. katedra informatiky FEI VŠB-TUO A-1007 / 597 324 213 http://www.cs.vsb.cz/benes Miroslav.Benes@vsb.cz Abstraktní datové typy omezené rozhraní

Více

8 Třídy, objekty, metody, předávání argumentů metod

8 Třídy, objekty, metody, předávání argumentů metod 8 Třídy, objekty, metody, předávání argumentů metod Studijní cíl Tento studijní blok má za cíl pokračovat v základních prvcích jazyka Java. Konkrétně bude věnována pozornost třídám a objektům, instančním

Více

Teoretické minimum z PJV

Teoretické minimum z PJV Teoretické minimum z PJV Pozn.: následující text popisuje vlastnosti jazyka Java zjednodušeně pouze pro potřeby výuky. Třída Zavádí se v programu deklarací třídy což je část programu od klíčových slov

Více

11. Přehled prog. jazyků

11. Přehled prog. jazyků Jiří Vokřínek, 2016 B6B36ZAL - Přednáška 11 1 Základy algoritmizace 11. Přehled prog. jazyků doc. Ing. Jiří Vokřínek, Ph.D. Katedra počítačů Fakulta elektrotechnická České vysoké učení technické v Praze

Více

5 Přehled operátorů, příkazy, přetypování

5 Přehled operátorů, příkazy, přetypování 5 Přehled operátorů, příkazy, přetypování Studijní cíl Tento studijní blok má za cíl pokračovat v základních prvcích jazyka Java. Konkrétně budou uvedeny detaily týkající se operátorů. Doba nutná k nastudování

Více

Algoritmizace a programování

Algoritmizace a programování Algoritmizace a programování Struktura programu Vytvoření nové aplikace Struktura programu Základní syntaktické elementy První aplikace Verze pro akademický rok 2012/2013 1 Nová aplikace NetBeans Ve vývojovém

Více

Předmluva k aktuálnímu vydání Úvod k prvnímu vydání z roku Typografické a syntaktické konvence... 20

Předmluva k aktuálnímu vydání Úvod k prvnímu vydání z roku Typografické a syntaktické konvence... 20 Obsah 5 Obsah Předmluva k aktuálnímu vydání 15 1 Úvod k prvnímu vydání z roku 2000 16 Typografické a syntaktické konvence................ 20 2 Základní pojmy 21 2.1 Trocha historie nikoho nezabije................

Více

Algoritmizace a programování

Algoritmizace a programování Algoritmizace a programování Výrazy Operátory Výrazy Verze pro akademický rok 2012/2013 1 Operace, operátory Unární jeden operand, operátor se zapisuje ve většině případů před operand, v některých případech

Více

Datové struktury. alg12 1

Datové struktury. alg12 1 Datové struktury Jedna z klasických knih o programování (autor prof. Wirth) má název Algorithms + Data structures = Programs Datová struktura je množina dat (prvků, složek, datových objektů), pro kterou

Více

Paměť počítače. alg2 1

Paměť počítače. alg2 1 Paměť počítače Výpočetní proces je posloupnost akcí nad daty uloženými v paměti počítače Data jsou v paměti reprezentována posloupnostmi bitů (bit = 0 nebo 1) Připomeňme: paměť je tvořena řadou 8-mi bitových

Více

Počítačové laboratoře bez tajemství aneb naučme se učit algoritmizaci a programování s využitím robotů CZ.1.07/1.3.12/04.0006

Počítačové laboratoře bez tajemství aneb naučme se učit algoritmizaci a programování s využitím robotů CZ.1.07/1.3.12/04.0006 Počítačové laboratoře bez tajemství aneb naučme se učit algoritmizaci a programování s využitím robotů CZ.1.07/1.3.12/04.0006 Lekce 1 Jazyk Java Tento projekt je spolufinancován Evropským sociálním fondem

Více

Generování vnitřní reprezentace programu

Generování vnitřní reprezentace programu Generování vnitřní reprezentace programu Miroslav Beneš Dušan Kolář Možnosti překladu Interpretace Okamžité provádění programu Překlad do instrukcí procesoru Závislost na konkrétním typu procesoru Překlad

Více

Algoritmizace a programování

Algoritmizace a programování Algoritmizace a programování Řídicí struktury jazyka Java Struktura programu Příkazy jazyka Blok příkazů Logické příkazy Ternární logický operátor Verze pro akademický rok 2012/2013 1 Struktura programu

Více

Struktura programu v době běhu

Struktura programu v době běhu Struktura programu v době běhu Miroslav Beneš Dušan Kolář Struktura programu v době běhu Vztah mezi zdrojovým programem a činností přeloženého programu reprezentace dat správa paměti aktivace podprogramů

Více

Zápis programu v jazyce C#

Zápis programu v jazyce C# Zápis programu v jazyce C# Základní syntaktická pravidla C# = case sensitive jazyk rozlišuje velikost písmen Tzv. bílé znaky (Enter, mezera, tab ) ve ZK překladač ignoruje každý příkaz končí ; oddělovač

Více

Programátorská příručka

Programátorská příručka KAPITOLA 1. PROGRAMÁTORSKÁ PŘÍRUČKA Kapitola 1 Programátorská příručka 1.1 Úvod 1.1.1 Technologie Program je psaný v jazyce Java 1.7. GUI je vytvářeno pomocí knihovny SWT. (http://eclipse.org/swt/) Pro

Více

Předměty. Algoritmizace a programování Seminář z programování. Verze pro akademický rok 2012/2013. Verze pro akademický rok 2012/2013

Předměty. Algoritmizace a programování Seminář z programování. Verze pro akademický rok 2012/2013. Verze pro akademický rok 2012/2013 Předměty Algoritmizace a programování Seminář z programování Verze pro akademický rok 2012/2013 Verze pro akademický rok 2012/2013 1 Přednášky Jiřina Královcová MTI, přízemí budovy A Tel: 48 53 53 521

Více

Obsah. Předmluva 13 Zpětná vazba od čtenářů 14 Zdrojové kódy ke knize 15 Errata 15

Obsah. Předmluva 13 Zpětná vazba od čtenářů 14 Zdrojové kódy ke knize 15 Errata 15 Předmluva 13 Zpětná vazba od čtenářů 14 Zdrojové kódy ke knize 15 Errata 15 KAPITOLA 1 Úvod do programo vání v jazyce C++ 17 Základní pojmy 17 Proměnné a konstanty 18 Typy příkazů 18 IDE integrované vývojové

Více

Bridge. Známý jako. Účel. Použitelnost. Handle/Body

Bridge. Známý jako. Účel. Použitelnost. Handle/Body Bridge Bridge Známý jako Handle/Body Účel odděluje abstrakci (rozhraní a jeho sémantiku) od její konkrétní implementace předchází zbytečnému nárůstu počtu tříd při přidávání implementací používá se v době

Více

Java a XML. 10/26/09 1/7 Java a XML

Java a XML. 10/26/09 1/7 Java a XML Java a XML Java i XML jsou přenositelné V javě existuje podpora pro práci s XML, nejčastější akce prováděné při zpracování XML: načítání XML elementů generování nových elementů nebo úprava starého zápis

Více

VYSOKÉ UČENÍ TECHNICKÉ V BRNĚ

VYSOKÉ UČENÍ TECHNICKÉ V BRNĚ VYSOKÉ UČENÍ TECHNICKÉ V BRNĚ BRNO UNIVERSITY OF TECHNOLOGY FAKULTA INFORMAČNÍCH TECHNOLOGIÍ ÚSTAV INFORMAČNÍCH SYSTÉMŮ FACULTY OF INFORMATION TECHNOLOGY DEPARTMENT OF INFORMATION SYSTEMS ZPĚTNÝ PŘEKLADAČ

Více

Jak v Javě primitivní datové typy a jejich reprezentace. BD6B36PJV 002 Fakulta elektrotechnická České vysoké učení technické

Jak v Javě primitivní datové typy a jejich reprezentace. BD6B36PJV 002 Fakulta elektrotechnická České vysoké učení technické Jak v Javě primitivní datové typy a jejich reprezentace BD6B36PJV 002 Fakulta elektrotechnická České vysoké učení technické Obsah Celočíselný datový typ Reálný datový typ Logický datový typ, typ Boolean

Více

PODOBÁ SE JAZYKU C S NĚKTERÝMI OMEZENÍMI GLOBÁLNÍ PROMĚNNÉ. NSWI162: Sémantika programů 2

PODOBÁ SE JAZYKU C S NĚKTERÝMI OMEZENÍMI GLOBÁLNÍ PROMĚNNÉ. NSWI162: Sémantika programů 2 PI JE JEDNODUCHÝ IMPERATIVNÍ PROGRAMOVACÍ JAZYK OBSAHUJE PODPORU ANOTACÍ NEOBSAHUJE NĚKTERÉ TYPICKÉ KONSTRUKTY PROGRAMOVACÍCH JAZYKŮ JAKO JSOU REFERENCE, UKAZATELE, GLOBÁLNÍ PROMĚNNÉ PODOBÁ SE JAZYKU C

Více

Úvod do programování v jazyce Java

Úvod do programování v jazyce Java Úvod do programování v jazyce Java Petr Krajča Katedra informatiky Univerzita Palackého v Olomouci 5. říjen, 2011 Petr Krajča (UP) KMI/UP3J: Seminář I. 5.10.2011 1 / 17 Organizační informace email: petr.krajca@upol.cz

Více

Úvod Seznámení s předmětem Co je.net Vlastnosti.NET Konec. Programování v C# Úvodní slovo 1 / 25

Úvod Seznámení s předmětem Co je.net Vlastnosti.NET Konec. Programování v C# Úvodní slovo 1 / 25 Programování v C# Úvodní slovo 1 / 25 Obsah přednášky Seznámení s předmětem Co je.net Vlastnosti.NET 2 / 25 Kdo je kdo Petr Vaněček vanecek@pf.jcu.cz J 502 Václav Novák vacnovak@pf.jcu.cz?? Při komunikaci

Více

ADT/ADS = abstraktní datové typy / struktury

ADT/ADS = abstraktní datové typy / struktury DT = datové typy obor hodnot, které může proměnná nabývat, s operacemi na tomto oboru určen: obor hodnot + výpočetní operace např. INT = { 2 147 483 648 až +2 147 483 647} + {+,,*,/,} ADT/ADS = abstraktní

Více

DSL manuál. Ing. Jan Hranáč. 27. října 2010. V této kapitole je stručný průvodce k tvorbě v systému DrdSim a (v

DSL manuál. Ing. Jan Hranáč. 27. října 2010. V této kapitole je stručný průvodce k tvorbě v systému DrdSim a (v DSL manuál Ing. Jan Hranáč 27. října 2010 V této kapitole je stručný průvodce k tvorbě v systému DrdSim a (v současné době krátký) seznam vestavěných funkcí systému. 1 Vytvoření nového dobrodružství Nejprve

Více

Algoritmizace a programování

Algoritmizace a programování Algoritmizace a programování Typy Základní (primitivní) datové typy Deklarace Verze pro akademický rok 2012/2013 1 Typy v jazyce Java Základní datové typy (primitivní datové typy) Celočíselné byte, short,

Více

Algoritmizace prostorových úloh

Algoritmizace prostorových úloh INOVACE BAKALÁŘSKÝCH A MAGISTERSKÝCH STUDIJNÍCH OBORŮ NA HORNICKO-GEOLOGICKÉ FAKULTĚ VYSOKÉ ŠKOLY BÁŇSKÉ - TECHNICKÉ UNIVERZITY OSTRAVA Algoritmizace prostorových úloh Datové struktury Daniela Szturcová

Více

Programovací jazyk Pascal

Programovací jazyk Pascal Programovací jazyk Pascal Syntaktická pravidla (syntaxe jazyka) přesná pravidla pro zápis příkazů Sémantická pravidla (sémantika jazyka) pravidla, která každému příkazu přiřadí přesný význam Všechny konstrukce

Více

Matematika v programovacích

Matematika v programovacích Matematika v programovacích jazycích Pavla Kabelíková am.vsb.cz/kabelikova pavla.kabelikova@vsb.cz Úvodní diskuze Otázky: Jaké programovací jazyky znáte? S jakými programovacími jazyky jste již pracovali?

Více

PŘETĚŽOVÁNÍ OPERÁTORŮ

PŘETĚŽOVÁNÍ OPERÁTORŮ PŘETĚŽOVÁNÍ OPERÁTORŮ Jazyk C# podobně jako jazyk C++ umožňuje přetěžovat operátory, tj. rozšířit definice některých standardních operátorů na uživatelem definované typy (třídy a struktury). Stejně jako

Více

Definice třídy. úplná definice. public veřejná třída abstract nesmí být vytvářeny instance final nelze vytvářet potomky

Definice třídy. úplná definice. public veřejná třída abstract nesmí být vytvářeny instance final nelze vytvářet potomky JAVA Třídy Definice třídy úplná definice [public] [abstract] [final] class Jmeno [extends Predek] [impelements SeznamInterfacu] {... // telo tridy public veřejná třída abstract nesmí být vytvářeny instance

Více

Dědění, polymorfismus

Dědění, polymorfismus Programování v jazyce C/C++ Ladislav Vagner úprava Pavel Strnad Dědění. Polymorfismus. Dnešní přednáška Statická a dynamická vazba. Vnitřní reprezentace. VMT tabulka virtuálních metod. Časté chyby. Minulá

Více

2 Postup při programování, úvod do programovacího jazyka Java

2 Postup při programování, úvod do programovacího jazyka Java 2 Postup při programování, úvod do programovacího jazyka Java Studijní cíl V tomto bloku bude věnována pozornost správnému postupu při programování, budou detailně vysvětleny jednotlivé etapy programování

Více

PROGRAMOVACÍ JAZYKY A PŘEKLADAČE PŘEKLADY TYPICKÝCH JAZYKOVÝCH KONSTRUKCÍ PROGRAMOVACÍCH JAZYKŮ.

PROGRAMOVACÍ JAZYKY A PŘEKLADAČE PŘEKLADY TYPICKÝCH JAZYKOVÝCH KONSTRUKCÍ PROGRAMOVACÍCH JAZYKŮ. PROGRAMOVACÍ JAZYKY A PŘEKLADAČE PŘEKLADY TYPICKÝCH JAZYKOVÝCH KONSTRUKCÍ PROGRAMOVACÍCH JAZYKŮ. 2011 Jan Janoušek BI-PJP Evropský sociální fond Praha & EU: Investujeme do vaší budoucnosti Dva základní

Více

1. Dědičnost a polymorfismus

1. Dědičnost a polymorfismus 1. Dědičnost a polymorfismus Cíl látky Cílem této kapitoly je představit klíčové pojmy dědičnosti a polymorfismu. Předtím však je nutné se seznámit se základními pojmy zobecnění neboli generalizace. Komentář

Více

Maturitní otázky z předmětu PROGRAMOVÁNÍ

Maturitní otázky z předmětu PROGRAMOVÁNÍ Wichterlovo gymnázium, Ostrava-Poruba, příspěvková organizace Maturitní otázky z předmětu PROGRAMOVÁNÍ 1. Algoritmus a jeho vlastnosti algoritmus a jeho vlastnosti, formy zápisu algoritmu ověřování správnosti

Více

3. Je defenzivní programování technikou skrývání implementace? Vyberte jednu z nabízených možností: Pravda Nepravda

3. Je defenzivní programování technikou skrývání implementace? Vyberte jednu z nabízených možností: Pravda Nepravda 1. Lze vždy z tzv. instanční třídy vytvořit objekt? 2. Co je nejčastější příčinou vzniku chyb? A. Specifikace B. Testování C. Návrh D. Analýza E. Kódování 3. Je defenzivní programování technikou skrývání

Více

Generické programování

Generické programování Generické programování Od C# verze 2.0 = vytváření kódu s obecným datovým typem Příklad generická metoda, zamění dva parametry: static void Swap(ref T p1, ref T p2) T temp; temp = p1; p1 = p2; p2 =

Více

MATURITNÍ OTÁZKY ELEKTROTECHNIKA - POČÍTAČOVÉ SYSTÉMY 2003/2004 PROGRAMOVÉ VYBAVENÍ POČÍTAČŮ

MATURITNÍ OTÁZKY ELEKTROTECHNIKA - POČÍTAČOVÉ SYSTÉMY 2003/2004 PROGRAMOVÉ VYBAVENÍ POČÍTAČŮ MATURITNÍ OTÁZKY ELEKTROTECHNIKA - POČÍTAČOVÉ SYSTÉMY 2003/2004 PROGRAMOVÉ VYBAVENÍ POČÍTAČŮ 1) PROGRAM, ZDROJOVÝ KÓD, PŘEKLAD PROGRAMU 3 2) HISTORIE TVORBY PROGRAMŮ 3 3) SYNTAXE A SÉMANTIKA 3 4) SPECIFIKACE

Více

7. Datové typy v Javě

7. Datové typy v Javě 7. Datové typy v Javě Primitivní vs. objektové typy Kategorie primitivních typů: integrální, boolean, čísla s pohyblivou řádovou čárkou Pole: deklarace, vytvoření, naplnění, přístup k prvkům, rozsah indexů

Více

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007 Úvod do programovacích jazyků (Java) Michal Krátký Katedra informatiky VŠB Technická univerzita Ostrava Úvod do programovacích jazyků (Java), 2006/2007 c 2006 Michal Krátký Úvod do programovacích jazyků

Více

Programování v C++ 2, 4. cvičení

Programování v C++ 2, 4. cvičení Programování v C++ 2, 4. cvičení statické atributy a metody, konstruktory 1 1 Fakulta jaderná a fyzikálně inženýrská České vysoké učení technické v Praze Zimní semestr 2018/2019 Přehled Přístupová práva

Více

Více o konstruktorech a destruktorech

Více o konstruktorech a destruktorech Více o konstruktorech a destruktorech Více o konstruktorech a o přiřazení... inicializovat objekt lze i pomocí jiného objektu lze provést přiřazení mezi objekty v původním C nebylo možné provést přiřazení

Více

Dynamicky vázané metody. Pozdní vazba, virtuální metody

Dynamicky vázané metody. Pozdní vazba, virtuální metody Dynamicky vázané metody Pozdní vazba, virtuální metody Motivace... class TBod protected: float x,y; public: int vrat_pocet_bodu() return 1; ; od třídy TBod odvodíme: class TUsecka: public TBod protected:

Více

Obsah. Úvod 11 Základy programování 11 Objektový přístup 11 Procvičování 11 Zvláštní odstavce 12 Zpětná vazba od čtenářů 12 Errata 13

Obsah. Úvod 11 Základy programování 11 Objektový přístup 11 Procvičování 11 Zvláštní odstavce 12 Zpětná vazba od čtenářů 12 Errata 13 Úvod 11 Základy programování 11 Objektový přístup 11 Procvičování 11 Zvláštní odstavce 12 Zpětná vazba od čtenářů 12 Errata 13 KAPITOLA 1 Na úvod o Javě 15 Počítačový program 15 Vysokoúrovňový programovací

Více

7. přednáška - třídy, objekty třídy objekty atributy tříd metody tříd

7. přednáška - třídy, objekty třídy objekty atributy tříd metody tříd 7. přednáška - třídy, objekty třídy objekty atributy tříd metody tříd Algoritmizace (Y36ALG), Šumperk - 7. přednáška 1 Třída jako zdroj funkcionality Třída v jazyku Java je programová jednotka tvořená

Více

Class loader. každá třída (java.lang.class) obsahuje referenci na svůj class loader. Implementace class loaderu

Class loader. každá třída (java.lang.class) obsahuje referenci na svůj class loader. Implementace class loaderu Seminář Java Zavádění tříd Radek Kočí Fakulta informačních technologií VUT Duben 2008 Radek Kočí Seminář Java Zavádění tříd 1/ 16 JVM zavádí třídy dynamicky Class loader objekt schopný zavádět třídy abstraktní

Více

Implementace LL(1) překladů

Implementace LL(1) překladů Překladače, přednáška č. 6 Ústav informatiky, FPF SU Opava sarka.vavreckova@fpf.slu.cz Poslední aktualizace: 30. října 2007 Postup Programujeme syntaktickou analýzu: 1 Navrhneme vhodnou LL(1) gramatiku

Více

Základní pojmy. Úvod do programování. Základní pojmy. Zápis algoritmu. Výraz. Základní pojmy

Základní pojmy. Úvod do programování. Základní pojmy. Zápis algoritmu. Výraz. Základní pojmy Úvod do programování Michal Krátký 1,Jiří Dvorský 1 1 Katedra informatiky VŠB Technická univerzita Ostrava Úvod do programování, 2004/2005 Procesor Procesorem je objekt, který vykonává algoritmem popisovanou

Více

Programování v C++ 3, 3. cvičení

Programování v C++ 3, 3. cvičení Programování v C++ 3, 3. cvičení úvod do objektově orientovaného programování 1 1 Fakulta jaderná a fyzikálně inženýrská České vysoké učení technické v Praze Zimní semestr 2018/2019 Přehled Dokončení spojového

Více

ALGORITMIZACE A PROGRAMOVÁNÍ

ALGORITMIZACE A PROGRAMOVÁNÍ Metodický list č. 1 Algoritmus a jeho implementace počítačovým programem Základním cílem tohoto tematického celku je vysvětlení pojmů algoritmus a programová implementace algoritmu. Dále je cílem seznámení

Více

4a. Makra Visual Basic pro Microsoft Excel Cyklické odkazy a iterace Makra funkce a metody

4a. Makra Visual Basic pro Microsoft Excel Cyklické odkazy a iterace Makra funkce a metody 4a. Makra Visual Basic pro Microsoft Excel Cyklické odkazy a iterace Makra funkce a metody Vytvořil Institut biostatistiky a analýz, Masarykova univerzita J. Kalina Cyklické odkazy a iterativní výpočty

Více

Paralelní programování

Paralelní programování Paralelní programování přednášky Jan Outrata únor duben 2011 Jan Outrata (KI UP) Paralelní programování únor duben 2011 1 / 14 Atomické akce dále nedělitelná = neproložitelná jiným procesem izolovaná =

Více

Tento studijní blok má za cíl pokračovat v základních prvcích jazyka Java. Konkrétně bude věnována pozornost rozhraním a výjimkám.

Tento studijní blok má za cíl pokračovat v základních prvcích jazyka Java. Konkrétně bude věnována pozornost rozhraním a výjimkám. 13 Rozhraní, výjimky Studijní cíl Tento studijní blok má za cíl pokračovat v základních prvcích jazyka Java. Konkrétně bude věnována pozornost rozhraním a výjimkám. Doba nutná k nastudování 2 2,5 hodiny

Více

Úvod do jazyka C. Ing. Jan Fikejz (KST, FEI) Fakulta elektrotechniky a informatiky Katedra softwarových technologií

Úvod do jazyka C. Ing. Jan Fikejz (KST, FEI) Fakulta elektrotechniky a informatiky Katedra softwarových technologií 1 Fakulta elektrotechniky a informatiky Katedra softwarových technologií 12. října 2009 Organizace výuky Přednášky Teoretické základy dle normy jazyka C Cvičení Praktické úlohy odpřednášené látky Prostřední

Více

Základy jazyka C# Obsah přednášky. Architektura.NET Historie Vlastnosti jazyka C# Datové typy Příkazy Prostory jmen Třídy, rozhraní

Základy jazyka C# Obsah přednášky. Architektura.NET Historie Vlastnosti jazyka C# Datové typy Příkazy Prostory jmen Třídy, rozhraní Základy jazyka C# doc. Ing. Miroslav Beneš, Ph.D. katedra informatiky FEI VŠB-TUO A-1007 / 597 324 213 http://www.cs.vsb.cz/benes Miroslav.Benes@vsb.cz Obsah přednášky Architektura.NET Historie Vlastnosti

Více

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007 Úvod do programovacích jazyků (Java) Michal Krátký Katedra informatiky VŠB Technická univerzita Ostrava Úvod do programovacích jazyků (Java), 2006/2007 c 2006 Michal Krátký Úvod do programovacích jazyků

Více

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007 Úvod do programovacích jazyků (Java) Michal Krátký 1 Katedra informatiky VŠB Technická univerzita Ostrava Úvod do programovacích jazyků (Java), 2006/2007 c 2006 Michal Krátký Úvod do programovacích jazyků

Více

GTL GENERATOR NÁSTROJ PRO GENEROVÁNÍ OBJEKTŮ OBJEKTY PRO INFORMATICA POWERCENTER. váš partner na cestě od dat k informacím

GTL GENERATOR NÁSTROJ PRO GENEROVÁNÍ OBJEKTŮ OBJEKTY PRO INFORMATICA POWERCENTER. váš partner na cestě od dat k informacím GTL GENERATOR NÁSTROJ PRO GENEROVÁNÍ OBJEKTŮ OBJEKTY PRO INFORMATICA POWERCENTER váš partner na cestě od dat k informacím globtech spol. s r.o. karlovo náměstí 17 c, praha 2 tel.: +420 221 986 390 info@globtech.cz

Více

Příklad : String txt1 = new String( Ahoj vsichni! ); //vytvoří instanci třídy String a přiřadí ji vnitřní hodnotu Ahoj vsichni!

Příklad : String txt1 = new String( Ahoj vsichni! ); //vytvoří instanci třídy String a přiřadí ji vnitřní hodnotu Ahoj vsichni! Java práce s řetězci Trochu povídání.. Řetězce jsou v Javě reprezentovány instancemi tříd StringBuffer a String. Tyto třídy jsou součástí balíčku java.lang, tudíž je možno s nimi pracovat ihned bez nutného

Více

Obsah přednášky. 12. Dokumentace zdrojového kódu Tvorba elektronické dokumentace UML. Co je diagram tříd. Ing. Ondřej Guth

Obsah přednášky. 12. Dokumentace zdrojového kódu Tvorba elektronické dokumentace UML. Co je diagram tříd. Ing. Ondřej Guth Evropský sociální fond. 12. Dokumentace zdrojového kódu Tvorba elektronické dokumentace Ing. Ondřej Guth Katedra teoretické informatiky Fakulta informačních technologií České vysoké učení technické v Praze

Více

5a. Makra Visual Basic pro Microsoft Escel. Vytvořil Institut biostatistiky a analýz, Masarykova univerzita J. Kalina

5a. Makra Visual Basic pro Microsoft Escel. Vytvořil Institut biostatistiky a analýz, Masarykova univerzita J. Kalina 5a. Makra Visual Basic pro Microsoft Escel Vytvořil Institut biostatistiky a analýz, Masarykova univerzita J. Kalina Cyklické odkazy a iterativní výpočty Zde bude stránka o cyklických odkazech a iteracích.

Více

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007 Úvod do programovacích jazyků (Java) Michal Krátký Katedra informatiky VŠB Technická univerzita Ostrava Úvod do programovacích jazyků (Java), 2006/2007 c 2006 Michal Krátký Úvod do programovacích jazyků

Více

Programování v Javě I. Únor 2009

Programování v Javě I. Únor 2009 Seminář Java Programování v Javě I Radek Kočí Fakulta informačních technologií VUT Únor 2009 Radek Kočí Seminář Java Programování v Javě (1) 1/ 44 Téma přednášky Datové typy Deklarace třídy Modifikátory

Více

Programování v Javě I. Leden 2008

Programování v Javě I. Leden 2008 Seminář Java Programování v Javě I Radek Kočí Fakulta informačních technologií VUT Leden 2008 Radek Kočí Seminář Java Programování v Javě (1) 1/ 45 Téma přednášky Datové typy Deklarace třídy Modifikátory

Více

10 Balíčky, grafické znázornění tříd, základy zapozdření

10 Balíčky, grafické znázornění tříd, základy zapozdření 10 Balíčky, grafické znázornění tříd, základy zapozdření Studijní cíl Tento studijní blok má za cíl pokračovat v základních prvcích jazyka Java. Konkrétně bude věnována pozornost příkazům balíčkům, grafickému

Více

typová konverze typová inference

typová konverze typová inference Seminář Java Programování v Javě II Radek Kočí Fakulta informačních technologií VUT Únor 2008 Radek Kočí Seminář Java Programování v Javě (2) 1/ 36 Téma přednášky Rozhraní: použití, dědičnost Hierarchie

Více

20. Projekt Domácí mediotéka

20. Projekt Domácí mediotéka Projekt Domácí mediotéka strana 211 20. Projekt Domácí mediotéka 20.1. Základní popis, zadání úkolu V projektu Domácí mediotéka (Dome) se jednoduchým způsobem evidují CD a videa. Projekt je velmi jednoduchý

Více

1. lekce. do souboru main.c uložíme následující kód a pomocí F9 ho zkompilujeme a spustíme:

1. lekce. do souboru main.c uložíme následující kód a pomocí F9 ho zkompilujeme a spustíme: 1. lekce 1. Minimální program do souboru main.c uložíme následující kód a pomocí F9 ho zkompilujeme a spustíme: #include #include int main() { printf("hello world!\n"); return 0; 2.

Více

PB161 Programování v jazyce C++ Přednáška 7

PB161 Programování v jazyce C++ Přednáška 7 PB161 Programování v jazyce C++ Přednáška 7 Statické položky tříd Základy OOP Nikola Beneš 6. listopadu 2018 PB161 přednáška 7: static, základy OOP 6. listopadu 2018 1 / 21 Klíčové slovo static Znáte z

Více

Programování v jazyce C a C++

Programování v jazyce C a C++ Programování v jazyce C a C++ Richter 1 Petyovský 2 1. března 2015 1 Ing. Richter Miloslav, Ph.D., UAMT FEKT VUT Brno 2 Ing. Petyovský Petr, UAMT FEKT VUT Brno C++ Stručná charakteristika Nesdíĺı normu

Více

for (i = 0, j = 5; i < 10; i++) { // tělo cyklu }

for (i = 0, j = 5; i < 10; i++) { // tělo cyklu } 5. Operátor čárka, - slouží k jistému určení pořadí vykonání dvou příkazů - oddělím-li čárkou dva příkazy, je jisté, že ten první bude vykonán dříve než příkaz druhý. Např.: i = 5; j = 8; - po překladu

Více

PB161 Programování v jazyce C++ Přednáška 7

PB161 Programování v jazyce C++ Přednáška 7 PB161 Programování v jazyce C++ Přednáška 7 Statické položky tříd Základy OOP Nikola Beneš 6. listopadu 2018 PB161 přednáška 7: static, základy OOP 6. listopadu 2018 1 / 21 Klíčové slovo static Znáte z

Více

Opakování programování

Opakování programování Opakování programování HW návaznost - procesor sběrnice, instrukční sada, optimalizace rychlosti, datové typy, operace (matematické, logické, podmínky, skoky, podprogram ) - paměti a periferie - adresování

Více

7 Formátovaný výstup, třídy, objekty, pole, chyby v programech

7 Formátovaný výstup, třídy, objekty, pole, chyby v programech 7 Formátovaný výstup, třídy, objekty, pole, chyby v programech Studijní cíl Tento studijní blok má za cíl pokračovat v základních prvcích jazyka Java. Konkrétně bude věnována pozornost formátovanému výstupu,

Více

Úvod do programování - Java. Cvičení č.4

Úvod do programování - Java. Cvičení č.4 Úvod do programování - Java Cvičení č.4 1 Sekvence (posloupnost) Sekvence je tvořena posloupností jednoho nebo více příkazů, které se provádějí v pevně daném pořadí. Příkaz se začne provádět až po ukončení

Více

Princip funkce počítače

Princip funkce počítače Princip funkce počítače Princip funkce počítače prvotní úlohou počítačů bylo zrychlit provádění matematických výpočtů první počítače kopírovaly obvyklý postup manuálního provádění výpočtů pokyny pro zpracování

Více

III/2 Inovace a zkvalitnění výuky prostřednictvím ICT

III/2 Inovace a zkvalitnění výuky prostřednictvím ICT Číslo a název šablony Číslo didaktického materiálu Druh didaktického materiálu Autor Jazyk Téma sady didaktických materiálů Téma didaktického materiálu Vyučovací předmět Cílová skupina (ročník) Úroveň

Více

1/1 ČESKÁ ZEMĚDĚLSKÁ UNIVERZITA V PRAZE PROVOZNĚ EKONOMICKÁ FAKULTA PŘIJÍMACÍ ŘÍZENÍ 2017/2018

1/1 ČESKÁ ZEMĚDĚLSKÁ UNIVERZITA V PRAZE PROVOZNĚ EKONOMICKÁ FAKULTA PŘIJÍMACÍ ŘÍZENÍ 2017/2018 ČESKÁ ZEMĚDĚLSKÁ UNIVERZITA V PRAZE PROVOZNĚ EKONOMICKÁ FAKULTA PŘIJÍMACÍ ŘÍZENÍ 2017/2018 Informační technologie 1 - Doporučená doba zpracování: 40 minut 1) Termín DCL v relačně databázové technologii

Více

Programovací jazyk Java

Programovací jazyk Java 1 z 8 Programovací jazyk Java Enumerace (výčty) Složitější definice výčtového typu Konstanty anonymních typů Výčtový typ a datové struktury Java packaging JAR archivy CLASSPATH Apache Ant 10. přednáška

Více

Algoritmizace prostorových úloh

Algoritmizace prostorových úloh INOVACE BAKALÁŘSKÝCH A MAGISTERSKÝCH STUDIJNÍCH OBORŮ NA HORNICKO-GEOLOGICKÉ FAKULTĚ VYSOKÉ ŠKOLY BÁŇSKÉ - TECHNICKÉ UNIVERZITY OSTRAVA Algoritmizace prostorových úloh Datové struktury Daniela Szturcová

Více

6 Příkazy řízení toku

6 Příkazy řízení toku 6 Příkazy řízení toku Studijní cíl Tento studijní blok má za cíl pokračovat v základních prvcích jazyka Java. Konkrétně bude věnována pozornost příkazům pro řízení toku programu. Pro všechny tyto základní

Více

Inovace a zkvalitnění výuky prostřednictvím ICT Základy programování a algoritmizace úloh. Ing. Hodál Jaroslav, Ph.D. VY_32_INOVACE_25 09

Inovace a zkvalitnění výuky prostřednictvím ICT Základy programování a algoritmizace úloh. Ing. Hodál Jaroslav, Ph.D. VY_32_INOVACE_25 09 Střední průmyslová škola a Vyšší odborná škola technická Brno, Sokolská 1 Šablona: Název: Téma: Inovace a zkvalitnění výuky prostřednictvím ICT Základy programování a algoritmizace úloh Operátory Autor:

Více

Základy objektové orientace I. Únor 2010

Základy objektové orientace I. Únor 2010 Seminář Java Základy objektové orientace I Radek Kočí Fakulta informačních technologií VUT Únor 2010 Radek Kočí Seminář Java Základy OO (1) 1/ 20 Téma přednášky Charakteristika objektově orientovaných

Více

přetížení operátorů (o)

přetížení operátorů (o) přetížení operátorů (o) - pro vlastní typy je možné přetížit i operátory (tj. definovat vlastní) - pro definici slouží klíčové slovo operator následované typem/znakem operátoru - deklarace pomocí funkčního

Více

Datové typy v Javě. Tomáš Pitner, upravil Marek Šabo

Datové typy v Javě. Tomáš Pitner, upravil Marek Šabo Datové typy v Javě Tomáš Pitner, upravil Marek Šabo Úvod k datovým typům v Javě Existují dvě základní kategorie datových typů: primitivní a objektové Primitivní v proměnné je uložena přímo hodnota např.

Více

2) Napište algoritmus pro vložení položky na konec dvousměrného seznamu. 3) Napište algoritmus pro vyhledání položky v binárním stromu.

2) Napište algoritmus pro vložení položky na konec dvousměrného seznamu. 3) Napište algoritmus pro vyhledání položky v binárním stromu. Informatika 10. 9. 2013 Jméno a příjmení Rodné číslo 1) Napište algoritmus pro rychlé třídění (quicksort). 2) Napište algoritmus pro vložení položky na konec dvousměrného seznamu. 3) Napište algoritmus

Více

Datové typy strana 29

Datové typy strana 29 Datové typy strana 29 3. Datové typy Jak již bylo uvedeno, Java je přísně typový jazyk, proto je vždy nutno uvést datový typ datového atributu, formálního parametru metody, návratové hodnoty metody nebo

Více

Správa paměti. Karel Richta a kol. Katedra počítačů Fakulta elektrotechnická České vysoké učení technické v Praze Karel Richta, 2016

Správa paměti. Karel Richta a kol. Katedra počítačů Fakulta elektrotechnická České vysoké učení technické v Praze Karel Richta, 2016 Správa paměti Karel Richta a kol. Katedra počítačů Fakulta elektrotechnická České vysoké učení technické v Praze Karel Richta, 2016 Objektové modelování, B36OMO 10/2016, Lekce 2 https://cw.fel.cvut.cz/wiki/courses/xxb36omo/start

Více