diff --git a/crates/voicevox_core_c_api/src/drop_check.rs b/crates/voicevox_core_c_api/src/drop_check.rs new file mode 100644 index 000000000..546739dbd --- /dev/null +++ b/crates/voicevox_core_c_api/src/drop_check.rs @@ -0,0 +1,126 @@ +use std::{ + collections::BTreeSet, + ffi::{c_char, CStr, CString}, + sync::Mutex, +}; + +/// dropして良い`*mut c_char`を把握し、チェックする。 +/// +/// `Mutex`による内部可変性を持ち、すべての操作は共有参照から行うことができる。 +/// +/// # Motivation +/// +/// `CString`は`Box`と同様Cの世界でもポインタ一つで実体を表すことができるため、こちら側 +/// で管理すべきものは本来無い。しかしながら本クレートが提供するAPIには「解放不要」な文字列を返すも +/// のが含まれている。ユーザーが誤ってそのような文字列を解放するのは未定義動作 (undefined behavior) +/// であるため、綺麗にSEGVするとも限らない。`once_cell::sync::Lazy`由来の文字列の場合、最悪解放が成 +/// 功してしまう。 +/// +/// この構造体はCの世界から帰ってきた`*mut c_char`を`CString`としてdropする際、それが本当にこちら側 +/// が送り出した`CString`かどうかをチェックする。 +/// +/// Cの世界に`CString`を送り出す前に`whitelist`を通し、戻って来た`*mut c_char`を`CString`にしてdrop +/// する前に`check`に通す。 +pub(crate) static C_STRING_DROP_CHECKER: CStringDropChecker = CStringDropChecker::new(); + +pub(crate) struct CStringDropChecker(Mutex); + +struct Inner { + owned_str_addrs: BTreeSet, + static_str_addrs: BTreeSet, +} + +impl CStringDropChecker { + const fn new() -> Self { + Self(Mutex::new(Inner { + owned_str_addrs: BTreeSet::new(), + static_str_addrs: BTreeSet::new(), + })) + } + + /// `CString`をホワイトリストに追加する。 + /// + /// Cの世界に`CString`を送り出す前にこの関数を挟む。 + pub(crate) fn whitelist(&self, s: CString) -> CString { + let Inner { + owned_str_addrs, .. + } = &mut *self.0.lock().unwrap(); + + let duplicated = !owned_str_addrs.insert(s.as_ptr() as usize); + assert!(!duplicated, "duplicated"); + s + } + + /// `&'static CStr`をブラックリストに追加する。 + /// + /// Cの世界に`Lazy`由来の`&'static CStr`を送り出す前にこの関数を挟む。 + /// + /// ホワイトリストとブラックリストは重複しないと考えてよく、ブラックリストはエラーメセージの制御 + /// のためのみに使われる。 + pub(crate) fn blacklist(&self, s: &'static CStr) -> &'static CStr { + let Inner { + static_str_addrs, .. + } = &mut *self.0.lock().unwrap(); + + static_str_addrs.insert(s.as_ptr() as usize); + s + } + + /// `*mut c_char`が`whitelist`を通ったものかどうかチェックする。 + /// + /// # Panics + /// + /// `ptr`が`Self::whitelist`を経由したものではないならパニックする。 + pub(crate) fn check(&self, ptr: *mut c_char) -> *mut c_char { + let Inner { + owned_str_addrs, + static_str_addrs, + .. + } = &mut *self.0.lock().unwrap(); + + let addr = ptr as usize; + if !owned_str_addrs.remove(&addr) { + if static_str_addrs.contains(&addr) { + panic!( + "解放しようとしたポインタはvoicevox_core管理下のものですが、\ + voicevox_coreがアンロードされるまで永続する文字列に対するものです。\ + 解放することはできません", + ) + } + panic!( + "解放しようとしたポインタはvoicevox_coreの管理下にありません。\ + 誤ったポインタであるか、二重解放になっていることが考えられます", + ); + } + ptr + } +} + +#[cfg(test)] +mod tests { + use std::ffi::{c_char, CStr}; + + use super::CStringDropChecker; + + #[test] + #[should_panic( + expected = "解放しようとしたポインタはvoicevox_coreの管理下にありません。誤ったポインタであるか、二重解放になっていることが考えられます" + )] + fn it_denies_unknown_char_ptr() { + let checker = CStringDropChecker::new(); + let s = CStr::from_bytes_with_nul(b"\0").unwrap().to_owned(); + checker.check(s.into_raw()); + } + + #[test] + #[should_panic( + expected = "解放しようとしたポインタはvoicevox_core管理下のものですが、voicevox_coreがアンロードされるまで永続する文字列に対するものです。解放することはできません" + )] + fn it_denies_known_static_char_ptr() { + let checker = CStringDropChecker::new(); + checker.blacklist(STATIC); + checker.check(STATIC.as_ptr() as *mut c_char); + + static STATIC: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") }; + } +} diff --git a/crates/voicevox_core_c_api/src/helpers.rs b/crates/voicevox_core_c_api/src/helpers.rs index 406ac15c7..c62fdeb83 100644 --- a/crates/voicevox_core_c_api/src/helpers.rs +++ b/crates/voicevox_core_c_api/src/helpers.rs @@ -1,5 +1,3 @@ -use std::alloc::Layout; -use std::collections::{BTreeMap, BTreeSet}; use std::fmt::Debug; use const_default::ConstDefault; @@ -177,194 +175,3 @@ impl ConstDefault for VoicevoxSynthesisOptions { } }; } - -// libcのmallocで追加のアロケーションを行うことなく、`Vec`や`Vec`の内容を直接Cの世界に貸し出す。 - -/// Rustの世界の`Box<[impl Copy]>`をCの世界に貸し出すため、アドレスとレイアウトを管理するもの。 -/// -/// `Mutex`による内部可変性を持ち、すべての操作は`&self`から行うことができる。 -pub(crate) struct BufferManager(Mutex); - -struct BufferManagerInner { - address_to_layout_table: BTreeMap, - owned_str_addrs: BTreeSet, - static_str_addrs: BTreeSet, -} - -impl BufferManager { - pub const fn new() -> Self { - Self(Mutex::new(BufferManagerInner { - address_to_layout_table: BTreeMap::new(), - owned_str_addrs: BTreeSet::new(), - static_str_addrs: BTreeSet::new(), - })) - } - - pub fn vec_into_raw(&self, vec: Vec) -> (*mut T, usize) { - let BufferManagerInner { - address_to_layout_table, - .. - } = &mut *self.0.lock().unwrap(); - - let slice = Box::leak(vec.into_boxed_slice()); - let layout = Layout::for_value(slice); - let len = slice.len(); - let ptr = slice.as_mut_ptr(); - let addr = ptr as usize; - - let not_occupied = address_to_layout_table.insert(addr, layout).is_none(); - - assert!(not_occupied, "すでに値が入っている状態はおかしい"); - - (ptr, len) - } - - /// `vec_into_raw`でC API利用側に貸し出したポインタに対し、デアロケートする。 - /// - /// # Safety - /// - /// - `buffer_ptr`は`vec_into_raw`で取得したものであること。 - pub unsafe fn dealloc_slice(&self, buffer_ptr: *const T) { - let BufferManagerInner { - address_to_layout_table, - .. - } = &mut *self.0.lock().unwrap(); - - let addr = buffer_ptr as usize; - let layout = address_to_layout_table.remove(&addr).expect( - "解放しようとしたポインタはvoicevox_coreの管理下にありません。\ - 誤ったポインタであるか、二重解放になっていることが考えられます", - ); - - if layout.size() > 0 { - // `T: Copy`より、`T: !Drop`であるため`drop_in_place`は不要 - - // SAFETY: - // - `addr`と`layout`は対応したものである - // - `layout.size() > 0`より、`addr`はダングリングではない有効なポインタである - std::alloc::dealloc(addr as *mut u8, layout); - } - } - - pub fn c_string_into_raw(&self, s: CString) -> *mut c_char { - let BufferManagerInner { - owned_str_addrs, .. - } = &mut *self.0.lock().unwrap(); - - let ptr = s.into_raw(); - owned_str_addrs.insert(ptr as _); - ptr - } - - /// `c_string_into_raw`でC API利用側に貸し出したポインタに対し、デアロケートする。 - /// - /// # Safety - /// - /// - `ptr`は`c_string_into_raw`で取得したものであること。 - pub unsafe fn dealloc_c_string(&self, ptr: *mut c_char) { - let BufferManagerInner { - owned_str_addrs, - static_str_addrs, - .. - } = &mut *self.0.lock().unwrap(); - - if !owned_str_addrs.remove(&(ptr as _)) { - if static_str_addrs.contains(&(ptr as _)) { - panic!( - "解放しようとしたポインタはvoicevox_core管理下のものですが、\ - voicevox_coreがアンロードされるまで永続する文字列に対するものです。\ - 解放することはできません", - ) - } - panic!( - "解放しようとしたポインタはvoicevox_coreの管理下にありません。\ - 誤ったポインタであるか、二重解放になっていることが考えられます", - ); - } - drop(CString::from_raw(ptr)); - } - - pub fn memorize_static_str(&self, ptr: *const c_char) -> *const c_char { - let BufferManagerInner { - static_str_addrs, .. - } = &mut *self.0.lock().unwrap(); - - static_str_addrs.insert(ptr as _); - ptr - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn buffer_manager_works() { - let mut buffer_manager = BufferManager::new(); - - rent_and_dealloc(&mut buffer_manager, vec::<()>(0, &[])); - rent_and_dealloc(&mut buffer_manager, vec(0, &[()])); - rent_and_dealloc(&mut buffer_manager, vec(2, &[()])); - - rent_and_dealloc(&mut buffer_manager, vec::(0, &[])); - rent_and_dealloc(&mut buffer_manager, vec(0, &[0u8])); - rent_and_dealloc(&mut buffer_manager, vec(2, &[0u8])); - - rent_and_dealloc(&mut buffer_manager, vec::(0, &[])); - rent_and_dealloc(&mut buffer_manager, vec(0, &[0f32])); - rent_and_dealloc(&mut buffer_manager, vec(2, &[0f32])); - - fn rent_and_dealloc(buffer_manager: &mut BufferManager, vec: Vec) { - let expected_len = vec.len(); - let (ptr, len) = buffer_manager.vec_into_raw(vec); - assert_eq!(expected_len, len); - unsafe { - buffer_manager.dealloc_slice(ptr); - } - } - - fn vec(initial_cap: usize, elems: &[T]) -> Vec { - let mut vec = Vec::with_capacity(initial_cap); - vec.extend_from_slice(elems); - vec - } - } - - #[test] - #[should_panic( - expected = "解放しようとしたポインタはvoicevox_coreの管理下にありません。誤ったポインタであるか、二重解放になっていることが考えられます" - )] - fn buffer_manager_denies_unknown_slice_ptr() { - let buffer_manager = BufferManager::new(); - unsafe { - let x = 42; - buffer_manager.dealloc_slice(&x as *const i32); - } - } - - #[test] - #[should_panic( - expected = "解放しようとしたポインタはvoicevox_coreの管理下にありません。誤ったポインタであるか、二重解放になっていることが考えられます" - )] - fn buffer_manager_denies_unknown_char_ptr() { - let buffer_manager = BufferManager::new(); - unsafe { - let s = CStr::from_bytes_with_nul(b"\0").unwrap().to_owned(); - buffer_manager.dealloc_c_string(s.into_raw()); - } - } - - #[test] - #[should_panic( - expected = "解放しようとしたポインタはvoicevox_core管理下のものですが、voicevox_coreがアンロードされるまで永続する文字列に対するものです。解放することはできません" - )] - fn buffer_manager_denies_known_static_char_ptr() { - let buffer_manager = BufferManager::new(); - unsafe { - buffer_manager.memorize_static_str(STATIC.as_ptr() as _); - buffer_manager.dealloc_c_string(STATIC.as_ptr() as *mut c_char); - } - - static STATIC: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") }; - } -} diff --git a/crates/voicevox_core_c_api/src/lib.rs b/crates/voicevox_core_c_api/src/lib.rs index 052b5e490..334cc03f4 100644 --- a/crates/voicevox_core_c_api/src/lib.rs +++ b/crates/voicevox_core_c_api/src/lib.rs @@ -1,8 +1,12 @@ mod c_impls; /// cbindgen:ignore mod compatible_engine; +mod drop_check; mod helpers; +mod slice_owner; +use self::drop_check::C_STRING_DROP_CHECKER; use self::helpers::*; +use self::slice_owner::U8_SLICE_OWNER; use chrono::SecondsFormat; use const_default::ConstDefault; use derive_getters::Getters; @@ -65,9 +69,6 @@ static RUNTIME: Lazy = Lazy::new(|| { Runtime::new().unwrap() }); -// C_APIに渡すために,VecやCStringのサイズを記憶しながら生ポインタを得るためのマネージャ -static BUFFER_MANAGER: BufferManager = BufferManager::new(); - /* * Cの関数として公開するための型や関数を定義するこれらの実装はvoicevox_core/publish.rsに定義してある対応する関数にある * この関数ではvoicevox_core/publish.rsにある対応する関数の呼び出しと、その戻り値をCの形式に変換する処理のみとする @@ -356,7 +357,11 @@ pub extern "C" fn voicevox_create_supported_devices_json( into_result_code_with_error((|| { let supported_devices = CString::new(SupportedDevices::create()?.to_json().to_string()).unwrap(); - output_supported_devices_json.write(BUFFER_MANAGER.c_string_into_raw(supported_devices)); + output_supported_devices_json.write( + C_STRING_DROP_CHECKER + .whitelist(supported_devices) + .into_raw(), + ); Ok(()) })()) } @@ -401,7 +406,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_audio_query( ))?; let audio_query = CString::new(audio_query_model_to_json(&audio_query)) .expect("should not contain '\\0'"); - output_audio_query_json.write(BUFFER_MANAGER.c_string_into_raw(audio_query)); + output_audio_query_json.write(C_STRING_DROP_CHECKER.whitelist(audio_query).into_raw()); Ok(()) })()) } @@ -444,7 +449,8 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_accent_phrases( ))?; let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases)) .expect("should not contain '\\0'"); - output_accent_phrases_json.write(BUFFER_MANAGER.c_string_into_raw(accent_phrases)); + output_accent_phrases_json + .write(C_STRING_DROP_CHECKER.whitelist(accent_phrases).into_raw()); Ok(()) })()) } @@ -476,7 +482,8 @@ pub unsafe extern "C" fn voicevox_synthesizer_replace_mora_data( )?; let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases)) .expect("should not contain '\\0'"); - output_accent_phrases_json.write(BUFFER_MANAGER.c_string_into_raw(accent_phrases)); + output_accent_phrases_json + .write(C_STRING_DROP_CHECKER.whitelist(accent_phrases).into_raw()); Ok(()) })()) } @@ -508,7 +515,8 @@ pub unsafe extern "C" fn voicevox_synthesizer_replace_phoneme_length( )?; let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases)) .expect("should not contain '\\0'"); - output_accent_phrases_json.write(BUFFER_MANAGER.c_string_into_raw(accent_phrases)); + output_accent_phrases_json + .write(C_STRING_DROP_CHECKER.whitelist(accent_phrases).into_raw()); Ok(()) })()) } @@ -540,7 +548,8 @@ pub unsafe extern "C" fn voicevox_synthesizer_replace_mora_pitch( )?; let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases)) .expect("should not contain '\\0'"); - output_accent_phrases_json.write(BUFFER_MANAGER.c_string_into_raw(accent_phrases)); + output_accent_phrases_json + .write(C_STRING_DROP_CHECKER.whitelist(accent_phrases).into_raw()); Ok(()) })()) } @@ -588,9 +597,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_synthesis( StyleId::new(style_id), &SynthesisOptions::from(options), ))?; - let (ptr, len) = BUFFER_MANAGER.vec_into_raw(wav); - output_wav.write(ptr); - output_wav_length.write(len); + U8_SLICE_OWNER.own_and_lend(wav, output_wav, output_wav_length); Ok(()) })()) } @@ -636,9 +643,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_tts( StyleId::new(style_id), &TtsOptions::from(options), ))?; - let (ptr, size) = BUFFER_MANAGER.vec_into_raw(output); - output_wav.write(ptr); - output_wav_length.write(size); + U8_SLICE_OWNER.own_and_lend(output, output_wav, output_wav_length); Ok(()) })()) } @@ -650,7 +655,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_tts( /// @param voicevox_audio_query で確保されたポインタであり、かつ呼び出し側でバッファの変更を行われていないこと #[no_mangle] pub unsafe extern "C" fn voicevox_json_free(json: *mut c_char) { - BUFFER_MANAGER.dealloc_c_string(json); + drop(CString::from_raw(C_STRING_DROP_CHECKER.check(json))); } /// wav データのメモリを解放する @@ -659,8 +664,8 @@ pub unsafe extern "C" fn voicevox_json_free(json: *mut c_char) { /// # Safety /// @param wav voicevox_tts,voicevox_synthesis で確保されたポインタであり、かつ呼び出し側でバッファの変更を行われていないこと #[no_mangle] -pub unsafe extern "C" fn voicevox_wav_free(wav: *mut u8) { - BUFFER_MANAGER.dealloc_slice(wav); +pub extern "C" fn voicevox_wav_free(wav: *mut u8) { + U8_SLICE_OWNER.drop_for(wav); } /// エラー結果をメッセージに変換する @@ -670,9 +675,12 @@ pub unsafe extern "C" fn voicevox_wav_free(wav: *mut u8) { pub extern "C" fn voicevox_error_result_to_message( result_code: VoicevoxResultCode, ) -> *const c_char { - BUFFER_MANAGER.memorize_static_str( - voicevox_core::result_code::error_result_to_message(result_code).as_ptr() as *const c_char, + let message = CStr::from_bytes_with_nul( + voicevox_core::result_code::error_result_to_message(result_code).as_ref(), ) + .expect("`error_result_to_message`が返す文字列はヌル終端であるはずである"); + + C_STRING_DROP_CHECKER.blacklist(message).as_ptr() } #[cfg(test)] diff --git a/crates/voicevox_core_c_api/src/slice_owner.rs b/crates/voicevox_core_c_api/src/slice_owner.rs new file mode 100644 index 000000000..8998be7be --- /dev/null +++ b/crates/voicevox_core_c_api/src/slice_owner.rs @@ -0,0 +1,113 @@ +use std::{cell::UnsafeCell, collections::BTreeMap, mem::MaybeUninit, sync::Mutex}; + +/// Cの世界に貸し出す`[u8]`の所有者(owner)。 +/// +/// `Mutex`による内部可変性を持ち、すべての操作は共有参照から行うことができる。 +/// +/// # Motivation +/// +/// 本クレートが提供するAPIとして、バイト列の生成(create)とその解放(free)がある。APIとしては"生成 +/// "時に`Box<[u8]>`のownershipがC側に渡され、"解放"時にはそのownershipがRust側に返されるといった形 +/// となる。 +/// +/// しかし実装としては`Box`の場合とは異なり、何かしらの情報をRust側で保持し続けなくては +/// ならない。実態としてはRust側がバッファの所有者(owner)であり続け、C側にはその参照が渡される形にな +/// る。この構造体はその"所有者"であり、実際にRustのオブジェクトを保持し続ける。 +pub(crate) static U8_SLICE_OWNER: SliceOwner = SliceOwner::new(); + +pub(crate) struct SliceOwner { + slices: Mutex>>>, +} + +impl SliceOwner { + const fn new() -> Self { + Self { + slices: Mutex::new(BTreeMap::new()), + } + } + + /// `Box<[T]>`を所有し、その先頭ポインタと長さを参照としてC API利用者に与える。 + pub(crate) fn own_and_lend( + &self, + slice: impl Into>, + out_ptr: &mut MaybeUninit<*mut T>, + out_len: &mut MaybeUninit, + ) { + let mut slices = self.slices.lock().unwrap(); + + let slice = slice.into(); + let ptr = slice.as_ptr() as *mut T; + let len = slice.len(); + + let duplicated = slices.insert(ptr as usize, slice.into()).is_some(); + assert!(!duplicated, "duplicated"); + + out_ptr.write(ptr); + out_len.write(len); + } + + /// `own_and_lend`でC API利用者に貸し出したポインタに対応する`Box<[u8]>`をデストラクトする。 + /// + /// # Panics + /// + /// `ptr`が`own_and_lend`で貸し出されたポインタではないとき、パニックする。 + pub(crate) fn drop_for(&self, ptr: *mut T) { + let mut slices = self.slices.lock().unwrap(); + + slices.remove(&(ptr as usize)).expect( + "解放しようとしたポインタはvoicevox_coreの管理下にありません。\ + 誤ったポインタであるか、二重解放になっていることが考えられます", + ); + } +} + +#[cfg(test)] +mod tests { + use std::mem::MaybeUninit; + + use super::SliceOwner; + + #[test] + fn it_works() { + lend_and_delete(vec::<()>(0, &[])); + lend_and_delete(vec(0, &[()])); + lend_and_delete(vec(2, &[()])); + + lend_and_delete(vec::(0, &[])); + lend_and_delete(vec(0, &[0u8])); + lend_and_delete(vec(2, &[0u8])); + + lend_and_delete(vec::(0, &[])); + lend_and_delete(vec(0, &[0f32])); + lend_and_delete(vec(2, &[0f32])); + + fn lend_and_delete(vec: Vec) { + let owner = SliceOwner::::new(); + let expected_len = vec.len(); + let (ptr, len) = unsafe { + let mut ptr = MaybeUninit::uninit(); + let mut len = MaybeUninit::uninit(); + owner.own_and_lend(vec, &mut ptr, &mut len); + (ptr.assume_init(), len.assume_init()) + }; + assert_eq!(expected_len, len); + owner.drop_for(ptr); + } + + fn vec(initial_cap: usize, elems: &[T]) -> Vec { + let mut vec = Vec::with_capacity(initial_cap); + vec.extend_from_slice(elems); + vec + } + } + + #[test] + #[should_panic( + expected = "解放しようとしたポインタはvoicevox_coreの管理下にありません。誤ったポインタであるか、二重解放になっていることが考えられます" + )] + fn it_denies_unknown_ptr() { + let owner = SliceOwner::::new(); + let x = 42; + owner.drop_for(&x as *const i32 as *mut i32); + } +}