<div dir="ltr">Currently diagnostics related to <code>objc_direct/objc_non_runtime_protocol</code> are done at each compilation unit. Even with these diagnostics, we will see issues at runtime<br>
<ul><li>Due to lack of global scope for our diagnostics. We can catch these issues with a global analysis.</li><li>Due to usages of dynamic APIs that require the metadata. We can catch these issues with runtime checks. Builds with runtime check enabled will execute extra code at runtime to catch issues.</li></ul>
<h2>Global Analysis #1</h2>
We will get a runtime error if a method is marked as direct but some callsites see the method as not direct. One example is with private and public headers:<br>
<pre>// public.h:<br>@interface A<br>@end<br><br>// private.h:<br>@interface A (Private)<br>- (void)foo;<br>@end<br><br>// <a href="http://impl.mm">impl.mm</a>:<br>#include “public.h”<br>@implementation A<br>- (void)foo __attribute__((objc_direct)) {}<br>@end<br><br><br>// <a href="http://test.mm">test.mm</a>:<br>#include “private.h”<br>void test(A *a) { [a foo]; }</pre>
→ When compiling <a href="http://test.mm">test.mm</a>, <code>foo</code> is not treated as direct, and IR codegen will generate a call to <code>bjc_msgSend</code>. When compiling <code><a href="http://impl.mm">impl.mm</a></code>, <code>foo</code> is direct and is removed from the ObjC metadata.<br>
<br>
We should emit an error message from the global analysis if there exists an <code>objc_msgSend</code> callsite that targets a direct method. With this global analysis, we can claim the invariant that within the link unit, all callsites for a direct method are converted to direct calls.<br>
<h3>What information do we need for the global analysis?</h3>
List of direct methods (class-name selector-name)<br>
List of <code>objc_msgSend</code> callsites from IRGen (static-receiver-type selector-name)<br>
<h2>Global Analysis #2</h2>
We want to make sure a direct method is the single definition along the class hierarchy globally. This is checked for each TU via clang diagnostics.<br>
<br>
Within a single TU, we have the following diagnostics:<br>
<ul><li>can't override a method that is declared direct by a superclass</li></ul>
<ul><li>methods that override superclass methods cannot be direct</li></ul>
<span style="color:rgb(28,30,33)">These diagnostics guarantee that  if a method is direct, there is no override along the class hierarchy when compiling each source file.</span><br>
<br>
<span style="color:rgb(28,30,33)">If we have a private header and a public header for a class, and we extend the class using the public header, current clang diagnostics will not report an issue.</span><br>
<br>
With this global analysis, we can claim the invariant that a direct method is the single definition along the class hierarchy globally. This will reduce runtime issues caused by overrides.<br>
<h3>What information do we need for the global analysis?</h3>
List of direct methods (class-name selector-name)<br>
Class hierarchy information (class-name base-class-name)<br>
<h2>Global Analysis #3</h2>
A class that declares conformance to a protocol does not need to see the protocol definition in order to compile successful. Thus we can end up with a situation as shown below:<br>
<br>
<pre><code>// proto.h<br>__attribute__((objc_non_runtime_protocol))<br>@protocol Static<br>- (void) doThing;<br>@end<br><br>// source.m<br>@protocol Static;<br>@interface Something : Root<Static><br>- (void) doThing;<br>@end<br>@implementation Something<br>- (void) doThing { ... }<br>@end<br></code></pre>
Compiling <code>source.m</code> will generate a reference to the protocol metadata for <code>Static</code> even though it was defined as non-runtime. This will emit a warning at compile time but a developer might choose to ignore it and face link errors. This will end up with a linker error for a missing symbol. <br>
<br>
A potentially better warning can be given to developers via global analysis saying <code>protocol "Static" was defined to be non-runtime in file "SomeFileThatIncludedProtoDotH.m" to and dynamic in "source.m"</code>. <br>
<br>This example problem can also expand to protocol inheritance hierarchies. Given an arbitrary hierarchy of runtime and non-runtime protocols and forward declarations of protocols, the compiler could potentially emit the wrong references given misleading forward decls. This can also be much more accurately diagnosed via global analysis.<br>
<h2>Runtime Check</h2>
The goal of runtime checks is to catch the usages of dynamic APIs targeting direct methods at runtime, e.g, <code>respondsToSelector</code>, <code>performSelector</code>, etc. The idea is to intercept Objc runtime’s invocation of <code>resolveInstanceMethod</code> and <code>resolveClassMethod</code> , which is responsible for resolving methods that aren’t recorded in the metadata, and check if the unresolved method is a direct method. It requires we collect the list of direct method names, and the names of the classes where they’re defined.<br>
There is another category of runtime issues which runtime check can potentially catch: the direct call calls the  incorrect direct method due to<br>
<ol><li>usage of class_addMethod</li><li>random type casting at the call site</li></ol>
This can be an incremental improvement outside of this RFC.<br>
<h2>Implementation Options</h2>
We can potentially implement the analysis/runtime check with and without LTO. It is better to implement runtime check without LTO as we are expecting developers to use runtime-check build locally. LTO will slow down developer’s working cycle. For analysis, it is not clear which option is better.<br>
<h3>noLTO Implementation</h3>
We create a special section containing those information for each TU. This section for the link unit will have the combined information. We then post-process this information to emit error messages.<br>
For each direct method, we create a struct containing the method name and the name of the class where it’s defined, e.g.<br>
<pre>// struct _objc_direct_method {<br>//   char *class_name;<br>//   char *method_name<br>// }</pre>
Then each class will have a list of <code>_objc_direct_method</code> structs, which can be put in a special section, e.g <br>
<pre>constant [4 x %struct._objc_direct_method][//the list of the structs],section "__DATA,__direct_method"</pre>
This special section for the link unit will have the combined information for all TUs in this link unit. At runtime, the intercepted <code>resolveInstanceMethod</code> and <code>resolveClassMethod</code> invocation can then read and check against this section. If it finds the unresolved method is a direct method, it emits an error message of this direct method cannot be used dynamically at runtime.<br>
<h3>ThinLTO Implementation</h3>
We add information in ModuleSummaryIndex and a ThinLTO analysis pass to analyze the combined Summary and emit error messages.<br>
<br><br>
We will upload patches for noLTO/runtime-check if there is no negative feedback about the RFC! Feedback/suggestions are welcome!<br><div><br></div><div>Thanks,</div><div>Manman</div></div>