ΑΥΤΟΜΑΤΟΣ ΕΛΕΓΧΟΣ ΜΟΝΤΕΛΟΥ ΛΟΓΙΣΜΙΚΟΥ ΜΕ ΤΟ JAVA PATHFINDER Ιωαννίδης Αντώνης Επιβλέπων Καθηγητής: Κατσαρός Παναγιώτης Αριστοτέλειο Πανεπιστήμιο Θεσσαλονίκης
ΠΕΡΙΕΧΟΜΕΝΑ Περιεχόμενα...2 1. Εισαγωγή...5 1.1 Σκοπός... 5 2. Εισαγωγικές Εννοιες...6 2.1 Αυτόματος Έλεγχος Μοντέλου (Model Checking)... 6 2.2 Εφαρμογή και λόγοι ύπαρξης του Αυτόματου Ελέγχου Μοντέλου... 7 2.2.1 Hardware και Αυτόματος Έλεγχος Μοντέλου... 7 2.2.2 Software και Αυτόματος Έλεγχος Μοντέλου... 8 2.2.3 Αυτόματος Έλεγχος Μοντέλων Λογισμικού (Program Model Checking)... 8 2.2.4 Model Checking και το φαινόμενο της έκρηξης του χώρου καταστάσεων... 9 3. Αυτόματος Έλεγχος Μοντέλων Λογισμικού (Software Model Checking)...10 3.1 Model Chekers... 11 3.1.1 Java PathFinder... 11 3.1.2 SPIN... 12 3.1.2 Bandera... 13 3.1.3 BLAST... 14 3.1.4 Bogor... 15 3.1.5 Εργαλείο BOOP... 16 3.1.6 LURCH... 16 3.1.7 Verisoft... 17 4. Java PathFinder...18 4.1. Property Checker... 18 4.1.1 Assertions... 18 4.1.2 Property Class... 18 4.1.3 Search Listener και VMListener... 19 4.2 Οργανώνοντας το πρόγραμμα με χρήση της κλάσης Verify... 20 4.2.1 Μη ντετερμινιστικοί Choice Generators... 20-2 -
4.2.2 Search Pruning... 21 4.2.3 State annotation... 22 4.2.4 Verification log output... 22 4.2.5 Explicit atomicity control... 22 4.3 Βασικά τμήματα του JPF... 23 4.3.1 Java Virtual Machine... 23 4.3.2 Το Search αντικείμενο... 24 4.4 On-the-fly Partial Order Reduction (POR)... 24 4.5 Search και VM Listeners... 26 4.5.1 Search Listener... 26 4.5.2 VM Listener... 28 4.5.3 Ρυθμίσεις... 30 4.6 Το Model Java Interface (MJI)... 30 4.7 Choice Generators... 31 5. Εγκατάσταση και Χρήση του Java PathFinder...33 5.1 Εγκατάσταση του Java PathFinder (2 τρόποι)... 33 5.1.1 Από το Command Line... 33 5.1.2 Μέσω του προγράμματος Eclipse (SUN)... 41 5.2 Χρήση του Java PathFinder μέσω παραδειγμάτων... 51 5.2.1 Πρόγραμμα AssertionCheck... 51 5.2.2 Πρόγραμμα lockexample... 53 5.2.3 Πρόγραμμα oldclassic... 56 5.3 Έλεγχος προγραμμάτων με το JPF (άλλα παραδείγματα)... 60 6. Γραφικό Περιβάλλον του Java PathFinder (VJP)...64 6.1 Εγκατάσταση του VJP στο Eclipse... 64 6.2 Διαδικασία χρήσης του VJP... 67 6.2.1 Δημιουργία του Mode-Property αρχείου... 67 6.2.2 Ρύθμιση του Mode-Property αρχείου... 68 6.2.3 Έλεγχος προγράμματος με χρήση του VJP... 69 6.3 Εμφάνιση αποτελεσμάτων ελέγχου του VJP... 69 6.3.1 Αποτελέσματα χωρίς να έχει βρεθεί σφάλμα... 70 6.3.2 Αποτελέσματα που έχει εντοπιστεί σφάλμα... 71-3 -
7. Συμπεράσματα...74 7.1 JPF και γραμμή εντολών... 74 7.2 JPF και Eclipse... 74 7.3 JPF και Eclipse σε συνδυασμό με το VJP... 75 7.4 Γενικά... 76 Παράρτημα A: Επεξήγηση Όρων...77 Παράρτημα Β: Χρήσιμοι Σύνδεσμοι...78 Παράρτημα Γ: Αναφορές...79-4 -
1. ΕΙΣΑΓΩΓΗ Η εργασία αυτή ασχολείται με τον έλεγχο σφαλμάτων λογισμικού και για αυτό τον σκοπό χρησιμοποιήθηκε το πρόγραμμα Java PathFinder. Ο αυτόματος έλεγχος μοντέλων λογισμικού (program model checking) αποτελεί ένα νέο σχετικά κλάδο της πληροφορικής και έχει σαν σκοπό την αποφυγή δραματικών καταστάσεων εξαιτίας σφαλμάτων σε λογισμικό. 1.1 Σκοπός Σκοπός της διπλωματικής αυτής είναι η εγκατάσταση του προγράμματος ελέγχου σφαλμάτων λογισμικού Java PathFinder, η κατανόησή του, η εξοικείωση με αυτό, η δοκιμή του, η εξαγωγή συμπερασμάτων από έτοιμα παραδείγματα και τέλος η χρήση του σε άλλα προγράμματα. Σημαντικό μέρος της εργασίας είναι η εξοικείωση με προγράμματα αυτόματου ελέγχου μοντέλου (software model checking) και η καταγραφή συμπερασμάτων σχετικά με την αποτελεσματικότητα και την ευκολία χρήσης τους. - 5 -
2. ΕΙΣΑΓΩΓΙΚΕΣ ΈΝΝΟΙΕΣ 2.1 Αυτόματος Έλεγχος Μοντέλου (Model Checking) Οι αυτόματοι έλεγχοι μοντέλων είναι μία συλλογή από τεχνικές που αναλύουν την αναπαράσταση ενός συστήματος με σκοπό την απόδειξη της ορθότητας μίας ή περισσότερων ιδιοτήτων (properties) τους συστήματος που μας ενδιαφέρουν. Πιο συγκεκριμένα, είναι μία αλγοριθμική τεχνική επαλήθευσης για ταυτοχρονισμένα συστήματα πεπερασμένων καταστάσεων (Clark 1986, Queille 1982, NASA 2004). Ένα ταυτοχρονισμένο σύστημα μπορεί να μοντελοποιηθεί από μία μηχανή πεπερασμένων καταστάσεων με τη μορφή ενός γράφου που αποτελείται από: Κόμβους, που αναπαριστούν τις καταστάσεις του συστήματος Ακμές, που αναπαριστούν τις ενώσεις/μεταβάσεις μεταξύ των καταστάσεων Θεωρητικά, ένας αυτόματος ελεγκτής μοντέλου (model checker) μπορεί να ελέγξει εξαντλητικά όλες τις πιθανές καταστάσεις ενός συστήματος και να εξακριβώσει την σωστή λειτουργία συγκεκριμένων, αν όχι όλων, ιδιοτήτων του. Αν μία ιδιότητα ικανοποιείται, ο ελεγκτής επιβεβαιώνει τη σωστή λειτουργία του. Σε αντίθετη περίπτωση παράγει ένα «μονοπάτι» (trace) το οποίο περιέχει τις καταστάσεις μέσα από τις οποίες το σύστημα οδηγείται στην κατάσταση λάθους που εντοπίστηκε. Για αυτό το λόγο ο αυτόματος έλεγχος μοντέλων μπορεί να χρησιμοποιηθεί είτε για την απόδειξη της ορθότητας ενός συστήματος είτε για την διόρθωση σφαλμάτων του. 2.2 Εφαρμογή και Λόγοι Ύπαρξης του Αυτόματου Ελέγχου Μοντέλων - 6 -
Ο αυτόματος έλεγχος μοντέλων βρίσκει εφαρμογή και στο hardware αλλά και στο software σε διαφορετικά όμως επίπεδα ανάπτυξης του συστήματος. 2.2.1 Hardware και Αυτόματος Έλεγχος Μοντέλου Η καταστροφή που αποτέλεσε την κύρια αιτία της δημιουργίας του αυτόματου ελέγχου μοντέλων σε επίπεδο hardware είναι το Intel Pentium bug (γνωστό και ως Pentium FDIV bug), το 1994, όπου η Intel αναγκάστηκε να αποσύρει και να αντικαταστήσει τους προβληματικούς επεξεργαστές. Έτσι οι ειδικοί αποφάσισαν ότι έπρεπε να βρούνε νέες μεθόδους ελέγχου του hardware καθώς αυτές που χρησιμοποιούσαν αποδείχθηκαν ελλιπείς. Έτσι με σκοπό να αποφύγουν νέα πολυέξοδα λάθη οδηγήθηκαν στον αυτόματο έλεγχο μοντέλων, με σκοπό την εύρεση σφαλμάτων στο hardware, όπως τον γνωρίζουμε σήμερα. 2.2.2 Software και Αυτόματος Έλεγχος Μοντέλου Ο αυτόματος έλεγχος μοντέλου γίνεται, συνήθως, νωρίς στην διαδικασία ανάπτυξης του συστήματος. Μπορεί να αναλύσει τα χαρακτηριστικά των απαιτήσεων του συστήματος και τα μοντέλα σχεδιασμού του συστήματος. Έτσι εξετάζοντας μικρά, αλλά ουσιαστικά για την πορεία της ανάπτυξης του συστήματος, προγράμματα στην αρχή της διαδικασίας ανάπτυξης, πετυχαίνουμε την αποφυγή κρίσιμων λαθών τα οποία μπορεί να ήταν πολύ ακριβό να διορθώσουμε σε κάποιο άλλο στάδιο της ανάπτυξης του συστήματος. Όπως είναι λογικό, υπάρχουν πολλά σφάλματα που δεν μπορούν να εντοπιστούν λόγω της πρώιμης κατάστασης του συστήματος. Είναι σαφές ότι πολλά σφάλματα θα προκύψουν κατά τη διαδικασία υλοποίησης του συστήματος. Η NASA αντιμετώπισε πολλά τέτοια σφάλματα λογισμικού. Για παράδειγμα, το 1999 το διαστημόπλοιο Mars Polar Lander (MPL) χάθηκε κατά την προσεδάφισή του στην επιφάνεια του Άρη με συνολικό κόστος κοντά στα $165 εκατομμύρια. Η αποτυχία της αποστολής - 7 -
αποδόθηκε σε ένα σφάλμα λογισμικού-μία λάθος γραμμή κώδικα. Μία μεταβλητή η οποία δεν επανα-αρχικοποιούταν και σχετιζόταν με το αν το σκάφος έχει ακουμπήσει στο έδαφος, οδήγησε στο λανθασμένο σβήσιμο των μηχανών τη στιγμή που το σκάφος βρισκόταν 130 πόδια πάνω από την επιφάνεια του Άρη. Αποτέλεσμα αυτού ήταν η καταστροφή του MPL και η απώλεια πολλών χρημάτων. Ο παραπάνω λόγος ήταν ένας από αυτούς που οδήγησαν στην δημιουργία του αυτόματου ελέγχου μοντέλων σχετικά με σφάλματα λογισμικού με τη μορφή που τον γνωρίζουμε σήμερα. 2.2.3 Αυτόματος Έλεγχος Μοντέλων Λογισμικού (Program Model Checking) Ο αυτόματος έλεγχος μοντέλων λογισμικού αναφέρεται σε τεχνικές ελέγχου σφαλμάτων που εξετάζουν το τελικό κομμάτι ενός συστήματος όπου ο κώδικας αποτελεί το σημείο ανάλυσης και εξέτασης. Μπορεί να εντοπίσει αποτελεσματικά κρίσιμα σφάλματα στο λογισμικό τα οποία θα ήταν δύσκολο να βρεθούν με τις συμβατικές μεθόδους εκσφαλμάτωσης όπως το testing. Υπάρχουν πολλά προγράμματα που βοηθάνε έναν προγραμματιστή να εντοπίσει σφάλματα στον κώδικα ενός προγράμματος, παρουσιάζουν όμως δύο σημαντικά μειονεκτήματα σε σχέση με τα μοντέλα ελέγχου. Το ένα αφορά τον έλεγχο ταυτοχρονισμένων (πολύ-νηματικών) συστημάτων. Ο έλεγχος αυτών των συστημάτων είναι προβληματικός γιατί ο δοκιμαστής (tester) δεν έχει απόλυτο έλεγχο της ροής εκτέλεσης των νημάτων και τα σφάλματα ταυτοχρονισμού είναι πολύ δύσκολο να επανεμφανιστούν. Με τον αυτόματο έλεγχο μοντέλου υπάρχει η δυνατότητα διαχείρισης της σειράς εκτέλεσης των νημάτων (threads) και με τα μονοπάτια σφαλμάτων (error traces) υπάρχει η δυνατότητα αναπαραγωγής του σφάλματος. Ο άλλος τομέας αναφέρεται στο ότι ο αυτόματος έλεγχος μοντέλου μπορεί να βοηθήσει στην ανάπτυξη των καταστάσεων ελέγχου. Χωρίς καμία οδηγία, ο model checker θα εξετάσει όλες τις πιθανές καταστάσεις του συστήματος. Επειδή κάτι - 8 -
τέτοιο είναι πρακτικά αδύνατο, καθοδηγούμε τον ελεγκτή προκειμένου να εξετάσει τα σημεία (properties) που μας ενδιαφέρουν. Έτσι εξετάζονται εξαντλητικά και συστηματικά όλες οι πιθανές καταστάσεις του συστήματος που μας ενδιαφέρουν και όχι κάποιες από αυτές που επιλέχθηκαν (συνήθως τυχαία) από τον εκάστοτε tester. Η εξαντλητική, συστηματική, αποτελεσματική και αυτόματη φύση του αυτόματου ελέγχου μοντέλου είναι αυτή που τα κάνει ελκυστικά σε αυτούς που ασχολούνται με την επαλήθευση λογισμικού. Επιπλέον, προσφέρει μεγαλύτερη εγγύηση αποτελεσματικότητας από τις υπόλοιπες διαθέσιμες μεθόδους ελέγχου σφαλμάτων. 2.2.4 Model Checking και το Φαινόμενο της Έκρηξης του Χώρου Καταστάσεων Όπως αναφέρθηκε παραπάνω, ο αυτόματος έλεγχος μοντέλου έχει τη δυνατότητα να ελέγξει την ορθότητα όλων των πιθανών καταστάσεων και μονοπατιών εκτέλεσης ενός συστήματος με συστηματικό και εξαντλητικό τρόπο. Κάτι τέτοιο πρακτικά και για ρεαλιστικά συστήματα είναι αδύνατο, καθώς ο model checker λόγω του μεγάλου πλήθους καταστάσεων εξαντλεί όλους τους διαθέσιμους πόρους του συστήματος, όπως μνήμη, πριν προλάβει να ολοκληρώσει με επιτυχία τον έλεγχο. Στη χειρότερη περίπτωση το πλήθος των καταστάσεων αυξάνεται εκθετικά κάθε φορά που προστίθεται μία μεταβλητή ή ένα νήμα στο σύστημα. Αυτό το φαινόμενο ονομάζεται έκρηξη του χώρου καταστάσεων και αποτελεί ένα πολύ σημαντικό κομμάτι έρευνας σχετικά με το software model checking. - 9 -
Σχ. 2.1 Model Checking - 10 -
3. ΑΥΤΟΜΑΤΟΣ ΈΛΕΓΧΟΣ ΜΟΝΤΕΛΩΝ ΛΟΓΙΣΜΙΚΟΥ (SOFTWARE MODEL CHECKING) Η διαδικασία που εκτελεί ένας model checker μπορεί να περιγραφεί με χρήση όρων από τον τομέα των γράφων. Οι κόμβοι αναπαριστούν τις καταστάσεις του προγράμματος και οι ακμές αναπαριστούν τα αποτελέσματα επιλογών από εντολές συνθήκης στον κώδικα ή από διαφορετικές εισόδους, οι οποίες μπορούν να παράγουν πολλαπλά μονοπάτια για την συνέχεια της εκτέλεσης του προγράμματος. Η κατάσταση του προγράμματος περιγράφεται από τον συνδυασμό των τιμών τον μεταβλητών και των δεδομένων, την εντολή που εκτελείται την συγκεκριμένη χρονική στιγμή και από τις πληροφορίες σχετικά με το ποιο νήμα (thread) εκτελείται την συγκεκριμένη χρονική στιγμή. Η κατάσταση του προγράμματος αλλάζει με την πάροδο του χρόνου. Ένας γράφος καταστάσεων αναπαριστά όλες τις δυνατές καταστάσεις του προγράμματος και τις μεταβάσεις μεταξύ αυτών. Ο model checker προσπαθεί να ελέγξει αποδοτικά όλες τις καταστάσεις του προγράμματος και να ακολουθήσει όλες τις μεταβάσεις, καλύπτοντας όλα τα πιθανά μονοπάτια εκτέλεσης του προγράμματος. Ορισμένοι model checkers υποστηρίζουν αποθήκευση καταστάσεων που τους δίνει τη δυνατότητα να «θυμούνται» τις καταστάσεις που έχουν επισκεφτεί. Σε περίπτωση που ο model checker φτάσει σε μία κατάσταση που έχει ξαναεπισκεφτεί, μπορεί να οπισθοδρομήσει και να δοκιμάσει κάποια άλλη πιθανή επιλογή. Αυτό δίνει τη δυνατότητα στον model checker να είναι συστηματικός και να ελέγξει όλο το σύνολο καταστάσεων του προγράμματος ελέγχοντας ταυτόχρονα τις ιδιότητες που ενδιαφέρουν τον ελεγκτή. - 11 -
Σχ. 3.1 Παράδειγμα model checking 3.1 Model Checkers Υπάρχουν διάφοροι model checkers οι οποίοι αναλαμβάνουν να ελέγξουν αν ένα πρόγραμμα περιέχει σφάλματα. Κάθε ένας από αυτούς έχει τα δικά του πλεονεκτήματα και μειονεκτήματα που τον καθιστούν κατάλληλο ή ακατάλληλο ανάλογα με το τι θέλει να εξετάσει ο χρήστης. Τα σημαντικότερα από αυτά περιγράφονται συνοπτικά παρακάτω. 3.1.1 Java PathFinder To Java PathFinder (JPF) είναι ένας model checker που αναπτύχθηκε με σκοπό την επαλήθευση και τον έλεγχο προγραμμάτων γραμμένων σε Java. Αναπτύχθηκε από τη NASA το 1999. Είναι ανοιχτού κώδικα από το 2005 και υπάρχει διαθέσιμο στο SourceForge (link). Αποτελείται από μία ειδικά διαμορφωμένη Java Virtual Machine (JVM) που διερμηνεύειι bytecode, σε συνδυασμό με ένα μηχανισμό εύρεσης καταστάσεων προκειμένου να εξετάζει όλες τις πιθανές καταστάσεις (συμπεριλαμβάνοντας όλα τα νήματα) του προγράμματος. Το ίδιο το JPF είναι γραμμένο σε Java και η αρχιτεκτονική του είναι διαμορφωμένη έτσι ώστε να υποστηρίζει την εύκολη προσθήκη νέων χαρακτηριστικών. Επειδή το JPF εκτελείται - 12 -
σε bytecode έχει τη δυνατότητα να ελέγχει προγράμματα σε επίπεδο κώδικα αλλά και bytecode. Ο πιο άμεσος τρόπος για τον καθορισμό και τον έλεγχο properties στο JPF είναι με χρήση assertions στο πρόγραμμα προς εξέταση. Το αρνητικό σε αυτή τη μέθοδο είναι ότι απαιτεί επέμβαση στον κώδικα του προγράμματος και αυτό μπορεί να οδηγήσει σε σημαντική αύξηση του χώρου καταστάσεων. Ένας άλλος τρόπος είναι με επέμβαση στα αρχεία gov.nasa.jpf.property ή gov.nasa.jpf.genericproperty τα οποία σχετίζονται με τον καθορισμό των properties που θέλουμε να εξετάσουμε. Σχ. 3.2 Η αρχιτεκτονική του Java PathFinder 3.1.2 SPIN Ο SPIN, νικητήριο πρόγραμμα του ACM Software Systems Award 2001, είναι ένας από τους καλύτερους model checkers στον τομέα του ελέγχου της λογικής συνοχής ταυτοχρονισμένων και κατανεμημένων συστημάτων, ειδικότερα για πρωτόκολλα - 13 -
επικοινωνίας δεδομένων. Το SPIN γράφτηκε από τον Gerard J. Holzmann και άλλους προγραμματιστές της ομάδας Computing Sciences Research Center στα Bell Labs στις αρχές του 1980. Υπάρχει διαθέσιμο ελεύθερα από το 1991 και από τότε αναβαθμίζεται συνεχώς. Από το 1995 και κάθε χρόνο διοργανώνονται συνέδρια για τους χρήστες του SPIΝ, τους ερευνητές αλλά και γενικά για οποιονδήποτε ενδιαφέρεται για το model checking. Ο βασικός στόχος του SPIN είναι η απόδειξη της σωστής λειτουργίας των αλληλεπιδράσεων μεταξύ των διεργασιών και όχι των εσωτερικών διαδικασιών της διεργασίας. Οι διεργασίες αναπαριστούν τα συστατικά στοιχεία ενός συστήματος που επικοινωνούν μεταξύ τους. Η γλώσσα μοντελοποίησης που χρησιμοποιεί το SPIN ονομάζεται Promela (Process Meta Language). Για την ακρίβεια το πλήρες όνομα του SPIN είναι Simple Promela Interpreter. Είναι μία γλώσσα που μοιάζει με την C και παρέχει πολλές δυνατότητες ταυτοχρονισμού, γεγονός που της δίνει τη δυνατότητα δημιουργίας μοντέλων για κατανεμημένα συστήματα. Έχει τη δυνατότητα δυναμικής δημιουργίας ταυτόχρονων διεργασιών που επικοινωνούν μεταξύ τους, συγχρονισμένα ή ασύγχρονα, μέσω μηνυμάτων. 3.1.3 Bandera Ο στόχος του εργαλείου BANDERA είναι να ενσωματωθεί σε κάποια υπάρχουσα τεχνική επεξεργασίας γλώσσας προγραμματισμού με νέες τεχνικές και να παρέχει αυτόματη εξαγωγή ασφαλών, συμπτυγμένων και πεπερασμένων καταστάσεων μοντέλων ώστε να είναι κατάλληλα για επαλήθευση με χρήση κώδικα Java. Παρόλο που ο στόχος είναι η απολύτως αυτόματη εξαγωγή του μοντέλου ενός συστήματος λογισμικού, το BANDERA λαμβάνει σαν δεδομένο ότι υπάρχει καθοδήγηση από κάποιον αναλυτή λογισμικού. Όπως φαίνεται στο σχήμα 3.3, τα εργαλεία του BANDERA είναι σχεδιασμένα με μία αρχιτεκτονική που επιτρέπει την προσθήκη νέων συστατικών ανάλυσης. - 14 -
Σχ. 3.3 Τα εργαλεία του BANDERA 3.1.4 BLAST O Berkeley Lazy Abstraction Software Verification Tool (BLAST) είναι ένας model checker που εξετάζει προγράμματα γραμμένα σε C. Ο στόχος του BLAST είναι να μπορεί να εξετάσει αν ένα λογισμικό μπορεί να ανταποκριθεί στις απαιτήσεις που έχουν τεθεί. Το BLAST χρησιμοποιεί αντιπαραδείγματα προκειμένου να κατασκευάσει ένα αφαιρετικό μοντέλο, το οποίο και εξετάζεται. Το αφαιρετικό μοντέλο κατασκευάζεται καθώς τρέχει το πρόγραμμα (on-the-fly), και στο βαθμό ακρίβειας που είναι απαραίτητο. - 15 -
3.1.5 Bogor Το Bogor (Robby, Dwyer και Hattcliff 2006) είναι ένα πλαίσιο λογισμικού modelchecking το οποίο περιλαμβάνει: Αλγορίθμους ελέγχου σφαλμάτων λογισμικού Αναπαραστάσεις Μία διεπαφή χρήστη, σχεδιασμένη έτσι ώστε να υποστηρίζει γενικού σκοπού αλλά και συγκεκριμένου σκοπού έλεγχο λογισμικού Το Bogor υποστηρίζει αντικειμενοστραφείς χαρακτηριστικά όπως δυναμική δημιουργία νημάτων (threads) και αντικειμένων, κληρονομικότητα αντικειμένων, πλήρως αφαιρετικές (virtual) μεθόδους, εξαιρέσεις και αυτόματη ελευθέρωση χώρου (garbage collection). Το Bogor μπορεί να επεκταθεί με νέες εκφράσεις και εντολές, νέους αλγόριθμους (π.χ. σχετικά με την αναζήτηση στον χώρο καταστάσεων κ.α.) και νέες βελτιστοποιήσεις (π.χ. ευριστικούς αλγόριθμους κ.α.). Σχ. 3.4 Η αρχιτεκτονική του Bogor - 16 -
3.1.6 Εργαλείο BOOP Το εργαλείο BOOP αναπτύχθηκε από το Institute για Software Technology στο Graz University of Technology. Βασίζεται στο σχέδιο SLAM και χρησιμοποιεί τις ίδιες τεχνικές επαλήθευσης μέσω αφαίρεσης και βελτιστοποίησης για προγράμματα γραμμένα σε C. Το BOOP ακολουθεί τρείς φάσεις: 1. Αφαίρεση χρησιμοποιώντας την απόδειξη θεωρημάτων 2. Model Checking του αφαιρετικού προγράμματος, προκειμένου να αναζητήσει εσφαλμένα μονοπάτια εκτέλεσης 3. Βελτιστοποίηση. Αν ο model checker βρήκε κάποιο μονοπάτι που οδηγεί σε σφάλμα, το οποίο υπάρχει και στο αρχικό πρόγραμμα, το αναφέρει στον χρήστη. Αν το λανθασμένο μονοπάτι δεν υπάρχει στο αρχικό πρόγραμμα, η διαδικασία επαναλαμβάνεται προσθέτοντας μεγαλύτερη λεπτομέρεια στο αφαιρετικό μοντέλο. Το BOOP toolkit είναι διαθέσιμο για download από το SourceForge. 3.1.7 LURCH Ο LURCH (Menzies 2004) χρησιμοποιεί τυχαίους (random) αλγόριθμους για να εξετάσει τον χώρο καταστάσεων ενός συστήματος. Επειδή η έρευνα είναι τυχαία, δεν εγγυάται ότι θα εξεταστούν εξαντλητικά όλες οι πιθανές καταστάσεις του συστήματος, επομένως το LURCH δεν μπορεί να χρησιμοποιηθεί για την επαλήθευση του συστήματος. Παρόλα αυτά, έχει τη δυνατότητα να εντοπίζει σφάλματα, επομένως μπορεί να χρησιμοποιηθεί στην εκσφαλμάτωση ενός συστήματος. Το LURCH μπορεί να εξετάσει ταυτοχρονισμένα συστήματα πεπερασμένων καταστάσεων γραμμένα σε γλώσσα C. Είναι σημαντικό να πούμε ότι το LURCH χαρακτηρίζεται από την μεγάλη του ταχύτητα και από το γεγονός ότι - 17 -
τείνει να χρησιμοποιεί σταθερή ποσότητα πόρων συστήματος. Για τους παραπάνω λόγους συνήθως χρησιμοποιείται σε μεγάλα συστήματα. 3.1.8 Verisoft Ο Verisoft είναι ένας model checker για συστήματα γραμμένα σε γλώσσα C και C++. Αναπτύχθηκε το 1996 από τα Bell Labs. Χρησιμοποιείται σε πολύ μεγάλα συστήματα τηλεπικοινωνιών και υπάρχει διαθέσιμο στην ιστοσελίδα του Verisoft [13]. Το συγκεκριμένο πακέτο περιλαμβάνει μία έκδοση του για ανάλυση προγραμμάτων, γραμμένα σε C ή C++, των οποίων οι διεργασίες επικοινωνούν μεταξύ τους μέσω ενός προκαθορισμένου τύπου αντικειμένων επικοινωνίας. Επειδή δεν αποθηκεύει τις καταστάσεις που έχει εξετάσει, χρησιμοποιεί έξυπνους αλγόριθμους αναζήτησης με μείωση του χώρου καταστάσεων (Partial Order Reduction) έτσι ώστε να αποφύγει την επανεξέταση καταστάσεων και να μπορεί να εγγυηθεί τον πλήρη έλεγχο του χώρου καταστάσεων του συστήματος, μέχρι κάποιο βάθος. Ο Verisoft μπορεί να εντοπίσει τέσσερα βασικά είδη λαθών: Deadlocks. Divergences. Προκύπτουν όταν μία διεργασία δεν επιχειρεί να επικοινωνήσει με το υπόλοιπο σύστημα για κάποιο καθορισμένο, από τον χρήστη, χρονικό διάστημα. Livelocks. Προκύπτουν όταν μία διεργασία είναι φραγμένη κατά τη διάρκεια μίας συνεχόμενης σειράς επιτυχημένων καταστάσεων, πλήθους μεγαλύτερου από κάποιο καθορισμένο (από τον χρήστη). Assertions, τα οποία έχουν οριστεί προηγουμένως χρησιμοποιώντας την συνάρτηση VX_assert_(Boolean_expr). - 18 -
4. JAVA PATHFINDER (JPF) Στο παραπάνω κεφάλαιο αναφέρθηκαν κάποια βασικά πράγματα για το Java PathFinder. Σε αυτό το κεφάλαιο θα αναλυθεί η αρχιτεκτονική του (βλ. Σχ.3.2) και οι δυνατότητες που διαθέτει με σκοπό την κατανόηση της λειτουργίας του. 4.1 Property Checker Προκειμένου να ελεγχθούν κάποιες συγκεκριμένες ιδιότητες/καταστάσεις ενός προγράμματος με τη βοήθεια του JPF υπάρχουν τρείς τρόποι: Με assertions Μέσω της κλάσης Property Με χρήση Search και VM Listener 4.1.1 Assertions Η χρήση των assertion μέσα στο πρόγραμμα είναι ο πιο απλός και άμεσος τρόπος για να ελεγχθούν κάποια στοιχεία τα οποία εξαρτώνται μόνο από τις τιμές των δεδομένων του προγράμματος. Το αρνητικό αυτής της μεθόδου είναι ότι απαιτείτε επέμβαση στον κώδικα του προγράμματος προς εξέταση και αυτό μπορεί να έχει ως αποτέλεσμα την σημαντική αύξηση του χώρου καταστάσεων. 4.1.2 Property Class Στιγμιότυπα της κλάσης Property χρησιμοποιούνται για να περιβάλουν τα στοιχεία που πρόκειται να εξεταστούν. Τα στιγμιότυπα μπορούν να ρυθμιστούν στατικά (μέσω του αρχείου search.properties) ή δυναμικά (μέσω της εντολής - 19 -
jpf.getsearch().add Property), και ελέγχονται από το αντικείμενο Search μετά από κάθε συναλλαγή. Σε περίπτωση που μία μέθοδος ελέγχου επιστρέψει σφάλμα και ζητηθεί τερματισμός του προγράμματος, η διεργασία εύρεσης (search process) τερματίζεται και εκτυπώνονται όλα τα εσφαλμένα στοιχεία (συνήθως αυτά αποτελούν και το μονοπάτι εύρεσης του λάθους). Το JPF περιλαμβάνει τις παρακάτω Property κλάσεις: NotDeadlockedProperty ελέγχει αν υπάρχει κάποιο thread που εξακολουθεί να εκτελείται ενώ δε θα έπρεπε NoAssertionViolatedProperty εξετάζει αν κάποιος έλεγχος με χρήση assertion έχει αποτύχει NoUncaughtExceptionsProperty εξετάζει αν κάποια εξαίρεση δεν αντιμετωπίστηκε σωστά από το πρόγραμμα Υπάρχει η δυνατότητα να προστεθούν επιπλέον κλάσεις ελέγχου στοιχείων υλοποιώντας τις συναρτήσεις της κλάσης Property και στη συνέχεια εισάγοντας τη νέα κλάση στο αρχείο default.properties. Η μορφή του αρχείου είναι ως εξής: search.properties=\ gov.nasa.jpf.jvm.notdeadlockedpropery:\ gov.nasa.jpf.jvm.noassertionviolatedproperty:\ gov.nasa.jpf.jvm.nouncaughtexceptionsproperty:\ x.y.z.mynewproperty 4.1.3 Search Listener και VMListener Τους χρησιμοποιούμε για να υλοποιήσουμε πιο περίπλοκους ελέγχους οι οποίοι χρειάζονται περισσότερες πληροφορίες από αυτές που είναι διαθέσιμες μετά από μία μετάβαση. Το μεγάλο πλήθος των callbacks δίνει τη δυνατότητα στους listeners να παρακολουθούν όλες τις λειτουργίες που εκτελεί το JPF και να τις μεταφράζουν σε - 20 -
εσωτερικές καταστάσεις. Ο έλεγχος εκτέλεσης του JPF μπορεί να επιτευχθεί με δύο τρόπους: Υλοποιώντας την κατάλληλη διασύνδεση listener και την διασύνδεση του property και τέλος εισάγοντάς την στο JPF με χρήση της μεθόδου Search.addProperty(). Έτσι το JPF θα ελέγξει αυτόματα για οποιαδήποτε αποτυχία ελέγχου τερματισμού ανάμεσα στις καταστάσεις του προγράμματος Καλώντας τη μέθοδο Search.Terminate(), έτσι ώστε να σταματήσει ο έλεγχος για νέες καταστάσεις. Αυτό μπορεί να γίνει από οποιοδήποτε σημείο του listener αλλά δεν εξάγει αυτόματα αναφορά σφαλμάτων. Κάτι τέτοιο θα πρέπει να γίνει ξεχωριστά από τον ίδιο τον listener. Για να είναι πιο εύκολη η προσθήκη νέων listener το JPF περιλαμβάνει την κλάση PropertyListenerAdapter, η οποία μπορεί να χρησιμοποιηθεί σα βάση για τη δημιουργία νέων listeners. Το μόνο που χρειάζεται είναι η υλοποίηση των απαραίτητων μεθόδων. Η προσθήκη του νέου listener γίνεται από το jpf.listener: είτε μέσω της γραμμής εντολών, + jpf.listener = MyPropertyListener είτε μέσα από το αρχείο property (όπως το jpf.properties) jpf.listener = :MyListener:. 4.2 Οργανώνοντας το πρόγραμμα με χρήση της κλάσης Verify Όπως έχει αναφερθεί παραπάνω, το σημαντικότερο πρόβλημα στο model checking είναι το φαινόμενο της έκρηξης του χώρου καταστάσεων. Προκειμένου να μειωθεί το φαινόμενο αυτό υπάρχουν κάποιες τεχνικές που περιλαμβάνονται στην κλάση Verify. 4.2.1 Μη ντετερμινιστικοί choice generators Η βασική ιδέα είναι ο choice generator να δέχεται μία μη ντετερμινιστική είσοδο δεδομένων και στη συνέχεια να ελέγχει συστηματικά όλες τις τιμές που μπορούν να - 21 -
πάρουν τα δεδομένα αυτά. Για παράδειγμα, για μία μεταβλητή τύπου Boolean το πρόγραμμα εξετάζεται σε περίπτωση που η τιμή της είναι true αλλά και false. Όμως οι μη ντετερμινιστικοί choice generators δεν περιορίζονται μόνο σε μεταβλητές που είναι από μόνες τους φραγμένες, όπως οι Boolean μεταβλητές. Το JPF υποστηρίζει τροποποιημένους ευριστικούς αλγόριθμους, βασισμένους στους choice generators, οι οποίοι περιορίζουν το εύρος των δυνατών τιμών μίας μεταβλητής μέσα από το property file ξεχωριστά για κάθε εφαρμογή. Σχ. 4.1 Παράδειγμα περιορισμού εύρους τιμών Οι ευριστικοί choice generators αναλύονται περισσότερο παρακάτω. 4.2.2 Search Pruning Με το search pruning ουσιαστικά κλαδεύουμε τον γράφο του χώρου καταστάσεων προκειμένου να μην εξετάσουμε κάποιες καταστάσεις. Εφαρμόζεται όταν είναι εμφανές ότι από κάποιο σημείο και μετά κάποιες τιμές δεν επηρεάζουν το κομμάτι που θέλουμε να εξεταστεί οπότε μειώνεται ο χώρος καταστάσεων. - 22 -
Σχ. 4.2 Search Pruning 4.2.3 State annotation Βασίζεται σε έναν συγκεκριμένο συνδυασμό τιμών που μπορεί να προκύψουν από την εκτέλεση της εφαρμογής. Δε σταματάει την εκτέλεση αλλά αποθηκεύει ένα «ενδιαφέρον» στοιχείο (σημείωση) σχετικά με την συγκεκριμένη κατάσταση. Αυτή η κατηγορία γίνεται ολοένα και λιγότερο σημαντική καθώς οι Search και VM listeners έχουν την δυνατότητα αποθήκευσης αντικειμένων σαν σημειώσεις. Σχ. 4.3 State annotation 4.2.4. Verification log output Είναι η πιο απλή κατηγορία και χρησιμοποιείται για τη διάκριση της εξόδου του προγράμματος στην περίπτωση μίας κανονικής εκτέλεσης και μίας εκτέλεσης επαλήθευσης. Όπως είναι λογικό, αποτελείται από συναρτήσεις εκτύπωσης print(). 4.2.5 Explicit atomicity control Η μέθοδος αυτή χρησιμοποιείται για τον περιορισμό του πλήθους των νημάτων (threads) που πρέπει να εξετάσει το JPF. Αν και μπορεί με αυτόν τον τρόπο να προσπεραστεί κάποιο σφάλμα, είναι συχνά ο μοναδικός τρόπος για τον περιορισμό - 23 -
του χώρου καταστάσεων ώστε να μπορέσει το JPF να ολοκληρώσει τον έλεγχο της εφαρμογής. Η χρήση της μεθόδου αυτής έχει μειωθεί καθώς πλέον χρησιμοποιείται η on-the-fly Partial Order Reduction (POR) για μείωση του χώρου καταστάσεων. Υπάρχουν όμως περιπτώσεις εφαρμογών που παρουσιάζουν πρόβλημα στη χρήση της POR και η explicit atomicity control αποτελεί τη μόνη λύση. Σχ. 4.4 Explicit atomicity control 4.3 Βασικά τμήματα του JPF Το JPF σχεδιάστηκε έχοντας δύο βασικά στοιχεία: την Java Virtual Machine (JVM) και το Search αντικείμενο (βλ. Σχ.3.2). 4.3.1 Java Virtual Machine H JVM είναι ο δημιουργός καταστάσεων της Java. Δημιουργεί αναπαραστάσεις που μπορούν να: Ελεγχθούν για ισότητα (αν έχει επισκεφτεί την ίδια κατάσταση προηγουμένως) Τοποθετηθούν σε ουρά (όπως τα theads) Αποθηκευτούν Ανακτηθούν - 24 -
Υπάρχουν τρεις βασικές μέθοδοι που χρησιμοποιεί η JVM (σε συνδυασμό με το VM- Search): Forward δημιουργεί την επόμενη κατάσταση, αναφέρει αν η κατάσταση έχει επιτύχει. Αν ναι, τότε την καταχωρεί σε μία στοίβα οπισθοδρόμησης για αποτελεσματική επαναφορά Backtrack επαναφέρει την τελευταία κατάσταση που υπάρχει αποθηκευμένη στη στοίβα επαναφοράς RestoreState επαναφέρει μία κατάσταση, η οποία δεν είναι απαραίτητο να είναι στην στοίβα επαναφοράς 4.3.2 Το Search αντικείμενο Το Search αντικείμενο είναι υπεύθυνο για την επιλογή της κατάστασης από την οποία θα συνεχιστεί η εκτέλεση του προγράμματος από το JPF. Αυτό γίνεται είτε επιλέγοντας ποιά κατάσταση θα δημιουργήσει η JVM είτε λέγοντας της να επιστρέψει (backtrack) σε μία προηγούμενη κατάσταση. Τα αντικείμενα Search μπορούν να χαρακτηριστούν και σαν «οδηγοί» για τα JVM αντικείμενα. 4.4 On-the-fly Partial Order Reduction (POR) Ο μεγάλος αριθμός των συνδυασμών εκτέλεσης που μπορεί να έχει ένα πρόγραμμα είναι η αιτία της έκρηξης του χώρου καταστάσεων. Ευτυχώς, πρακτικά δε χρειάζεται να εξεταστούν όλες αυτές τις καταστάσεις. Ο αριθμός των καταστάσεων μπορεί να μειωθεί πολύ ομαδοποιώντας όλες τις διαδικασίες σε ένα thread που δε μπορεί να επηρεάσει το πρόγραμμα. Αυτή η τεχνική ονομάζεται Partial Order Reduction (POR) και μειώνει περισσότερο από 70% το χώρο καταστάσεων. Το JPF υλοποιεί ένα on-the-fly POR το οποίο αυτόματα κατά την εκτέλεση του προγράμματος επιλέγει ποιες διαδικασίες πρέπει να ληφθούν σαν όρια μεταβάσεων. Έτσι το JPF εκτελεί όλες τις διαδικασίες μέχρις ότου: - 25 -
1. Η επόμενη διαδικασία να είναι μία από αυτές που μπορούν να επηρεάσουν τα όρια των μεταβάσεων και επομένως πρέπει να εξεταστεί 2. Η επόμενη διαδικασία χρειάζεται κάποια είσοδο για να συνεχιστεί το πρόγραμμα, επομένως πρέπει να εξεταστούν όλες οι πιθανές είσοδοι. Κάθε εντολή (instruction) εξετάζεται από την υποκλάση gov.nasa.jpf.instruction, σχετικά με την επιρροή της στη ροή εκτέλεσης του προγράμματος, με βάση τους παρακάτω παράγοντες: Instruction Type επειδή η JVM χρησιμοποιεί στοίβα, μόνο περίπου το 10% των εντολών μπορούν να επηρεάσουν την ροή εκτέλεσης του προγράμματος, δηλαδή να επηρεάσουν τα όρια των μεταβάσεων ανάμεσα στα νήματα. Οι εντολές που παρουσιάζουν ενδιαφέρον είναι εντολές άμεσου συγχρονισμού (monitorenter, monitorexit, invokex σε συνχρονισμένες μεθόδους), πρόσβαση σε πεδία δεδομένων (putx, getx), πρόσβαση σε πίνακες στοιχείων (Xaload, Xastore) και εντολές αλλαγής κατάστασης για νήματα (start(), sleep(), yield(), join() ) και αντικείμενα (wait(), notify() ). Object Reachability εκτός από εντολές άμεσου συγχρονισμού, η πρόσβαση σε πεδία δεδομένων είναι ο κύριος λόγος συγκρούσεων μεταξύ νημάτων. Παρόλα αυτά, δε χρειάζεται να ληφθούν υπόψη όλες οι εντολές putx/getx, αλλά μόνο αυτές που αναφέρονται σε αντικείμενα που χρησιμοποιούνται από τουλάχιστον δύο νήματα ώστε να προκύψει πρόβλημα ανταγωνισμού. Thread Information ακόμα και αν τα δύο παραπάνω δείχνουν ότι μία εντολή επηρεάζει τη ροή εκτέλεσης του προγράμματος, δε χρειάζεται να επέμβουμε αν την στιγμή που εκτελείται δεν υπάρχει κάποιο άλλο νήμα που εκτελείται ταυτόχρονα. - 26 -
4.5 Search και VM Listeners Οι listeners υπάρχουν έτσι ώστε να μπορούμε να προσθέσουμε κάποιες επιπλέον λειτουργίες ελέγχου χωρίς να χρειάζεται να αλλάξουμε το πρόγραμμα. Οι απαραίτητες επεκτάσεις γίνονται με τη χρήση ενός listener, ο οποίος μπορεί να αλληλεπιδρά με αντικείμενα και να εξάγει πληροφορίες από αυτά ή ακόμα και να επηρεάζει τη συμπεριφορά τους. Τα αντικείμενα γνωρίζουν ανά πάσα στιγμή ποιοι listeners είναι ενεργοί. Υπάρχουν τρία διαφορετικά επίπεδα εξαγωγής πληροφορίας από έναν listener: 1. Generic είναι εκτός του προγράμματος και χρησιμοποιεί πληροφορίες που είναι δημόσια διαθέσιμες μέσω του gov.nasa.jpf.search/vm 2. Search-specific ο listener μπορεί να είναι είτε Search είτε VM και καθορίζεται από μία παράμετρο. Χρησιμοποιώντας το δημόσιο API εξάγει πληροφορίες σχετικά με την υλοποίηση του προγράμματος 3. Internal o listener έχει πρόσβαση στις ιδιωτικές πληροφορίες του πακέτου (package) που υπάγεται το πρόγραμμα 4.5.1 Search Listener Οι search listeners χρησιμοποιούνται για να αναπαραστήσουν τον χώρο καταστάσεων της διεργασίας όπως για παράδειγμα φτιάχνοντας γραφικές παραστάσεις. Επίσης, παρέχουν ειδοποιήσεις για όλες τις κύριες λειτουργίες. Στο παρακάτω σχήμα φαίνεται η μορφή ενός Search Listener: - 27 -
Σχ. 4.5 Search Listener Interface Για τον standard Depth First Search (DFS), οι υλοποιήσεις του listener μπορούν να βασιστούν στο παρακάτω μοντέλο ειδοποιήσεων: - 28 -
Σχ. 4.6 Depth First Search Notification Automaton 4.5.2 VM Listener Οι VM Listeners συνήθως παρακολουθούν κάποιες συγκεκριμένες λειτουργίες της VM, όπως PUTFIELD και GETFIELD, λειτουργίες ικανές να προκαλέσουν συνθήκες ανταγωνισμού. Όπως οι Search Listeners, έτσι και οι VM έχουν τη δικιά τους μορφή: - 29 -
Σχ. 4.7 VM Listener Inteface - 30 -
4.5.3 Ρυθμίσεις Οι ρυθμίσεις στους listeners μπορούν να γίνουν με δύο τρόπους: (1) από το αρχείο properties, και (2) δυναμικά κατά την εκτέλεση του προγράμματος. 1. Properties η property «jpf.listener χρησιμοποιείται για να οριστεί μία λίστα από listeners: jpf.listener = x.y.myfirstlistener:x.z.mysecondlistener 2. Δυναμικά συνήθως γίνεται από εφαρμογές που έχουν ενσωματωμένο το JPF (βλ. Σχ.4.8). Σχ. 4.8 Dynamic Listener Configuration 4.6.Το Model Java Interface (MJI) Παρόλο που το JPF είναι ουσιαστικά μία Java εφαρμογή, μπορεί να θεωρηθεί και σαν μία Java Virtual Machine (JVM). Το αποτέλεσμα αυτού είναι ότι τα αρχεία *.class αντιμετωπίζονται με δύο διαφορετικούς τρόπους σε μία JVM που εκτελεί το JPF Σαν συνηθισμένες κλάσεις της Java, τις οποίες διαχειρίζεται και εκτελεί η JVM του host Σαν μοντελοποιημένες κλάσεις, τις οποίες διαχειρίζεται και επεξεργάζεται η JVM του JPF - 31 -
Παρόλο που το MJI υποστηρίζει ένα μεγάλο πλήθος εφαρμογών, υπάρχουν τρεις βασικοί λόγοι για τους οποίους χρειαζόμαστε την ανάθεση της εκτέλεσης μέρους του κώδικα από την JVM του host: Οι βασικές μέθοδοι της JVM είναι απαραίτητες γιατί χωρίς αυτές το JPF θα αποτύχαινε να εξετάσει προγράμματα βασισμένα σε αυτές. Η διαχείριση κάποιων συναρτήσεων της βασικής βιβλιοθήκης είναι απαραίτητη γιατί επηρεάζει εσωτερικά μοντέλα κλάσεων, αντικειμένων και νημάτων. Πρέπει να σημειωθεί ότι το MJI μπορεί να χρησιμοποιηθεί για την επέκταση των δυνατοτήτων του JPF χωρίς επέμβαση στην υλοποίησή του. Αναθέτοντας την εκτέλεση ορισμένων κομματιών του κώδικα προς εξέταση στην JVM του host, επιτυγχάνεται μεγάλη μείωση του χώρου καταστάσεων, αφού η host JVM δεν εξετάζει όλες τις πιθανές καταστάσεις του προγράμματος και εκτελεί το συγκεκριμένο κομμάτι με τον τρόπο που θέλει ο χρήστης. 4.7 Choice Generators Ένα από τα σημαντικότερα πράγματα του software model checking είναι να γίνουν οι σωστές επιλογές, έτσι ώστε να ελεγχθούν όλες οι «ενδιαφέρουσες» καταστάσεις, μέσα στα περιθώρια που δίνουν οι πόροι του model checker και του περιβάλλοντος εκτέλεσης. Στο JPF για να ελεγχτεί συστηματικά ο χώρος των καταστάσεων χρησιμοποιούνται choice generators. Στο JPF υποστηρίζεται η δυνατότητα για «τυχαία» επιλογή μίας κατάστασης έτσι ώστε να συνεχιστεί η εκτέλεση του προγράμματος. Αυτή η μέθοδος είχε καλά αποτελέσματα για μικρά πεδία τιμών όπως Boolean. Όταν όμως η επεξεργασία μεγάλων πεδίων τιμών, όπως int, ήταν απαραίτητη το πρόγραμμα αντιμετώπιζε προβλήματα. Ειδικά στην περίπτωση που το πεδίο τιμών δεν είναι πεπερασμένο, όπως αυτό στην περίπτωση του double, το πρόγραμμα αποτύχαινε τελείως. Προκειμένου να αντιμετωπιστεί αυτό το πρόβλημα, πρέπει να φύγουμε από τον ιδεατό κόσμο του model checking, όπου ελέγχονται όλες τις περιπτώσεις, και να - 32 -
χρησιμοποιηθούν ευριστικοί αλγορίθμοι έτσι ώστε το πλήθος των επιλογών να γίνει πεπερασμένο και επεξεργάσιμο. Όμως, ο κατάλληλος ευριστικός αλγόριθμος εξαρτάται άμεσα από την εφαρμογή που πρόκειται να εξεταστεί και το πεδίο τιμών. Επομένως θα ήταν κακή ιδέα να γραφτεί ένας αλγόριθμος για όλες τις εφαρμογές από τη στιγμή που δε θα ήταν αποτελεσματικός. Αυτά οδηγούν σε ορισμένες απαιτήσεις που πρέπει να ικανοποιεί ο μηχανισμός επιλογής του JPF: 1. Οι μηχανισμοί επιλογής πρέπει να είναι ανεξάρτητοι. Για παράδειγμα, οι επιλογές σχετικά με νήματα θα πρέπει να γίνονται με διαφορετικά κριτήρια από αυτά για επιλογές σχετικά με δεδομένα, επιλογές σχετικά με double πρέπει να διαφέρουν από αυτές σχετικά με int. 2. Τα πεδία επιλογών και η απαρίθμησή τους πρέπει να περιορίζονται σε αντικείμενα συγκεκριμένου τύπου. Η Virtual Machine (VM) θα πρέπει να γνωρίζει μόνο τους βασικούς τύπους τιμών και σε διαφορετική περίπτωση θα πρέπει να χρησιμοποιεί ένα γενικό interface για την διαδικασία της επιλογής. 3. Η επιλογή των ευριστικών αλγορίθμων και η παραμετροποίηση του αντικειμένου του Choice Generator θα πρέπει να μπορεί να γίνεται κατά τη διάρκεια της εκτέλεσης του προγράμματος. - 33 -
5. ΕΓΚΑΤΑΣΤΑΣΗ ΚΑΙ ΧΡΗΣΗ ΤΟΥ JAVA PATHFINDER 5.1 Εγκατάσταση του Java PathFinder (2 τρόποι) Η εγκατάσταση του JPF μπορεί να γίνει με δύο τρόπους. Ο ένας είναι από το command line με χρήση του Ant και του J2SE, και ο άλλος με τη βοήθεια του Eclipse. Όπως είναι λογικό, ο πρώτος τρόπος είναι πολύ πιο δύσκολος στην εφαρμογή του αλλά μέχρι πρόσφατα ήταν ο μοναδικός. 5.1.1 Από το Command Line Για την εγκατάσταση του JPF απαιτούνται ορισμένα προγράμματα τα οποία πρέπει να υπάρχουν στο λειτουργικό σύστημα. Αυτά είναι: JavaTM 2 Platform, Standard Edition (J2SE TM ), προτεινόμενη έκδοση είναι η SDK 1.4.2 Apache Ant, πρόγραμμα για την μεταγλώττιση των αρχείων του JPF Στη συνέχεια πρέπει να οριστεί η μεταβλητή περιβάλλοντος JAVA_HOME στον φάκελο που έχει εγκατασταθεί το J2SE. Για να γίνει αυτό ακολουθούμε τα παρακάτω βήματα: 1. Δεξί κλικ στο εικονίδιο «Ο Υπολογιστής μου», κλικ στην επιλογή «Ιδιότητες» 2. Επιλογή της ετικέτας «Για προχωρημένους» 3. Κλικ στην επιλογή «Μεταβλητές Περιβάλλοντος» (βλ. Σχ. 5.1) - 34 -
Σχ. 5.1 Επιλογή των Μεταβλητών Περιβάλλοντος 4. Στη μεταβλητή συστήματος «JAVA_HOME» (αν δεν υπάρχει, την δημιουργούμε) ορίζουμε σαν τιμή τo path του φακέλου που έγινε η εγκατάσταση του J2SE (βλ. Σχ. 5.2) - 35 -
Σχ. 5.2 Ορισμός της μεταβλητής περιβάλλοντος «JAVA_HOME» 5. Στη συνέχεια πρέπει να οριστεί η μεταβλητή περιβάλλοντος «ANT_HOME» (αν δεν υπάρχει την φτιάχνουμε), με τιμή το Path\ant_bin (όπου Path ο φάκελος στον οποίο είναι το Ant). Υποθέτουμε ότι το Ant είναι στον φάκελο C:\Ant. (βλ. Σχ. 5.3) - 36 -
Σχ. 5.3 Ορισμός της μεταβλητής περιβάλλοντος «ANT_HOME» 6. Τέλος, προκειμένου να εκτελεστεί το ant από το command line πρέπει να προστεθεί στο τέλος της μεταβλητής περιβάλλοντος «Path», βάζοντας «;», το path του φακέλου ant\bin. (βλ. Σχ. 5.4) - 37 -
Σχ. 5.4 Επεξεργασία της μεταβλητής «Path» Προκειμένου να εξετάσουμε αν η εγκατάσταση του Ant έγινε σωστά, πηγαίνουμε στο command line και γράφουμε: >ant version. Αν όλα έχουν πάει θα πρέπει να βγάλει κάτι παρόμοιο με το σχήμα 5.5. Σχ. 5.5 >ant -version Αφού ολοκληρωθεί η παραπάνω διαδικασία, πρέπει να γίνει compile το JPF. Για να γίνει αυτό, πηγαίνουμε στο φάκελο του JPF (συνήθως jpf release) από το - 38 -
command line και εκτελούμε την εντολή: >ant compile. Αφού εκτελέσει πολλές λειτουργίες το τελικό αποτέλεσμα θα πρέπει να είναι όπως το σχήμα 5.6. Σχ. 5.6 >ant compile Αφού γίνει Build το JPF πρέπει να κάνουμε compile και τα προγράμματα που θέλουμε να εξετάσουμε. Στο παράδειγμά μας θα χρησιμοποιήσουμε τα παραδείγματα που έχει από μόνο του το JPF. Για να κάνουμε compile εκτελούμε την εντολή: >ant compile-examples. Μετά την επιτυχή εκτέλεση της εντολής το αποτέλεσμα θα πρέπει να είναι όπως το σχήμα 5.7. Σχ. 5.7 >ant compile-examples - 39 -
Αφού έχει γίνει επιτυχώς το compile των παραδειγμάτων, μπορούμε να τα εξετάσουμε. Σαν παράδειγμα θα χρησιμοποιήσουμε το HelloWorld. Για να το εξετάσουμε εκτελούμε την εντολή: >jpf HelloWorld. Το αποτέλεσμα της εκτέλεσης πρέπει να είναι το παρακάτω (Σχ. 5.8): Σχ. 5.8 >jpf HelloWorld Όπως βλέπουμε στο πρόγραμμα HelloWorld δεν βρέθηκε κάποιο σφάλμα. Αν τρέξουμε το παράδειγμα Sorter1 με την εντολή >jpf sorter1, θα δούμε διαφορετικά αποτελέσματα (Σχ. 5.9). - 40 -
Σχ. 5.9 >jpf sorter1 Όπως φαίνεται στο σχήμα 5.9 βρέθηκε ένα σφάλμα, τύπου uncaught exception, στο πρόγραμμα sorter1. Το JPF εμφανίζει ένα μονοπάτι εκτέλεσης που οδήγησε στο σφάλμα. Ξεκινώντας από το τέλος βλέπουμε ότι το σφάλμα προέκυψε στην εντολή assert όπου και κάνουμε την σύγκριση των τιμών. Σε περίπτωση που η έκφραση μέσα στην assert είναι false τότε αυτό είναι ένδειξη λάθους. Κοιτώντας πιο ψηλά βλέπουμε τις εντολές που εκτελέστηκαν, καθώς επίσης και την γραμμή στην οποία - 41 -
βρίσκονται. Επίσης μας ενημερώνει ότι προκειμένου να φτάσουμε στο σφάλμα απαιτούνται πέντε βήματα. Εκτός από τις εντολές του ant που χρησιμοποιήσαμε για να τρέξουμε το παράδειγμα το ant έχει τη δυνατότητα να εκτελέσει και άλλες εντολές, σχετικές ή όχι, με το JPF. Με την εντολή >ant help, εμφανίζονται όλες οι εντολές που υποστηρίζει το ant. Με την εντολή >ant projecthelp, εμφανίζονται όλες οι εντολές που υποστηρίζονται από το πρόγραμμα που θέλουμε να εκτελέσουμε με το ant, στην προκειμένη περίπτωση το JPF. Επίσης με την εντολή >jpf help, μπορούμε να δούμε ποιες εντολές υποστηρίζει το JPF. Με την εντολή >jpf show, εμφανίζονται οι τρέχουσες ρυθμίσεις που χρησιμοποιεί το JPF για τον έλεγχο των προγραμμάτων. Τέλος μπορούμε να διαγράψουμε τα αρχεία που δημιουργήθηκαν από το compile εκτελώντας την εντολή >ant clean. Είναι εμφανές ότι η εγκατάσταση και η χρήση του JPF μέσω του command line είναι αρκετά περίπλοκη και δύσκολη. Ένας δεύτερος τρόπος για την εγκατάσταση του JPF είναι με τη βοήθεια του Eclipse. 5.1.2 Μέσω του προγράμματος Eclipse (SUN) Για την εγκατάσταση του JPF μέσω του προγράμματος Eclipse απαιτούνται τα παρακάτω προγράμματα: Eclipse IDE for Java EE Developers, πρόγραμμα ανάπτυξης προγραμμάτων λογισμικού γραμμένα στη γλώσσα Java Java Runtime Environment, απαραίτητο για τη λειτουργία του προγράμματος Eclipse Η φιλοσοφία της εγκατάστασης του JPF μέσω του Eclipse είναι απλή. Απλά αντιμετωπίζουμε το JPF σαν ένα πρόγραμμα γραμμένο σε Java και το εισάγουμε στο Eclipse. Τα βήματα είναι τα εξής: - 42 -
1. Εισάγουμε το JPF σαν project στο Eclipse. Δεξί κλικ στο Project Explorer, επιλέγουμε Import Import (βλ. Σχ. 5.10) Σχ. 5.10 Project Import 2. Στο παράθυρο διαλόγου, επιλέγουμε General Existing Projects into Workspace και πατάμε Next. Στη συνέχεια, βάζουμε στο πεδίο Select root directory το path στο οποίο βρίσκεται ο φάκελος με όνομα jpf release. Πατάμε Finish και αν όλα πήγαν καλά θα πρέπει να υπάρχει ένα project με όνομα jpf release. 3. Δεξί κλικ στο project jpf release και επιλέγουμε την επιλογή properties. Στο παράθυρο διαλόγου, επιλέγουμε στα αριστερά Java Build Path, επιλέγουμε την καρτέλα Libraries και κάνουμε κλικ στο κουμπί Add JARs (βλ. Σχ. 5.11). - 43 -
Σχ. 5.11 Add JARs (βήμα 1 ο ) 4. Στο νέο παράθυρο διαλόγου, επιλέγουμε τον φάκελο lib και εισάγουμε τα 4 αρχεία *.jar που περιέχει (βλ Σχ. 5.12). - 44 -
Σχ. 5.12 Add JARs (βήμα 2 ο ) 5. Πηγαίνουμε στην καρτέλα Source και κάνουμε κλικ στο κουμπί Add Folder. Στο νέο παράθυρο διαλόγου, επιλέγουμε τους φακέλους build-tools, jpf και jvm (υποφάκελοι του env), examples και src (βλ. Σχ. 5.13). - 45 -
Σχ. 5.13 Add Folders 6. Επιλέγουμε στα αριστερά, Java Compiler και ορίζουμε το compliance level σε 1.4 (βλ. Σχ. 5.14). Σχ. 5.14 Ρύθμιση του Java Compiler - 46 -
7. Πατάμε OK στο παράθυρο Properties for jpf release. Στη συνέχεια, κάνουμε δεξί κλικ στο jpf release, και επιλέγουμε Run As Run Configurations (βλ. Σχ. 5.15). Σχ. 5.15 Run Configurations 8. Στο παράθυρο διαλόγου, φτιάχνουμε μία νέα Java Application με όνομα jpf, project το jpf release και Main Class την gov.nasa.jpf.jpf (βλ. Σχ. 5.16). - 47 -
Σχ. 5.16 Run Configurations (βήμα 1 ο ) 9. Τέλος, επιλέγουμε την καρτέλα Arguments και στο πεδίο Program Arguments γράφουμε το όνομα της κλάσης που θέλουμε να εξετάσουμε (στο παράδειγμα εξετάζουμε την κλάση HelloWorld βλ. Σχ. 5.17). Σχ. 5.17 Run Configurations (βήμα 2 ο ) - 48 -
10. Κάνουμε κλικ στο κουμπί Run και το JPF εξετάζει το πρόγραμμα. Τα αποτελέσματα της εξέτασης εμφανίζονται στο Console κομμάτι του Eclipse όπως φαίνεται στο σχήμα 5.18. Σχ. 5.18 Αποτελέσματα εξέτασης του προγράμματος HelloWorld Όπως φαίνεται, δε βρέθηκε κάποιο σφάλμα στο πρόγραμμα HelloWolrd. Ας εξετάσουμε το πρόγραμμα Sorter1 αλλάζοντας το πεδίο Program Arguments από την επιλογή Run Configurations. Όπως φαίνεται, στο σχήμα 5.19, βρέθηκε ένα σφάλμα, τύπου uncaught exception. Το JPF εμφανίζει ένα μονοπάτι εκτέλεσης που οδήγησε στο σφάλμα. Ξεκινώντας από το τέλος βλέπουμε ότι το σφάλμα προέκυψε στην εντολή assert όπου και κάνουμε την σύγκριση των τιμών. Σε περίπτωση που η έκφραση μέσα στην assert είναι false τότε αυτό είναι ένδειξη λάθους. Κοιτώντας πιο πάνω βλέπουμε τις εντολές που εκτελέστηκαν, καθώς επίσης και την γραμμή στην οποία βρίσκονται. Επίσης μας ενημερώνει ότι προκειμένου να φτάσουμε στο σφάλμα απαιτούνται πέντε βήματα (παρακάτω θα αναλυθούν εκτενέστερα τα αποτελέσματα της εκτέλεσης του JPF). - 49 -
Σχ. 5.19 Αποτελέσματα εξέτασης του προγράμματος sorter1-50 -
5.2 Χρήση του Java PathFinder μέσω παραδειγμάτων Σε αυτό το κομμάτι θα αναλυθεί περισσότερο ο τρόπος λειτουργίας του JPF μέσα από ορισμένα παραδείγματα που υπάρχουν διαθέσιμα από την NASA. Όλα τα παρακάτω παραδείγματα εκτελούνται με τις default ρυθμίσεις του JPF. 5.2.1 Πρόγραμμα AssertionCheck Σαν πρώτο παράδειγμα θα αναλυθεί το πρόγραμμα AssertionCheck. Είναι ένα πολύ απλό πρόγραμμα που έχει σαν σκοπό μία πρώτη εισαγωγή στον τρόπο χρήσης των assertions. Ο κώδικάς του είναι ο εξής (Σχ. 5.20): Σχ. 5.20 AssertionCheck Source code Η assert χρησιμοποιείται για να ελέγξει αν ισχύει η συνθήκη «1==0». Αυτό όμως δεν ισχύει και επομένως αναμένεται η εύρεση σφάλματος. Το αποτέλεσμα της εξέτασης είναι το παρακάτω (Σχ. 5.21): - 51 -
Σχ. 5.21 AssertionCheck execution result Όπως αναμενόταν το λάθος βρέθηκε. Οι πληροφορίες που δίνονται είναι οι εξής (ανάλυση από πάνω προς τα κάτω): Το σφάλμα βρέθηκε με χρήση ελέγχου τύπου assert στην γραμμή 28 (ο κώδικας περιέχει και σχόλια που δεν εμφανίζονται στο σχήμα 5.20) Για την εκτέλεση του σφάλματος εκτελέστηκε μία εντολή (path to error length:1) και εμφανίζεται η σειρά εκτέλεσης από κάτω Πριν το end error path, εμφανίζεται η εντολή που προκάλεσε το σφάλμα Τέλος, βρέθηκε ένα σφάλμα τύπου Uncaught Exception Η συνθήκη μέσα στην assert μπορεί να περιέχει σύγκριση μεταβλητών, μεταβλητές τύπου Boolean καθώς επίσης και συναρτήσεις τύπου Boolean. - 52 -
5.2.2 Πρόγραμμα lockexample Σε αυτό το παράδειγμα χρησιμοποιείται η κλάση Verify σε συνδυασμό με την χρήση της εντολής Assert. Η Verify χρησιμοποιείται για την κλήση της συνάρτησης Verify.randomBool(), η οποία επιστρέφει τυχαία true ή false. O κώδικας του προγράμματος είναι ο εξής (Σχ. 5.22): Σχ. 5.22 lockexample source code - 53 -
Αναλύοντας τον κώδικα μερικά από τα μονοπάτια εκτέλεσης είναι τα παρακάτω: 1. Int got_lock=0 επιτυχία της if (γραμμή 37) κλήση της lock() επιτυχία assert και LOCK=1 got_lock=1 got_lock=0 αποτυχία του while (γραμμή 47) Επιτυχής τερματισμός 2. Int got_lock=0 επιτυχία της if (γραμμή 37) κλήση της lock() επιτυχία assert και LOCK=1 got_lock=1 got_lock=0 επιτυχία του while (γραμμή 47) αποτυχία του if (γραμμή 37) κλήση της unlock() επιτυχία assert και LOCK=0 got_lock=-1 επιτυχία while (γραμμή 47). Επιτυχής τερματισμός 3... Όπως φαίνεται παραπάνω, το πρόγραμμα είναι δυνατόν να εκτελεστεί σωστά χωρίς κάποιο πρόβλημα. Εξετάζοντας το πρόγραμμα με το JPF εξάγονται τα παρακάτω αποτελέσματα (Σχ. 5.23): - 54 -
Σχ. 5.23 lockexample execution result Όπως φαίνεται από τα αποτελέσματα το JPF βρήκε ένα σφάλμα τύπου Uncaught Exception. Το σφάλμα βρέθηκε με χρήση ελέγχου τύπου assert στην συνάρτηση unlock() και στην γραμμή 51, η οποία κλήθηκε από την main() στην γραμμή 43. - 55 -
Αμέσως μετά ακολουθεί ένα μονοπάτι εκτέλεσης που οδηγεί στην εμφάνιση του σφάλματος. Μεταφράζοντας το error path, η εκτέλεση του προγράμματος που οδηγεί στο σφάλμα είναι η εξής: Int got_lock=0 αποτυχία της if (γραμμή 37) got_lock=-1 επιτυχία του while (γραμμή 47) αποτυχία της if (γραμμή 37) κλήση της unlock() αποτυχία του assert (γραμμή 51) Αποτυχημένη εκτέλεση! Το πλήθος των εντολών που εκτελέστηκαν μέχρι την εύρεση του σφάλματος είναι τέσσερις (path to error (length 4)) και είναι οι εξής (είναι με πλάγια γράμματα στο μονοπάτι εκτέλεσης): 1. int got_lock=0; 2. get_lock=1; 3. unlock(); 4. assert (LOCK == 1); 5.2.3 Πρόγραμμα oldclassic Σε αυτό το παράδειγμα εξετάζουμε την ταυτόχρονη λειτουργία δύο νημάτων (threads). Είναι ένα κλασσικό παράδειγμα που παρουσιάζει πρόβλημα τύπου deadlock, δηλαδή το ένα thread περιμένει το άλλο προκειμένου να συνεχίσει την εκτέλεσή του, με αποτέλεσμα την αποτυχία του προγράμματος. Ο κώδικας του παραδείγματος είναι ο εξής (σχ. 5.24 και σχ. 5.25): - 56 -
- 57 - Σχ. 5.24 oldclassic source code (κλάσεις oldclassic και Event)
Σχ. 5.25 oldclassic source code (κλάσεις FirstTask και SecondTask) Εξετάζοντας το πρόγραμμα με το JPF και χρησιμοποιώντας το error path εξάγεται το παρακάτω μονοπάτι αποτυχημένης εκτέλεσης: Εκτελείται αρχικά το thread (1) και - 58 -
περιμένει την εκτέλεσή του thread (2) (γραμμή 93). Στη συνέχεια εκτελείται το thread (2), πριν ολοκληρωθεί στέλνει signal_event για την εκτέλεση του thread (1) (γραμμή 127) και στη συνέχεια περιμένει για signal_event (γραμμή 130) προκειμένου να ολοκληρώσει την εκτέλεσή του. Την ίδια στιγμή, το thread (1) έχει ολοκληρώσει την εκτέλεσή του και το signal_event που στέλνει (γραμμή 97) πάει χαμένο. Αυτό έχει σαν αποτέλεσμα το thread (2) να περιμένει το signal_event του thread (1) και το thread (1) να περιμένει το signal_event του thread (2). Έτσι τελικά το πρόγραμμα αποτυγχάνει. Η δομή της αναφοράς σφάλματος έχει την ίδια μορφή με τα παραπάνω παραδείγματα. Η μόνη διαφορά είναι ότι στην περίπτωση εκτέλεσης threads το JPF επιστρέφει την στοίβα των νημάτων (σχ. 5.26). Σχ. 5.26 oldclassic thread stack Στο παραπάνω παράδειγμα, ο περιορισμός του εύρους τιμών της μεταβλητής count (γραμμή 63) είναι πολύ σημαντικός για την ταχύτητα της εκτέλεσης του ελέγχου. Ο περιορισμός αυτός δεν είναι απολύτως απαραίτητος στην συγκεκριμένη περίπτωση, καθώς το JPF θα έβρισκε το σφάλμα σε ένα λογικό βάθος αλλά σε άλλες περιπτώσεις θα μπορούσε να αποτελέσει αιτία αδυναμίας ελέγχου. - 59 -
5.3 Έλεγχος προγραμμάτων με το JPF (άλλα παραδείγματα) Τα προγράμματα που εξετάστηκαν παραπάνω αποτελούσαν παραδείγματα που βρίσκονται μέσα στο πακέτο του JPF και έχουν σαν σκοπό την εξοικείωση του χρήστη με το πρόγραμμα. Όμως, όπως είναι λογικό, κάθε χρήστης θέλει να εξετάσει το δικό του πρόγραμμα. Αν το πρόγραμμα δεν απαιτεί την χρήση επιπλέον κλάσεων που υποστηρίζει το JPF τα πράγματα είναι αρκετά απλά. Το μόνο που χρειάζεται είναι η αντιγραφή του αρχείου/πρόγραμμα στον φάκελο jpf release.examples και η εισαγωγή του στο default package του project. Στη συνέχεια προστίθεται το όνομα του αρχείου, που περιέχει την main κλάση του προγράμματος, στις παραμέτρους εκτέλεσης του JPF (βλ. κεφ. 5.1.2 βήμα 9). Ο κώδικας του προγράμματος που θα εξεταστεί είναι ο εξής (σχ. 5.27): Σχ. 5.27 MyRaceCondition source code - 60 -
Το πρόγραμμα MyRaceCondition περιέχει σφάλμα τύπου συνθήκης ανταγωνισμού (race condition). To JPF υποστηρίζει τον έλεγχο για race condition αλλά δεν συμπεριλαμβάνεται στις default ρυθμίσεις. Το αποτέλεσμα του ελέγχου με τις default ρυθμίσεις του JPF, είναι το παρακάτω (σχ. 5.28): Σχ. 5.28 MyRaceCondition execution result (default properties) - 61 -
Όπως φαίνεται, το JPF δε μπόρεσε να βρει κάποιο σφάλμα εκτέλεσης στο πρόγραμμα εφόσον οι default ρυθμίσεις δεν υποστηρίζουν τον έλεγχο για race conditions. Για την υποστήριξη του ελέγχου για συνθήκες ανταγωνισμού προσθέτουμε, όπως φαίνεται στο σχήμα 5.29, στις παραμέτρους εκτέλεσης του JPF +jpf.listener=gov.nasa.jpf.tools.racedetector. Σχ. 5.29 Προσθήκη του ελέγχου συνθηκών ανταγωνισμού Ελέγχοντας το πρόγραμμα με τις νέες ρυθμίσεις εξάγονται τα παρακάτω αποτελέσματα (σχ. 5.30): - 62 -
Σχ. 5.30 MyRaceCondition execution result (configured properties) Όπως φαίνεται, με τις νέες ρυθμίσεις του JPF, το σφάλμα βρέθηκε επιτυχώς. Επομένως, πρέπει να τονιστεί ότι οι βασικές ρυθμίσεις του JPF είναι κατάλληλες για ένα ευρύ φάσμα προβλημάτων, αλλά σε ορισμένες περιπτώσεις ίσως χρειαστεί κάποια επιπλέον μετατροπή. - 63 -
6. ΓΡΑΦΙΚΟ ΠΕΡΙΒΑΛΛΟΝ ΤΟΥ JAVA PATHFINDER (VJP) Αν και η χρήση του JPF και η κατανόηση των αποτελεσμάτων του είναι σχετικά εύκολη με την βοήθεια του Eclipse, όλες οι απαραίτητες ενέργειες πριν τον έλεγχο ενός προγράμματος γίνονται χειροκίνητα. Σε μία προσπάθεια διευκολυνθεί η χρήση του JPF, αναπτύχθηκε το Visual Java PathFinder (VJP). Το VJP είναι ένα plug-in του Eclipse το οποίο δίνει τη δυνατότητα στους χρήστες του να ελέγξουν τον κώδικα ενός εκτελέσιμου προγράμματος με το πάτημα ενός κουμπιού. Με το VJP διευκολύνονται: η ενημέρωση (update) του JPF, η μεταγλώτιση του προγράμματος που θα εξεταστεί, η μετατροπή των ρυθμίσεων ελέγχου του JPF και η εξέταση του εκτελέσιμου προγράμματος Το VJP διαχειρίζεται τα παραπάνω και παρέχει γραφικό περιβάλλον για την μετατροπή των ρυθμίσεων του JPF και για τον έλεγχο των εκτελέσιμων προγραμμάτων. Επιπλέον, ένα από τα σημαντικά προτερήματα του VJP είναι η δυνατότητα εμφάνισης των αποτελεσμάτων με τη βοήθεια γραφικών. Τα μονοπάτια εκτέλεσης παρουσιάζονται γραφικά παρέχοντας πληροφορίες για κάθε βήμα εκτέλεσης. 6.1 Εγκατάσταση του VJP στο Eclipse Η εγκατάσταση του VJP είναι απλή (απαιτείται δυνατότητα σύνδεσης στο internet). Τα βήματα για την εγκατάσταση είναι τα εξής (ο οδηγός είναι για το Eclipse version 3.5): 1. Από το μενού επιλογών επιλέγουμε Help Install New Software 2. Στο παράθυρο διαλόγου, στο πεδίο Work with εισάγουμε το site ανανέωσης του VJP http://visualjpf.sourceforge.net/update, επιλέγουμε το VJP από τη - 64 -
λίστα των αντικειμένων προς εγκατάσταση και πατάμε το κουμπί Next (σχ. 6.1). Σχ. 6.1 Εγκατάσταση του Visual Java PathFinder στο Eclipse v.3.5 3. Στο νέο παράθυρο διαλόγου Install Details εμφανίζονται κάποιες λεπτομέρειες εγκατάστασης του VJP. Κάνουμε κλικ στο κουμπί Next για να συνεχίσουμε την εγκατάσταση. 4. Στο νέο παράθυρο Review Licenses αποδεχόμαστε τους όρους χρήσης του VJP και πατάμε Finish. 5. Αν όλα έχουν πάει καλά, θα εμφανιστεί στην οθόνη ένα νέο παράθυρο που παρουσιάζει την πορεία της εγκατάστασης (σχ. 6.2). - 65 -
Σχ.6.2 Installation progress window 6. Αφού ολοκληρωθεί επιτυχώς η εγκατάσταση του VJP θα σας ζητηθεί να επανεκινήσετε το Eclipse. Επιλέξτε Yes. 7. Πλέον στο Eclipse πάνω αριστερά έχει προστεθεί ένα πράσινο εικονίδιο V (σχ. 6.3) με το οποίο εκτελείτε το VJP. Σχ. 6.3 Το κουμπί εκκίνησης του VJP - 66 -
6.2 Διαδικασία χρήσης του VJP Για τη χρήση του VJP απαιτούνται τρία πράγματα: 1. Δημιουργία ενός Mode-Property αρχείου 2. Ρύθμιση του Mode-Property αρχείου 3. Έλεγχος προγράμματος με χρήση του VJP Το Mode-Property αρχείο περιλαμβάνει τις απαραίτητες ρυθμίσεις που χρησιμοποιεί το JPF για τον έλεγχο του εκάστοτε προγράμματος. Το αρχείο αυτό μπορεί να μετατραπεί χειροκίνητα από έναν οποιοδήποτε επεξεργαστή κεμένου ή αυτόματα (προτείνεται) από το VJP. 6.2.1 Δημιουργία του Mode-Property αρχείου Η δημιουργία του Mode-Property αρχείου μπορεί να γίνει με τρείς βασικούς τρόπους. Ο πρώτος τρόπος είναι να δημιουργηθεί ένα άδειο αρχείου κειμένου, να οριστεί η κλάση που πρόκειται να ελεγχθεί (π.χ. target=main ) και να αποθηκευτεί με την επέκταση *.jpf σε έναν φάκελο που περιέχει ένα Java Eclipse Project. Σε οποιοδήποτε σημείου του φακέλου και να τοποθετηθεί το αρχείο, θα βρεθεί από το VJP αυτόματα. Ένας άλλος τρόπος είναι κάνοντας κλικ στο κουμπί του VJP (σχ. 6.3). Στη συνέχεια στο παράθυρο διαλόγου που θα εμφανιστεί, κλικ στο κουμπί New και αποθήκευση σε κάποιο σημείο μέσα στον φάκελο του project. Ο τρίτος τρόπος, και απλούστερος, είναι κάνοντας δεξί κλικ πάνω στο αρχείο της κλάσης (στο υποπαράθυρο Package Explorer ) και επιλέγοντας Verify. Το VJP θα δημιουργήσει αυτόματα το Mode-Property αρχείο και θα θέσει σαν target την συγκεκριμένη κλάση. - 67 -
6.2.2 Ρύθμιση του Mode-Property αρχείου Ο ενδεδειγμένος τρόπος επεξεργασίας του Mode-Property αρχείου είναι κάνοντας κλικ στο εικονίδιο του VJP και στη συνέχεια επιλέγοντας το προς επεξεργασία Mode-Property αρχείο. Θα εμφανιστεί ένας επεξεργαστής ο οποίος παρέχει δυνατότητες προσθήκης, αφαίρεσης και επεξεργασίας των ιδιοτήτων εκτέλεσης του JPF (σχ. 6.4). Σχ. 6.4 Verify Properties Editor Υπάρχουν διαθέσιμες δύο καρτέλες, Custom Properties και Default Properties. Οι Default Properties είναι οι ιδιότητες που κληρονομούνται από τα αρχεία default.properties και jpf.properties. Αυτά τα αρχεία είναι αποθηκευμένα στο VJP και δεν πρέπει να μετατραπούν. Σε περίπτωση που κάποια από αυτές τις ρυθμίσεις πρέπει να αλλάξει, απεπιλέγεται το Use Default και στη συνέχεια μπορεί να μετατραπεί (σχ. 6.5). Οι Custom Properties είναι ρυθμίσεις που πρέπει να οριστούν και δεν είναι ορισμένες στα αρχεία default.properties και jpf.properties. - 68 -
Σχ. 6.5 Αλλαγή των Default Properties Φυσικά η επεξεργασία των αρχείων ρυθμίσεων μπορεί να γίνει από έναν οποιοδήποτε επεξεργαστή κειμένου. Επίσης, υπάρχει η δυνατότητα προσθήκης σχόλιων μέσα στα αρχεία. 6.2.3 Έλεγχος προγράμματος με χρήση του VJP Ο έλεγχος ενός προγράμματος μπορεί να γίνει με δύο τρόπους. Ο ένας είναι κάνοντας κλικ σε ένα από τα κουμπιά Step Verify και Run Verify. Ο άλλος, και απλούστερος, τρόπος είναι ο τρίτος τρόπος δημιουργίας του αρχείου Mode-Property (κεφ. 6.2.1), καθώς ταυτόχρονα με την δημιουργία του αρχείου γίνεται και ο έλεγχος. Σε περίπτωση που το αρχείο Mode-Property έχει μετατραπεί, ο έλεγχος του προγράμματος γίνεται με τις νέες ρυθμίσεις. Η διαφορά ανάμεσα στο Step Verify και Run Verify έγκειται στο ότι με το πρώτο το JPF σταματάει μετά από κάθε μετάβαση σε επόμενη κατάσταση ή οπισθοδρόμηση. 6.3 Εμφάνιση αποτελεσμάτων ελέγχου του VJP Με τη χρήση του VJP τα αποτελέσματα του ελέγχου εμφανίζονται με διαφορετικό τρόπο από αυτόν που περιγράφεται στο κεφάλαιο 5. Ένα καλό παράδειγμα είναι το πρόγραμμα MyRaceCondition, το οποίο χρησιμοποιήθηκε και στο κεφάλαιο 5.3. Τα βήματα που ακολουθούνται για την εκτέλεση του ελέγχου περιγράφονται στο κεφάλαιο 6.2. - 69 -
6.3.1 Αποτελέσματα χωρίς να έχει βρεθεί σφάλμα Όπως αναφέρθηκε παραπάνω, το JPF στις default ρυθμίσεις του δεν περιλαμβάνει έλεγχο για Race Conditions. Επομένως, η εκτέλεση του ελέγχου με τις βασικές ρυθμίσεις δε βρίσκει κάποιο σφάλμα. Σε μία τέτοια περίπτωση το VJP παρέχει τρία είδη πληροφορίας στην καρτέλα JPF Topics : 1. System under test που αναφέρεται το αρχείο της εφαρμογής που εξετάστηκε 2. Results που το VJP αναφέρει ότι δεν βρέθηκαν σφάλματα 3. Statistics που υπάρχουν κάποια στοιχεία σχετικά με την εκτέλεση του ελέγχου, οι σημαντικότερες των οποίων είναι η χρονική διάρκεια του ελέγχου, πληροφορίες σχετικά με τις καταστάσεις του προγράμματος (δημιουργήθηκαν, επισκέφθηκε το πρόγραμμα, οπισθοδρομήσεις, τελικές) και η μνήμη που χρησιμοποιήθηκε (σχ. 6.6). Σχ. 6.6 JPF Topics - Statistics Results Επίσης το VJP παρέχει την δυνατότητα εμφάνισης της πορείας του ελέγχου του προγράμματος. Αυτή εμφανίζεται στην καρτέλα JPF Output. Στην προκειμένη περίπτωση η καρτέλα αυτή είναι κενή. Για την ενεργοποίηση αυτής της δυνατότητας απαιτείται η προσθήκη της ιδιότητας vjp.tracelistener=true, όπως φαίνεται στο σχήμα 6.4. Στη συνέχεια επαναλαμβάνουμε τον έλεγχο του προγράμματος. Επιλέγουμε την καρτέλα JPF Output, κάνουμε κλικ στο κουμπί Run 2 φορές (ή Step για έλεγχο βήμα-βήμα) και ο έλεγχος ξεκινά (συνήθως διαρκεί περισσότερο από προηγουμένως). Στο αποτέλεσμα ελέγχου υπάρχουν στα αριστερά όλες οι μεταβάσεις, με τη σειρά που εκτελέστηκαν, και στα δεξιά παρέχονται ορισμένες πληροφορίες σχετικά με την εκάστοτε μετάβαση (αριθμός της μετάβασης, σε ποιά - 70 -
εντολή οφείλεται, πόσα βήματα εκτελέστηκαν και ποιά). Κάτω δεξιά, εμφανίζεται το αποτέλεσμα του ελέγχου (σχ. 6.7). Σχ. 6.7 JPF Output No error 6.3.2 Αποτελέσματα που έχει εντοπιστεί σφάλμα Προκειμένου να ελέγξει το VJP για συνθήκες ανταγωνισμού πρέπει να προστεθεί η property jpf.listener=gov.nasa.jpf.tools.racedetector. Ελέγχοντας με την νέες ρυθμίσεις το πρόγραμμα, το σφάλμα εντοπίζεται επιτυχώς. Σε μία τέτοια περίπτωση το VJP παρέχει έξι είδη πληροφορίας στην καρτέλα JPF Topics. Τα τρία από αυτά είναι ίδια με παραπάνω. Τα επιπλέον τρία είναι: 1. Error που αναφέρεται ο τύπος του σφάλματος και με ποιο εργαλείο βρέθηκε 2. Trace που παρουσιάζεται το μονοπάτι εκτέλεσης που οδήγησε στο σφάλμα (σχ. 6.8) 3. Snapshot που εμφανίζει σε ποια κατάσταση βρισκόντουσαν τα νήματα που εκτελούνταν αμέσως πριν προκύψει το σφάλμα - 71 -
Σχ. 6.8 JPF Topics - Trace Όπως παραπάνω, υπάρχει η δυνατότητα εμφάνισης της αναλυτικής πορείας ελέγχου του προγράμματος από την καρτέλα JPF Output. Η μορφή των αποτελεσμάτων φαίνεται στο σχήμα 6.9. Σχ. 6.9 JPF Output Error found - 72 -