blob: 25c59f5c9844b66c894f32b41cfea5f0319d0354 [file] [log] [blame]
// 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 'package:ffi/ffi.dart';
import 'package:meta/meta.dart' show internal;
import 'errors.dart';
import 'jni.dart';
import 'third_party/generated_bindings.dart';
@internal
extension ProtectedJReference on JReference {
void setAsReleased() {
_setAsReleased();
}
/// Similar to [pointer].
///
/// Detaches the finalizer so the underlying pointer will not be deleted.
JObjectPtr toPointer() {
_setAsReleased();
return _finalizable.pointer;
}
}
/// A thin wrapper around a pointer that makes it [Finalizable].
@pragma('vm:deeply-immutable')
final class _JFinalizable implements Finalizable {
final Pointer<Void> pointer;
_JFinalizable(this.pointer);
}
@pragma('vm:deeply-immutable')
abstract final class JReference implements Finalizable {
final _JFinalizable _finalizable;
JReference._(this._finalizable);
/// The underlying JNI reference.
///
/// Throws [UseAfterReleaseError] if the object is previously released.
///
/// Be careful when storing this in a variable since it might have gotten
/// released upon use.
JObjectPtr get pointer {
if (isReleased) throw UseAfterReleaseError();
return _finalizable.pointer;
}
/// Whether the underlying JNI reference is deleted or not.
bool get isReleased;
/// Whether the underlying JNI reference is `null` or not.
bool get isNull;
/// Deletes the underlying JNI reference and marks this as released.
///
/// Releasing in one isolate while using or releasing in another isolate might
/// crash in the JNI layer.
///
/// Throws [DoubleReleaseError] if this is already released.
///
/// Further uses of this object will throw [UseAfterReleaseError].
void release() {
_setAsReleased();
_deleteReference();
}
void _deleteReference();
void _setAsReleased();
}
/// A managed JNI global reference.
///
/// Uses a [NativeFinalizer] to delete the JNI global reference when finalized.
@pragma('vm:deeply-immutable')
final class JGlobalReference extends JReference {
/// The finalizable handle that deletes [_JFinalizable.pointer].
final Dart_FinalizableHandle _jobjectFinalizableHandle;
final Pointer<Bool> _isReleased;
final Pointer<Pointer<Char>> _releasedStackTracePointer;
JGlobalReference._(super.finalizable, this._jobjectFinalizableHandle,
this._isReleased, this._releasedStackTracePointer)
: super._();
factory JGlobalReference(Pointer<Void> pointer) {
final finalizable = _JFinalizable(pointer);
final isReleased = calloc<Bool>();
var releasedStackTracePointer = nullptr.cast<Pointer<Char>>();
final jobjectFinalizableHandle =
InternalJniExtension.newJObjectFinalizableHandle(
finalizable, finalizable.pointer, JObjectRefType.JNIGlobalRefType);
InternalJniExtension.newBooleanFinalizableHandle(finalizable, isReleased);
assert(() {
releasedStackTracePointer = calloc<Pointer<Char>>();
InternalJniExtension.newStackTraceFinalizableHandle(
finalizable, releasedStackTracePointer.cast());
return true;
}());
return JGlobalReference._(finalizable, jobjectFinalizableHandle, isReleased,
releasedStackTracePointer);
}
@override
bool get isNull => pointer == nullptr;
@override
JObjectPtr get pointer {
if (isReleased) {
throw UseAfterReleaseError(_releasedStackTrace);
}
return _finalizable.pointer;
}
void _appendToStackTrace(String stackTrace) {
final previousStackTrace = _releasedStackTrace ?? '';
final nativeStr = '$previousStackTrace$stackTrace'.toNativeUtf8();
if (_releasedStackTracePointer != nullptr) {
malloc.free(_releasedStackTracePointer.value);
}
_releasedStackTracePointer.value = nativeStr.cast();
}
@override
void _setAsReleased() {
if (isReleased) {
throw DoubleReleaseError(_releasedStackTrace);
}
_isReleased.value = true;
InternalJniExtension.deleteFinalizableHandle(
_jobjectFinalizableHandle, _finalizable);
assert(() {
if (Jni.captureStackTraceOnRelease) {
_appendToStackTrace('Object was released at:\n${StackTrace.current}\n');
}
return true;
}());
}
@internal
void registeredInArena() {
if (Jni.captureStackTraceOnRelease) {
_appendToStackTrace(
'Object was registered to be released by an arena at:\n'
'${StackTrace.current}\n');
}
}
String? get _releasedStackTrace {
if (_releasedStackTracePointer.value != nullptr) {
return _releasedStackTracePointer.value.cast<Utf8>().toDartString();
}
return null;
}
@override
void _deleteReference() {
Jni.env.DeleteGlobalRef(_finalizable.pointer);
}
@override
bool get isReleased => _isReleased.value;
}
final JReference jNullReference = _JNullReference();
@pragma('vm:deeply-immutable')
final class _JNullReference extends JReference {
_JNullReference() : super._(_JFinalizable(nullptr));
@override
bool get isReleased => false;
@override
void _deleteReference() {
// No need to delete `null`.
}
@override
void _setAsReleased() {
// No need to release `null`.
}
@override
bool get isNull => true;
}