Progetto e realizzazione di uno strumento per la modifica sistematica di codice JavaScript finalizzato al testing

  • 86 views
Uploaded on

Tesi di laurea

Tesi di laurea

More in: Education
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
    Be the first to like this
No Downloads

Views

Total Views
86
On Slideshare
0
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
0
Comments
0
Likes
0

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. ` UNIVERSITA DEGLI STUDI DI TRIESTE ` FACOLTA DI INGEGNERIA Corso di Laurea Triennale in Ingegneria dell’Informazione Tesi di Laurea in Reti di Calcolatori Progetto e realizzazione di uno strumento per la modifica sistematica di codice JavaScript finalizzato al testing LAUREANDO RELATORE Dennis Morello prof. Alberto Bartoli CORRELATORI prof. Eric Medvet dott. Andrea De Lorenzo Anno Accademico 2012/2013
  • 2. Ai miei cari
  • 3. Indice 1 Introduzione 1 2 Scenario 2.1 Scopo del progetto . . . . . . . . 2.2 Language parsing . . . . . . . . . 2.2.1 Grammatiche . . . . . . . 2.3 ANTLR . . . . . . . . . . . . . . 2.3.1 Cos’` ANTLR? . . . . . . e 2.3.2 Grammatiche ANTLR . . 2.3.2.1 Lexer Grammar 2.3.2.2 Parser Grammar 2.3.2.3 Tree Grammar . 2.4 Abstract Syntax Tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 3 3 4 5 5 6 7 8 8 9 3 Progettazione 3.1 Specifiche del problema . . . . . . 3.2 Manipolazione di codice sorgente . 3.2.1 Da codice sorgente ad AST 3.2.2 Manipolazione di AST . . . 3.2.3 Da AST a codice sorgente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 11 11 11 13 15 4 Implementazione 4.1 Impiego di ANTLR . . . . . . . . . . . . . . . 4.1.1 ANTLRWorks . . . . . . . . . . . . . 4.2 Ordinamento di un vettore . . . . . . . . . . 4.2.1 BubbleSort . . . . . . . . . . . . . . . 4.2.1.1 Pseudo-codice di BubbleSort 4.3 Prove eseguite . . . . . . . . . . . . . . . . . . 4.3.1 Generazione casuale di vettori . . . . . 4.3.2 Ordinamento con BubbleSort originale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 17 17 18 19 19 20 21 23 v
  • 4. vi INDICE 4.3.3 4.3.4 4.3.5 Modifica di BubbleSort . . . . . . . . . . . . . . . Ordinamento con BubbleSort modificato . . . . . . Indici di prestazione . . . . . . . . . . . . . . . . . 5 Risultati 5.1 Situazioni possibili . . . . . . . . . 5.1.1 Ordinamento corretto . . . 5.1.2 Ordinamento con mutazione 5.2 Analisi dei risultati . . . . . . . . . 5.2.1 1000 mutazioni . . . . . . . 5.2.2 5000 mutazioni . . . . . . . 5.2.3 Confronto tra i risultati . . . . . . . . . . . . . . . . . . . . . . non equivalente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 24 27 31 31 31 31 32 33 34 36 6 Conclusioni 37 6.1 Sviluppi futuri . . . . . . . . . . . . . . . . . . . . . . . . 37 7 Ringraziamenti 39
  • 5. Capitolo 1 Introduzione Modificare in modo automatico e sistematico il codice sorgente di un programma pu` essere utile in varie applicazioni, quali ad esempio la o correzione automatica di bug. Un modo per raggiungere questo scopo ` quello che prevede un ape proccio evoluzionistico: si generano tante mutazioni casuali al codice sorgente originale e, per ognuna di esse, si valutano i risultati e si sceglie la soluzione migliore. A tal fine, ` necessario mettere a punto uno strumento che autonomae mente esegua modifiche al codice, che si occupi di eseguirlo e che analizzi i risultati conseguiti, confrontandoli con quelli attesi. In questa tesi ` stato progettato e sviluppato uno strumento utile e in questo ambito, in particolare, uno strumento in grado di misurare l’impatto che una modifica al codice sorgente di un programma produce sul suo output, nel senso che verr` illustrato dettagliatamente nei capitoli a successivi. 1
  • 6. Capitolo 2 Scenario Verranno affrontati in questo capitolo alcuni argomenti preliminari e verr` chiarito lo scopo del lavoro di tesi. a 2.1 Scopo del progetto Lo scopo di questo lavoro ` quello di determinare le conseguenze proe dotte da mutazioni successive nella struttura del codice sorgente di una funzione JavaScript. 2.2 Language parsing Prima di affrontare l’argomento che d` il nome a questo paragrafo, ` a e necessario introdurre alcuni concetti. Un linguaggio naturale si definisce a partire da un alfabeto; con i suoi simboli ` possibile formare un insieme di sequenze, dette parole, ma non e tutte le sequenze sono parole del linguaggio naturale. La grammatica del linguaggio fornisce le regole per decidere quali sequenze sono parole del linguaggio. Le parole del linguaggio naturale possono essere raggruppate in insiemi detti frasi, ma anche qui non tutte le frasi appartengono al linguaggio. La sintassi del linguaggio fornisce le regole per decidere quali sequenze sono frasi del linguaggio (si parla di frasi sintatticamente corrette). Un linguaggio di programmazione ` un linguaggio artificiale; per poe terlo definire ` necessario dare ancora qualche definizione. e Dato un insieme finito non vuoto V (chiamato solitamente alfabeto), si definisce universo linguistico su V , e si indica con V ∗, l’insieme delle sequenze finite di lunghezza arbitraria di elementi di V . Un linguaggio L sull’alfabeto V ` allora un sottoinsieme di V ∗. e 3
  • 7. 4 2. Scenario Nel considerare i linguaggi di programmazione, non siamo interessati a tutti i possibili sottoinsiemi di V ∗, ma solo a quelli definibili in maniera finita; questa descrizione pu` essere, per esempio, fornita attraverso una o grammatica (si veda il paragrafo successivo). Una volta definita la grammatica, per language parsing si intende il processo di analisi di stringhe di simboli, in accordo con quanto sancito dalla grammatica. Qui non si intende risalire al significato di una sequenza di caratteri, ma semplicemente riconoscere ed isolare alcune sequenze (che in ambito operativo vengono chiamate token). 2.2.1 Grammatiche In informatica, una grammatica G ` definita da e • Un alfabeto V di simboli terminali • Un alfabeto N di simboli non terminali • Un assioma S ∈ N tale che V ∩ N = ∅ • Un insieme finito di regole sintattiche P del tipo X → α, dove X ∈ N e α ∈ (N ∪ V )∗ e si legge “X produce α” Se in una grammatica vi sono pi` regole aventi la stessa parte sinistra, u ad esempio X → α1 X → α2 ··· X → αn allora esse sono raggruppate usando la convenzione notazionale X → α1 | α1 | · · · | αn La descrizione della grammatica di un linguaggio di programmazione avviene per mezzo di un metalinguaggio formale1 che prende il nome di BNF (Backus-Naur Form, dai nomi dei due studiosi che per primi l’hanno introdotta negli anni ’50) e sue varianti. Il formalismo BNF viene spesso usato non nella sua forma originale, ma utilizzando alcune estensioni che permettono una scrittura pi` concisa u delle grammatiche; si parla allora di EBN F (Extended BNF - non ci si addentrer` nei dettagli, per maggiori informazioni si rimanda all’indirizzo a http://www.cs.cmu.edu/∼pattis/misc/ebnf.pdf). 1 Un metalinguaggio ` un linguaggio usato per parlare di un altro linguaggio. e
  • 8. 5 2.3 2.3.1 ANTLR ANTLR Cos’` ANTLR? e ANTLR2 (ANother Tool for Language Recognition) ` uno strumento e software tipicamente impiegato nella definizione di linguaggi formali e nella costruzione di strumenti che operino su di essi, come traduttori, compilatori o analizzatori di codice sorgente; permette, tra le altre cose, di generare parser di testo e Abstract Syntax Trees (AST) per mezzo della definizione di grammatiche. Una grammatica ANTLR ` un file di testo contenente una successione e di regole che stabiliscano come interpretare il flusso di byte da analizzare (per maggiori dettagli vedasi il paragrafo 2.3.2). Per ogni file di grammatica, ANTLR genera alcuni file sorgenti Java (ma ` possibile, mediante l’inserimento dell’opzione language=x all’intere no della grammatica, generare file sorgenti nello specifico linguaggio x; la lista dei linguaggi supportati da ANTLR 3 ` disponibile all’indirizzo http: e //www.antlr.org/wiki/display/ANTLR3/Code+Generation+Targets/) che verranno poi utilizzati dall’applicazione sviluppata. I file sorgenti generati da ANTLR fanno riferimento a strumenti che riconoscano, analizzino e trasformino dati di input la cui struttura sia stata meticolosamente descritta nella grammatica; tali strumenti sono sostanzialmente i seguenti: Lexer Viene generato a partire dal file contenente la lexer grammar; legge una sequenza di byte (siano essi caratteri o dati binari), li divide in token secondo i pattern specificati (si veda il paragrafo 2.3.2.1) e genera una sequenza di token come output; alcuni di essi (come spazi vuoti o caratteri di punteggiatura) possono essere ignorati ed omessi dall’output. Parser Viene generato a partire dal file contenente la parser grammar; legge una sequenza di token generata dal lexer e li raggruppa in frasi del linguaggio considerato secondo le regole specificate (par. 2.3.2.2); pu` inoltre eseguire alcune azioni aggiuntive, come raffinao re le frasi prodotte, emettere del testo a seconda dei token incontrati (tipico comportamento di un traduttore) o generare un AST che venga poi manipolato per modificare la struttura del linguaggio (` e questo l’uso di ANTLR che ` stato fatto nel presente lavoro di tesi). e Tree Parser Viene generato a partire dal file contenente la tree grammar; visita 2 http://www.antlr.org/
  • 9. 2. Scenario 6 l’albero generato dal parser e, per ogni nodo incontrato, esegue le operazioni eventualmente descritte nella relativa grammatica (par. 2.3.2.3). Tali operazioni possono essere principalmente output di testo o manipolazione di altri nodi. 2.3.2 Grammatiche ANTLR Come accennato nei paragrafi 2.2.1 e 2.3.1, una grammatica ANTLR ` una sequenza di regole che descrivono ciascuna un sottoinsieme della e sintassi del linguaggio; assieme ad esse, una grammatica contiene il suo tipo (lexer grammar o tree grammar ), il suo nome, alcune opzioni, ed eventualmente altre sezioni come la specifica di token, attributi e azioni: grammarType grammar name; <<options>> <<tokens>> <<attributes>> <<actions>> rule1 : ... | ... | ... ; ... ruleN : ... | ... | ... ; La sintassi con cui vengono specificate le regole in ANTLR riprende la notazione Extended Backus-Naur Form (EBNF, si veda paragrafo 2.2.1). In analogia a quanto avviene in Java nella corrispondenza tra il nome di una classe e quello del file che la contiene, in ANTLR il nome della grammatica deve essere uguale al nome del file in cui ` memorizzata; i e file di grammatica hanno usualmente estensione g. La regola da cui inizia il parsing ` chiamata regola di partenza (ogni e regola pu` esserlo); ogni regola consiste a sua volta in una o pi` alternao u tive, che altro non sono che sotto-regole; l’ordine con cui si susseguono ` e ininfluente. A titolo di esempio, una regola chiamata variableDefinition potrebbe avere due alternative, la prima per una semplice definizione di variabile (ad esempio var x;) e la seconda per una definizione con inizializzazione (var x=12;): variableDefinition : declaration | declarationWithInitialization ; Da una generica grammatica T contenuta nel file T.g (supponendo che il linguaggio di programmazione scelto per lo sviluppo del progetto
  • 10. 7 ANTLR sia Java), ANTLR genera i file sorgenti TLexer.java e TParser.java i quali contengono, rispettivamente, il lexer ed il parser per il proprio linguaggio; se sono stati specificati dei token immaginari (ridenominazione di sequenze di caratteri utilizzabili all’interno della grammatica, come ad esempio EQUALS=’=’;), essi vengono memorizzati nel file T.tokens. Nei paragrafi seguenti si analizzeranno le tre tipologie di grammatiche definibili in ANTLR. 2.3.2.1 Lexer Grammar Scopo della lexer grammar ` quello di definire come debbano essere coe struiti i token a partire dall’analisi in prima battuta dell’input assegnato. Un token `, nell’accezione tipica, un insieme di caratteri che rispetti e una particolare regola definita nella lexer grammar. Ad esempio, volendo riconoscere tutti i numeri interi senza segno di lunghezza arbitraria che abbiano come prima cifra un carattere diverso dallo zero, potremmo definire la regola seguente: INT : (’1’..’9’)(’0’..’9’)* ; dove il carattere * indica che il gruppo (’0’..’9’) pu` comparire zero o o pi` volte. u Una volta definite le regole3 per il riconoscimento dei token desiderati, ogni successione di caratteri che abbia senso per la lexer grammar dar` a origine ad un token; se tra le regole definite vi fosse la regola INT descritta nell’esempio sopra, la sequenza di caratteri 0x032C produrrebbe il token 32. Fatte queste premesse, ` possibile affermare che la lexer grammar ` e e una sequenza di modi di riconoscere i token d’interesse: grammar T; ... RULE_1 : ... | ... | ... ; ... RULE_N : ... | ... | ... ; La costruzione dei token ` indispensabile per il passaggio successivo, e quello che vede come protagonista il parser. Terminata la stesura di questa grammatica, ANTLR genera il file TLexer.java, che potr` poi essere utilizzato nel progetto che si sta a sviluppando (T ` il nome della grammatica dell’esempio). e 3 Per convenzione, i nomi delle regole della lexer grammar sono tutte maiuscole.
  • 11. 8 2. Scenario 2.3.2.2 Parser Grammar Una parser grammar ` un insieme di regole necessarie al parser per rie conoscere particolari sequenze di token generate dal lexer; ad esempio, ` possibile riconoscere lo statement return di JavaScript specificando la e regola returnStatement : ’return’ expr ’;’ ; dove expr ` un’opportuna regola. e Valgono le stesse considerazioni fatte per la lexer grammar relativamente alle alternative di una regola. Specificando l’opzione output=AST nella sezione options della grammatica, ` possibile generare l’AST associato al particolare input; per e generare un nodo dell’AST per la regola returnStatement ` sufficiente e modificarne la definizione come segue: returnStatement : ’return’ expr ’;’ -> ^(’return’ expr) ; Tale regola genera un nodo che ha per padre la stringa "return" e per figlio il sotto-albero expr. Per i dettagli su come vengano definiti gli AST in ANTLR si veda il paragrafo ad essi dedicato. Da notare che lexer grammar e parser grammar sono di norma contenute in un unico file. Al termine della stesura della parser grammar, ` possibile utilizzare e ANTLR per generare il file TParser.java. 2.3.2.3 Tree Grammar La tree grammar ` un file contenente una sequenza di regole necessarie e a riconoscere una determinata struttura di un AST. In particolare, ciascuna regola specifica la conformazione di un sottoalbero costituito da un padre e uno o pi` figli (i quali possono essere a u loro volta altri padri o foglie). Ad esempio, la regola seguente riconosce il sotto-albero che rappresenta lo statement return: returnNode : ^(’return’ expr) ; ` E poi possibile eseguire operazioni specifiche in corrispondenza di un nodo che rispetti una determinata regola (ad esempio, emettere del testo
  • 12. 9 Abstract Syntax Tree come output) inserendo dopo la definizione di un’alternativa un blocco di codice Java (o comunque nel linguaggio target) racchiuso tra partentesi graffe: returnNode : ^(’return’ expr) { System.out.println("Nodo return"); } ; Questa regola, ogni qualvolta si incontri un nodo return, produrr` una a stampa a video della stringa "Nodo return". Una volta scritte e combinate tra loro tutte le regole necessarie, se il file contenente la grammatica si chiamasse TWalker.g, ANTLR produrrebbe il file TWalker.java, necessario al riconoscimento effettivo degli AST la cui struttura ` conforme alla grammatica; questo file rappresenta e quello che in precedenza ` stato chiamato tree parser. e 2.4 Abstract Syntax Tree Un Abstract Syntax Tree ` una rappresentazione ad albero della struttura e sintattica di un codice sorgente scritto in un particolare linguaggio di programmazione. Ogni nodo dell’albero denota un costrutto presente nel codice sorgente, sebbene non ne rappresenti fedelmente la sintassi, ma solo le parti essenziali. Ad esempio, le parentesi graffe che racchiudono un blocco di codice nei linguaggi C-like sono implicite in un AST, e non vi vengono rappresentate. A titolo esemplificativo, sia dato il seguente codice sorgente JavaScript: 1 2 3 4 5 6 7 8 9 function prova (a , b ) { var c ; if ( a +b >9) { c =2; } else { c =0; } return c ; } Il corrispondente AST, generato a partire dalla grammatica TinyJavaScript, ` quello riportato in figura 2.1. e
  • 13. 2. Scenario 10 Figura 2.1: AST corrispondente al codice JavaScript dell’esempio. Il nodo nil rappresenta la radice4 dell’albero, il nodo SLIST denota una successione di statement; tutti gli altri nodi hanno un significato immediato. 4 Secondo la convenzione adottata da ANTLR.
  • 14. Capitolo 3 Progettazione La fase progettuale mette in chiaro come si sia scelto di affrontare il passaggio da un codice JavaScript ad un altro che differisca dal primo solo per una singola modifica, nel senso che verr` definito pi` avanti. a u 3.1 Specifiche del problema Il problema affrontato prevede di analizzare una singola funzione JavaScript (costituita da un insieme di statement di numerosit` arbitraria) a e di scambiare le posizioni di due parti di codice scelte “a caso”; nei paragrafi successivi si tratteranno approfonditamente i dettagli di queste operazioni. 3.2 Manipolazione di codice sorgente Le manipolazioni nel codice sorgente che verranno qui considerate consistono in scambi casuali di nodi dell’AST corrispondente. Per fare ci` ` necessario prima analizzare il codice, costruirne il reoe lativo AST, eseguire gli scambi e visitare l’AST mutato per generare il nuovo codice sorgente. 3.2.1 Da codice sorgente ad AST Il passaggio da codice sorgente ad AST richiede come prerequisito la stesura della grammatica necessaria al riconoscimento dei costrutti JavaScript; a tale scopo ` stata scritta la grammatica TinyJavaScript nel file e TinyJavaScript.g utilizzando ANTLRWorks (par. 4.1.1); analizziamo brevemente il suo contenuto. 11
  • 15. 12 3. Progettazione La prima sezione, denominata options, contiene dei settaggi che ANTLR utilizzer` per generare il lexer ed il parser (par. 2.3.1): a 1 2 3 4 5 options { output = AST ; ASTLabelType = CommonTree ; backtrack = true ; } In particolare, l’opzione output=AST; dice ad ANTLR che l’output del parser sia l’AST corrispondente al codice sorgente ispezionato. Segue poi la definizione di alcuni token immaginari (par. 2.3.2) che verranno impiegati all’interno della grammatica: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 tokens { VAR ; FUNC ; ARG ; NEW ; CALL ; INDEX ; SLIST ; FOR ; COND ; ITERATE ; WHILE_DO ; DO_WHILE ; IF ; THEN ; ELSE ; SWITCH ; DEFAULT ; RETURN ; } // // // // // // // // // // // // // // // // // // variable definition function definition formal argument new object function call array index statement list for cycle condition in for cycle iteration in for cycle while cycle do .. while cycle if statement then block else block switch statement switch default case return statement TinyJavaScript.g contiene sia le regole del lexer (normalmente denotate con lettere maiuscole) sia quelle del parser. Prendiamo in esame una delle regole del lexer: 1 2 3 ID : ( ’a ’.. ’ z ’| ’A ’.. ’ Z ’| ’_ ’) ( ’a ’.. ’ z ’| ’A ’.. ’ Z ’| ’0 ’.. ’9 ’| ’ _ ’) * ; Questa regola consente di riconoscere tutte le stringhe di caratteri che inizino con una lettera minuscola o maiuscola oppure col carattere under-
  • 16. 13 Manipolazione di codice sorgente score e a cui seguono, eventualmente (l’asterisco al termine del secondo gruppo significa appunto che quel gruppo ` opzionale), altre lettere mae iuscole o minuscole oppure numeri oppure caratteri underscore. Qualsiasi altro carattere (punteggiatura, parentesi, ...) non viene preso in considerazione da questa regola. Una regola del parser ` invece ad esempio questa: e 1 2 3 4 ifStat : ’if ’ ’( ’ cond = condStat ’) ’ mthen = stat ( ’ else ’ melse = stat ) ? -> ^( IF ^( COND $cond ) ^( THEN $mthen ) ^( ELSE $melse ?) ) ; Si tratta, com’` intuibile, della regola per il riconoscimento di costrutti e if-then-else, con l’accorgimento che il ramo else pu` essere assente. o I caratteri “->” indicano che l’AST generato deve avere la struttura specificata come segue: il nodo padre ` IF seguito dai figli COND, THEN e e ELSE. Ciascuno di essi ` a loro volta padre di altri sotto-alberi, eccetto e il nodo ELSE, il quale pu` non avere figli se nel corrispondente codice o JavaScript non ` presente il blocco else. e Il parsing del codice inizia dalla regola program mediante le istruzioni 1 2 3 4 5 6 ANTLRFileStream stream = new ANTLRFileStream ( " BubbleSort . js " ) ; lexer = new T i n yJ a v aS c r ip t L ex e r ( stream ) ; tokens = new Co mmonTo kenStr eam ( lexer ) ; T i n y J a v a S c r i p t P a r s e r parser = new T i n y J a v a S c r i p t P a r s e r ( tokens ) ; T i n y J a v a S c r i p t P a r s e r . program_return r = parser . program () ; tree = ( CommonTree ) r . getTree () ; Una volta terminato il parsing, la variabile tree contiene l’AST generato. 3.2.2 Manipolazione di AST La manipolazione dell’AST inizia specificando quanti scambi di nodi devono essere eseguiti; supponiamo siano in numero n. Si esegue dunque un ciclo for avente n iterazioni; ad ogni iterazione si compiono le operazioni seguenti:
  • 17. 3. Progettazione 14 • Si inizia un ciclo while, eseguito fintantoch´ non venga prodotta e una mutazione valida; ogni iterazione di questo ciclo prevede le seguenti istruzioni: – Vengono generati due numeri casuali interi non negativi k1 e k2 che assumono come valore minimo 1 e massimo p, con p numero totale di sotto-alberi; tali numeri costituiscono gli indici dei nodi che verranno selezionati – Si copia l’AST attuale per consentirne il ripristino in caso di scambio non valido – Si esegue lo scambio del nodo k1 col nodo k2 – Si verifica la legittimit` dello scambio; se lo ` si esce dal a e ciclo while e si procede con la successiva iterazione del ciclo for esterno, altrimenti si ripristina l’AST alla situazione precedente allo scambio e si ripetono i passi del ciclo while • A mutazione avvenuta, si visita l’AST per generarne il codice sorgente JavaScript (paragrafo successivo) e viene scritto il risultato su un file avente nome ModBubble*.js, con * numero progressivo che va da 0 a n − 1 • Al termine del ciclo for la variabile tree contiene la versione mutata della funzione JavaScript originale Il codice Java che implementa quanto descritto ` e 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 CommonTree sree ; int index = 0; for ( int i =0; i <n ; i ++) { boolean changed = false ; while (! changed ) { count = 0; trees . clear () ; visitTree ( tree ) ; k1 = random . nextInt ( count ) ; k2 = random . nextInt ( count ) ; sree = copyTree ( tree ) ; swapNodes ( trees . get ( k1 ) , trees . get ( k2 ) ) ; changed = ( sree . toStringTree () . equals ( tree . toStringTree () ) ? false : true ) ; } writeCodeToFile ( " ModBubble " + index + " . js " ) ; index ++; }
  • 18. 15 Manipolazione di codice sorgente Si noti che la presenza del ciclo while assicuri che vengano eseguiti esattamente n scambi. La funzione visitTree(t) ispeziona l’AST per calcolarne il numero totale di sotto-alberi e, per completezza, li inserisce nell’ArrayList chiamato trees (definito all’inizio della classe): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static void visitTree ( CommonTree t ) { if ( t . getChildCount () > 0) { for ( int i =0; i <t . getChildCount () ; i ++) { if ( down ) { count ++; trees . add ( t ) ; } down = true ; visitTree (( CommonTree ) t . getChild ( i ) ) ; } } else { count ++; trees . add ( t ) ; down = false ; } } Il metodo writeCodeToFile(s) verr` discusso nel paragrafo che sea gue. 3.2.3 Da AST a codice sorgente Il passaggio da AST a codice sorgente avviene visitando l’albero utilizzando l’algoritmo Depth-First Search 1 . Si parte dal nodo denominato program e si ispezionano i nodi figli, da sinistra verso destra. Ad ogni visita di un nodo si stampa il suo contenuto e si passa all’ispezione dei figli; al raggiungimento del nodo foglia per quel sotto-albero, si passa ad ispezionare il nodo fratello successivo a quello che si era preso in esame. Si procede in questo modo fino a quando tutti i nodi dell’AST siano stati ispezionati. Una volta terminata la generazione del codice JavaScript, si scrive il risultato su un file il cui nome viene specificato dal parametro del metodo writeCodeToFile(s). 1 2 public static void writeCodeToFile ( String name ) { C o de S t ri n g Ge n e ra t o r cg = new C o de S t ri n g Ge n e ra t o r ( tree ) ; 1 http://en.wikipedia.org/wiki/Depth-first search
  • 19. 3. Progettazione cg . visitTree ( tree ) ; cg . writeCodeToFile ( name ) ; 3 4 5 16 } La classe CodeStringGenerator contiene tutti i metodi necessari alla generazione del codice a partire dall’ispezione dell’AST; tra di essi vi ` e writeCodeToFile(s), il cui listato ` riportato qui sotto: e 1 2 3 4 5 6 7 8 9 10 11 public boolean writeCodeToFile ( String fileName ) { if ( code . length () ==0) visitTree ( tree ) ; try { FileWriter fw = new FileWriter ( fileName , false ) ; fw . write ( code ) ; fw . close () ; return true ; } catch ( Exception e ) { return false ; } } Come si vede, se la scrittura ` andata a buon fine, il metodo restituisce e il valore booleano true, altrimenti restituisce false.
  • 20. Capitolo 4 Implementazione 4.1 Impiego di ANTLR Il tipico impiego di ANTLR consiste nelle seguenti tre macro-operazioni: 1. Uso del lexer e del parser in serie per analizzare il flusso di dati in ingresso, controllarne la conformit` alle grammatiche sviluppate e, a se non sono stati incontrati errori, generare una rappresentazione intermedia come l’AST; 2. Opzionalmente, modificare l’AST per mezzo di uno o pi` Tree u Parser (par. 2.3.1); 3. Produrre l’output finale utilizzando un Tree Parser, ad esempio per generare codice sorgente modificato. 4.1.1 ANTLRWorks ANTLRWorks (http://www.antlr3.org/works/) ` un IDE per la stesura e di grammatiche ANTLR scritto in Java, originariamente sviluppato da Jean Bovet in collaborazione con il prof. Terence Parr. Sebbene attualmente sia disponibile la versione 2.1 che supporta pienamente le specifiche di ANTLR 4, si ` qui preferito utilizzare la versioe ne 1.5 in quanto ` stata specificatamente studiata per ANTLR 3.x (la e versione di ANTLR utilizzata nel progetto ` la 3.5). e ANTLRWorks fornisce funzionalit` quali evidenziazione della sintasa si, creazione dell’albero delle regole, ricerca mediante espressioni regolari, refactoring, suggerimenti in tempo reale, generazione del diagramma decisionale, interprete interattivo per il testing delle regole, visualizzazione grafica di AST, esportazione su file dei diagrammi generati, debugger e molto altro ancora. 17
  • 21. 4. Implementazione 18 Seguono alcuni screenshot dell’applicazione: Figura 4.1: Scrittura di regole ANTLR. Figura 4.2: Funzionalit` di debugging. a 4.2 Ordinamento di un vettore Come algoritmo su cui eseguire le manipolazioni che verranno pi` avanti u descritte, ` stato scelto un algoritmo di ordinamento di un vettore; nello e specifico, ` stato considerato BubbleSort. e
  • 22. 19 4.2.1 Ordinamento di un vettore BubbleSort L’algoritmo BubbleSort (letteralmente “ordinamento a bolla”) funziona in modo piuttosto intuitivo: dato un vettore di n numeri, si inizia a confrontare il primo elemento con il successivo; se il primo risulta essere maggiore del secondo allora avviene uno scambio di posizione, altrimenti no. Si procede poi confrontando il secondo con il terzo elemento, si riesegue il confronto ed eventualmente si invertono le posizioni; si ripete questo procedimento sino al confronto tra il penultimo e l’ultimo elemento (dunque n − 1 volte), dopodich´ si ricomincia dalla prima coppia del e vettore. Si continuano ad eseguire confronti lungo tutta la lunghezza del vettore sino a quando non sono stati eseguiti n(n − 1) confronti totali. Per meglio spiegarne l’idea, si ricorre ad un esempio: si immagini di avere in mano n carte da gioco francesi e si supponga di volerle ordinare. A tale scopo, si confronti la prima carta con la seconda: se la prima ` e maggiore della seconda allora la prima carta prende il posto della seconda e viceversa, altrimenti non avviene alcuno scambio. Si proceda poi con la seconda e la terza carta e si ripeta il confronto; l’intero procedimento va ripetuto sino all’ultima carta in mano. A questo punto si avr` una successione di carte non ancora completamente ordinata; per a raggiungere una situazione di ordinamento globale ` necessario rieseguire e il procedimento descritto sino a quando la mano di carte risulti ordinata. Un limite superiore al numero di confronti che ` necessario eseguire per e ordinare n carte con questo metodo ` appunto n(n − 1). e 4.2.1.1 Pseudo-codice di BubbleSort Si riporta di seguito lo pseudo-codice dell’algoritmo BubbleSort: bubblesort(v): u = v n = length(v) for i=1 to n do for j=1 to n-1 do if u[j]>u[j+1] then k = u[j] u[j] = u[j+1] u[j+1] = k return u Come si nota, tale algoritmo non modifica il vettore v originale, ma produce una sua copia ordinata. In realt`, nella presente tesi ` stata utilizzata una versione modifia e cata della versione elementare di BubbleSort qui esposta: in effetti, non
  • 23. 4. Implementazione 20 sempre ` necessario eseguire n(n − 1) iterazioni prima di giungere all’ore dinamento completo; se si introduce una variabile sentinella che segnali l’assenza di scambi in un’iterazione del secondo ciclo for (e dunque si ` e giunti prematuramente all’ordinamento), ` possibile interrompere i cicli e e restituire direttamente il vettore u. Lo pseudo-codice di questa versione ottimizzata di BubbleSort ` il e seguente: smart_bubblesort(v): u = v n = length(v) for i=1 to n do flag = false for j=1 to n-1 do if u[j]>u[j+1] then k = u[j] u[j] = u[j+1] u[j+1] = k flag = true if flag==false then return u return u 4.3 Prove eseguite Il linguaggio di programmazione scelto per l’implementazione di BubbleSort ` JavaScript. e Nel file BubbleSort.js ` stato memorizzato il seguente codice: e 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function bubbleSort ( array ) { var ordered = copyArray ( array ) ; for ( var i =0; i < ordered . length ; i ++) { var flag = false ; for ( var j =0; j < ordered . length -1; j ++) { if ( ordered [ j ]) { var k = ordered [ j ]; ordered [ j ] = ordered [ j +1]; ordered [ j +1] = k ; flag = true ; } } if (! flag ) { break ; } }
  • 24. 21 return ordered ; 17 18 Prove eseguite } ` E questo il file di partenza che viene modificato dal cuore del programma (par. 3.2.2). Per interfacciarsi alle funzionalit` richieste ` stata creata una pagina a e HTML divisa in varie sezioni. La prima sezione contiene un form attraverso cui specificare le caratteristiche dei vettori da generare casualmente; per casualmente si intende che ciascuno di essi ` costituito da un numero casuale di elementi come presi tra un valore minimo e uno massimo specificati dall’utente e che ogni elemento dei vettori generati ` scelto a caso, con il vincolo che non e vi siano ripetizioni di elementi all’interno dello stesso vettore. Figura 4.3: Form di generazione casuale dei vettori. La seconda sezione consente di ordinare i vettori generati con l’algoritmo BubbleSort originale, ovvero non ancora mutato. La terza sezione contiene una drop area in cui trascinare i file contenenti le versioni mutate di BubbleSort. Infine, la quarta ed ultima sezione contiene alcuni grafici relativi alle performance degli algoritmi eseguiti; i dettagli verranno discussi nel capitolo relativo all’analisi dei risultati. 4.3.1 Generazione casuale di vettori Il primo step per la valutazione delle prestazioni degli algoritmi prodotti consiste nella generazione dei vettori su cui poi lavorare. Una volta specificati il numero di vettori da produrre, il numero minimo e massimo degli elementi di ciascuno di essi e il massimo valore che ogni elemento pu` assumere (inteso come valore assoluto: specio ficando 100 come valore massimo, i valori possibili saranno contenuti
  • 25. 4. Implementazione 22 Figura 4.4: Output dei vettori generati. Figura 4.5: Ordinamento dei vettori con BubbleSort non modificato. Figura 4.6: Inserimento dei file contenenti versioni mutate di BubbleSort. nell’intervallo discreto [−100, 100]), la funzione che genera i vettori ` la e seguente:
  • 26. 23 1 2 3 4 5 6 7 Prove eseguite function genRandMatrix ( rows , colsMin , colsMax , valMax ) { var matrix = new Array ( rows ) ; for ( var i =0; i < rows ; i ++) { matrix [ i ] = genRandArray ( colsMin , colsMax , valMax ) ; } return matrix ; } La funzione genRandArray(a,b,c) produce un vettore che ha tra a e b elementi e ciascuno di essi ` compreso tra -c e +c; ` cos` costituita: e e ı 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function genRandArray ( colsMin , colsMax , valMax ) { var l = posRand ( colsMin , colsMax ) ; var array = new Array ( l ) ; var rand ; var present ; for ( var i =0; i <l ; i ++) { present = true ; rand = random ( valMax ) ; while ( present && i > 0) { for ( var j =0; j <i ; j ++) { if ( array [ j ]== rand ) { rand = random ( valMax ) ; break ; } else { if ( j == i -1) present = false ; } } } array [ i ] = rand ; } return array ; } La funzione posRand(a,b) genera un numero intero casuale compreso tra a e b; il ciclo while e la variabile booleana present sono necessarie per assicurarsi che non vi siano elementi ripetuti nel vettore generato; la funzione random(a) genera un numero intero casuale compreso tra -a e +a. 4.3.2 Ordinamento con BubbleSort originale Dopo che i vettori siano stati generati, si procede al loro ordinamento con la versione originale di BubbleSort; si ottiene come risultato una copia
  • 27. 4. Implementazione 24 correttamente ordinata dei vettori di partenza. Si noti che i vettori originali non vengono eliminati: dovranno infatti essere ordinati dalle versioni modificate di BubbleSort. 4.3.3 Modifica di BubbleSort Partendo dal file BubbleSort.js che contiene il codice JavaScript di BubbleSort originale, si esegue il programma sviluppato per la generazione di n versioni mutate di BubbleSort; il funzionamento di tale programma ` sintetizzabile nei seguenti passaggi: e • Analizza il file BubbleSort.js e dal codice sorgente genera l’AST (par. 3.2.1) • Esegue n volte quanto segue: – Sceglie a caso due nodi dell’albero – Verifica se lo scambio ` fattibile (i due nodi non devono essere e l’uno l’antenato dell’altro e non devono essere lo stesso nodo) – Esegue un tentativo di scambio; se lo scambio ` andato a e buon fine (nel senso che il nuovo albero ` conforme alla tree e grammar) allora si procede al prossimo scambio, altrimenti si ripetono i due passi precedenti fintantoch´ non venga eseguito e uno scambio valido (par. 3.2.2) – L’AST mutato viene ritrasformato in codice JavaScript ed il risultato viene scritto nel file ModBubble*.js, dove al posto del carattere * vi ` un numero progressivo compreso tra 0 e e n − 1 (par. 3.2.3) • Al termine del ciclo, tutte le mutazioni sono state scritte su file; il programma ` stato studiato appositamente per avere la certezza e che in ogni caso vi siano esattamente n mutazioni Il risultato dell’esecuzione del programma appena descritto ` una suce cessione di n file, ciascuno contenente una funzione BubbleSort che differisce da quella contenuta nel file immediatamente precedente e successivo solamente per una modifica al corrispondente AST. 4.3.4 Ordinamento con BubbleSort modificato L’ordinamento dei vettori con le versioni mutate di BubbleSort inizia trascinando tutti i file ModBubble*.js all’interno della drop area nella pagina HTML descritta sopra.
  • 28. 25 Prove eseguite La drop area di figura 4.6 ` stata ottenuta mediante l’inserimento e di due tag <div> annidati; il primo funge da contenitore, il secondo ` e associato agli eventi necessari alla cattura dei file: 1 2 3 < div id = " drop_container " > < div id = " drop_zone " > Drop Algorithms Here ! </ div > </ div > Gli eventi necessari alla cattura dei file sono due: handleDragOver e handleFileSelect. Il primo serve a rendere l’effetto visivo della disponibilit` ad accogliere file durante il loro trascinamento nella drop area, a il secondo si occupa effettivamente di leggerli e di eseguire le versioni di BubbleSort in essi contenute. L’associazione degli eventi alla drop area avviene per mezzo delle istruzioni sottostanti: 1 2 3 var dropZone = document . getElementById ( ’ drop_zone ’) ; dropZone . addEventListener ( ’ dragover ’ , handleDragOver , false ) ; dropZone . addEventListener ( ’ drop ’ , handleFileSelect , false ) ; Il contenuto della funzione handleDragOver() ` il seguente: e 1 2 3 4 5 function handleDragOver ( evt ) { evt . stopPropagation () ; evt . preventDefault () ; evt . dataTransfer . dropEffect = ’ copy ’; } Il contenuto della funzione handleFileSelect() ` invece questo: e 1 2 3 function handleFileSelect ( evt ) { evt . stopPropagation () ; evt . preventDefault () ; 4 5 6 7 8 9 var files = evt . dataTransfer . files ; perfs = new Array ( files . length ) ; bubbles = new Array ( files . length ) ; fnames = new Array ( files . length ) ; w = 0; 10 11 12 strPerfIndexes = " <h3 > PERFORMANCE INDEXES </ h3 > " ; var txtPerfMatrix = document . getElementById ( " txtPerfMatrix " ) ;
  • 29. 4. Implementazione 26 setHidden ( txtPerfMatrix , false ) ; 13 14 for ( var i =0 , f ; f = files [ i ]; i ++) { if (! f . type . match ( ’ javascript ’) ) { continue ; } var reader = new FileReader () ; fnames [ i ] = f . name ; reader . onload = loaded ; reader . readAsText ( f ) ; } 15 16 17 18 19 20 21 22 23 24 } Brevemente, la variabile files ` di tipo FileList, un vettore di e oggetti File i quali sono a loro volta interfacce ai file selezionati. Queste funzionalit` sono messe a disposizione dalla File API di HTML5 (http: a //www.w3.org/TR/FileAPI/). All’interno del ciclo for viene innanzitutto verificato che il file attuale sia effettivamente un sorgente JavaScript; se lo ` si procede con le e istruzioni successive, altrimenti si passa ad analizzare il prossimo file (se c’`). e L’oggetto reader, istanziazione di FileReader, mette a disposizione la funzionalit` di lettura del contenuto del file. a L’istruzione reader.onload = loaded; serve per lanciare la funzione loaded() una volta chiamata reader.readAsText(f);. La funzione loaded() ` proprio quella che estrae il contenuto del file, e lo valuta, ne crea un oggetto function e la applica ai vettori da ordinare: 1 2 3 function loaded ( evt ) { var fileString = evt . target . result ; bubbles [ w ] = fileString ; 4 5 6 7 8 9 10 11 12 13 14 15 16 try { newBubbleSort = eval ( " ( " + fileString + " ) " ) ; ordermatrix = matrixOrder ( omatrix ) ; var perfArrays = new Array ( rows ) ; for ( var j =0; j < rows ; j ++) { perfArrays [ j ]= perfIndexArray ( bubblematrix [ j ] , ordermatrix [ j ]) ; } perfs [ w ] = globalPerfIndex ( perfArrays ) ; } catch ( e ) { perfs [ w ] = null ; } finally { strPerfIndexes += fnames [ w ]+ " : <b > " + perfs [ w ]+ " </b > < br / > " ;
  • 30. 27 w ++; txtPerfMatrix . innerHTML = strPerfIndexes ; 17 18 } 19 20 Prove eseguite } Viene poi calcolato l’indice di prestazione (par. 4.3.5) relativo a quella mutazione di BubbleSort e viene scritto sulla pagina HTML (riga 18) sotto la drop area. Figura 4.7: Output degli indici di prestazione. 4.3.5 Indici di prestazione L’indice di prestazione tra due vettori costituiti dagli stessi elementi ` qui e definito come media aritmetica delle reciproche distanze tra la posizione occupata dall’i-esimo elemento nel primo vettore e la posizione occupata dallo stesso elemento nel secondo vettore: k= 1 n d(i, j) i,j Supponendo che il primo vettore sia v = [3, 1, 2] e che il secondo sia u = [1, 2, 3], si procede al calcolo del vettore w, in cui l’i-esimo elemento rappresenta il numero di posizioni per cui differisce v[i] dallo stesso elemento all’interno di u. Nel caso in esame, il numero 3 occupa la prima posizione in v e in u occupa la terza posizione; la distanza vale dunque w[1] = |3 − 1| = 2. Completando il calcolo delle distanze ai due elementi rimanenti, si ottiene w = [2, 1, 1], il quale genera un indice di prestazione k pari a (2 + 1 + 1)/3 = 4/3 1, 33.
  • 31. 4. Implementazione 28 Si noti che nel caso in cui k = 0 allora i due vettori sono ordinati allo stesso modo (e quindi vi ` massima similitudine), mentre all’aumentare e di k aumenta il grado di mutazione nelle posizioni degli elementi. Detta n la lunghezza dei vettori da confrontare, un limite superiore per k ` k = n/2 se n ` pari, mentre ` k = 2 (n − l)/n con l = (n−1), (n− e e e 3), · · · , 2 se n ` dispari (situazioni che corrisponderebbero all’inversione e totale delle posizioni degli elementi tra il primo ed il secondo vettore); da queste considerazioni, si pu` affermare che o 0≤k≤ n 2 (n − l)/n se n pari se n dispari Non vi sono situazioni di ambiguit` nel calcolo dell’indice k poich´, a e per come sono stati generati i vettori di partenza (par. 4.3.1), nessuno di essi contiene elementi ripetuti. Ora che ` stato definito l’indice di prestazione relativo ad una singola e coppia di vettori, si pu` introdurre la definizione di indice globale di o prestazione, denotato con K: esso rappresenta la media aritmetica degli indici di prestazioni kr relativi a tutti i vettori generati: 1 K= n n kr r=1 Questo indice ` significativo in quanto, data una mutazione τ di e BubbleSort originale, K rappresenta sinteticamente la correttezza del risultato rispetto a quello desiderato. Diremo che τ ` una mutazione equivalente se, per ogni vettore d’ine gresso, il risultato prodotto ` uguale a quello di BubbleSort originale. In e tal caso l’indice globale di prestazione Kτ associato a τ vale 0. Supponiamo, per maggiore chiarezza, che la mutazione τ sia tale che i vettori da esso generati siano ordinati secondo un ordinamento decrescente1 (ovvero il contrario di quanto faccia BubbleSort originale). Partendo dai vettori x1 = [1, −3, −6] x2 = [1, 0, 2] x3 = [−6, 7] l’esecuzione di BubbleSort originale su di essi produrrebbe i vettori y1 = [−6, −3, 1] y2 = [0, 1, 2] y3 = [−6, 7] mentre l’esecuzione di τ produrrebbe z1 = [1, −3, −6] 1 z2 = [2, 1, 0] z3 = [7, −6] Si tratta di una situazione prettamente esemplificativa, in quanto una simile mutazione ` impossibile nel presente lavoro (qui si considerano mutazioni costituite da e scambi di nodi dell’AST).
  • 32. 29 Prove eseguite Per valutare la “bont`” di τ (nel senso di cui sopra) si procede dunque a al calcolo degli indici kr : k1 = (2+0+2)/3 1, 33 k2 = (2+0+2)/3 1, 33 k3 = (1+1)/2 = 1 e dunque l’indice di prestazione globale associato a τ vale Kτ = (k1 + k2 + k3 )/3 = (1, 33 + 1, 33 + 1)/3 1, 22 Pu` capitare, e anzi ` una situazione piuttosto frequente, che per o e qualche τ il risultato della sua esecuzione non sia un vettore: a tal proposito si veda il paragrafo 5.1.2. L’algoritmo per il calcolo dei kr ` il seguente: e 1 2 3 function perfIndexArray ( a1 , a2 ) { return Math . round ( sumArray ( rankArray ( a1 , a2 ) ) / a1 . length *100) /100; } dove sumArray(v) restituisce la somma degli elementi del vettore v, mentre la funzione rankArray(a,b) ` definita in questo modo: e 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function rankArray ( a1 , a2 ) { var matched ; var distances = new Array ( a1 . length ) ; for ( var i =0; i < a1 . length ; i ++) { matched = false ; for ( var j =0; j < a2 . length && ! matched ; j ++) { if ( a1 [ i ]== a2 [ j ]) { distances [ i ] = Math . abs (i - j ) ; matched = true ; } } } return distances ; } Per quanto riguarda infine il calcolo dell’indice globale di prestazione Kτ , l’algoritmo che lo calcola ` il seguente: e 1 2 3 function globalPerfIndex ( perfArray ) { return Math . round ( sumArray ( perfArray ) / perfArray . length *100) /100; } dove il vettore perfArray contiene tutti gli indici kr .
  • 33. Capitolo 5 Risultati In questo capitolo verranno discussi i risultati ottenuti a fronte dell’esecuzione degli esperimenti. 5.1 Situazioni possibili Si esamineranno nel seguito le situazioni che si possono verificare dopo l’esecuzione di una mutazione di BubbleSort originale. 5.1.1 Ordinamento corretto Il caso di ordinamento corretto si verifica dopo l’esecuzione dell’algoritmo BubbleSort originale o di una sua mutazione equivalente1 τeq . Come risultato, si avranno dei vettori correttamente ordinati in senso crescente, degli indici di prestazione kr tutti nulli e un indice globale di prestazione associato all’algoritmo in esame Kτ anch’esso nullo. 5.1.2 Ordinamento con mutazione non equivalente Se la mutazione τ di BubbleSort non ` equivalente, vi sono le seguenti e situazioni mutualmente esclusive: • Errore a runtime: τ produce un errore durante la sua esecuzione che ne provoca la prematura terminazione • Output diverso da vettore: τ viene eseguito senza intoppi ma l’oggetto restituito non ` un vettore e • Output ` un vettore: τ viene eseguito senza intoppi e l’oggetto e restituito ` un vettore non correttamente ordinato e 1 Si veda il paragrafo 4.3.5. 31
  • 34. 5. Risultati 32 • Ciclo infinito: τ resta bloccato in un ciclo infinito e non termina mai I primi due casi, seppure rappresentino l’esito della maggioranza degli esperimenti, hanno l’effetto di generare degli indici kr,τ (intendendo con tale notazione l’indice di prestazione riferito al vettore r dopo l’esecuzione di τ ) e Kτ non ben definiti; si ` scelto quindi di comportarsi nei seguenti e modi: • Errore a runtime: kr,τ = null e Kτ = null • Output diverso da vettore: kr,τ = NaN e Kτ = NaN Il caso invece di corretta esecuzione di τ e di output di un vettore (inevitabilmente non correttamente ordinato data l’ipotesi di mutazione non equivalente) presenter` degli indici di prestazione globali e non gloa bali tutti diversi da zero; l’unica situazione in cui essi sono nulli ` che e tutti i vettori da ordinare siano costituiti da un solo elemento (situazione banale). Tali indici saranno tanto pi` elevati tanto pi` diverso dalla situazione u u ideale sar` l’ordinamento degli elementi dei vettori prodotti. a Infine, nel caso di ciclo infinito, non viene prodotto alcun indice di prestazione, dato che ` necessario forzare la terminazione del programma. e 5.2 Analisi dei risultati Per avere un quadro suffucientemente ampio dei risultati ottenuti, sono stati condotti due esperimenti; il primo producendo 1000 mutazioni di BubbleSort, il secondo producendone 5000. Per ogni esperimento sono stati elaborati due grafici: • Grafico di tentativi di scambio per mutazione: mostra quanti tentativi di scambio si sono resi necessari per passare dalla mutazione τi alla mutazione τi+1 ; in ascisse ` riportato il numero di e mutazione, mentre in ordinate i corrispondenti numeri di scambi richiesti • Grafico di errori a runtime: mostra quante mutazioni hanno prodotto un errore a runtime e quante invece hanno terminato correttamente Un terzo grafico, detto grafico degli indici globali di prestazione, che mostra l’andamento degli indici Kτ al variare della mutazione τ , non ` e stato incluso nel presente lavoro poich´ non fornisce alcuna informazione e significativa a causa dei risultati ottenuti.
  • 35. 33 Analisi dei risultati Per entrambi gli esperimenti sono stati presi in esame 30 vettori, ciascuno dei quali costituito da un numero di elementi variabile da 10 a 30 e ogni elemento pu` assumere valori interi nel range [−100, 100]. o 5.2.1 1000 mutazioni Si analizza per prima cosa il numero di tentativi di scambio per mutazione: dai dati raccolti si osserva che si va da un minimo di 1 tentativo di scambio ad un massimo di 34 tentativi, con una media di 5,14 tentativi/mutazione. 50 40 30 20 10 0 0 200 400 600 800 1000 Per quanto riguarda l’esito dell’esecuzione degli algoritmi mutati, ` e emerso che 968/1000 hanno prodotto errori a runtime terminando prematuramente, 31/1000 sono stati interamente eseguiti e 1/1000 presentava un ciclo infinito che ha richiesto la terminazione forzata del programma.
  • 36. 34 5. Risultati 1000 900 800 700 600 500 400 300 200 100 0 Non eseguiti Eseguiti Cicli infiniti Delle 31 mutazioni che hanno terminato correttamente la propria esecuzione, solo 2 hanno restituito vettori; si tratta in particolare delle prime 2 mutazioni: Mutazione τ0 τ1 τ2 ··· τ999 Indice Kτ 6,29 6,30 null null o NaN null Tabella 5.1: Indici globali di prestazione nel primo esperimento. Un indice Kτ pari a circa 6 significa che mediamente ogni vettore ordinato da quella mutazione presenta elementi distanti 6 posizioni da quelle che dovrebbero assumere nel caso di ordinamento corretto. 5.2.2 5000 mutazioni In questo secondo esperimento il numero di tentativi di scambio per mutazione sono i seguenti: si va da un minimo di 1 tentativo di scambio ad un massimo di 45 tentativi, con una media di 25,92 tentativi/mutazione.
  • 37. 35 Analisi dei risultati 50 40 30 20 10 0 0 1000 2000 3000 4000 5000 L’esito dell’esecuzione degli algoritmi mutati ha evidenziato che 4923/5000 hanno terminato prematuramente a causa di errori a runtime, 49/5000 sono stati eseguiti per intero e i rimanenti 28/5000 non terminavano a causa di cicli infiniti. 5000 4500 4000 3500 3000 2500 2000 1500 1000 500 0 Non eseguiti Eseguiti Cicli infiniti Delle 49 mutazioni che hanno correttamente terminato la propria esecuzione, nessuna di esse ha restituito vettori e dunque gli indici globali di prestazione assumono tutti o valori null oppure NaN:
  • 38. 36 5. Risultati Mutazione τ0 τ1 τ2 ··· τ999 Indice Kτ null null null null o NaN null Tabella 5.2: Indici globali di prestazione nel secondo esperimento. Ci` significa che ogni mutazione priva di cicli infiniti non produce o vettori, per ogni input. 5.2.3 Confronto tra i risultati Nel primo esperimento, quello che coinvolge 1000 mutazioni di BubbleSort, si sono evidenziati risultati pi` soddisfacenti rispetto al secondo u esperimento, sia in termini di numero medio di tentativi di scambio necessari per passare dalla mutazione τi alla mutazione τi+1 (5,14 contro 25,92), sia in termini di numero di mutazioni che hanno prodotto vettori (2 contro 0). La seguente tabella riassume la situazione: Mutazioni coinvolte Media scambi/mutazione Mutazioni con errori a runtime Mutazioni eseguite interamente Mutazioni con cicli infiniti Mutazioni con output vettore Esperimento 1 1000 5,14 96,80% 3,10% 0,10% 0,20% Esperimento 2 5000 25,92 98,46% 0,98% 0,56% 0,00% Tabella 5.3: Comparazione dei risultati. In ultima analisi, all’aumentare del numero di mutazioni generate diminuisce la percentuale di mutazioni che producono vettori (solitamente solo le prime lo fanno). Non ci si deve stupire del fatto di aver ottenuto una cos` bassa perı centuale di mutazioni pseudocorrette: le modifiche successive effettuate agli AST sono di natura casuale e consistono nello scambio di nodi; ci` si o traduce in un codice sorgente che inevitabilmente subisce grandi modifiche anche per (apparentemente) piccole mutazioni (si pensi ad esempio allo scambio tra la primissima istruzione e lo statement return).
  • 39. Capitolo 6 Conclusioni L’obiettivo di questo lavoro di tesi ` stato quello di studiare gli effetti e prodotti da una serie di mutazioni successive nel codice di una funzione JavaScript sui dati d’ingresso. Quanto esposto nei capitoli precedenti permette di affermare che tale obiettivo sia stato pienamente raggiunto. Il software sviluppato permette, infatti, di eseguire un numero a piacere di mutazioni su una funzione JavaScript memorizzata su file che viene chiesta in ingresso; a partire da questa, il software esegue tutte le elaborazioni necessarie e, oltre ai file contenenti le versioni modificate della funzione originale, produce inoltre una statistica circa il numero di tentativi di scambio eseguiti per ogni mutazione effettiva. Un secondo software sviluppato in JavaScript si occupa poi di generare i vettori su cui eseguire i test, di prendere in ingresso l’insieme dei file contenenti gli algoritmi mutati e di generare gli indici globali di prestazione. 6.1 Sviluppi futuri Una possibile estensione di quanto qui svolto potrebbe essere quella di considerare un numero maggiore di mutazioni e lavorare su pi` funzioni u iniziali, al fine di giungere ad un probabile miglioramento degli indici globali di prestazione. Un’altra strada percorribile che richiede, almeno in prima battuta, la sola scrittura di nuove grammatiche, ` quella di prendere in considerae zione altri linguaggi di programmazione. In ogni caso, in questa tesi la grammatica ` stata sviluppata in modo e tale da isolare ogni singola parte degli statement previsti; ` ragionee 37
  • 40. 6. Conclusioni 38 vole pensare che considerando invece statement interi la probabilit` di a giungere a mutazioni fallimentari diminuisca.
  • 41. Capitolo 7 Ringraziamenti Desidero ringraziare in maniera sentita il prof. Alberto Bartoli per avermi offerto la possibilit` di far parte del suo laboratorio, a concedendomi, cos` di trascorrere un periodo ı, a stretto contatto con il mondo della ricerca. Un grazie al prof. Medvet, o meglio Eric, che si ` dimostrato spesso polemico ma sempre costruttivo. e Un grazie al dott. De Lorenzo, per noialtri Andrea, sempre disponibile a chiarire tutti i miei dubbi e senza il quale questo lavoro non sarebbe stato n´ meno difficile e n´ pi` divertente :P e u Impossibile dimenticare Giulio e Daniele gli altri tesisti che hanno lavorato con me nel mai banale Machine Learning Lab. Un ringraziamento che non pu` trovare lo spazio appropriato o fra le ultime righe di questa tesi, lo devo ai miei amici, vecchi e nuovi, ed ai miei fantastici coinquilini, che hanno allietato il mio passato, che rendono unico il mio presente, e che spero continueranno a farmi dono della loro presenza nel mio futuro. Non mi sembra opportuno ringraziarli ad uno ad uno, perch´ sarebbe un elenco troppo lungo e e soprattutto perch´ citare i loro nomi e 39
  • 42. Ringraziamenti 40 conferirebbe un ordine implicito che non renderebbe giustizia e non rispecchierebbe i miei sentimenti. Un immenso grazie va ad Alessio, per avermi sostenuto in questo percorso universitario, per essersi sub` i miei ıto sfoghi pi` o meno fondati ma, soprattutto, u per avermi sopportato tutti questi anni e, spero, continuer` a farlo per molti anni a venire. a Infine, il mio pensiero va ai miei genitori i quali mi hanno dato tutto un affetto senza limiti, una giusta educazione, un sostegno nei periodi di necessit`. a . . . ai miei genitori che hanno favorito le mie passioni e che tuttora si prodigano in questa direzione. . . . ai miei genitori perch´ credo e non avrei potuto averne di migliori. Come ultimissima cosa vorrei invece invitare chi mi ha ripetutamente smontato e criticato a farsi un esame di coscienza ed ammettere che, in fondo, qualche torto ce l’avesse. Auguro a costoro di guarire dal brutto morbo dell’invidia.