Een op spiegel-reflectie gebaseerde debugger voor aspect geori¨enteerd platformen Wouter De Borger 6 juni 2008
Inhoudsopgave 1 Inleiding 1.1 Aspecten . . . . . . . . . . . . . . . 1.2 Debugging . . . . . . . . . . . . . . . 1.3 De vereisten van de aspectdebugger . 1.3.1 Structurele reflectie . . . . . 1.3.2 Zichtbaarheid . . . . . . . . . 1.3.3 Traceerbaarheid . . . . . . . 1.3.4 Inkapseling . . . . . . . . . . 1.4 Het doel van de thesis . . . . . . . . 1.5 Aanpak . . . . . . . . . . . . . . . . 1.6 Tekststructuur . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
2 Bestaande technologie 2.1 AspectJ . . . . . . . . . . . . . . . . . . . . . . . 2.1.1 Syntaxis en Semantiek . . . . . . . . . . . 2.1.2 Joinpoint Model . . . . . . . . . . . . . . 2.1.3 Bestaande ondersteuning voor debugging 2.2 JBoss . . . . . . . . . . . . . . . . . . . . . . . . 2.2.1 Syntaxis en Semantiek . . . . . . . . . . . 2.2.2 Joinpointmodel . . . . . . . . . . . . . . . 2.2.3 Bestaande ondersteuning voor debugging 2.3 Java Platform Debugging Architecture . . . . . . 2.3.1 JVM Tool Interface . . . . . . . . . . . . 2.3.2 Java Debug Wire Protocol . . . . . . . . . 2.3.3 Java Debug Interface . . . . . . . . . . . . 2.4 Alternatieve debugging strategie¨en . . . . . . . . 2.4.1 Source Weaving . . . . . . . . . . . . . . . 2.4.2 Omniscient debugging . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . .
5 5 7 8 8 8 9 9 9 10 10
. . . . . . . . . . . . . . .
11 11 11 13 14 15 15 17 18 18 18 19 20 20 20 20
3 Architectuur 23 3.1 Rationale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 3.1.1 Het gebruik van JDI . . . . . . . . . . . . . . . . . . . . . . . . . . 24 3.1.2 De splitsing front-end back-end . . . . . . . . . . . . . . . . . . . . 25 1
INHOUDSOPGAVE 3.2 3.3 3.4
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
26 30 31 31 33 34 35 36 37 37 38 39 39 39 39
4 Eerste validatie: AspectJ 4.1 AspectJ in relatie tot AJDI . . . . . . . . 4.2 ABC Bytecode . . . . . . . . . . . . . . . 4.2.1 Singleton Aspect . . . . . . . . . . 4.2.2 Per Object Aspect . . . . . . . . . 4.2.3 Per CFLOW Aspect . . . . . . . . 4.2.4 CFLOW . . . . . . . . . . . . . . . 4.2.5 ITD . . . . . . . . . . . . . . . . . 4.3 ABC Bytecode in relatie tot ADB . . . . 4.4 De Aspect Bench Compiler . . . . . . . . 4.4.1 Onderdelen . . . . . . . . . . . . . 4.4.2 Het compilatieproces . . . . . . . . 4.5 Agent . . . . . . . . . . . . . . . . . . . . 4.5.1 Oorspronggebaseerde lijnnummers 4.5.2 Aspectinformatie . . . . . . . . . . 4.5.3 Crosscuttinginformatie . . . . . . . 4.6 Backend . . . . . . . . . . . . . . . . . . . 4.7 Resultaat . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
41 41 43 43 45 45 47 47 49 51 51 52 53 54 56 56 57 58
5 Tweede validatie: JBoss 5.1 JBoss in relatie tot AJDI . . . . . . . . 5.2 JBoss weaving model . . . . . . . . . . . 5.3 JBoss weaving model in relatie tot ADB 5.4 Agent . . . . . . . . . . . . . . . . . . . 5.4.1 Aspect naar klasse . . . . . . . . 5.4.2 Klasse naar aspect . . . . . . . . 5.4.3 Adviezen per aspect . . . . . . . 5.4.4 Applicaties per methode . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
60 60 62 63 65 66 66 67 67
3.5
3.6
Ondersteunbare functionaliteiten Beperkingen . . . . . . . . . . . . Onderdelen . . . . . . . . . . . . 3.4.1 AJDI . . . . . . . . . . . 3.4.2 ADB-Back-end Interface . 3.4.3 ADB . . . . . . . . . . . . 3.4.4 ADB-Back-end . . . . . . 3.4.5 Agent . . . . . . . . . . . Beperkingen . . . . . . . . . . . . 3.5.1 Identiteit . . . . . . . . . 3.5.2 API-overerving . . . . . . 3.5.3 Object Consistentie . . . 3.5.4 Modulaire compositie . . 3.5.5 Ge¨ısoleerde aggregatie . . Evaluatie . . . . . . . . . . . . .
2 . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . .
INHOUDSOPGAVE
5.5 5.6
3
5.4.5 Applicaties per advies . . . . . . . . . . . . . . . . . . . . . . . . . 68 Backend . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 Resultaat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
6 Conclusie
71
A ABC-broncode
73
B Demotool
74
C Bibliografie
77
D Woordenlijst
79
E Acroniemen
82
Voorwoord Eerst en vooral wil ik een aantal mensen bedanken die elk op hun manier hebben bijgedragen aan deze thesis. Mijn speciale dank gaat uit naar: Prof. Dr. ir. W. Joosen, promotor, voor de kritische kijk en de mogelijkheid om AOSD te bezoeken. Bert Lagaisse, dagelijks begeleider, voor de psychologische steun en sturing. Stefan Walraven, lotgenoot, voor de vele boeiende gesprekken over onze thesissen. Mijn familie, voor het warme nest, de levenslange steun en het vele nalezen. Katrien, mijn lieve vriendin, voor alle steun. Excuses voor de vele maanden waarin mijn gedachten ver van de wereld waren.
4
Hoofdstuk 1
Inleiding De aspectgeori¨enteerde technologie wordt stilaan volwassen. De gebruikte technieken worden geavanceerder. Het aantal bestaande talen is al groot en neemt nog toe. De overstap naar de industrie is begonnen. De gebruiksondersteuning is echter nog niet zo volledig. Een echte aspectdebugger bestaat nog niet. In de volgende paragrafen zullen aantonen dat hier nochthans grote nood aan is. Later in deze thesis zal ik ingaan op de architectuur en implementatie van een debugger die zowel AspectJ als JBoss AOP ondersteunt. De eerste twee paragrafen behandelen de fundamentele begrippen op het gebied van aspectgeori¨enteerde technologie en debugging. De derde paragraaf behandelt het probleem en de vereisten die eigen zijn aan aspect debugging. Tot slot worden het doel en de aanpak toegelicht.
1.1
Aspecten
De volgende paragraaf omvat een korte samenvatting van de belangrijkste terminologie op het gebied van aspectge¨ ori¨enteerde technologie. Aspectgeori¨enteerde programmering is het antwoord op crosscuttingconcerns[11]. Dit zijn vereisten die niet modulariseerbaar zijn met klassieke technieken. Typevoorbeelden hiervan zijn logging, concurrency, distributie en persistentie. Een aspect is een samenhangend geheel van functionaliteiten die doorheen de structuur van de uitvoering ingeweven moeten worden. Dit is vergelijkbaar met een klasse. Net zoals klassen kunnen aspecten velden en methoden hebben en abstract zijn. In tegenstelling tot klassen kunnen aspecten niet door de programmeur ge¨ınstantieerd worden: een aspect bepaalt zelf wanneer een instantie aangemaakt moet worden. Aspecten hebben adviezen. Dit zijn functionele eenheden die elders in de uitvoering ingevoegd kunnen worden. Dit is vergelijkbaar met een methode. Een fundamenteel verschil is dat een advies niet aangeroepen kan worden maar ingeweven wordt. Om te bepalen waar adviezen moeten inwerken zijn er joinpoints. Een joinpoint is een gebeurtenis of locatie in het programma waarop een advies kan aangrijpen. Een methodeoproep of toekenning aan een veld zijn voorbeelden van joinpoints. 5
HOOFDSTUK 1. INLEIDING
6
Een pointcut is een conditionele expressie waaraan een bepaalde set van joinpoints voldoet. Door een pointcut aan een advies te binden, wordt bepaald op welke joinpoints het advies ingeweven wordt. Wanneer een advies toegepast wordt op een joinpoint spreekt men van een adviestoepassing. Wanneer meer dan ´e´en advies aangrijpt op ´e´en zelfde joinpoint ontstaat er een advies keten. De volgorde binnen de keten wordt bepaald door de onderlinge voorrangsregels tussen aspecten. Zoals eerder aangehaald zijn aspecten zelfinitialiserend. De aspect scope bepaalt wanneer er een instantie moet worden aangemaakt. Tabel 1.1 beschrijft de verschillende scopes. naam singleton per class per object
verklaring er is maar ´e´en instantie per VM voor elke klasse is er ´e´en instantie voor elk object is er ´e´en instantie
per joinpoint per joinpoint class
voor elk joinpoint is er ´e´en instantie voor elk joinpoint is er ´e´en instantie die gedeeld wordt tussen alle instanties die van dezelfde klasse zijn telkens als in een thread een bepaalde pointcut gepasseerd wordt, wordt er een nieuwe instantie aangemaakt
per CFLOW
synoniemen per VM
vormen
per instance
per this per target
Tabel 1.1: Aspect scopes en hun betekenis
Figuur 1.1: Statisch ER model van aspectsystemen
HOOFDSTUK 1. INLEIDING
7
In dit basismodel ontbreken nog twee belangrijke elementen die we in de hedendaagse AO-systemen terugvinden: ITD en CFLOW. ITD staat voor inter type declaration. ITD’s zijn declaraties binnen een aspect die in staat zijn de type structuur van een andere klasse aan te passen. Dit kan bestaan uit het toevoegen van methoden, velden en interfaces, het relaxeren van de foutpropagatie of het toewijzen van een andere superklasse. CFLOW is het gebruik van controleflow informatie in pointcuts. Dit komt erop neer dat een advies slechts uitgevoerd wordt als de uitvoeringsstapel van de huidige thread in een bepaalde toestand is.
1.2
Debugging
Een groot probleem bij het zoeken naar fouten in software is het feit dat een programmeur de interne toestand van het programma niet kan onderzoeken. Een mogelijke oplossing hiervoor is println debugging: het invoegen van instructies die op bepaalde punten in de uitvoering gegevens uitprinten. Dit is natuurlijk een gevaarlijke manier van debugging omdat het programma ervoor aangepast moet worden. Een betere aanpak is debugging met een debugger. Een debugger is in staat het programma stil te zetten als aan bepaalde voorwaarden voldaan is. De programmeur kan dan, via de debugger, de interne toestand onderzoeken. Dit soort debugging vereist geen expliciete aanpassing aan het basisprogramma en de inspectie kan veel preciezer gedaan worden. Het meest vanzelfsprekende model voor een debugger is een gebruikersgericht programma, zoals bijvoorbeeld de Eclipse Java debugger[8]. Hier wordt er zoveel mogelijk aan de gebruiker overgelaten: er zijn breekpuntbreekpunten om de uitvoering stil te zetten en er is de mogelijkheid tot inspectie van objecten, maar het is niet mogelijk de type structuur te inspecteren. Het is wel zo dat men voor elk type een overzicht van de velden en methoden kan zien, maar dit is volledig van de broncode afhankelijk. Als de broncode en bytecode niet overeenkomen is de informatie fout. Een complexer model, dat meer consistentiegaranties geeft, is spiegel-reflectie [4]. Een spiegeldebugger is in staat een model op te bouwen van het onderzochte programma. Dit model bevat ´e´en object voor elke entiteit in het onderzochte programma. Het spiegelmodel kan ondervraagd worden om zo de toestand van het onderzochte programma te onderzoeken. Om het model in stand te houden, is er natuurlijk een zekere overhead. Het bewaren van de consistentie maakt het ontwikkelen van spiegeldebuggers veel complexer dan de puur gebruikersgerichte debuggers. Een voorbeeld van een spiegel-API is JDI[9]. De paper van Bracha[4] over spiegel-reflectie geeft een aantal principes voor het ontwerpen van een spiegel-API. De vier belangrijkste zijn: encapsulatie, stratificatie, structurele correspondentie en temporele correspondentie. Encapsulatie : de debugger moet zijn implementatie verbergen; Stratificatie : de debugger moet los staan van de taal;
HOOFDSTUK 1. INLEIDING
8
Structurele correspondentie : de structuur van de debugger moet overeenkomen met de structuur van de taal; Temporele correspondentie : de API moet zo gelaagd zijn dat er een verschil is tussen statische en dynamische functionaliteiten.
1.3
De vereisten van de aspectdebugger
De programmeur wil dus de interne structuur van zijn programma’s kunnen inspecteren. Bij OO-talen is het niet zo belangrijk dat er een overzicht is van de controleflow en type informatie, omdat deze informatie allemaal beschikbaar is in de broncode: als je weet welke lijn in de broncode de laatst uitgevoerde is, weet je ook waar alle gerelateerde broncode zich bevindt. Bij AO-talen is dit echter niet meer zo: ITD’s kunnen de type structuur aanpassen en adviezen kunnen de controleflow ombuigen. Met een AO-debugger wil de programmeur kunnen zien waar aspecten inwerken en hoe. Een tweede punt dat aspectdebugging typeert, is dat de machinecode die na compilatie ontstaat structureel verschillend kan zijn van het geschreven programma, omdat er adviezen en ITD’s zijn ingevoegd. Dit zorgt soms voor uitermate complexe machinecode, die in een basistaal-debugger heel moeilijk te begrijpen is. De interne toestand van de taalmechanismen is niet verborgen of verwerkt tot een duidelijk formaat. Om deze problemen op te lossen, stel ik vier aandachtspunten voor:Structurele reflectie, Zichtbaarheid, Traceerbaarheid en Inkapseling.
1.3.1
Structurele reflectie
Om de gebruiker in staat te stellen zijn idee te toetsen aan de toestand van het programma, is het in de eerst plaats belangrijk dat de verschillende structuren van het programma onderzocht kunnen worden. We kunnen drie belangrijk structuren onderscheiden: statische structuur : typeinformatie met eventuele ITD’s; runtime structuur : de toestand van de uitvoeringsstapel en de waarden van alle velden en variabelen; cross cutting structuur : de inwerking van alle aspecten. Er zijn OO debuggers die structurele reflectie aanbieden, zoals JDI, of pseudo structurele reflectie, zoals Eclipse jdt debugging[8], die zijn structurele reflectie baseert op de broncode. Natuurlijk is er nog geen ondersteuning voor ITD’s of cross cutting.
1.3.2
Zichtbaarheid
Zichtbaarheid is de mogelijkheid om runtime-entiteiten terug te brengen tot taalentiteiten en hun lijnnummers. Zichtbaarheid is in OO-debuggers meestal zeer goed ondersteund als het over lijnnummers van instructies gaat, als het over andere entiteiten gaat echter niet. Bij OO debuggers is dit ook niet nodig omdat de broncode coherent
HOOFDSTUK 1. INLEIDING
9
is en alle relevante instructies dus in de buurt van de huidige instructie staan of in een ouderklasse. Bij AO-debuggers is het belangrijk dat alle entiteiten terug te brengen zijn tot lijnnummers, omdat ITD’s entiteiten kunnen induceren in andere klassen.
1.3.3
Traceerbaarheid
Traceerbaarheid is de mogelijkheid om de controleflow te inspecteren. In Eclipse en JDI wordt traceerbaarheid verwezenlijkt door een overzicht van alle stackframes in alle threads. Dit is ook bruikbaar voor een AO debugger, maar er moeten wel aangegeven worden waar de controleflow wordt aangepast door adviezen.
1.3.4
Inkapseling
Om een duidelijk beeld van een programma te geven is het belangrijk dat we geen informatie laten doorlekken over structuren die niet voorkomen in het programma. Alle extra code die de weaver heeft aangemaakt, moet dus verborgen worden. Dit volgt ook uit het principe van de structurele correspondentie voor spiegel-API’s: elke taal moet zijn eigen spiegel-API hebben en er mogen geen details uit de onderliggende taal doorlekken, zelfs niet als dit betekent dat er een heel raamwerk aan het oog moet onttrokken worden. De onderliggende structuur, zij het een taal, zij het een raamwerk, wordt verondersteld foutloos te werken. De debugger staat in voor het onttrekken van de juiste gegevens op een transparante manier.
1.4
Het doel van de thesis
Uit het voorgaande valt af te leiden dat aspect debugging op zich een probleem vormt met duidelijk vereisten. Het is eigenlijk een zeer breed probleem met verschillende facetten: de communicatie met de gebruiker, de verwerking van de gegevens, het verzamelen van de gegevens en het (meta-)modelleren van de taal of taalfamilie. Langs de ander kant zijn er ook vele aspectgeori¨enteerde talen. Het is niet mogelijk al deze problemen op te lossen binnen het bestek van deze thesis. Ik heb dus voor een specifiek deelprobleem gekozen: het construeren van een multiplatform spiegeldebugger. Dit omhelst dus niet de communicatie met de gebruiker, maar wel het modelleren van de taal en het verzamelen en verwerken van de debugging informatie en dat voor meer dan ´e´en taal. Het hoofddoel van mijn thesis is het onderzoeken van de mogelijkheden en knelpunten bij het bouwen van zo een debugger. Ik probeer dus niet zo veel mogelijk functionaliteit te implementeren, maar zoek een evenwicht tussen de meerwaarde voor de gebruiker, de haalbaarheid en de impact op de architectuur. Ik wil de architectuur zo veel mogelijk op de proef stellen, maar toch tijdig eindigen met een afgewerkt en nuttig geheel.
HOOFDSTUK 1. INLEIDING
1.5
10
Aanpak
Om zicht te krijgen op de problemen en mogelijkheden bij het bouwen van een multiplatform spiegeldebugger, zal ik een spiegeldebugger bouwen, die bruibaar is voor meerdere talen. Omdat een spiegeldebugger altijd nood heeft aan een accuraat taalmodel, is het niet zinvol is een spiegel-API te maken voor talen die niet semantisch gelijkaardig zijn. Ik beperk mij dus tot een familie van Java-gebaseerde talen die sematisch gelijkend zijn aan AspectJ. De eerste stap is het ontwerpen van een spiegel-reflectie gebaseerde debugging architectuur: de AO-debugging architecture (AODA). Deze architectuur omvat een spiegelmodel (AJDI) van de ondersteunde taalfamilie en een implementatie van de taalonafhankelijke componenten van de debugger (ADB). Daarna valideer ik de architectuur voor AspectJ en JBoss AOP door de taal afhankelijke componenten van de architectuur uit te werken. AspectJ is de eerste validatie, met een vrij brede ondersteuning. JBoss is de tweede validatie, die vooral moet aantonen dat de architectuur herbruikbaar is. Het is dus niet de bedoeling functionaliteit te ondersteunen waarvan de moeilijkheid vooral ligt in het onttrekken van informatie aan JBoss
1.6
Tekststructuur
Hoofdstuk 2 brengt de beginselen aan van de gebruikte technolgie (AspectJ en JBoss), de ondersteunende technologie (JDPA) en de bestaande AO debugging technologie. Hoofdstuk 3 beschrijft en evalueert in detail de architectuur. Hoofdstuk 4 beschrijft en evalueert de validatie voor AspectJ. Dit omvat ook een gedetaileerde beschrijving van de werking van de ABC-compiler. Hoofdstuk 5 beschrijft en evalueert de validatie voor JBoss AOP. Dit omvat ook een gedetailleerde beschrijving van de werking van de relevante onderdelen van JBoss AOP. Hoofdstuk 6 tracht een conclusie te formuleren over de knelpunten en sterkten van de architectuur. Om het lezen van de tekst makkelijker te maken is achteraan een woordenlijst en een afkortingenlijst toegevoegd.
Hoofdstuk 2
Bestaande technologie Dit hoofdstuk omschrijft de belangrijkste technologie¨en die in mijn thesis gebruikt worden. Eerst komen de ondersteunde talen aan bod: AspectJ en JBoss AOP. Van de talen wordt telkens de syntaxis en semantiek, het joinpoint model en de bestaande ondersteuning voor debugging besproken. Dan komt de ondersteunende technologie aan bod: de Java Platform Debugging Architectuur(JPDA). Tot slot worden ook nog kort twee andere mogelijke werkwijzen voor aspectdebugging behandeld.
2.1
AspectJ
AspectJ is een taaluitbreiding op Java, die buiten de normale Java syntaxis ook een syntaxis voor aspecten aanbiedt. Het is een ’levende taal’: er worden nog steeds mogelijkheden aan toegevoegd. AspectJ is vooral gericht op het aanbrengen van aspecten bij de compilatie. In dit hoofdstuk zal ik de belangrijkste mogelijkheden van versie 1.2.1 overlopen.
2.1.1
Syntaxis en Semantiek
Zoals in Listing 2.1 te zien is, worden aspecten gedefinieerd zoals klassen. Aspecten kunnen methodes en velden hebben en abstract zijn. Ze kunnen overerven van klassen, abstracte aspecten en interfaces. Een aspect mag slechts ´e´en constructor aanbieden en deze mag geen argumenten hebben. Klassen kunnen nooit overerven van aspecten. Een aspect kan een willekeurig aantal adviezen declareren. Aspecten kunnen ook hun onderlinge voorrang declareren. Hiermee kan bepaald worden welk advies eerst moet uitgevoerd worden, als er meerdere adviezen inwerken op ´e´en joinpoint. Aspecten kunnen ook privileged zijn, dit wil zeggen dat ze toegang hebben tot de private velden van andere klassen en aspecten. Een advies kan before, after, after throwing, after returning en around zijn. Before advice wordt uitgevoerd voor het joinpoint waarop het wordt toegepast. After advice
11
HOOFDSTUK 2. BESTAANDE TECHNOLOGIE
12
Listing 2.1: AspectJ code sample 1 package sample ; 2 3 public aspect Sample1 { 4 pointcut stringmethod ( String s ): 5 call (* method *( String )) && args ( s ); 6 7 before (): execution ( String method *()) { 8 System . out . println ( " called " ); 9 } 10 11 before ( String s ): stringmethod ( s ){ 12 System . out . println ( " called 2 " + s ); 13 } 14 }
wordt uitgevoerd na het joinpoint, ongeacht of de terugkeer normaal was of exceptioneel. After throwing advice en after returning advice worden ook uitgevoerd na het joinpoint, maar enkel als de terugkeer respectievelijk normaal of exceptioneel was. Around advice wordt uitgevoerd in plaats van het joinpoint. Bij around advice is de speciale functie proceed() beschikbaar waarmee men de normale uitvoering kan voortzetten. Hiermee bedoel ik het volgende advies op het zelfde joinpoint uitvoeren of, als er geen volgend advies meer is, de code van het joinpoint uitvoeren. Elk advies is gebonden aan juist ´e´en pointcut. De pointcut kan argumenten doorgeven aan het advies. De argumenten moeten juist getypeerd zijn, anders zal het advies niet uitgevoerd worden. Deze typering wordt uitgevoerd door de compiler en is dus statisch. Een advies kan beschikken over informatie van het joinpoint waarvoor het uitgevoerd wordt door de referenties thisJoinPoint en thisJoinPointStaticPart. Elk joinpoint heeft een this en een target referentie om, respectievelijk, naar de uitvoerder en het doel van uitvoering te verwijzen. Een advies kan gebruik maken van deze twee referentie via de thisJoinPoint referentie. Elk aspect kan een willekeurig aantal pointcuts defini¨eren. Deze kunnen een naam hebben als ze direct onder het aspect gedefinieerd zijn (zoals stringmethod in het voorbeeld) of anoniem zijn, als ze deel uitmaken van een adviesdeclaratie. In een abstract aspect kunnen abstracte pointcuts voorkomen. Door een advies te binden aan een abstracte pointcut kan men een advies ´e´en keer defini¨eren en meerdere keren binden. Hierdoor kan men herbruikbare aspecten maken zonder meervoudige of externe binding toe te staan. Aspecten kunnen vier verschillende scopes hebben: singleton, per this, per target en per CFLOW. De scope bepaalt welke aspectinstantie het huidige aspect is. Als er geen huidig aspect is, worden er geen adviezen uitgevoerd.
HOOFDSTUK 2. BESTAANDE TECHNOLOGIE
13
Singleton wil zeggen dat er ´e´en instantie is per VM en het huidige object altijd hetzelfde is. De per CFLOW scope heeft een pointcut als argument. Telkens de controleflow een joinpoint binnengaat dat deze pointcut matcht, wordt er een nieuwe instantie op een stack geduwd. Als het joinpoint weer verlaten wordt, wordt de instantie weer van de stack gehaald. Het bovenste element van de stack wordt altijd gebruikt als het huidige aspect. Per target en per this hebben ook een pointcut als argument. Als de controleflow een joinpoint binnen gaat dat deze pointcut matcht, wordt een instantie van het aspect verbonden aan respectievelijk het this- of targetobject van deze joinpoint. Het huidige aspect op elk gegeven joinpoint is dat aspect dat verbonden is met respectievelijk het this of target object van dat joinpoint. AspectJ ondersteunt ook ITD’s: elk aspect kan velden, methodes, interfaces en constructoren invoegen bij klassen of interfaces. Het is zelfs mogelijk om de ouderklasse van een klasse aan te passen. CFLOW is natuurlijk ook ondersteund: uit elke basis-pointcut kan een CFLOWpointcut afgeleid worden. Een CFLOW-pointcut matcht elke andere joinpoint wanneer het basis-joinpoint op de uitvoeringsstapel staat. Als de basis-pointcut argumenten heeft, die de CFLOW-pointcut gebruikt, dan worden deze op een stack geduwd voor het begin van de basis-joinpoint en er weer af gehaald nadat de basisjoinpoint verlaten wordt. Tot slot zijn er nog de statische mogelijkheden van AspectJ: declare error en declare warning. Hiermee genereert de compiler een waarschuwing of fout wanneer een bepaalde pointcut toegepast wordt. Soften exception is een andere statische feature: hiermee kan men checked Exceptions omzetten in RuntimeExceptions.
2.1.2
Joinpoint Model
Het joinpointmodel van AspectJ omvat zes joinpointtypes, met elk een of meerdere varianten. In dit paragraaf zal ik deze overlopen. method omvat de uitvoering van methoden en heeft twee varianten. call omvat het aanroepen van de methode. Het joinpoint omvat dus de instructies die de methode-oproep realiseren. De thisreferentie van dit soort joinpoint refereert naar het aanroepende object en de targetreferentie naar het aangeroepen object. execution omvat het uitvoeren van de methode. De joinpoint omvat dus het lichaam van de methode. De this- en targetreferentie van dit soort joinpoint refereren beide naar het aangeroepen object constructor omvat de uitvoering van constructoren en heeft twee varianten. call omvat het aanroepen van de constructor. Het joinpoint omvat dus de instructies die de constructoroproep realiseren. De thisreferentie van dit soort
HOOFDSTUK 2. BESTAANDE TECHNOLOGIE
14
joinpoint refereert naar het aanroepende object en de target-referentie is onbestaand omdat het doelobject nog niet is aangemaakt. execution omvat het uitvoeren van de constructor. De joinpoint omvat dus het lichaam van de constructor. De this- en targetreferentie van dit soort joinpoint refereren beide naar het aangeroepen object initialisation omvat de initialisatie van klassen en objecten en heeft drie varianten. sinit omvat het uitvoeren van de statische initialisatie van een klasse. De context is statisch, dus er is geen this- of targetreferentie beschikbaar. init omvat het initialiseren van de klasse, of met andere woorden, het uitvoeren van elke constructor van de klasse. Als een constructor een andere constructor aanroept, wordt dit joinpoint geen tweede keer binnen gegaan. Dit is het grote verschil met constructor execution joinpoints. De this- en targetreferentie verwijzen naar het uitvoerende, nieuwe object. pinit omvat de pre-initialisatie van een klasse: dit zijn de instructies die zich in de constuctor bevinden voor de aanroep van een super (of this) constuctor. De this- en targetreferentie zijn niet beschikbaar. field omvat de toegang tot velden en heeft twee varianten. ref omvat het lezen uit een veld. De thisreferentie refereert naar de aanroepende klasse en de targetreferentie naar de klasse die het veld bevat. set omvat het schrijven naar een veld. De thisreferentie refereert naar de aanroepende klasse en de targetreferentie naar de klasse die het veld bevat. advice omvat de uitvoering van adviezen en heeft ´e´en variant. execution komt overeen met methode execution, maar dan voor adviezen in plaats van methoden. De this- en targetreferentie refereren dus naar het uitvoerende aspect. handler omvat de uitvoering van exception handlers en heeft ´e´en variant. execution omvat het lichaam van een exception handler. De this- en targetreferentie refereren dus weer naar het uitvoerende aspect. Voor meer details kan u de AspectJ programming guide raadplegen[15].
2.1.3
Bestaande ondersteuning voor debugging
AspectJ is een onderdeel van Eclipse en wordt er dus goed door ondersteund. Het AJDT project[1] onderhoudt niet enkel de compiler, maar ook een toolset. Op basis van de broncode kan de cross cutting en type-informatie weergegeven worden. Als de versie van de broncode en de bytecode verschillen kan deze informatie fout zijn. Voor al deze informatie kan de broncode locatie aangegeven worden. Lijnnummers, de stacktrace en
HOOFDSTUK 2. BESTAANDE TECHNOLOGIE
15
instantie-informatie worden op basis van JDI aangemaakt en deze worden als ongefilterde Java-gegevens weergegeven.
2.2
JBoss
JBoss AOP is een van de belangrijkste onderdelen van de JBoss Application server. Het wordt actief verder ontwikkeld. Omdat het intensief gebruikt wordt in een middleware platform is de code geoptimaliseerd en bestaat er veel legacy code. De documentatie is eerder beperkt: de architectuur is grotendeels onbeschreven en de gebruikersdocumentatie is helemaal niet meer up-to-date. In deze thesis gebruik ik de SVN-versie 72465. Ik gebruik een SVN-versie omdat de volgende major release niet meer op tijd komt voor mijn thesis en omdat de code bruikbaarder is dan de vorige release. De code is meer actueel, gestructureerd en de gegevensstructuren zijn beter toegankelijk voor de debugger. Helaas is de documentatie schaars.
2.2.1
Syntaxis en Semantiek
JBoss AOP is een raamwerk gebaseerd op Java. Er wordt gebruik gemaakt van gewone Java-code en een configuratie in XML of met Java-annotaties. De configuratie kan ook programmatorisch tot stand gebracht worden, maar dit is zeer foutgevoelig. JBoss biedt een functionaliteit aan die nog niet eerder besproken is: interceptors. Hiermee bedoelt men een aspect met juist ´e´en advies. Listing 2.2: JBoss interceptor code sample 1 import org . jboss . aop . joinpoint . Invocation ; 2 import org . jboss . aop . advice . Interceptor ; 3 4 @InterceptorDef 5 @Bind ( pointcut = " execution ( String Test - > method *()) " ) 6 public class S impleI nterce ptor implements Interceptor { 7 public String getName (){ return " S impleI nterce ptor " ;} 8 9 public Object invoke ( Invocation invocation ) 10 throws Throwable { 11 System . out . println ( " called " ); 12 return invocation . invokeNext (); 13 } 14 } Zoals te zien in Listing 2.2 is een interceptor een klasse die de interface org.jboss.aop.advice.Interceptor implementeert. Het enige advies van dit aspect heet invoke. De
HOOFDSTUK 2. BESTAANDE TECHNOLOGIE
16
Listing 2.3: JBoss intreceptor code sample 1 xml version = " 1.0 " encoding = " UTF -8 " ? > 2 < aop > 3 < bind pointcut = " execution ( String Test - > method *()) " > 4 < interceptor class = " S impleI nterce ptor " / > 5 bind > 6 aop >
invoke() methode heeft een argument van het type org.jboss.aop.JoinPointInvocation. Dit Invocation-object bevat alle metadata en contextinformatie. Het Invocationobject kan ook gebruikt worden om invokeNext() en invokeTarget() aan te roepen. invokeNext() roept het volgende advies aan en invokeTarget() slaat alle volgende adviezen over en roept het joinpoint aan. Als de programmeur vergeet invokeNext() of invokeTarget() aan te roepen, wordt de advies keten gebroken. Het joinpoint wordt dan nooit uitgevoerd. De annotatie @InterceptorDef geeft aan dat dit een interceptor is. De @Bind annotatie geeft de binding aan. Een alternatieve notatie is te zien in Listing 2.3. Door gebruik te maken van XML kan de configuratie aangepast worden zonder in de code te moeten graven. Gewone aspecten bestaan uit klassen die meerdere methoden bevatten die een Interceptor-argument aanvaarden. Om uit deze klassen een aspect te vormen kan de naam van de klassen meegegeven worden via een annotatie of XML. Een ander optie is het doorgeven van de naam van een factoryklasse. Deze factory wordt aangeroepen telkens er een aspectinstantie nodig is. Het is mogelijk om superinterfaces van Interceptor als argument voor een advies te gebruiken. Dan wordt dit advies enkel aangeroepen als het Invocation object een instantie van dat specifiek subtype is. Men kan dus ook adviezen overloaden. De exacte semantiek hiervan is zeer slecht gedocumenteerd. De huidige versie van JBoss AOP bevat fouten in de code die dit gedrag bestuurt, dus de juiste werking is niet volledig duidelijk. Net zoals in AspectJ is het mogelijk om de voorrang tussen aspecten (en interceptors) te regelen met de precedence-constructie. JBoss AOP ondersteunt ook ITD’s in de vorm van interface introductions, annotation introduction en mixins. Interface introductions staan in voor het toevoegen van interfaces aan klassen. Annotation introduction laat toe annotaties in te voegen en mixin compositie laat toe om zowel een interface als een implementatie toe te voegen aan een klasse. Ook CFLOWwordt ondersteund door de constructie CFLOW-stack. Hiermee kan men een patroon defini¨eren waaraan stack moet voldoen voor het advies uitgevoerd wordt. Dynamische residu’s worden ook ondersteund door de constructie dynamicCFLOW. Hiermee kan men willekeurige code laten uitvoeren om te bepalen of een advies
HOOFDSTUK 2. BESTAANDE TECHNOLOGIE
17
uitgevoerd moet worden. De code moet verpakt worden in de shouldExecute() methode van een klasse van het type org.jboss.aop.pointcut.DynamicCFlow. In JBoss AOP kan de configuratie on-the-fly aangepast worden: adviezen kunnen toegevoegd of verwijderd worden op bestaande joinpoints. Er kunnen echter geen nieuwe joinpoints toegevoegd worden. Om een joinpoint dat geen enkel advies heeft toch te laten voorbereiden voor gebruik bestaat de prepare-constructie. Net zoals bij AspectJ zijn er nog de statische mogelijkheden: declare error en declare warning. Hiermee genereert het raamwerk een waarschuwing of fout wanneer een bepaalde pointcut toegepast wordt. In tegenstelling tot AspectJ heeft JBoss AOP ook ondersteuning voor het gebruik van metadata: adviezen kunnen metadata doorgeven aan elkaar. Bij het initialiseren van aspecten kunnen metadata uit verschillende bronnen uitgelezen worden.
2.2.2
Joinpointmodel
Het JBoss AOP joinpointmodel biedt geen this- of targetreferentie aan. Het Invocationobject dat bij een joinpoint hoort, heeft specifieke methoden om de context op te vragen die afhangen van het joinpointtype. Het joinpointmodel met de aangeboden contextmethoden is te zien in figuur 2.1. Het model lijkt erg op dat van AspectJ, maar is semantisch verschillend.
Figuur 2.1: JBoss joinpointmodel
HOOFDSTUK 2. BESTAANDE TECHNOLOGIE
18
De joinpoints die het meest overeenkomen met hun AspectJ-equivalenten zijn de constructor en method call joinpoints. Ze hebben een doel en een oproeper en worden ingeweven in de oproeper. De structuur is in 4 gesplitst om meer precieze context methoden te kunnen aanbieden, die een onderscheid maken tussen methoden en constructoren. Het method execution joinpoint is ook niet erg verschillend. Het joinpoint omvat de uitvoering van een methode en wordt ingeweven in die methode. Het constructor execution joinpoint daarentegen vertoont wel een verschilend gedrag. Het joinpoint omvat de constructor en wordt daarin aangeroepen, maar beinvloedt ook de instructies waardoor de constructor aangeroepen wordt. Het construction joinpoint komt overeen met het constructor execution joinpoint van AspectJ. Het wordt ingeweven in de constructor en be¨ınvloed de aanroepende klassen niet. Uiteindelijk is er ook nog field access. Hier zijn zoals in AspectJ twee subtypen. Het ene is schrijftoegang, het andere leestoegang.
2.2.3
Bestaande ondersteuning voor debugging
Voor zover wij weten, is er geen specifieke ondersteuning beschikbaar voor JBoss. Er is in de documentatie[10] sprake van een JBoss AOP IDE Eclipse plug-in. Deze plug-in bestaat niet meer en is opgenomen in de JBoss Tools. Deze bieden ondersteuning voor JBoss, maar niet voor AOP debugging. Er zijn ook referenties naar een management bean die in staat is om de informatie over de cross cutting weer te geven. Hierover is verder geen informatie terug te vinden. Er zijn verschillende pagina’s op de site van JBoss over AOP debugging, de meeste zijn al meer dan drie jaar niet bijgewerkt. De werkwijze die daar gepromoot wordt is het gebruik van een standaard Java debugger. Gegeven de complexiteit van JBoss AOP is dit zeer onproductief. Println debugging is waarschijnlijk de beste oplossing.
2.3
Java Platform Debugging Architecture
De Java Platform Debugging Architecture(JPDA)[9] is de standaardarchitectuur voor Java-debugging. Zoals te zien in figuur 2.2 bestaat de architectuur uit drie delen: JVMTI, JDWP en JDI. De volgende paragrafen behandelen deze onderdelen.
2.3.1
JVM Tool Interface
De JVM-TI is een ’native interface’ die aangeboden wordt door de JVM. Als de JVM opstart, kunnen er een of meerdere agenten ingeladen worden die dan de JVM-TI kunnen gebruiken. De JVM-TI laat de agenten toe om de JVM te monitoren zonder deze al te veel te vertragen. De agenten zijn in C geschreven (native) en zijn dus platform afhankelijk. De agenten kunnen op 3 manieren aan informatie komen. Ten eerste zijn er 31 verschillende soorten events die de JVM kan doorgeven naar de agenten. Ten tweede is er de mogelijkheid om de JVM te ondervragen. Ten derde is er de mogelijkheid
HOOFDSTUK 2. BESTAANDE TECHNOLOGIE
19
Figuur 2.2: Overzicht van de Java Platform Debugging Architecture om bytecode te ’instrumenteren’. Dit wil zeggen het invoegen van instructies in de bestaande bytecode. Hierdoor kan men de bytecode zelf events laten uitsturen waardoor er geen contextwissel moet gebeuren naar de agent. Voor systemen met zeer regelmatige eventpatronen zoals profilers en omniscient debuggers zorgt dit voor een grote tijdswinst.
2.3.2
Java Debug Wire Protocol
JPDA voorziet zelf een JVM-TI agent die met elke JVM meegeleverd wordt. Deze agent kan bestuurd worden met het Java Debug Wire Protocol(JDWP). De agent kan gebruikt worden om normale debugging functionaliteit aan te bieden. JDWP laat de transportlaag ongedefinieerd. De meest gebruikte transportmedia zijn natuurlijk TCP voor remote debugging en shared memory voor lokale debugging. JDWP is een binair protocol en dus niet leesbaar zonder cli¨ent die instaat voor de marshaling en unmarshaling van de gegevens. Er wordt gewerkt met twee soorten pakketten: command en response. Zowel de agent als de debugger kunnen beide soorten paketten uitsturen en de volgorde is niet vastgelegd. Elke response wordt gelinkt aan zijn command door een uniek volgnummer. Events kunnen aangevraagd worden door de debugger aan de agent, deze antwoordt dan met een uniek volgnummer voor het request. Elk event dat uitgestuurd wordt, is een commando van de agent naar de debugger waarop geen antwoord komt. Elk event zal het volgnummer van zijn request dragen. Events worden altijd in sets uitgestuurd, zodat gelijktijdige events ook als dusdanig behandeld worden. Elke referentie naar een object of een type wordt door JDWP behandeld als een uniek nummer. Om meer informatie te bekomen moeten er meerdere berichten uitgewisseld worden.
HOOFDSTUK 2. BESTAANDE TECHNOLOGIE
2.3.3
20
Java Debug Interface
JDI is de standaard spiegel-API voor Java. De standaardimplementatie is een JDWP cli¨ent. De implementatie staat in voor het correct gebruikt van JDWP, caching en het aanbieden van een bruikbaarder eventsysteem. JDI bestaat uit meer dan honderd interfaces die de structuur van Java modeleren. Het centrale spiegelobject is de afspiegeling van de JVM: VirtualMachine. Dit spiegelobject kan men bekomen door met de VirtualMachineManager een connectie op te zetten met een JDWP-agent. In figuur 2.3 kan men de belangrijkste delen van JDI terugvinden. De structuur van Java is er makkelijk in terug te vinden. Alle verbanden tussen afspiegelingen van types zijn navigeerbaar langs beide zijden. Het verband tussen een afspiegeling van een waarde en die van haar type is slechts navigeerbaar van waarde naar type. De afspiegeling gaat niet tot op instructieniveau. De link tussen entiteiten en broncodelocaties bestaat enkel voor instructiegerelateerde entiteiten: StackFrames, Events en Methods. StackFrame is een belangrijke entiteit die niet in de taal Java voorkomt. Elke afspiegeling van een thread heeft een lijst met StackFrames die de huidige toestand van de stack voorstellen en inspectie van de uitvoering toelaten. Buiten de afspiegeling van het programma is er ook nog het eventsysteem waarmee een debugger zich kan inschrijven voor bepaalde events. De meest belangrijke zijn class prepare, method entry, location breakpoint en step.
2.4
Alternatieve debugging strategie¨ en
Volgende paragrafen behandelen twee alternatieve debugging strategi¨een: source weaving en omniscient debugging.
2.4.1
Source Weaving
Source weaving is een techniek waarbij de broncode wordt geweven, in plaats van de bytecode. Hierdoor bewaart de programmeur makelijk het overzicht. De geweven broncode is ook gelinkt aan de originele broncode. Het grote nadeel is dat source weaving niet altijd bruikbaar is, omdat de meeste systemen inherent bytecode weavers zijn. Een voorbeeld van een krachtige source weaver is Wicca [5]. Helaas ondersteunt deze debugger enkel zijn eigen specifieke taal.
2.4.2
Omniscient debugging
Omniscient debugging, of alwetende debugging, is een debugging strategie waarbij men de volledige toestand van een programma opslaat tijdens de gehele uitvoering. Hierdoor zijn alle gegevens altijd beschikbaar en kan men vrij door de executie bewegen, voorwaarts en achterwaarts in de tijd. Het is dus zeer simpel om oorzaken van fouten op te sporen, zelfs als deze in het verleden liggen.
HOOFDSTUK 2. BESTAANDE TECHNOLOGIE
Figuur 2.3: Overzicht van de belangrijkste JDI interfaces
21
HOOFDSTUK 2. BESTAANDE TECHNOLOGIE
22
Deze techniek is extreem krachtig voor elke soort van debugging en dus ook voor AOdebugging[14]. Het grote nadeel is dat het opnemen van de volledige toestand van een programma extreem veel gegevens voortbrengt. Deze gegevens moeten zo opgeslagen worden dat ze nog makkelijk doorzocht kunnen worden, maar het wegschrijven moet toch voldoende snel gebeuren. Dit wil zeggen dat omiscient debugging over langere tijd of van grotere systemen dedicated hardware vereist, wat niet altijd haalbaar is. Dit is een zeer krachtige en geavanceerde techniek, toch kan deze thesis een contributie zijn voor omniscient debugging, meer bepaald op het gebied van aggregatie van aspectgeori¨enteerde gegevens. Een omniscient debugger mag dan alles weten, hij is niet noodzakelijk in staat om de juiste informatie uit de gegevens te halen.
Hoofdstuk 3
Architectuur De architectuur(AODA) die ik voorstel bied een spiegel-API(AJDI) aan die steunt op JDI. De architectuur heeft 4 belangrijke componenten: de Java debugging interface(JDI), de aspect Java debugging interface (AJDI), de aspectdebugger(ADB) en de ADB-backend. De grijze onderdelen maken deel uit van JPDA, de Java platform debugging architecture en zijn dus niet door mij ontworpen.
Figuur 3.1: Algemeen architectuur overzicht JDI (3) is de interface waarmee de standaard debugging mogelijkheden van de JVM aangesproken kunnen worden. Dit werd al besproken in paragraaf 2.3.3. AJDI (1) is de spiegel-API die aan de gebruiker zal blootgesteld worden. Het is een op JDI gebaseerde set van interfaces, die meer in detail besproken zal worden in paragraaf 3.4.1. ADB (2) is de implementatie van AJDI die instaat voor het aggregeren van alle debuginformatie, het bewaren van de consistentie en caching van gegevens. Dit wordt 23
HOOFDSTUK 3. ARCHITECTUUR
24
mee in detail besproken in paragraaf 3.4.3. ADB-back-end interface (4) is een set van interfaces waarmee de aspectdebugger (ADB) debuginformatie kan opvragen op een doelplatform onafhankelijk manier. Dit wordt besproken in paragrafen 3.4.2 en 3.4.4. Agent (7) is de component die instaat voor het verzamelen van debuginformatie. Elke agent heeft zijn eigen agent protocol(6) om met de ADB-back-end(5) te communiceren. De ADB-back-end zet deze agent informatie om naar de neutrale ADB-back-end interface. Dit wordt meer in detail besproken in paragraaf 3.4.5 De rest van dit hoofdstuk licht de architectuur verder toe. Eerst wordt de rationale toegelicht. Daarna worden alle ondersteunbare functionaliteiten overlopen en hieruit wordt een subset gekozen die ik zal proberen te implementeren. Dan worden de beperkingen overlopen die aan de architectuur opgelegd worden om de complexiteit te beheersen. Daarna wordt elke onderdeel in detail uitgewerkt. Vervolgens worden de zwakheden van de architectuur, die door de validatie aan het licht zijn gekomen, overlopen. Tot slot volgt een conclusie.
3.1
Rationale
De architectuur steunt op twee fundamentele beslissingen: het gebruik van JDI en de verdeling in een back-end en een front-end. In de volgende paragrafen zal ik deze beslissingen toelichten en uitdiepen.
3.1.1
Het gebruik van JDI
De Java Platform Debugging Architecture geeft ons de mogelijkheid om op drie plaatsen informatie te verzamelen: JVM-TI, JDWP en JDI. Op elk van die punten is alle benodigde informatie verkrijgbaar. We moeten er dus de beste uitkiezen. Het eerste interceptiepunt is de JVM Tooling Interface die ons toelaat een agent in de JVM te laden die daar alle bewerkingen kan volgen. De agent moeten in C geschreven worden en bij het opstarten van de JVM ingeladen worden. JVM-TI heeft als voordeel dat er zeer snel gereageerd kan worden met een lage overhead. Er is ook een ernstig nadeel: de agent moet in C geschreven worden, wat foutgevoeliger en platformafhankelijk is. Het tweede punt is het Java Debug Wire Protocol. Dit is een communicatieprotocol dat ons toelaat om met de standaard debuggingagent, die deel uit maakt van de meeste virtuele machines, te praten over verschillende transportprotocols. Dit interceptiepunt heeft als voordeel dat er op een laag niveau gewerkt kan worden, waardoor er veel controle en plaats voor optimalisatie is. Een ander voordeel is dat men zo de performante en betrouwbare standaard debuggingagent kan gebruiken. Het nadeel is dat de implementatie van de protocolcli¨ent van Sun Microsystems niet open source is, dus dat het protocol zelf volledig ge¨ımplementeerd moet worden, wat tijdrovend en foutgevoelig is.
HOOFDSTUK 3. ARCHITECTUUR
25
Het derde punt is JDI, de Java debugging interface. Dit is een spiegel-API die steunt op een JDWP-cli¨ent. Het grote voordeel is dat JDI ons een betrouwbare spiegel-API geeft om mee van start te gaan. Het nadeel is dat die gaat over een honderdtwingtigtal interfaces die allemaal gewrapped moeten worden, wat veel werk is dat eigenlijk geen functioneel nut heeft. Het tweede probleem is dat JDI aan caching moet doen om zijn gegevens consistent te houden en dat ADB al dat werk moet overdoen. De keuze is op JDI gevallen omdat dit een stabiele en platform-onafhankelijke ondersteuning biedt. Het overdoen van caching en het maken van de wrappercode is misschien een spijtig tijdverlies en een zeer vervelend werkje, maar het is ook een veilige keuze die niet veel kansen heeft op fouten.
3.1.2
De splitsing front-end back-end
Het is uiteindelijk de bedoeling verschillende Java-gebaseerde aspectsystemen te ondersteunen. Daarom is het interessant om alle doelplatform afhankelijke bewerkingen apart onder te brengen en de complexiteit zo goed mogelijk te verdelen. De front-end, ADB, wordt verantwoordelijk voor drie belangrijk taken: consistentie, caching en aggregatie. De back-end is verantwoordelijk voor het aanleveren van de juiste debuginformatie. De eerste taak van de front-end, het bewaren van de consistentie, is op zich een complexe taak met vele verschillende facetten. Het omvat hoofdzakelijk het aanmaken en relateren van spiegelobjecten. Omdat het niet de bedoeling is om de volledige toestand te weerspiegelen, moeten spiegelobjecten enkel aangemaakt worden indien nodig. De meeste relaties in een spiegel-API zijn langs beide zijden toegankelijk en er moeten dus veel dubbele bindingen in stand gehouden worden. Soms is de bindingspartner niet beschikbaar, omdat zijn klasse nog niet geladen is en zijn spiegelobject dus niet aangemaakt kan worden. Als dit gebeurt en de gebruiker vraagt het spiegelobject op, dan moeten de juiste excepties doorgegeven worden. Eventueel moet de gebruiker in staat zijn toch informatie in te winnen over de bindingspartner. Door de vele dubbele bindingen is het ook belangrijk dat elk spiegelobject uniek is en niet per ongeluk twee keer wordt aangemaakt. Anders kan er een ketting van conceptueel identieke objecten ontstaan, wat de performantie niet ten goede komt. Om uniciteit te verzekeren moet alles gecached worden zolang er minstens een referentie naar bestaat. Deze taak is dus voorbehouden voor de front-end. Om de splitsing goed te realiseren, is het belangrijk dat er zo weinig mogelijk AJDI objecten in de back-end terechtkomen. Als de back-end AJDI-objecten zou opvragen, ontstaat er een risico op oneindige lussen, omdat de informatie die nodig is om AJDI-objecten aan te maken uit de back-end moet komen. Om oneindige lussen te voorkomen zou de back-end moeten weten op welke volgorde objecten opgevraagd mogen worden zonder risico op lussen of schending van de caching consistentie. Dit zou de back-end heel complex, foutgevoelig en verweven met de front-end maken. De back-end interface moet zich dus zoveel mogelijk beperken tot primitieve data en specifieke, AJDI ongerelateerde structuren. De tweede taak van de front-end is caching van spiegelobjecten. Dit is niet enkel belangrijk voor de performantie, maar ook voor de consistentie. Zoals al eerder gesteld
HOOFDSTUK 3. ARCHITECTUUR
26
moet alles gecached worden zolang er minstens `e`en andere referentie naar bestaat. Java biedt hier ondersteuning voor, via Java.lang.ref, maar het blijft een ongewone techniek. De derde en meest belangrijke taak voor de front-end is aggregatie van gegevens. Dit is het samenvoegen van de JDI spiegelobjecten met de back-endinformatie. Doordat een spiegel-API een gesloten geheel moet vormen en Java niet in staat is alle returntypes van JDI op een juiste manier te overriden moet JDI volledig gewrapped worden. (Zie paragraaf 3.5.2) De wrappercode vormt is een belangrijk onderdeel van de samenvoeging. Door de splitsing van front-end en back-end kan de wrappercode makkelijk herbruikt worden. Doordat de aggregatielogica in de front-end zit, kan de back-end interface heel concreet en duidelijk gemaakt worden, met korte, duidelijk queries. Dit schermt de backendprogrammeur goed af, zodat hij zich enkel moet bezighouden met het onttrekken en formateren van de gegevens. Dit verkleint de kans op onduidelijk en verweven backendcode. Hieruit volgt dat de back-end enkel instaat voor het ’echte’ werk: het verzamelen van aspectgerelateerde debuginformatie. In de back-end komen dus geen AJDI objecten voor, behalve in zeer specifieke, goed ge¨ısoleerde, gevallen. De meeste back-ends hebben zelfs geen JDI objecten nodig om hun werk te doen. Deze splitsing stelt ons dus in staat om de complexiteit mooi te verdelen en code te herbruiken.
3.2
Ondersteunbare functionaliteiten
De debugger kan een aantal verschillende functionaliteiten ondersteunen. Deze functionaliteiten zijn onderling gerelateerd. Ze kunnen afhankelijkheidsrelaties hebben met andere functionaliteiten als ze niet gerealiseerd kunnen worden zonder dat de andere al aanwezig zijn. Er kunnen ook consistentierelaties bestaan: als slechts een van de functionaliteiten beschikbaar is, zal er een inconsistentie ontstaan in de debuginformatie. Sommige functionaliteiten zijn ook onverenigbaar: als ze allebei voorkomen, zullen ze inconsistentie veroorzaken. In figuur 3.2 zijn de verbanden tussen de meest relevante functionaliteiten aangegeven. De vakjes binnen de zwarte kader bevinden zich op het niveau van AJDI, degene die er buiten staan op het niveau van JDI. De rode vakjes zijn functionaliteiten die niet aangeboden worden, de groene zijn wel ge¨ımplementeerd. De zwarte pijlen geven functionele afhankelijkheid aan, de blauwe een consistentierelatie en de rode een consistentieconflict. (De functionaliteiten zijn zeker niet evenwaardig in belangrijkheid of in benodigde inspanning om ze te realiseren.) In de volgende paragrafen zal ik alle mogelijkheden uit het diagram bespreken en uitleggen waarom ik deze al dan niet ge¨ımplementeerde heb. Type- en instantieafspiegeling zijn de meest fundamentele onderdelen. Ze voorzien de spiegelobjecten voor types en instanties. Deze functionaliteit wordt aangeleverd door JDI. Wrapping Om bruikbaar te zijn in AJDI moeten de JDI-spiegelobjecten ’gewrapped’ worden. Dit wil zeggen: herverpakt zodat ze open staan voor aanpassing en con-
HOOFDSTUK 3. ARCHITECTUUR
27
Figuur 3.2: Mogelijke functionaliteiten en hun onderlinge verbanden; zwarte pijlen: functionele afhankelijkheid; blauwe pijlen: consistentie afhankelijkheid; rode pijlen: consistentie conflict
HOOFDSTUK 3. ARCHITECTUUR
28
form AJDI zijn. Dit heb ik dan ook gedaan, omdat het de basis is voor de meeste andere mogelijkheden. Hierover is meer terug te vinden in paragraaf 3.4.1. Aspect compositie informatie (ACI) is een grote uitbreiding van de afspiegeling van types die alle aspectgerelateerde informatie in het model inbrengt. Dit gaat over de typeinformatie van aspecten, het inwerken van adviezen, een basis voor pointcutafspiegeling en een basis voor residuafspiegeling. Dit is zonder twijfel het meest interessante onderdeel omdat het veel impact heeft op de de architectuur in zijn geheel: het vormt het grootste deel van de API en dus ook van de implementatie ervan. Alle voorzien datapaden worden gebruikt en het geheel wordt dus op de proef gesteld. Het is ook het voetstuk waarop de meer geavanceerde functionaliteiten steunen. Ook is het op zichzelf een heel bruikbaar onderdeel: het laat de gebruiker toe om de inwerking van aspecten te onderzoeken. Dit komt goed tegemoet aan de vraag naar structurele reflectie, wat toch een belangrijke vereiste was. Dit onderdeel vormt een groot deel van het werk. ITD ondersteuning lijkt op de ondersteuning van crosscutting informatie: het is ook een aanpassing aan de typeafspiegeling, maar een veel kleinere. In plaats van de inwerking van aspecten en aspect types te introduceren in het gehele model, voert het een enkel nieuwe notie van ouderschap in. Ook de relevantie voor de gebruiker is iets kleiner. Omdat het technisch gelijkend is aan ACI heb ik besloten hier geen tijd in te inversteren. Typefiltering bestaat uit het verwijderen van type-entiteiten die de compiler heeft ingevoegd om de AOP-functionaliteit te realiseren. Dit verbergt veel rommel voor de gebruiker. Omdat deze typefiltering zeer belangrijk is voor de inkapseling, maar niet echt een uitdaging voor de architectuur, heb ik ze gedeeltelijk aangebracht: de methodes en velden worden gefilterd in hun JDI-vorm en interfaces blijven ongefilterd. Multi-source lijnummering is een techniek die beschreven is in JSR45[6]. Het laat een standaard Java-debugger toe om lijnnummers correct weer te geven voor talen anders dan Java die naar Java-bytecode gecompileerd worden. Oorspronggebaseerde lijnnummering is het nummeren van de instructies zodat elke instructie het lijnnummer draagt van de lijn in de broncode die deze instructie veroorzaakt heeft. Initieel leek mij dit een nuttig onderdeel van de debugger en een goede manier om de compiler te leren kennen. Omdat het steunt op multisource debugging moet JDI niet aangepast of gewrapped worden. Later heb ik ondervonden dat dit het aanmaken van een duidelijke stacktrace veel moeilijker maakt, omdat de code die een advies inweeft door dit advies veroorzaakt wordt, maar in de stacktrace vermeld moet worden als onderdeel van de methode die het advies aanroept. Daarom heb ik deze functionaliteit wel gemaakt, maar gebruik ik ze niet.
HOOFDSTUK 3. ARCHITECTUUR
29
Stack-afspiegeling is het weergeven van de toestand van de uitvoeringsstack. Dit is ook voorzien door JDI en moet dus ook herverpakt worden om bruikbaar te zijn. Stack-afspiegeling kan uitgebreid worden met duidelijk indicaties over het inwerken van adviezen en filtering. Omdat de stacktraces van JBoss AOP uitermate complex zijn en het wrappen van de stack veel informatie kan herbruiken uit CCI, heb ik deze filtering ge¨ımplementeerd. Eventsysteem is het onderdeel dat instaat voor breakpoints en andere waarschuwingen die de debugger kan voortbrengen. Om bruikbaar te zijn dient het echter ook gewrapped te worden. Dit is niet zo simpel omdat dit een van de minst duidelijk gespecifieerde delen van JDI is. De correcte afhandeling van het stilzetten en weer vrijlaten van de virtuele machine is erg belangrijk, maar ook heel foutgevoelig. De bestaande implementatie reageert niet altijd geheel zoals verwacht. Dit zou zeker een interessante uitdaging zijn, maar de tijd ontbrak. De implementatie van de wrapper is wel grotendeels af, maar nooit getest. Aspect events zijn berichten van de debugger als een aspect wordt ingeladen of aangeroepen of als een advies wordt opgeroepen. Dit soort berichten kan de programmeur gebruiken om de uitvoering beter te volgen. De grootste verandering aan de architectuur is het proactief worden van de agent. Deze toevoeging is ook zeer interessant, maar afhankelijk van een gewrapped eventsysteem. Dit was echter niet beschikbaar, dus deze functionaliteit heb ik niet voorzien. Dynamic ACI is aspectcompositie-informatie die zich live aanpast aan de toestand van het gespiegelde systeem. Dit kan bekomen worden door helemaal niets te cachen of door de nodige klassen naar de aspectevents te laten luisteren. Ik heb dit echter niet ingebouwd omdat de ondersteunende systemen nog niet klaar waren. CFLOW informatie kan ook aan de gebruiker verstrekt worden. Dit geeft twee grote punten van complexiteit: modellering en data-acquisitie. Dit laatste is niet uitzonderlijk moeilijk, maar het modelleren van CFLOW informatie is wel zeer moeilijk, zeker omdat dit concept niet op dezelfde manier voorkomt in de verschillende AOsystemen. Ik heb dus gekozen om deze uitdaging niet aan te gaan, omdat de andere functionaliteiten volgens mij meer praktisch nut hebben en omdat het wel eens zeer moeilijk zou kunnen zijn om CFLOW te modelleren. Instructie-afspiegeling stelt de gebruiker in staat de instructies te onderzoeken via spiegeling en ze uit te voeren en aan te passen via de debugger. JPDA ondersteunt dit niet omdat het veel beperkingen zou opleggen aan de uitvoerings- en compilatiemechanismen. Residu’s zijn de stukken van een pointcut die niet statisch ge¨evalueerd kunnen worden en dus tijdens de uitvoering ge¨evalueerd moeten worden. Aangezien dit instructie sequenties zijn, is instructie-afspiegeling vereist voor geavanceerde residuafspiegeling.
HOOFDSTUK 3. ARCHITECTUUR
30
Pointcutafspiegeling vereist een pointcutmodel. Dit is echter een onderzoek op zich, zeker als het model intu¨ıtief bruikbaar moet zijn en geschikt moet zijn voor meerdere talen. Daarom heb ik voor een tekstuele voorstelling van pointcuts gekozen in plaats van spiegelobjecten.
3.3
Beperkingen
Om de architectuur te concretiseren moeten we niet alleen bepalen wat er wel moet kunnen, maar ook een aantal beperkingen opleggen. In deze paragraaf worden de belangrijkste beperkingen van AODA overlopen. Deze beperkingen zijn niet noodzakelijk voor een goede werking, maar een poging om het systeem niet al te complex te laten worden. Voor elke beperking wordt ook ingeschat wat de impact van de beperking is en hoeveel moeite het zou kosten om achteraf de beperking te doorbreken. De belangrijkste beperking is dat Java-types ´e´en-op-´e´en moeten overeenkomen met aspecttypes. Het is dus niet toegestaan om een klasse als twee verschillende aspecten te gebruiken of als klasse en aspect tegelijkertijd. Deze beperking is ingevoerd omdat het anders niet mogelijk is om AJDI-types af te leiden van JDI-types. Het zou noodzakelijk worden om voor elke instantie aan de back-end te vragen wat het type ervan is. Hiervoor zou de back-end een notie van instantie-identiteit nodig hebben. In een spiegel-API is er hiervoor maar ´e´en kandidaat: het spiegelobject dat de instantie voorstelt. De backend zou dus erg verweven worden met JDI en ADB. Het zou zelfs noodzakelijk kunnen worden voor het back-end protocol om instantie identiteiten door te sturen. Dit zou een JDI-agent noodzakelijk maken, wat ook voor problemen kan zorgen. Het verwijderen van deze beperking zou in de front-end aanpassingen betekenen in slechts enkele klassen, die wel een stuk complexer zouden worden. De back-end-interfaces zouden volledig hervormd moeten worden. Back-ends voor systemen die niet aan het eenop-een-principe voldoen hebben een hoge inherente complexiteit en zullen waarschijnlijk voor problemen zorgen. Een tweede beperking is dat een aspect ook een (subklasse van) klasse is in het model (AJDI) en in de implementatie (ADB). In de ADB wil dit zeggen dat een aspect gedeclareerd kan zijn, maar toch niet beschikbaar als spiegelobject, omdat de klasse nog niet geladen is. Een andere beperking die hieruit voortvloeit, is dat de methoden die de adviezen vormen ook in die klasse moeten zitten. In het model wil dit zeggen dat aspect een subklasse is van klasse, maar dat sommige operaties (zoals instantiatie) een exceptie gooien. Het weglaten van deze beperking zou de notie van aspect veel vager maken. Het modelleren zal dus meer werk vergen en meer onzekerheid opleveren. De front- en backend zouden dus een stuk complexer worden en het is niet duidelijk of er systemen bestaan die niet aan deze beperking voldoen.
HOOFDSTUK 3. ARCHITECTUUR
3.4
31
Onderdelen
De volgende paragrafen omschrijven de onderdelen van de architectuur. De onderdelen van de front-end, die platformonafhankelijk zijn, worden in detail besproken. Voor onderdelen van de back-end, die platformafhankelijk zijn, wordt een mogelijke sub-architectuur aangeboden.
3.4.1
AJDI
De aspect Java debugging interface(AJDI) is de spiegel-API die aan de gebruiker wordt aangeboden. AJDI is sterk ge¨ınspireerd door JDI, omdat JDI een goede spiegel-API is voor Java. AJDI biedt niet alle mogelijkheden van JDI 1.6 aan, maar richt zich op versie 1.4.0. In figuur 3.3 zijn de belangrijkste onderdelen van AJDI afgebeeld. De
Figuur 3.3: Overzicht van de belangrijkste onderdelen van AJDI
HOOFDSTUK 3. ARCHITECTUUR
32
witte klassen zijn gebaseerd op JDI, de grijze zijn nieuw. De meest vanzelfsprekende toevoegingen zijn Aspect, Aspect Instance en Advice. Zoals al vermeldt in paragraaf 3.3 erven aspecten over van klassen. Adviezen erven over van methoden, omdat ze veel overeenkomsten vertonen. Methoden en adviezen zijn de enige entiteiten die uitvoerbare instructies bevatten. Een advies kan een aantal bindingen hebben, die elk ´e´en pointcut hebben. Een gebonden advies kan meerdere toepassingen hebben, die elk door ´e´en van de bindingen veroorzaakt zijn. Elke toepassing heeft een residu en een joinpoint. De joinpoints vallen uiteen in twee grote klassen, namelijk de statement joinpoints en de method execution joinpoints. Statement joinpoints grijpen aan op een specifieke bytecode-instructie. Voorbeelden hiervan zijn de call en field joinpoints van AspectJ. De method execution joinpoint omvatten de uitvoering van methodes zoals de execution en init joinpoints. Een methode kan dus een willekeurig aantal statement joinpoints hebben en een method execution joinpoint. Constructoren kunnen nog twee extra execution joinpoints hebben, namelijk PreInit en Init. Een klasse kan juist een statische initialisatie joinpoint hebben, die ook de method execution joinpoint is van de
() methode. Adviezen hebben natuurlijk geen gewone method execution joinpoint, maar een advice execution joinpoint. De statement joinpoints zijn niet verder uitgewerkt, om tijd te besparen. Het is natuurlijk perfect mogelijk om deze hi¨erarchie verder uit te splitsen. Alle verbindingen tussen entiteiten zijn navigeerbaar langs bijde zijden, behalve als het anders is aangegeven. Er zijn ook veel methodes om specifieke subsets op te vragen, zoals bijvoorbeeld alle methoden met een bepaalde naam. Een belangrijk aspect dat niet is afgebeeld is de locatiehi¨erarchie. Dit is de weergave van broncode- en bytecodelocaties. In JDI is er enkel het Location type dat een broncode- en een bytecodelocatie te gelijk aanbiedt. In AJDI is er een iets uitgebreidere hierarchie, zoals te zien in figuur 3.4. De locaties zijn opgesplitst in enerzijds broncodelocaties en locaties die zowel in broncode als bytecode voorkomen. Anderzijds is er een onderscheid tussen punten en gebieden. In de meeste gevallen zijn de punten in de bytecode die JDI aanwijst eigenlijk gebieden. Het gegeven punt is dan het begin van het gebied. Om niet te veel extra complexiteit te veroorzaken worden er punten gebruikt in klassen die van JDI afgeleid zijn en gebieden in nieuwe AJDI-klassen. Het broncodegebied van een joinpoint omvat de lijnen die het joinpoint vormen. Het bytecodegebied van een joinpoint is het gebied tussen de eerste en de laatste ingeweven instructie. Het is dus mogelijk, bij before en after advies dat de instructie die het joinpoint vormt, niet in het bytecodegebied ligt, maar er net voor of achter. Voor elk praktisch doel zou deze nummering voldoende moeten zijn: na het einde van het joinpointgebied zullen er geen adviezen meer uitgevoerd worden. Aangezien er geen instructie-afspiegeling mogelijk is, is het ook niet zo belangrijk om de juist instructie te kennen. Een tweede aspect dat niet is afgebeeld, is de aangepaste stackafspiegeling. Om aan te geven of de uitvoering normaal verloopt of geadviseerd wordt zijn er HookFrames voorzien. Als de uitvoering zich in een joinpoint gebied bevindt wordt er een hook frame ingelast. Hiermee kan de gebruiker het joinpoint opvragen. Het hook frame verbergt
HOOFDSTUK 3. ARCHITECTUUR
33
Figuur 3.4: Locatie hierarchie ook eventuele synthetische methodes.
3.4.2
ADB-Back-end Interface
Het is uiteindelijk de bedoeling verschillende Java-gebaseerde aspectsystemen te ondersteunen. Daarom is het interesant om alle doelplatformafhankelijke bewerkingen apart onder te brengen. Alle informatie die niet op een onafhankelijk manier door JDI kan aangeleverd worden, komt via de ADB-Back-end Interface binnen. De ADB-Backend Interface bestaat uit meedere Java-interfaces. Er zijn drie belangrijke interfacetypes: ruwe informatieleveranciers, filters en factories. De ruwe leveranciers brengen de debuginformatie aan als primitieve data en specifieke data structuren. Ze gebruiken geen AJDI objecten en moeten zich dus niets aantrekken van caching en oneindige lussen. Ze zijn in principe bruikbaar zonder AJDI of ADB. Filters verbergen onnodige informatie door JDI of AJDI objecten uit te filteren. Ze kunnen gebruikmaken van de ruwe leveranciers. Ook hier gebeuren geen oproepen naar AJDI of ADB, dus er is geen speciaal risico. Deze onderdelen zijn wel zo verweven met AJDI en JDI dat ze er niet los van kunnen bestaan. De factories maken objecten aan die ADB niet zelf kan aanmaken. ADB zorgt er voor dat de objecten geen twee keer aangemaakt worden. Omdat factories AJDI objecten maken, kunnen factories niet zonder ADB en AJDI. Ruwe interfaces Er zijn drie leveranciers van ruwe gegevens: FieldLocationProvider, AspectInfoProvider en CrossCuttingInfoProvider. De FieldLocationProvider is de simpelste van de drie: het enige wat hij moet doen is voor elke veld het juiste lijnnummer en het juiste bronbestand aanleveren of een AbsentInformationException gooien. AspectInfoProvider is verantwoordelijk voor alle informatie over aspecten en adviezen. Dit omvat het omzetten van een aspectnaam naar een klassenaam en vice versa
HOOFDSTUK 3. ARCHITECTUUR
34
en het aanbrengen van de attributen van een aspect: scope, broncodelocatie en adviezen. Voor elk advies wordt doorgegeven in welke methode de code van het advies zich bevindt, waar de broncode te vinden is, welke bindingen er zijn en wat het type is. CrossCuttingInfoProvider brengt de joinpoints per methode en de toepassingen per advies aan. Factories Er is maar ´e´en pure factory: de AspectInstanceProvider. Het is een factory die instaat voor het aanmaken van spiegelobjecten van aspectinstanties. Het kan nodig zijn dat deze provider methode-oproepen doet in de doel VM om de JDI spiegelobjecten van de aspectinstantie terug te vinden. Hiervoor is een thread nodig in de doel VM, die actief is, maar stil staat op een breekpunt. Het is dus noodzakelijk dat de gebruiker een thread mee geeft wanneer hij een instantie opvraagt. Dit is conform de semantiek van JDI voor het oproepen van methoden en instanti¨eeren van klassen. Het opvragen van een instantie kan als neveneffect hebben dat er een nieuwe instantie aangemaakt wordt in de doel VM. In zeker zin doet deze factory het werk van een JDI-agent, maar omdat er een thread beschikbaar gesteld wordt door de gebruiker is dit niet problematisch. Filters Er is ook maar ´e´en pure filter. Deze staat in voor het filteren van methodes en velden op het niveau van JDI. Omdat deze filter volstaat is er nog geen AJDI-filter, omdat dit weer extra overhead is. Hybriden Tot slot is er nog een factory-filterhybride voor de uitvoeringsstapel. Omdat de uitvoeringsstapel niet enkel uitgefilterd moet worden, maar ook voorzien van nieuwe frames om aan te geven waar adviezen inwerken is het te complex voor de front-end om hier apparte interfaces voor aan te bieden en wordt alles gedaan in de back-end. Omdat stackframes een zeer korte levensduur hebben is caching niet nodig. De wrapping gebeurt ook in deze factory en dus is er toegang tot ADB-functionaliteit vereist. Er is geen risico op oneindige lussen omdat stacktraces nergens intern gebruikt worden.
3.4.3
ADB
ADB is de implementatie van AJDI. Het is een vrij complexe structuur, dus ik zal enkel de belangrijkste ontwerpprincipes overlopen. JDI is gestructureerd om een centrale component, VirtualMachine, die een afspiegeling vormt voor een virtuele machine. Elke JDI-interface erft over van com.sun.jdi.Mirror. Deze interface bied een virtualMachine() methode aan. Hiermee kan voor elk object het VirtualMachine spiegelobject opgevraagd worden dat verantwoordelijk is voor het object.
HOOFDSTUK 3. ARCHITECTUUR
35
ADB is op een gelijkaardige manier gestructureerd. Elke AJDI-interface erft over van ajdi.Shadow. Deze interface biedt een methode shadowMaster() aan. Hiermee kan een ShadowMaster opgevraagd worden. Deze centrale component wrapt een VirtualMachine instantie. Om de caching optimaal te doen werken wordt er zo dicht mogelijk bij de consument gecached en zijn er twee soorten caching: harde caches en zachte caches. De hard caches houden hun gegevens vast tot het omvattende object opgeruimd wordt door de garbage collector. Zachte caches werken samen met de garbage collector en kunnen een object laten vallen als er geen enkele andere referentie meer naar bestaat. Het is belangrijk dat er geen ketens van harde referentie onstaan, waardoor de zachte caches niet meer leeggemaakt kunnen worden.
Figuur 3.5: caching tussen de ADB enteiten
Zoals in figuur 3.5 te zien is slaan alle klassen en aspecten hun methodes, velden en adviezen op in harde caches, nadat ze de eerste keer zijn opgevraagd. Methoden slaan hun joinpoints op in zachte caches nadat ze de eerste keer zijn opgevraagd. Adviezen zouden hun adviestoepassing zacht kunnen cachen, maar doen dit niet omdat er nog geen event systeem beschikbaar is om de cache te invalideren, wat in dit geval zeker nodig zou zijn. De shadow master heeft de belangrijkste caches en gebruikt alleen zachte caches: alle type-afspiegelingen en instantie-afspiegelingen worden gecached in de master om consistentie te verzekeren. Joinpoint en adviestoepassingen worden gecached in de joinpointmanager, die enkel aanspreekbaar is langs de shadow master. Als er JDI-objecten ingepakt moeten worden tot AJDI-objecten of omgekeerd, dan is dit de verantwoordelijkheid van de shadow master. Er wordt dan gecontroleerd of de instanties die geconverteerd worden wel onder de verantwoordelijkheid van deze shadow master en zijn VM vallen. Om de conversie te doen worden eerst de relevante caches gecontroleerd en indien er geen bestaand object gevonden kan worden, wordt de nodig informatie opgezocht en het nieuwe object wordt aangemaakt en in cache opgeslagen.
3.4.4
ADB-Back-end
De ADB-Back-end is onafhankelijk van de kern architectuur en mag dus vrij ingevuld worden. Een praktische invulling die de verantwoordelijkheden goed afschermt is te zien
HOOFDSTUK 3. ARCHITECTUUR
36
in figuur 3.6.
Figuur 3.6: Referentiearchitectuur ADB-Backend De Agent Protocol Controller staat in voor het opzetten, onderhouden en encapsuleren van de connectie met de agent. Deze component biedt een centrale interne interface aan waarlangs de andere componenten informatie kunnen opvragen. De andere componenten staan in voor de aggregatie van een bepaald type informatie en bieden elk een ADB-Back-end Interface aan. Dit model kan ook nog uitgebreid worden met een al of niet aspectgebaseerde caching service op de verschillende interfaces.
3.4.5
Agent
Net zoals de ADB-Back-end is de agent onafhankelijk van de architectuur. We kunnen vijf soorten agenten onderscheiden: compiler agent, VM agent, statische JDI-agent, dynamische JDI-agent en half JDI-agent. De compiler agent zit ingebouwd in de compiler. Dit soort agent kan alleen gebruikt worden als we met voorgecompileerde klassen werken. Mogelijke communicatiekanalen zijn bestanden met debuginformatie, databases en attributen. Attributes[12] zijn gebieden in een classfile die de compiler zelf kan invullen, maar die de uitvoering niet be¨ınvloeden. De VM-agent is een server process dat mee opgestart wordt in de doel VM. De backend kan hiermee communiceren via een socketconnectie. Dit soort agent is vrij makelijk
HOOFDSTUK 3. ARCHITECTUUR
37
te maken, maar heeft als nadeel dat hij in de doel VM aanwezig is en invloed heeft op de toestand van de doel VM. Door zorgvuldig ontwerp kan deze invloed beperkt worden. ADB kan in staat gesteld worden om de agent weg te filteren uit de afspiegeling, zodat de gebruiker niet gestoord wordt. De agent moet normaal mee met het doelprogramma opgestart worden. Als dit niet het geval zou zijn, maar de agent bevindt zich wel in het classpath, dan kan JDI gebruikt worden om de agent op te starten. Een nadeel van een VM-agent is class loading: als een thread door de debugger stil gezet is tijdens het laden van een klasse en de VM-agent tracht ook, rechtstreeks of onrechtstreeks, een klasse te laden dan zal de agent blokkeren op de classloaderlock. Hierdoor kan de debugger ook blokkeren en ontstaat er deadlock. Een ander nadeel is dat wanneer de VM volledig stilgezet wordt op een breakpoint, de agent ook stil gezet wordt. Omdat er nog geen wrapping van het eventsysteem is, is dit een re¨eel risico. Een JDI-agent onderzoekt de toestand doorheen de debugging interface. Een statische agent doet dit door zelf het juiste resultaat te gaan zoeken in de datastructuren, zonder code uit te voeren in de doel-VM. Dit is natuurlijk extreem moeilijk en foutgevoelig omdat JDI niet in staat is om instructies te spiegelen en alle gebruikte klassen dus eigenlijk een nieuwe, identieke implementatie moeten krijgen die gebruik maakt van JDI-spiegelobjecten in plaats van echte data. Een dynamische JDI-agent gebruikt functies in de doel-VM om de informatie te vinden. Het grote nadeel hiervan is dat er een thread beschikbaar moet zijn die stilstaat op een breekpunt. Het verkrijgen van zo’n beschikbare thread is soms zeer moeilijk. Een mogelijke oplossing hiervoor is het invoegen van een thread die een oneindige lus uitvoert en die thread steeds stil te zetten op een breakpoint. Een half-JDI-agent is een dynamische JDI-agent die bepaalde niet standaard functies gebruikt, die mee in het classpath van de doel-VM moeten zitten.
3.5
Beperkingen
Na de validatie is gebleken dat de architectuur ook een aantal gebreken vertoont. Die worden in de volgende paragrafen besproken.
3.5.1
Identiteit
In de context van een aspectdebugger kunnen we verschillende soorten eniteiten vaststellen die we moeten identificeren: instantie, klasse, aspect, methode, advies, joinpoint, binding en adviestoepassing. Er zijn zes belangrijk interfaces waardoor deze entiteiten moeten doorgegeven worden: JVM-TI, JDWP, JDI, ADB-Back-end Interfaces, agentprotocol en AJDI. Om de identiteit vast te leggen kunnen we gebruikmaken van betekenis volle en betekenisloze identiteiten. De naam van een type is een betekenisvolle identiteit, een identificatienummer is betekenis loos. Betekenisvolle identiteiten hebben het voordeel dat ze buiten de context van een sessie gebruikt kunnen worden. Betekenisloze identiteiten hebben het voordeel dat ze nergens aan vasthangen en dat verandering van de
HOOFDSTUK 3. ARCHITECTUUR
38
interfaces dus makelijker is. Ze kunnen veranderen zonder dat de gebruiker er last van heeft. In een spiegel-API zijn de referenties naar de spiegelobjecten de identiteiten. Dit zijn betekenis loze identiteiten. Meestal is er voor een spiegelobjecten ook een betekenisvolle identiteit af te leiden zoals een unieke naam. In de context van een VM- en een compileragent zijn betekenisvolle identiteiten zeer belangrijk, omdat er twee sessies zijn. De JDI-sessie en de agent protocolsessie. De gegevens van deze sessies kunnen slechts ge¨ unificeerd worden door de afgeleide identeiten. Ik had verwacht dat dit soort agenten het meest performant zouden zijn. Achteraf gezien zijn half-JDI-agenten met een eigen thread mischien toch beter, omdat ze maar ´e´en datakanaal hebben, ze een notie van instantie-identiteit kunnen ondersteunen en ze best werken als de JVM stil staat, wat de toestand van de JVM is die meest gebruikt wordt bij debugging. Ik heb echter nog geen half-agent gemaakt en ben dus niet zeker. Op dit moment gebruiken de ruwe ADB-Back-end Interfaces dus betekenis volle identiteiten. Daarom is het gebruik van instantie-identiteit uitgesloten. Aspect- en klasseidentiteit vallen samen door de beperking op meervoudige types en dus kan de unieke typenaam gebruikt worden. Methoden kunnen benoemd worden op basis van hun klasse, naam en signatuur. Adviezen kunnen altijd benoemd worden op basis van de methode waarin ze vervat zitten. Dit is soms oneffici¨ent. Joinpoints worden ge¨ıdentificeerd op basis van hun methode, hun type en locatie in de methode. Advies toepassingen worden herkend aan hun joinpoint en advies. Bindingen kunnen een naam hebben of als elke advies juist ´e´en binding heeft geidentificeerd worden op basis van hun advies. Als de back-end interface zou werken met JDI-identiteiten onstaan er andere beperkingen, zoals het onvermogen om puur synthetische aspecten, die geen Java-type hebben, te gebruiken. In de huidige vorm is de aanpassing die nodig is om volledig ingeweven aspecten te ondersteunen niet extreem groot omdat er JDI-onafhankelijk identiteiten gebruikt worden. Ik denk nu dat het doorgeven van identiteiten de grootste zwakte is van de architectuur in het kader van uitbreidbaarheid. Het gebruik van identificerende objecten was waarschijnlijk beter geweest. Dit had weer een indirectie meer betekend, maar ook meer flexibiliteit opgeleverd. Hiermee zou het mogelijk zijn om JDI identiteiten en betekenis volleidentiteiten samen te ondersteunen. Wat veel belangrijker is, het zou encapsulatie van identiteit opleveren, zodat de behandeling ervan aangepast kan worden bij de producenten en consumenten zonder de transport- en opslagmechanismen te wijzigen.
3.5.2
API-overerving
Een van de problemen die veel tijd opslorpen bij het uitbreiden van JDI is het tekortschieten van de compositiemechanismen in Java. Omdat elke spiegel-API een gesloten systeem moet zijn, moeten alle returntypes uit JDI verstrengd worden tot hun overge¨erfd equivalent uit AJDI. Het Lyskof-substitutieprincipe laat dit toe. Met generieke lijsten ontstaat er echter een probleem omdat lijsten invariant zijn. De lijsten die JDI teruggeeft zijn echter onwijzigbaar en dus covariant. In Java kan men dit aangeven door een wildcard (List extends T>). Het probleem is dat elke keer men de lijst wil gebrui-
HOOFDSTUK 3. ARCHITECTUUR
39
ken, hij gecast moet worden om de wildcard weg te werken. Daarom heeft JDI exacte typeparameters en kunnen de types van de lijsten niet gewijzigd worden. Er zijn binnen de context van Java wel oplossingen te bedenken om dit probleem op te lossen, maar die zijn weinig elegant. Een krachtiger typesysteem zou hier een goede oplossing kunnen bieden.
3.5.3
Object Consistentie
Een spiegel-API vormt een zeer dicht netwerk van dubbelgelinkte entiteiten: alle members zijn aan hun ouder gelinkt en vice versa, alle joinpoints aan hun applicaties en hun eigenaar en vice versa,. . . Om aan de invarianten van de dubbele linken te voldoen en om het geheugengebruik te beperken is het noodzakelijk dat een spiegelobject niet twee keer aangemaakt wordt. Dit moet in JDI gebeuren en in AJDI, wat vrij veel inspanning vraagt, zowel op het vlak van ontwerp als op het vlak van uitvoering. Er zijn twee moelijke oplossingen voor dit probleem. Ten eerste kunnen key-value pairs in JDI spiegelobjecten voorzien worden, zodat het AJDI equivalent mee gecached kan worden. Ten tweede kan men een meer aspectgerichte implemenatie voor caching voorzien. Ik moet hier wel aan toevoegen dat ik geen concreete invulling voor de aspectgerichte oplossing kan bieden.
3.5.4
Modulaire compositie
Zoals al eerder aangehaald zijn er vele mogelijk sets van functionaliteiten die we kunnen aanbieden. Het is echter niet mogelijk om die functionaliteiten modulair aan te bieden, enerzijds omdat API-inheritance niet goed genoeg werkt en anderzijds omdat er geen meervoudige overerving is. De modulaire compositie die Scala aanbiedt, zou hiervoor een zeer elegante oplossing kunnen bieden. Ik heb dit niet gebruikt om niet te veel onbekende factoren tegelijk in het project te betrekken.
3.5.5
Ge¨ısoleerde aggregatie
De instrcuties die de back-end gegevens aggregeren is verspreid over de gehele back-end. Dit maakt het moeilijk om ze aan te passen. Het zou ideaal zijn als deze code een afgescheiden geheel zou vormen, zodat ze herbruikt kan worden in andere debuggers.
3.6
Evaluatie
Als we deze architectuur evalueren tegen de vooropgestelde aandachtspunten. Stucturele reflectie, zichtbaarheid, traceerbaarheid en inkapseling. Dan blijkt dat de architectuur hier goed aan voldoet. Structurele reflectie is fundamenteel voor spiegel-reflectie en dus goed ondersteund. Statische en dynamische reflectie zijn volledig ondersteund, aspectcompositie-informatie is ook beschikbaar. ITD’s zijn afwezig maar liggen zeker binnen de mogelijkheden.
HOOFDSTUK 3. ARCHITECTUUR
40
Zichtbaarheid is ook beter ondersteund door de architectuur door een fijnere notie van lijnnummering en extra input langs de back-end. De traceerbaarheid is ook verbeterd door een filtering en transformatie van de stack. Dit ligt echter grotendeels in de handen van de back-end. De kwaliteit kan dus erg verschillen. Inkapseling is het paradepaardje van deze architectuur. Langs de ene kant is er inkaspeling van de basistaal. Hiervoor zijn er filters voorzien. De goede werking hiervan hangt ook weer af van de back-end. Een andere vorm van inkapseling ligt in de bruikbaarheid van de API voor meerdere platformen. Elke taal die het patroon volgt dat de architectuur vooropstelt kan volledig transparant opgenomen worden. Tot slot is er de inkapseling van de back-end. Door de splitsing tussen front-end en back-end kan een back-endprogrammeur een zeer gerichte inspanning leveren zonder echt vertouwd te zijn met ADB. Zoals in de vorige sectie vermeld zijn er nog veel punten van onzekerheid. De juiste groeirichting is nog niet bekend, dus de gemaakte keuzes kunnen nog niet volledig naar waarde geschat worden. De architectuur is ook nog niet volledig ontrold, niet alle mogelijkheden zijn al ingebouwd, veel dingen zijn niet getest tot in de details. Ondanks deze onzekerheden zijn er duidelijk sterke punten aan deze stuctuur.
Hoofdstuk 4
Eerste validatie: een debugger voor AspectJ Zoals al besproken in paragraaf 1.4 is het doel van de AspectJ-implementatie om zoveel mogelijk functionaliteit te ondersteunen en de architectuur te beproeven. In praktijk zal de AspectJ-implementatie dus alle functionaliteit beschreven in paragraaf 3.2 ondersteunen. De meest belangrijke onderdelen zijn aspectcompositie-informatie, verbeterde stack trace, instantie-afspiegeling en oorspronggebaseerde lijnnummering. Er zijn twee belangrijke compilers voor AspectJ: de AJC[7] en de ABC[2][3]. AJC is de referentiecompiler. ABC is een onderzoekscompiler die probeert AJC zo goed mogelijk na te maken. AJC is erg verweven met Eclipse en moeilijk los ervan te gebruiken. ABC is zeer goed gedocumenteerd en gestructureerd. Ik gebruik de 1.2.1 versie van ABC. In de komende paragrafen zal ik eerst de technische details van AspectJ en ABC overlopen om daarna te bespreken hoe de debugger agent en back-end opgebouwd zijn.
4.1
AspectJ in relatie tot AJDI
De relaties tussen AspectJ en AJDI-entiteiten zijn zeer goed. Uit vergelijking tussen figuur 3.3 en figuur 4.1 blijkt dat AJDI zelfs een beetje algemener is, in die zin dat AJDI meerdere bindingen per advies en meerdere adviezen per binding toelaat terwijl dit bij AspectJ altijd exact ´e´en binding voor ´e´en advies is. De mapping van AspectJ-joinpoints naar AJDIjoinpoints is goed. Alles is modeleerbaar, maar er is toch ook een verlies aan informatie. In figuur 4.2 is duidelijk te zien dat er veel verschillende soorten joinpoints afgebeeld worden op statement joinpoints. Hier is zeker nog plaats voor verfijning van het joinpoint model. Method en constructor execution worden ook afgebeeld op ´e´en type, maar hier kan men achteraf het onderscheid nog maken. Er zijn nu nog enkele functionaliteiten van AspectJ die niet voorkomen in AJDI. CFLOW en ITD zijn de belangrijkste, maar zoals al vermeld in 3.2 is dit ook niet de bedoeling. Declare warning en declare error komen niet voor, omdat deze functionaliteiten enkel effect hebben tijdens de compilatie en geen enkel spoor achterlaten in de 41
HOOFDSTUK 4. EERSTE VALIDATIE: EEN DEBUGGER VOOR ASPECTJ
Figuur 4.1: Statisch AspectJ ER model
Figuur 4.2: Joinpoints in AJDI en AspectJ
42
HOOFDSTUK 4. EERSTE VALIDATIE: EEN DEBUGGER VOOR ASPECTJ
43
uitvoering. Soften exception zou mischien nog een interessante toevoeging aan het model kunnen zijn, maar er zijn ook een aantal nadelen aan verbonden. Ten eerste is de afspiegeling van excepties zeer minimaal ondersteund in JDI. Dit is waarschijnlijk zo omdat instructieafspiegeling belangrijk is zinvol aan exceptie-afspiegeling te doen. JDI ondersteunt geen exceptie-afspiegeling. Ten tweede kan declare soft omgevormd worden tot een around advice en zo gespiegeld worden. Voorlopig wordt declare soft niet ondersteund. Precedence tot slot is ook niet traceerbaar. Het is natuurlijk mogelijk om in het model te zien in welke volgorde de adviezen uitgevoerd zullen worden, maar het is niet mogelijk om vast te stellen hoe die volgorde tot stand is gekomen. Dit zou zeker nog een mooie toevoeging aan AJDI kunnen zijn.
4.2
ABC Bytecode
De volgende sectie handelt over de vorm van de AspectJ-bytecode zoals ABC ze produceert. De AJC- en ABCbytecode zijn niet erg verschillend omdat ABC probeert AJC zo goed mogelijk te volgen.
4.2.1
Singleton Aspect Listing 4.1: aspectj code sample: singleton aspect en advice
1 public class Test { 2 private String method1 (){ 3 return " normal method 1 " ; 4 } 5 public static void main ( String [] args ){ 6 Test t = new Test (); 7 System . out . println ( t . method1 ()); 8 } 9 } 10 11 public aspect Ex1 { 12 before (): execution ( String method *()){ 13 System . out . println ( " called " ); 14 } 15 } In Listing 4.1 zijn een aspect met een advies en een klasse waarop het advies toegepast wordt zichtbaar. Na compilatie met ABC en decompilatie (zie Listing 4.2) is het aspect omgezet in een klasse en het advies in een methode. De nodige velden voor het singletongedrag zijn aangemaakt. Er is ook een aspectOf() method aangemaakt om de
HOOFDSTUK 4. EERSTE VALIDATIE: EEN DEBUGGER VOOR ASPECTJ
Listing 4.2: aspectj code sample: singleton aspect en advice, gedecompileerd 1 public class Test { 2 private String method1 (){ 3 Ex1 r1 = Ex1 . aspectOf (); 4 r1 . before$0 (); 5 return " normal method 1 " ; 6 } 7 8 public static void main ( String [] r0 ){ 9 System . out . println ( new Test (). method1 ()); 10 } 11 } 12 13 public class Ex1 { 14 public static final Ex1 a b c $ p e r S i n g l e t o n I n s t a n c e ; 15 private static Throwable a b c $ i n i t F a i l u r e C a u s e ; 16 17 static { 18 try { 19 abc$postClinit () 20 } catch ( Throwable $r0 ){ 21 a b c $ i n i t F a i l u r e C a u s e = $r0 ; 22 } 23 } 24 25 private static void abc$postClinit (){ 26 a b c $ p e r S i n g l e t o n I n s t a n c e = new Ex1 (); 27 } 28 29 public static Ex1 aspectOf () 30 throws org . aspectj . lang . N o A s p e c t B o u n d E x c e p t i o n { 31 Ex1 r0 = a b c $ p e r S i n g l e t o n I n s t a n c e ; 32 if ( r0 != null ) 33 return r0 ; 34 throw new N o A s p e c t B o u n d E x c e p t i o n 35 ( " Ex1 " , a b c $ i n i t F a i l u r e C a u s e ); 36 } 37 38 public static boolean hasAspect (){ 39 if ( a b c $ p e r S i n g l e t o n I n s t a n c e == null ) 40 return false ; 41 return true ; 42 } 43 44 public final void before$0 (){ 45 System . out . println ( " called " ); 46 } 47 }
44
HOOFDSTUK 4. EERSTE VALIDATIE: EEN DEBUGGER VOOR ASPECTJ
45
instantie op te vragen. Een hasAspect() method is ook verschenen. hasAspect() en aspectOf maken deel uit van de AspectJ-specificatie[15] en zijn dus steeds aanwezig. In de klasse Test is het advies ingeweven op de juiste plaats.
4.2.2
Per Object Aspect Listing 4.3: aspectj code sample: Per object aspect and advice
1 public aspect Ex1 perthis ( voidmethod ()){ 2 pointcut voidmethod (): execution ( String method *()); 3 4 before (): voidmethod () { 5 System . out . println ( " called " ); 6 } 7 } In Listing 4.3 is de code te zien voor een per this aspect. In Listing 4.4 is weer de gedecompileerde code te zien. Het advies zelf is weggelaten. De belangrijkste vaststelling dat er geen map wordt bijgehouden van de objectaspectparen in het aspect, maar dat de aspect instantie opgeslagen is in een veld in de klasse Test en dat Test de interface Ex1$abc$PerThis implementeert. Dit wil in de praktijk zeggen dat elk per object aspect een interface en zijn implementatie toevoegt aan elke klasse in het systeem. Het inweven van deze interface zou natuurlijk beperkt kunnen worden, maar dat doet ABC niet omdat AJC het ook niet doet. Het binden van de instantie gebeurt door een synthetisch before-advies dat voorrang heeft op alle mogelijk niet-synthetische adviezen en dat gebonden is aan de pointcut die meegegeven wordt met de per object scope. Het advies roept de abc$perThisBind() methode aan met het juiste argument. Het synthetische advies verschilt van normale adviezen in die zin dat het statisch is en dus geen aspectinstantie nodig heeft. In AspectJ zelf bestaan statische adviezen niet. Ook het aanroepen van het advies is iets anders ingevuld omdat het niet meer zeker is dat er een instantie beschikbaar is. De hasAspect method wordt dus gebruikt.
4.2.3
Per CFLOW Aspect
In Listing 4.5 is de code voor een per CFLOW aspect te zien. In Listing 4.6 is de gedecompileerde versie te zien. De uitvoering van het advies is identiek aan het per object geval en dus afwezig. Ook hier is er weer een synthetisch advies. Dit keer is het een statisch before-after advies. Omdat het belangrijk is dat niet alleen het binnengaan, maar ook het verlaten van de joinpoint gesignaleerd wordt, moet er een around advies gebruikt worden. Dit zou echter voor teveel overhead zorgen en men gebruikt dus before-after-advice. Dit is een speciaal advies type dat bestaat uit een before en een after advice samen.
HOOFDSTUK 4. EERSTE VALIDATIE: EEN DEBUGGER VOOR ASPECTJ
46
Listing 4.4: aspectj code sample: Per object aspect en advice; decompiled 1 public class Test implements Ex1$abc$PerThis { 2 private transient Ex1 E x 1 $ a b c $ P e r T h i s F i e l d ; 3 4 private String method1 (){ 5 String r1 = " normal method 1 " ; 6 Ex1 . abc$perThisBind ( this ); 7 if ( Ex1 . hasAspect ( this )) 8 Ex1 . aspectOf ( this ). before$0 (); 9 return r1 ; 10 } 11 12 public Ex1 Ex 1$ ab c$ Per Th is Ge t (){ 13 return E x 1 $ a b c $ P e r Th i s F i e l d ; 14 } 15 public void Ex 1$ ab c$ Per Th is Se t ( Ex1 r1 ){ 16 E x 1 $ a b c $ P e r T h i s F i e ld = r1 ; 17 } 18 ... 19 20 public class Ex1 { 21 public static void abc$perThisBind ( Object r0 ){ 22 if ( r0 instanceof Ex1$abc$PerThis ){ 23 Ex1$abc$PerThis r2 = ( Ex1$abc$PerThis ) r0 ; 24 if ( r2 . Ex 1$ ab c$ Per Th is Ge t () == null ){ 25 r2 . Ex 1$ ab c$ Per This Se t ( new Ex1 ()); 26 } 27 } 28 } 29 30 public static Ex1 aspectOf ( Object r0 ) 31 throws org . aspectj . lang . N o A s p e c t B o u n d E x c e p t i o n { 32 if ( r0 instanceof Ex1$abc$PerThis ){ 33 Ex1 r2 = (( Ex1$abc$PerThis ) r0 ). Ex 1$ ab c$ Pe rTh is Ge t (); 34 if ( r2 != null ){ 35 return r2 ; 36 } 37 } 38 throw new N o A s p e c t B o u n d E x c e p t i o n (); 39 } 40 41 public static boolean hasAspect ( Object r0 ){ 42 return r0 instanceof Ex1$abc$PerThis && 43 (( Ex1$abc$PerThis ) r0 ). Ex 1$ ab c$ Per Th is Ge t () != null ); 44 } 45 ...
HOOFDSTUK 4. EERSTE VALIDATIE: EEN DEBUGGER VOOR ASPECTJ
47
Listing 4.5: aspectj code sample: Per CFLOW Aspect and Advice 1 public aspect Ex1 percflow ( main ()){ 2 pointcut voidmethod (): execution ( String method *()); 3 pointcut main (): 4 execution ( public static void main ( String [])); 5 6 before (): voidmethod (){ 7 System . out . println ( " called " ); 8 } 9 }
Belangijk is natuurlijk ook dat de CFlowStack threadLocale is en zich dus in alle threads gedraagt alsof hij slecht door een thread gebruikt wordt.
4.2.4
CFLOW
In Listing 4.7 is een CFLOW pointcut te zien. Het CFLOW-gedeelte van deze pointcut zal resulteren in een synthetisch advies en een CFLOWresidu. Het synthetisch advies is vergelijkbaar met dat voor CFLOW-scoping: het advies duwt de nodige argumenten op een stack. Er zijn nog enkele optimaties ten opzichte van scoping, om de overhead te beperken. Het CFLOWresidubestaat uit een test die nagaat of de stack leeg is. Als dit het geval is, wordt het advies niet uitgevoerd, anders worden de argumenten afgehaald en het advies uitgevoerd.
4.2.5
ITD
In Listing 4.8 is een voorbeeld van ITD te zien, dat een veld en een methode aanmaakt in Test. De nieuwe methode maakt gebruik van het private veld field. Dit is toegestaan omdat het aspect privileged is. De ITD-methode extra() wordt aangeroepen in het basis programma. In AspectJ is dit toegestaan. Het gedecompileerd programma is te vinden in listing 4.9. Het lichaam van de method wordt in het aspect gedefinieerd. Volgens de abc-2004-4 paper[3] is dit gedaan om makkelijkere toegang te hebben tot statische velden in het aspect en voor betere namematching. Het veld wordt ingeweven in de doelklasse, maar de initialisatie gebeurt in het aspect. Waarom dit zo gebeurt, is slecht gedocumenteerd, maar in de code staat (abc.weaving.weaver.InterTypeAdjuster:1170) dat deze constructie bestaat om de whitin code pointcut correct te doen werken. Om toegang tot het private veld field te verkrijgen, worden er twee accessormethoden aangemaakt.
HOOFDSTUK 4. EERSTE VALIDATIE: EEN DEBUGGER VOOR ASPECTJ
Listing 4.6: aspectj code sample: Per CFLOW Aspect and Advice; decompiled 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
public class Ex1 { public static CFlowStack a bc$per CflowS tack = new CFlowStack (); public static Ex1 aspectOf () throws N o A s p e c t B o u nd E x c e p t i o n { return ( Ex1 ) a bc$per CflowS tack . peekInstance (); } public static boolean hasAspect (){ return a bc$per CflowS tack . isValid (); } public static void abc$perCflowPush (){ bc$perCflowStack . pushInstance ( new Ex1 ()); } ... public class Test { ... public static void main ( String [] r0 ){ Ex1 . abc$perCflowPush (); try { Test r3 = new Test (); String r2 = " call " ; System . out . println ( r3 . method1 ( r2 )); } catch ( Throwable $r8 ){ Ex1 . a bc$per CflowS tack . pop (); throw $r8 ; } Ex1 . a bc$per CflowS tack . pop (); } ...
48
HOOFDSTUK 4. EERSTE VALIDATIE: EEN DEBUGGER VOOR ASPECTJ
49
Listing 4.7: aspectj code sample: CFLOW pointcut 1 2 3 4 5
abstract pointcut creation ( Data data ); abstract pointcut run ( String arg ); pointcut activeCreation ( Data data , String arg ): creation ( data ) && cflow ( run ( arg ));
Listing 4.8: aspectj code sample: ITD 1 public class Test { 2 private String field = " initval " ; 3 public static void main ( String [] args ){ 4 Test t = new Test (); 5 t . extra (); 6 } 7 } 8 9 privileged aspect Ex1 { 10 String Test . field2 = " ITD " ; 11 public void Test . extra (){ 12 System . out . println ( " extra " + field + field2 ); 13 } 14 }
4.3
ABC Bytecode in relatie tot ADB
Zoals te zien in Figuur is de mapping van AspectJ-types naar Java-types is ´e´en op ´e´en. Klassen worden klassen, aspecten worden klassen, methoden worden methoden en adviezen worden methoden.
Figuur 4.3: mapping van AspectJ-entiteiten op Java-entiteiten
HOOFDSTUK 4. EERSTE VALIDATIE: EEN DEBUGGER VOOR ASPECTJ
Listing 4.9: aspectj code sample: ITD; decompiled 1 public class Test { 2 private String field ; 3 public String Ex1$field2$2 ; 4 5 public String g e t $ a c c e s s o r $ f i e l d $ 6 (){ 6 return field ; 7 } 8 9 public String s e t $ a c c e s s o r $ f i e l d $ 7 ( String r1 ){ 10 field = r1 ; 11 return r1 ; 12 } 13 14 public Test (){ 15 Ex1 . fieldinit$8 ( this ); 16 field = " initval " ; 17 } 18 19 public void extra (){ 20 Ex1 . extra ( this ); 21 } 22 } 23 24 public class Ex1 { 25 public static void extra ( Test r0 ){ 26 System . out . println ( " extra " + 27 r0 . g e t $ a c c e s s o r$ f i e l d $ 6 () + 28 r0 . Ex1$field2$2 ); 29 } 30 31 public static void fieldinit$8 ( Test r0 ){ 32 r0 . Ex1$field2$2 = Ex1 . init$field2$5 ( r0 ); 33 } 34 35 public static String init$field2$5 ( Test r0 ){ 36 return " ITD " ; 37 } 38 39 ...
50
HOOFDSTUK 4. EERSTE VALIDATIE: EEN DEBUGGER VOOR ASPECTJ
51
Ook de lijnnummering blijft behouden voor alle non-synthetische instructies.
4.4
De Aspect Bench Compiler
Zoals eerder vermeld is de Aspect Bench Compiler (ABC) een modulaire onderzoekscompiler. Hij werd gebouwd door de Programming Tools Group van de Oxford University en de Sable research group aan McGill University in Montrea.
4.4.1
Onderdelen
De ABC tracht zoveel mogelijk software te herbruiken en bouwt dus verder op bestaande technologie. In figuur 4.4 is hiervan een overzicht te zien. ABC gebruikt polyglot[13] als front-end voor parsing en typechecking en soot[16] als back-end voor weaving en optimalisatie. Polyglot op zijn beurt gebruikt Cup en JFlex als respectievelijk parseren lexergenerator. Soot gebruikt Jasmin om class files aan te maken. ABC
Polyglot
Jflex
Soot
Cup
Jasmin
Figuur 4.4: Technologische afhankelijkheden van de ABC compiler Polyglot is een uitbreidbare compiler front-end voor Java. ABC 1.2.1 steunt op Polyglot 1.3.0. De gehele architectuur van Polyglot is gericht op uitbreidbaarheid. De belangrijkse functionaliteiten voor ABC zijn uitbreidbare grammaticas, zeer dynamische job scheduling, veel herbruikbare compiler passes en een geavanceerd visitor systeem. Voor meer informatie zie[13]. Soot, de back-end, is een optimisatieraamwerk. Het heeft dus zeer uitgebreide ondersteuning voor analyse en adaptie van de programmastructuur. Er zijn heel wat bruikbare optimalisaties aanwezig in Soot. Soot beschik over meerdere interne taalrepresentaties: jimple is een three-address respresentatie, baf is een getypeerde vorm van het Javabytecodeformaat, shimple is een SSA(static single assignment) representatie, dava is geschik voor decompilatie,. . . Binnen Soot is het ook mogelijk om aan elke entiteit tags vast te maken, deze tags worden behouden doorheen de transformaties. Dit is natuurlijk praktisch voor het transport van debuginformatie. Als men informatie vastmaakt aan een instructie, zal de informatie vrijwel zeker de output-stage bereiken. Het enige nadeel aan Soot is dat de scheduling van de passes nogal gebrekkig is.
HOOFDSTUK 4. EERSTE VALIDATIE: EEN DEBUGGER VOOR ASPECTJ
4.4.2
52
Het compilatieproces
Het compilatieprocess van AspectJ is vrij complex in vele opzichten. Het zou ons een beetje te ver leiden om daar dieper op in te gaan. Een globaal overzicht van het proces is te zien in figuur 4.5. Een meer volledige behandeling is te vinden in [3].
Figuur 4.5: Controle flow door de ABC compiler Parse De broncode wordt gelexed en geparsed om zo een abstract syntex tree(AST) te verkrijgen. Clean De AST wordt schoongemaakt. De typeinformatie wordt verzameld en er worden disambiguaties doorgevoerd. Dit wil zeggen dat namen zoals List omgezet worden in volledig gekwalificeerde namen zoals java.util.List of java.awt.List. Deze stap komt twee keer voor omdat alle type informatie pas beschikbaar is na het invoeren van ITD’s en het invoeren van ITD’s al bepaalde type informatie nodig heeft. Transform types De type-informatie wordt aangepast aan de ITD’s. Deze verandering wordt enkel doorgevoerd in de type-informatie en niet in de AST.
HOOFDSTUK 4. EERSTE VALIDATIE: EEN DEBUGGER VOOR ASPECTJ
53
Collect aspect info Alle aspectgerelateerde informatie wordt opgehaald en opgeslagen in een centrale datastructuur. Check Alle checks worden uitgevoerd. Dit omvat onder andere type checking en excepion checking. Transform AST Alle Java-vreemde entiteiten worden omgezet in Java-entiteiten: aspecten worden klassen, adviezen worden methoden, joinpoints worden weggegooid. Het is belangrijk om te weten dat lijnnummers behouden blijven in dit process en dat de instructies die adviezen vormen dus correct genummerd is. Jimplify structure De AST wordt omgezet naar jimple, ´e´en van de interne taalrepresentaties van Soot. De eigenlijk instructies in de methoden worden nog niet aangemaakt. Weave ITD’s De ITD’s worden ingeweven in de Sootstructuren. Jimplify methods De omzetting naar jimple wordt volledig doorgevoerd: de lichamen van methoden worden gevuld met instructies. Match Alle pointcuts worden gematched tegen alle mogelijk joinpoints. Dit is een tijdrovende stap omdat de matching nogal naief gebeurt. Weave De eigenlijke inweving van de aspecten. Optimize residue de residus worden geoptimiseerd door analyse van de geweven code. Daarna wordt het weven ongedaan gemaakt en er wordt opnieuw geweven met de geoptimiseerde residus. Dit proces wordt herhaald tot een maximum aantal iteraties overschreden is of er geen verbetering meer merkbaar is. Optimize code De code wordt geoptimaliseerd met standaardoptimalisaties die Soot beschikbaar stelt, die uitgebreid zijn met kennis over de invarianten van de ingeweven code. Output De classfiles worden aangemaakt. Bij het aanmaken van de classfiles kunnen tags geagregeerd worden tot Jasmin tags. Deze kunnen als attributen mee verpakt worden in de classfile.
4.5
Agent
De debugging agent moet welbepaalde gegevens verzamelen en door de compiler loodsen zodat ze de outputstage bereiken. De makkelijkste manier om gegevens daar te krijgen zijn tags. Het probleem van de agent bestaat vooral uit het oppikken van informatie wanneer ze beschikbaar is en ze dan in tags omzetten. Dan moeten de gegevens na de transformatie tot bytecode(BAF) geagregeerd worden en in de class file opgeslagen als attributen.
HOOFDSTUK 4. EERSTE VALIDATIE: EEN DEBUGGER VOOR ASPECTJ
54
Het doel van de aspectj ADB-back-end implementatie omvat het aanleveren van aspectcompositie-informatie, stacktrace-informatie, instantie-afspiegeling, oorspronggebasseerde lijnnummers en veldlijnnummers. De aspectcompositie-informatie valt uiteen in twee types: aspectinformatie en crosscutting-informatie. De stacktrace-informatie kan volledig afgeleid worden uit crosscutting-informatie. Instantie-afspiegeling kan zoals eerder besproken best in de back-end afgehandeld worden. Veldlijnnummers zijn aanwezig in de outputstage en moeten enkel nog geaggregeerd worden.
4.5.1
Oorspronggebaseerde lijnnummers
Zoals eerder vermeld, is oorspronggebaseerde lijnnummering in conflict met de juiste werking van de ADB. Dit is dus een alleenstaande functionaliteit. Soot is in staat willekeurige tags aan te brengen op elke instructie en deze tags blijven bewaard onder alle transformaties. Onder de standaardconfiguratie worden alle lijnnummers en de naam van het oorspronkelijke bestand vastgemaakt aan elke eerste instructie die dit lijnnummer draagt. Deze worden als gewone Java-lijnnummers geaggregeerd en in de classfile verpakt. Ook de omzetting van advies naar methode behoudt de lijnnummers. Adviezen zijn dus juist genummerd. Hieruit volgt dat er maar twee soorten code zijn waarvoor een ingreep nodig is: synthetische code en ge¨ınlinede code uit andere klassen. Synthetische code omdat ze helemaal geen lijnnummers heeft en ge¨ınlinede code omdat ze uit een ander bestand komt. Standaard Java-lijnnummeringsmechanismen ondersteunen dit niet. Om de syntethische code van lijnnummers te voorzien zijn er drie problemen. Ten eerste moet het punt gevonden worden waar het productieproces van de code begint. Op dit punt (punt een) is de oorsprong van de synthetische code gekend en kan een lijnnummer afgeleid worden. Het tweede probleem is het vinden van alle locaties waarop synthetische instucties gemaakt worden (punten twee). Hier moeten tags bevestigd worden. Het derde probleem is dat het eerste punt hoog in de uitvoeringsstapel zit en het tweede helemaal beneden. Met traditionele OO-technieken zou dit betekenen dat er argumenten moeten meegegeven worden van punt een naar alle mogelijke punten twee. Daar aangekomen moeten de instructie-instanties ´e´en voor ´e´en van een tag voorzien worden. Dit is ondoenbaar en moeilijk onderhoudbaar. Het is een crosscutting concern. Dit probleem kan opgelost worden met een aspect: de context kan onderschept worden met een CFLOW-pointcut en het aanmaken van nieuwe instructies met een gewone pointcut. Een advies kan dan de tag toevoegen. Dit aspect is te zien in Listing 4.10. Het opslaan van de nummering van ge¨ınlinede code en van de synthetische code is niet mogelijk met het normale lijnnummeringsmechanisme van Java omdat dit ervan uitgaan dat al de broncode van ´e´en klasse in hetzelfde bestand zit. Er is echter wel een anders formaat dat de standaard Java-infrastructuur ondersteunt dat dit wel toelaat. Dit formaat is beschreven in JSR45[6]. Dit formaat is eigenlijk bedoeld voor programmeertalen die hun broncode outputten in een lagere taal en uiteindelijk Java-broncode outputten. In de terminologie van deze standaard vormt elke taal een stratum. Voor elk lijnnummer in de input-
HOOFDSTUK 4. EERSTE VALIDATIE: EEN DEBUGGER VOOR ASPECTJ
Listing 4.10: Aspect voor het toevoegen van lijnnummers 1 public aspect D e b u g I n f o r m a t i o n P a s s i n g { 2 // creatie van statements 3 pointcut stmtcreation (): 4 call ( public Host + soot . jimple . Jimple . ne *(..)); 5 6 // het inweven van a dv ie st oe pa ss in ge n 7 pointcut p o i n t c u t C o d e G e n C o n t e x t 8 ( SootMethod meth , A dviceA pplica tion parent ): 9 cflow ( 10 execution ( private void 11 abc . weaving . weaver . PointcutCodeGen . weave_one ( 12 SootClass , 13 SootMethod , 14 LocalGeneratorEx , 15 A dviceA pplica tion )) 16 && args (* , meth ,* , parent )); 17 18 // het aanmaken van aspectOf , hasAspect ,... 19 pointcut a s p e c t C o d e G e n C o n t e x t ( Aspect parent ): 20 cflow ( 21 execution ( public void 22 abc . weaving . weaver . AspectCodeGen . fillInAspect ( 23 Aspect )) 24 && args ( parent )); 25 26 after ( Aspect parent ) 27 returning ( Host unit ): 28 stmtcreation () && a s p e c t C o d e G e n C o n t e x t ( parent ){ 29 SootClass sc = 30 parent . getInstanceClass (). getSootClass (); 31 unit . addTag ( sc . getTag ( " SourceFileTag " )); 32 unit . addTag ( sc . getTag ( " SourceLnPosTag " )); 33 } 34 35 after ( SootMethod meth , A dviceA pplica tion parent ) 36 returning ( Host unit ): 37 stmtcreation () && p o i n t c u t C o d e G e n C o n t e x t ( meth , parent ){ 38 polyglot . util . Position pos = 39 parent . advice . getPosition (); 40 unit . addTag ( new SourceFileTag ( pos . file ())); 41 unit . addTag ( new SourceLnPosTag ( pos . line () , 42 pos . endLine () , pos . column () , pos . endColumn ())); 43 } 44 }
55
HOOFDSTUK 4. EERSTE VALIDATIE: EEN DEBUGGER VOOR ASPECTJ
56
taal wordt bijgehouden welke lijnen broncode in de outputtaal aangemaakt zijn. Dit is de source mapping(SMAP). Door alle opeenvolgende sourcemappings te combineren krijgt men een resolved SMAP die een stratum afbeelt op Java broncode. Deze SMAPS kunnen in de classfile verpakt worden. Alle JPDA-implementaties die JSR-45 ondersteunen zijn in staat om op basis van deze omzettingstabbellen en de standaard Javalijnnummerinformatie de juiste lijnnummers te outputten voor alle beschikbare strata. Het enige struikelblok voor het gebruik hiervan is dat ABC geen source-to-source compiler is. Daarom is het noodzakelijk om een virtuele broncode aan te maken. Deze broncode heeft juist een lijn voor elke jimple-instructie. Er wordt dan een SMAP aangemaakt die de juiste lijnnummers mapt op de virtuele source. De lijnnummers voor het Java-stratum worden vervangen door de virtuele nummers. Dit zorgt ervoor dat een debugger die JSR-45 niet ondersteunt enkel de virtuele lijnnummers te zien krijgt, die op zich betekenisloos zijn. Een technische ingreep is nog nodig om dit geheel te doen werken en dat is het ‘opensmeren’ van de lijnnummers. In de standaardimplementatie zijn lijnnummers enkel bevestigd op de eerste instructie die dit nummer draagt. Ingeweven code kan dit gebied onderbreken en het is dus nodig om deze tag te repliceren naar al de volgende instructies. Oorspronggebasseerde lijnnummering werk dus in op de compiler in ‘jimplify methods’ en ‘weave’. Het ‘opensmeren’ wordt is een onderdeel van “jimplify methods”. De weaving moet voorzien worden van een aspect. En vlak na de weaving moeten de lijnnummers gevirtualiseerd worden. De virtualisatie kan de SMAP al klaar maken om in de class file verpakt te worden en de aggregatie van de lijnnummers is al onderdeel van de output stage, dus er is geen extra werk meer in de output stage.
4.5.2
Aspectinformatie
De agent moet weten welke klassen eigenlijk aspect zijn, wat hun scope is, hun adviezen, de pointcut, het type en de signatuur van elk advies. Extra informatie die nuttig kan zijn, zijn de begin- en eindlijnnummers van de adviezen en het aspect. Als deze informatie is beschikbaar in de GlobalAspectInfo singleton. Dit is de groene lijn in figuur 4.5. Vlak voor het weaven kan al deze informatie verzameld worden, gefilterd en aan de klasse bevestigd worden als een AspectTag die klaar is voor output. Het filteren houdt in dat synthetische adviezen, zoals CFLOW setup (zie paragraaf 4.2.3), uitgefilterd worden.
4.5.3
Crosscuttinginformatie
De crosscuttinginformatie bestaat uit een lijst met alle inwerkende joinpoints die aan elke methode bevestigd wordt. Voor elk joinpoint wordt bijgehouden wat het type is, het lijnnummer, de begin- en eindinstructie van de ingevoegde code en alle adviestoepassingen die inwerken op dat joinpoint. Per adviestoepassing wordt het residue bijgehouden en een referentie naar het advies. Deze referentie bestaat uit de naam van het aspect en de naam en signatuur van de implementerende methode.
HOOFDSTUK 4. EERSTE VALIDATIE: EEN DEBUGGER VOOR ASPECTJ
57
Welke adviezen aangrijpen op welke methode is terug te vinden in de GlobalAspectInfo singleton. Deze informatie kan dan gesorteerd worden per joinpoint en alle informatie over adviestoepassing kan hieruit afgeleid worden. Deze informatie wordt in een CrossCuttingTag verpakt en aan de methode bevestigd. Dit kan gebeuren op elk moment na de matching. Het laatste stukje informatie dat we nodig hebben is de eerste en laatste instructie van een joinpoint om de stacktrace correct te kunnen weergeven. Het verkrijgen van deze informatie is een probleem dat analoog is aan dat van op oorspronggebaseerde lijnnumering omdat de informatie enkel beschikbaar is wanneer de instructies aangemaakt worden, maar zich hoger in de stack bevindt. Hiervoor bestaat een aspect, nagenoeg identiek aan dat van Listing 4.10 dat AdviceApplicationTags aanbrengt op de betrokken instructies. De aggregatie van al deze gegevens is iets ingewikkelder omdat de informatie in tags zit, verspreid over alle mogelijke klassen. De aggregatie heeft pas zin als alle instructies omgezet zijn in baf, vlak voor het aanmaken van de classfile. Dit is zo omdat het bijhouden van referenties naar instructies zinloos is omdat de instructies toch verloren gaan in de omzetting van jimple naar baf. De informatie moet dus in tags verpakt blijven tot in de output stage. De aggregatie is dus mee gepland in de finale aggregatiepass van Soot als BodyTransformer. Soot biedt alle method bodies aan de transformer aan op het meest geschikte moment. Als de methode adviezen bevat wordt de aggregatie aangevat. Eerst worden alle joinpoints die opgeslagen liggen in de CrossCuttingTag van de methode overlopen. Er wordt een map aangelegd die adviestoepassingen afbeeldt op joinpoints. Dan wordt het lichaam van de methode overlopen. Als een AdviceApplicationTag gevonden wordt, wordt deze verwijderd en het bijhorende joinpoint wordt opgezocht in de map. Als er nog geen eerste instructie gekend is voor het joinpoint, wordt de huidige instructie opgeslagen als de eerste en laatste instructie. Anders wordt ze enkel opgeslagen als de laatste. Met dit simpele algoritme worden de joinpoints van begin en eind instructie voorzien.
4.6
Backend
De back-end volgt de voorgestelde referentie architectuur uit paragraaf 3.4.4. De back-end moet in staat zijn attributen uit de classfiles te halen. De JVM en JPDA zijn hiertoe niet in staat. Daarom wordt gebruikgemaakt van de Byte Code Engineering Library (BCEL). De agentproctol controller zal, wanneer nodig, classfiles inladen en alle nuttige attributen hiervan decoderen en cachen. Deze back-end moet dus toegang hebben tot de classfiles. De providers aggregeren deze gegevens. Dit is meestal een simpele herverpakking, maar er zijn enkele speciale gevallen. In de CrossCuttingInfoProvider is de query die alle applicaties van een advies opvraagt een beetje een probleem, omdat applicaties enkel zijn opgeslagen in de methode waarop ze van toepassing zijn. Hiervoor moet de back-end dus alle methoden van alle geladen klassen overlopen en doorzoeken. Om te weten te komen welke klassen geladen
HOOFDSTUK 4. EERSTE VALIDATIE: EEN DEBUGGER VOOR ASPECTJ
58
zijn heeft de CrossCuttingInfoProvider rechtstreekse toegang tot het VirtualMachine spiegelobject nodig. Deze provider krijgt dus via zijn constructor een referentie mee naar het VirtualMachine spiegelobject. Een andere mogelijk oplossing voor dit probleem zou het opslaan van de reverse mapping zijn in de classfile of een aparte debug assembly. Dit is sneller maar kan minder consistentiegaranties geven. De AspectInstanceProvider is natuurlijk ook een speciaal geval omdat hij methoden uitvoert in de doel VM. Om een aspectinstantie te vinden, komt dit neer op het uitvoeren van de aspectOf() methode. De query om voor een per object aspectinstantie het juiste object te vinden werkt iets anders. Omdat deze mapping nergens wordt bijgehouden, ontstaat er een probleem. Gelukkig heeft JDI 1.6 de mogelijkheid om alle objecten die naar een andere object refereren op te vragen. Hiermee kan het juiste object makelijk opgespoord worden. De StackFilterManager en de FilterManager delen heel wat functionaliteit, daarom zijn ze uitgevoerd in ´e´en klasse. De FilterManager filtert synthetische entiteiten en advies methode weg. De synthetische entiteiten worden herkend aan bepaalde patronen, zoals de string “abc$” of “et$accessor$” die in de namen van synthetische velden voorkomen. De AspectInfoProvider wordt ook geraadpleegt om te zien of een klasse een aspect is. Als dit het geval is, worden de hasAspect() en aspectOf() methoden verborgen. Adviesmethoden worden ook langs de AspectInfoProvider opgespoord en weggelaten. De StackFilterManager gebruikt enkel het interne back-end-protocol en een deel van de methodefilter. Alle stackframes waarvan de methode synthetisch is, worden weggelaten. Voor elke stackframe wordt er nagekeken of er joinpoints aangrijpen op de betreffende methode. Als dit het geval is wordt nagegaan of de instructie counter tussen de begin- en eindinstructie van een joinpoint ligt. Als dit zo is wordt er een HookFrame ingevoegd.
4.7
Resultaat
Het doel van de AspectJ-implementatie was het testen van de architectuur en het onderzoeken van de mogelijkheden. Alle mogelijkheden die de architectuur op dit moment ondersteunt, kunnen door de AspectJ-back-end ingvuld worden. Zelfs met deze beperkte subset vormt dit een debugging API voor AspectJ die unieke mogelijkheden biedt. Hieruit kunnen we besluiten dat spiegel-reflectie van aspectsystemen waardevol en haalbaar is. Langs de andere kant heeft AspectJ nog mogelijkheden die AJDI niet ondersteunt, of waar meer detail geboden kan worden. De meeste van deze verbeteringen zouden zeker nog met de huidige architectuur ondersteund kunnen worden. Ook de architectuur heeft zijn nut bewezen: de volledige debugging functionaliteit kan aangeboden worden door implementatie van een back-end die volledig afgescheiden is van de front-end. Het implementeren van de back-end is vrij simpel. De echte complexiteit zit in de agent en de front-end. De front-end is herbruikbaar, dus de grote inspanning ligt bij de agent. Bij het construeren van de agent is het probleem het begrijpen van de compiler en
HOOFDSTUK 4. EERSTE VALIDATIE: EEN DEBUGGER VOOR ASPECTJ
59
het verzamelen van de gegevens. De studie van de compiler kost veel tijd en moeite. Als de agent echter zou worden gemaakt door de ontwerper van de compiler, zou dit zeer vlot kunnen gebeuren.
Hoofdstuk 5
Tweede validatie: een debugger voor JBoss Zoals vermeld in 1.4 is het doel van de JBoss AOP-implementatie om aan te tonen dat de architectuur bruikbaar is op een radicaal verschillend doelplatform. Er zal dus maar een minimale ondersteuning zijn. Alle functionaliteiten, behalve oorspronggebaseerde lijnnummering en aspectinstantie-afspiegeling worden ondersteund, maar minder volledig: enkel method execution joinpoints worden ondersteund, geen ondersteuning voor lijnnummers bij XML en annotaties,. . . Ik beperk me bij JBoss tot het minimum omdat JBoss AOP slecht gedocumenteerd is. Verder wordt er ook bytecode aanmaakt tijdens de uitvoering, wat de decompilatie moeilijk maakt. Omdat JBoss AOP vooral snel moet zijn, is alle code gericht op snelle uitvoering en niet op het verzamelen van informatie. Dit alles maakt de code complex en moeilijk begrijpbaar. Volledige ondersteuning zou om praktische redenen te veel tijd kosten. Eerst zal ik een technisch overzicht geven van de relevante onderdelen van JBoss AOP. Daarna zal ik de werking van de ADB back-end en de ADB agent voor JBoss AOP bespreken. Om te besluiten zal ik de resultaten overlopen.
5.1
JBoss in relatie tot AJDI
De relaties tussen JBoss- en AJDIentiteiten is ´e´en-op-´e´en, dit is duidelijk te zien in figuur 3.3 en figuur 5.1. De mapping van JBoss joinpoints naar AJDI joinpoints is ook goed in die zin dat alles modelleerbaar is, maar er is toch ook een verlies aan informatie. In figuur 5.2 is duidelijk te zien dat er veel verschillende soorten joinpoints afgebeeld worden op statement joinpoints. Hier is zeker nog plaats voor verfijning van het joinpoint model. Method en constructor execution worden ook afgebeeld op ´e´en type, maar hier kan men achteraf het onderscheid nog maken. Pinit, sinit en advice execution komen nooit voor.
60
HOOFDSTUK 5. TWEEDE VALIDATIE: EEN DEBUGGER VOOR JBOSS
Figuur 5.1: Statisch JBoss ER model
Figuur 5.2: Joinpoints in AJDI en AspectJ
61
HOOFDSTUK 5. TWEEDE VALIDATIE: EEN DEBUGGER VOOR JBOSS
5.2
62
JBoss weaving model
In deze sectie worden de algemene AOP-functionaliteiten besproken van JBOSS AOP die nodig zijn om een minimale debugger-back-end te bouwen. Mixins, containers, etc komen niet ter sprake. Enkel method execution joinpoints worden behandeld. Om de uiteenzetting begrijpbaarder te maken is er een object diagram (figuur 5.2) dat de structuur weergeeft van de pointcut uit Listing 2.3 en een sequentie diagramma (figuur 5.4) dat de controleflow weergeeft bij uitvoering van de geadviseerd methode Test.method(). In JBoss AOP zijn er drie soorten weaving: er is weaving die voor het inladen van de klasse moet gebeuren, er is on-the-fly weaving en hot swapping. De eerste soort wordt gebruikt om joinpoints voor te bereiden. Na het inladen van de klasse kunnen er dus geen nieuwe joinpoints toegevoegd worden. On-the-fly weaving wordt gebruikt om adviesketens op te stellen. Hot swapping kan gebruikt worden om ongebruikte, maar voorbereide joinpoints sneller te laten uitvoeren Het voorbereiden van joinpoints bestaat in de eerste plaats uit het vinden van alle nodige joinpoints. Als een klasse een joinpoint bevat, wordt dit joinpoint voorbereid. Het voorbereiden omvat twee stappen: ten eerste wordt de klasse voorzien van drie advisor velden en de interface Advised. Ten tweede worden de instructies die het joinpoint vormen vervangen door een aanroep naar het raamwerk. Advisors zijn delegates die instaan voor de aspectgerelateerde gedragingen. Het eerste advisorveld is het statische veld aop$classAdvisor$aop van het type Advisor. Dit veld bevat de advisor voor deze klasse. Het tweede veld is instanceAdvisor$aop van het type InstanceAdvisor. Dit veld wordt enkel gebruikt als deze instantie van het object een specifieke adviesketen heeft. Het derde veld is currentAdvisor$aop van het type Advisor. Het verwijst naar de classadvisor als er geen instance-advisor beschikbaar is, anders verwijst het naar de instance-advisor. De advisorinstanties die in de advisorvelden van een klasse zitten zijn van een type dat door middel van load time weaving speciaal is aangemaakt voor deze klasse. Dit type bevat een veld en een methode voor elk joinpoint in die klasse. De joinpointvelden zijn van een type dat uniek is voor dit joinpoint en dat overerft van Invocation. Als ´e´en van de joinpointmethoden aangeroepen wordt probeert ze haar bijhorend veld te vullen, als dit lukt roept ze de invokeJoinpoint() methode op van haar invocationobject, als dit niet lukt roept ze de originele code van haar joinpoint aan. Het vullen van het veld is meer dan een gewone instantiatie van een object. Om het gebruik van reflectie voor het aanroepen van adviezen te voorkomen, wordt er een nieuwe type aangemaakt waarin de advice chain hardcodegehardcoded is. Dus telkens de adviesketen aangepast wordt, wordt er door middel van on-the-fly weaving een subtype van het unieke joinpointtype aangemaakt. Door middel van reflectie wordt er een instantie aangemaakt en deze wordt in het juiste veld opgeslagen. Een tweede verandering die elk joinpoint ondergaat is het verhuizen van de instucties die het joinpoint vormen naar een nieuwe methode, waarna ze vervangen worden door een aanroep van hun bijhorende joinpointmethode in de huidige advisor. De methode zal indien nodig het invocationobject aanmaken en hiervan de invokeJoinpoint() methode
HOOFDSTUK 5. TWEEDE VALIDATIE: EEN DEBUGGER VOOR JBOSS
63
aanroepen. Hierdoor zal het invocationobject een nieuwe instantie van zichzelf aanmaken en hierop invokeNext() aanroepen. Hierdoor wordt de adviesketen doorlopen. Al deze verrichtingen worden aangestuurd door een centrale component: de AspectManager. Deze component is een goed beginpunt om de zoektocht naar debuginformatie te beginnen.
5.3
JBoss weaving model in relatie tot ADB
De mapping van JBoss-entiteiten op Java-entiteiten is te zien in figuur 5.5. Hier kunnen we enkele opvallende vaststellingen doen: een Java-klasse kan meerdere JBoss aspecten en ´e´en JBoss klasse terzelfdertijd zijn, een advies kan op meer dan ´e´en methode afgebeeld worden en ´e´en methode kan meerdere adviezen vormen. Een Java-klasse kan zowel aspect als klasse terzelfdertijd zijn omdat JBoss geen poging doet om dit te voorkomen. De progammeur doet met klassen die als aspect gebruikt worden wat hij wil. Langs de andere kant is het mogelijk om dezelfde klasse te gebruiken om meerdere aspecten te declareren en hierdoor kan ´e´en methode meerdere adviezen zijn. De architecturale beperking op uniciteit van types is dus geschonden. Het is dus niet mogelijk om JBoss AOP-programmas die zich niet aan de 1-op-1 relatie houden correct te debuggen. Een advies kan bestaan uit meerdere methoden met dezelfde naam en een verschillend argument. Het argument moet altijd een subtype van Invocation zijn. Als dit het geval is, zal het advies enkel aangeroepen worden als het aanroepende joinpoint van het juiste type is. Dit soort overloading kan beschouwd worden als een splitsing van het advies in verschillende adviezen met een residu dat het joinpointype nakijkt. Op deze manier
Figuur 5.3: Object en type structuur van de pointcut uit Listing 2.3
HOOFDSTUK 5. TWEEDE VALIDATIE: EEN DEBUGGER VOOR JBOSS
64
passen overloaded adviezen in het model van ADB. Om JBoss bruikbaar te maken voor ADB is het belangrijk dat we deze mapping kunnen maken. Dit is niet altijd mogelijk omdat aspectinstanties kunnen aangemaakt worden door factories die de gebruiker definieert. In dat geval is de klasse van het aspect onbekend. Het is zelfs mogelijk dat de factory voor elke instantie een ander type gebruikt. Dit probleem zal ik niet aanpakken, maar wel een aantal mogelijke oplossingen bespreken. Het probleem bestaat uit twee delen: ten eerste moet er voor elke instantie geweten zijn wat het type is en ten tweede moet er van elk type geweten zijn wat de velden, methoden en adviezen zijn.
Figuur 5.4: Controleflow bij uitvoering van de geadviseerde methode Test.method()
Figuur 5.5: mapping van JBoss-entiteiten op Java-entiteiten
HOOFDSTUK 5. TWEEDE VALIDATIE: EEN DEBUGGER VOOR JBOSS
65
Als we van type-uniciteit uitgaan, kunnen we het eerste probleem oplossen door het Java-type van het aspect te kennen. Het is mogelijk om de maker van de factory te verplichten ons die informatie door te spelen, door elke factory verplicht te voorzien van een methode die het juiste type doorgeeft. Een andere optie is een plug-insysteem in de ADB-agent te voorzien, zodat een derde partij in staat is de type informatie aan te brengen. Als we niet van uniciteit uitgaan kunnen we gelijkaardige technieken toepassen, maar dan moet de interface of de plug-in de mogelijkheid voorzien om voor een willekeurig object te vragen tot welk aspect het behoort. Het is ook mogelijk om een breakpoint aan de factory te gebruiken om alle daar aangemaakte objecten te onderscheppen en te onthouden. Dit is natuurlijk traag en vraagt veel geheugen. Een meer intrusieve techniek bestaat uit het toevoegen van een veld aan alle klassen dat hun JBoss-type onthoudt. Het andere probleem, het opvragen van de eigenschappen van het type is in de eerste plaats een modelleer probleem. Welke attributen willen we weergeven? Die van een intersectietype met een minimale interface waaraan alle instanties van dit type voldoen? Een unietype dat alle instanties omvat? Een unieke type dat de uniciteit van de types herstelt? Wanneer moet de informatie beschikbaar zijn? Na het declareren van het aspect, na het inladen van de factory, nadat de eerste instantie gemaakt wordt? Moet de informatie volledig beschikbaar zijn of gebruiken we excepties om ontbrekende informatie te signaleren? Zoals bij alle modelleerproblemen spelen veel factoren een rol. Om geen onnodige veronderstellingen te moeten maken ga ik hier niet verder op in.
5.4
Agent
Een agent voor JBoss AOP moet in staat zijn om informatie uit de VM te halen omdat JBoss AOP dynamisch geconfigureerd kan worden. Hiervoor kunnen we een VM- of een JDI-agent gebruiken. Ik gebruik een uitbreidbare VM-agent omdat ik verwacht dat dit het makkelijkst is. De communicatie met de agent gebeurt via een socket en een ’synchroon plain text request response protocol’. Dit maakt het ontwerp van de agent eenvoudig en maakt het heel simpel om de agent te testen. Dit laat echter geen events toe vanuit de agent, maar dit is ook niet nodig in de huidige versie van ADB. Om deze agent op te starten is er de Agent klasse. Ze wordt opgestart als mainklasse en krijgt de eigenlijk main-klasse mee als argument. De Agent klasse maakt een SocketCore aan, die een eigen daemon thread afsplitst, waarna de eigenlijke main-klasse wordt geladen en de main-methode uitgevoerd. Zoals te zien in figuur 5.6 is de agent een spiegelbeeld van de ADB-Back-end. Achteraan is er de collectie- en aggregatiecomponent die informatie onttrekt aan de AspectManager. De informatie wordt dan op de juiste manier verpakt in de communicatie module. Deze bestaat uit twee delen: een communicatiebeheerscomponent, SocketCore en een set Commandproviders die specifieke queries kunnen verwerken. Een command provider is een zelfdocumenterende modulaire component die bepaalde
HOOFDSTUK 5. TWEEDE VALIDATIE: EEN DEBUGGER VOOR JBOSS
66
aanvragen kan afhandelen. Elke command provider heeft een prefix. Als de SocketCore een commando binnenkrijgt langs zijn socket, dan wordt het eerste woord hiervan afgesplitst (alle tekens tot de eerste spatie.) Als dit woord overeenkomt met de prefix van een command provider, wordt de rest van het commando doorgegeven aan deze provider, die het dan kan afhandelen. Elke command provider voorziet ook een getHelp() methode. Als de help provider geladen is kan deze gebruikt worden om deze ingebouwde hulpfunctie uit te lezen. In de standaardconfiguratie heeft de agent twee command providers: de AspectInfoProvider en de CrossCuttingInfoProvider. De aspectinformatieprovider staat in voor het zoeken van de klasse en de attributen voor elk aspect, het aspect voor elke klasse en alle adviezen voor elke klasse. De crosscuttinginformatieprovider staat in voor het vinden van alle adviestoepassingen voor elk advies en elke methode.
5.4.1
Aspect naar klasse
De omzetting van aspect naar klasse kan effici¨ent gebeuren doordat de AspectManager een map bijhoudt die aspectnamen afbeelt op AspectDefinition objecten. Hieruit kan alle informatie over het aspect bekomen worden, behalve de bijhorende klasse. Het is wel mogelijk om de aspectfactory op te vragen. Als deze van het type GenericAspectFactory is kunnen we de naam van de klasse gewoon opvragen. Als ze van het type AspectFactoryDelegator is, wordt er een gebruikergedefini¨eerde factory gebruikt en kunnen we een foutmelding doorgeven omdat dit niet ondersteund wordt. Als zij van een ander type is, is er iets ernstig mis en kunnen we ook een foutmelding doorgeven. Een speciale ingreep die hier ook nog gebeurt en die de normale werking be¨ınvloedt, is het inladen van deze klasse, zodat de debugger zeker is dat er een spiegelobject kan aangemaakt worden voor dit type.
5.4.2
Klasse naar aspect
De omzetting van klasse naar aspect kan niet op een effici¨ente manier gebeuren omdat deze mapping niet bijgehouden wordt. Het is wel mogelijk om voor elk aspect te kijken
Figuur 5.6: Structuur van de JBoss AOP agent
HOOFDSTUK 5. TWEEDE VALIDATIE: EEN DEBUGGER VOOR JBOSS
67
of het dit type heeft. Als er aspecten met custom factories gebruikt worden, zal er hier geen uitzondering gegooid worden en zal er een inconsistentie ontstaan.
5.4.3
Adviezen per aspect
Van adviezen willen we in JBOSS AOP weten wat hun naam is, hun methodenaam, hun bindingen en welke signaturen hun methodes hebben. Dit laatste is om overloaded adviezen juist te kunnen behandelen. Van elke binding willen we weten wat de naam is, de pointcut en op welke adviezen ze toegepast is. Om de adviezen te identificeren moeten we hun aspect, methodenaam en signaturen kennen. De AspectManager slaat bindingen op in een map die bindingsnamen afbeeldt op bindingen. Adviezen worden opgeslagen in een map die adviesnamen afbeeldt op InterceptorFactory objecten. De bindingen hebben een referentie naar hun InterceptorFactory. De InterceptorFactory objecten hebben methoden om informatie op te vragen over hun aspect, de naam van hun advies, de naam van hun adviesmethode en hun type. Het komt er dus op neer dat de map met de bindingen gefilterd en gesorteerd moet worden: enkel de InterceptorFactory objecten van een bepaald aspect zijn nodig en de bindingen moet gesorteerd worden per InterceptorFactory. Dan moeten de InterceptorFactory objecten die geen bindingen hebben toegevoegd worden. Hiervoor moet de volledige map met InterceptorFactory objecten doorlopen en gefilterd worden. Omdat we werken met twee mappen moeten deze gelijktijdig vergrendeld worden. Dit geeft natuurlijk een risico op deadlock omdat er nergens beschreven staat wat de juist vergrendelings volgorde is. Het zoeken van deze informatie is dus een vrij ineffici¨ent werkje. Om deze operatie sneller te doen verlopen zouden er extra gegevens structuren ingebouwd kunnen worden. Dit zou JBoss AOP trager maken en geeft minder garantie op consistentie omdat fouten in extra structuren geen operationele gevolgen hebben. Bijvoorbeeld: de data structuur die alle InterceptorFactory objecten bijhoud werd in JBoss AOP eigenlijk niet gebruikt. Deze datastructuur had een ernstig consistentie probleem dat ondertussen opgelost werd nadat ik het gerapporteerd had. (http: //jira.jboss.com/jira/browse/JBAOP-554)
5.4.4
Applicaties per methode
Omdat we enkel method execution joinpoints ondersteunen, kan elke methode maximaal ´e´en joinpoint hebben. Dit joinpoint is terug te vinden in de Advisor. Omdat we geen ondersteuning bieden voor objectspecifieke adviezen volstaat het om de class advisor te vinden. De AspectManager biedt een methode aan om deze advisor op te vragen. De advisor heeft een map die de hash van een methodesignatuur afbeeldt op een MethodMatchInfo object. De Advisor biedt helaas geen methode aan om deze structuren op te vragen.
HOOFDSTUK 5. TWEEDE VALIDATIE: EEN DEBUGGER VOOR JBOSS
68
Deze method heb ik dus zelf toegevoegd. De juiste hash om het juiste MethodMatchInfo object op te vragen kan berekend worden met de MethodHashing klasse. De MethodMatchInfo objecten bevatten alle nodige informatie over de methode, de aanwezige bindingen en de daaraan gelinkte InterceptorFactories. Al deze informatie kan doorgegeven worden.
5.4.5
Applicaties per advies
Het zoeken van de applicaties per advies is de meest omslachtige bewerking. Omdat het advies binnenkomt als aan aspectnaam en een methodenaam moet eerst de juiste InterceptorFactory opgezocht worden. Omdat ook de juiste bindingen gekend moeten zijn moeten ook deze opgespoord worden. Het zoeken van de InterceptorFactory en de bindingen gebeurt op dezelfde manier als het zoeken van de adviezen voor een aspect. Dan wordt voor elke binding de Advisor opgevraagd. Hiervan wordt de lijst met MethodMatchInfo structuren opgevraagd. Hieruit worden alle MethodMatchInfo objecten gefilterd die de binding in hun lijst van bindingen hebben staan. Uit deze lijst kan alle nodige informatie afgeleid worden.
5.5
Backend
De back-end volgt de voorgestelde referentie-architectuur uit paragraaf 3.4.4. De back-end moet een socket connectie opzetten met de agent. Hiervoor is het dus nodig dat de agent al opgestart is. De back-end kan dus pas gebruikt worden nadat het programma geladen is en de agent zijn thread heeft afgesplitst. Het is ook erg belangrijk dat de debugger de agent zelf nooit stop zetop een breekpunt. Als dit gebeurt, is de agent niet langer responsief en kan er deadlock ontstaan. Omdat het event systeem nog niet gewrapped is, kan dit niet uitgesloten worden. De back-end biedt een simpele interface aan waaraan vragen gesteld kunnen worden en die het reply request protocol volledig encapsuleert. De providers aggregeren deze gegevens. Dit is meestal een simpele herverpakking, maar er zijn enkele speciale gevallen. Net zoals bij AspectJ zijn de StackFilterManager en de FilterManager uitgevoerd in ´e´en klasse. De FilterManager filtert weerom synthetische entiteiten en adviesmethoden weg. Synthetische velden kunnen herkend worden aan de string ´$aop´. Synthetische methoden bevatten ook ‘$aop’ of zijn ‘ getAdvisor’,‘ getClassAdvisor’ of ‘ getInstanceAdvisor’ . De AspectInfoProvider wordt, net als bij AspectJ gebruikt om adviesmethoden te verbergen. De StackFilterManager bij JBoss AOP is erg verschillend aan die voor AspectJ. Er wordt enkel gebruik gemaakt van JDI-queries, zonder methoden uit te voeren in de doel VM. Een eerste soort filtering is het verbergen van de opstartcode van de agent. Dit is zeer simpel: als het eerste frame van de stack afkomstig is van de main methode van de klasse adb.agent.Agent moeten de eerste vijf frames verworpen worden. Synthetische methode worden ook weggefilterd.
HOOFDSTUK 5. TWEEDE VALIDATIE: EEN DEBUGGER VOOR JBOSS
69
Het algoritme om adviezen uit te filteren is gebaseerd op het typepatroon dat de uitvoeringsstapel vertoont bij de uitvoering van adviezen (Tabel 5.1). Dit patroon komt natuurlijk overeen met de uitvoering zoals beschreven in figuur 5.4. Alle Sx markeringen geven een naam aan met een welbepaalde structuur. Het is mogelijk om ook deze structurele vereisten na te kijken, maar de gegevens aanwezig in de tabel zijn voldoende om het patroon te herkennen. De zesde lijn van de tabel, tussen de twee lijnen, is de eigenlijke uitvoering van het advies. Alles voor deze lijn wordt ´e´en hook frame. Deze lijn kan N keer herhaald worden en eventueel de uitvoering van andere adviesketens bevatten. Er kunnen ook aanroepen van invokeNext() of invokeTarget() in voorkomen die een nieuwe advies aanroepen. We gaan ervanuit dat adviesketens niet gemengd worden door de programmeur. Hiermee boedoel ik dat het Invocation object doorgegeven wordt en pas aangeroepen wanneer er al een andere adviesketen begonnen is. Type * A implements Advised S1 extends Advisor S3 S3 * S3 A
Methode * B S2 invokeJoinpoint invokeNext || invokeTarget * dispatch S4
Tabel 5.1: Stack toestand bij advice execution
5.6
Resultaat
Het doel van de JBoss AOP-implementatie was het onder druk zetten van de architectuur. Omdat JBoss in vele opzichten extreem complex en onoverzichtelijk is, heeft de architectuur heel wat te bieden op het gebied van filtering en information hiding. Mijn debugger is, voor zover ik dat kan vaststellen, de enige poging om de complexiteit te verbergen en geraffineerde informatie aan te bieden. JBoss is extreem verschillend van AspectJ. Het is dus zeker een sterk punt dat het ondersteund kan worden. Langs de andere kant is het ook duidelijk dat de architectuur onvoldoende is om JBoss volledig te ondersteunen. Een van de kritieke veronderstellingen uniciteit van type is geschonden. Om hieraan tegemoet te komen zullen grote veranderingen nodig zijn. Door het onder druk zetten van de architectuur zijn ook meer impliciete veronderstellingen fout gebleken. In de eerste plaats is de gerichtheid op compiler en VM-agenten misschien fout. Een JDI-halfagent met een eigen daemon thread zou simpeler kunnen
HOOFDSTUK 5. TWEEDE VALIDATIE: EEN DEBUGGER VOOR JBOSS
70
zijn dan een VM-agent, omdat er geen extra communicatie protocol meer nodig is en omdat een JDI-agent best werkt als de VM stil staat. Dit is de toestand waarin het meeste debugwerk gebeurt. Het feit dat JDI-agenten een notie van instantie identiteit ondersteunen is natuurlijk ook doorslaggegevend. Hierdoor komt de tweede ernstige fout aan het licht: de gebrekkige encapsulatie van de identiteiten. Zoals besproken in paragraaf 3.5.1 maakt dit het veel moeilijker om de notie van identiteit aan te passen en kan het het gebruik van een optimale identiteit onmogelijk maken. Het doorbreken van de type uniciteit zal hierdoor waarschijnlijk nog lastiger worden.
Hoofdstuk 6
Conclusie Wat is er nu eigenlijk bereikt, wat biedt de AO-debugging architectuur de gebruiker aan? AODA biedt een gestructureerde API(AJDI) waarmee de structuur van een programma onderzocht kan worden, zonder dat de broncode hiervoor nodig is en zonder dat de interne werking van de taal wordt blootgesteld. Er is de mogelijkheid om alle onderdelen van een programma terug te brengen tot hun broncode. De reflectie strekt zich uit over de types, instanties, de stack en het inwerken van adviezen. Voor AspectJ is dit alles volledig ondersteund, met lijnnummering, aspectcompositieinformatie, instantie-afspiegeling, filtering van velden, methoden en stacktraces en ondersteuning voor alle joinpointtypes. Voor JBoss is de ondersteuning gedeeltelijk met aspectcompositie-informatie, filtering voor methoden, velden en stacktraces en ondersteuning voor method joinpoints. Wat veel belangrijker is, is dat er een architectuur ontwikkeld is die nog meer talen en functionaliteiten kan ondersteunen op een uniforme manier. Door de opdeling van de architectuur in een gedeelde front-end en een specifieke back-end, is de inspanning om dit te realiseren minimaal. De meest fundamentele conclusie die we kunnen trekken is dat aspect debugging mogelijk is. Het is zelfs niet nodig om vanaf nul te beginnen. Net zoals aspecttechnologie bouwt op bestaande technologie, zo kan een aspectdebugger steunen op een bestaande debugger. Een andere belangrijke conclusie is dat het eerste probleem van aspectdebugging er een is van modellering; kiezen voor de juiste mate van abstractie. Men kan een objectgeori¨enteerde debugger gebruiken om aspecten te debuggen. Men kan dit versterken met informatie zoals die aangeleverd wordt door de ADB-Backends en AJDT. Of men kan trachten alles te aggregeren en enkel die informatie te tonen die nut heeft. Hierin speelt spiegel-reflectie een belangrijke rol. Enerzijds maakt spiegel-reflectie het model heel expliciet en dwingt een duidelijk en weldoordacht model af. Dit voorkomt het gradueel verslechteren van de onderhoudbaarheid door het onordelijk toevoegen van functionaliteit. Anderzijds bieden de principes van spiegel-reflectie duidelijke richtlijnen voor de constructie van het model. De technische realisatie geeft aan dat de overhead van spiegel-reflectie binnen de perken blijft.
71
HOOFDSTUK 6. CONCLUSIE
72
Als er nog meer tijd beschikbaar zou zijn, zijn er nog vele interessante uitbreidingen denkbaar. Dit niet alleen naar functionaliteit, maar ook naar modulariteit, gebruikersinterfaces, modellering,. . . toe. Een andere mogelijk technologie die hieruit kan ontstaan is een raamwerkafspiegeling. Dit soort mirrors zou niet proberen een taal te modelleren, maar een raamwerk. Deze mirroring zou gebruikt kunnen worden om de toestand van componenten in een toepassingsserver te bestuderen en controleren. Dit zou ook gebruikt kunnen worden om architectuurabstracties te gebruiken als model, zodat er een architectuurafspiegeling onstaat die voor monitoring en validatie gebruikt kan worden. Als we deze gedachtengang doortrekken bekomen we een debugger met configureerbare abstracties, zodat de gebruiker zelf afspiegelingen en metadata kan aggregeren. Hierdoor kunnen er raamwerkmodellen gemaakt worden door gebruikers van de debugger en worden de raamwerkdebuggers toegankelijker.
Bijlage A
ABC-broncode Omdat de brondcode van ABC nogal omvangrijk is vermeld ik hier de belangrijkste klassen. Project Klasse Methode oorspronggebaseerde lijnnummering ABC ABC.main.Main spreadTags adb.lnr.VirtualLineNumberTransformer adb.tagkit.SmapTag DebugInformationPassing aspectinformatie ABC adb.agent.AspectInformationTagger adb.tagkit.AspectTag crosscuttinginformatie ABC adb.agent.CrossCuttingInformationTagger adb.agent.CrossCuttingInformationAggregator adb.agent.JoinPointDescriptor adb.tagkit.AdviceApplicationTag adb.tagkit.CrossCuttingTag
73
Bijlage B
Demotool Om het testen van ADB mogelijk te maken heb ik twee werktuigen gebouwd: een JUnit testharnas voor AJDI en de demotool. Het schrijven en uitvoeren van unittesten bleek al snel ondoenbaar. De dekking van de testen is te klein en het is te veel werk om veel testen te maken. De demotool, langs de andere kant, is wel bruikbaar. Het is een commandline tool waarmee de API gebruikt kan worden. De tool werkt met een centraal object: current. Door reflectie kunnen er operaties op het current object worden toegepast. De returnwaarde van die oproepen wordt de nieuwe current. Op deze manier kan de gebruiker zich door de structuur van het programma bewegen. Demotool is opgebouwd zoals de JBoss-agent: er is een centrale component met daarin een lijst van CommandProviders. Elke provider kan een reeks commando’s aanbieden, afhankelijk van het current object. De help provider stelt de gebruiker in staat om de lijst beschikbare commando’s op te vragen. Als help een andere commando als argument meekrijgt, zal het de helpfunctie van dat commando aanbieden. De demotool heeft een geheugen dat objecten kan opslaan onder een bepaalde naam. Sommige objecten, zoals het VirtualMachine spiegelobject en de shadow master, worden automatisch in het geheugen geplaatst. Omdat het opstarten van programma’s nogal veel configuratie en argumenten vraagt, bestaan er fixtures. Deze klassen bevatten de volledige configuratie om een progamma, JDI en ADB op te starten. De fixtures kunnen met ´e´en enkel commando aangeroepen worden: init. Er is ook een macrosysteem, dat een tekstbestand met commando’s inleest en sequentieel uitvoert. De macro’s kunnen als voorbeeld dienen als u zou willen experimenteren met de demotool. Sommige macro’s zijn zeer gevoelig aan racecondities en kunnen dus soms falen. De macro autoexec wordt bij het opstarten uitgevoerd. De tool kan ook visualisaties tonen. De meest complexe is de graaf van de typestructuur van het programma. Deze grafe wordt geactiveert met het command “graph on”. Er zijn twee entiteiten die altijd opgenomen worden: het current object, als het een klasse of aspect is en het type van de main klasse. Verder wordt de graaf aangevuld door de verbanden te overlopen.
74
BIJLAGE B. DEMOTOOL
type klasse methode joinpoint adviestoepassing advies aspect binding
kleur groen blauw oranje licht blauw rood geel verbinding
75
opschrift naam naam type en location residue naam naam naam
Tabel B.1: kleurcode demotool grafe
Algemeen exit toString list load X store X clear do-X Fixtures init init X break N C M S resume wr shadow Visualisaties echo on graph on location on stack on val on resume on
sluit demotool print current naar console toon alle entries aanwezig in het geheugen haal X uit het geheugen en maak X current sla current op in het geheugen als X wis het geheugen voer methode X uit op current toon alle beschikbare fixtures start de VM voor fixture X maak een methode breekpunt met naam N in klasse C op de methode met naam M en signatuur S hervat de uitvoering hervat de uitvoering en blokeer tot de VM een event geeft. start ADB voor de huidige fixture als current verandert, print current dan uit. toon een graaf van de statische structuur toon de broncodelocatie toon de stacktrace in AJD en JDI vorm toon de waarden van de velden, indien current een instantie is toon een resume knop Tabel B.2: belangrijkste commando’s
BIJLAGE B. DEMOTOOL
Figuur B.1: screenshot van de demotool
76
Bijlage C
Bibliografie [1] AspectJ Development Tools. http://www.eclipse.org/ajdt/. toegang: 23/05/08. Eclipse Foundation, Inc. [2]
P. Avgustinov et al. abc: An extensible AspectJ compiler. 2007.
[3]
P. Avgustinov et al. Building the abc AspectJ compiler with Polyglot and Soot. 2004.
[4]
Gilad Bracha and David Ungar. “Mirrors: design principles for meta-level facilities of object-oriented programming languages.” In: OOPSLA. 2004. Pp. 331–344.
[5]
Marc Eaddy et al. “Debugging Aspect-Enabled Programs”. In: Software Composition. 2007. Pp. 200–215.
[6]
Robert Field (ed.) JSR 45: Debugging Support for Other Languages. http://jcp. org/en/jsr/detail?id=45. Sun Microsystems Inc. 2003.
[7]
Erik Hilsdale and Jim Hugunin. “Advice weaving in AspectJ”. In: AOSD ’04: Proceedings of the 3rd international conference on Aspect-oriented software development. Lancaster, UK: ACM, 2004. Pp. 26–35. ISBN: 1-58113-842-3.
[8] Java Development Tools: Debugging. http://www.eclipse.org/eclipse/debug/ jdt/index.php. toegang: 23/05/08. Eclipse Foundation, Inc. [9] Java Platform Debugger Architecture. 60th ed. toegang: 23/05/08. Sun Microsystems, Inc. [10] JBoss AOP User Guide. http://www.jboss.org/jbossaop/docs/1.5.0.GA/ docs/aspect-framework/userguide/en/html/index.html. toegang: 23/05/08. JBoss, a division of Red Hat. [11]
Gregor Kiczales et al. “Aspect-Oriented Programming”. In: Proceedings European Conference on Object-Oriented Programming. Ed. by Mehmet Ak¸sit and Satoshi Matsuoka. Vol. 1241. Berlin, Heidelberg, and New York: Springer-Verlag, 1997. Pp. 220–242.
77
BIJLAGE C. BIBLIOGRAFIE [12]
78
Tim Lindholm and Frank Yellin. The Java(TM) Virtual Machine Specification (2nd Edition). Prentice Hall PTR, 1999. ISBN: 0201432943. URL: http://java. sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html.
[13]
N. Nystrom, M. Clarkson, and A. Myers. Polyglot: An extensible compiler framework for java. 2003. ´ [14] Guillaume Pothier and Eric Tanter. “Extending omniscient debugging to support aspect-oriented programming”. In: SAC ’08: Proceedings of the 2008 ACM symposium on Applied computing. Fortaleza, Ceara, Brazil: ACM, 2008. Pp. 266–270. ISBN: 978-1-59593-753-7. DOI: http://doi.acm.org/10.1145/ 1363686.1363753. [15] The AspectJ Programming Guide. toegang: 23/05/08. Palo Alto Research Center, Inc. [16]
R. Vall et al. Soot - a Java bytecode optimization framework. 1999.
Bijlage D
Woordenlijst advies Eenheid code die ingevoegd kan worden door de weaver. advies keten Opeenvolging van adviezen die aangrijpen op hetzelfde joinpoint.. adviestoepassing Welbepaalde toepassing van een welbepaald advies op een welbepaald joinpoint.. aspect Coherent geheel van adviezen. Een aspect kan net als een klasse velden en methods hebben, is zelf instanti¨erend en kan ITD’s bevatten. aspect scope Levensduur van een aspectinstantie; zie tabel 1.1. at runtime Tijdens de uitvoering. attribuut Gegevensstructuur die in een classfile verpakt kan worden. Alle attributen die niet expliciet in de JVM-specificatie vermeld worden, dienen door de VM genegeerd te worden.. binding Het vastmaken van een pointcut aan een advies. breekpunt Omschrijving van een gebeurtenis in de uitvoering. De debugger zet de uitvoering stil als de gebeurtenis zich voordoet.. controle flow Met betrekking tot de toestand van de uitvoering en de toestand van de stack. 79
Woordenlijst
80
controleflow De voortgang van de uitvoering.. crosscuttingconcern Vereiste waaraan niet op ´e´en plaats in de broncode voldaan kan worden. doel VM De virtuele machine waarin het gedebugde zich bevindt.. encapsulatie Basisprincipe van mirrors: de debugger moet zijn implementatie verbergen. hardcode Zodanig in het programma ingebed dat het enkel veranderd kan worden door hercompilatie.. hook frame Frame dat in de stacktrace wordt ingevoegd om aan te geven dat de controleflow door een joinpoint loopt. interceptor JBoss AOP-term; aspect met ´e´en advies. intertype declaration Declaratie die het type van een andere klasse aanpast door velden, methodes of interfaces toe te voegen of door de ouderklasse te veranderen. joinpoint Punt waarop een advies kan aangrijpen. pointcut Conditionele expressie waaraan een bepaalde set van joinpoints voldoet. residu Onderdeel van een pointcut dat niet statisch ge¨evalueerd kan worden en dus mee ingeweven moet worden voor runtime evaluatie.. shadow master Onderdeel van ADB verantwoordelijk voor de creatie en het beheer van alle andere spiegelobjecten. stratificatie Basisprincipe van mirrors: de debugger moet losstaan van de taal.
Woordenlijst
81
structurele correspondentie Basisprincipe van mirrors: de structuur van de debugger moet overeenkomen met de structuur van de taal. temporele correspondentie Basisprincipe van mirrors: de api moet zo gelaagd zijn dat er een verschil is tussen statische en dynamische features. type structuur Het geheel van relaties tussen verschillende types onderling en tussen types en hun constituenten.. weaver Softwarecomponent die instaat voor het samenweven van een origineel programma en zijn aspecten.
Bijlage E
Acroniemen ACI AspectCompositie-Informatie; alle informatie over bestaande aspecten, hun adviezen en het inweven van adviezen in een programma. ADB Aspect Debugger; zie paragraaf 3 en paragraaf 3.4.3. AJDI Aspect Java Debugging Interface; zie paragraaf 3 en paragraaf 3.4.1. AJDT AspectJ Development Tools; zie [1]. AO Aspect Oriented: aspectgeori¨enteerd. AODA AO debugging architecture. CCI CrossCuttingInformatie; alle informatie over het inweven van aspecten in een programma; bepaalde back-end interface (zie paragraaf 3.4.2). CFLOW ControleFLOW. ITD Inter Type Declaration. JDI Java Debugging Interface; zie paragraaf 2.3.3.
82
Acroniemen JDWP Java Debug Wire Protocol; zie paragraaf 2.3.2. JVM Java Virtual Machine; zie [12]. JVM-TI Java Virtual Machine Tooling Interface; zie paragraaf 2.3.1.
83