Gatekeeper’s Opaque Whitelist

I wrote previously about the confusion that arose when many developers, trying to comply with Apple’s new code signing rules, ran across strange system behavior in which version 1 signatures seemed to work, yielding the curious system policy message “accepted CDHash.”

I can’t believe I didn’t think until now to check Apple’s open source Security framework for clues about this. Poking around today I found something very curious. In the policyengine.cpp source file, search for “consult the whitelist” and you’ll find the clump of code that very deliberately avoids rejecting an app for its “obsolete resource envelope” if it passes some whitelist check:

if (mOpaqueWhitelist.contains(code, rc, trace))
	allow = true;

Well I’ll be darned. Could this explain the fact that many, many people observed that their apps with old, V1 signatures continue to pass Gatekeeper’s scrutiny e.g. on 10.9.5, even though Apple stated that V2 code signatures would be required?

The whitelist database is stored on your Mac in the following location:

/var/db/gkopaque.bundle/Contents/Resources/gkopaque.db

Go ahead, poke around at it. It’s big! It will be a bit easier to play with if you have any experience at all using the sqlite3 command line tool. With it, for example I was able to discover that there’s a table called “whitelist” within it, and that it contains two columns: “current” and “opaque”. And there are a LOT of rows in the table:

sqlite> select COUNT(*) from whitelist
68101

So what does a typical row from the table look like?

sqlite> select quote(current), quote(opaque) \
           from whitelist limit 1;
X'000327ECE1FB5A27B5F5C51A009900B1E4854BB7'|
X'CBE56B9784974E0A1C0159C41F392B77421B4D23'

By scrutinizing the code and poking around, I’ve determined that the “current” column is not unique, and corresponds to the “CDHash” of a given code object being analyzed. For example, a version of MarsEdit that shipped with a V1 code signature and which does not seem affected by the changes in Apple’s Gatekeeper policy has a CDHash of “D1FBA2AB9A4814877BE8C1D2A8615FB48D8D4026”, and on my system anyway there are two rows corresponding to that CDHash.

I don’t really get what the “opaque” column is about, and my ability to scrutinize Security source code isn’t great enough to easily be able to tell by reading the source, either. But it seems to me that it must somehow be a way of informing the security system that certain specific (possibly modified?) instances of an app are still essentially the same as the “current” CDHash being tested.

Using some more sqlite3 magic, we can determine the number of unique values in the “current” column:

sqlite> select count(distinct current) from whitelist;
36215

OK, I run a lot of software, but I’m quite positive I have not run 36K unique parcels of code in recent memory. My suspicion is that in the run-up to the major changes Apple has made to Gatekeeper, they painstakingly accumulated a list of 36215 “trusted” hashes and deposited them on everybody’s Mac so that the effect of 10.9.5’s stricter code signing checks would be mitigated.

One test I did to confirm that the database is not just a personalized list of the apps I have used, was to download an app that I don’t use regularly, Panic’s Unison, but that I thought would be popular and reputable enough to appear on Apple’s whitelist. I downloaded it, and before running it even once, I checked its CDHash (“35d9c847ebb7461aee5f08bb8e017b5a3891bc0f”) with the database. Sure enough, 7 rows match this CDHash. Perhaps accommodating 7 distinct releases of Unison? Again, I’m not sure. I then took the extra step of logging into another machine entirely, one which has never seen Unison even as a downloaded file. The database on that machine also contains the hash.

Want to see if your apps are “whitelisted” or not? It’s pretty easy to do from the Terminal:

  1. Find your CDHash by running “codesign -dvvv Your.app” and searching for the CDHash value in the output.
  2. Grep the database for your value:
    sqlite3 /var/db/gkopaque.bundle/Contents/Resources/gkopaque.db "select quote(current), quote(opaque) from whitelist" | grep -i [yourCDHashHere]
    

This whitelist offers a significant amount of explanation as to why some apps are allowed to launch without issue on 10.9.5 and 10.10. I don’t understand the database completely, particularly the meaning of those “opaque” CDHash values in the second column, but I feel as though a lot of mysterious behavior on all of our Macs is suddenly a lot more understandable.

Update later on Oct 6: After publishing this entry, Ed Marczak chimed in on Twitter with some information about the database:

In other words, according to Ed, Apple gathered the list of “whitelisted CDHashes” by surveying the software that people actually run, and reporting that data to Apple in the form the unique code signature hashes. I’m not sure what criteria they applied in the end to decide which of those apps are included in the whitelist, but it sounds reasonable to assume that if nobody ran your app on 10.9.4, Apple did not have the opportunity to consider including you in the whitelist.

(Thanks to Jeff Johnson for talking through some of the discoveries that led to this post).

Fixing 90-Day Apple ID Expirations

I wrote a week ago that I was cautiously optimistic about a solution to a long-standing ordeal with my Apple ID password expiring every 90 days.

Today I got a very encouraging response from the Apple support representative who was shepherding the problem through to the engineering team that evidently knew how to fix the underlying issue with my account.

An update on the ticket I submitted to the Engineers states that you should now no longer receive those password reset prompts. Please contact us back referencing our case number: 663194617 if for any reason the issue persists after 90 days.

I have raised my cautious optimism to outright good cheer at the sight of evidence from Apple that “Engineering” saw a problem, claims to have fixed it, and both they and the support representative are confident about the solution. Furthermore, they offer a very special kind of 90-day warranty by inviting me to get back in touch for another round of effort should they be mistaken in their assessment.

I’ve been complaining about this issue on Twitter for such a long time and with such repetition that I’ve had the opportunity to hear feedback from tens if not a hundred or more people who have let me know that they too suffer the problem, and are eager to know of a solution. In the wake of last week’s post, at least one person was inspired to get in touch with Apple, only be to be shunted from Apple’s developer relations team to AppleCare, where I had previously failed to obtain any successful result.

As helpful as the representative was who helped me, I don’t feel comfortable sharing her name or direct contact information. But the fact that she seemed genuinely interested in solving this problem made me realize that it would be ideal if others who go down this path could stand at least a better chance of finding somebody as empathetic to the problem as she was.

So I asked her if she had any problem with me sharing my case number with “everybody on Twitter.” I didn’t exactly want to sic a mob of frustrated developers on Apple, but I do genuinely wish that everybody who has been as annoyed by this problem as I have will have a path of hope towards seeing it resolved. To my satisfaction she agreed enthusiastically with my proposition:

I don’t have an issue at all with you mentioning this case. I’d be more than happy for our group to help anyone in this situation if/when we can.

So in summary, this long journey has led me to a position where I now believe I can offer what is the most definitive set of instructions for developers with Apple Developer Connection accounts who would like to eliminate the annoying 90-day password resets:

  1. Go to the Apple Developer Program Support page.
  2. Select “Access Issues” from the Subject popup.
  3. Briefly explain the problem with your Apple ID password requiring a reset every 90 days, and reference my case #663194617 as a likely comparable issue which has been handled by developer support.

The fact that you’ll be in touch with a support staff that is empowered to consult with the pertinent engineering team, combined with the fact that they have persistent access to the documentation for my case will hopefully be a recipe that sets you up for the same success (finally!) that I have had.

Accepted CDHash

A bit over a month ago, Apple announced big changes to the way Mac OS X versions 10.9.5 and 10.10 will recognize the code signatures of 3rd party applications, hinting very strongly that consequences would be dire for any developer neglecting to re-sign their apps:

Important: For your apps to run on updated versions of OS X they must be signed on OS X version 10.9 or later and thus have a version 2 signature.

Few details were given as to why the old signatures would no longer be respected, leaving developers with little to go on except to take Apple’s word that we should drop everything and update our code signing processes, which for some of us was a non-trivial amount of work. Those of us willing to grant Apple the benefit of the doubt assumed that there was some greater purpose to our collective suffering. Surely this nuisance is in the name of preserving or increasing security for all of Apple’s and our mutual customers.

That, and the fact that Apple stated bluntly that if we don’t make these changes, our apps will not pass Gatekeeper’s assessment and thus will not be allowed to launch without considerable work by users. This is an unacceptable user experience to any developer worth her or his salt, so of course the vast majority of us complied.

Some of us complied even when the logic of doing so was comically bent. For example Apple implied that developers of apps for the Mac App Store also needed to update their code signatures, in spite of the fact that all Mac App Store apps are signed by Apple, not by the original developers. The process of submitting software for sale on the App Store does include a code signing phase, but the signature is replaced by Apple before it is distributed to customers. So, if there is a security issue with version 1 code signatures, Apple is in a position to remedy the problem without the involvement of 3rd party developers. Some of us sought clarification from Apple on this point. Questions in the Apple Developer Forums along the lines of “do we really have to re-sign apps and submit a new version just to accommodate this new requirement?” were met by terse restatements from Apple along the lines of “all apps must be signed with version two code signatures.”

Sigh.

To make matters yet more confusing, a developer who has signed off on the chore of complying with Apple’s requests would not necessarily be able to verify the job was done right, because for example on pre-release builds of 10.9.5 and 10.10, many apps with “old and busted” version 1 signatures unexpectedly passed the system’s Gatekeeper check, contrary to the firm indication from Apple that they shouldn’t. Apple’s documentation provided a specific command to run from the Terminal that would verify or reject any specific app’s code signing:

spctl -a -t exec -vv Foo.app

For many of us with very old version 1 code signatures, the command line came back with a cryptic “accepted cdhash”, and the system happily opened and ran the apps without issue.

I reported the problem as a security bug because it seemed to suggest that apps with broken, insecure code signatures would still be allowed to run. I waited to see whether, with each subsequent beta release, Apple would finally ratchet things down to give me a taste of how customers would experience trying to run these apps with outdated signatures. But each beta release continued to allow them to run without incident.

Finally, a few days ago I got my bug sent back to me to be closed. Oh, they finally fixed it? Nope. It was returned with the classification “Behaves as intended.” Then yesterday, 10.9.5 shipped and lo and behold, contrary to every warning and veiled threat from Apple, many of these apps with old, version 1 code signatures, of the variety that yield a cryptic “accepted cdhash” assessment from the spctl tool, well, they just work. They’re fine. This happens to apply to my entire line of apps and this is probably also the case for many other developers. In short? I dropped everything, spent hours revising my code signing process, investigating unexpected results, contacting Apple for clarification, trying to make sense of Apple’s terse replies, and finally doing my best to comply regardless of doubt. And it turns out I didn’t actually have to lift a finger.

Which is not to say that nobody had to lift a finger. Any developer whose code signatures involved the use of “custom resource rules,” and (I think) any developer whose version 1 signature was signed with a new enough version of Xcode to escape the “accepted cdhash” loophole, but not new enough to be a “version 2” signature, did need to re-sign their apps.

This was a classic case of Apple communicating far too poorly about a situation that purported to affect potentially every Mac developer. Many of us spent way too much time trying to decode and make sense of the situation when Apple could have done so for us through careful clarification of the specific code signatures that needed updating, how they could be reliably verified, and what the actual consequences of inaction would be.

Cautious Optimism

For years I have been suffering a relatively benign yet still infuriating problem with my Apple ID: the password expires every 90 days, like clockwork, forcing me to choose a new one.

It sounds like a minor inconvenience, but it’s made somewhat worse by the fact that my Apple ID and its password are tied to countless different Apple services, each of which saves a copy of the credentials separately from the others. Long story short? I have to enter the password umpteen different times, every 90 days. A litany of authorization panels appear to let me know that, when I least expected it, Messages, iCloud, calendar syncing, iTunes connect, iTunes itself, the Apple Store, Xcode’s ADC integration, etc., etc., all need to be reauthorized. And for many of these services I must carry out this dance on my Mac, iPad, and iPhone. Oh, and my Apple TV.

A couple months ago I got it in mind that I would finally take the plunge and see what AppleCare, Apple’s famously courteous and helpful customer support team, could do for me. I was impressed from the outset by the seriousness with which they took my request, and by the assiduousness of the attention they gave to my problem. I was passed up the ranks of the support team until I was on a first name basis with a very helpful agent out of Austin, TX, who, though she seemed unable to solve the problem, also seemed unwilling to give up until she could. She liaised with various groups within and outside of AppleCare, keeping me posted about the status of this, that, or other approach that may or may not get to the bottom of things. Her colleagues who specialized in AppleID problems offered various suggestions and she diligently came back to me with questions about whether I was a member of this or that program. This went on for a week or two, but it felt invigorating. Even though it was tedious, it felt as though we would end up at a solution. Eventually this relentless, passionate agent would figure out the problem and give me the blessed call to let me know that, at last, everything was going to be OK.

And then she never called me again.

I don’t know what happened to my case. Maybe it’s sitting open in the system, in her queue, collecting dust. Maybe she’s moved on to another job and her tickets are collateral damage. Or maybe she just got tired of trying and one day decided to close it without saying another word.

My 90 day anniversary came up again a couple days ago, and I was inspired to, you’ll never believe this, gripe about it on Twitter. Usually my griping is met by a choir of fellow sufferers who also wish they could eliminate this hex on their Apple ID account. This time was no different, although there was one reply that offered an optimistic take. Rosyna Keller assured me that the problem is well known as an ADC-specific issue:

Sure enough, everybody I’ve ever known who suffered the problem has an ADC account, and most if not all of the afflicted are very long-time members. But still I sighed: I anticipated another weeks-long ordeal possibly ending in me being no better off than I was.

All the same, yesterday I decided to take the initiative to send a note to Apple’s Developer Relations support team. After all I’ve done it would be a shame to live with this problem for yet another 90 days if there are simple steps that could prevent it.

I went to the Contact Us page for the Apple Developer Program, selected “Access Issues” as the subject, and entered the following message:

For years I have been forced to reset my Apple ID ([My AppleID]) password every 90 days. I recently went through an unproductive, several-days-long support interaction with AppleCare that ended in no change. I have recently been encouraged to believe that actually my affiliation with ADC may be the source of the security restriction.

Is it possible for you to lift the password reset requirement on my account? It’s frustrating to have to change every 90 days especially because so many different Apple services, many of which do not share a centralized, common keychain entry, require the password to function at all. Every 90 days is marked by a sudden explosion of password dialogs on my Macs, iPhone, iPads, etc.

My team ID is [My Team ID]. Thank you for any help you can provide,

Daniel Jalkut
Red Sweater Software

That was yesterday. Today, less than 24 hours after I submitted the request, I got a call on my phone from an area code (916) that I recognized as coming from far Northern California (Sacramento and above). I struggled to imagine who could be calling me from that part of the world, but was relieved when I picked up and met my senior advisor from Apple’s Developer Relations support team.

She indicated clearly that she was filing a ticket on my behalf with the technical team, asking them to look for any flag on my account that could cause this. She suggested that probably what will happen is they will find something, eradicate it, and then they will ask her to keep the ticket open for 90 days to ensure that the problem has in fact been addressed. In the mean time, she promises to keep my apprised of any news.

The main difference between this interaction and the previous interaction with AppleCare is that my contact in the developer support team seems to actually recognize this is an issue and seems confident that it can be fixed. I asked her a little more about it and she said it did sound familiar but she hadn’t run into the problem for a long time. She tended to think it has something to do with an old option for password expiration that was available in the past but no longer is. Curiously, she said she had run across the issue a few times suddenly just in the past few days. I wonder… maybe I’m not the only one who got inspired by Rosyna’s tweet.

I’m not 100% confident that my problems with the 90-day Apple ID password expiration are over, but I would definitely go so far as to say I’m cautiously optimistic. If you’ve suffered with this problem and felt there was nothing to be done about it, maybe it’s time to get in touch with developer support and see if there is enough optimism to go around.

Re-signing Code

Given the implied urgency of Apple’s demand that developers provide updated versions of apps with “version 2” code signatures, many people are scrambling to get their build processes updated so that e.g. the apps are built on 10.9 or higher. This is the most natural and attractive technique for ensuring that your Mac apps are signed properly.

However, for a variety of reasons many of us either need to build with older versions of Mac OS X or Xcode. We face a conundrum that can be solved by signing (or more accurately, re-signing) the apps on 10.9, ensuring that the signature is up to snuff even if the code was compiled and the app was assembled with earlier tools.

This is a fairly straight-forward process, but there are some gotchas and you should be aware of what effect running codesign with various flags will have on your finished product. In a nutshell, you want to establish new code signatures on all the binaries in your bundle, but you don’t necessarily want to reset specific entitlements or other metadata that was set on the code by your old-and-busted code signatures.

Felix Schwarz offers a bash script suited to re-signing and further packaging the resulting app into a .pkg file suitable for submission to the Mac App Store. If you’re looking at automating this process, his script may serve as a good starting point!

In my tests I ran into some issues having to do with the fact that some of my apps have custom “designated requirements.” I have ironed out all the kinks yet but it seems to help in this scenario to re-establish all the code signing for the bundle first and then as a final icing on the cake, re-sign the app package with the custom designated requirement.

Take This Code and Sign It

Big news from Apple this week that forthcoming releases of Mac OS X 10.9.5 and 10.10 will enforce new policies that will render some 3rd party apps “untrusted” unless they are updated. The long and short of it is that apps using an older version of code signing will not be trusted, and even those using the newer version must forego the use of custom “resource rules,” which allowed developers to exclude certain files in a bundle from consideration as part of the code signature. A file that was thus excluded could be removed or modified without “breaking the seal” and triggering warnings about the code signature.

Jeff Johnson points out a far-reaching implication of this new limitation, which is that developers who bundle frameworks in their apps will need to see to it that the header files are either removed before signing the framework, or left in-tact. Removing the headers after signing will now necessarily lead to a broken seal and thus an untrusted app that Gatekeeper refuses to run.

Jeff’s novel solution involves overriding the default location both for where frameworks install their headers while being built, and for where host apps look for the headers of frameworks they depend upon. This seems like a reasonable solution that many people will benefit from.

Me? I’ve been burned too many times by default code signing behaviors, so I long ago switched to an approach which many will consider too complicated, but which has nonetheless saved my bacon on repeated occasions. The “resource rules” change from Apple wouldn’t have even registered on my radar, because the final code signing of all my bundled frameworks, plugins, XPC services … every darned executable, is controlled by a custom build phase which is run very late in my apps’ build process, right before the final code signing of the overall app that Xcode handles at the very end.

How does this work in practice? Here’s what MarsEdit’s build process looks like from Xcode’s build phase editor:

Screen Shot 2014 08 05 at 2 51 43 PM

You can see that the process of assembling a typical Red Sweater app involves a lot of building up (copying things in), followed by a lot of tearing down, in which I remove framework headers, localizations, etc. These processes are basically fast enough that I don’t mind doing it all programmatically at build time, because it lets me worry less about the pristineness of individual components, and leaves me with with confidence that everything in the final product has been cleaned up. For the Mac App Store build I even have an additional build phase at the end called “Verify Mac App Store Requirements” in which I double check things that have bit me before in App Store submissions, but which Apple doesn’t flag themselves as part of the submission process.

That item at the very bottom is where all the good code signing happens. It’s a somewhat complicated python script that in turn relies upon a library of utility script modules I’ve written for these kinds of things, but the gist of what the “Sign Bundled Frameworks, Apps, Bundles & Tools” build phase does is:

1. Identify the target app being built. In python, you can do this using something like this in a build phase script:

appBundlePath = "%s/%s/" % (os.environ["BUILT_PRODUCTS_DIR"], os.environ["WRAPPER_NAME"])

2. Iterate through the target app bundle, looking for “executable” code. I could hard-code this for every project, but I like the relative safety of knowing that if I add a framework, tool, whatever, it will get caught in this phase. There is also a little gotcha here in that you want to find and sign the deepest items first e.g. so that you sign a helper tool in a framework before the framework that contains it. I have a python module that handles iterating for these items and returns the path to each one in appropriate order.

3. Now the moment of truth: for each item you find, you want to sign it in such a way that you give it your final blessing with all the nuanced code signing flags you care about. My actual code is a little more complicated than this in that I have special cases to avoid using Apple’s timestamp server for debug builds, and some other little tweaks. A common kind of customization you might want to make is to e.g. add arguments to codesign that will cause the preservation of entitlements, so that e.g. an XPC service with its own entitlements will not have those blown away by your re-signing of the app. See “man codesign” for more information about the “-preserve-metadata” option and other flags. But keeping things simple, the gist of signing the code manually from python in a build phase looks like this:

def signAppOrExecutable(thePath):
	signingIdentity = os.environ["CODE_SIGN_IDENTITY"]
	signArgs = ["/usr/bin/codesign",]
	signArgs += ["-f", "-s", signingIdentity, thePath]

	myCall = subprocess.Popen(signArgs, stdout=subprocess.PIPE)
	myResults = myCall.communicate()
	return (myCall.returncode == 0)

Pass the path of every executable tool, plugin, bundle, library, framework, and helper app to this function right before your app is finished building, and all of your custom tweaks, removed header files, adjusted content will be sealed into the final code signature, and you’ll never (ha!) have to think about it again. “Simple,” right?

Bridging iOS WebViews

I hinted in my previous post that similar techniques could be used to bridge JavaScript to Objective-C on iOS, but that it would require using undocumented methods. As with the Mac-based solution, it’s not such a big deal so long as you’re only going to use it for private, debugging builds of your app. So how can it be done?

WebKit on iOS is for the most part famously, frustratingly hidden from developer use. As apps like Safari show, there is an extensive, powerful WebKit framework just as there is on the Mac, but iOS developers are limited to the comparatively impotent UIWebView.

But here’s a hint: UIWebView is itself a client of “proper WebKit,” and thus implements many of the powerful delegate methods that you would implement as the Mac delegate of a WebView. So if I were an iOS developer and wanted to play around with bridging WebView to Objective-C, I would simply subclass UIWebView, and override the delegate methods methods I am interested in:

#if DEBUG
@interface UIWebView (PrivateMethods)
- (void)webView:(UIWebView*)webView
      didClearWindowObject:(id)windowObject
      forFrame:(id)frame;
@end

@interface MyWebView : UIWebView
@end

@implementation MyWebView

// Override methods on UIWebView that it itself employs
// as delegate of a "proper" WebKit WebView object
- (void)webView:(UIWebView*)webView
      didClearWindowObject:(id)windowObject
      forFrame:(id)frame
{
   [super webView:webView didClearWindowObject:windowObject forFrame:frame];

   id myDelegate = [[UIApplication sharedApplication] delegate];
   [windowObject setValue:myDelegate forKey:@"appDelegate"];
}

@end

#endif

Set your WebView to use the custom class “MyWebView”, and now your iOS-based WebView can pass whatever information it needs to directly to your delegate, just as I illustrated for the Mac version.

JavaScript Bug Traps

I am the kind of developer who leans heavily on automated systems for the improvement of my code. For this reason I tend to enable the maximum number of warnings, rely upon static analysis to discover nuanced bugs, develop and run unit tests to automatically confirm expected behaviors, and I encourage my apps to crash hard if they are going to crash at all.

I would characterize all of these behaviors as part of an overall attitude of vigilance against software defects. Learning about problems in code quickly and clearly is a key step maintaining an overall high level of code quality.

Most of my software is developed in Objective-C, which enables me to leverage compile-time and run-time tools from Apple that catch my errors quickly. Between compile-time warnings and errors, and runtime exceptions and crashes, the vast majority of my programming errors in Objective-C are caught within minutes of writing them.

On the other hand, MarsEdit features a rich HTML text editor largely implemented in JavaScript, a language which tends not to afford any of these convenient alerts to my mistakes. I have often stared in bewilderment at the editor window’s perplexing behavior, only to discover after many lost minutes or hours, that some subtle JavaScript error has been preventing the expected behavior of my code. I’ll switch to the WebKit inspector for the affected WebView, and discover the console is littered with red: errors being dutifully logged by the JavaScript runtime, but ignored by everybody.

Wouldn’t it be great if the JavaScript code crashed as hard as the Objective-C code does?

In fact, these days mine does. By a combination of JavaScript hooks that are alerted to the errors, Objective-C bridges that propagate them to the host app, and the judicious use of undocumented WebKit Inspector methods, development versions of my WebView-based apps now automatically present an inspector window the moment any error occurs in the view’s underlying JavaScript code.

For those of you who also develop JavaScript-heavy WebKit apps, I’ll share the steps for enabling this same type of behavior in your app. These steps all apply to Mac-based WebViews, but most of the same techniques could be used with iOS if you are willing to use some private methods to establish a bridge between the WebView and host app (for debug builds only, of course).

Step 1: Catch the error in JavaScript.

In whatever the main source content is for your WebView-based user interface, add some script code to register a callback for JavaScript errors:

<script type="text/javascript">
   function gotJavaScriptError(errorEvent)
   {
      console.log("Error: " + errorEvent.message);
   }

   window.addEventListener("error", gotJavaScriptError,  false);
</script>

Now if you encounter some error and happen to be attached with the web inspector, you’ll see additional confirmation that the error has in fact been noticed. if you’re following along in your own code while you read, I strongly encourage you to add a testing mechanism for generating errors. A simple “click” event callback (or “touchstart” on iOS) does the trick well:

<script type="text/javascript">
   function gotClick(theEvent)
   {
      blargh
   }

   window.addEventListener("click", gotClick, false);
</script>

Now when you click, you blargh. And when you blargh, the error is logged. Of course, just logging the error isn’t very useful because it’s quiet and goes ignored. Just like JavaScript errors usually do…

Step 2: Propagate the error to the host app.

You could do some interesting stuff in the WebView itself. For example you might convert the error into a string and display a prominent alert message. But since there’s so much more you can do once the host app is in charge, it’s worth going the extra mile for that functionality.

To tie the host app to the WebView we need to wait for the WebView’s frame to finish preparing its “window script object,” and then set a named attribute on that object that refers to an Objective-C instance. See that your WebView has a frameLoadDelegate configured, and within that delegate object implement this method:

- (void)webView:(WebView *)webView 
        didClearWindowObject:(WebScriptObject *)windowObject
        forFrame:(WebFrame *)frame
{
   // Expose ourselves as a JavaScript window attribute
   [[webView windowScriptObject] setValue:self forKey:@"appDelegate"];
}

At this point any JavaScript code, including our error-handling callback, can reference the Objective-C delegate directly. But in order to make good use of the delegate we’ll have to add a couple other methods. The first lets the WebKit runtime know that we are not fussy about which of our methods are accessible to the WebView:

+ (BOOL) isSelectorExcludedFromWebScript:(SEL)aSelector
{
   return NO;
}

Now JavaScript code within the WebView can call any method it chooses on the delegate object. Implement something suitable for catching the news of the JavaScript error:

- (void) javaScriptErrorOccurred:(WebScriptObject*)errorEvent
{
   NSLog(@"WARNING JavaScript error: %@", errorEvent);
}

And back in your WebView HTML source file, amend the JavaScript error handler to call through to Objective-C instead of pointlessly logging to the JavaScript console:

<script type="text/javascript">
   function gotJavaScriptError(errorEvent)
   {
      console.log("Error: " + errorEvent.message);
      window.appDelegate.javaScriptErrorOccurred_(errorEvent);
   }

   ...
</script>

Notice that the colons in Objective-C method names are simply replaced with underscores to make them compatible with the JavaScript function naming rules. The errorEvent argument in this case is translated to a DOMEvent object instance in Objective-C, where it can be further interrogated. Alternatively you could pull the parts of the event you want out in JavaScript, and pass along only those bits.

Now we’ve done a considerable amount of work, only to upgrade our easy to ignore JavaScript errors from littering the web console to littering the system console. At least we’d notice these errors in the Xcode debugger console, and might eventually get around to taking a closer look. But we can still do much better.

Step 3: Get face to face with the error.

Of course from Objective-C there are any manner of ways you could handle the error. In a shipping app it might make sense to prompt the user with news of the issue and offer, much like a crash reporter would, to send information about the error to you. Or if the contents and behavior of your WebView are critical enough, maybe it’s worth forcing the app to quit just as it would for a native code crash. But for debugging builds I’ve find it very helpful simply to have the error force the Web Inspector open so I am no longer able to quietly ignore all the red console logging. In the delegate class, go back to the javaScriptErrorOccurred: method and replace the NSLog call with this string of fancy mumbo-jumbo

- (void) javaScriptErrorOccurred:(id)errorEvent
{
#if DEBUG
   id inspector = [[self webView] performSelector:@selector(inspector)];
   [inspector performSelector:@selector(showConsole:) withObject:nil];
#endif
}

That’s it. Now when you run into a WebView-based JavaScript error, the web inspector appears and the list of pertinent errors is front-and-center.

I encourage you to leave the DEBUG barrier in place, because as the prolific use of “performSelector” may suggest, these are private WebKit methods that would probably not be viewed as acceptable by e.g. App Store reviewers. Anyway, you probably don’t want customers being pushed into the Web Inspector.

I hope this technique proves useful for those of you with extensive JavaScript source bases of your own. For everybody else, perhaps I’ve helped to drive home the idea that we should be as vigilant as possible against software defects. All developers write bugs, all the time. It’s only fair that we try to balance the scales and give ourselves a fighting chance by also being alerted to the bugs we write, all the time.

Coding Under Par

Brent Simmons reflects on his ambition to stop coding “late at night”:

I may think I’m adding productive hours to my day – but I’m not. I’m writing bugs, or, at best, not the best code I could be writing. And I pay for it later.

I read Brent’s piece with a lot of nodding my own head. I find late night coding perhaps more alluring than ever because as a husband and father of two, who happens to work from home, much of my daytime development time is compromised by commitments to family, distractions, or even just the knowledge that whatever I’m working on now has a firm and fixed stopping time.

I suppose the same is true late at night, but when it’s midnight and I’m on a perceived roll with some coding challenge, there doesn’t appear to be any stopping me. I “have all night,” or at least that’s what my monkey brain says. Of course, the smarter half of me knows I should be getting calling it a day and getting some much-needed rest.

The next morning, I usually realize that whatever challenge was tantalizing me into the wee hours was in fact a 15 minute problem that I could have, should have, put off until I was more capable.

I think Brent’s observation about the perils of late-night coding are a special case of a larger problem: your best work will come at unpredictable times. As a rule, we probably won’t do our best work at midnight, but there will be mornings when 9AM is not the best time for cranking out code, either. I have often made, and continue making the mistake of assuming that productivity in software development is directly related to time. It’s not. Any of us with a history of working in code has memories of those “weird days” where weeks of work seemed to vanish under the inspired direction of 4 hours “working in the zone.”

I don’t know how to get in the zone reliably, but I am learning to recognize that when I’m not there, it’s not worth pushing it. If you’re banging away at the keyboard and nothing seems to be working as well as it should, maybe it’s time to go to sleep, go for a run, go to a museum, get lunch with a friend, you get the idea. Maybe it’s time to do anything but endeavor to write code as well as you do when you’re at your best.

F-Script Anywhere With LLDB

Ever since the start of my career at Apple, working with the venerable Macsbug, I have prided myself on making the most of whatever debugging facilities are at my disposal. I came to be quite capable with Macsbug, adding custom commands and data templates that helped me speed through crucial debugging sessions that would have otherwise taken much longer.

When I moved from the classic Mac OS team to Mac OS X, I was forced, only slightly earlier than every other Mac developer, to adapt to gdb. I did so with modest aplomb, adding custom commands that made it easy to, for example, continue until the next branch instruction (as it happens, also the first real post on the Red Sweater Blog).

When Apple started shifting away from gdb to lldb a few years ago, I realized I would have to throw out all my old tricks and start building new ones. To my great shame, progress on this front has been slower than I wished. The shame is made greater by the fact that lldb is so delightfully extensible, it practically begs for a nerd like me to go town adding every manner of finessing to suit my needs.

The nut of lldb’s extensibility is that much of its functionality is actually implemented in Python, and developers such as ourselves are invited to extend that functionality by providing Python modules of our own.

I finally decided to break the ice with lldb’s extensibility by adding a shortcut command for something I often want to do, but frequently put off because it’s too cumbersome: injecting F-Script into an arbitrary application running on my Mac. F-Script is a novel dynamic programming interface that lets you query the runtime of a Cocoa app using a custom language. It also features a handy tool for drilling down into an app’s view hierarchy and then navigating the various superviews and subviews, along with all their attributes. In some respects it’s very similar to a “web inspector,” only for native Objective-C applications on the Mac (and sadly, with far fewer features).

There are Automator workflows that aim to automate the process of injecting F-Script into a target app, by running the required commands, via gdb or lldb, to make the injection work seamlessly. For some reason, these workflows have never worked so seamlessly for me, so I’m always reduced to attaching to the process with lldb, and running the required commands manually to get the framework loaded.

Fortunately for me, “having to run lldb” is not such a big deal. Usually when I want to poke around at an app, I’m in lldb anyway, trying to break on a specific function or method, or examining the application’s windows and views via the command line. Once I’m attached to a process with lldb, getting F-Script to inject itself is as easy as running these two commands:

expr (void) [[NSBundle bundleWithPath:@"/Library/Frameworks/FScript.framework"] load]
expr (void) [FScriptMenuItem insertInMainMenu]

That’s all well and good but to do that I always have to find the memo I took with the specific commands, then copy and paste them individually into lldb. Far too often, I wind up imagining the struggle of this work and put it off until I’ve spent minutes if not hours doing things “the harder way” until I finally relent and load F-Script.

Today I decided that I need to stop manually copying and pasting these commands, and I need to finally learn the slightest bit about lldb’s Python-based script commands. The fruit of this effort is summarized in this GitHub gist. Follow the directions on that link, and you’ll be no further away than typing “fsa” in lldb from having all of its utility instantly injected into the attached app from lldb.

Even if you’re not interested in F-Script Anywhere, the gist is a concise example of what it takes to install a simple, Python-backed macro command into lldb. Enjoy!