diff --git a/CHANGELOG.md b/CHANGELOG.md index 7036a8c4..b90e6198 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ - Add support for ObjC Blocks that can be invoked from any thread, using NativeCallable.listener. +- Fix invalid exceptional return value ObjCBlocks that return floats. +- Fix return_of_invalid_type analysis error for ObjCBlocks. +- Fix crash in ObjC methods and blocks that return structs by value. - Bump min SDK version to 3.2.0-114.0.dev. # 9.0.1 diff --git a/example/swift/swift_api.h b/example/swift/swift_api.h index 0928c492..14ebedf9 100644 --- a/example/swift/swift_api.h +++ b/example/swift/swift_api.h @@ -1,4 +1,4 @@ -// Generated by Apple Swift version 5.7.2 (swiftlang-5.7.2.135.5 clang-1400.0.29.51) +// Generated by Apple Swift version 5.8.1 (swiftlang-5.8.0.124.5 clang-1403.0.22.11.100) #ifndef SWIFT_MODULE_SWIFT_H #define SWIFT_MODULE_SWIFT_H #pragma clang diagnostic push @@ -21,7 +21,6 @@ # include #endif -#pragma clang diagnostic ignored "-Wduplicate-method-match" #pragma clang diagnostic ignored "-Wauto-import" #if defined(__OBJC__) #include @@ -30,10 +29,24 @@ #include #include #include +#include +#include +#include +#include #else #include #include #include +#include +#endif +#if defined(__cplusplus) +#if __has_include() +# include +#else +# ifndef __ptrauth_swift_value_witness_function_pointer +# define __ptrauth_swift_value_witness_function_pointer(x) +# endif +#endif #endif #if !defined(SWIFT_TYPEDEFS) @@ -69,53 +82,66 @@ typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); # if __has_feature(objc_class_property) # define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ # else -# define SWIFT_CLASS_PROPERTY(...) +# define SWIFT_CLASS_PROPERTY(...) # endif #endif - -#if __has_attribute(objc_runtime_name) -# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) -#else -# define SWIFT_RUNTIME_NAME(X) +#if !defined(SWIFT_RUNTIME_NAME) +# if __has_attribute(objc_runtime_name) +# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) +# else +# define SWIFT_RUNTIME_NAME(X) +# endif #endif -#if __has_attribute(swift_name) -# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) -#else -# define SWIFT_COMPILE_NAME(X) +#if !defined(SWIFT_COMPILE_NAME) +# if __has_attribute(swift_name) +# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) +# else +# define SWIFT_COMPILE_NAME(X) +# endif #endif -#if __has_attribute(objc_method_family) -# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) -#else -# define SWIFT_METHOD_FAMILY(X) +#if !defined(SWIFT_METHOD_FAMILY) +# if __has_attribute(objc_method_family) +# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) +# else +# define SWIFT_METHOD_FAMILY(X) +# endif #endif -#if __has_attribute(noescape) -# define SWIFT_NOESCAPE __attribute__((noescape)) -#else -# define SWIFT_NOESCAPE +#if !defined(SWIFT_NOESCAPE) +# if __has_attribute(noescape) +# define SWIFT_NOESCAPE __attribute__((noescape)) +# else +# define SWIFT_NOESCAPE +# endif #endif -#if __has_attribute(ns_consumed) -# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) -#else -# define SWIFT_RELEASES_ARGUMENT +#if !defined(SWIFT_RELEASES_ARGUMENT) +# if __has_attribute(ns_consumed) +# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) +# else +# define SWIFT_RELEASES_ARGUMENT +# endif #endif -#if __has_attribute(warn_unused_result) -# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) -#else -# define SWIFT_WARN_UNUSED_RESULT +#if !defined(SWIFT_WARN_UNUSED_RESULT) +# if __has_attribute(warn_unused_result) +# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define SWIFT_WARN_UNUSED_RESULT +# endif #endif -#if __has_attribute(noreturn) -# define SWIFT_NORETURN __attribute__((noreturn)) -#else -# define SWIFT_NORETURN +#if !defined(SWIFT_NORETURN) +# if __has_attribute(noreturn) +# define SWIFT_NORETURN __attribute__((noreturn)) +# else +# define SWIFT_NORETURN +# endif #endif #if !defined(SWIFT_CLASS_EXTRA) -# define SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_EXTRA #endif #if !defined(SWIFT_PROTOCOL_EXTRA) -# define SWIFT_PROTOCOL_EXTRA +# define SWIFT_PROTOCOL_EXTRA #endif #if !defined(SWIFT_ENUM_EXTRA) -# define SWIFT_ENUM_EXTRA +# define SWIFT_ENUM_EXTRA #endif #if !defined(SWIFT_CLASS) # if __has_attribute(objc_subclassing_restricted) @@ -135,28 +161,25 @@ typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); # define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) # endif #endif - #if !defined(SWIFT_PROTOCOL) # define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA # define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA #endif - #if !defined(SWIFT_EXTENSION) # define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) #endif - #if !defined(OBJC_DESIGNATED_INITIALIZER) # if __has_attribute(objc_designated_initializer) # define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) # else -# define OBJC_DESIGNATED_INITIALIZER +# define OBJC_DESIGNATED_INITIALIZER # endif #endif #if !defined(SWIFT_ENUM_ATTR) -# if defined(__has_attribute) && __has_attribute(enum_extensibility) +# if __has_attribute(enum_extensibility) # define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) # else -# define SWIFT_ENUM_ATTR(_extensibility) +# define SWIFT_ENUM_ATTR(_extensibility) # endif #endif #if !defined(SWIFT_ENUM) @@ -185,14 +208,16 @@ typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); #if !defined(SWIFT_DEPRECATED_MSG) # define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) #endif -#if __has_feature(attribute_diagnose_if_objc) -# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) -#else -# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +#if !defined(SWIFT_DEPRECATED_OBJC) +# if __has_feature(attribute_diagnose_if_objc) +# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) +# else +# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +# endif #endif #if defined(__OBJC__) #if !defined(IBSegueAction) -# define IBSegueAction +# define IBSegueAction #endif #endif #if !defined(SWIFT_EXTERN) @@ -205,26 +230,31 @@ typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); #if !defined(SWIFT_CALL) # define SWIFT_CALL __attribute__((swiftcall)) #endif +#if !defined(SWIFT_INDIRECT_RESULT) +# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result)) +#endif +#if !defined(SWIFT_CONTEXT) +# define SWIFT_CONTEXT __attribute__((swift_context)) +#endif +#if !defined(SWIFT_ERROR_RESULT) +# define SWIFT_ERROR_RESULT __attribute__((swift_error_result)) +#endif #if defined(__cplusplus) -#if !defined(SWIFT_NOEXCEPT) # define SWIFT_NOEXCEPT noexcept -#endif #else -#if !defined(SWIFT_NOEXCEPT) # define SWIFT_NOEXCEPT #endif +#if defined(_WIN32) +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport) #endif -#if defined(__cplusplus) -#if !defined(SWIFT_CXX_INT_DEFINED) -#define SWIFT_CXX_INT_DEFINED -namespace swift { -using Int = ptrdiff_t; -using UInt = size_t; -} +#else +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL #endif #endif #if defined(__OBJC__) -#if __has_feature(modules) +#if __has_feature(objc_modules) #if __has_warning("-Watimport-in-framework-header") #pragma clang diagnostic ignored "-Watimport-in-framework-header" #endif diff --git a/lib/src/code_generator/objc_built_in_functions.dart b/lib/src/code_generator/objc_built_in_functions.dart index 05cd1300..b092e738 100644 --- a/lib/src/code_generator/objc_built_in_functions.dart +++ b/lib/src/code_generator/objc_built_in_functions.dart @@ -94,24 +94,18 @@ $objType $name(String name) { _blockReleaseFunc, ); - // We need to load a separate instance of objc_msgSend for each signature. - final _msgSendFuncs = {}; - Func getMsgSendFunc(Type returnType, List params) { + // We need to load a separate instance of objc_msgSend for each signature. If + // the return type is a struct, we need to use objc_msgSend_stret instead, and + // for float return types we need objc_msgSend_fpret. + final _msgSendFuncs = {}; + ObjCMsgSendFunc getMsgSendFunc( + Type returnType, List params) { var key = returnType.cacheKey(); for (final p in params) { key += ' ${p.type.cacheKey()}'; } - return _msgSendFuncs[key] ??= Func( - name: '_objc_msgSend_${_msgSendFuncs.length}', - originalName: 'objc_msgSend', - returnType: returnType, - parameters: [ - Parameter(name: 'obj', type: PointerType(objCObjectType)), - Parameter(name: 'sel', type: PointerType(objCSelType)), - for (final p in params) Parameter(name: p.name, type: p.type), - ], - isInternal: true, - ); + return _msgSendFuncs[key] ??= ObjCMsgSendFunc( + '_objc_msgSend_${_msgSendFuncs.length}', returnType, params); } final _selObjects = {}; @@ -285,8 +279,8 @@ class $name implements ${w.ffiLibraryPrefix}.Finalizable { _retainFunc.addDependencies(dependencies); _releaseFunc.addDependencies(dependencies); _releaseFinalizer.addDependencies(dependencies); - for (final func in _msgSendFuncs.values) { - func.addDependencies(dependencies); + for (final msgSendFunc in _msgSendFuncs.values) { + msgSendFunc.func.addDependencies(dependencies); } for (final sel in _selObjects.values) { sel.addDependencies(dependencies); @@ -396,3 +390,50 @@ class ObjCInternalGlobal extends LookUpBinding { binding?.addDependencies(dependencies); } } + +enum ObjCMsgSendVariant { + normal('objc_msgSend'), + stret('objc_msgSend_stret'), + fpret('objc_msgSend_fpret'); + + final String name; + const ObjCMsgSendVariant(this.name); + + static ObjCMsgSendVariant fromReturnType(Type returnType) { + if (returnType is Compound && returnType.isStruct) { + return ObjCMsgSendVariant.stret; + } else if (returnType == floatType || returnType == doubleType) { + return ObjCMsgSendVariant.fpret; + } + return ObjCMsgSendVariant.normal; + } +} + +/// A wrapper around the objc_msgSend function, or the stret or fpret variants. +class ObjCMsgSendFunc { + final ObjCMsgSendVariant variant; + late final Func func; + + ObjCMsgSendFunc(String name, Type returnType, List params) + : variant = ObjCMsgSendVariant.fromReturnType(returnType) { + func = Func( + name: name, + originalName: variant.name, + returnType: isStret ? voidType : returnType, + parameters: [ + if (isStret) Parameter(name: 'stret', type: PointerType(returnType)), + Parameter(name: 'obj', type: PointerType(objCObjectType)), + Parameter(name: 'sel', type: PointerType(objCSelType)), + for (final p in params) Parameter(name: p.name, type: p.type), + ], + isInternal: true, + ); + } + + String get name => func.name; + bool get isStret => variant == ObjCMsgSendVariant.stret; + + void addDependencies(Set dependencies) { + func.addDependencies(dependencies); + } +} diff --git a/lib/src/code_generator/objc_interface.dart b/lib/src/code_generator/objc_interface.dart index 2cf44af1..bf4d8872 100644 --- a/lib/src/code_generator/objc_interface.dart +++ b/lib/src/code_generator/objc_interface.dart @@ -46,7 +46,7 @@ class ObjCInterface extends BindingType { final ObjCBuiltInFunctions builtInFunctions; late final ObjCInternalGlobal _classObject; late final ObjCInternalGlobal _isKindOfClass; - late final Func _isKindOfClassMsgSend; + late final ObjCMsgSendFunc _isKindOfClassMsgSend; ObjCInterface({ String? usr, @@ -130,7 +130,14 @@ class $name extends ${superType?.name ?? '_ObjCWrapper'} { for (final m in methods.values) { final methodName = m._getDartMethodName(uniqueNamer); final isStatic = m.isClass; - final returnType = m.returnType; + final isStret = m.msgSend!.isStret; + + var returnType = m.returnType; + var params = m.params; + if (isStret) { + params = [ObjCMethodParam(PointerType(returnType), 'stret'), ...params]; + returnType = voidType; + } // The method declaration. if (m.dartDoc != null) { @@ -159,7 +166,7 @@ class $name extends ${superType?.name ?? '_ObjCWrapper'} { s.write(methodName[0].toUpperCase() + methodName.substring(1)); break; } - s.write(paramsToString(m.params, isStatic: true)); + s.write(paramsToString(params, isStatic: true)); } else { if (superType?.methods[m.originalName]?.sameAs(m) ?? false) { s.write('@override\n '); @@ -170,18 +177,25 @@ class $name extends ${superType?.name ?? '_ObjCWrapper'} { s.write(_getConvertedReturnType( returnType, w, name, m.isNullableReturn)); s.write(' $methodName'); - s.write(paramsToString(m.params, isStatic: false)); + s.write(paramsToString(params, isStatic: false)); break; case ObjCMethodKind.propertyGetter: - // returnType get methodName s.write(_getConvertedReturnType( returnType, w, name, m.isNullableReturn)); - s.write(' get $methodName'); + if (isStret) { + // void getMethodName(Pointer stret, NativeLibrary _lib) + s.write(' get'); + s.write(methodName[0].toUpperCase() + methodName.substring(1)); + s.write(paramsToString(params, isStatic: false)); + } else { + // returnType get methodName + s.write(' get $methodName'); + } break; case ObjCMethodKind.propertySetter: // set methodName(...) s.write(' set $methodName'); - s.write(paramsToString(m.params, isStatic: false)); + s.write(paramsToString(params, isStatic: false)); break; } } @@ -192,10 +206,13 @@ class $name extends ${superType?.name ?? '_ObjCWrapper'} { final convertReturn = m.kind != ObjCMethodKind.propertySetter && _needsConverting(returnType); - if (returnType != NativeType(SupportedNativeType.Void)) { + if (returnType != voidType) { s.write(' ${convertReturn ? 'final _ret = ' : 'return '}'); } s.write('_lib.${m.msgSend!.name}('); + if (isStret) { + s.write('stret, '); + } s.write(isStatic ? '_lib.${_classObject.name}' : '_id'); s.write(', _lib.${m.selObject!.name}'); for (final p in m.params) { @@ -427,7 +444,7 @@ class ObjCMethod { final bool isClass; bool returnsRetained = false; ObjCInternalGlobal? selObject; - Func? msgSend; + ObjCMsgSendFunc? msgSend; ObjCMethod({ required this.originalName, diff --git a/test/native_objc_test/block_test.dart b/test/native_objc_test/block_test.dart index 87a735a9..908e8fc4 100644 --- a/test/native_objc_test/block_test.dart +++ b/test/native_objc_test/block_test.dart @@ -109,6 +109,52 @@ void main() { expect(BlockTester.callFloatBlock_(lib, block), closeTo(5.79, 1e-6)); }); + test('Double block', () { + final block = ObjCBlock3.fromFunction(lib, (double x) { + return x + 4.56; + }); + expect(block(1.23), closeTo(5.79, 1e-6)); + expect(BlockTester.callDoubleBlock_(lib, block), closeTo(5.79, 1e-6)); + }); + + test('Struct block', () { + final inputPtr = calloc(); + final input = inputPtr.ref; + input.x = 1.2; + input.y = 3.4; + input.z = 5.6; + input.w = 7.8; + + final tempPtr = calloc(); + final temp = tempPtr.ref; + final block = ObjCBlock4.fromFunction(lib, (Vec4 v) { + // Twiddle the Vec4 components. + temp.x = v.y; + temp.y = v.z; + temp.z = v.w; + temp.w = v.x; + return temp; + }); + + final result1 = block(input); + expect(result1.x, 3.4); + expect(result1.y, 5.6); + expect(result1.z, 7.8); + expect(result1.w, 1.2); + + final result2Ptr = calloc(); + final result2 = result2Ptr.ref; + BlockTester.callVec4Block_(lib, result2Ptr, block); + expect(result2.x, 3.4); + expect(result2.y, 5.6); + expect(result2.z, 7.8); + expect(result2.w, 1.2); + + calloc.free(inputPtr); + calloc.free(tempPtr); + calloc.free(result2Ptr); + }); + Pointer funcPointerBlockRefCountTest() { final block = ObjCBlock1.fromFunctionPointer( lib, Pointer.fromFunction(_add100, 999)); diff --git a/test/native_objc_test/block_test.m b/test/native_objc_test/block_test.m index 9e619b64..a864f2ef 100644 --- a/test/native_objc_test/block_test.m +++ b/test/native_objc_test/block_test.m @@ -5,8 +5,17 @@ #import #import +typedef struct { + double x; + double y; + double z; + double w; +} Vec4; + typedef int32_t (^IntBlock)(int32_t); typedef float (^FloatBlock)(float); +typedef double (^DoubleBlock)(double); +typedef Vec4 (^Vec4Block)(Vec4); typedef void (^VoidBlock)(); // Wrapper around a block, so that our Dart code can test creating and invoking @@ -23,6 +32,8 @@ - (void)pokeBlock; + (void)callOnSameThread:(VoidBlock)block; + (NSThread*)callOnNewThread:(VoidBlock)block; + (float)callFloatBlock:(FloatBlock)block; ++ (double)callDoubleBlock:(DoubleBlock)block; ++ (Vec4)callVec4Block:(Vec4Block)block; @end @implementation BlockTester @@ -92,4 +103,17 @@ + (float)callFloatBlock:(FloatBlock)block { return block(1.23); } ++ (double)callDoubleBlock:(DoubleBlock)block { + return block(1.23); +} + ++ (Vec4)callVec4Block:(Vec4Block)block { + Vec4 vec4; + vec4.x = 1.2; + vec4.y = 3.4; + vec4.z = 5.6; + vec4.w = 7.8; + return block(vec4); +} + @end diff --git a/test/native_objc_test/method_test.dart b/test/native_objc_test/method_test.dart index ebb1fba9..21ba7e06 100644 --- a/test/native_objc_test/method_test.dart +++ b/test/native_objc_test/method_test.dart @@ -8,13 +8,14 @@ import 'dart:ffi'; import 'dart:io'; +import 'package:ffi/ffi.dart'; import 'package:test/test.dart'; import '../test_utils.dart'; import 'method_bindings.dart'; import 'util.dart'; void main() { - MethodInterface? testInstance; + late MethodInterface testInstance; late MethodTestObjCLibrary lib; group('method calls', () { @@ -29,19 +30,19 @@ void main() { group('Instance methods', () { test('No arguments', () { - expect(testInstance!.add(), 5); + expect(testInstance.add(), 5); }); test('One argument', () { - expect(testInstance!.add_(23), 23); + expect(testInstance.add_(23), 23); }); test('Two arguments', () { - expect(testInstance!.add_Y_(23, 17), 40); + expect(testInstance.add_Y_(23, 17), 40); }); test('Three arguments', () { - expect(testInstance!.add_Y_Z_(23, 17, 60), 100); + expect(testInstance.add_Y_Z_(23, 17, 60), 100); }); }); @@ -62,5 +63,35 @@ void main() { expect(MethodInterface.sub_Y_Z_(lib, 10, 7, 3), -20); }); }); + + group('Regress #608', () { + test('Structs', () { + final inputPtr = calloc(); + final input = inputPtr.ref; + input.x = 1.2; + input.y = 3.4; + input.z = 5.6; + input.w = 7.8; + + final resultPtr = calloc(); + final result = resultPtr.ref; + testInstance.twiddleVec4Components_(resultPtr, input); + expect(result.x, 3.4); + expect(result.y, 5.6); + expect(result.z, 7.8); + expect(result.w, 1.2); + + calloc.free(inputPtr); + calloc.free(resultPtr); + }); + + test('Floats', () { + expect(testInstance.addFloats_Y_(1.23, 4.56), closeTo(5.79, 1e-6)); + }); + + test('Doubles', () { + expect(testInstance.addDoubles_Y_(1.23, 4.56), closeTo(5.79, 1e-6)); + }); + }); }); } diff --git a/test/native_objc_test/method_test.m b/test/native_objc_test/method_test.m index 86ea0904..179f4ef6 100644 --- a/test/native_objc_test/method_test.m +++ b/test/native_objc_test/method_test.m @@ -1,5 +1,12 @@ #import +typedef struct { + double x; + double y; + double z; + double w; +} Vec4; + @interface MethodInterface : NSObject { } @@ -14,6 +21,10 @@ +(int32_t)sub:(int32_t)x; +(int32_t)sub:(int32_t)x Y:(int32_t) y; +(int32_t)sub:(int32_t)x Y:(int32_t) y Z:(int32_t) z; +-(Vec4)twiddleVec4Components:(Vec4)v; +-(float)addFloats:(float)x Y:(float) y; +-(double)addDoubles:(double)x Y:(double) y; + @end @implementation MethodInterface @@ -50,4 +61,21 @@ +(int32_t)sub:(int32_t)x Y:(int32_t) y Z:(int32_t) z { return - x - y - z; } +-(Vec4)twiddleVec4Components:(Vec4)v { + Vec4 u; + u.x = v.y; + u.y = v.z; + u.z = v.w; + u.w = v.x; + return u; +} + +-(float)addFloats:(float)x Y:(float) y { + return x + y; +} + +-(double)addDoubles:(double)x Y:(double) y { + return x + y; +} + @end diff --git a/test/native_objc_test/property_test.dart b/test/native_objc_test/property_test.dart index 72c11e03..9bd4f3c6 100644 --- a/test/native_objc_test/property_test.dart +++ b/test/native_objc_test/property_test.dart @@ -8,13 +8,14 @@ import 'dart:ffi'; import 'dart:io'; +import 'package:ffi/ffi.dart'; import 'package:test/test.dart'; import '../test_utils.dart'; import 'property_bindings.dart'; import 'util.dart'; void main() { - PropertyInterface? testInstance; + late PropertyInterface testInstance; late PropertyTestObjCLibrary lib; group('properties', () { @@ -29,12 +30,12 @@ void main() { group('instance properties', () { test('read-only property', () { - expect(testInstance!.readOnlyProperty, 7); + expect(testInstance.readOnlyProperty, 7); }); test('read-write property', () { - testInstance!.readWriteProperty = 23; - expect(testInstance!.readWriteProperty, 23); + testInstance.readWriteProperty = 23; + expect(testInstance.readWriteProperty, 23); }); }); @@ -48,5 +49,38 @@ void main() { expect(PropertyInterface.getClassReadWriteProperty(lib), 101); }); }); + + group('Regress #608', () { + test('Structs', () { + final inputPtr = calloc(); + final input = inputPtr.ref; + input.x = 1.2; + input.y = 3.4; + input.z = 5.6; + input.w = 7.8; + + final resultPtr = calloc(); + final result = resultPtr.ref; + testInstance.structProperty = input; + testInstance.getStructProperty(resultPtr); + expect(result.x, 1.2); + expect(result.y, 3.4); + expect(result.z, 5.6); + expect(result.w, 7.8); + + calloc.free(inputPtr); + calloc.free(resultPtr); + }); + + test('Floats', () { + testInstance.floatProperty = 1.23; + expect(testInstance.floatProperty, closeTo(1.23, 1e-6)); + }); + + test('Doubles', () { + testInstance.doubleProperty = 1.23; + expect(testInstance.doubleProperty, 1.23); + }); + }); }); } diff --git a/test/native_objc_test/property_test.m b/test/native_objc_test/property_test.m index 117c6d53..0085034c 100644 --- a/test/native_objc_test/property_test.m +++ b/test/native_objc_test/property_test.m @@ -2,6 +2,13 @@ @class UndefinedTemplate; +typedef struct { + double x; + double y; + double z; + double w; +} Vec4; + @interface PropertyInterface : NSObject { } @@ -10,6 +17,9 @@ @interface PropertyInterface : NSObject { @property (class, readonly, copy) UndefinedTemplate *regressGH436; @property (class, readonly) int32_t classReadOnlyProperty; @property (class) int32_t classReadWriteProperty; +@property float floatProperty; +@property double doubleProperty; +@property Vec4 structProperty; @end @@ -33,4 +43,4 @@ + (void)setClassReadWriteProperty:(int32_t)x { _classReadWriteProperty = x; } -@end \ No newline at end of file +@end