Proposal: Add a runtime availability check feature to Objective-C, C

Hello,

I'm an intern at Apple this summer, and my project is to add the following
feature to C and Objective-C.

The feature in question is adding the ability to safely access APIs that are
only available in some of the deployment targets available, based on the
availability attribute that the declarations were declared with.

Swift has an analogous feature exposed through the following syntax:

  if #available(iOS 8, *) {
    // do something iOS8-ey
  }

In the context of the body of the `if`, accesses to declarations that are marked
as being available in iOS version 8 or below can be accessed safely and without
compiler errors, even if the deployment target is iOS 7, for example. Outside of
that block, accessing the declarations would be a hard error.

Currently, Clang exposes no safe way to perform a similar check. Right now,
there are 2 main methods for checking for availability in Objective-C:

  1. Using respondsToSelector
  2. Checking if a weak symbol is NULL

There are two main problems with these methods. Firstly, they allow programmers
to accidentally use SPIs ("Secret" Programming Interface, aka internal stuff),
ignoring that they are marked as being unavailable to them. Secondly, we cannot
use them for diagnostics in all cases, as they can be combined with arbitrary
control flow. If we were to infer the availability of a declaration based on
these existing checks, then code would be fragile — a small change that doesn’t
alter the semantics of the function could cause the compiler to become confused
and incorrectly handle diagnostics.

There also does exist a -Wpartial-availability (introduced in r232750), which
just statically compares the deployment target with the marked availability of
the API, potentially warning if necessary. This doesn't allow using a newer API
with an older deployment target, as this feature does.

My proposal is to implement a similar feature to Swift for C and Objective-C.
The current syntax (subject to change) is as follows:

  if (@available(macos 10.10, *)) {
    fancy_new_fn();
  }

And the following for C and C++:

  if (__builtin_available(macos 10.10, *)) {
    fancy_new_fn();
  }

And would have similar semantics to the swift version discussed above, meaning
it would compile to a runtime check of the availability, branching on the
result. In the body of the if, the deployment target would effectively be the
target in the @available check. This means that any declaration that is marked
with an `__attribute__((availability))` which was introduced before or at MacOS
10.10 can be accessed in the block. Here is a complete example:

  void flashy_new_function() __attribute__((availability(macosx, introduced=10.10)));
  void boring_old_function() __attribute__((availability(macosx, introduced=10.9)));

  int main() {
    if (@available(macos 10.10, *))
      flashy_new_function();
    else
      boring_old_function();
  }

And compiled with:

  $ clang example.m -mmacos-version-min=10.9

In this case, since the call to fancy_new_function() is guarded by the runtime
check, clang would emit no warnings. If main were instead:

  int main() {
    flashy_new_function();
  }

Then clang will warn that the call is unsafe. Additionally, Clang will emit a
fixit that could wrap the call to flashy_new_function in an @available (or
__builtin_available, when needed) check.

I would appreciate hearing any thoughts or questions about this feature.

Cheers,
Erik Pilkington

Hi Erik,

this sounds very useful, thanks for working on it. One think Ted had suggested on the review for r232750 (that I failed to follow up on :-/) was to also have a #pragma to mark whole regions as e.g. 10.10+. From https://www.mail-archive.com/cfe-commits@cs.uiuc.edu/msg117130.html:

#pragma clang assume_availability(macosx, 10.8) begin

#pragma clang assume_availability(macosx, 10.8) end

Is this something you’ll look at as well?

Nico