| // Copyright (c) 2024, 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. |
| |
| // All @Native bindings in this file live in the package's native asset dylib. |
| @DefaultAsset('package:objective_c/objective_c.dylib') |
| library; |
| |
| import 'dart:ffi'; |
| |
| import 'package:ffi/ffi.dart'; |
| import 'package:native_test_helpers/native_test_helpers.dart'; |
| import 'package:objective_c/objective_c.dart'; |
| import 'package:objective_c/src/c_bindings_generated.dart' as c; |
| import 'package:objective_c/src/internal.dart' |
| as internal_for_testing |
| show isValidClass; |
| |
| final _executeInternalCommand = () { |
| try { |
| return DynamicLibrary.process() |
| .lookup<NativeFunction<Void Function(Pointer<Char>, Pointer<Void>)>>( |
| 'Dart_ExecuteInternalCommand', |
| ) |
| .asFunction<void Function(Pointer<Char>, Pointer<Void>)>(); |
| // ignore: avoid_catching_errors |
| } on ArgumentError { |
| return null; |
| } |
| }(); |
| |
| bool canDoGC = _executeInternalCommand != null; |
| |
| void doGC() { |
| final gcNow = 'gc-now'.toNativeUtf8(); |
| _executeInternalCommand!(gcNow.cast(), nullptr); |
| calloc.free(gcNow); |
| } |
| |
| @Native<Int Function(Pointer<Void>)>( |
| isLeaf: true, |
| symbol: 'isReadableMemory', |
| assetId: 'package:objective_c/objective_c.dylib', |
| ) |
| external int _isReadableMemory(Pointer<Void> ptr); |
| |
| @Native<Uint64 Function(Pointer<Void>)>( |
| isLeaf: true, |
| symbol: 'getObjectRetainCount', |
| assetId: 'package:objective_c/objective_c.dylib', |
| ) |
| external int _getObjectRetainCount(Pointer<Void> object); |
| |
| int objectRetainCount(Pointer<ObjCObjectImpl> object) { |
| if (_isReadableMemory(object.cast()) == 0) return 0; |
| final header = object.cast<Uint64>().value; |
| |
| // package:objective_c's isValidObject function internally calls |
| // object_getClass then isValidClass. But object_getClass can occasionally |
| // crash for invalid objects. This masking logic is a simplified version of |
| // what object_getClass does internally. This is less likely to crash, but |
| // more likely to break due to ObjC runtime updates, which is a reasonable |
| // trade off to make in tests where we're explicitly calling it many times |
| // on invalid objects. In package:objective_c's case, it doesn't matter so |
| // much if isValidObject crashes, since it's a best effort attempt to give a |
| // nice stack trace before the real crash, but it would be a problem if |
| // isValidObject broke due to a runtime update. |
| // These constants are the ISA_MASK macro defined in runtime/objc-private.h. |
| const maskX64 = 0x00007ffffffffff8; |
| const maskArm = 0x0000000ffffffff8; |
| final mask = Abi.current() == Abi.macosX64 ? maskX64 : maskArm; |
| final clazz = Pointer<ObjCObjectImpl>.fromAddress(header & mask); |
| |
| if (!internal_for_testing.isValidClass(clazz)) return 0; |
| return _getObjectRetainCount(object.cast()); |
| } |
| |
| String pkgDir = findPackageRoot('objective_c').toFilePath(); |
| |
| // --------------------------------------------------------------------------- |
| // Block retain count (from util.c: getBlockRetainCount). |
| // --------------------------------------------------------------------------- |
| |
| @Native<Uint64 Function(Pointer<Void>)>( |
| isLeaf: true, |
| symbol: 'getBlockRetainCount', |
| ) |
| external int _getBlockRetainCount(Pointer<Void> block); |
| |
| int blockRetainCount(Pointer<c.ObjCBlockImpl> block) => |
| _getBlockRetainCount(block.cast()); |
| |
| // --------------------------------------------------------------------------- |
| // GC injection helpers (from gc_inject.m) — only available on macOS. |
| // --------------------------------------------------------------------------- |
| |
| @Native<Void Function()>(isLeaf: true, symbol: 'initGCInject') |
| external void initGCInject(); |
| |
| @Native<Void Function()>(isLeaf: true, symbol: 'installGCInjectSwizzle') |
| external void installGCInjectSwizzle(); |
| |
| @Native<Void Function()>(isLeaf: true, symbol: 'removeGCInjectSwizzle') |
| external void removeGCInjectSwizzle(); |
| |
| @Native<Void Function(Bool)>(isLeaf: true, symbol: 'setGCInjectActive') |
| external void setGCInjectActive(bool active); |
| |
| @Native<Bool Function()>(isLeaf: true, symbol: 'wasBlockFreedBeforeRetain') |
| external bool wasBlockFreedBeforeRetain(); |
| |
| @Native<Bool Function()>(isLeaf: true, symbol: 'gcNowAvailableFromNative') |
| external bool gcNowAvailableFromNative(); |
| |
| // Must NOT be isLeaf: the native side calls Dart_ExecuteInternalCommand which |
| // requires the Dart thread to be at a proper native-mode safepoint. |
| @Native<Void Function()>(symbol: 'callGCNowFromNative') |
| external void callGCNowFromNative(); |