Fragile binary interface problem

Last updated

The fragile binary interface problem or FBI is a shortcoming of certain object-oriented programming language compilers, in which internal changes to an underlying class library can cause descendant libraries or programs to cease working. It is an example of software brittleness.

Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which can contain data, in the form of fields, and code, in the form of procedures. A feature of objects is an object's procedures that can access and often modify the data fields of the object with which they are associated. In OOP, computer programs are designed by making them out of objects that interact with one another. OOP languages are diverse, but the most popular ones are class-based, meaning that objects are instances of classes, which also determine their types.

A compiler is a computer program that transforms computer code written in one programming language into another programming language. Compilers are a type of translator that support digital devices, primarily computers. The name compiler is primarily used for programs that translate source code from a high-level programming language to a lower level language to create an executable program.

In computer programming and software engineering, software brittleness is the increased difficulty in fixing older software that may appear reliable, but fails badly when presented with unusual data or altered in a seemingly minor way. The phrase is derived from analogies to brittleness in metalworking.

Contents

This problem is more often called the fragile base class problem or FBC; however, that term has a wider sense.

The fragile base class problem is a fundamental architectural problem of object-oriented programming systems where base classes (superclasses) are considered "fragile" because seemingly safe modifications to a base class, when inherited by the derived classes, may cause the derived classes to malfunction. The programmer cannot determine whether a base class change is safe simply by examining in isolation the methods of the base class.

Cause

The problem occurs due to a "shortcut" used with compilers for many common object-oriented (OO) languages, a design feature that was kept when OO languages were evolving from earlier non-OO structured programming languages such as C and Pascal.

Structured programming is a programming paradigm aimed at improving the clarity, quality, and development time of a computer program by making extensive use of the structured control flow constructs of selection (if/then/else) and repetition, block structures, and subroutines.

C (programming language) general-purpose programming language

C is a general-purpose, imperative computer programming language, supporting structured programming, lexical variable scope and recursion, while a static type system prevents many unintended operations. By design, C provides constructs that map efficiently to typical machine instructions, and it has therefore found lasting use in applications that were previously coded in assembly language. Such applications include operating systems, as well as various application software for computers ranging from supercomputers to embedded systems.

Pascal is an imperative and procedural programming language, which Niklaus Wirth designed in 1968–69 and published in 1970, as a small, efficient language intended to encourage good programming practices using structured programming and data structuring. It is named in honor of the French mathematician, philosopher and physicist Blaise Pascal.

In these languages there were no objects in the modern sense, but there was a similar construct known as a record (or "struct" in C) that held a variety of related information in one piece of memory. The parts within a particular record were accessed by keeping track of the starting location of the record, and knowing the offset from that starting point to the part in question. For instance a "person" record might have a first name, last name and middle initial, to access the initial the programmer writes thisPerson.middleInitial which the compiler turns into something like a = location(thisPerson) + offset(middleInitial). Modern CPUs typically include instructions for this common sort of access.

In computer science, a record is a basic data structure. Records in a database or spreadsheet are usually called "rows".

In computer science, an offset within an array or other data structure object is an integer indicating the distance (displacement) between the beginning of the object and a given element or point, presumably within the same object. The concept of a distance is valid only if all elements of the object are of the same size.

When object-oriented language compilers were first being developed, much of the existing compiler technology was used, and objects were built on top of the record concept. In these languages the objects were referred to by their starting point, and their public data, known as "fields", were accessed through the known offset. In effect the only change was to add another field to the record, which is set to point to an immutable virtual method table for each class, such that the record describes both its data and methods (functions). When compiled, the offsets are used to access both the data and the code (via the virtual method table).

A virtual method table (VMT), virtual function table, virtual call table, dispatch table, vtable, or vftable is a mechanism used in a programming language to support dynamic dispatch.

Symptoms

This leads to a problem in larger programs when they are constructed from libraries. If the author of the library changes the size or layout of the public fields within the object, the offsets are now invalid and the program will no longer work. This is the FBI problem.

Computer program Instructions to be executed by a computer

A computer program is a collection of instructions that performs a specific task when executed by a computer. A computer requires programs to function.

Although changes in implementation may be expected to cause problems, the insidious thing about FBI is that nothing really changed, only the layout of the object that is hidden in a compiled library. One might expect that if one changes doSomething to doSomethingElse that it might cause a problem, but in this case one can cause problems without changing doSomething, it can be caused as easily as moving lines of source code around for clarity. Worse, the programmer has little or no control over the resulting layout generated by the compiler, making this problem almost completely hidden from view.

In complex object-oriented programs or libraries the highest-level classes may be inheriting from tens of classes. Each of those base classes could be inherited by hundreds of other classes as well. These base classes are fragile because a small change to one of them could cause problems for any class that inherits from it, either directly or from another class that does. This can cause the library to collapse like a house of cards as many classes are damaged by one change to a base class. The problem may not be noticed when the modifications are being written if the inheritance tree is complex. Indeed, the developer modifying the base class is generally unaware of which classes, developed by others, are using it.

Solutions

Languages

One solution to the fragile binary interface problem is to write a language that knows the problem exists, and does not let it happen in the first place. Most custom-written OO languages, as opposed to those evolved from earlier languages, construct all of their offset tables at load time. Changes to the layout of the library will be "noticed" at that point. Other OO languages, like Self, construct everything at runtime by copying and modifying the objects found in the libraries, and therefore do not really have a base class that can be fragile. Some languages, like Java, have extensive documentation on what changes are safe to make without causing FBI problems.

Another solution is to write out an intermediate file listing the offsets and other information from the compile stage, known as meta-data. The linker then uses this information to correct itself when the library is loaded into an application. Platforms such as .NET do this.

However, the market has selected programming languages such as C++ that are indeed "position dependent" and therefore exhibit FBI. In these cases there are still a number of solutions to the problem. One puts the burden on the library author by having them insert a number of "placeholder" objects in case they need to add additional functionality in the future (this can be seen in the structs used in the DirectX library). This solution works well until you run out of these dummies -- and you do not want to add too many because it takes up memory.

Objective-C 2.0 provides non-fragile instance variables by having an extra level of indirection for instance variable access.

Another partial solution is to use the Bridge pattern, sometimes known as "Pimpl" ("Pointer to implementation"). The Qt framework is an example of such an implementation. Each class defines only one data member, which is a pointer to the structure holding the implementation data. The size of the pointer itself is unlikely to change (for a given platform), so changing the implementation data does not affect the size of the public structure. However, this does not avoid other breaking changes such as introducing virtual methods to a class that has none, or changing the inheritance graph.

Linkers

Another solution requires a smarter linker. In the original version of Objective-C, the library format allowed for multiple versions of one library and included some functionality for selecting the proper library when called. However this was not always needed because the offsets were only needed for fields, since methods offsets were collected at runtime and could not cause FBI. Since methods tend to change more often than fields, ObjC had few FBI problems in the first place, and those it did could be corrected with the versioning system. Objective-C 2.0 added a "modern runtime" which solved the FBI problem for fields as well. Additionally, the TOM language uses runtime collected offsets for everything, making FBI impossible.

Using static instead of dynamic libraries where possible is another solution, as the library then cannot be modified without also recompiling the application and updating the offsets it uses. However static libraries have serious problems of their own, such as a larger binary and the inability to use newer versions of the library "automatically" as they are introduced.

Architecture

In these languages the problem is lessened by enforcing single inheritance (as this reduces the complexity of the inheritance tree), and by the use of interfaces instead of base classes with virtual functions, as interfaces themselves do not contain code, only a guarantee that each method signature the interface declares will be supported by every object that implements the interface.

Distribution method

The whole problem collapses if the source code of the libraries is available. Then a simple recompilation will do the trick.

See also

Related Research Articles

In object-oriented programming, a class is an extensible program-code-template for creating objects, providing initial values for state and implementations of behavior. In many languages, the class name is used as the name for the class, the name for the default constructor of the class, and as the type of objects generated by instantiating the class; these distinct concepts are easily conflated.

Dylan (programming language) multi-paradigm programming language

Dylan is a multi-paradigm programming language that includes support for functional and object-oriented programming (OOP), and is dynamic and reflective while providing a programming model designed to support generating efficient machine code, including fine-grained control over dynamic and static behaviors. It was created in the early 1990s by a group led by Apple Computer.

Multiple inheritance is a feature of some object-oriented computer programming languages in which an object or class can inherit characteristics and features from more than one parent object or parent class. It is distinct from single inheritance, where an object or class may only inherit from one particular object or class.

<i>Design Patterns</i> Computer Science book

Design Patterns: Elements of Reusable Object-Oriented Software (1994) is a software engineering book describing software design patterns. The book's authors are Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides with a foreword by Grady Booch. The book is divided into two parts, with the first two chapters exploring the capabilities and pitfalls of object-oriented programming, and the remaining chapters describing 23 classic software design patterns. The book includes examples in C++ and Smalltalk.

This is a comparison of Java and C++, two prominent object-oriented programming languages.

In object-oriented programming, in languages such as C++, and Object Pascal, a virtual function or virtual method is an inheritable and overridable function or method for which dynamic dispatch is facilitated. This concept is an important part of the (runtime) polymorphism portion of object-oriented programming (OOP). In short, a virtual function defines a target function to be executed, but the target might not be known at compile time.

This article compares two programming languages: C# with Java. While the focus of this article is mainly the languages and their features, such a comparison will necessarily also consider some features of platforms and libraries. For a more detailed comparison of the platforms, please see Comparison of the Java and .NET platforms.

IBM System Object Model

In computing, the System Object Model (SOM) is an object-oriented shared library system developed by IBM. DSOM, a distributed version based on CORBA, allowed objects on different computers to communicate.

In computer science, dynamic dispatch is the process of selecting which implementation of a polymorphic operation to call at run time. It is commonly employed in, and considered a prime characteristic of, object-oriented programming (OOP) languages and systems.

Class-based programming, or more commonly class-orientation, is a style of Object-oriented programming (OOP) in which inheritance occurs via defining classes of objects, instead of inheritance occurring via the objects alone.

Late binding, dynamic binding, or dynamic linkage is a computer programming mechanism in which the method being called upon an object or the function being called with arguments is looked up by name at runtime.

Managed Extensions for C++ or Managed C++ is a now-deprecated set of language extensions for C++, including grammatical and syntactic extensions, keywords and attributes, to bring the C++ syntax and language to the .NET Framework. These extensions were created by Microsoft to allow C++ code to be targeted to the Common Language Runtime (CLR) in the form of managed code, as well as continue to interoperate with native code.

The object-relational impedance mismatch is a set of conceptual and technical difficulties that are often encountered when a relational database management system (RDBMS) is being served by an application program written in an object-oriented programming language or style, particularly because objects or class definitions must be mapped to database tables defined by a relational schema.

In object-oriented programming, inheritance is the mechanism of basing an object or class upon another object or class, retaining similar implementation. Also defined as deriving new classes from existing ones and forming them into a hierarchy of classes. In most class-based object-oriented languages, an object created through inheritance acquires all the properties and behaviors of the parent object. Inheritance allows programmers to create classes that are built upon existing classes, to specify a new implementation while maintaining the same behaviors, to reuse code and to independently extend original software via public classes and interfaces. The relationships of objects or classes through inheritance give rise to a directed graph. Inheritance was invented in 1969 for Simula.

A foreign function interface (FFI) is a mechanism by which a program written in one programming language can call routines or make use of services written in another.

Component Object Model (COM) is a binary-interface standard for software components introduced by Microsoft in 1993. It is used to enable inter-process communication object creation in a large range of programming languages. COM is the basis for several other Microsoft technologies and frameworks, including OLE, OLE Automation, Browser Helper Object, ActiveX, COM+, DCOM, the Windows shell, DirectX, UMDF and Windows Runtime. The essence of COM is a language-neutral way of implementing objects that can be used in environments different from the one in which they were created, even across machine boundaries. For well-authored components, COM allows reuse of objects with no knowledge of their internal implementation, as it forces component implementers to provide well-defined interfaces that are separated from the implementation. The different allocation semantics of languages are accommodated by making objects responsible for their own creation and destruction through reference-counting. Type conversion casting between different interfaces of an object is achieved through the QueryInterface method. The preferred method of "inheritance" within COM is the creation of sub-objects to which method "calls" are delegated.

Objective-C is a general-purpose, object-oriented programming language that adds Smalltalk-style messaging to the C programming language. It was the main programming language supported by Apple for the macOS and iOS operating systems, and their respective application programming interfaces (APIs) Cocoa and Cocoa Touch until the introduction of Swift. The programming language Objective-C was originally developed in the early 1980s. It was selected as the main language used by NeXT for its NeXTSTEP operating system, from which macOS and iOS are derived. Portable Objective-C programs that do not use the Cocoa or Cocoa Touch libraries, or those using parts that may be ported or reimplemented for other systems, can also be compiled for any system supported by GNU Compiler Collection (GCC) or Clang.

Swift is a general-purpose, multi-paradigm, compiled programming language developed by Apple Inc. for iOS, macOS, watchOS, tvOS, Linux, and z/OS. Swift is designed to work with Apple's Cocoa and Cocoa Touch frameworks and the large body of existing Objective-C code written for Apple products. It is built with the open source LLVM compiler framework and has been included in Xcode since version 6, released in 2014. On Apple platforms, it uses the Objective-C runtime library which allows C, Objective-C, C++ and Swift code to run within one program.