Niezawodne systemy informatyczne (W04INA-SI0835G)


Bombowiec B-1B (fot. avioners.net)
Formal methods should be part of the education of every computer scientist and software engineer, just as the appropriate branch of applied maths is a necessary part of the education of all other engineers.
Z raportu FAA (Federal Aviation Authority) i NASA
NVIDIA's selection of Ada and SPARK ushers in a new era in the history of safety- and security-critical software development.
Quentin Ochem
Aktualności
Opis kursu
Moduł wprowadzający w zagadnienia związane z tworzeniem systemów co do których stawia się krytyczne wymagania. Wykłady rozpoczynają się wstępem dotyczącym niezawodności oprogramowania i sprzętu. Następnie omawiane są narzędzia wspierające weryfikację poprawności programów stworzone dla języka Ada, który znajduje zastosowanie w systemach o znaczeniu krytycznym. Przedstawione zostaje również projektowanie kontraktowe wraz z przykładami jego wykorzystania w wybranych językach programowania. Podczas zajęć laboratoryjnych rozwiązywane są zadania dzięki którym nabywa się praktycznej umiejętności stosowania narzędzi omawianych na wykładach.
Zasady zaliczenia
- Ocena z grupy kursów wyliczana jest na podstawie średniej ważonej z punktów uzyskanych na laboratorium i na kartkówkach.
- Aby zaliczyć grupę kursów należy uzyskać co najmniej 50 punktów z laboratorium i co najmniej 50 punktów z kartkówek.
- Progi ocen zamieszczono poniżej w postaci kontraktu procedury Oblicz_Zaliczenie.
- Kartkówki odbędą się na ostatnich wykładach w semestrze (terminy i tematy zostaną podane wkrótce).
- Listy zadań na laboratoriach należy rozwiązywać samodzielnie i zaliczać w terminie podanym przy każdej liście.
- Laboratorium musi być zaliczone do 15-go tygodnia zajęć (nie ma możliwości zaliczania laboratorium w sesji egzaminacyjnej).
- Przy każdym zadaniu na liście zadań podano maksymalną liczbę punktów jaką można za nie otrzymać.
- Liczba punktów otrzymywanych za zadanie zależy od tego, które warunki udało się programowi gnatprove automatycznie udowodnić.
- Kryteria wyliczania liczby punktów podano na wstępie do każdej listy zadań.
procedure Oblicz_Zaliczenie(LAB: in Float; KOL: in Float; ZAL: out Float) with Pre => LAB >= 50.0 and LAB <= 100.0 and KOL >= 50.0 and KOL <= 100.0, Contract_Cases => ( 0.4 * LAB + 0.6 * KOL < 50.0 => ZAL = 2.0, 0.4 * LAB + 0.6 * KOL < 60.0 => ZAL = 3.0, 0.4 * LAB + 0.6 * KOL < 70.0 => ZAL = 3.5, 0.4 * LAB + 0.6 * KOL < 80.0 => ZAL = 4.0, 0.4 * LAB + 0.6 * KOL < 90.0 => ZAL = 4.5, others => ZAL = 5.0 );
Plan wykładu
Wstęp (2h)
SPARK (14h)
- Elementy języka SPARK
- Pakiety
- Sprawdzanie zaleźności
- Dowodzenie
Metody formalne (6h)
- Weryfikacja programów while
- Analiza przepływu sterowania
- Analiza przepływu danych
Systemy krytyczne (4h)
- Pewność
- Specyfikowanie systemów krytycznych
- Wytwarzanie systemów krytycznych
Przegląd innych narzędzi (2h)
- Frama-C dla języka C
- KeY dla Java
Kolokwium (2h)
Prezentacja
TBA
Wybrane przykłady prezentowane na wykładzie można znaleźć w repozytorium przemko/ada-spark-nsi.
Terminy kartkówek
TBA
Literatura
SPARK
- J. McCormick, P. Chapin. Building High Integrity Applications with SPARK. Cambridge University Press, 2015. (na stronie wydawcy w zakładce Resources znajdują się źródła programów zamieszczonych w książce)
- J. Barnes. SPARK: The Proven Approach to High Integrity Software. Altran Praxis, 2012. (dotyczy starszej wersji SPARK)
- SPARK 2014 Toolset User's Guide. AdaCore and Altran UK Ltd
- SPARK 2014 Reference Manual. AdaCore and Altran UK Ltd
- R. Chapman. Industrial Experience with SPARK. ACM SIGAda Ada Letters, Volume XX, Issue 4, Dec. 2000.
Metody formalne
- K.R. Apt, F.S. de Boer, E-R Olderog. Verification of Sequential and Concurrent Programs. Springer, 2009. (rozdziały 3 while Programs)
- J-F Bergeretti, B.A. Carre. Information-Flow and Data-Flow Analysis of while-Programs. ACM Transactions on Programming Languages and Systems, Vol. 7, No. 1, January 1985.
- P.B. Jackson, G.O. Passmore. Proving SPARK Verification Conditions with SMT solvers.
Niezawodność
- I. Sommerville. Inżynieria oprogramowania. WNT, Warszawa, 2003. (część IV Systemy krytyczne, część V Weryfikacja i zatwierdzanie)
- G.J. Holzmann. The Power of 10: Rules for Developing Safety-Critical Code. IEEE Computer, June 2006.
Dziesięć fundamentalnych zasad
Poniższe zasady pochodzą z artykułu Gerarda Holzmanna The Power of 10: Rules for Developing Safety-Critical Code:
- Ogranicz cały kod do bardzo prostych konstrukcji sterujących -- nie używaj instrukcji goto, konstrukcji setjmp lub longjmp ani bezpośredniej lub pośredniej rekurencji.
- W każdej pętli podawaj stały górny zakres.
- Nie używaj dynamicznej alokacji pamięci po zainicjowaniu.
- Żadna funkcja nie powinna być dłuższa na wydruku niż jedna strona papieru, przy standardowym formatowaniu z jednym wierszem na instrukcję i jednym wierszem na deklarację.
- Średnia liczba asercji w kodzie powinna wynosić co najmniej dwie na jedną funkcję.
- Deklaruj wszystkie obiekty danych w najwęższym możliwym zakresie.
- Każde wywołanie funkcji musi sprawdzać zwracaną przez funkcję wartość a każda wywołana funkcja musi sprawdzać poprawność wartości wszystkich parametrów dostarczonych w wywołaniu.
- Użycie preprocesora musi ograniczać się jedynie do dołączania plików nagłówkowych i prostych makrodefinicji.
- Użycie wskaźników musi być ograniczone. W szczególności poziom dereferencji nie może być większy niż jeden.
- Cały kod musi być kompilowany od pierwszego dnia rozwijania z ostrzeżeniami kompilatora na jak najbardziej pedantycznym poziomie. Cały kod musi kompilować się bez żadnych ostrzeżeń.
Odnośniki do stron
Przykłady projektów w języku SPARK
Poniższe projekty udostępniają źródła programów:
Narzędzia
Opis instalacji narzędzi w przypadku systemu Linux:
- Zainstaluj zarządcę pakietów Alire
- Po zainicjowaniu nowego pakietu przejdź do jego katalogu
- Zdefiniuj zależność za pomocą polecenia alr with gnatprove
- Wykonaj analizę kodu za pomocą polecenia alr gnatprove
Przykład weryfikacji
Zainicjuj nowy pakiet test:
alr init --bin test
Przejdź do katalogu test i wpisz do pliku src/test.adb następujący kod:
procedure Test with SPARK_Mode is X : Integer; begin X := 2 + 2; pragma Assert (X = 4); end Test;
Zdefiniuj następującą zależność:
alr with gnatprove
Aby zweryfikować poprawność procedury Test, należy wydać następujące polecenie:
alr gnatprove --report=all
Wynik analizy:

Pliki powstałe podczas weryfikacji znalazły się w podkatalogu obj/development/gnatprove. Raport z weryfikacji został zamieszczony w pliku gnatprove.out:
cat obj/development/gnatprove/gnatprove.out Summary of SPARK analysis ========================= --------------------------------------------------------------------------------- SPARK Analysis results Total Flow Provers Justified Unproved --------------------------------------------------------------------------------- Data Dependencies . . . . . Flow Dependencies . . . . . Initialization 1 1 . . . Non-Aliasing . . . . . Run-time Checks . . . . . Assertions 1 . 1 (CVC5) . . Functional Contracts . . . . . LSP Verification . . . . . Termination . . . . . Concurrency . . . . . --------------------------------------------------------------------------------- Total 2 1 (50%) 1 (50%) . . max steps used for successful proof: 1 Analyzed 1 unit in unit test, 1 subprograms and packages out of 1 analyzed Test at test.adb:1 flow analyzed (0 errors, 0 checks, 0 warnings and 0 pragma Assume statements) and proved (1 checks)
Aby posprzątać po weryfikacji należy wykonać następujące polecenie:
alr gnatprove --clean
Listy zadań
Rozwiąż samodzielnie poniższe listy zadań:
TBA
Wskazówka
Czasami istnieje konieczność dopisania w programie asercji, które umożliwią automatyczne udowodnienie poprawności programu.
Na przykład, w zadaniu o schemacie Hornera można pomyśleć o takiej asercji:
pragma Assert (for all A in Integer => (for all B in Positive => A ** B = A * A ** (B - 1) ));
Na przykład, w zadaniu o schemacie Hornera można pomyśleć o takiej asercji:
pragma Assert (for all A in Integer => (for all B in Positive => A ** B = A * A ** (B - 1) ));
Wady częściowej poprawności
Sprawdzenie częściowej poprawności programu nie gwarantuje jego poprawności w przypadku gdy nie kończy on pracy. Pamiętaj aby dla każdej pętli podać za pomocą pragmy Loop_Variant wielkość, której zmiana gwarantuje zakończenie pętli.
Poniżej zamieszczono przykład kodu, dla którego można udowodnić asercję ewidentnie fałszywą. Wynika to z tego, że niekończący się program spełnia każdy warunek zakończenia — również fałszywy.
Poniżej zamieszczono przykład kodu, dla którego można udowodnić asercję ewidentnie fałszywą. Wynika to z tego, że niekończący się program spełnia każdy warunek zakończenia — również fałszywy.
procedure Schrodinger with SPARK_Mode is Cat_Is_Dead : Boolean := False with Ghost; begin loop null; end loop; pragma Assert ( Cat_Is_Dead and not Cat_Is_Dead ); end Schrodinger;