Bring up skeleton crashpad_client_ios.

First steps at bringing up the crashpad_client on iOS.  Also updates
the XCUITest to trigger various crashes, with some swizzling
necessary to allow crashes.

Change-Id: Iba46048981f885fa3353e839bd54eadfc8ba8df7
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/2039470
Commit-Queue: Justin Cohen <justincohen@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
Reviewed-by: Rohit Rao <rohitrao@chromium.org>
GitOrigin-RevId: 9ed82905471121a59fb8827627df3a98b7cdfe55
diff --git a/client/BUILD.gn b/client/BUILD.gn
index a61c66d..4ec0386 100644
--- a/client/BUILD.gn
+++ b/client/BUILD.gn
@@ -43,6 +43,10 @@
     ]
   }
 
+  if (crashpad_is_ios) {
+    sources += [ "crashpad_client_ios.cc" ]
+  }
+
   if (crashpad_is_linux || crashpad_is_android) {
     set_sources_assignment_filter([])
     sources += [
@@ -137,7 +141,9 @@
     "../util",
   ]
 
-  data_deps = [ "../handler:crashpad_handler" ]
+  if (!crashpad_is_ios) {
+    data_deps = [ "../handler:crashpad_handler" ]
+  }
 
   if (crashpad_is_win) {
     data_deps += [ "../handler:crashpad_handler_console" ]
diff --git a/client/crashpad_client.h b/client/crashpad_client.h
index 207f978..e0cd2f1 100644
--- a/client/crashpad_client.h
+++ b/client/crashpad_client.h
@@ -423,9 +423,21 @@
   //!
   //! \param[in] unhandled_signals The set of unhandled signals
   void SetUnhandledSignals(const std::set<int>& unhandled_signals);
-
 #endif  // OS_LINUX || OS_ANDROID || DOXYGEN
 
+#if defined(OS_IOS) || DOXYGEN
+  //! \brief Configures the process to direct its crashes to the iOS in-process
+  //! Crashpad handler.
+  //!
+  //! This method is only defined on iOS.
+  //!
+  //! \return `true` on success, `false` on failure with a message logged.
+  //!
+  //! TODO(justincohen): This method will need to take database, metrics_dir,
+  //! url and annotations eventually.
+  bool StartCrashpadInProcessHandler();
+#endif
+
 #if defined(OS_MACOSX) || DOXYGEN
   //! \brief Sets the process’ crash handler to a Mach service registered with
   //!     the bootstrap server.
diff --git a/client/crashpad_client_ios.cc b/client/crashpad_client_ios.cc
new file mode 100644
index 0000000..c19152b
--- /dev/null
+++ b/client/crashpad_client_ios.cc
@@ -0,0 +1,75 @@
+// Copyright 2020 The Crashpad Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "client/crashpad_client.h"
+
+#include <unistd.h>
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "client/client_argv_handling.h"
+#include "util/posix/signals.h"
+
+namespace crashpad {
+
+namespace {
+
+// A base class for Crashpad signal handler implementations.
+class SignalHandler {
+ public:
+  // Returns the currently installed signal hander.
+  static SignalHandler* Get() {
+    static SignalHandler* instance = new SignalHandler();
+    return instance;
+  }
+
+  bool Install(const std::set<int>* unhandled_signals) {
+    return Signals::InstallCrashHandlers(
+        HandleSignal, 0, &old_actions_, unhandled_signals);
+  }
+
+ private:
+  SignalHandler() = default;
+
+  // The base implementation for all signal handlers, suitable for calling
+  // directly to simulate signal delivery.
+  void HandleCrash(int signo, siginfo_t* siginfo, void* context) {
+    // Do Something.
+
+    // Always call system handler.
+    Signals::RestoreHandlerAndReraiseSignalOnReturn(
+        siginfo, old_actions_.ActionForSignal(signo));
+  }
+
+  // The signal handler installed at OS-level.
+  static void HandleSignal(int signo, siginfo_t* siginfo, void* context) {
+    Get()->HandleCrash(signo, siginfo, context);
+  }
+
+  Signals::OldActions old_actions_ = {};
+
+  DISALLOW_COPY_AND_ASSIGN(SignalHandler);
+};
+
+}  // namespace
+
+CrashpadClient::CrashpadClient() {}
+
+CrashpadClient::~CrashpadClient() {}
+
+bool CrashpadClient::StartCrashpadInProcessHandler() {
+  return SignalHandler::Get()->Install(nullptr);
+}
+
+}  // namespace crashpad
diff --git a/compat/BUILD.gn b/compat/BUILD.gn
index 62ee528..2f24656 100644
--- a/compat/BUILD.gn
+++ b/compat/BUILD.gn
@@ -19,7 +19,7 @@
 
   if (crashpad_is_mac) {
     include_dirs += [ "mac" ]
-  } else {
+  } else if (!crashpad_is_ios) {
     include_dirs += [ "non_mac" ]
   }
 
@@ -43,7 +43,7 @@
 }
 
 template("compat_target") {
-  if (crashpad_is_mac) {
+  if (crashpad_is_mac || crashpad_is_ios) {
     # There are no sources to compile, which doesn’t mix will with a
     # static_library.
     group(target_name) {
@@ -67,7 +67,7 @@
       "mac/mach/mach.h",
       "mac/sys/resource.h",
     ]
-  } else {
+  } else if (!crashpad_is_ios) {
     sources += [
       "non_mac/mach-o/loader.h",
       "non_mac/mach/mach.h",
diff --git a/handler/BUILD.gn b/handler/BUILD.gn
index 8862fab..3db3c89 100644
--- a/handler/BUILD.gn
+++ b/handler/BUILD.gn
@@ -140,25 +140,27 @@
   }
 }
 
-crashpad_executable("crashpad_handler") {
-  sources = [ "main.cc" ]
+if (!crashpad_is_ios) {
+  crashpad_executable("crashpad_handler") {
+    sources = [ "main.cc" ]
 
-  deps = [
-    ":handler",
-    "../build:default_exe_manifest_win",
-    "../compat",
-    "../third_party/mini_chromium:base",
-  ]
+    deps = [
+      ":handler",
+      "../build:default_exe_manifest_win",
+      "../compat",
+      "../third_party/mini_chromium:base",
+    ]
 
-  if (crashpad_is_win) {
-    if (crashpad_is_in_chromium || crashpad_is_in_dart) {
-      remove_configs = [ "//build/config/win:console" ]
-      configs = [ "//build/config/win:windowed" ]
-    } else {
-      remove_configs =
-          [ "//third_party/mini_chromium/mini_chromium/build:win_console" ]
-      configs =
-          [ "//third_party/mini_chromium/mini_chromium/build:win_windowed" ]
+    if (crashpad_is_win) {
+      if (crashpad_is_in_chromium || crashpad_is_in_dart) {
+        remove_configs = [ "//build/config/win:console" ]
+        configs = [ "//build/config/win:windowed" ]
+      } else {
+        remove_configs =
+            [ "//third_party/mini_chromium/mini_chromium/build:win_console" ]
+        configs =
+            [ "//third_party/mini_chromium/mini_chromium/build:win_windowed" ]
+      }
     }
   }
 }
@@ -190,19 +192,21 @@
   }
 }
 
-crashpad_executable("crashpad_handler_test_extended_handler") {
-  testonly = true
+if (!crashpad_is_ios) {
+  crashpad_executable("crashpad_handler_test_extended_handler") {
+    testonly = true
 
-  sources = [ "crashpad_handler_test_extended_handler.cc" ]
+    sources = [ "crashpad_handler_test_extended_handler.cc" ]
 
-  deps = [
-    ":handler",
-    "../build:default_exe_manifest_win",
-    "../compat",
-    "../minidump:test_support",
-    "../third_party/mini_chromium:base",
-    "../tools:tool_support",
-  ]
+    deps = [
+      ":handler",
+      "../build:default_exe_manifest_win",
+      "../compat",
+      "../minidump:test_support",
+      "../third_party/mini_chromium:base",
+      "../tools:tool_support",
+    ]
+  }
 }
 
 if (crashpad_is_win) {
diff --git a/test/ios/BUILD.gn b/test/ios/BUILD.gn
index 225bf2a..d548a27 100644
--- a/test/ios/BUILD.gn
+++ b/test/ios/BUILD.gn
@@ -40,6 +40,7 @@
   deps = [
     "../../build:ios_enable_arc",
     "../../build:ios_xctest",
+    "../../test/ios:google_test_runner_shared_headers",
     "../../third_party/mini_chromium:base",
   ]
   libs = [ "UIKit.framework" ]
diff --git a/test/ios/crash_type_xctest.mm b/test/ios/crash_type_xctest.mm
index ed72f61..8ebdafa 100644
--- a/test/ios/crash_type_xctest.mm
+++ b/test/ios/crash_type_xctest.mm
@@ -14,26 +14,135 @@
 
 #import <XCTest/XCTest.h>
 
+#include <objc/runtime.h>
 #import "Service/Sources/EDOClientService.h"
-#import "test/ios/host/edo_placeholder.h"
+#import "test/ios/host/cptest_shared_object.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
-@interface CPTestTestCase : XCTestCase
+@interface CPTestTestCase : XCTestCase {
+  XCUIApplication* _app;
+}
+
 @end
 
 @implementation CPTestTestCase
 
+- (void)handleCrashUnderSymbol:(id)arg1 {
+  // For now, do nothing.  In the future this can be something testable.
+}
+
++ (void)setUp {
+  // Swizzle away the handleCrashUnderSymbol callback.  Without this, any time
+  // the host app is intentionally crashed, the test is immediately failed.
+  SEL originalSelector = NSSelectorFromString(@"handleCrashUnderSymbol:");
+  SEL swizzledSelector = @selector(handleCrashUnderSymbol:);
+
+  Method originalMethod = class_getInstanceMethod(
+      objc_getClass("XCUIApplicationImpl"), originalSelector);
+  Method swizzledMethod =
+      class_getInstanceMethod([self class], swizzledSelector);
+
+  method_exchangeImplementations(originalMethod, swizzledMethod);
+
+  // Override EDO default error handler.  Without this, the default EDO error
+  // handler will throw an error and fail the test.
+  [EDOClientService setErrorHandler:^(NSError* error){
+      // Do nothing.
+  }];
+}
+
 - (void)setUp {
-  [[[XCUIApplication alloc] init] launch];
+  _app = [[XCUIApplication alloc] init];
+  [_app launch];
 }
 
 - (void)testEDO {
-  EDOPlaceholder* rootObject = [EDOClientService rootObjectWithPort:12345];
+  CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345];
   NSString* result = [rootObject testEDO];
   XCTAssertEqualObjects(result, @"crashpad");
 }
 
+- (void)testSegv {
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+
+  // Crash the app.
+  CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345];
+  [rootObject crashSegv];
+
+  // Confirm the app is not running.
+  XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]);
+  XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning);
+
+  // TODO: Query the app for crash data
+  [_app launch];
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+}
+
+- (void)testKillAbort {
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+
+  // Crash the app.
+  CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345];
+  [rootObject crashKillAbort];
+
+  // Confirm the app is not running.
+  XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]);
+  XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning);
+
+  // TODO: Query the app for crash data
+  [_app launch];
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+}
+
+- (void)testTrap {
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+
+  // Crash the app.
+  CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345];
+  [rootObject crashTrap];
+
+  // Confirm the app is not running.
+  XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]);
+  XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning);
+
+  // TODO: Query the app for crash data
+  [_app launch];
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+}
+
+- (void)testAbort {
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+
+  // Crash the app.
+  CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345];
+  [rootObject crashAbort];
+
+  // Confirm the app is not running.
+  XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]);
+  XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning);
+
+  // TODO: Query the app for crash data
+  [_app launch];
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+}
+
+- (void)testBadAccess {
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+
+  // Crash the app.
+  CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345];
+  [rootObject crashBadAccess];
+
+  // Confirm the app is not running.
+  XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]);
+  XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning);
+
+  // TODO: Query the app for crash data
+  [_app launch];
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+}
+
 @end
diff --git a/test/ios/host/BUILD.gn b/test/ios/host/BUILD.gn
index 39c8d99..e0d289d 100644
--- a/test/ios/host/BUILD.gn
+++ b/test/ios/host/BUILD.gn
@@ -22,13 +22,9 @@
 
 source_set("app_shared_sources") {
   testonly = true
-  sources = [
-    "edo_placeholder.h",
-  ]
+  sources = [ "cptest_shared_object.h" ]
   configs += [ "../../..:crashpad_config" ]
-  deps = [
-    "../../../build:ios_enable_arc",
-  ]
+  deps = [ "../../../build:ios_enable_arc" ]
   libs = [ "UIKit.framework" ]
 }
 
@@ -45,6 +41,7 @@
   deps = [
     ":app_shared_sources",
     "../../../build:ios_enable_arc",
+    "../../../client",
     "../../../third_party/edo",
   ]
   libs = [
@@ -56,7 +53,5 @@
 ios_app_bundle("ios_crash_xcuitests") {
   info_plist = "Info.plist"
   testonly = true
-  deps = [
-    ":app_host_sources",
-  ]
+  deps = [ ":app_host_sources" ]
 }
diff --git a/test/ios/host/application_delegate.mm b/test/ios/host/application_delegate.mm
index 115d0ae..a31d4d6 100644
--- a/test/ios/host/application_delegate.mm
+++ b/test/ios/host/application_delegate.mm
@@ -16,8 +16,9 @@
 
 #import "Service/Sources/EDOHostNamingService.h"
 #import "Service/Sources/EDOHostService.h"
+#include "client/crashpad_client.h"
+#import "test/ios/host/cptest_shared_object.h"
 #import "test/ios/host/crash_view_controller.h"
-#import "test/ios/host/edo_placeholder.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -28,6 +29,10 @@
 
 - (BOOL)application:(UIApplication*)application
     didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
+  // Start up crashpad.
+  crashpad::CrashpadClient client;
+  client.StartCrashpadInProcessHandler();
+
   self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
   [self.window makeKeyAndVisible];
   self.window.backgroundColor = UIColor.greenColor;
@@ -37,17 +42,37 @@
 
   // Start up EDO.
   [EDOHostService serviceWithPort:12345
-                       rootObject:[[EDOPlaceholder alloc] init]
+                       rootObject:[[CPTestSharedObject alloc] init]
                             queue:dispatch_get_main_queue()];
-  [EDOHostNamingService.sharedService start];
-
   return YES;
 }
 
 @end
 
-@implementation EDOPlaceholder
+@implementation CPTestSharedObject
 - (NSString*)testEDO {
   return @"crashpad";
 }
+
+- (void)crashBadAccess {
+  strcpy(0, "bla");
+}
+
+- (void)crashKillAbort {
+  kill(getpid(), SIGABRT);
+}
+
+- (void)crashSegv {
+  long zero = 0;
+  *(long*)zero = 0xC045004d;
+}
+
+- (void)crashTrap {
+  __builtin_trap();
+}
+
+- (void)crashAbort {
+  abort();
+}
+
 @end
diff --git a/test/ios/host/cptest_shared_object.h b/test/ios/host/cptest_shared_object.h
new file mode 100644
index 0000000..483fce4
--- /dev/null
+++ b/test/ios/host/cptest_shared_object.h
@@ -0,0 +1,40 @@
+// Copyright 2020 The Crashpad Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef CRASHPAD_TEST_IOS_HOST_SHARED_OBJECT_H_
+#define CRASHPAD_TEST_IOS_HOST_SHARED_OBJECT_H_
+
+#import <UIKit/UIKit.h>
+
+@interface CPTestSharedObject : NSObject
+// Returns the string "crashpad" for testing EDO.
+- (NSString*)testEDO;
+
+// Triggers an EXC_BAD_ACCESS exception and crash.
+- (void)crashBadAccess;
+
+// Triggers a crash with a call to kill(SIGABRT).
+- (void)crashKillAbort;
+
+// Triggers a segfault crash.
+- (void)crashSegv;
+
+// Trigger a crash with a __builtin_trap.
+- (void)crashTrap;
+
+// Trigger a crash with an abort().
+- (void)crashAbort;
+@end
+
+#endif  // CRASHPAD_TEST_IOS_HOST_SHARED_OBJECT_H_
diff --git a/test/ios/host/edo_placeholder.h b/test/ios/host/edo_placeholder.h
deleted file mode 100644
index 84bf0c4..0000000
--- a/test/ios/host/edo_placeholder.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2020 The Crashpad Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef CRASHPAD_TEST_IOS_HOST_EDO_PLACEHOLDER_H_
-#define CRASHPAD_TEST_IOS_HOST_EDO_PLACEHOLDER_H_
-
-#import <UIKit/UIKit.h>
-
-@interface EDOPlaceholder : NSObject
-- (NSString*)testEDO;
-@end
-
-#endif  // CRASHPAD_TEST_IOS_HOST_EDO_PLACEHOLDER_H_
diff --git a/tools/BUILD.gn b/tools/BUILD.gn
index 41f0fb6..5e0228b 100644
--- a/tools/BUILD.gn
+++ b/tools/BUILD.gn
@@ -25,29 +25,31 @@
   deps = [ "../third_party/mini_chromium:base" ]
 }
 
-crashpad_executable("crashpad_database_util") {
-  sources = [ "crashpad_database_util.cc" ]
+if (!crashpad_is_ios) {
+  crashpad_executable("crashpad_database_util") {
+    sources = [ "crashpad_database_util.cc" ]
 
-  deps = [
-    ":tool_support",
-    "../build:default_exe_manifest_win",
-    "../client",
-    "../compat",
-    "../third_party/mini_chromium:base",
-    "../util",
-  ]
-}
+    deps = [
+      ":tool_support",
+      "../build:default_exe_manifest_win",
+      "../client",
+      "../compat",
+      "../third_party/mini_chromium:base",
+      "../util",
+    ]
+  }
 
-crashpad_executable("crashpad_http_upload") {
-  sources = [ "crashpad_http_upload.cc" ]
+  crashpad_executable("crashpad_http_upload") {
+    sources = [ "crashpad_http_upload.cc" ]
 
-  deps = [
-    ":tool_support",
-    "../build:default_exe_manifest_win",
-    "../compat",
-    "../third_party/mini_chromium:base",
-    "../util",
-  ]
+    deps = [
+      ":tool_support",
+      "../build:default_exe_manifest_win",
+      "../compat",
+      "../third_party/mini_chromium:base",
+      "../util",
+    ]
+  }
 }
 
 crashpad_executable("base94_encoder") {
@@ -60,7 +62,7 @@
   ]
 }
 
-if (!crashpad_is_fuchsia) {
+if (!crashpad_is_fuchsia && !crashpad_is_ios) {
   crashpad_executable("generate_dump") {
     sources = [ "generate_dump.cc" ]
 
@@ -88,7 +90,8 @@
     }
 
     if (crashpad_is_win) {
-      cflags = [ "/wd4201" ]  # nonstandard extension used : nameless struct/union
+      cflags =
+          [ "/wd4201" ]  # nonstandard extension used : nameless struct/union
     }
   }
 }
diff --git a/util/stdlib/strnlen.cc b/util/stdlib/strnlen.cc
index a238728..7ef8d3b 100644
--- a/util/stdlib/strnlen.cc
+++ b/util/stdlib/strnlen.cc
@@ -14,7 +14,8 @@
 
 #include "util/stdlib/strnlen.h"
 
-#if defined(OS_MACOSX) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7
+#if defined(OS_MACOSX) && !defined(OS_IOS) && \
+    MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7
 
 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
 // Redeclare a method only available on Mac OS X 10.7 and later to suppress a
diff --git a/util/stdlib/strnlen.h b/util/stdlib/strnlen.h
index e85d8c7..1db5f6e 100644
--- a/util/stdlib/strnlen.h
+++ b/util/stdlib/strnlen.h
@@ -20,7 +20,7 @@
 
 #include "build/build_config.h"
 
-#if defined(OS_MACOSX)
+#if defined(OS_MACOSX) && !defined(OS_IOS)
 #include <AvailabilityMacros.h>
 #endif
 
@@ -38,7 +38,7 @@
 //!     and not all systems’ standard libraries provide an implementation.
 size_t strnlen(const char* string, size_t max_length);
 
-#if !defined(OS_MACOSX) || \
+#if !defined(OS_MACOSX) || defined(OS_IOS) || \
     MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7
 inline size_t strnlen(const char* string, size_t max_length) {
   return ::strnlen(string, max_length);