Riassunto puntate precedenti Java Bytecode Semantica Concreta Semantica a tracce Punto fisso Interpretazione astratta in pratica Un analizzatore generico per Java Pietro Ferrara Università Ca’ Foscari di Venezia I-30170 Venezia (Italy) [email protected] Ecole Polytechnique, Paris F-91128 Palaiseau (France) [email protected] April 21, 2008 Pietro Ferrara Interpretazione astratta in praticaUn analizzatore generico per Ja Abbiamo visto Concetti basilari di matematica C senza puntatori C con puntatori Java Cos’è Java viene compilato in un linguaggio intermedio: il bytecode Perchè? Compilo una volta, eseguo su diverse piattaforme, sistemi operativi, ... codice Java javac bytecode Java virtualmachine esecuzione La Java Virtual Machine è specifica per la piattaforma e il sistema operativo! Idee di base Linguaggio intermedio Più semplice di Java Un po’ più ad alto livello dell’assembly Maggiori differenze Valori passati attraverso uno stack apposito Praticamente spariscono le variabili Codice non strutturato! Un esempio public void prova(int i) { if(i >= 0) System.out.println(”Ok”); else System.out.println(”No”); } 0 iload 1 1 iflt 15 (+14) 4 getstatic #2 < java/lang/System.out > 7 ldc #3 < Ok > 9 invokevirtual #4 < java/io/PrintStream.println > 12 goto 23 (+11) 15 getstatic #2 < java/lang/System.out > 18 ldc #5 < No > 20 invokevirtual #4 < java/io/PrintStream.println > 23 return 0 iload 1 1 iflt 15 (+14) 4 getstatic #2 < java/lang/System.out > 7 ldc #3 < Ok > 9 invokevirtual #4 < java/io/PrintStream.println > 12 goto 23 (+11) 15 getstatic #2 < java/lang/System.out > 18 ldc #5 < No > 20 invokevirtual #4 < java/io/PrintStream.println > 23 return entry point iload 1 Q QQQ QQQ>=0 QQQ <0 QQQ QQ( 15 getstatic #2 18 ldc #5 < No > 20 invokevirtual #4 7 ldc #3 < Ok > 9 invokevirtual #4 lll lll l l ll lll l v ll 23 return Overview dei comandi Lettura e scritture sulle variabili locali: iload, aload, iload 1, istore, ... Operazioni aritmetiche: iadd, dmul, ... Costanti: iconst 0, ldc, ... Eccezioni: throw Oggetti: new, putfield, getfield Istruzioni condizionali: goto, ifnull, ifeq, ifge, if icmplt, ... Etc.! Stato di esecuzione Due componenti principali Lo stack delle operazioni L’array di valori locali Un valore può essere Un numero intero long intero short byte ... non esiste il tipo booleano! Un indirizzo Definition (Val) Val = Ref ∪ N Stack Lo stack è usato per passare valori Ad esempio: add Prende i due valori più in alto nello stack Li somma Mette sullo stack il risultato Definition (Op) Op = ST(Val) Array di valori locali Utilizzato per immagazzinare e leggere valori locali Intuitivamente corrisponde alle variabili locali di un metodo Ad esempio: iload 1 Legge un valore intero dall’elemento n.1 dell’array Mette sullo stack tale valore Definition (LV) LV = AR(Val) Heap In ciascun indirizzo un elemento di array di valori locali array di valori locali ≈ ambiente! Oggetto Immagazzinato nello heap Ogni volta che si accede un campo: Si passa tramite stack l’indirizzo da accedere putfield e getfield hanno un indice per sapere quale elemento dell’array accedere Definition (H) H = [Ref → LV] Frame Lo heap è unico Stack e array sono locali al metodo Ad ogni invocazione di metodo viene creato un frame, ovvero una coppia composta da uno stack e un array Definition (F) F = Op × LV Stato concreto Stack di frame Heap Definition (Σ) Σ = ST(F) × H Un esempio public class Classe { int n = 0; public void prova(int i) { Classe v = new Classe(); if(i >= 0) v.n = i + 1; else v.n = 0; } } Un esempio 0 new #3 < Classe > 3 dup 4 invokespecial #4 < Classe. < init >> 7 astore 2 8 iload 1 9 iflt 22 (+13) 12 aload 2 13 iload 1 14 iconst 1 15 iadd 16 putfield #2 < Classe.n > 19 goto 27 (+8) 22 aload 2 23 iconst 0 24 putfield #2 < Classe.n > 27 return 0 aload 0 1 invokespecial #1 < java/lang/Object. < init >> 4 aload 0 5 iconst 0 6 putfield #2 < Classe.n > 9 return Supponiamo che l’indirizzo di this sia #0, che il suo campo n sia uguale a 5, e che il valore passato al metodo sia 1 Stack di valori: Valori locali: 0 1 2 #0 1 Heap: Add. Index #0 2 Value 5 0 new #3 < Classe > Stack di valori: #1 Valori locali: 0 1 2 #0 1 Heap: Add. Index #0 2 #1 Value 5 3 dup Stack di valori: #1 #1 Valori locali: 0 1 2 #0 1 Heap: Add. Index #0 2 #1 Value 5 4 invokespecial #4 < Classe. < init >> 0 aload 0 1 invokespecial #1 < java/lang/Object. < init >> 4 aload 0 5 iconst 0 6 putfield #2 < Classe.n > 9 return Stack di valori: #1 #1 Valori locali: 0 1 2 #0 1 Heap: Add. Index #0 2 #1 2 Value 5 0 7 astore 2 8 iload 1 Stack di valori: #1 1 Valori locali: 0 1 2 #0 1 #1 Heap: Add. Index #0 2 #1 2 Value 5 0 9 iflt 22 (+13) Stack di valori: 1 Valori locali: 0 1 2 #0 1 #1 Heap: Add. Index #0 2 #1 2 Value 5 0 12 aload 2 Stack di valori: #1 Valori locali: 0 1 2 #0 1 #1 Heap: Add. Index #0 2 #1 2 Value 5 0 13 iload 1 Stack di valori: 1 #1 Valori locali: 0 1 2 #0 1 #1 Heap: Add. Index #0 2 #1 2 Value 5 0 14 iconst 1 Stack di valori: 1 1 #1 Valori locali: 0 1 2 #0 1 #1 Heap: Add. Index #0 2 #1 2 Value 5 0 15 iadd Stack di valori: 1 21 #1 Valori locali: 0 1 2 #0 1 #1 Heap: Add. Index #0 2 #1 2 Value 5 0 16 putfield #2 < Classe.n > Stack di valori: 2 #1 Valori locali: 0 1 2 #0 1 #1 Heap: Add. Index #0 2 #1 2 Value 5 2 19 goto 27 (+8) Stack di valori: Valori locali: 0 1 2 #0 1 #1 Heap: Add. Index #0 2 #1 2 Value 5 2 27 return Stack di valori: Valori locali: 0 1 2 #0 1 #1 Heap: Add. Index #0 2 #1 2 Value 5 2 Esecuzione Fin qui abbiamo visto il dominio concreto Ma come viene utilizzato questo per avere informazione sull’esecuzione? Funzione di trasferimento Per ogni istruzione del bytecode definiamo i suoi effetti su uno stato del dominio Funzione di trasferimento (s0 , v) = popOS (s), (s00 , r) = popOS (s0 ), h0 = h[r 7→ h(r)[i 7→ v]] C~putfield #i(hs, h) → hs00 , h0 ii (s0 , r) = popOS (s), (v, t, pc, c, m0 ) = h(r)(i ), s00 = pushOS (s0 , v) C~getfield #i(hs, hi) → hs00 , hi (s0 , v1 ) = popOS (s), (s00 , v2 ) = popOS (s), s000 = pushOS (s00 , v1 + v2 ) C~iadd(hs, hi) → hs00 , hi Semantica a tracce Ma come gestiamo goto e if...? La domanda è un’altra: Come rappresentiamo una singola esecuzione? Come una sequenza ordinata di stati! Se ad esempio l’esecuzione inizia con uno stato s0 , prosegue con s1 , e poi con s2 , lo rappresentiamo come s0 → s1 → s2 Le istruzioni di salto (condizionale o no) vengono trattate a questo livello Nello stato infatti c’è solo la memoria e non lo stato del controllo Un esempio 0 new #3 < Classe > ⇓ 3 dup ⇓ 4 invokespecial #4 < Classe. < init >> ⇓ 7 astore 2 ⇓ 8 iload 1 ⇓ 9 iflt 22 (+13) ⇓ 12 aload 2 ⇓ 13 iload 1 ⇓ 14 iconst 1 ⇓ 15 iadd ⇓ 16 putfield #2 < Classe.n > ⇓ 19 goto 27 (+8) ⇓ 27 return Esecuzioni parziali Abbiamo già definito Dominio Funzione di trasferimento Manca Semantica del programma! Costruiamo istruzione per istruzione la traccia di esecuzione Attraverso la funzione di trasferimento In forma di punto fisso Esecuzioni parziali! Un esempio All’inizio si parte da una traccia vuota Prima iterazione {, 0 new #3 < Classe >} Seconda iterazione {, 0 new #3 < Classe >, 0 new #3 < Classe > → 3 dup} Terza iterazione {, 0 new #3 < Classe >, 0 new #3 < Classe > → 3 dup, 0 new #3 < Classe > → 3 dup → 4 invokespecial #4 < Classe. < init >>} ... Finchè non si arriva a un punto fisso! Dominio Dominio composto da un insieme di tracce di esecuzioni parziali Diversi input Numeri casuali Interleaving casuali di diversi threads ... Costruiamo incrementalmente la tracce di esecuzione Finchè abbiamo costruito tutte le possibili esecuzioni parziali... ... Punto fisso! Solo esecuzioni finite ⊆ operatore di ordinamento, ∪ operatore di upper bound, ...