You get a bonus - 1 coin for daily activity. Now you have 1 coin

Binary Compatibility in Software

Lecture



Binary compatibility (also called binary compatibility; English: binary compatibility) — a form of software compatibility that allows a program to run in different environments without modifying its executable files.

This term is often used to mean «operating system compatibility», and in that case it refers to the ability of an already compiled version of a program built for one operating system to run on a different operating system without recompilation. For example, practically all programs written for Windows 2000 can be run on Windows XP — this means that Windows 2000 and Windows XP are binary (byte-level) compatible.

Binary compatibility encompasses byte-for-byte compatibility of the load fields, complete identity of the function call mechanism, of variable passing and of obtaining computation results, and a full implementation of the programming interface. Technically, the implementation may be entirely different — the key requirement is that all calls are implemented and that they produce the expected result, while the means by which that result is achieved is left to the program's creators.

Breaking binary compatibility means discontinuing support for programs and mandating that a program be recompiled, along with any necessary fixes, in order for it to work. For example, after Apple began using Intel processors in its computers, binary compatibility was broken with all applications written for PowerPC processors. To avoid losing all the software products developed in the past for the Mac OS operating system, Apple uses the lightweight translator Rosetta, which translates Mac OS system calls for PowerPC into Mac OS calls for Intel. This example also demonstrates a possible method for dealing with a break in binary compatibility.

On the Solaris 10 operating system for x86, it is possible to run Linux applications without recompilation using BrandZ. On the SPARC platform, this capability is not available.

Many of you have probably wondered things like «What happens if someone slips the wrong version of a library into my application?». It's a good question, and you'll find the answer to it, and to a few others, in this post. As a warm-up, here's a little puzzle: suppose we have two interfaces and a class implementing one of them:

  Binary Compatibility in Software


And also a class that has a method foo, overloaded for A and B. This method is called on an instance of class C:

  Binary Compatibility in Software


It's fairly obvious that «A» will be printed. It's no less obvious that if we were to say C implements A, B, we'd get a compilation error (for those to whom the latter is not obvious, I can recommend reading about how method resolution works. For example, in the specification in section 15.12.2, or in places that describe it more simply).
But what happens if we recompile only C.java and then run CompatibilityChecker from an existing class file — that is a more complicated question. Interested? Read on!

Static dispatch

Those who know that overloaded methods are resolved at compile time can figure out that, for this reason, the class file will already contain the information about which method to call, and therefore «A» will be printed as a result. Let's verify this assumption:

  Binary Compatibility in Software


Indeed, as you can see from the instruction at offset 19, a quite specific method is being called. However, those who have heard about the verifier might object and suggest that it will notice that these are the wrong bees class C has changed, and throw an exception. Fortunately, they are mistaken, since the verifier checks only the structural correctness of classes and interfaces, not the version consistency of class files.

So, let's go ahead and run our code and confirm that the original assumption was correct: «A» will indeed be printed.

In addition, one might assume that the virtual address table could contain some incorrect address, and therefore everything would break at runtime with a NoSuchMethodError. This assumption is also mistaken, since the method foo(A) is called, and there is only one such method in the virtual table. It would be a different matter if there were subclasses overriding it…

Dynamic dispatch

Suppose we have the following three classes:

  Binary Compatibility in Software 

And a class that calls foo in different variations:

 Binary Compatibility in Software 

Everyone, of course, knows that since methods on overridden types are resolved at runtime, the initial output will be as follows:

Binary Compatibility in Software 


Now it's time to play a dirty trick, replacing the class file of A with the compilation result of the following code:

 Binary Compatibility in Software


It's fairly easy to understand what will happen in this case. First, all attempts to call methods where foo is called on an instance of class A will definitely fail with NoSuchMethodError. Among these attempts is also the call to super.foo() in class C. Second, as we have already seen earlier, the method B.foo() will be called successfully.

Now let's change tactics: let's once again make A.foo the way it originally was, but now change B and C, removing the override of the method foo from them entirely:

Binary Compatibility in Software 


When the code runs, dynamic dispatch will find only a single entry for A.foo, and therefore will call it in all cases, as a result of which we will see in the console only the letter «A» and a complete absence of any exceptions.

Let's continue our investigations, once again overriding the methods in B and C. After running it, as we might expect, dynamic dispatch will find all the entries in the virtual table and produce exactly the same output as the one we would get by recompiling everything from scratch.

Fields of mismatched types

Earlier we only tried experimenting with methods. Now let's look at what happens with fields. Suppose we have a class that stores a value of type int and a subclass of this class:

 Binary Compatibility in Software


And, as usual, a consumer of class B:

  Binary Compatibility in Software


Now let's add to class B its own field with the same name:

 Binary Compatibility in Software


Let's look at the bytecode that was generated for CompatibilityChecker:

  Binary Compatibility in Software


This listing may be misleading, since at offset 11 the comment seems to say that the field belongs to B. So one might assume that when B is recompiled we'll run into an error. However, it turns out that this is not the case at all. Since the field physically exists only in the base class, the operand of the putfield instruction points precisely to that very field we need, and as a result the code continues to work after the changes.

And what does the specification actually say?


In the specification, an entire chapter is devoted to binary compatibility, in which the core concept is a «binary compatible» or «safe» change. The specification states that when only safe changes are made, safe execution of the application is guaranteed without recompiling everything else and without linking errors. Oddly enough, in the entire enormous chapter there is no exact definition of a binary compatible operation, but there are plenty of examples:

  • Changing the implementation of existing methods, constructors, or initialization blocks
  • Adding new fields, methods, and constructors to existing classes and interfaces
  • Removing private fields, methods, and constructors of a class
  • Moving methods up the class hierarchy
  • Adding new classes and interfaces


It is perhaps precisely because of such a weak definition of binary compatible operations that problems arise.

As it turned out, the specification is not strict enough, and it's possible to come up with a case in which safe changes lead to a linking error. Let's consider an interface, a class implementing this interface, and one more class that uses them:

  Binary Compatibility in Software

Let's make two safe changes: add a method foo to interface A and change the implementation of the main method of class CompatibilityChecker:

 Binary Compatibility in Software  


When it runs, as you may have guessed, an error will occur, namely AbstractMethodError: B.foo()V, which in principle should not happen. This problem is known and lies at the very core of Java bytecode handling. There have been proposals to fix the situation, but so far they have led nowhere.

So, the answer to the question that appeared at the very beginning of the article («What happens if someone slips the wrong version of a library into my application?») is this: «Who knows. It depends on how the version that was used during compilation differs from the one used at runtime».

The article does not touch on some obvious things. For example, that with incompatible changes such as removing methods and classes or turning a class into an interface, merciless errors will be thrown, such as NoSuchMethodError, NoClassDefFoundError or IncompatibleClassChangeError.

See also

  • Software
  • programming
  • Deprecation
  • [[b9176]]
  • Bug compatibility, backward compatibility with known defects
  • Compatibility mode
  • Computer compatibility
  • Obsolescence
  • Downgrade attack
  • Emulator
  • Flag day (computing)
  • Legacy mode
  • Shim (computing)
  • Software regression
  • Vendor lock-in


See also

created: 2021-06-15
updated: 2026-03-08
182



Was this answer useful?
Choose a quick rating so we can improve the next answer for you.
How satisfied are you?


Comments

To leave a comment

If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Lectures and tutorial on "Software and information systems development"

Terms: Software and information systems development