[ffigen] Throw an error if ObjC class fails to load (#423)

diff --git a/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart b/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart
index a92dbcb..c7208a9 100644
--- a/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart
+++ b/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart
@@ -40,12 +40,17 @@
       ObjCInternalFunction('_getClass', _getClassFunc, (Writer w, String name) {
     final s = StringBuffer();
     final objType = _getClassFunc.functionType.returnType.getCType(w);
-    s.write('\n$objType $name(String name) {\n');
-    s.write('  final cstr = name.toNativeUtf8();\n');
-    s.write('  final clazz = ${_getClassFunc.name}(cstr.cast());\n');
-    s.write('  ${w.ffiPkgLibraryPrefix}.calloc.free(cstr);\n');
-    s.write('  return clazz;\n');
-    s.write('}\n');
+    s.write('''
+$objType $name(String name) {
+  final cstr = name.toNativeUtf8();
+  final clazz = ${_getClassFunc.name}(cstr.cast());
+  ${w.ffiPkgLibraryPrefix}.calloc.free(cstr);
+  if (clazz == ${w.ffiLibraryPrefix}.nullptr) {
+    throw Exception('Failed to load Objective-C class: \$name');
+  }
+  return clazz;
+}
+''');
     return s.toString();
   });
 
diff --git a/pkgs/ffigen/test/native_objc_test/failed_to_load_config.yaml b/pkgs/ffigen/test/native_objc_test/failed_to_load_config.yaml
new file mode 100644
index 0000000..2d114a1
--- /dev/null
+++ b/pkgs/ffigen/test/native_objc_test/failed_to_load_config.yaml
@@ -0,0 +1,12 @@
+name: FailedToLoadTestObjCLibrary
+description: 'Tests failing to load an Objective-C library'
+language: objc
+output: 'test/native_objc_test/failed_to_load_bindings.dart'
+functions:
+  exclude:
+    - '.*'
+headers:
+  entry-points:
+    - 'test/native_objc_test/failed_to_load_test.m'
+preamble: |
+  // ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/pkgs/ffigen/test/native_objc_test/failed_to_load_test.dart b/pkgs/ffigen/test/native_objc_test/failed_to_load_test.dart
new file mode 100644
index 0000000..8d1bb70
--- /dev/null
+++ b/pkgs/ffigen/test/native_objc_test/failed_to_load_test.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Objective C support is only available on mac.
+@TestOn('mac-os')
+
+import 'dart:ffi';
+import 'dart:io';
+
+import 'package:test/test.dart';
+import '../test_utils.dart';
+import 'failed_to_load_bindings.dart';
+import 'util.dart';
+
+void main() {
+  group('Failed to load', () {
+    setUpAll(() {
+      logWarnings();
+      generateBindingsForCoverage('failed_to_load');
+    });
+
+    test('Failed to load Objective-C class', () {
+      // Load from the host executable, which is missing all the classes for
+      // this test, but has the core ObjC functions, such as objc_getClass. The
+      // library should load ok, because the classes are lazy loaded.
+      final lib = FailedToLoadTestObjCLibrary(DynamicLibrary.executable());
+
+      // But when we try to instantiate one of the classes, we get an error.
+      expect(
+          () => ClassThatWillFailToLoad.new1(lib),
+          throwsA(predicate(
+              (e) => e.toString().contains('ClassThatWillFailToLoad'))));
+    });
+  });
+}
diff --git a/pkgs/ffigen/test/native_objc_test/failed_to_load_test.m b/pkgs/ffigen/test/native_objc_test/failed_to_load_test.m
new file mode 100644
index 0000000..b4d5e68
--- /dev/null
+++ b/pkgs/ffigen/test/native_objc_test/failed_to_load_test.m
@@ -0,0 +1,15 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#import <Foundation/NSObject.h>
+
+@interface ClassThatWillFailToLoad : NSObject {}
+-(int32_t)get123;
+@end
+
+@implementation ClassThatWillFailToLoad
+-(int32_t)get123 {
+  return 123;
+}
+@end
diff --git a/pkgs/ffigen/test/native_objc_test/setup.dart b/pkgs/ffigen/test/native_objc_test/setup.dart
index 78c7427..9194c45 100644
--- a/pkgs/ffigen/test/native_objc_test/setup.dart
+++ b/pkgs/ffigen/test/native_objc_test/setup.dart
@@ -45,21 +45,19 @@
   print('Generated bindings for: $config');
 }
 
-const testNames = [
-  'automated_ref_count',
-  'bad_method',
-  'block',
-  'cast',
-  'category',
-  'forward_decl',
-  'is_instance',
-  'method',
-  'native_objc',
-  'nullable',
-  'property',
-  'rename',
-  'string',
-];
+List<String> _getTestNames() {
+  const configSuffix = '_config.yaml';
+  final names = <String>[];
+  for (final entity in Directory.current.listSync()) {
+    final filename = entity.uri.pathSegments.last;
+    if (filename.endsWith(configSuffix)) {
+      names.add(filename.substring(0, filename.length - configSuffix.length));
+    }
+  }
+  return names;
+}
+
+final testNames = _getTestNames();
 
 Future<void> build() async {
   print('Building Dynamic Library for Objective C Native Tests...');