Skip to content

Commit

Permalink
間違ったchar*の解放を明示的に拒否する (#500)
Browse files Browse the repository at this point in the history
  • Loading branch information
qryxip authored May 29, 2023
1 parent 768042e commit 7de38d3
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 37 deletions.
111 changes: 101 additions & 10 deletions crates/voicevox_core_c_api/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::alloc::Layout;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Debug;

use thiserror::Error;
Expand Down Expand Up @@ -232,25 +232,38 @@ impl Default for VoicevoxSynthesisOptions {
// libcのmallocで追加のアロケーションを行うことなく、`Vec<u8>`や`Vec<f32>`の内容を直接Cの世界に貸し出す。

/// Rustの世界の`Box<[impl Copy]>`をCの世界に貸し出すため、アドレスとレイアウトを管理するもの。
pub(crate) struct BufferManager {
///
/// `Mutex`による内部可変性を持ち、すべての操作は`&self`から行うことができる。
pub(crate) struct BufferManager(Mutex<BufferManagerInner>);

struct BufferManagerInner {
address_to_layout_table: BTreeMap<usize, Layout>,
owned_str_addrs: BTreeSet<usize>,
static_str_addrs: BTreeSet<usize>,
}

impl BufferManager {
pub const fn new() -> Self {
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<T: Copy>(&mut self, vec: Vec<T>) -> (*mut T, usize) {
pub fn vec_into_raw<T: Copy>(&self, vec: Vec<T>) -> (*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 = self.address_to_layout_table.insert(addr, layout).is_none();
let not_occupied = address_to_layout_table.insert(addr, layout).is_none();

assert!(not_occupied, "すでに値が入っている状態はおかしい");

Expand All @@ -262,9 +275,14 @@ impl BufferManager {
/// # Safety
///
/// - `buffer_ptr`は`vec_into_raw`で取得したものであること。
pub unsafe fn dealloc_slice<T: Copy>(&mut self, buffer_ptr: *const T) {
pub unsafe fn dealloc_slice<T: Copy>(&self, buffer_ptr: *const T) {
let BufferManagerInner {
address_to_layout_table,
..
} = &mut *self.0.lock().unwrap();

let addr = buffer_ptr as usize;
let layout = self.address_to_layout_table.remove(&addr).expect(
let layout = address_to_layout_table.remove(&addr).expect(
"解放しようとしたポインタはvoicevox_coreの管理下にありません。\
誤ったポインタであるか、二重解放になっていることが考えられます",
);
Expand All @@ -278,6 +296,53 @@ impl BufferManager {
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)]
Expand Down Expand Up @@ -320,11 +385,37 @@ mod tests {
#[should_panic(
expected = "解放しようとしたポインタはvoicevox_coreの管理下にありません。誤ったポインタであるか、二重解放になっていることが考えられます"
)]
fn buffer_manager_denies_unknown_ptr() {
let mut buffer_manager = BufferManager::new();
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") };
}
}
53 changes: 26 additions & 27 deletions crates/voicevox_core_c_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ pub(crate) fn lock_internal() -> MutexGuard<'static, Internal> {
}

// C_APIに渡すために,VecやCStringのサイズを記憶しながら生ポインタを得るためのマネージャ
static BUFFER_MANAGER: Mutex<BufferManager> = Mutex::new(BufferManager::new());
static BUFFER_MANAGER: BufferManager = BufferManager::new();

/*
* Cの関数として公開するための型や関数を定義するこれらの実装はvoicevox_core/publish.rsに定義してある対応する関数にある
Expand Down Expand Up @@ -131,7 +131,7 @@ static VOICEVOX_VERSION: once_cell::sync::Lazy<CString> =
/// @return SemVerでフォーマットされたバージョン
#[no_mangle]
pub extern "C" fn voicevox_get_version() -> *const c_char {
VOICEVOX_VERSION.as_ptr()
BUFFER_MANAGER.memorize_static_str(VOICEVOX_VERSION.as_ptr())
}

/// モデルを読み込む
Expand Down Expand Up @@ -166,14 +166,14 @@ pub extern "C" fn voicevox_finalize() {
/// @return メタ情報のjson文字列
#[no_mangle]
pub extern "C" fn voicevox_get_metas_json() -> *const c_char {
lock_internal().get_metas_json().as_ptr()
BUFFER_MANAGER.memorize_static_str(lock_internal().get_metas_json().as_ptr())
}

/// サポートデバイス情報をjsonで取得する
/// @return サポートデバイス情報のjson文字列
#[no_mangle]
pub extern "C" fn voicevox_get_supported_devices_json() -> *const c_char {
lock_internal().get_supported_devices_json().as_ptr()
BUFFER_MANAGER.memorize_static_str(lock_internal().get_supported_devices_json().as_ptr())
}

/// 音素ごとの長さを推論する
Expand Down Expand Up @@ -201,7 +201,7 @@ pub unsafe extern "C" fn voicevox_predict_duration(
std::slice::from_raw_parts_mut(phoneme_vector, length),
speaker_id,
)?;
let (ptr, size) = BUFFER_MANAGER.lock().unwrap().vec_into_raw(output_vec);
let (ptr, size) = BUFFER_MANAGER.vec_into_raw(output_vec);

output_predict_duration_data_length.write(size);
output_predict_duration_data.write(ptr);
Expand All @@ -217,10 +217,7 @@ pub unsafe extern "C" fn voicevox_predict_duration(
/// @param predict_duration_data voicevox_predict_durationで確保されたポインタであり、かつ呼び出し側でバッファの変更が行われていないこと
#[no_mangle]
pub unsafe extern "C" fn voicevox_predict_duration_data_free(predict_duration_data: *mut f32) {
BUFFER_MANAGER
.lock()
.unwrap()
.dealloc_slice(predict_duration_data as *const f32);
BUFFER_MANAGER.dealloc_slice(predict_duration_data as *const f32);
}

/// モーラごとのF0を推論する
Expand Down Expand Up @@ -269,7 +266,7 @@ pub unsafe extern "C" fn voicevox_predict_intonation(
std::slice::from_raw_parts(end_accent_phrase_vector, length),
speaker_id,
)?;
let (ptr, len) = BUFFER_MANAGER.lock().unwrap().vec_into_raw(output_vec);
let (ptr, len) = BUFFER_MANAGER.vec_into_raw(output_vec);
output_predict_intonation_data.write(ptr);
output_predict_intonation_data_length.write(len);

Expand All @@ -285,10 +282,7 @@ pub unsafe extern "C" fn voicevox_predict_intonation(
/// @param predict_duration_data voicevox_predict_intonationで確保された,ポインタでありかつ,呼び出し側でバッファの変更を行われていないこと.
#[no_mangle]
pub unsafe extern "C" fn voicevox_predict_intonation_data_free(predict_intonation_data: *mut f32) {
BUFFER_MANAGER
.lock()
.unwrap()
.dealloc_slice(predict_intonation_data as *const f32);
BUFFER_MANAGER.dealloc_slice(predict_intonation_data as *const f32);
}

/// decodeを実行する
Expand Down Expand Up @@ -324,7 +318,7 @@ pub unsafe extern "C" fn voicevox_decode(
std::slice::from_raw_parts(phoneme_vector, phoneme_size * length),
speaker_id,
)?;
let (ptr, len) = BUFFER_MANAGER.lock().unwrap().vec_into_raw(output_vec);
let (ptr, len) = BUFFER_MANAGER.vec_into_raw(output_vec);
output_decode_data.write(ptr);
output_decode_data_length.write(len);
Ok(())
Expand All @@ -338,7 +332,7 @@ pub unsafe extern "C" fn voicevox_decode(
/// @param decode_data voicevox_decodeで確保されたポインタであり、かつ呼び出し側でバッファの変更を行われていないこと
#[no_mangle]
pub unsafe extern "C" fn voicevox_decode_data_free(decode_data: *mut f32) {
BUFFER_MANAGER.lock().unwrap().dealloc_slice(decode_data);
BUFFER_MANAGER.dealloc_slice(decode_data);
}

/// Audio query のオプション
Expand Down Expand Up @@ -376,7 +370,7 @@ pub unsafe extern "C" fn voicevox_audio_query(
let text = CStr::from_ptr(text);
let audio_query = create_audio_query(text, speaker_id, Internal::audio_query, options)?;

output_audio_query_json.write(audio_query.into_raw());
output_audio_query_json.write(BUFFER_MANAGER.c_string_into_raw(audio_query));
Ok(())
})())
}
Expand Down Expand Up @@ -417,7 +411,7 @@ pub unsafe extern "C" fn voicevox_accent_phrases(
let accent_phrases =
create_accent_phrases(text, speaker_id, Internal::accent_phrases, options)?;

output_accent_phrases_json.write(accent_phrases.into_raw());
output_accent_phrases_json.write(BUFFER_MANAGER.c_string_into_raw(accent_phrases));
Ok(())
})())
}
Expand Down Expand Up @@ -447,7 +441,8 @@ pub unsafe extern "C" fn voicevox_mora_length(
let accent_phrases_with_mora_length =
modify_accent_phrases(&accent_phrases, speaker_id, Internal::mora_length)?;

output_accent_phrases_json.write(accent_phrases_with_mora_length.into_raw());
output_accent_phrases_json
.write(BUFFER_MANAGER.c_string_into_raw(accent_phrases_with_mora_length));
Ok(())
})())
}
Expand Down Expand Up @@ -477,7 +472,8 @@ pub unsafe extern "C" fn voicevox_mora_pitch(
let accent_phrases_with_mora_pitch =
modify_accent_phrases(&accent_phrases, speaker_id, Internal::mora_pitch)?;

output_accent_phrases_json.write(accent_phrases_with_mora_pitch.into_raw());
output_accent_phrases_json
.write(BUFFER_MANAGER.c_string_into_raw(accent_phrases_with_mora_pitch));
Ok(())
})())
}
Expand Down Expand Up @@ -507,7 +503,8 @@ pub unsafe extern "C" fn voicevox_mora_data(
let accent_phrases_with_mora_data =
modify_accent_phrases(&accent_phrases, speaker_id, Internal::mora_data)?;

output_accent_phrases_json.write(accent_phrases_with_mora_data.into_raw());
output_accent_phrases_json
.write(BUFFER_MANAGER.c_string_into_raw(accent_phrases_with_mora_data));
Ok(())
})())
}
Expand Down Expand Up @@ -553,7 +550,7 @@ pub unsafe extern "C" fn voicevox_synthesis(
&serde_json::from_str(audio_query_json).map_err(CApiError::InvalidAudioQuery)?;
let wav = lock_internal().synthesis(audio_query, speaker_id, options.into())?;

let (ptr, len) = BUFFER_MANAGER.lock().unwrap().vec_into_raw(wav);
let (ptr, len) = BUFFER_MANAGER.vec_into_raw(wav);
output_wav.write(ptr);
output_wav_length.write(len);

Expand Down Expand Up @@ -599,7 +596,7 @@ pub unsafe extern "C" fn voicevox_tts(
into_result_code_with_error((|| {
let text = ensure_utf8(CStr::from_ptr(text))?;
let output = lock_internal().tts(text, speaker_id, options.into())?;
let (ptr, size) = BUFFER_MANAGER.lock().unwrap().vec_into_raw(output);
let (ptr, size) = BUFFER_MANAGER.vec_into_raw(output);
output_wav.write(ptr);
output_wav_length.write(size);
Ok(())
Expand All @@ -613,7 +610,7 @@ pub unsafe extern "C" fn voicevox_tts(
/// @param voicevox_audio_query で確保されたポインタであり、かつ呼び出し側でバッファの変更を行われていないこと
#[no_mangle]
pub unsafe extern "C" fn voicevox_audio_query_json_free(audio_query_json: *mut c_char) {
drop(CString::from_raw(audio_query_json));
BUFFER_MANAGER.dealloc_c_string(audio_query_json);
}

/// jsonフォーマットされた AccnetPhrase データのメモリを解放する
Expand All @@ -623,7 +620,7 @@ pub unsafe extern "C" fn voicevox_audio_query_json_free(audio_query_json: *mut c
/// @param voicevox_accent_phrases で確保されたポインタであり、かつ呼び出し側でバッファの変更を行われていないこと
#[no_mangle]
pub unsafe extern "C" fn voicevox_accent_phrases_json_free(accented_phrase_json: *mut c_char) {
drop(CString::from_raw(accented_phrase_json));
BUFFER_MANAGER.dealloc_c_string(accented_phrase_json);
}

/// wav データのメモリを解放する
Expand All @@ -633,7 +630,7 @@ pub unsafe extern "C" fn voicevox_accent_phrases_json_free(accented_phrase_json:
/// @param wav voicevox_tts,voicevox_synthesis で確保されたポインタであり、かつ呼び出し側でバッファの変更を行われていないこと
#[no_mangle]
pub unsafe extern "C" fn voicevox_wav_free(wav: *mut u8) {
BUFFER_MANAGER.lock().unwrap().dealloc_slice(wav);
BUFFER_MANAGER.dealloc_slice(wav);
}

/// エラー結果をメッセージに変換する
Expand All @@ -643,7 +640,9 @@ 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 {
voicevox_core::error_result_to_message(result_code).as_ptr() as *const c_char
BUFFER_MANAGER.memorize_static_str(
voicevox_core::error_result_to_message(result_code).as_ptr() as *const c_char
)
}

#[cfg(test)]
Expand Down

0 comments on commit 7de38d3

Please sign in to comment.