What's The Best Language For Safety Critical Software?
stackoverflow.com
stackoverflow.com
The software includes integration all the standard military navigation and weapons targeting and control functionality, along with a secure datalink using the Link-ZA protocol and a nifty radar simulation system (basically an airborne LAN for air-to-air training) that runs over it.
They just delivered the latest iteration of the software which allows them to receive data-linked air picture information from other aircraft, such as an AWACS platform. Throughout all this the project has had no major snags, has missed no deadlines and has come in on budget.
So my point being that it's possible to write complex safety-critical software in C if your processes and programmers are good enough and you're willing to pay about $100 million for it.
But that being said: this is just proof that it "can" be done, not that there aren't better choices. Specifically, could you do a missile management system in less than a million lines of code or for less than $100M with equivalent performance and quality on another platform? I think most of our intuitions would agree that you could.
And for the record, I didn't call you anything. A "nit" is a pedantic correction, c.f "nitpicking". I could have said "quibble" too. It's a reference to your words, not you.
Probably part of the $100M price tag is a hefty license fee to use said OS.
Avionics software certification is phenomenally expensive. Both Airbus and Boeing have warned in recent years that it has become such a big part of their development costs that they're concerned about it becoming unsustainable.
> if your processes ... are good enough
I would say that it's possible to write complex safety-critical software in any language, provided it's completely specified beforehand in, well, military detail. This program was likely done when that spec hit the programmers' desks--the project from that point on basically consisted of transforming pseudocode to language-of-choice-X without mucking anything up in the process.
And while the specification process is obviously extremely extensive, it's only a part of the total process and not a guarantee of bug-free code. Many of these projects have gone pear-shaped during the implementation phase despite having good specifications.
So that's why having good programmers, a good development process, solid libraries and great tooling is so important for the implementation phase. Especially because the requirements always change during development, even for mission-critical military avionics software.
One of the main problems with the F-35 program is that the implementation phase for the development of the plane's software has gone badly wrong. It's already by far the most expensive avionics program ever.
Finally the testing phase is super-critical. ATE designed their own extremely thorough testing setup, ranging from software verifiers to full-scale test-benches to simulate on the ground the full range of software functionality in flight.
It's a difficult process that's very easy to get wrong as a result of the incredible complexity of modern avionics and weapons systems. I am in awe of those teams that get it right.
You could argue the one language which actually does lend itself to formal verification is the one which has been formally specified via operational semantics; namely, Standard ML.
However, any system that runs on top of a virtual machine is problematic because of how extensive their runtimes are. Developing an efficient and competitive DO-178b-certified JVM, for example, would be a huge undertaking, but so would modifying an existing JVM.
Further, anything garbage collected is a problem because you can't have any situations where a timing constraint is violated because of your garbage collector. The best way to prevent this would be to schedule, say, 50 milliseconds every second for garbage collection. But there's still a limit there where the GC can no longer keep up with the rate of garbage production, which could lead to your application running out of memory and failing. Proving that your application will never reach said limit would be hard either by static or dynamic analysis.
We switched because we wanted to reduce the risks of bugs, but you must remember that in most mission critical applications, one must consider failure not only in the software. You use redundancy - in the software, in the hardware, and in the network. We even used several separate power systems and several redundant generators.
EDIT: see http://goedel.cs.uiowa.edu/MVD/talks/Vitek.pdf presentation slides from 9/2012 on realtime and safety-critical Java.
There are a few interesting developments regarding Java for safety-critical and mission-critical applications. One is JSR-302, which is defining a safety-critical subset of Java which will not have garbage collection and will use of a run-time stack rather than a heap for temporary object allocation, the removal of dynamic class-loading, changes to task scheduling and a smaller standard library. These should make it possible to certify JSR-302-compatible Java runtimes and applications for safety-critical and mission-critical tasks.
The second is DO-178C, the follow-on to DO-178B, which will incorporate additional work on the certification of object-oriented languages such as Java.
http://cpptruths.blogspot.com/2005/11/c-templates-are-turing...
Most likely no one will write such a template in a mission critical system. However, from a formal verification standpoint the only way to verify the code is to check the binaries.
As much as I love Erlang, nothing in this paragraph applies to it (it would apply much, much better to Haskell though still not perfectly): Erlang is not a pure language and it does not put any limitation on side-effects (the way Haskell does by putting them in separate monadic containers unless an `unsafe` function is used).
Erlang does use immutability (its only mutable data structure is the process dictionary[-1]), it does not share state between concurrent processes[0] and features pattern-matched bindings (technically `=` in Erlang performs pattern-matching, not equality, but if a matchable part is unbound Erlang will simply fill it with the correspondence from the other side, as a result it behaves much like single assignment in many situations), which do help in making code pure and correct, but fundamentally its approach to reliability is not to make code unbreakable (the way you'd do by formally proving it for instance) but by making recovery and error management easy, simple and widely supported by the runtime and its libraries (OTP), in (no small part) part by applying the telecom-borne principles of separating concerns (between the thing that does the job and the thing which recovers the error in case the first one blows up) and building redundant systems (if you have a single machine and it crashes you're gone, if you have 2 machines and the first one crashes the second one can handle things until the first one comes back (and you can have a third machine overseeing that with its own hot spare), Erlang encourages doing that at the process level)
[-1] a process mailbox might also be considered mutable, in a way
[0] it does share memory for big binaries — they are shared and reference-counted — but these binaries are immutable so no state
But it is important to understand what "pure" is and is not, that almost no language is pure (because it's hard to produce useful pure software, or even to define how it would work precisely) and that very few strongly encourage purity (at the language level, many do as a social level but offer no help or support for actually doing it when writing code)
Meanwhile, languages like Haskell don't cheat quite as much as you suggest. Monads are functions, and even in the IO Monad you're doing purely functional manipulation of IO primitives. Think of it as a purely functional funnel through which the dirty data of reality is poured.
Also, it's possible to write PHP in functional style, it's just not common. http://www.michielovertoom.com/software/functional-php/
In computer science, functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids state and mutable data. It emphasizes the application of functions, in contrast to the imperative programming style, which emphasizes changes in state.
All programming languages can be written in a functional style. I can create new function types in C++ with templates and function pointers to simulate the action of closures. A functional programming languages is one which has the λ-calculus as its base model of computation as idiomatic in the language. Note that the description above is exactly what is meant by "λ-calculus as the base model of computation." What the article is trying to say is that some languages can be described almost entirely as syntactic sugar on λ-calculus (or System Fω in certain cases).
You do understand that I can simulate first-class functions in any mainstream language? This is not what PL researchers mean when they say "functional programming." Functional programming languages are those which are also predominantly used in a functional style.
I didn't notice purity become quite the deal it's become until recently, when boosters started talking it up as a concurrency panacea. Sure it's always been ubiquitous, and mutability was seen as a tricky tool that's best used sparingly, but that was seen as a matter of culture as much as it was a matter of definition. Valuing immutability definitely wasn't presented as functional 'territory' - a somewhat difficult position to hold given things like the PURE keyword in Fortran, or the variable and constant keywords in Ada.
In other words it doesn't matter how we define "function" as long as they are first class? that's nonsense.
Regardless of historical (ab)usage "functional programming" should be re-appropriated to mean (from wikipedia): "a programming paradigm based on mathematical functions rather than changes in variable states".
Functions are from math, and mathematical functions are first of all pure (f(x) = x + 1 shouldn't also give your dog a bath).
In the future, I expect the next things to become mainstream from the Weird FP Languages will be more type inference, and side-effect annotations. I've often wished that I could declare a few types in a language where you normally omit them, and have the compiler infer as much from those declarations as it reasonably can. I know it's possible, because the SBCL compiler for Common Lisp already does it. This gives many of the same benefits as stricter typing, but in a very lightweight way. And the idea of side-effect annotation is that you can declare whether or not functions have side-effects, and then have the compiler warn you if your code breaks that assumption -- like the PURE functions in Fortran you mentioned. Considering the dangers of mutable state, anything easy you can do to help keep it straight is probably a win.
(Note that in a sufficiently extensible language, e.g. Arc, these things can easily exist outside the language core. I hope that this kind of extensibility becomes mainstream, but wouldn't get my hopes up.)
Also, I think you mean "first class functions" rather than "first order".
Sorry if it's not relevant, but I thought it might be interesting to others.
Employer: Airbus.
Which language did the team choose for the analyzer? OCaml.
It is possible to write OCaml programs that are provably correct.
[0] "Caml language family" http://caml.inria.fr/
[1] "Success Stories" http://caml.inria.fr/about/successes.en.html
[2] "OCaml: a serious contender" http://caml.inria.fr/about/programming-contest.en.html
edit: clarity
Something like "if you want to write small, solid and safe control logic for an embedded system, you should generally use C. But if you want to prove it mathematically correct, you'll need to test it with software written in this obscure language from INRIA in Grenoble."
Disclosure: Ocaml is my favorite language by far.
For a real eye opener, look through the current list of research topics at INRIA. I have (mis)spent hours reading the published papers and source code from some of their projects.
[0] "Equipes de recherche du centre Grenoble" http://www.inria.fr/recherches/equipes-de-recherche/recherch...
The language is a part of this, but assembling the right team with the right mentality is the key. There was the DOD Ada mandate and over the years there were some interesting studies showing reliability differences between Ada and C++, but also in hopes of keeping costs lower, they use C and C++ in a lot of that stuff now so that they can use off the shelf software libraries and such. That says a lot, the ability to hire people and develop with some velocity matters too. Language won't make a shitty architecture good. Language won't make a shitty team good, either.
Fundamentally, if you pay attention to where this stuff matters most, Ada has to be a standout choice but those industries don't change, they don't adopt new stuff and the whole process by which that happens in them isn't one that results in the best technology, it's one that results in a consensus technology. The risk of a line of code knocking a plane down or letting the missile hit the wrong target are just fundamentally different than letting someone log in to a web site; maybe they shouldn't be but the very culture difference between those groups is huge and so you can look at them and say "ah, we should use Ada too, 'it's safer'" but that is probably not a great business decision. At least, not right now.
In such a case, the programming languages and programming practices which facilitate formal reasoning are more or less the same ones which facilitate informal reasoning. Avoid "spooky action at a distance", i.e. keep state changes localized (this enables you to use, say, separation logics). Funky flow control is difficult to model/reason about. Non-determinism as well. Type safety and static type systems help.
Then again, if "safety" includes "security", you not only have to wonder whether your system is correctly implementing its API, but also whether the API is secure in itself. A paper by Steel & al [1] contains a partly amusing, and partly frightening account of one such investigation.
[1]: http://www.lsv.ens-cachan.fr/Publis/PAPERS/PDF/BCFS-ccs10.pd...
Not exactly relevant to this discussion, but good to keep in mind.
I have no problem believing that aerospace people distrust optimizers. They are in fact unlikely to trust anything before they have inspected the assembler output from the compiler.
Quick development has its drawbacks as well, as you so beautifully demonstrated.
---
NOTE ON JAVA SUPPORT. THE SOFTWARE PRODUCT MAY CONTAIN SUPPORT FOR PROGRAMS WRITTEN IN JAVA. JAVA TECHNOLOGY IS NOT FAULT TOLERANT AND IS NOT DESIGNED, MANUFACTURED, OR INTENDED FOR USE OR RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF JAVA TECHNOLOGY COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE.
---
Made me smile and also scared me at the same time.
In hindsight, I do think I might have been a bit judgmental back then, Eiffel has some very nice constructs and language limitations that allow you to build safety critical software (as you put it).
There should be some sort of ban or restriction for teachers teaching their own textbooks or obscure languages.
But in some cases, you simply can't use something like C. A friend of mine is currently trying to write a microkernel and mathematically prove that it's 100% bug-free (similar works on the L4 microkernel: [1] and [2], though they might not be the best articles on this matter as I just found these links with a quick Google search). He uses Haskell and ML for this project (He'd use Ada, but he already knows some Haskell and ML and doesn't want to re-learn everything).
[1]: http://ertos.nicta.com.au/research/l4.verified/
[2]: http://www.linuxfordevices.com/c/a/News/NICTA-sel4-OK-Labs-O...
[1] http://www.ertos.nicta.com.au/publications/papers/Klein_08.p...
I think it'd be a hard sell to use ML in safety-critical systems due to the non-deterministic runtime of the GC and potential for memory exhaustion.
As a counter-example, C doesn't prevent formal methods - FWIW VxWorks puts out Arinc 653 and MILS products that are verified* using formal methods.
* - This is carries the following caveat, I believe the core OS is formally verified but the particular BSP/configuration needs be tested/certified by the customer or at the customer's expense.
One of the foundational concepts of safety-critical design is strict scheduling of resources. The schedule must be strictly deterministic. The process must get it's work done in the time allocated because the scheduler will move on regardless. It's why you don't do recursion in safety-critical systems. Strictly speaking, you're not even supposed to use while loops in safety-critical systems. In the five years I worked in avionics, the only time I saw a while loop was in code review where the person who wrote it (always a new person) was directed to take it out.
All that being said, safety-critical software production is more about the processes than coding. Even crappy programmers can be taught how to write good safety-critical code when the company adheres to best practices.
As far as the original post, Ada and C. Ada has tremendous support for concurrency and is stable as the day is long. Not much in the way of libraries, but honestly not that important on the kinds of projects you would use Ada to build.
I can understand the concern about loop termination, but what is the alternative? Are for loops without a hardcoded upper limit allowed? Or must all loops be unrolled?
Do you have any links to your friends work?
I work with C every day as an embedded developer. I've done a lot of safety critical work. By far the worst aspect of safety critical development is the complete inadequacy of many programmers who work on it.
The level of complexity that shows up in these system scares me. Even when introducing languages like Ada, people find a way to abuse them. Budgets get tight, schedules slip, and verification gets lax. These programmers are then the only people capable of dealing with the massively complex system they've built and the cycle repeats itself.
Ada's a great language, but it's not a panacea. I'm working on a language for embedded systems as well, but it's not going to ever fix the 'bad programmer' problem. The best I can hope to do is find ways to reduce the complexity of these systems through language features.
That is the absolute major issue with doing safety-critical development right: it has a cost, that cost is very, very high, and few want to pay it.
One of the few groups I'm aware of which does pay it is the (now defunct, I guess) software shuttle group. Their work was expensive, it was process-heavy (the 1997 story on them quoted 2500 pages of spec for GPS integration which ended up totaling 6.3kloc change to the source, 1.5% of it) but they delivered exactly what they were set to: critically safe code (in fact, if I remember correctly the Shuttle software group is the only area of NASA Feynman praised in his Challenger report)
This is absolutely true. The costs to manage complexity now seem high when software is being built. The costs of managing that complexity in the future are far higher and may have to be paid in lives.
Global complexity might not be much harder to measure than local complexity, perhaps even easier. Look for interdependencies.
I've thought about ratcheting it down to 15 based on stuff I've read, but unfortunately that particular failure metric can only be set as low as the class-level in the latest version. The next version (4.0) will have the ability to set the threshold at the method level.
In any case, I'd rather have a handful of places in the code where someone has to do something goofy to work around that metric rather than accidentally allow the whole project's complexity to creep up as time goes on.
Are your co-workers cool with that? Complexity metrics are a little "squishy", but the costs of a broken build are large.
Then,
pmccabe `find . -name '*.c'` | sort -nr | awk '($1 > 10)'
gives me all functions that have a cyclomatic complexity over 10; I often add it as a target in a Makefile, so I can easily check this. Similar, for functions of a certain length.https://en.wikipedia.org/wiki/Cyclomatic_complexity
checkstyle is a Java lint that, among other things, can check code complexity:
The one thing that fucks up SO is the moderators.
I'd like to see an increasing threshold for closing once reopened. Something along the lines of if reopened once, 10 close votes required... if reopened twice, 20 close votes required.
That being said, avoid PHP, Matlab and C# at all costs if safety is at all important to you.
[1] - http://www.amazon.com/Toward-Defect-Programming-Allan-Stavel...
What's "best" for what? Does the safety critical system need to be fast and respond to real time events in real time? Or is it safety critical but is allowed to be slow? What kind of hardware is it running on?
Depending on your hardware, the only safety critical language may be assembler, so you can check every single instruction and make sure it's precise.
Having said all that, Apollo 11's code was written in a meta-language, then compiled to assembly instructions, then hand-compiled into computer code. You don't get any more safety critical than taking the first men to the moon.
http://googlecode.blogspot.com.br/2009/07/apollo-11-missions...
There are concrete answers to the question. Many languages are just flat not capable of being safety-critical.
This question has been around since 2008 and clearly has garnered enough interest to earn 167 votes on its top answer. It's definitely one of those question/answers that I walk away feeling more knowledgable from -- and presumably the people upvoting it on HN feel the same way.
So yeah it survives intact and unmolested for nearly 5 years -- but an hour on the HN front page is its death knell.
I guess I understand the reasoning behind closing it, but it smacks of the worst kind of pedantry.
I've also seen some pretty horrible flight software written in C.
Naturally, and as with all software, clear requirements, avoidance of "feature creep", and a clear vision of the lead developers is what distinguishes the good from the bad. So these are critical components of "safe" software development, irrespective of whatever "safe" language you're using...
Edit: On second thought, strict adherence to a uniform coding standard is incredibly important too. Even though it's just cosmetic, it holds the developers to a higher standard, and strongly discourages breaking the rules to get things done quickly.
I'll quite happily edit that out of the answer if I'm wrong. Can somebody elucidate this here - Am I barking up the wrong tree about verifying Erlang systems?
In the future, as we figure out how to make things like Haskell or an ML meet all three requirements, we'll move away from the labour intensive languages.
In fact, if strict real-time requirements aren't necessary Haskell is already useful. It's being used in the UK's national air traffic system.
Certain assumptions can make it trivial to verify the performance of code (e.g. assume all memory accesses miss cache and happen right at the beginning of a DRAM refresh for the address you are accessing).
That usually ends up too crappy, so you bound the worst case a bit more by using the cache-replacement model of the CPU you are using. Writeback cache make this analysis quite difficult which is one of many reasons for the existance of writethrough cache. In any event, given a certain execution model, it is tractable to bound the worst-case performance of a system formally.
http://www.misra-c.com/Activities/MISRAC/tabid/160/Default.a...
I can see safety critical software, software that has been verified to a very high degree to not contain faults. I see fault tolerant software software that does have internal faults but can recover quickly and often transparently to the user.
He seems to be talking about proofs by hand, though, where you model a protocol mathematically and then write out a paper proving the protocol to be have desired properties. I have no idea if anyone's tried to quantify how that kind of complexity scales.
Similarly, with multi-threading, either the program works for a simple reason that it's author understands, or its correctness is not understood (and it probably doesn't work). People don't work through exponentially increasing number of cases in a growing project, and nor will formal proofs.