Skip to content

Commit

Permalink
add: C APIの#[repr(Rust)]なものへのアクセスをすべて安全にする (VOICEVOX#849)
Browse files Browse the repository at this point in the history
C APIにおいて、Rust APIのオブジェクトそのものの代わりにトークンのような
1-bit長の構造体ユーザーに渡すようにすることで、次のことを実現する。

1. "delete"時に対象オブジェクトに対するアクセスがあった場合、アクセスが
    終わるまで待つ
2. 次のユーザー操作に対するセーフティネットを張り、パニックするようにす
    る
    1. "delete"後に他の通常のメソッド関数の利用を試みる
    2. "delete"後に"delete"を試みる
    3. そもそもオブジェクトとして変なダングリングポインタが渡される

ドキュメントとしてのsafety requirementsもあわせて緩和する。

Resolves VOICEVOX#836.
  • Loading branch information
qryxip authored Oct 8, 2024
1 parent a55b013 commit f2e6b60
Show file tree
Hide file tree
Showing 13 changed files with 645 additions and 244 deletions.
24 changes: 23 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@ ndarray-stats = "0.5.1"
octocrab = { version = "0.19.0", default-features = false }
once_cell = "1.20.1"
ouroboros = "0.18.4"
parking_lot = "0.12.1"
parse-display = "0.8.2"
pollster = "0.3.0"
predicates = "3.1.2"
pretty_assertions = "1.4.1"
proc-macro2 = "1.0.86"
pyo3 = "0.20.3"
Expand Down
6 changes: 5 additions & 1 deletion crates/voicevox_core_c_api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ chrono = { workspace = true, default-features = false, features = ["clock"] }
colorchoice.workspace = true
const_format.workspace = true
cstr.workspace = true
derive-getters.workspace = true
duplicate.workspace = true
easy-ext.workspace = true
educe.workspace = true
itertools.workspace = true
libc.workspace = true
parking_lot = { workspace = true, features = ["arc_lock"] }
process_path.workspace = true
ref-cast.workspace = true
serde_json = { workspace = true, features = ["preserve_order"] }
Expand All @@ -45,10 +47,12 @@ clap = { workspace = true, features = ["derive"] }
duct.workspace = true
easy-ext.workspace = true
inventory.workspace = true
indexmap = { workspace = true, features = ["serde"] }
libloading.workspace = true
libtest-mimic.workspace = true
ndarray.workspace = true
ndarray-stats.workspace = true
predicates.workspace = true
regex.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_with.workspace = true
Expand Down
85 changes: 16 additions & 69 deletions crates/voicevox_core_c_api/include/voicevox_core.h

Large diffs are not rendered by default.

88 changes: 65 additions & 23 deletions crates/voicevox_core_c_api/src/c_impls.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
use std::{ffi::CString, path::Path};
use std::{
collections::HashMap,
ffi::CString,
path::Path,
ptr::NonNull,
sync::{Arc, LazyLock},
};

use camino::Utf8Path;
use duplicate::duplicate_item;
use easy_ext::ext;
use ref_cast::ref_cast_custom;
use voicevox_core::{InitializeOptions, Result, SpeakerMeta, VoiceModelId};

use crate::{
helpers::CApiResult, OpenJtalkRc, VoicevoxOnnxruntime, VoicevoxSynthesizer,
helpers::CApiResult,
object::{CApiObject, CApiObjectPtrExt as _},
OpenJtalkRc, VoicevoxOnnxruntime, VoicevoxSynthesizer, VoicevoxUserDict,
VoicevoxVoiceModelFile,
};

Expand Down Expand Up @@ -61,61 +71,93 @@ macro_rules! to_cstr {
use to_cstr;

impl OpenJtalkRc {
pub(crate) fn new(open_jtalk_dic_dir: impl AsRef<Utf8Path>) -> Result<Self> {
Ok(Self {
open_jtalk: voicevox_core::blocking::OpenJtalk::new(open_jtalk_dic_dir)?,
})
pub(crate) fn new(open_jtalk_dic_dir: impl AsRef<Utf8Path>) -> Result<NonNull<Self>> {
let body = voicevox_core::blocking::OpenJtalk::new(open_jtalk_dic_dir)?;
Ok(<Self as CApiObject>::new(body))
}
}

impl VoicevoxSynthesizer {
pub(crate) fn new(
onnxruntime: &'static VoicevoxOnnxruntime,
open_jtalk: &OpenJtalkRc,
open_jtalk: *const OpenJtalkRc,
options: &InitializeOptions,
) -> Result<Self> {
let synthesizer = voicevox_core::blocking::Synthesizer::new(
) -> Result<NonNull<Self>> {
let body = voicevox_core::blocking::Synthesizer::new(
&onnxruntime.0,
open_jtalk.open_jtalk.clone(),
open_jtalk.body().clone(),
options,
)?;
Ok(Self { synthesizer })
Ok(<Self as CApiObject>::new(body))
}
}

pub(crate) fn onnxruntime(&self) -> &'static VoicevoxOnnxruntime {
VoicevoxOnnxruntime::new(self.synthesizer.onnxruntime())
#[ext(VoicevoxSynthesizerPtrExt)]
impl *const VoicevoxSynthesizer {
pub(crate) fn onnxruntime(self) -> &'static VoicevoxOnnxruntime {
VoicevoxOnnxruntime::new(self.body().onnxruntime())
}

pub(crate) fn load_voice_model(
&self,
self,
model: &voicevox_core::blocking::VoiceModelFile,
) -> CApiResult<()> {
self.synthesizer.load_voice_model(model)?;
self.body().load_voice_model(model)?;
Ok(())
}

pub(crate) fn unload_voice_model(&self, model_id: VoiceModelId) -> Result<()> {
self.synthesizer.unload_voice_model(model_id)?;
pub(crate) fn unload_voice_model(self, model_id: VoiceModelId) -> Result<()> {
self.body().unload_voice_model(model_id)?;
Ok(())
}

pub(crate) fn metas(&self) -> CString {
metas_to_json(&self.synthesizer.metas())
pub(crate) fn metas(self) -> CString {
metas_to_json(&self.body().metas())
}
}

impl VoicevoxVoiceModelFile {
pub(crate) fn open(path: impl AsRef<Path>) -> Result<Self> {
pub(crate) fn open(path: impl AsRef<Path>) -> Result<NonNull<Self>> {
let model = voicevox_core::blocking::VoiceModelFile::open(path)?;
Ok(Self { model })
Ok(Self::new(model))
}
}

pub(crate) fn metas(&self) -> CString {
metas_to_json(self.model.metas())
#[ext(VoicevoxVoiceModelFilePtrExt)]
impl *const VoicevoxVoiceModelFile {
pub(crate) fn metas(self) -> CString {
metas_to_json(self.body().metas())
}
}

fn metas_to_json(metas: &[SpeakerMeta]) -> CString {
let metas = serde_json::to_string(metas).expect("should not fail");
CString::new(metas).expect("should not contain NUL")
}

#[duplicate_item(
H B;
[ OpenJtalkRc ] [ voicevox_core::blocking::OpenJtalk ];
[ VoicevoxUserDict ] [ voicevox_core::blocking::UserDict ];
[ VoicevoxSynthesizer ] [ voicevox_core::blocking::Synthesizer<voicevox_core::blocking::OpenJtalk> ];
[ VoicevoxVoiceModelFile ] [ voicevox_core::blocking::VoiceModelFile ];
)]
impl CApiObject for H {
type RustApiObject = B;

fn heads() -> &'static std::sync::Mutex<Vec<Self>> {
static HEADS: std::sync::Mutex<Vec<H>> = std::sync::Mutex::new(vec![]);
&HEADS
}

fn bodies() -> &'static std::sync::Mutex<
HashMap<usize, Arc<parking_lot::RwLock<Option<Self::RustApiObject>>>>,
> {
#[expect(clippy::type_complexity, reason = "`CApiObject::bodies`と同様")]
static BODIES: LazyLock<
std::sync::Mutex<HashMap<usize, Arc<parking_lot::RwLock<Option<B>>>>>,
> = LazyLock::new(Default::default);

&BODIES
}
}
Loading

0 comments on commit f2e6b60

Please sign in to comment.