Skip to main content

Program Verification in the Presence of I/O

Semantics, Verified Library Routines, and Verified Applications

  • Conference paper
  • First Online:

Part of the book series: Lecture Notes in Computer Science ((LNPSE,volume 11294))

Abstract

Software verification tools that build machine-checked proofs of functional correctness usually focus on the algorithmic content of the code. Their proofs are not grounded in a formal semantic model of the environment that the program runs in, or the program’s interaction with that environment. As a result, several layers of translation and wrapper code must be trusted. In contrast, the CakeML project focuses on end-to-end verification to replace this trusted code with verified code in a cost-effective manner.

In this paper, we present infrastructure for developing and verifying impure functional programs with I/O and imperative file handling. Specifically, we extend CakeML with a low-level model of file I/O, and verify a high-level file I/O library in terms of the model. We use this library to develop and verify several Unix-style command-line utilities: cat, sort, grep, diff and patch. The workflow we present is built around the HOL4 theorem prover, and therefore all our results have machine-checked proofs.

This is a preview of subscription content, log in via an institution.

Buying options

Chapter
USD   29.95
Price excludes VAT (USA)
  • Available as PDF
  • Read on any device
  • Instant download
  • Own it forever
eBook
USD   39.99
Price excludes VAT (USA)
  • Available as EPUB and PDF
  • Read on any device
  • Instant download
  • Own it forever
Softcover Book
USD   54.99
Price excludes VAT (USA)
  • Compact, lightweight edition
  • Dispatched in 3 to 5 business days
  • Free shipping worldwide - see info

Tax calculation will be finalised at checkout

Purchases are for personal use only

Learn about institutional subscriptions

Notes

  1. 1.

    https://hol-theorem-prover.org/.

  2. 2.

    https://cakeml.org/.

  3. 3.

    https://hol-theorem-prover.org/.

  4. 4.

    https://cakeml.org/vstte18/x86_binaries.zip.

  5. 5.

    The LCS is not always unique: both [ac] and [bc] are LCSes of [abc] and [bac].

  6. 6.

    There are algorithms that do better than quadratic time for practically interesting special cases [3]; we leave their verification for future work.

  7. 7.

    Note that this differs from the default behaviour of the GNU implementation of diff, which uses heuristics that do not compute the minimal list if doing so would be prohibitively expensive.

  8. 8.

    The verification is described in more detail in a blog post by Yannick Moy: https://blog.adacore.com/formal-verification-of-legacy-code.

  9. 9.

    @ appends lists.

  10. 10.

    Finding a termination proof for the kind of Brzozowski derivation we use is an open problem that is not addressed by Slind’s work nor by the present paper. See, e.g., Nipkow and Traytel [27] for a discussion.

References

  1. Amani, S., et al.: Cogent: verifying high-assurance file system implementations. In: Conte, T., Zhou, Y. (eds.) Proceedings of the Twenty-First International Conference on Architectural Support for Programming Languages and Operating Systems, ASPLOS 2016, Atlanta, GA, USA, 2–6 April 2016, pp. 175–188. ACM (2016). https://doi.org/10.1145/2872362.2872404

  2. Anand, A., et al.: CertiCoq: a verified compiler for Coq. In: Coq for Programming Languages (CoqPL) (2017)

    Google Scholar 

  3. Apostolico, A., Galil, Z. (eds.): Pattern Matching Algorithms. Oxford University Press, Oxford (1997)

    MATH  Google Scholar 

  4. Appel, A.W.: Verified software toolchain. In: Barthe, G. (ed.) ESOP 2011. LNCS, vol. 6602, pp. 1–17. Springer, Heidelberg (2011). https://doi.org/10.1007/978-3-642-19718-5_1

    Chapter  Google Scholar 

  5. Arkoudas, K., Zee, K., Kuncak, V., Rinard, M.: Verifying a file system implementation. In: Davies, J., Schulte, W., Barnett, M. (eds.) ICFEM 2004. LNCS, vol. 3308, pp. 373–390. Springer, Heidelberg (2004). https://doi.org/10.1007/978-3-540-30482-1_32

    Chapter  Google Scholar 

  6. Bulwahn, L., Krauss, A., Haftmann, F., Erkök, L., Matthews, J.: Imperative functional programming with Isabelle/HOL. In: Mohamed, O.A., Muñoz, C., Tahar, S. (eds.) TPHOLs 2008. LNCS, vol. 5170, pp. 134–149. Springer, Heidelberg (2008). https://doi.org/10.1007/978-3-540-71067-7_14

    Chapter  Google Scholar 

  7. Charguéraud, A.: Characteristic formulae for the verification of imperative programs. In: Proceeding of the 16th ACM SIGPLAN International Conference on Functional Programming, ICFP 2011, pp. 418–430 (2011). https://doi.org/10.1145/2034773.2034828

  8. Chlipala, A., et al.: The end of history? Using a proof assistant to replace language design with library design. In: Summit on Advances in Programming Languages (SNAPL). Schloss Dagstuhl - Leibniz-Zentrum fuer Informatik (2017). https://doi.org/10.4230/LIPIcs.SNAPL.2017.3

  9. Delaware, B., Pit-Claudel, C., Gross, J., Chlipala, A.: Fiat: deductive synthesis of abstract data types in a proof assistant. In: Principles of Programming Languages (POPL), pp. 689–700. ACM (2015). https://doi.org/10.1145/2676726.2677006

  10. Ernst, G., Schellhorn, G., Haneberg, D., Pfähler, J., Reif, W.: Verification of a virtual filesystem switch. In: Cohen, E., Rybalchenko, A. (eds.) VSTTE 2013. LNCS, vol. 8164, pp. 242–261. Springer, Heidelberg (2014). https://doi.org/10.1007/978-3-642-54108-7_13

    Chapter  Google Scholar 

  11. Filliâtre, J.-C., Paskevich, A.: Why3—where programs meet provers. In: Felleisen, M., Gardner, P. (eds.) ESOP 2013. LNCS, vol. 7792, pp. 125–128. Springer, Heidelberg (2013). https://doi.org/10.1007/978-3-642-37036-6_8

    Chapter  Google Scholar 

  12. Glondu, S.: Vers une certification de lextraction de Coq. Ph.D. thesis, Universit Paris Diderot (2012)

    Google Scholar 

  13. Guéneau, A., Myreen, M.O., Kumar, R., Norrish, M.: Verified characteristic formulae for CakeML. In: Yang, H. (ed.) ESOP 2017. LNCS, vol. 10201, pp. 584–610. Springer, Heidelberg (2017). https://doi.org/10.1007/978-3-662-54434-1_22

    Chapter  Google Scholar 

  14. Heisel, M.: Specification of the Unix file system: a comparative case study. In: Alagar, V.S., Nivat, M. (eds.) AMAST 1995. LNCS, vol. 936, pp. 475–488. Springer, Heidelberg (1995). https://doi.org/10.1007/3-540-60043-4_72

    Chapter  Google Scholar 

  15. Ho, S., Abrahamsson, O., Kumar, R., Myreen, M.O., Tan, Y.K., Norrish, M.: Proof-producing synthesis of CakeML with I/O and local state from monadic HOL functions. In: International Joint Conference on Automated Reasoning (IJCAR) (2018, to appear)

    Google Scholar 

  16. Hobor, A.: Oracle Semantics. Princeton University, Princeton (2008)

    MATH  Google Scholar 

  17. IEEE Computer Society, The Open Group: The open group base specifications issue 7. IEEE Std 1003.1, 2016 Edition (2016)

    Google Scholar 

  18. Jeannerod, N., Marché, C., Treinen, R.: A formally verified interpreter for a shell-like programming language. In: Paskevich, A., Wies, T. (eds.) VSTTE 2017. LNCS, vol. 10712, pp. 1–18. Springer, Cham (2017). https://doi.org/10.1007/978-3-319-72308-2_1

    Chapter  MATH  Google Scholar 

  19. Khanna, S., Kunal, K., Pierce, B.C.: A formal investigation of Diff3. In: Arvind, V., Prasad, S. (eds.) FSTTCS 2007. LNCS, vol. 4855, pp. 485–496. Springer, Heidelberg (2007). https://doi.org/10.1007/978-3-540-77050-3_40

    Chapter  MATH  Google Scholar 

  20. Kumar, R., Myreen, M.O., Norrish, M., Owens, S.: CakeML: a verified implementation of ML. In: POPL 2014: Proceedings of the 41st ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, pp. 179–191. ACM Press, January 2014

    Google Scholar 

  21. Lammich, P.: Refinement to Imperative/HOL. In: Urban, C., Zhang, X. (eds.) ITP 2015. LNCS, vol. 9236, pp. 253–269. Springer, Cham (2015). https://doi.org/10.1007/978-3-319-22102-1_17

    Chapter  Google Scholar 

  22. Leino, K.R.M.: Dafny: an automatic program verifier for functional correctness. In: Clarke, E.M., Voronkov, A. (eds.) LPAR 2010. LNCS (LNAI), vol. 6355, pp. 348–370. Springer, Heidelberg (2010). https://doi.org/10.1007/978-3-642-17511-4_20

    Chapter  MATH  Google Scholar 

  23. Leroy, X.: A formally verified compiler back-end. J. Autom. Reason. 43(4), 363–446 (2009)

    Article  MathSciNet  Google Scholar 

  24. Letouzey, P.: Extraction in Coq: an overview. In: Beckmann, A., Dimitracopoulos, C., Löwe, B. (eds.) CiE 2008. LNCS, vol. 5028, pp. 359–369. Springer, Heidelberg (2008). https://doi.org/10.1007/978-3-540-69407-6_39

    Chapter  Google Scholar 

  25. McCormick, J.W.: Building High Integrity Applications with Spark ADA. Cambridge University Press, Cambridge (2015)

    Book  Google Scholar 

  26. Myreen, M.O., Owens, S.: Proof-producing translation of higher-order logic into pure and stateful ML. J. Funct. Program. 24(2–3), 284–315 (2014)

    Article  MathSciNet  Google Scholar 

  27. Nipkow, T., Traytel, D.: Unified decision procedures for regular expression equivalence. In: Klein, G., Gamboa, R. (eds.) ITP 2014. LNCS, vol. 8558, pp. 450–466. Springer, Cham (2014). https://doi.org/10.1007/978-3-319-08970-6_29

    Chapter  Google Scholar 

  28. Ntzik, G., Gardner, P.: Reasoning about the POSIX file system: local update and global pathnames. In: Aldrich, J., Eugster, P. (eds.) Proceedings of the 2015 ACM SIGPLAN International Conference on Object-Oriented Programming, Systems, Languages, and Applications, OOPSLA 2015, part of SPLASH 2015, Pittsburgh, PA, USA, 25–30 October 2015, pp. 201–220. ACM (2015). https://doi.org/10.1145/2814270.2814306

  29. Owens, S., Reppy, J.H., Turon, A.: Regular-expression derivatives re-examined. J. Funct. Program. 19(2), 173–190 (2009). https://doi.org/10.1017/S0956796808007090

    Article  MathSciNet  MATH  Google Scholar 

  30. Ridge, T., Sheets, D., Tuerk, T., Giugliano, A., Madhavapeddy, A., Sewell, P.: SibyLFS: formal specification and oracle-based testing for POSIX and real-world file systems. In: Miller, E.L., Hand, S. (eds.) Proceedings of the 25th Symposium on Operating Systems Principles, SOSP 2015, Monterey, CA, USA, 4–7 October 2015, pp. 38–53. ACM (2015). https://doi.org/10.1145/2815400.2815411

  31. Slind, K.L.: High performance regular expression processing for cross-domain systems with high assurance requirements. Presented at the Third Workshop on Formal Methods And Tools for Security (FMATS3) (2014)

    Google Scholar 

  32. Swamy, N., et al.: Dependent types and multi-monadic effects in F*. In: 43rd ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages (POPL), pp. 256–270. ACM, January 2016. https://www.fstar-lang.org/papers/mumon/

  33. Tan, Y.K., Myreen, M.O., Kumar, R., Fox, A., Owens, S., Norrish, M.: A new verified compiler backend for CakeML. In: ICFP 2016: Proceedings of the 21th ACM SIGPLAN International Conference on Functional Programming, pp. 60–73. ACM Press, September 2016

    Google Scholar 

  34. Kosmatov, N., Marché, C., Moy, Y., Signoles, J.: Static versus dynamic verification in Why3, Frama-C and SPARK 2014. In: Margaria, T., Steffen, B. (eds.) ISoLA 2016. LNCS, vol. 9952, pp. 461–478. Springer, Cham (2016). https://doi.org/10.1007/978-3-319-47166-2_32

    Chapter  Google Scholar 

Download references

Acknowledgements

The first and fourth authors were supported by EPSRC Grant EP/N028759/1, UK. The second and fifth authors were partly supported by the Swedish Research Council. We would also like to thank the anonymous reviewers for their constructive and insightful comments and corrections.

Author information

Authors and Affiliations

Authors

Corresponding authors

Correspondence to Hugo Férée or Johannes Åman Pohjola .

Editor information

Editors and Affiliations

A Appendix: Further Example Programs

A Appendix: Further Example Programs

For the benefit of readers, we describe our verified implementations of the grep, sort, and cat command-line utilities.

1.1 A.1 Cat

A verified cat implementation was presented in our previous work on CF [13]. The cat implementation presented here differs in two respects: first, it is verified with respect to a significantly more low-level file system model (see Sect. 3.1). Second, it has significantly improved performance, since it is implemented in terms of more low-level I/O primitives. Hence this example demonstrates that reasonably performant I/O verified with respect to a low-level I/O model is feasible in our setting. Here is the code:

figure r

The difference over the previous implementation is pipe_2048, which gains efficiency by requesting 2048 characters at a time from the input stream, rather than single characters as previously. We elide its straightforward CF specification, which essentially states that the output produced on stdout is the concatenation of the file contents of the filenames given as command line arguments. The cat implementation above does not handle exceptions thrown by TextIO.openIn; hence the specification assumes that all command line arguments are valid names of existing files.

1.2 A.2 Sort

The sort program reads all of the lines in from a list of files given on the command-line, puts the lines into an array, sorts them using Quicksort, and then prints out the contents of the array. The proof that the printed output contains all of the lines of the input files, and in sorted order, is tedious, but straightforward.

We do not use an existing Quicksort implementation, but write and verify one from scratch. Unlike the various list-based Quicksort algorithms found in HOL, Coq, and Isabelle, we want an efficient array-based implementation of pivoting. Hence we implement something more akin to Hoare’s original algorithm. We sweep two pointers inward from the start and end of the array, swapping elements when they are on the wrong side of the pivot. We stop when the pointers pass each other. Note that we pass in a comparison function: our Quicksort is parametric in the type of array elements.

figure s

Because this is intrinsically imperative code, we do not use the synthesis tool, but instead verify it with CF directly. The only tricky thing about the proof is working out the invariants for the various recursive functions, which are surprisingly subtle, for an algorithm so appealingly intuitive.

Our approach to verifying the algorithm is to assume a correspondence between the CakeML values in the array, and HOL values that have an appropriate ordering on them. The Quicksort algorithm needs that ordering to be a strict weak order. This is a less restrictive assumption than requiring it to be a linear order (strict or otherwise). Roughly speaking, this will allow us to assume that unrelated elements are equivalent, even when they are not equal. Hence, we can sort arrays that hold various kinds of key/value pairs, where there are duplicate keys which might have different values.

figure t

Even though we are not using the synthesis tool, we do use its refinement invariant combinators to maintain the CakeML/HOL correspondence. This enforces a mild restriction that our comparison function must be pure, but greatly simplifies the proof by allowing us to reason about ordering and permutation naturally in HOL.

The following is our correctness theorem for partition. We assume that there is a strick weak order \({\textsf {cmp}}\) that corresponds to the CakeML value passed in as the comparison. We also assume some arbitrary refinement invariant \( a \) on the elements of the array. The combinator lifts refinement invariants to functions.

figure u

We can read the above as follows, starting in the conclusion of the theorem. Partition takes 5 arguments \( cmp\texttt {\_}{}v \), \( arr\texttt {\_}{}v \), \( pivot\texttt {\_}{}v \), \( lower\texttt {\_}{}v \), and \( upper\texttt {\_}{}v \), all of which are CakeML values. As a precondition, the array’s contents can be split into 3 lists of CakeML values \( elems\texttt {\_}{}vs_{\mathrm {1}} \), \( elems\texttt {\_}{}vs_{\mathrm {2}} \), and \( elems\texttt {\_}{}vs_{\mathrm {3}} \).Footnote 9 Now looking at the assumptions, the length of \( elem\texttt {\_}{}vs_{\mathrm {1}} \) must be the integer value for the lower pointer. A similar relation must hold for the upper pointer, so that \( elem\texttt {\_}{}vs_{\mathrm {2}} \) is the list of elements in-between the pointers, inclusive. We also must assume that the pivot element is in segment to be partitioned (excluding the last element).

The postcondition states that the partition code will terminate, and that there exists two partitions. The array in the heap now contains the two partitions instead of \( elem\texttt {\_}{}vs_{\mathrm {2}} \). The \({\textsf {partition\_pred}}\) predicate (definition omitted), ensures that the two partitions are non-empty, permute \( elem\texttt {\_}{}vs_{\mathrm {2}} \), and that the elements of the first are not greater than the pivot, and the elements of the second are not less. These last two points use the shallowly embedded \({\textsf {cmp}}\) and \( elems_{\mathrm {2}} \), rather than \( cmp\texttt {\_}{}v \) and \( elems\texttt {\_}{}vs_{\mathrm {2}} \).

1.3 A.3 grep

grep  prints to stdout every line from the files that matches the regular expression . Unlike sort, diff and patch which need to see the full file contents before producing output, grep can process lines one at a time and produce output after each line. The main loop of grep reads a line, and prints it if it satisfies the predicate m:

figure x

For each filename, we run the above loop if the file can be opened, and print an appropriate error message to stderr otherwise:

figure y

The latter function satisfies the following CF specification (eliding stderr output):

figure z

The postcondition states that the output to stdout is precisely those lines in \( f \) that satisfy \( m \), with \( f \) and a colon prepended to each line. The three assumptions mean, respectively: that \( f \) is a string without null characters, and \( fv \) is its corresponding deeply embedded CakeML value; that our view of the file system has a free file descriptor; and that \( m \) is a fully specified (i.e., lacking preconditions) function of type \(\texttt {char}\;\texttt {lang}\) and \( mv \) is the corresponding CakeML closure value.

The main function of grep is as follows:

figure aa

parse_regexp and build_matcher are synthesised from a previous formalisation of regular expressions by Slind [31], based on Brzozowski derivatives [29].

The semantics of grep is given by the function \({\textsf {grep\_sem}}\), which returns a tuple of output for stdout and stderr, respectively.

figure ab

regexp_lang is a specification of build_matcher due to Slind, and grep_sem_file is a semantics definition for print_matching_lines_in_file. The final CF specification states that the output to the std* streams are as in \({\textsf {grep\_sem}}\), and has two premises: that there is an unused file descriptor, and that Brzozowski derivation terminates on the given regular expressionFootnote 10.

Rights and permissions

Reprints and permissions

Copyright information

© 2018 Springer Nature Switzerland AG

About this paper

Check for updates. Verify currency and authenticity via CrossMark

Cite this paper

Férée, H., Åman Pohjola, J., Kumar, R., Owens, S., Myreen, M.O., Ho, S. (2018). Program Verification in the Presence of I/O. In: Piskac, R., Rümmer, P. (eds) Verified Software. Theories, Tools, and Experiments. VSTTE 2018. Lecture Notes in Computer Science(), vol 11294. Springer, Cham. https://doi.org/10.1007/978-3-030-03592-1_6

Download citation

  • DOI: https://doi.org/10.1007/978-3-030-03592-1_6

  • Published:

  • Publisher Name: Springer, Cham

  • Print ISBN: 978-3-030-03591-4

  • Online ISBN: 978-3-030-03592-1

  • eBook Packages: Computer ScienceComputer Science (R0)

Publish with us

Policies and ethics