Vai alla Home Page About me Courseware Federica Living Library Federica Federica Podstudio Virtual Campus 3D Le Miniguide all'orientamento Gli eBook di Federica La Corte in Rete
 
 
Il Corso Le lezioni del Corso La Cattedra
 
Materiali di approfondimento Risorse Web Il Podcast di questa lezione

Anna Rita Fasolino » 17.Tecniche di Analisi Statica del codice e il Debugging


Principali tecniche di analisi statica del codice

  • Analisi statica in compilazione
  • Code reading
  • Code inspections o reviews
  • Walktrough
  • Control flow analysis
  • Data flow analysis
  • Esecuzione simbolica

Analisi statica in compilazione

I compilatori effettuano una analisi statica del codice per verificare che un programma soddisfi particolari caratteristiche di correttezza statica, per poter generare il codice oggetto.

Tipiche anomalie identificabili:

  • nomi di identificatori non dichiarati,
  • incoerenza tra tipi di dati coinvolti in una istruzione,
  • incoerenza tra parametri formali ed effettivi in chiamate a subroutine,
  • codice non raggiungibile dal flusso di controllo, …

Code Reading (o Desk Checking)

Si esegue una attenta lettura individuale del codice per individuare errori e/o discrepanze con il progetto.

Il lettore effettua mentalmente una pseudo-esecuzione del codice e processi di astrazione che lo conducono a verificare la correttezza del codice rispetto alle specifiche e il rispetto di standard adottati.

Tipici difetti identificabili:
nomi di identificatori errati, errato innesto di strutture di controllo, loop infiniti, inversione di predicati, commenti non consistenti con il codice, incorretto accesso ad array o altre strutture dati, incoerenza tra tipi di dati coinvolti in una istruzione, incoerenza tra parametri formali ed effettivi in chiamate a subroutine, inefficienza dell’algoritmo, non strutturazione del codice, codice morto, etc.

L’efficacia di tale tecnica è limitata se chi la esegue è la stessa persona che ha scritto il codice.

Code Inspections (o review)

Riunioni formali cui partecipa un gruppo di persone tra cui almeno una del gruppo di sviluppo, un moderatore ed altri esperti.

Lo sviluppatore legge il codice ad alta voce, linea per linea, e i partecipanti fanno commenti e/o annotazioni.

Tipicamente queste riunioni sono preannunciate ai partecipanti cui viene fornita la documentazione necessaria (codice e relativi documenti) per la revisione.

È una tecnica che riesce ad individuare fra il 30 e il 70% degli errori nella logica del programma.

Code Inspection

L’obiettivo della riunione è scoprire difetti, non correggerli. Comunque, spesso l’analisi dei difetti effettuata viene discussa e vengono decise le eventuali azioni da intraprendere:

  • accettazione del codice, rigetto, annotazioni su eventuali non aderenze a specifiche, indicazioni delle modifiche da apportare.

Il codice è analizzato usando checklist dei tipici errori di programmazione, quali:

  • Errori di data reference, data declaration, di calcolo, di confronto, sul flusso di controllo, di interfaccia, di I/O.
  • Le checklist sono generiche (indipendenti dal linguaggio) ma possono essere adattate agli specifici linguaggi analizzati.

Esempio di Checklist per le Ispezioni


Walkthrough

Analisi informale del codice svolta da vari partecipanti i quali ‘operano come il computer’: in pratica, si scelgono alcuni casi di test e si simula l’esecuzione del codice a mano (si attraversa- walkthrough- il codice).

L’organizzazione della riunione è simile a quella della tecnica delle Ispezioni:

  • Tra 3 e 5 partecipanti;
  • Riunioni brevi (max. 120 minuti);
  • Attenzione sulla ricerca dei difetti, piuttosto che sulla correzione;
  • Attenzione a non criminalizzare il programmatore (autore del difetto)!

Control Flow Analysis

Il flusso di controllo è esaminato per verificarne la correttezza.

Il codice è rappresentato tramite un grafo, il grafo del flusso di controllo (Control flow Graph – CfG), i cui nodi rappresentano statement (istruzioni eo predicati) del programma e gli archi il passaggio del flusso di controllo.

Il grafo è esaminato per identificare ramificazioni del flusso di controllo e verificare l’esistenza di eventuali anomalie quali codice irraggiungibile e non strutturazione.

Data flow analysis – statica

Analisi dell’evoluzione del valore delle variabili durante l’esecuzione di un programma, permettendo di rilevare anomalie.

Intrinsecamente è dinamica, ma alcuni aspetti possono essere analizzati staticamente. L’analisi statica è legata alle operazioni eseguite su una variabile:

  • definizione: alla variabile è assegnato un valore;
  • uso: il valore della variabile è usato in un’espressione o un predicato;
  • annullamento: al termine di un’istruzione il valore associato alla variabile non è più significativo.

Es. Nell’espressione

a:=b+c;
la variabile a è definita mentre b e c sono usate

Data Flow Analysis

La definizione (d) di una variabile, così come un annullamento (a), cancella l’effetto di una precedente definizione della stessa variabile, ovvero ad essa è associato il nuovo valore derivante dalla nuova definizione (o il valore nullo).

Una corretta sequenza di operazioni prevede che:

  • L’uso (u) di una variabile deve essere sempre preceduto da una definizione della stessa variabile senza annullamenti intermedi (Sequenza valida: du)
    • Una variabile non definita ha un valore “sporco”;
  • Una definizione di una variabile deve essere sempre seguita da un uso della variabile, prima di un’altra definizione o di un annullamento della stessa variabile (Sequenze non valide: dd, da)
    • Una doppia definizione è indice del fatto che la prima definizione è risultata inutile.

Data Flow Analysis (segue)

Sequenze di istruzioni sono riconducibili a sequenze di definizioni (d),
 usi (u) e annullamenti (a) delle variabili referenziate nei comandi.

Sequenze di istruzioni sono riconducibili a sequenze di definizioni (d), usi (u) e annullamenti (a) delle variabili referenziate nei comandi.


Data Flow Analysis – esempio


Data Flow Analysis – esempio (segue)


Esecuzione Simbolica

Il programma non è eseguito con i valori effettivi ma con valori simbolici dei dati di input.

L’esecuzione procede come una esecuzione normale ma non sono elaborati valori, bensì formule formate dai valori simbolici degli input.

Gli output sono formule dei valori simbolici degli input.

L’esecuzione simbolica anche di programmi di modeste dimensioni può risultare molto difficile.

Ciò è dovuto all’esecuzione delle istruzioni condizionali: deve essere valutato ciascun caso (vero e falso); in programmi con cicli ciò può portare a situazioni difficilmente gestibili.

Esecuzione simbolica: un esempio


Esempio


Path Conditions

Nel caso di esecuzioni simboliche con condizioni, alcuni statement sono eseguiti solo se gli input soddisfano determinate condizioni.

Una Path Condition (pc), per un determinato statement, indica le condizioni che gli input devono soddisfare affinchè una esecuzione percorra un cammino lungo cui lo statement viene eseguito.

Una pc è un’espressione Booleana sugli input simbolici di un programma.

Path Condition (segue)

All’inizio dell’esecuzione simbolica essa assume il valore vero (pc := true).

Per ogni condizione che si incontrerà lungo l’esecuzione, pc assumerà differenti valori a seconda dei differenti casi relativi ai diversi cammini dell’esecuzione.

Esempio

Esempio

Esempio

Esempio


Un esempio

  1. Function max (x, y, z: integer): integer;
  2. Begin
  3. if x<= y then
  4. max= y
  5. else
  6. max= x;
  7. if max <= z then
  8. max= z;
  9. End;

Quali sono le Path Condition per eseguire gli statement
4
6
8 ?

Il Control Flow Graph per la function max.

Il Control Flow Graph per la function max.


Un esempio

Tabella riepilogativa delle Path Condition per istruzione.

Tabella riepilogativa delle Path Condition per istruzione.


Path Condition

Ciascun nodo foglia dell’execution tree sarà percorso per una certa pc. 
In foto iI CFG ed il corrispondente Execution Tree.

Ciascun nodo foglia dell'execution tree sarà percorso per una certa pc. In foto iI CFG ed il corrispondente Execution Tree.


Analisi dell’Execution Tree

Ogni foglia dello execution tree rappresenta un cammino che sarà percorso per certi valori di input.
Le Pc associate a due differenti foglie sono distinte; ciascuna foglia dello execution tree rappresenta un cammino che sarà percorso per la Pc ad essa associata.
Non esistono esecuzioni per cui sono vere contemporaneamente più Pc (vero solo per programmi sequenziali).

Feasible Path: un cammino per il quale esiste un insieme di dati di ingresso che soddisfa la path condition.
Unfeasible Path: un cammino per il quale non esiste un insieme di dati di ingresso che soddisfa la path condition.
Se l’output ad ogni foglia è corretto allora il programma è corretto.
Ma, quanti rami può avere un execution tree?

Determinazione della eseguibilità di un cammino (Path feasibility)

Davis (1973)
Il problema di stabilire se esiste una soluzione per un sistema di diseguaglianze é indecidibile.

Un cammino é eseguibile se esiste un punto del dominio di ingresso che rende soddisfatta la sua path condition (… un sistema di diseguaglianze).
La determinazione della feasibility o della infeasibility di un cammino è indecidibile.

NB. se si riesce a dimostrare che ciascun predicato nella path condition è dipendente linearmente dalle variabili di ingresso, allora il problema è risolvibile con algoritmi di programmazione lineare.

Il Debugging

Attività di ricerca e correzione dei difetti che sono causa di malfunzionamenti.

É l’attività conseguenziale all’esecuzione di un test che ha avuto successo.

Il debugging è ben lungi dall’essere stato formalizzato

  • Metodologie e tecniche di debugging rappresentano soprattutto un elemento dell’esperienza del programmatore/tester.

Difficoltà del debugging

  1. Il sintomo e la causa possono essere lontani.
  2. Il sintomo può scomparire solo temporaneamente (a seguito di correzione di altro errore).
  3. Il sintomo può non essere causato da errori specifici (ma intrinseci all’ambiente di esecuz.-es errori di arrotondamento).
  4. Può dipendere da errori di temporizzazione e non di elaborazione.
  5. Può essere difficile riprodurre le condizioni di partenza.
  6. Il sintomo può essere intermittente.

Localizzazione dei difetti

Ridurre la distanza tra difetto e malfunzionamento.

Mantenendo un’immagine dello stato del processo in esecuzione in corrispondenza dell’esecuzione di specifiche istruzioni.

  • Watch point e variabili di watch
    • Un watch, in generale, è una semplice istruzione che inoltra il valore di una variabile verso un canale di output
      • L’inserimento di un watch (sonda) è un’operazione invasiva nel codice: anche nel watch potrebbe annidarsi un difetto;
      • In particolare l’inserimento di sonde potrebbe modificare sensibilmente il comportamento di un software concorrente;
  • Asserzioni, espressioni booleane dipendenti da uno o più valori di variabili legate allo stato dell’esecuzione.

Metodologie per il Debugging

  • Forza Bruta
  • Ragionamento Induttivo
  • Ragionamento deduttivo
  • Backtracking

“Forza Bruta”

Il modo più inefficace per fare debugging. Diversi approcci possibili:

  • Usare storage dump (stampe dello stato della memoria in esadecimale o ottale…);
  • Disseminare il codice di sonde per catturare quante più informazioni possibili e valutarle, in cerca di indizi;
  • Usare qualche strumento di debugging automatico (che permette di analizzare l’esecuzione del programma inserendo punti di break, osservazione di variabili, etc..);
  • Largamente inefficace perché può produrre eccessive informazioni da comprendere.

Ricorrervi solo quando altre tecniche hanno fallito!

Debugging usando l’approccio Induttivo

Processo di ragionamento Induttivo: dal particolare al generale.
Basato sulla raccolta di dati ed indizi sul fallimento, formulazione e verifica di ipotesi sulle possibili cause, in modo iterativo.

Processo di ragionamento Induttivo: dal particolare al generale. Basato sulla raccolta di dati ed indizi sul fallimento, formulazione e verifica di ipotesi sulle possibili cause, in modo iterativo.


Un modo per strutturare la raccolta di indizi

Un metodo per strutturare gli indizi ed un Esempio.

Un metodo per strutturare gli indizi ed un Esempio.


Debugging usando l’approccio Deduttivo

Si procede dal generale al particolare:
Si formulano varie ipotesi sulla causa dell’errore e si raccolgono dati per validarle o scartarle.

Si procede dal generale al particolare: Si formulano varie ipotesi sulla causa dell'errore e si raccolgono dati per validarle o scartarle.


Debugging per BackTracking

Si cerca di ripercorrere il codice “all’indietro” a partire dal punto dove si è verificato il malfunzionamento (istruzione di output oppure eccezione).

Analogamente alla tecnica delle Path Condition, diventa via via più difficile procedere all’indietro all’allargarsi del campo di possibilità.

Automatizzazione del debugging

Il debugging è un’attività estremamente intuitiva, che però deve essere operata nell’ambito dell’ambiente di sviluppo e di esecuzione del codice.

Strumenti a supporto del debugging sono quindi convenientemente integrati nelle piattaforme di sviluppo (IDE), in modo da poter accedere ai dati del programma, anche durante la sua esecuzione, senza essere invasivi rispetto al codice;
In assenza di ambienti di sviluppo, l’inserimento di codice di debugging invasivo rimane l’unica alternativa.

Funzionalità comuni di debugging

  • Inserimento break point
  • Esecuzione passo passo del codice
    • Entrando o meno all’interno dei metodi chiamati
    • Uscendo.
  • Verifica di asserzioni
    • Le asserzioni possono anche essere utilizzate come parametri per break point condizionali.
  • Valutazione (watch) del valore delle variabili, mentre l’esecuzione è in stato di interruzione
    • Nei linguaggi interpretati (tra cui anche Java), è possibile anche fornire la possibilità di modificare in fase di esecuzione il valore di variabili, semplificando notevolmente il problema della ricerca di casi di test in grado di replicare il malfunzionamento.
  • Contenuti protetti da Creative Commons
  • Feed RSS
  • Condividi su FriendFeed
  • Condividi su Facebook
  • Segnala su Twitter
  • Condividi su LinkedIn
Progetto "Campus Virtuale" dell'Università degli Studi di Napoli Federico II, realizzato con il cofinanziamento dell'Unione europea. Asse V - Società dell'informazione - Obiettivo Operativo 5.1 e-Government ed e-Inclusion