The goal of this thesis is to present a generic static analysis of Java multithreaded programs.
Multithreaded programs execute many task, called threads, in parallel. Threads communicate through the shared memory implicitly, and they synchronize on monitors, wait-notify primitives, etc... Some years ago dual core architectures started being distributed on the
broad market at low price. Today almost all the computers are at least dual core. Manycore, i.e. putting more and more cores on the same CPU, is now the current trend of CPU market. This multicore revolution yields to new challenges on the programming side too, asking the developers to implement multithreaded programs. Multithreading is supported natively by the most common programming languages, e.g. Java and C#.
The goal of static analysis is to compute behavioral information about the executions of a program, in a safe and automatic way. An application of static analysis is the development of tools that help to debug programs. In the field of static analysis, many di erent approaches have been proposed. We will follow the framework of abstract interpretation, a mathematical theory that allows to define and soundly approximate semantics of programs. This methodology has been already applied to a wide set of programming languages.
The basic idea of generic analyzers is to develop a tool that can be plugged with di erent numerical domains and properties. During the last years many works addressed this issue, and they were successfully applied to debug industrial software. The strength of these analyzers is that the most part of the analysis can be re-used in order to check several properties. The use of di erent numerical domains allows to develop faster and less precise or slower and more precise analyses.
In this thesis, the design of a generic analyzer for multithreaded programs is presented.
First of all, we define the happens-before memory model in fixpoint form and we abstract
it with a computable semantics. Memory models define which behaviors are allowed
during the execution of a multithreaded program. Starting from the (informal) definition of the happens-before memory model, we define a semantics that builds up all the finite executions following this memory model. An execution of a multithreaded program is represented as a function that relates threads to traces of states. We show how to design a computable abstract semantics, and we prove the correctness of the resulting analysis, in
a formal way.
Then we define and abstract a new property focused on the non-deterministic behaviors
due to multithreading, e.g. the arbitrary interleaving during the execution of di erent threads.
First of all, the non-determinism of a multithreaded program is defined as di erence
between executions. If two executions expose di erent behaviors because of values read
from and written to the shared memory, then that program is not deterministic. We abstract it in two steps: in the first step we collect, for each thread, the (abstract) value that it may write into a given location of the shared memory. At the second level we summarize all the values written in parallel, while tracking the set of threads that may have written it. At the first level of abstraction, we introduce the new concept of weak determinism.
We propose other ways in order to relax the deterministic property, namely by projecting
traces and states, and we define a global hierarchy. We formally study how the presence
of data races may a ict the determinism of the program.
We apply this theoretical framework to Java. In particular, we define a concrete semantics
of bytecode language following its specification. Then we abstract it in order to track the information required by the analysis of multithreaded programs. The core is an alias analysis that approximates references in order to identify threads, to check the accesses to the shared memory, and to detect when two threads own a common monitor thereby inferring which parts of the code cannot be executed in parallel.
The generic analyzer described above has been fully implemented, leading to heckmate,
the first generic analyzer of Java multithreaded programs. We report and deeply study some experimental results. In particular, we analyze the precision of the analysis when applied to some common pattern of concurrent programming and some case studies, and its performances when applied to an incremental application and to a set of well-known benchmarks.
An additional contribution of the thesis is about the extension of an existing industrial
generic analyzer, Clousot, to the checking of bu er overrun. It turns out that this analysis is scalable and precise. In summary, we present an application of an existing, industrial, and generic static analyzer to a property of practical interest, showing the strength of this
approach in order to develop useful tools for developers.
L’obiettivo di questa tesi è di presentare un’analisi statica generica per programmi Java multithread.
Un programma multithread esegue molteplici task, chiamati thread, in parallelo. I thread
comunicano implicitamente attraverso una memoria condivisa, e si sincronizzano attraverso monitor, primitive wait-notify, etc... Le prime architetture dual-core sono apparse sul mercato a prezzi contenuti alcuni anni fa; oggi praticamente tutti i computer sono almeno dual-code. L’attuale trend di mercato è addirittura quello del many-core, ovvero di aumentare sempre di più il numero di core presenti su una CPU. Alcune nuove sfide sono state introdotte da questa rivoluzione multicore a livello di linguaggi di programmazione, dal momento che gli sviluppatori software devono implementare programmi multithread.
Questo pattern di programmazione è supportato nativamente dalla maggior parte dei linguaggi di programmazione moderni come Java e C#.
Lo scopo dell’analisi statica è di calcolare automaticamente e in maniera conservativa una
serie di informazioni sul comportamento a tempo di esecuzione di un programma; una sua
applicazione è lo sviluppo di strumenti che aiutino a trovare e correggere errori software.
In questo campo svariati approcci sono stati proposti: nel corso della tesi verr`a seguita
le teoria dell’interpretazione astratta, un approccio matematico che permette di definire e approssimare correttamente la semantica dei programmi. Questa metodologia è già stata utilizzata con successo per l’analisi di un vasto insieme di linguaggi di programmazione.
Gli analizzatori generici possono essere instanziati con diversi domini numerici e applicati a svariate proprietà. Negli ultimi anni numerosi lavori sono stati centrati su questo approccio, e alcuni di essi sono stati utilizzati con successo in contesto industriale. Il loro punto di forza è il riutilizzo della maggior parte dell’analizzatore per verificare molteplici
proprietà, e l’utilizzo di diversi domini numerici permette di ottenere analisi più veloci ma più approssimate, oppure più precise ma più lente.
Nel corso di questa tesi presenteremo un analizzatore generico per programmi multithread.
Definiremo innanzitutto il modello di memoria happens-before sotto forma di punto fisso e lo approssimeremo con una semantica che sia calcolabile. Un modello di memoria
definisce quali comportamenti di un programma multithread sono consentiti durante la sua esecuzione. A partire da una definizione informale del modello di memoria happensbefore, introdurremo una semantica che costruisca tutte le esecuzioni finite che rispettino tale modello di memoria; in tale contesto un’esecuzione è rappresentata come una funzione che associa ciascun thread ad una traccia di stati che rappresenta la sua esecuzione.
Introdurremo infine una semantica astratta che pu`o essere calcolata, provandone la correttezza formalmente.
Definiremo e approssimeremo quindi una nuova proprietà focalizzata sui comportamenti
non deterministici causati dall’esecuzione multithread (ad esempio dall’intercalarsi arbitrario durante l’esecuzione in parallelo di diversi thread). Prima di tutto, il non determinismo di un programma multithread è definito come di erenza tra esecuzioni. Un programma è non deterministico se due diverse esecuzioni espongono comportamenti di erenti a causa dei valori letti e scritti sulla memoria condivisa. Astrarremo quindi tale proprietà su due livelli: inizialmente tracceremo per ogni thread il valore astratto che potrebbe aver scritto sulla o letto dalla memoria condivisa. Al successivo passo di astrazione tracceremo un solo valore, che approssimerà tutti i possibili valori scritti in parallelo, e l’insieme
dei thread che potrebbero aver fatto ciò. Sul primo livello di astrazione definiremo poi il concetto di determinismo debole. Proporremo quindi diverse modalità di rilassamento di tale proprietà, in particolare proiettandola su un sottoinsieme delle traccie di esecuzione e degli stati, definendo una gerarchia complessiva. Infine studieremo come la presenza di data race possa influenzare il determinismo di un programma.
Tutto questo lavoro teorico verrà quindi applicato a programmi Java. In particolare definiremo una semantica concreta del linguaggio Java bytecode seguendo la sua specifica.
Quindi lo approssimeremo in maniera da astrarre precisamente le informazioni richieste per poter analizzare un programma multithread. Il fulcro di ciò è l’approssimazione degli indirizzi di memoria per poter identificare i diversi thread, per controllare gli accessi alla memoria condivisa e per poter scoprire quando due thread sono sempre sincronizzati su
uno stesso monitor e quindi quali parti di codice non possono essere eseguite in parallelo.
L’analizzatore generico definito fin qui formalmente è stato implementato in heckmate, il primo analizzatore generico di programmi Java multithread. Riporteremo e studieremo approfonditamente i risultati sperimentali: in particolare verrà studiata la precisione dell’analisi quando utilizzata su alcuni pattern comuni di programmazione concorrente e alcuni casi di studio, e le sue prestazioni quando eseguita su un’applicazione incrementale e su un insieme di benchmark esterni.
L’ultimo contributo della tesi sarà l’estensione di un analizzatore generico industriale esistente (Clousot) all’analisi degli accessi e ettuati tramite puntatori diretti alla memoria.
In questa parte finale presenteremo l’applicazione di un analizzatore generico ad una proprietà di interesse pratico su codice industriale, mostrando quindi la forza di questo tipo di approccio allo scopo di costruire strumenti utili per sviluppare software.
Le but de cette thèse est de présenter une analyse statique générique pour des programmes multitâche écrits en Java.
Les programmes multitâche exécutent plusieurs tâches en parallèle. Ces tâches communiquent implicitement par le biais de la mémoire partagée et elles se synchonisent sur des moniteurs (les primitives wait-notify, etc, . . . ). Il y a quelques années, les architectures avec double processeurs ont commencé à être disponibles sur le marché à petit prix. Aujourd’hui, presque tous les ordinateurs ont au moins deux noyaux, la tendance actuelle du marché étant de mettre de plus en plus de processeurs par puce. Cette révolution
amène également de nouveaux défis en matière de programmation, car elle demande aux
développeurs d’implanter des programmes multitâche. Le multitâche est supporté en natif par la plupart des langages de programmation courants, comme Java et C#.
Le but de l’analyse statique est de calculer des informations sur le comportement
d’un programme, de manière conservative et automatique. Une application de l’analyse
statique est le développement d’outils qui aident au débogage des programmes. Plusieurs
méthodes d’analyse statique ont été proposées. Nous suivrons le cadre de l’interprétation abstraite, une théorie mathématique permettant de définir des approximations correctes de sémantiques de programmes. Cette méthode a déjà été utilisée pour un large spectre de langages de programmation.
L’idée fondamentale des analyseurs statiques génériques est de développer un outils
qui puissent être interfacé avec différents domaines numériques et différentes propriétés. Pendant ces dernières années, beaucoup de travaux se sont attaqué à cet enjeu, et ils ont été appliqués avec succès pour déboguer des logiciels industriels. La force de ces analyseurs réeside dans le fait qu’une grande partie de l’analyse peut être réutilisée pour vérifier plusieurs propriétés. L’utilisation de différents domaines numériques permet le développement d’analyses plus rapides mais moins précises, ou plus lentes mais plus précises.
Dans cette thèse, nous présentons la conception d’un analyseur générique pour des
programmes multitâche. Avant tout, nous définissons le modèle mémoire, appelé happensbefore memory model. Puis, nous approximons ce modéle mémoire en une semantique calculable. Les modéles mémoire définissent les comportements autorisés pendant l’exécution d’un programme multitâche. Commençant par la définition (informelle) de ce modèle mémoire particulier, nous définissons une sémantique qui construit toutes les exécutions finies selon ce modèle mémoire. Une exécution d’un programme multitâche est décrite par une function qui associe les tâches à des séquences (ou traces) d’états. Nous montrons comment concevoir une sémantique abstraite calculable, et nous montrons formellement
la correction des résultat de cette analyse.
Ensuite, nous définissons et approximons une nouvelle propriété qui porte sur les comportements non déterministes causés par le multitâche, c’est à dire ceux qui sont dus
aux entrelacements arbitraires pendant l’exécution de différentes instructions de lecture.
Avant tout, le non déterminisme d’un programme multitâche se définit par une différence entre plusieurs exécutions. Si deux exécutions engendrent des comportements différents dus aux valeurs qui sont lues ou écrites en mémoire partagée, alors le programme est non déterministe. Nous approximons cette propriété en deux étapes : dans un premier temps, nous regroupons, pour chaque tâche, la valeur (abstraite) qui peut être écrite dans la mémoire partagée à un point de programme donné. Dans un deuxième temps, nous résumons toutes les valeurs pouvant être écrites en parallèle, tout en nous rapellant l’ensemble des tâches qui pourraient les avoir écrites. À un premier niveau d’approximation, nous introduisons un nouveau concept de déterminisme faible. Nous proposons par ailleurs d’autres manière a aiblir la propriété de déterminisme, par exemple par projection des traces et des états, puis nous d´efinissons une hierarchie globale de ces a aiblissements.
Nous étudions aussi comment la présence de conflit sur les accès des données
peut a ecter le déterminisme du programme.
Nous appliquons ce cadre de travail théorique à Java. En particulier, nous définissons une sémantique du language objet de Java, selon sa spécification. Ensuite, nous approximons cette sémantique afin de garder uniquement l’information qui est nécessaire pour l’analyse des programmes multitâche. Le coeur de cette abstraction est une analyse d’alias qui approxime les références afin d’identifier les tâches, de vérifier les accès en mémoire partagée, et de détecter quand deux tâches ont un moniteur commun afin d’en déduire quelles parties du code ne peuvent pas être éxécutées en parallèle.
L’analyseur générique qui est décrit ci-dessus a été entierement implanté, dans un outils appelé Checkmate. Checkmate est ainsi le premier analyseur générique pour des programmes multitâche écrits en Java. Des résultats expérimentaux sont donnés et analysés en détails. En particulier, nous étudions la précision de l’analyse lorsqu’elle est appliquée à des schémas courants de la programmation concurrente, ainsi qu’à d’autres exemples.
Nous observons également les performances de l’analyse lorsqu’elle est appliquée à une
application incrémentale, ainsi qu’à des exemples de référence bien connus.
Une autre contribution de cette thèse est l’extension d’un analyseur générique existant
qui s’appelle Clousot et qui permet de vérifier le non débordement des mémoires tampons.
Il s’avère que cette analyse passe à l’échelle des programmes industriels et qu’elle est précise. En résumé, nous présentons une application d’un analyseur statique générique industriel existant pour détecter et prouver une propriété présentant un intérêt pratique, ce qui montre la puissance de cette approche dans le développement d’outils qui soient utiles
pour les déeveloppeurs.