[ffigen] Correctly handle strings with null characters (#454)
* String fix mostly working using NSString.dataUsingEncoding:
* Handle case where NSData wasn't parsed
* Fix formatting (apparently Dart at HEAD disagrees with v2.18)
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 ff96125..270cd18 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
@@ -300,20 +300,33 @@
_interfaceRegistry[interface.originalName] = interface;
}
+ ObjCInterface get nsData {
+ return _interfaceRegistry["NSData"] ??
+ (ObjCInterface(
+ originalName: "NSData",
+ builtInFunctions: this,
+ isBuiltIn: true,
+ ));
+ }
+
void generateNSStringUtils(Writer w, StringBuffer s) {
- // Generate a constructor that wraps stringWithCString, and a toString
- // method that wraps UTF8String.
+ // Generate a constructor that wraps stringWithCharacters, and a toString
+ // method that wraps dataUsingEncoding.
s.write('''
factory NSString(${w.className} _lib, String str) {
- final cstr = str.toNativeUtf8();
- final nsstr = stringWithCString_encoding_(_lib, cstr.cast(), 4 /* UTF8 */);
+ final cstr = str.toNativeUtf16();
+ final nsstr = stringWithCharacters_length_(_lib, cstr.cast(), str.length);
${w.ffiPkgLibraryPrefix}.calloc.free(cstr);
return nsstr;
}
@override
- String toString() =>
- (UTF8String).cast<${w.ffiPkgLibraryPrefix}.Utf8>().toDartString();
+ String toString() {
+ final data = dataUsingEncoding_(
+ 0x94000100 /* NSUTF16LittleEndianStringEncoding */);
+ return data.bytes.cast<${w.ffiPkgLibraryPrefix}.Utf16>().toDartString(
+ length: length);
+ }
''');
}
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
index a6099b8..68da95d 100644
--- a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
+++ b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
@@ -51,19 +51,25 @@
ObjCInterface({
String? usr,
required String originalName,
- required String name,
- required this.lookupName,
+ String? name,
+ String? lookupName,
String? dartDoc,
required this.builtInFunctions,
required this.isBuiltIn,
- }) : super(
+ }) : lookupName = lookupName ?? originalName,
+ super(
usr: usr,
originalName: originalName,
- name: name,
+ name: name ?? originalName,
dartDoc: dartDoc,
- );
+ ) {
+ if (isBuiltIn) {
+ builtInFunctions.registerInterface(this);
+ }
+ }
bool get isNSString => isBuiltIn && originalName == "NSString";
+ bool get isNSData => isBuiltIn && originalName == "NSData";
@override
BindingString toBindingString(Writer w) {
@@ -224,10 +230,6 @@
dependencies.add(this);
builtInFunctions.addDependencies(dependencies);
- if (isBuiltIn) {
- builtInFunctions.registerInterface(this);
- }
-
_classObject = ObjCInternalGlobal(
'_class_$originalName',
(Writer w) => '${builtInFunctions.getClass.name}("$lookupName")',
@@ -241,6 +243,10 @@
_addNSStringMethods();
}
+ if (isNSData) {
+ _addNSDataMethods();
+ }
+
if (superType != null) {
superType!.addDependencies(dependencies);
_copyClassMethodsFromSuperType();
@@ -290,20 +296,39 @@
void _addNSStringMethods() {
addMethod(ObjCMethod(
- originalName: 'stringWithCString:encoding:',
+ originalName: 'stringWithCharacters:length:',
kind: ObjCMethodKind.method,
isClass: true,
returnType: this,
params_: [
- ObjCMethodParam(PointerType(charType), 'cString'),
- ObjCMethodParam(unsignedIntType, 'enc'),
+ ObjCMethodParam(PointerType(wCharType), 'characters'),
+ ObjCMethodParam(unsignedIntType, 'length'),
],
));
addMethod(ObjCMethod(
- originalName: 'UTF8String',
+ originalName: 'dataUsingEncoding:',
+ kind: ObjCMethodKind.method,
+ isClass: false,
+ returnType: builtInFunctions.nsData,
+ params_: [
+ ObjCMethodParam(unsignedIntType, 'encoding'),
+ ],
+ ));
+ addMethod(ObjCMethod(
+ originalName: 'length',
kind: ObjCMethodKind.propertyGetter,
isClass: false,
- returnType: PointerType(charType),
+ returnType: unsignedIntType,
+ params_: [],
+ ));
+ }
+
+ void _addNSDataMethods() {
+ addMethod(ObjCMethod(
+ originalName: 'bytes',
+ kind: ObjCMethodKind.propertyGetter,
+ isClass: false,
+ returnType: PointerType(voidType),
params_: [],
));
}
diff --git a/pkgs/ffigen/test/native_objc_test/string_test.dart b/pkgs/ffigen/test/native_objc_test/string_test.dart
index 458621e..068e5ed 100644
--- a/pkgs/ffigen/test/native_objc_test/string_test.dart
+++ b/pkgs/ffigen/test/native_objc_test/string_test.dart
@@ -25,16 +25,17 @@
generateBindingsForCoverage('string');
});
- // TODO(#329): Add 'Embedded\u0000Null'.
- for (final s in ['Hello', '🇵🇬']) {
+ for (final s in ['Hello', '🇵🇬', 'Embedded\u0000Null']) {
test('NSString to/from Dart string [$s]', () {
final ns1 = NSString(lib, s);
- expect(ns1.toString(), s);
expect(ns1.length, s.length);
+ expect(ns1.toString().length, s.length);
+ expect(ns1.toString(), s);
final ns2 = s.toNSString(lib);
- expect(ns2.toString(), s);
expect(ns2.length, s.length);
+ expect(ns2.toString().length, s.length);
+ expect(ns2.toString(), s);
});
}