Checking Security of Java Bytecode by Abstract Interpretation
Checking Security of Java Bytecode by Abstract Interpretation R. Barbuti - C. Bernardeschi - N. De Francesco Corso di Analisi e Verifica dei Programmi Carnielli Efrem 804207 Terenzoni Alessandro 804545
Sommario • • Introduzione Il linguaggio JVML (ossia JVML 0) La semantica operazionale di JVML Il modello per definire “sicuro” di JVML La semantica concreta di JVML La semantica astratta di JVML Considerazioni finali e sviluppi innerenti
Introduzione • Si presenta un metodo per certificare come sicuro un sottoinsieme di comandi di Java Bytecode. • Un metodo basato sulla interpretazione astratta della semantica operazionale del linguaggio.
Perché è importante che sia sicuro Java Bytecode? (1) Java Virtual Machine Bytecode Language (JVML) può essere scaricato da internet ed eseguito da un browser Java-compatibile. L’esecuzione del codice scaricato: - può richiedere l’accesso ai dati privati dell’utente; - può aver bisogno di informazioni in Internet che compromettono la riservatezza dei dati privati dell’utente.
Perché è importante che sia sicuro Java Bytecode? (2) Quindi bisogna garantire la sicurezza dei dati privati dell’utente in ogni forma di possibile utilizzo.
Perché è importante che sia sicuro Java Bytecode? (3) Alternativa possibile: l’utente potrebbe fare la scelta di vietare che del codice possa accedere ai suoi dati personali, ma ciò comporterebbe la possibilità che il codice non verrebbe più eseguito correttamente.
Informazione sicura • In base alla proprietà che riguarda il flusso sicuro di informazione (secure information flow property) si ha che l’informazione con un dato livello di sicurezza (security level) non può essere utilizzata a un livello di sicurezza più basso.
Flusso sicuro Dato un programma in linguaggio ad alto livello nel quale ogni variabile è considerata sicura (security level) si ha un flusso sicuro (secure flow) dell’informazione se, alla terminazione del programma, il valore di ogni variabile al livello più basso non dipende dal valore della variabile con livello di sicurezza più alto.
Esempio Supponiamo che la variabile y ha un livello di sicurezza più alto della variabile x se scrivo: x: = y oppure if y=0 then x: =1 else x: =0 Si nota come alla fine dell’esecuzione del codice il valore di x “rivela” il valore della variabile y, variabile con un livello di sicurezza più alto di x.
Falla nella sicurezza Possibile falla nella sicurezza (security leakage) che potrebbe accadere nei linguaggi ad alto livello si ha quando l’informazione sicura (security level) viene rivelato il suo valore con il comportamento del programma eseguito.
Esempio while (y>0) do skip Se programma non termina per loop-infinito si capisce che la variabile y definita con alto livello di sicurezza, è maggiore di zero.
JVML • Il linguaggio assembly utilizza uno stack. • L’articolo considera un sottoinsieme del linguaggio. • Definiremo una semantica estesa a piccoli passi del linguaggio considerando principalmente l’influenza che ha il flusso di informazione sullo stack.
Flusso di informazione Considerando il flusso di informazioni anche in un codice assembly bisogna tener conto del: - flusso di informazione esplicito - flusso di informazione implicito
Flusso di informazione esplicito Nel linguaggio ad alto livello si ha quando viene fatto un assegnamento. Esempio x: =y Il livello di sicurezza può essere definito dall’espressione nella parte destra dell’assegnamento. Nel codice assembly essendo il valore messo come push nello stack, una istruzione di pop non mostra la sorgente del valore.
Flusso di informazione implicito Nel linguaggio ad alto livello l’ambiente (scope) del flusso di informazione implicito può essere visto su comandi condizionali o comandi iterativi coincidente con l’ambiente del comando stesso. Esempio if y=0 the x: =1 else x: =0 Il linguaggio assembly non è strutturato. Lo stack può essere influenzato da un flusso implicito perché lo stack può essere manipolato in modi diversi attraverso i rami di una istruzione condizionale oppure può eseguire un diverso numero di operazioni push e pop in ordine differente.
Stato della macchina In JVML si deve parlare anche di stato della macchina. Il linguaggio assembly definisce una macchina astratta nel quale il programma eseguito può modificare lo stato della macchina che a sua volta può causare una modifica nel livello di sicurezza dell’informazione: ad esempio il contenuto del program counter quando il programma termina.
Il linguaggio JVML 0
Il linguaggio JVML 0 In JVML 0 si ha un operand stack, una memoria contenente: le variabili locali, istruzioni aritmetiche semplici, salti condizionali e non condizionali
Insieme di istruzioni JVML 0 • op dove op {add, sub, …} Estrae due operandi dallo stack, esegue l’ operazione e mette il risultato nello stack • pop estrae il valore in cima allo stack • push k mette una costante k sullo stack • load x mette il valore della variabile x nello stack • store x estrae dallo stack e mette il valore nella variabile x
Insieme di istruzioni JVML 0 • if j estrae il valore dalla cima dallo stack e salta a j se non-zero • goto j salta a j • jsr j all’indirizzo p, salta all’indirizzo j e ritorna all’indirizzo p+1 messo nell’operand stack • ret x salta all’indirizzo della variabile x • halt istruzione di terminazione del programma
Insieme di istruzioni JVML 0 • Un programma è una sequenza c di istruzioni, numerate partendo dall’indirizzo 1. • Per ogni i {1, …, #c}, c[i] è l’istruzione all’indirizzo i • Noi assumiamo che il programma rispetti i seguenti vincoli statici, generalmente verificabili da un verificatore di codice: non si presenta nessun stack overflow o underflow, l’esecuzione non salta ad un indirizzo indefinito.
Insieme di istruzioni JVML 0 • Data una sequenza di istruzioni c, denoteremo con Var(c) il nome delle variabili che occorrono in c.
Semantica operazionale di JVML 0 • La semantica operazionale descrive il significato di un linguaggio di programmazione specificando come il programma viene eseguito su una macchina astratta.
Semantica operazionale di JVML 0 • La semantica sarà data in termini di sistema di transizioni, dove il - cammino rappresenta la traccia di esecuzione e dove - i nodi mostrano che il programma cambia stato
Semantica operazionale di JVML 0
Semantica operazionale di JVML 0 La semantica standard del linguaggio definisce un insieme di regole …
Semantica operazionale di JVML 0
Semantica operazionale di JVML 0
Semantica operazionale di JVML 0
Semantica operazionale di JVML 0
Semantica operazionale di JVML 0
Semantica operazionale di JVML 0
Grafo del flusso di controllo (1) Dato un programma c composto di n istruzioni (#c=n), grafo di flusso di controllo di un programma è un grafo diretto (V, E) dove V={1, …, n+1} è un insieme di nodi e E sottoinsieme V V contiene gli archi (i, j) sse l’istruzione all’indirizzo j può essere immediatamente eseguita dopo quella all’indirizzo i, in più contiene l’arco (i, n+1) per ogni indirizzo i tale che c[i]=halt.
Grafo del flusso di controllo (2) Siano i e j nodi di grafo di flusso di controllo di un programma con n istruzioni. Diremo che il nodo j prevale su (postdominates) i, denotato da j pd i, se j i e j è in ogni cammino che parte da i. Diremo che il nodo j prevale immediatamente (immediate postdominates) i, denotato da j ipd i, se j pd i e non c’è nessun nodo r tale che j pd r pd i. Useremo la notazione j=ipd(i). Per ogni nodo i n+1 tale che ipd(i) non esiste, scriveremo ipd(i)=n+1.
Esempio 1 push 1 2 push 0 3 load y i 4 if 6 1 push 1 2 push 0 3 load y 4 if 6 5 pop 6 store x 7 halt j=ipd(i)
Il modello per definire “sicuro” JVML 0(1) • Assumiamo un reticolo finito (L, ) dove L è un insieme di livelli di sicurezza, σ, τ … secondo un ordinamento parziale . • Dati σ, τ L, σ τ indica il least upper bound di σ e di τ. • σ τ significa che σ τ.
Il modello per definire “sicuro” JVML 0(2) • Nel modello di sicurezza di JVML 0 noi considereremo di annotare il programma dove ad ogni variabile verrà associato un livello di sicurezza. • Un programma è una coppia (c, Λ) dove c è una sequenza di istruzioni e Λ è una partizione delle variabili in Var(c).
Il modello per definire “sicuro” di JVML 0(3)
Il modello per definire “sicuro” di JVML 0(3)
Il modello per definire “sicuro” di JVML 0(4)
Il modello per definire “sicuro” di JVML 0(5)
Il modello per definire “sicuro” di JVML 0(6)
Esempi di σ-non secure programs
Semantica concreta Ø Che cos’è : la semantica concreta non è altro che l’ estensione della semantica operazionale appena vista, in modo che possa controllare violazioni di s-security. Ø Come viene estesa: • i valori trattati vengono arricchiti con un nuovo parametro, il livello di sicurezza. Durante la computazione di un programma il livello di sicurezza di un valore è dato dal LUB dei livelli di sicurezza dei flussi di informazione, sia esplicito che implicito, da cui il valore dipende. • le istruzioni sono eseguite all’interno di un ambiente di sicurezza, che non è altro che un livello di sicurezza. In ogni momento dell’esecuzione, l’ambiente di sicurezza rappresenta il lub dei livelli di sicurezza del flusso implicito. L’ambiente di sicurezza può essere ”upgraded” (incrementato) da un’ istruzioni di branching (IF e RET) e può essere “downgraded” alla terminazione del flusso implicito.
Semantica concreta Come trattare il flusso implicito La semantica si basa sul control flow graph per manipolare il flusso implicito. Il flusso implicito di una istruzione IF o RET all’indirizzo i termina con l’istruzione all’indirizzo ipd(i). Eseguendo la IF o la RET all’interno di un ambiente s sicuro (cioè un ambiente a cui è stato assegnato un livello di sicurezza s), la semantica memorizza la coppia (ipd(i), s), e l’ambiente viene upgraded per considerare: • per la IF il livello di sicurezza della condizione, cioè il valore al top dello stack degli operandi • per la RET X il livello di sicurezza dell’indirizzo memorizzato in x Quando il flusso implicito termina cioè l’istruzione all’indirizzo ipd(i) è raggiunta, l’ambiente è reimpostato a s, cioè al valore di sicurezza dell’ambiente in cui l’istruzione che ha generato il flusso implicito è stata eseguita. Per modellare istruzioni di branching innestati, la semantica registra la coppia (ipd(i), s)’ in uno stack, poiché, in presenza di istruzioni di branching innestati, il flusso implicito più interno termina prima. Lo stack per contenere tali valori è chiamato ipd stack.
Semantica concreta Introduciamo i domini della semantica concreta: Ø I valori diventano coppie v = (k, s) è chiamato s -valore e V è l’insieme di questi valori. V = (Ve x L) è il dominio dei valori concreti indicati come v, v’, v 1, … Ø M : var V è il dominio delle memorie concrete, indicato come M, M’, M 1, … (cioè insieme delle funzioni che ad ogni variabile associano un s valore) Ø S =V* sono gli stack degli operandi , indicati come S, S’, S 1, … (sequenza finita di s valori) Ø R = (N x L)* indicati come r, r’, . . . Sono ipd stack. Ø Le regole della semantica operazionale sono evidenziate nella slide successiva. Tali regole definiscono una relazione di transizione C x C , dove C è l’insieme degli stati concreti.
Regole della semantica concreta
Le regole Semantica concreta Le regole assicurano che tutti gli elementi presenti nello stack degli operandi abbiano livello di sicurezza maggiore o uguale al livello di sicurezza dell’ambiente. E questo per il Lemma 1 che vedremo in seguito. Per mantenere il livello di sicurezza di un valore uguale al livello di sicurezza dell’informazione da cui dipende, la semantica modifica il livello di sicurezza di ogni valore inserito (pushed) sullo stack degli operandi, in accordo con il livello di sicurezza dell’ambiente. La PUSH assegna alla costante k il livello di sicurezza dell’ambiente (una costante non ancora usata può essere associata al minimo livello di sicurezza). La LOAD X assegna al valore inserito (pushed) sullo stack il LUB tra il livello di sicurezza di M(x) e l’ambiente. La JRS associa l’indirizzo di ritorno inserito sullo stack con il livello di sicurezza dell’ambiente.
Semantica concreta L’indirizzo i non deve essere nell’ipd stack Stato di partenza, con ambiente s Stato finale con ambiente t Come già osservato un flusso implicito comincia all’esecuzione di una IF o una RET. Le regole IF true e IF false applicate alla IF all’indirizzo i: se il valore in cima (top) allo stack degli operandi (prossimo ad essere preso in considerazione) è (k, t) il ramo selezionato verrà eseguito sotto l’ambiente di con sicurezza t. Inoltre (ipd(i), s) è inserito sul top dell’ipd stack ((ipd(i), s), terminazione del flusso implicito. r), dal quale sarà ripreso alla Assumiamo che non consenta l’inserimento di un indirizzo i sull’ipd stack se tale i è gia sulla cima dello stack. Infatti, se due o più istruzioni di branching innestate terminano allo stesso ipd, alla terminazione del flusso l’ambiente deve essere quello che era stato preso all’entrata di quello più esterno.
Semantica concreta Le regole per la IF upgrade (incrementano) il livello di sicurezza di ogni valore che viene assegnato ad una variabile tramite una STORE nel più corto dei due rami: sia W = {x | c[j] = STORE x, e j appartiene ad un cammino del grafo di controllo che comincia da i e finisce in ipd(i), escludendo ipd(i)}. Per ogni , se M(x) = (k, s), allora upgrade. M(M, i, t)(x) = (k, s t). Il livello di sicurezza delle variabili che non appartengono a W non è modificato dall’ upgrade. M(M, i, t). Fare l’upgrade della memoria in questo modo tiene in considerazione il fatto che una variabile può essere modificata in un ramo e non nell’altro. Anche lo stack degli operandi può essere influenzato dai flussi impliciti. Le regole infatti modificano anche il livello di sicurezza di ogni valore presente nello stack degli operandi applicando la funzione upgrade. S(S, t): esso aumenta il valore di ogni variabile v in S al LUB di t ed il livello di sicurezza attuale di v. La RET X, è trattata allo stesso modo, imposta il nuovo ambiente al LUB tra l’ambiente attuale ed il livello di sicurezza di x, e fa l’upgrade della memoria in conformità con lo stack. Nell’upgrade dello stack degli operandi all’entrata del flusso implicito bisogna tener conto del fatto che lo stack può essere manipolato in modi diversi da rami diversi. Un ramo può eseguire un numero diverso di POP e PUSH, e con un ordine differente. Così anche elementi che non vengono usati in nessun ramo possono cambiare la loro posizione all’interno dello stack, a causa del flusso implicito. Questi elementi possono essere usati anche dopo la terminazione del flusso implicito cioè quando l’ambiente precedente è ripristinato; ma in questo modo dovrebbero tener traccia del fatto che sono influenzati dal flusso implicito. La nostra scelta è di fare l’upgrade di tutti gli elementi dello stack degli operandi all’entrata del flusso implicito.
Semantica concreta Vediamo più chiaramente tramite un esempio: • quando l’istruzione c[6] = STORE X viene eseguita, in cima allo stack c’è il valore 0 o 1, a seconda del ramo scelto dalla IF all’indirizzo 4 che testa il valore della y. • l’istruzione c[6] non appartiene allo scope del flusso implicito di valore piu’ alto generato dall’istruzione c[4], e così è eseguita sotto l’ambiente trattenuto prima dell’entrata nel flusso implicito che ha valore piu’ basso. • in questo modo il flusso viene individuato come non sicuro tramite l’upgrade dello stack degli operandi, all’entrata del flusso implicito, che causa l’assegnamento di un valore elevato a x dall’istruzione c[6]. E’ NECESSARIO FARE L’UPGRADE DELLO STACK !
Semantica concreta La regola dell’IPD, unica nuova regola, aggiorna l’ambiente di sicurezza quando il flusso implicito termina, cioè quando l’istruzione che è ipd di una istruzione di branching eseguita precedentemente è raggiunta. La regola è applicata solo se il contenuto del program counter è presente sulla cima di r, cioè se il contenuto del program counter è i e r[1] = (i, t) per qualche t. La regola non cambia il program counter, ma modifica solo l’ambiente, che è impostato a t. Dato un programma P = (c, L) e una memoria M, la semantica concreta di P è il sistema di transizione C(P, M) = (C, , ) dove s 0 è il più basso livello di sicurezza, lo stato iniziale consiste nell’indirizzo della prima istruzione, la memoria è quella data, lo stack degli operandi è vuoto (l) e l’ipd stack anch’esso vuoto( l). Nota che, se ignoriamo informazioni sulla sicurezza, la semantica concreta coincide con la semantica standard del linguaggio. Cosi può essere usata per eseguire il programma. Ora passiamo a dare alcune definizioni e risultati. Introdurremo alcune definizioni e dei teoremi che ci permettono di affermare che la tecnica che stiamo utilizzando è corretta.
Semantica concreta Il seguente lemma esprime qualche proprietà della semantica concreta riguardante lo stack degli operandi e l’ ipd stack. Lemma 1. Dato P=<c, L> e Tale lemma assicura, come era stato accennato prima, che in ogni stato con ambiente di sicurezza s gli elementi nello stack degli operandi hanno livello di sicurezza maggiore o uguale a s, mentre gli elementi nell’ipd stack hanno livello di sicurezza minore o uguale a s e sono ordinati in maniera decrescente rispetto la sicurezza da top a bottom. Definiamo la nozione di s safeness. • una memoria è s-safe per P se ogni variabile associata in P con un livello di sicurezza minore o uguale a s, mantiene in M un livello di sicurezza minore o uguale a s. • uno stack degli operandi è s-safe per P se contiene solo valori con livello di sicurezza minore o uguale a s.
Semantica concreta Più formalmente … Definizione 5. Dato P=<c, L> una memoria concreta M con D(M) = Var(c) è s-safe per P se e solo se per ogni x allora Uno stack concreto S è s-safe per P se e solo se Il seguente teorema afferma l’adeguatezza della semantica concreta per la caratterizzazione di un flusso di informazione sicuro. Dato P=<c, L> , consideriamo una memoria concreta M con D(M) = Var(c) in cui, per ogni la variabile con livello t ha t-valore. Chiamiamo questa memoria L – consistente. , Teorema 1. Sia P=<c, L> un programma e. Se ogni esecuzione terminante di P comincia da una memoria L – consistente termina con uno stato <i, M, S, r> -/-> tale che M e S sono s-safe per P e , allora P è s-sicuro.
Semantica astratta In questa sezione presenteremo una semantica operazionale astratta in grado di controllare la s security. Perché utilizziamo la semantica astratta: Ø la semantica concreta non può essere usata come mezzo per l’analisi statica di una s-security, perché mostra il flusso di informazioni di una particolare esecuzione, mentre la s security fa riferimento a tutte le esecuzioni. Ø il sistema di transizioni concrete potrebbe anche essere infinito. Ø lo scopo dell’interpretazione astratta (o semantica astratta) è di approssimare correttamente la semantica concreta di tutte le esecuzioni in modo finito, in maniera da essere usata per costruire uno strumento utile nella pratica. Come otteniamo la semantica astratta: tale semantica è un’astrazione della semantica concreta presentata nella precedente sezione: i valori concreti sono astratti considerando solo il loro livello di sicurezza e tralasciando la loro parte numerica. Tutte le altre strutture sono astratte di conseguenza.
Semantica astratta I domini astratti : • V# = L (il nuovo dominio dei valori è il dominio delle s security) • M# = var V# (dominio delle memorie astratte) • S# = (V#)* (operandi concreti ) Il dominio dell’ipd stack rimane invariato, gli stati astratti C#, sono analoghi a quelli concreti, ma in cui la memoria M viene sostituita con una memoria astratta M# M# e lo stack S sostituito con uno astratto S# S#. Le funzioni astratte (associano informazioni concrete a quelle astratte): • la funzione astratta sui valori è a. V: V L , tale che a. V((k, s)) = s (k tralasciato) • la funzione astratta sulle memorie a. M: M M# , tale che (a. M(M))(x) = a. V(M(x)) per ogni x D(M). • la funzione astratta sugli stack, a S : S S# , tale che (a. S(S))[i] = a. V(S[i]), per ogni i • la funzione astratta sugli stati, a C : C C# come segue: {1, …, #S}.
Semantica astratta Le regole della semantica concreta vengono ridefinite sui nuovi domini. Le regole di IF e RET invece vengono modificate: E è l’insieme degli archi del grafo di controllo del programma. Consideriamo le regole IF: nella semantica astratta entrambe le diramazioni sono esplorate per ogni condizione. Di conseguenza il sistema di transizioni astratte può contenere percorsi multipli. Le funzioni di upgrade sulla memoria e sullo stack sono le stesse viste nella semantica concreta, ma ridefinite sul dominio astratto.
Semantica astratta La relazione di transizione della semantica astratta è denotata con #. Dato un programma P ed una memoria astratta M#, il sistema di transizione astratto definito con le regole di astrazione è denotato con A(P, M#) = (C#, #, ). Teorema 2: sia P = <c, L> un programma e M#0 $ M sia la memoria L –consistente. Consideriamo lo stato finale di A(P, M#0 ), cioè gli stati -/->. Se per ogn’uno di questi stati M#, S# sono mantenuti s-safe per P e , allora P è s sicuro. Il teorema sopra è la base del nostro metodo di controllo della sicurezza: la dimostrazione della ssafety di un programma P=(c, L) può essere fatta costruendo il sistema di transizione astratta che comincia da uno stato iniziale e con l’ambiente minimo in L, e in cui ogni variabile del programma è assegnata ad un livello di sicurezza secondo quanto specificato da L. Successivamente, gli stati finali sono esaminati secondo la s- safety di memoria e di stack degli operandi, e secondo il valore degli ambienti. Nota che il sistema di transizione astratto è finito. Infatti, essendo livelli di sicurezza, gli ambienti e i valori astratti finiti, anche la memoria astratta è finita. Inoltre, gli ipd stack sono finiti poiché contengono al più tutti gli indirizzi. Infine gli stack degli operandi astratti sono finiti perché assumiamo stack limitati.
Related Work (sviluppi innerenti) In questa ultima parte quanto appena visto finora verrà messo brevemente in relazione con altri lavori che si prefiggono anch’essi di effettuare un controllo sulla sicurezza di un linguaggio. Il tema della sicurezza dei linguaggi è molto diffuso e le proprietà del concetto di flusso di informazioni sicuro dei programmi è stato via formulato in maniera sempre più precisa. Le soluzioni finora affrontate si possono dividere in due categorie: Ø l’approccio basato sui tipi (type-based). Ø l’approccio basato sulla semantica (semantic-based).
Type-based • questa tecnica prevede che le informazioni sulla sicurezza di ogni variabile appartengono al corrispondente tipo ed il flusso delle informazioni di sicurezza del sistema è controllato dal significato del sistema di tipi. • viene introdotto il concetto di non-inferenza, che prevede di considerare gli stati in cui il valore finale di ogni variabile non dipende dal valore iniziale delle variabili a più alti valori di sicurezza. • in alcune versioni più forti la non inferenza include anche l’assenza di un flusso non sicuro dovuto alla non terminazione. • nelle nostre impostazioni tale proprietà è posseduta se il programma è s – sicuro per ogni s. • la non inferenza controlla che nell’ultimo stato del sistema di transizione astratta ogni variabile in L abbia un livello di sicurezza minore o uguale a s. • lavori recenti che seguono questo tipo di approccio considerano linguaggi funzionali e a cui danno un sistema di tipi; e altri in cui il linguaggio Java viene esteso dando ai programmi annotazioni sul flusso di informazioni.
Semantic-based E’ l’approccio che abbiamo visto finora. • alcuni lavori definiscono un interpretazione astratta per il controllo del flusso di informazioni in linguaggio imperativo ad alto livello, tale metodo è basato sulla semantica denotazionale del linguaggio. Si parte, come abbiamo visto, col definire una semantica concreta che poi viene sviluppata ottenendo uno strumento statico per l’analisi. La differenza è che noi utilizziamo la semantica operazionale, il vantaggio nell’usare l’ operazionale è che essa definisce una macchina astratta e così permette di tenere informazioni che non sono legate al comportamento del programma in input e output, ma riguardano gli stati della macchina durante l’esecuzione • altri lavori prevedono un controllo della sicurezza basato sulla semantica assiomatica • un altro approccio può essere basato su relazioni di equivalenza parziale ( che però ha lo svantaggio di essere difficilmente reso automatizzabile) PROBLEMA: l’utilizzo di tecniche così diversificate comporta che molti programmi che possono considerarsi sicuri per un metodo non lo possano essere per un altro.
Typed vs Semantic-based Il vantaggio dell’approccio semantico è che in generale consente di certificare la sicurezza di una larga classe di programmi. Lo svantaggio è che la semantica richiede l’esecuzione del programma anche se in maniera astratta, e questo aumenta la complessità dell’analisi in termini di spazio e tempo che può essere elevata per programmi di notevoli dimensioni, che possono essere invece ben gestiti da sistemi di tipi poiché trattengono poche informazioni. Progetti futuri : estensione del metodo proposto per consentire l’utilizzo di tale approccio all’intero Java Bytecode.
- Slides: 62