diff --git a/vnkey-engine/Cargo.lock b/vnkey-engine/Cargo.lock index 5dbd15c..55da5c1 100644 --- a/vnkey-engine/Cargo.lock +++ b/vnkey-engine/Cargo.lock @@ -2,15 +2,186 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.2", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + [[package]] name = "vnkey-engine" version = "1.0.3" dependencies = [ + "num_enum", "once_cell", ] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] diff --git a/vnkey-engine/Cargo.toml b/vnkey-engine/Cargo.toml index 7ef9653..174da35 100644 --- a/vnkey-engine/Cargo.toml +++ b/vnkey-engine/Cargo.toml @@ -10,4 +10,5 @@ name = "vnkey_engine" crate-type = ["lib", "staticlib"] [dependencies] +num_enum = "0.7" once_cell = "1" diff --git a/vnkey-engine/src/ffi.rs b/vnkey-engine/src/ffi.rs index 92fed8f..61cde3f 100644 --- a/vnkey-engine/src/ffi.rs +++ b/vnkey-engine/src/ffi.rs @@ -8,7 +8,7 @@ use std::ffi::{CStr, CString}; use std::os::raw::c_char; use std::ptr; -use std::sync::Mutex; +use std::sync::{Mutex, MutexGuard}; use crate::engine::Engine; use crate::input::InputMethod; @@ -16,32 +16,30 @@ use crate::charset::{self, Charset}; static ENGINE: Mutex> = Mutex::new(None); +fn lock_recover(mutex: &Mutex) -> MutexGuard<'_, T> { + mutex.lock().unwrap_or_else(|poisoned| poisoned.into_inner()) +} + fn with_engine(f: F) -> R where F: FnOnce(&mut Engine) -> R, R: Default, { - let mut guard = match ENGINE.lock() { - Ok(g) => g, - Err(_) => return R::default(), - }; - if guard.is_none() { - *guard = Some(Engine::new()); - } - f(guard.as_mut().unwrap()) + let mut guard = lock_recover(&ENGINE); + f(guard.get_or_insert_with(Engine::new)) } /// Khởi tạo engine. Gọi một lần khi khởi động. #[no_mangle] pub extern "C" fn vnkey_setup() { - let mut guard = ENGINE.lock().unwrap(); + let mut guard = lock_recover(&ENGINE); *guard = Some(Engine::new()); } /// Tắt engine và giải phóng tài nguyên. #[no_mangle] pub extern "C" fn vnkey_cleanup() { - let mut guard = ENGINE.lock().unwrap(); + let mut guard = lock_recover(&ENGINE); *guard = None; } @@ -639,3 +637,24 @@ pub unsafe extern "C" fn vnkey_app_charset_update(exe_name: *const c_char) { pub extern "C" fn vnkey_app_charset_get_current() -> i32 { crate::app_charset::get_current_app_charset().unwrap_or(-1) } + +#[cfg(test)] +mod tests { + use super::lock_recover; + use std::sync::{Arc, Mutex}; + + #[test] + fn poisoned_mutex_is_recovered_without_panicking() { + let mutex = Arc::new(Mutex::new(7)); + let poison_target = Arc::clone(&mutex); + + let _ = std::thread::spawn(move || { + let _guard = poison_target.lock().expect("test mutex should start healthy"); + panic!("poison test mutex"); + }) + .join(); + + *lock_recover(&mutex) = 9; + assert_eq!(*lock_recover(&mutex), 9); + } +} diff --git a/vnkey-engine/src/input.rs b/vnkey-engine/src/input.rs index 6458e2a..10b37dd 100644 --- a/vnkey-engine/src/input.rs +++ b/vnkey-engine/src/input.rs @@ -15,7 +15,7 @@ pub enum InputMethod { } /// Loại sự kiện phím -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::TryFromPrimitive)] #[repr(u8)] pub enum KeyEvType { RoofAll = 0, @@ -43,12 +43,7 @@ pub enum KeyEvType { impl KeyEvType { /// An toàn: chuyển u8 sang KeyEvType, trả về Normal nếu ngoài phạm vi pub fn from_u8(v: u8) -> Self { - if v <= KeyEvType::Normal as u8 { - // SAFETY: repr(u8), các variant liên tục 0..=Normal, đã kiểm tra biên - unsafe { std::mem::transmute(v) } - } else { - KeyEvType::Normal - } + Self::try_from(v).unwrap_or(Self::Normal) } } @@ -285,3 +280,15 @@ impl Default for InputProcessor { Self::new() } } + +#[cfg(test)] +mod tests { + use super::KeyEvType; + + #[test] + fn key_event_conversion_rejects_out_of_range_values() { + assert_eq!(KeyEvType::from_u8(KeyEvType::RoofAll as u8), KeyEvType::RoofAll); + assert_eq!(KeyEvType::from_u8(KeyEvType::Normal as u8), KeyEvType::Normal); + assert_eq!(KeyEvType::from_u8(u8::MAX), KeyEvType::Normal); + } +} diff --git a/vnkey-engine/src/vnlexi.rs b/vnkey-engine/src/vnlexi.rs index d7fb417..c202cd4 100644 --- a/vnkey-engine/src/vnlexi.rs +++ b/vnkey-engine/src/vnlexi.rs @@ -3,7 +3,7 @@ /// Tên ký tự tiếng Việt. Chẵn = hoa, lẻ = thường. /// Dấu: base, 1(sắc), 2(huyền), 3(hỏi), 4(ngã), 5(nặng) /// Hậu tố: r = mũ (â,ê,ô), b = trăng (ă), h = móc (ơ,ư) -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, num_enum::TryFromPrimitive)] #[repr(i16)] #[allow(non_camel_case_types)] pub enum VnLexiName { @@ -55,11 +55,9 @@ use VnLexiName::*; impl VnLexiName { /// An toàn: chuyển i16 sang VnLexiName, trả về NonVnChar nếu ngoài phạm vi pub fn from_i16(val: i16) -> Self { - if val >= VnLexiName::NonVnChar as i16 && val < VnLexiName::LastChar as i16 { - // SAFETY: repr(i16), variant liên tục -1..LastChar, đã kiểm tra biên - unsafe { std::mem::transmute(val) } - } else { - VnLexiName::NonVnChar + match Self::try_from(val) { + Ok(Self::LastChar) | Err(_) => Self::NonVnChar, + Ok(value) => value, } } @@ -177,7 +175,7 @@ pub fn get_tone(sym: VnLexiName) -> i32 { // ============= Chuỗi nguyên âm ============= -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, num_enum::TryFromPrimitive)] #[repr(i16)] #[allow(non_camel_case_types)] pub enum VowelSeq { @@ -215,11 +213,7 @@ pub enum VowelSeq { impl VowelSeq { pub fn from_i16(val: i16) -> Self { - if val >= VowelSeq::Nil as i16 && val <= VowelSeq::VS_YERU as i16 { - unsafe { std::mem::transmute(val) } - } else { - VowelSeq::Nil - } + Self::try_from(val).unwrap_or(Self::Nil) } } @@ -227,7 +221,7 @@ use VowelSeq::*; // ============= Chuỗi phụ âm ============= -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, num_enum::TryFromPrimitive)] #[repr(i16)] #[allow(non_camel_case_types)] pub enum ConSeq { @@ -245,11 +239,7 @@ pub enum ConSeq { impl ConSeq { pub fn from_i16(val: i16) -> Self { - if val >= ConSeq::Nil as i16 && val <= ConSeq::CS_X as i16 { - unsafe { std::mem::transmute(val) } - } else { - ConSeq::Nil - } + Self::try_from(val).unwrap_or(Self::Nil) } } @@ -590,3 +580,19 @@ pub fn iso_to_vn_lexi(key_code: u32) -> VnLexiName { _ => NonVnChar, } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn primitive_conversions_reject_sentinels_and_out_of_range_values() { + assert_eq!(VnLexiName::from_i16(VnLexiName::A as i16), VnLexiName::A); + assert_eq!(VnLexiName::from_i16(VnLexiName::LastChar as i16), VnLexiName::NonVnChar); + assert_eq!(VnLexiName::from_i16(i16::MAX), VnLexiName::NonVnChar); + assert_eq!(VowelSeq::from_i16(VowelSeq::VS_YERU as i16), VowelSeq::VS_YERU); + assert_eq!(VowelSeq::from_i16(i16::MAX), VowelSeq::Nil); + assert_eq!(ConSeq::from_i16(ConSeq::CS_X as i16), ConSeq::CS_X); + assert_eq!(ConSeq::from_i16(i16::MAX), ConSeq::Nil); + } +}