How Programming Languages Evolve to Reduce Risks
The future of microcontroller- and embedded processor-based systems is clear. While there is definitely a large amount of logic that can be directly encoded in the silicon or FPGA, there is also an increasing need for more complex or easier-to-update features to be developed in software on top of it. And these systems may have extremely demanding requirements for safety and/or security. Trends in the automotive domain with assisted or automated driving are a very good example.
The bad news is that programming is much more an empirical process than a deterministic science. Developers can write up to hundreds of lines of code per day—shared with potentially hundreds of other developers—all having slightly different appreciation of what the program should do or how software engineering principles should be applied. Considering that some of these applications will be maintained by generations of developers, it’s no surprise to hear that there is roughly one bug per hundred lines of code . Spread over millions of lines running on the simplest device nowadays, this means tens of thousands of lurking bugs, opening doors to hackers and potentially jeopardizing life or property.
Traditional industry response has been a combination of processes and tools. These have been successful, but also come at a cost in terms of verification effort. However, this also comes at a cost in terms of verification effort. As a result, outside of domains such as aerospace and defense, almost no industry has been able to justify the effort.
The tide is turning, though. As software is getting more and more tightly involved in almost every device, so are demands for safety and security. The automotive domain with assisted or autonomous driving is a good example (Figure 1). The increased cost may look prohibitive at first, but fortunately, there are other ways to improve the situation: starting by improving the programming language itself.
Beyond the Infamous C
A painful number of bug reports are linked to vulnerabilities associated with the C programming language, or C-based relatives such as C++. Many tools exist for no other reason than to try to offset these shortcomings, which can be split into two main categories:
• Error-prone language definition: These relate to constructs that may either be ambiguous, or be difficult to interpret. Something like: “int * i; i = i + 1” may mean pointer arithmetic to a careful developer, or be mistaken for an integer increment. While simple issues can be identified through static analysis, their accumulation may eventually render the code very difficult to analyze.
• Lack of specification capabilities: While C fully allows a developer to express how a program works, it doesn’t provide much capability for expressing what it should do, or under which constraints it should operate. Lack of such specification means many missed opportunities to check software against its intended functionality. External tools exist to work around this issue, but because they’re not integrated in the language, they’re facing many limitations.
The good news is that there seems to be a renewed effort in the programming language community to provide alternatives to C. For the first issue, an extremely promising example is the Rust language  which seems to be getting lot of traction with an imaginative approach to pointer safety. Even within the C community there’s a growing understanding that “trusting the developer” shouldn’t be a design principle anymore .
Initiatives also exist on the second aspect. The “easy” answer is programming by assertion, for example by adding intermediate verification checks in the software that can be enabled or disabled depending on the situation. But there is also progress through the evolution of programming languages. A very good example is the work on the new C++ 202X  standard which is looking at extending specification with contracts.
Ada & SPARK: The New Wave?
There’s no doubt that programming languages at large are improving at meeting safety and security concerns. However, a programming language was designed 35 years ago to solve these very issues, and today is still one of the most credible alternatives to C for safety and security purposes: the Ada programming language. Like others, it has come through many revisions, with contract-based programming introduced in its 2012 revision for example. From the start, it has offered a very precise and explicit semantics, and has provided mechanisms for specifying code constraints such as scalar type ranges, array bounds, data mapping on memory, floating point value precision and many others.
Having a precise definition and constraints in the language gave rise to the formally analyzable SPARK subset  of Ada. With Ada as with any other languages, processes and tools will be needed. But with a strong foundation, it’s possible to go much further. Most C static analysis tools are in the business of identifying potential bugs while being unable to guarantee that all have been caught.
Using the SPARK subset of Ada together with the corresponding tool support can guarantee that all errors of a certain category have been found, with a very low rate of false alarms. Demonstrating a property such as absence of buffer overflow for example becomes a practical matter, opening the door to much more advanced functional analysis, such as proving a program against some of its requirements. This is not just an advantage in theory; an analysis conducted by market research firm VDC in 2018  demonstrated that Ada could lead to cost reduction up to 40% over C in certain industries.
It’s therefore no surprise that the Ada and SPARK, which were still a few years ago mostly used within the aerospace domain, are now being adopted by a new wave of users, in industries such as medical devices  , automotive , security  and semiconductors .
The Daunting Legacy
There is, however, an important point that all of the above assumes. Adopting Ada or SPARK, going the Rust route or switching to a future version of C or C++ will require you to deal with legacy software, which wasn’t written for these standards. While it may be reasonable to take on some minimal rewrite, the cost of rewriting these millions of lines of code will be prohibitive.
As always, there’s a reasonable alternative. Most of these languages interface well with legacy C software. With C or C++ this is obvious, but for example, Rust and Ada/SPARK also provide specific support for interfacing with C. So, the idea that the industry is going towards to is: keep the legacy software, rewrite what’s highly critical or sensitive, and develop new modules with whichever new language is selected. This will allow the new code to reach proper levels of safety more effectively, giving some more time to find solutions to improving what can’t change.
Published in Circuit Cellar Magazine Issue 346 • May 2019 — Get a PDF of the Issue