1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
//! Helper functionality for passing [`String`]s through FFI boundaries.

use std::{
    ffi::{CStr, CString},
    os::raw::c_char,
    ptr,
};

use crate::api::propagate_panic;

/// Pointer to an extern function that frees the provided Dart native string.
type FreeDartNativeStringFunction = extern "C" fn(ptr::NonNull<c_char>);

/// Stores a pointer to the [`FreeDartNativeStringFunction`] extern function.
///
/// Must be initialized by Dart during FFI initialization phase.
static mut FREE_DART_NATIVE_STRING: Option<FreeDartNativeStringFunction> = None;

/// Constructs a Rust [`String`] from the provided raw C string.
///
/// # Panics
///
/// If the provided C string UTF-8 validation fails.
///
/// # Safety
///
/// Same as for [`CStr::from_ptr()`].
#[must_use]
pub unsafe fn c_str_into_string(string: ptr::NonNull<c_char>) -> String {
    CStr::from_ptr(string.as_ptr()).to_str().unwrap().to_owned()
}

/// Leaks the given [`String`] returning a raw C string that can be passed
/// through FFI boundaries.
///
/// The pointer (returned by this function) must be returned to Rust and
/// reconstituted via [`CString::from_raw()`] for proper deallocating.
///
/// # Panics
///
/// If the provided [`String`] contains an internal `0x0` byte.
#[must_use]
pub fn string_into_c_str(string: String) -> ptr::NonNull<c_char> {
    ptr::NonNull::new(CString::new(string).unwrap().into_raw()).unwrap()
}

/// Converts the provided C-string received from Dart into a Rust [`String`].
///
/// # Safety
///
/// The provided value must represent a valid C-string (`Pointer<Utf8>`
/// allocated on Dart-side).
#[must_use]
pub unsafe fn dart_string_into_rust(
    dart_string: ptr::NonNull<c_char>,
) -> String {
    let rust_string = c_str_into_string(dart_string);
    free_dart_native_string(dart_string);

    rust_string
}

/// Retakes ownership over a [`CString`] previously transferred to Dart via
/// [`CString::into_raw()`].
///
/// # Safety
///
/// Same as for [`CString::from_raw()`].
#[no_mangle]
pub unsafe extern "C" fn String_free(s: ptr::NonNull<c_char>) {
    propagate_panic(move || {
        drop(CString::from_raw(s.as_ptr()));
    });
}

/// Registers the provided [`FreeDartNativeStringFunction`] as
/// [`FREE_DART_NATIVE_STRING`].
///
/// # Safety
///
/// Must ONLY be called by Dart during FFI initialization.
#[no_mangle]
pub unsafe extern "C" fn register_free_dart_native_string(
    f: FreeDartNativeStringFunction,
) {
    FREE_DART_NATIVE_STRING = Some(f);
}

/// Calls Dart to release memory allocated for the provided native string.
///
/// Should be used when Dart cannot release memory in place, e.g when Rust calls
/// a Dart function returning a native string.
///
/// # Safety
///
/// `FREE_DART_NATIVE_STRING` function must be registered and the provided
/// pointer must be a valid native string.
pub unsafe fn free_dart_native_string(s: ptr::NonNull<c_char>) {
    FREE_DART_NATIVE_STRING.unwrap()(s);
}