Skip to content

Commit f2e6b60

Browse files
authored
add: C APIの#[repr(Rust)]なものへのアクセスをすべて安全にする (#849)
C APIにおいて、Rust APIのオブジェクトそのものの代わりにトークンのような 1-bit長の構造体ユーザーに渡すようにすることで、次のことを実現する。 1. "delete"時に対象オブジェクトに対するアクセスがあった場合、アクセスが 終わるまで待つ 2. 次のユーザー操作に対するセーフティネットを張り、パニックするようにす る 1. "delete"後に他の通常のメソッド関数の利用を試みる 2. "delete"後に"delete"を試みる 3. そもそもオブジェクトとして変なダングリングポインタが渡される ドキュメントとしてのsafety requirementsもあわせて緩和する。 Resolves #836.
1 parent a55b013 commit f2e6b60

File tree

13 files changed

+645
-244
lines changed

13 files changed

+645
-244
lines changed

Cargo.lock

Lines changed: 23 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,10 @@ ndarray-stats = "0.5.1"
5858
octocrab = { version = "0.19.0", default-features = false }
5959
once_cell = "1.20.1"
6060
ouroboros = "0.18.4"
61+
parking_lot = "0.12.1"
6162
parse-display = "0.8.2"
6263
pollster = "0.3.0"
64+
predicates = "3.1.2"
6365
pretty_assertions = "1.4.1"
6466
proc-macro2 = "1.0.86"
6567
pyo3 = "0.20.3"

crates/voicevox_core_c_api/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ chrono = { workspace = true, default-features = false, features = ["clock"] }
2525
colorchoice.workspace = true
2626
const_format.workspace = true
2727
cstr.workspace = true
28-
derive-getters.workspace = true
28+
duplicate.workspace = true
2929
easy-ext.workspace = true
30+
educe.workspace = true
3031
itertools.workspace = true
3132
libc.workspace = true
33+
parking_lot = { workspace = true, features = ["arc_lock"] }
3234
process_path.workspace = true
3335
ref-cast.workspace = true
3436
serde_json = { workspace = true, features = ["preserve_order"] }
@@ -45,10 +47,12 @@ clap = { workspace = true, features = ["derive"] }
4547
duct.workspace = true
4648
easy-ext.workspace = true
4749
inventory.workspace = true
50+
indexmap = { workspace = true, features = ["serde"] }
4851
libloading.workspace = true
4952
libtest-mimic.workspace = true
5053
ndarray.workspace = true
5154
ndarray-stats.workspace = true
55+
predicates.workspace = true
5256
regex.workspace = true
5357
serde = { workspace = true, features = ["derive"] }
5458
serde_with.workspace = true

crates/voicevox_core_c_api/include/voicevox_core.h

Lines changed: 16 additions & 69 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/voicevox_core_c_api/src/c_impls.rs

Lines changed: 65 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
1-
use std::{ffi::CString, path::Path};
1+
use std::{
2+
collections::HashMap,
3+
ffi::CString,
4+
path::Path,
5+
ptr::NonNull,
6+
sync::{Arc, LazyLock},
7+
};
28

39
use camino::Utf8Path;
10+
use duplicate::duplicate_item;
11+
use easy_ext::ext;
412
use ref_cast::ref_cast_custom;
513
use voicevox_core::{InitializeOptions, Result, SpeakerMeta, VoiceModelId};
614

715
use crate::{
8-
helpers::CApiResult, OpenJtalkRc, VoicevoxOnnxruntime, VoicevoxSynthesizer,
16+
helpers::CApiResult,
17+
object::{CApiObject, CApiObjectPtrExt as _},
18+
OpenJtalkRc, VoicevoxOnnxruntime, VoicevoxSynthesizer, VoicevoxUserDict,
919
VoicevoxVoiceModelFile,
1020
};
1121

@@ -61,61 +71,93 @@ macro_rules! to_cstr {
6171
use to_cstr;
6272

6373
impl OpenJtalkRc {
64-
pub(crate) fn new(open_jtalk_dic_dir: impl AsRef<Utf8Path>) -> Result<Self> {
65-
Ok(Self {
66-
open_jtalk: voicevox_core::blocking::OpenJtalk::new(open_jtalk_dic_dir)?,
67-
})
74+
pub(crate) fn new(open_jtalk_dic_dir: impl AsRef<Utf8Path>) -> Result<NonNull<Self>> {
75+
let body = voicevox_core::blocking::OpenJtalk::new(open_jtalk_dic_dir)?;
76+
Ok(<Self as CApiObject>::new(body))
6877
}
6978
}
7079

7180
impl VoicevoxSynthesizer {
7281
pub(crate) fn new(
7382
onnxruntime: &'static VoicevoxOnnxruntime,
74-
open_jtalk: &OpenJtalkRc,
83+
open_jtalk: *const OpenJtalkRc,
7584
options: &InitializeOptions,
76-
) -> Result<Self> {
77-
let synthesizer = voicevox_core::blocking::Synthesizer::new(
85+
) -> Result<NonNull<Self>> {
86+
let body = voicevox_core::blocking::Synthesizer::new(
7887
&onnxruntime.0,
79-
open_jtalk.open_jtalk.clone(),
88+
open_jtalk.body().clone(),
8089
options,
8190
)?;
82-
Ok(Self { synthesizer })
91+
Ok(<Self as CApiObject>::new(body))
8392
}
93+
}
8494

85-
pub(crate) fn onnxruntime(&self) -> &'static VoicevoxOnnxruntime {
86-
VoicevoxOnnxruntime::new(self.synthesizer.onnxruntime())
95+
#[ext(VoicevoxSynthesizerPtrExt)]
96+
impl *const VoicevoxSynthesizer {
97+
pub(crate) fn onnxruntime(self) -> &'static VoicevoxOnnxruntime {
98+
VoicevoxOnnxruntime::new(self.body().onnxruntime())
8799
}
88100

89101
pub(crate) fn load_voice_model(
90-
&self,
102+
self,
91103
model: &voicevox_core::blocking::VoiceModelFile,
92104
) -> CApiResult<()> {
93-
self.synthesizer.load_voice_model(model)?;
105+
self.body().load_voice_model(model)?;
94106
Ok(())
95107
}
96108

97-
pub(crate) fn unload_voice_model(&self, model_id: VoiceModelId) -> Result<()> {
98-
self.synthesizer.unload_voice_model(model_id)?;
109+
pub(crate) fn unload_voice_model(self, model_id: VoiceModelId) -> Result<()> {
110+
self.body().unload_voice_model(model_id)?;
99111
Ok(())
100112
}
101113

102-
pub(crate) fn metas(&self) -> CString {
103-
metas_to_json(&self.synthesizer.metas())
114+
pub(crate) fn metas(self) -> CString {
115+
metas_to_json(&self.body().metas())
104116
}
105117
}
106118

107119
impl VoicevoxVoiceModelFile {
108-
pub(crate) fn open(path: impl AsRef<Path>) -> Result<Self> {
120+
pub(crate) fn open(path: impl AsRef<Path>) -> Result<NonNull<Self>> {
109121
let model = voicevox_core::blocking::VoiceModelFile::open(path)?;
110-
Ok(Self { model })
122+
Ok(Self::new(model))
111123
}
124+
}
112125

113-
pub(crate) fn metas(&self) -> CString {
114-
metas_to_json(self.model.metas())
126+
#[ext(VoicevoxVoiceModelFilePtrExt)]
127+
impl *const VoicevoxVoiceModelFile {
128+
pub(crate) fn metas(self) -> CString {
129+
metas_to_json(self.body().metas())
115130
}
116131
}
117132

118133
fn metas_to_json(metas: &[SpeakerMeta]) -> CString {
119134
let metas = serde_json::to_string(metas).expect("should not fail");
120135
CString::new(metas).expect("should not contain NUL")
121136
}
137+
138+
#[duplicate_item(
139+
H B;
140+
[ OpenJtalkRc ] [ voicevox_core::blocking::OpenJtalk ];
141+
[ VoicevoxUserDict ] [ voicevox_core::blocking::UserDict ];
142+
[ VoicevoxSynthesizer ] [ voicevox_core::blocking::Synthesizer<voicevox_core::blocking::OpenJtalk> ];
143+
[ VoicevoxVoiceModelFile ] [ voicevox_core::blocking::VoiceModelFile ];
144+
)]
145+
impl CApiObject for H {
146+
type RustApiObject = B;
147+
148+
fn heads() -> &'static std::sync::Mutex<Vec<Self>> {
149+
static HEADS: std::sync::Mutex<Vec<H>> = std::sync::Mutex::new(vec![]);
150+
&HEADS
151+
}
152+
153+
fn bodies() -> &'static std::sync::Mutex<
154+
HashMap<usize, Arc<parking_lot::RwLock<Option<Self::RustApiObject>>>>,
155+
> {
156+
#[expect(clippy::type_complexity, reason = "`CApiObject::bodies`と同様")]
157+
static BODIES: LazyLock<
158+
std::sync::Mutex<HashMap<usize, Arc<parking_lot::RwLock<Option<B>>>>>,
159+
> = LazyLock::new(Default::default);
160+
161+
&BODIES
162+
}
163+
}

0 commit comments

Comments
 (0)