Part 1 of this three-article series described how object-oriented programming (OOP) introduces a number of vulnerabilities that need to be mitigated if OOP is to be used successfully in high-integrity applications that require DO-1781,2 certification. Part 2 summarized the main elements of Ada’s OOP model. In this final installment, we’ll explain how these features can help developers meet the high integrity challenge.
Although Ada did not, strictly speaking, start out as an OOP environment, it included the concepts of packages, objects, and encapsulation, which lent themselves to object-oriented techniques. Subsequent releases, particularly Ada 95, added major OOP functionality such as classes/tagged types, inheritance, polymorphism, and dynamic dispatch, and Ada 20053,4 built on that foundation. Now, Ada 20125 boasts a number of new OOP-related facilities that support high-integrity development.
Many of the features in Ada that help developers write safe and secure programs in general, also apply with object-orientated methodology. These features include:
Strongly typed numeric data. The Ada datatype facility allows programmers to declare distinct integer types and distinct floating-point types to help prevent the misuse of numeric values. Different units of measure can be captured by different floating-point types such as Miles and Kilometers, for example; assigning a value of type Miles to a variable of type Kilometers would generate a compile-time error. This facility makes the program’s intent clear and helps avoid subtle implicit conversion bugs.
Scalar range constraints. Ada allows scalar variables to be declared with explicit range constraints. A check is made, typically at run time, to ensure that assigned values are within the specified bounds. Range constraints are instrumental in catching “wild value” errors and in facilitating formal proof of program properties. Generalizing this facility, Ada 2012 has introduced subtype predicates, allowing an arbitrary Boolean expression to be provided as part of a subtype declaration. The predicate is checked at specific points of execution, such as on assignment, and an exception raised if the predicate is False.
Array index checking. Array indexing in Ada is checked, raising a run-time exception when the index is out of bounds. “Buffer overrun” and similar vulnerabilities do not arise.
Integer overflow checking. In Ada, a signed integer arithmetic operation yielding a result that would exceed the largest integer value will raise a run-time exception rather than “wrapping around” to produce a negative value with large magnitude. Operations that produce a result lower than the most negative integer value received analogous treatment. This check prevents errors that can lead to vulnerabilities.
Safe pointers to declared objects. Ada allows access values, or pointers, to denote declared data objects, but provides several kinds of checks to ensure safety. First, an object that will be designated by an access value needs to be explicitly marked as “aliased,” to make its intent obvious to the human reader and to prevent the compiler from performing potentially unsafe optimizations. Second, Ada requires a scope depth check (typically at compile time) to prevent dangling references, i.e., to ensure that a pointer to a local variable does not have a longer lifetime than the variable itself.
Alternatives to dynamic allocation. Ada’s discriminated record facility, and more generally the bounded container types introduced in Ada 2005, allow the definition of flexible-length data structures—i.e., which can grow or shrink within specified bounds—without the need for dynamic allocation.
Restrictions on feature usage. Ada supplies a compiler directive, pragma Restrictions, that allows the programmer to specify features that are not being used. Based on the features so listed, the Ada implementation can supply simpler versions of run-time libraries that both simplify certification and result in smaller-footprint executables.