[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...');