// 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 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 _isReleased; final Pointer> _releasedStackTracePointer; JGlobalReference._(super.finalizable, this._jobjectFinalizableHandle, this._isReleased, this._releasedStackTracePointer) : super._(); factory JGlobalReference(Pointer pointer) { final finalizable = _JFinalizable(pointer); final isReleased = calloc(); var releasedStackTracePointer = nullptr.cast>(); final jobjectFinalizableHandle = InternalJniExtension.newJObjectFinalizableHandle( finalizable, finalizable.pointer, JObjectRefType.JNIGlobalRefType); InternalJniExtension.newBooleanFinalizableHandle(finalizable, isReleased); assert(() { releasedStackTracePointer = calloc>(); 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().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; }