blob: cbe85342fe514133391abd499f789a74ce2d3131 [file] [log] [blame] [edit]
// 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 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
import 'package:ffigen/ffigen.dart';
import 'package:objective_c/objective_c.dart';
import 'package:objective_c/src/internal.dart' as internal_for_testing
show isValidClass, isValidBlock;
import 'package:path/path.dart' as p;
import '../test_utils.dart';
void generateBindingsForCoverage(String testName) {
// The ObjC test bindings are generated in setup.dart (see #362), which means
// that the ObjC related bits of ffigen are missed by test coverage. So this
// function just regenerates those bindings. It doesn't test anything except
// that the generation succeeded, by asserting the file exists.
final path = p.join('test', 'native_objc_test', '${testName}_config.yaml');
final config = testConfig(File(path).readAsStringSync(), filename: path);
final library = parse(config);
final file = File(p.join('test', 'debug_generated', '${testName}_test.dart'));
library.generateFile(file);
assert(file.existsSync());
file.delete();
}
@Native<Void Function(Pointer<Char>, Pointer<Void>)>(
symbol: 'Dart_ExecuteInternalCommand')
external void _executeInternalCommand(Pointer<Char> cmd, Pointer<Void> arg);
void doGC() {
final gcNow = "gc-now".toNativeUtf8();
_executeInternalCommand(gcNow.cast(), nullptr);
calloc.free(gcNow);
}
@Native<Bool Function(Pointer<Void>)>(isLeaf: true, symbol: 'isReadableMemory')
external bool _isReadableMemory(Pointer<Void> ptr);
@Native<Uint64 Function(Pointer<Void>)>(
isLeaf: true, symbol: 'getBlockRetainCount')
external int _getBlockRetainCount(Pointer<Void> block);
int blockRetainCount(Pointer<ObjCBlock> block) {
if (!_isReadableMemory(block.cast())) return 0;
if (!internal_for_testing.isValidBlock(block)) return 0;
return _getBlockRetainCount(block.cast());
}
@Native<Uint64 Function(Pointer<Void>)>(
isLeaf: true, symbol: 'getObjectRetainCount')
external int _getObjectRetainCount(Pointer<Void> object);
int objectRetainCount(Pointer<ObjCObject> object) {
if (!_isReadableMemory(object.cast())) 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 = 0x00000001fffffff8;
final mask = Abi.current() == Abi.macosX64 ? maskX64 : maskArm;
final clazz = Pointer<ObjCObject>.fromAddress(header & mask);
if (!internal_for_testing.isValidClass(clazz)) return 0;
return _getObjectRetainCount(object.cast());
}