Files
oak-editor/crates/oak-plugin/src/suites/property.rs
T
Mike-Solar fe8fbff8a9 test(oak-timeline, oak-plugin): undo commands and plugin host
Ripple/pointer/split/general undo commands with their edge cases, and
plugin host/instance/suite coverage including the built-in test
plugin bundle.
2026-09-22 20:54:04 +08:00

1559 lines
44 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Oak Video Editor - Non-Linear Video Editor
// Copyright (C) 2026 Oak Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! OfxPropertySuite v1:属性读写。
//!
//! 语义逐条对照 HS: ofxhPropertySuite.cpp:
//! - 读未定义属性 / 类型不符 → kOfxStatErrUnknown(HS: propGet 的
//! `fetchTypedProperty` 失败路径,ofxhPropertySuite.cpp:787-794);
//! - 越界读写 → kOfxStatErrBadIndex(HS: `getValueRaw`/
//! `setValue`,ofxhPropertySuite.cpp:257/284);
//! - propGetN 拷贝 min(count, dimension) 个元素,不报错
//! (HS: `getValueNRaw`,ofxhPropertySuite.cpp:271-280);
//! - propSetN 维度变化时整体替换:本 crate 的属性维度始终跟随数组
//! 长度(HS 对固定维度属性 count > dimension 会返回
//! kOfxStatErrBadIndex,ofxhPropertySuite.cpp:299——本 crate 不区分
//! 固定/可变维度,如实更宽容;三个系统 bundle 均以正确 count 调用,
//! 无行为分歧);
//! - 只读属性:SDK 无 kOfxStatErrReadOnly,HostSupport 也不在 suite
//! 层拦截写(`_pluginReadOnly` 仅供宿主内部逻辑查询)——本实现
//! 如实不拦截;若日后需要,在 PropertySet 增加只读标记再评。
//! - propReset:HostSupport 恢复到 define 时默认值
//! (ofxhPropertySuite.cpp:330);本 crate 不保存默认值快照,
//! 第 1 期明确返回 kOfxStatErrUnsupported(真话比静默 no-op 安全)。
use std::ffi::{c_char, c_double, c_int, c_void, CStr, CString};
use crate::property::{Property, PropertySet, Value};
use crate::suites::status;
/// 函数表布局(与 SDK `OfxPropertySuiteV1` 逐字段一致)。
#[repr(C)]
pub struct PropertySuiteV1 {
/// propSetPointer
pub set_pointer: unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut c_void) -> c_int,
/// propSetString
pub set_string: unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *const c_char) -> c_int,
/// propSetDouble
pub set_double: unsafe extern "C" fn(*mut c_void, *const c_char, c_int, c_double) -> c_int,
/// propSetInt
pub set_int: unsafe extern "C" fn(*mut c_void, *const c_char, c_int, c_int) -> c_int,
/// propSetPointerN
pub set_pointer_n:
unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *const *mut c_void) -> c_int,
/// propSetStringN
pub set_string_n:
unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *const *const c_char) -> c_int,
/// propSetDoubleN
pub set_double_n:
unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *const c_double) -> c_int,
/// propSetIntN
pub set_int_n: unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *const c_int) -> c_int,
/// propGetPointer
pub get_pointer:
unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut *mut c_void) -> c_int,
/// propGetString
pub get_string:
unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut *mut c_char) -> c_int,
/// propGetDouble
pub get_double: unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut c_double) -> c_int,
/// propGetInt
pub get_int: unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut c_int) -> c_int,
/// propGetPointerN
pub get_pointer_n:
unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut *mut c_void) -> c_int,
/// propGetStringN
pub get_string_n:
unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut *mut c_char) -> c_int,
/// propGetDoubleN
pub get_double_n:
unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut c_double) -> c_int,
/// propGetIntN
pub get_int_n: unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut c_int) -> c_int,
/// propReset
pub reset: unsafe extern "C" fn(*mut c_void, *const c_char) -> c_int,
/// propGetDimension
pub get_dimension: unsafe extern "C" fn(*mut c_void, *const c_char, *mut c_int) -> c_int,
}
/// 属性值类别(suite 层的类型检查;对应 HS 的 `Property::TypeEnum`)。
#[derive(Clone, Copy, PartialEq, Eq)]
enum Kind {
Int,
Double,
Str,
Pointer,
}
impl Kind {
fn of(v: &Value) -> Kind {
match v {
Value::Int(_) => Kind::Int,
Value::Double(_) => Kind::Double,
Value::String(_) => Kind::Str,
Value::Pointer(_) => Kind::Pointer,
}
}
}
/// 属性 suite 公共入口模板:解句柄 + panic 兜底。
///
/// 句柄按 [`crate::suites::tag`] 约定:低 3 位是对象种类标签,地址
/// 即对象 props 字段(偏移 0);剥标签后可直接当 PropertySet 用。
/// 裸 PropertySet 指针(标签 0,宿主内部/测试直传)原样通过。
///
/// `# Safety`:`handle` 必须指向活的 `PropertySet` 或已注册对象
/// (suite 生命周期契约:宿主对象先于 suite 调用创建,后于全部调用
/// 销毁)。
#[track_caller]
unsafe fn caught(handle: *mut c_void, f: impl FnOnce(&PropertySet) -> Result<(), c_int>) -> c_int {
let caller = std::panic::Location::caller();
let code = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
if handle.is_null() {
return status::ERR_BAD_HANDLE;
}
let set = unsafe { &*crate::suites::tag::strip(handle) };
f(set).map_or_else(|code| code, |()| status::OK)
}))
.unwrap_or(status::FAILED);
if code != status::OK && std::env::var_os("OAK_OFX_TRACE").is_some() {
let tag = crate::suites::tag::kind(handle);
eprintln!("[ofx] property suite error {code} at {caller} (handle {handle:p} tag {tag})");
}
code
}
/// 属性名:空指针 / 非 UTF-8 → kOfxStatErrValue(防御性;HostSupport
/// 直接解引用会崩)。
///
/// `# Safety`:`name` 必须是有效的 NUL 结尾 C 字符串(插件契约)。
unsafe fn c_name<'a>(name: *const c_char) -> Result<&'a str, c_int> {
if name.is_null() {
return Err(status::ERR_VALUE);
}
unsafe { CStr::from_ptr(name) }
.to_str()
.map_err(|_| status::ERR_VALUE)
}
/// 越界防护:`index` 转 usize(负数 → 越界错误)。
fn idx(index: c_int) -> Result<usize, c_int> {
usize::try_from(index).map_err(|_| status::ERR_BAD_INDEX)
}
/// 先类型后索引的取值(HS 顺序:`fetchTypedProperty` 在
/// `getValueRaw` 之前,ofxhPropertySuite.cpp:787/794/257)。
fn get_value<'a>(
props: &'a [Property],
name: &str,
index: c_int,
kind: Kind,
) -> Result<&'a Value, c_int> {
let result = get_value_inner(props, name, index, kind);
if std::env::var_os("OAK_OFX_TRACE").is_some() {
match &result {
Ok(_) => eprintln!("[ofx] propGet({name}[{index}]) -> ok"),
Err(c) => eprintln!("[ofx] propGet({name}[{index}]) -> {c}"),
}
}
result
}
fn get_value_inner<'a>(
props: &'a [Property],
name: &str,
index: c_int,
kind: Kind,
) -> Result<&'a Value, c_int> {
let p = props
.iter()
.find(|p| p.name == name)
.ok_or(status::ERR_UNKNOWN)?;
// 首元素代理整条属性的类型(宿主只定义同构数组;HS 是定义期
// 固定类型,等价)。空数组没有类型探针,跳过类型检查,越界
// 由下面的索引访问报 BadIndex(HS getValueRaw 语义)。
if let Some(probe) = p.values.first() {
// Int <-> Double 数值协随(与 get_n 同款宽容宿主语义)。
let numeric = matches!(Kind::of(probe), Kind::Int | Kind::Double)
&& matches!(kind, Kind::Int | Kind::Double);
if Kind::of(probe) != kind && !numeric {
if std::env::var_os("OAK_OFX_TRACE").is_some() {
eprintln!("[ofx] property kind mismatch: {name}");
}
return Err(status::ERR_UNKNOWN);
}
}
p.values.get(idx(index)?).ok_or(status::ERR_BAD_INDEX)
}
// ---- propSet(单元素)-----------------------------------------------------
/// propSet 通用实现:属性未定义时按 OFX 语义**隐式创建**(ofxProperty.h:
/// "If the property does not exist it is created",HS setValue 同)——
/// 插件 describe 期写的大量描述符属性(grouping、各 capability 开关)
/// 并未由宿主预定义,拒绝创建会让 describe 直接失败。已定义但类型
/// 不符 → Unknown;越界 → BadIndex。
fn set_value(set: &PropertySet, name: &str, index: c_int, value: Value) -> Result<(), c_int> {
set.with_locked(|props| {
let idx = idx(index)?;
let Some(p) = props.iter_mut().find(|p| p.name == name) else {
// 隐式创建:维度 index+1,前导槽位以同值填充(HS 的新属性
// 默认值语义——新建属性先有一个默认元素再逐位写)。
let mut values = vec![value.clone(); idx];
values.push(value);
props.push(crate::property::Property {
name: name.to_string(),
values,
});
return Ok(());
};
// 空属性(宿主预定义的空数组,如 OfxImageEffectPropSupportedContexts)
// 没有类型探针:按 HS 的 index == size 追加语义直接 push。
if p.values.is_empty() {
if idx != 0 {
if std::env::var_os("OAK_OFX_TRACE").is_some() {
eprintln!("[ofx] propSet into empty predefined array: {name}[{idx}]");
}
return Err(status::ERR_BAD_INDEX);
}
p.values.push(value);
return Ok(());
}
let probe = &p.values[0];
if Kind::of(probe) != Kind::of(&value) {
return Err(status::ERR_UNKNOWN);
}
// HS `setValue` 允许 index == size 时追加(ofxhPropertySuite.cpp:284)
// ——addSupportedContext 等协商属性正是这样逐位增长的。
if idx == p.values.len() {
p.values.push(value);
return Ok(());
}
if idx >= p.values.len() && std::env::var_os("OAK_OFX_TRACE").is_some() {
eprintln!("[ofx] propSet out of range: {name}[{idx}] (dim {})", p.values.len());
}
let slot = p.values.get_mut(idx).ok_or(status::ERR_BAD_INDEX)?;
*slot = value;
Ok(())
})
}
unsafe extern "C" fn prop_set_pointer(
handle: *mut c_void,
name: *const c_char,
index: c_int,
value: *mut c_void,
) -> c_int {
unsafe {
caught(handle, |set| {
let name = c_name(name)?;
set_value(set, name, index, Value::Pointer(value))
})
}
}
unsafe extern "C" fn prop_set_string(
handle: *mut c_void,
name: *const c_char,
index: c_int,
value: *const c_char,
) -> c_int {
unsafe {
caught(handle, |set| {
let name = c_name(name)?;
if value.is_null() {
return Err(status::ERR_VALUE);
}
// C 语义:截断到首个 NUL;CString::new 不会失败
//(from_ptr 已保证 NUL 结尾)。
let s = CString::new(CStr::from_ptr(value).to_bytes()).unwrap();
set_value(set, name, index, Value::String(s))
})
}
}
unsafe extern "C" fn prop_set_double(
handle: *mut c_void,
name: *const c_char,
index: c_int,
value: c_double,
) -> c_int {
unsafe {
caught(handle, |set| {
let name = c_name(name)?;
set_value(set, name, index, Value::Double(value))
})
}
}
unsafe extern "C" fn prop_set_int(
handle: *mut c_void,
name: *const c_char,
index: c_int,
value: c_int,
) -> c_int {
unsafe {
caught(handle, |set| {
let name = c_name(name)?;
set_value(set, name, index, Value::Int(value))
})
}
}
// ---- propSetN(批量)------------------------------------------------------
/// 从 C 数组读出 `count` 个元素并整体写入;count != 现有维度时
/// 整体替换(HS `setValueN` 的 resize 语义,ofxhPropertySuite.cpp:299-311)。
fn set_values(
set: &PropertySet,
name: &str,
count: c_int,
values: Vec<Value>,
) -> Result<(), c_int> {
set.with_locked(|props| {
let Some(p) = props.iter_mut().find(|p| p.name == name) else {
// 与单元素 set_value 一致:未定义属性按 OFX 语义隐式创建。
props.push(crate::property::Property {
name: name.to_string(),
values,
});
return Ok(());
};
// count == 0 时无类型可探(HS 仍会做 fetchTypedProperty)。
if let Some(first) = p.values.first() {
if let Some(v) = values.first() {
if Kind::of(first) != Kind::of(v) {
return Err(status::ERR_UNKNOWN);
}
}
}
let count = idx(count)?;
if count != p.values.len() {
// 维度变化:整体替换(HS 是 resize + 逐位写,等价)。
p.values = values;
} else {
for (i, v) in values.into_iter().enumerate() {
p.values[i] = v;
}
}
Ok(())
})
}
unsafe extern "C" fn prop_set_pointer_n(
handle: *mut c_void,
name: *const c_char,
count: c_int,
values: *const *mut c_void,
) -> c_int {
unsafe {
caught(handle, |set| {
let name = c_name(name)?;
if count > 0 && values.is_null() {
return Err(status::ERR_VALUE);
}
let vals = (0..idx(count)?)
.map(|i| Value::Pointer(*values.add(i)))
.collect();
set_values(set, name, count, vals)
})
}
}
unsafe extern "C" fn prop_set_string_n(
handle: *mut c_void,
name: *const c_char,
count: c_int,
values: *const *const c_char,
) -> c_int {
unsafe {
caught(handle, |set| {
let name = c_name(name)?;
if count > 0 && values.is_null() {
return Err(status::ERR_VALUE);
}
let mut vals = Vec::with_capacity(count.max(0) as usize);
for i in 0..idx(count)? {
let v = *values.add(i);
if v.is_null() {
return Err(status::ERR_VALUE);
}
vals.push(Value::String(
CString::new(CStr::from_ptr(v).to_bytes()).unwrap(),
));
}
set_values(set, name, count, vals)
})
}
}
unsafe extern "C" fn prop_set_double_n(
handle: *mut c_void,
name: *const c_char,
count: c_int,
values: *const c_double,
) -> c_int {
unsafe {
caught(handle, |set| {
let name = c_name(name)?;
if count > 0 && values.is_null() {
return Err(status::ERR_VALUE);
}
let vals = (0..idx(count)?)
.map(|i| Value::Double(*values.add(i)))
.collect();
set_values(set, name, count, vals)
})
}
}
unsafe extern "C" fn prop_set_int_n(
handle: *mut c_void,
name: *const c_char,
count: c_int,
values: *const c_int,
) -> c_int {
unsafe {
caught(handle, |set| {
let name = c_name(name)?;
if count > 0 && values.is_null() {
return Err(status::ERR_VALUE);
}
let vals = (0..idx(count)?)
.map(|i| Value::Int(*values.add(i)))
.collect();
set_values(set, name, count, vals)
})
}
}
// ---- propGet(单元素)-----------------------------------------------------
/// 非字符串标量读取(克隆值写出;字符串走内驻指针路径)。
fn get_scalar(set: &PropertySet, name: &str, index: c_int, kind: Kind) -> Result<Value, c_int> {
set.with_locked(|props| get_value(props, name, index, kind).cloned())
}
unsafe extern "C" fn prop_get_pointer(
handle: *mut c_void,
name: *const c_char,
index: c_int,
out: *mut *mut c_void,
) -> c_int {
unsafe {
caught(handle, |set| {
let name = c_name(name)?;
if out.is_null() {
return Err(status::ERR_VALUE);
}
let v = get_scalar(set, name, index, Kind::Pointer)?;
match v {
Value::Pointer(p) => {
if std::env::var_os("OAK_OFX_TRACE").is_some() {
eprintln!("[ofx] propGetPointer(h={handle:p}, {name}[{index}]) -> {p:p}");
}
*out = p;
Ok(())
}
_ => Err(status::FAILED),
}
})
}
}
/// propGetString:写**内驻** C 串指针(OFX 契约:指向宿主内部存储,
/// 属性被下次修改前有效;克隆的 CString 指针会悬垂,绝不能给插件)。
fn get_string(set: &PropertySet, name: &str, index: c_int) -> Result<*mut c_char, c_int> {
set.with_locked(|props| match get_value(props, name, index, Kind::Str)? {
Value::String(s) => Ok(s.as_ptr() as *mut c_char),
_ => Err(status::FAILED),
})
}
unsafe extern "C" fn prop_get_string(
handle: *mut c_void,
name: *const c_char,
index: c_int,
out: *mut *mut c_char,
) -> c_int {
unsafe {
caught(handle, |set| {
let name = c_name(name)?;
if out.is_null() {
return Err(status::ERR_VALUE);
}
let r = get_string(set, name, index);
if std::env::var_os("OAK_OFX_TRACE").is_some() {
let rendered = match &r {
Ok(p) => format!("0 <- {:?}", std::ffi::CStr::from_ptr(*p)),
Err(c) => format!("{c}"),
};
eprintln!("[ofx] propGetString(h={handle:p}, {name}[{index}]) -> {rendered}");
}
*out = r?;
Ok(())
})
}
}
unsafe extern "C" fn prop_get_double(
handle: *mut c_void,
name: *const c_char,
index: c_int,
out: *mut c_double,
) -> c_int {
unsafe {
caught(handle, |set| {
let name = c_name(name)?;
if out.is_null() {
return Err(status::ERR_VALUE);
}
let v = get_scalar(set, name, index, Kind::Double)?;
match v {
Value::Double(d) => {
*out = d;
Ok(())
}
// Int <-> Double 数值协随(宽容宿主语义,同 get_n)。
Value::Int(i) => {
*out = i as f64;
Ok(())
}
_ => Err(status::FAILED),
}
})
}
}
unsafe extern "C" fn prop_get_int(
handle: *mut c_void,
name: *const c_char,
index: c_int,
out: *mut c_int,
) -> c_int {
unsafe {
caught(handle, |set| {
let name = c_name(name)?;
if out.is_null() {
return Err(status::ERR_VALUE);
}
let v = get_scalar(set, name, index, Kind::Int)?;
match v {
Value::Int(i) => {
*out = i;
Ok(())
}
// Int <-> Double 数值协随(宽容宿主语义,同 get_n)。
Value::Double(d) => {
*out = d as i32;
Ok(())
}
_ => Err(status::FAILED),
}
.inspect_err(|&c| {
if std::env::var_os("OAK_OFX_TRACE").is_some() {
eprintln!("[ofx] propGetInt({name}) -> {c}");
}
})
})
}
}
// ---- propGetN(批量)------------------------------------------------------
/// 批量读取:拷贝 min(count, dimension) 个(HS `getValueNRaw`,
/// ofxhPropertySuite.cpp:271-280,不报错)。
fn get_n(
set: &PropertySet,
name: &str,
count: c_int,
kind: Kind,
write: impl Fn(&Value, usize),
) -> Result<(), c_int> {
set.with_locked(|props| {
let p = props
.iter()
.find(|p| p.name == name)
.ok_or(status::ERR_UNKNOWN)?;
let probe = p.values.first().ok_or(status::ERR_UNKNOWN)?;
// Int <-> Double coercion (forgiving-host semantics): the CImg
// framework reads the DOUBLE render window with propGetIntN and
// must still get the values; strict kind checks turn that into a
// bogus MissingHostFeature at render time.
let numeric = matches!(Kind::of(probe), Kind::Int | Kind::Double)
&& matches!(kind, Kind::Int | Kind::Double);
if Kind::of(probe) != kind && !numeric {
return Err(status::ERR_UNKNOWN);
}
let n = idx(count)?.min(p.values.len());
for (i, v) in p.values.iter().take(n).enumerate() {
write(v, i);
}
if std::env::var_os("OAK_OFX_TRACE").is_some() {
let dump: Vec<String> = p
.values
.iter()
.take(n)
.map(|v| match v {
Value::Int(i) => i.to_string(),
Value::Double(d) => d.to_string(),
_ => "?".to_string(),
})
.collect();
eprintln!("[ofx] propGetN({name} x{n}) -> ok [{dump:?}]");
}
Ok(())
})
.inspect_err(|&code| {
// The property the plugin asked for and we did not have — the
// single most useful line when a plugin reports
// MissingHostFeature (trace-gated, like fetchSuite misses).
if std::env::var_os("OAK_OFX_TRACE").is_some() {
eprintln!("[ofx] property miss: {name} (code {code})");
}
})
}
unsafe extern "C" fn prop_get_pointer_n(
handle: *mut c_void,
name: *const c_char,
count: c_int,
out: *mut *mut c_void,
) -> c_int {
unsafe {
caught(handle, |set| {
let name = c_name(name)?;
if count > 0 && out.is_null() {
return Err(status::ERR_VALUE);
}
get_n(set, name, count, Kind::Pointer, |v, i| if let Value::Pointer(p) = v { *out.add(i) = *p })
})
}
}
unsafe extern "C" fn prop_get_string_n(
handle: *mut c_void,
name: *const c_char,
count: c_int,
out: *mut *mut c_char,
) -> c_int {
unsafe {
caught(handle, |set| {
let name = c_name(name)?;
if count > 0 && out.is_null() {
return Err(status::ERR_VALUE);
}
get_n(set, name, count, Kind::Str, |v, i| if let Value::String(s) = v { *out.add(i) = s.as_ptr() as *mut c_char })
})
}
}
unsafe extern "C" fn prop_get_double_n(
handle: *mut c_void,
name: *const c_char,
count: c_int,
out: *mut c_double,
) -> c_int {
unsafe {
caught(handle, |set| {
let name = c_name(name)?;
if count > 0 && out.is_null() {
return Err(status::ERR_VALUE);
}
get_n(set, name, count, Kind::Double, |v, i| match v {
Value::Double(d) => *out.add(i) = *d,
Value::Int(d) => *out.add(i) = *d as f64,
_ => {}
})
})
}
}
unsafe extern "C" fn prop_get_int_n(
handle: *mut c_void,
name: *const c_char,
count: c_int,
out: *mut c_int,
) -> c_int {
unsafe {
caught(handle, |set| {
let name = c_name(name)?;
if count > 0 && out.is_null() {
return Err(status::ERR_VALUE);
}
get_n(set, name, count, Kind::Int, |v, i| match v {
Value::Int(d) => *out.add(i) = *d,
Value::Double(d) => *out.add(i) = *d as i32,
_ => {}
})
})
}
}
// ---- propReset / propGetDimension -----------------------------------------
unsafe extern "C" fn prop_reset(handle: *mut c_void, name: *const c_char) -> c_int {
unsafe {
caught(handle, |set| {
let name = c_name(name)?;
// HS `reset()` 恢复到 define 时的默认值
// (ofxhPropertySuite.cpp:330);本 crate 不保存默认值快照,
// 第 1 期明确不支持(见模块文档)。
let _ = (set, name);
Err(status::ERR_UNSUPPORTED)
})
}
}
unsafe extern "C" fn prop_get_dimension(
handle: *mut c_void,
name: *const c_char,
out: *mut c_int,
) -> c_int {
unsafe {
caught(handle, |set| {
let name = c_name(name)?;
if out.is_null() {
return Err(status::ERR_VALUE);
}
// HS: fetchProperty 失败(未定义)→ Unknown(ofxhPropertySuite.cpp:992);
// 已定义的空数组(如协商属性的初始状态)是合法维度 0,不是错误。
let exists = set.with_locked(|props| props.iter().any(|p| p.name == name));
if !exists {
if std::env::var_os("OAK_OFX_TRACE").is_some() {
let tag = crate::suites::tag::kind(handle);
eprintln!("[ofx] property miss (dimension): {name} (handle {handle:p} tag {tag})");
}
return Err(status::ERR_UNKNOWN);
}
*out = set.dimension(name) as c_int;
Ok(())
})
}
}
/// 静态函数表实例(fetch_suite 返回其地址)。
pub fn suite_v1() -> &'static PropertySuiteV1 {
static SUITE: std::sync::OnceLock<PropertySuiteV1> = std::sync::OnceLock::new();
SUITE.get_or_init(|| PropertySuiteV1 {
set_pointer: prop_set_pointer,
set_string: prop_set_string,
set_double: prop_set_double,
set_int: prop_set_int,
set_pointer_n: prop_set_pointer_n,
set_string_n: prop_set_string_n,
set_double_n: prop_set_double_n,
set_int_n: prop_set_int_n,
get_pointer: prop_get_pointer,
get_string: prop_get_string,
get_double: prop_get_double,
get_int: prop_get_int,
get_pointer_n: prop_get_pointer_n,
get_string_n: prop_get_string_n,
get_double_n: prop_get_double_n,
get_int_n: prop_get_int_n,
reset: prop_reset,
get_dimension: prop_get_dimension,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn handle(set: &PropertySet) -> *mut c_void {
set as *const PropertySet as *mut c_void
}
fn cs(s: &str) -> CString {
CString::new(s).unwrap()
}
/// `Value` 未实现 PartialEq(Pointer 无法比较)——用 Debug 串比较。
fn val_eq(a: &Value, b: &Value) -> bool {
format!("{a:?}") == format!("{b:?}")
}
fn assert_value(set: &PropertySet, name: &str, index: usize, want: &Value) {
assert!(
val_eq(set.get(name, index).as_ref().expect("属性应存在"), want),
"{name}[{index}] 值不符"
);
}
/// 空句柄 → BadHandle;空/非 UTF-8 属性名 → ErrValue。
#[test]
fn bad_handles_and_bad_names() {
let s = suite_v1();
let set = PropertySet::new();
let name = cs("a");
let mut iv = 0;
let mut dv = 0.0;
let mut sv: *mut c_char = std::ptr::null_mut();
let mut pv: *mut c_void = std::ptr::null_mut();
unsafe {
assert_eq!(
(s.set_int)(std::ptr::null_mut(), name.as_ptr(), 0, 1),
status::ERR_BAD_HANDLE
);
assert_eq!(
(s.get_int)(std::ptr::null_mut(), name.as_ptr(), 0, &mut iv),
status::ERR_BAD_HANDLE
);
// 空属性名。
assert_eq!((s.set_int)(handle(&set), std::ptr::null(), 0, 1), status::ERR_VALUE);
assert_eq!(
(s.get_int)(handle(&set), std::ptr::null(), 0, &mut iv),
status::ERR_VALUE
);
assert_eq!(
(s.set_double)(handle(&set), std::ptr::null(), 0, 1.0),
status::ERR_VALUE
);
assert_eq!(
(s.set_string)(handle(&set), std::ptr::null(), 0, name.as_ptr()),
status::ERR_VALUE
);
assert_eq!(
(s.get_double)(handle(&set), std::ptr::null(), 0, &mut dv),
status::ERR_VALUE
);
assert_eq!(
(s.get_string)(handle(&set), std::ptr::null(), 0, &mut sv),
status::ERR_VALUE
);
assert_eq!(
(s.get_pointer)(handle(&set), std::ptr::null(), 0, &mut pv),
status::ERR_VALUE
);
assert_eq!(
(s.get_dimension)(handle(&set), std::ptr::null(), &mut iv),
status::ERR_VALUE
);
assert_eq!(
(s.reset)(handle(&set), std::ptr::null()),
status::ERR_VALUE
);
// 非 UTF-8 属性名(0xFF 后接 NUL)。
let bad: [c_char; 2] = [0xFFu8 as c_char, 0];
assert_eq!(
(s.set_int)(handle(&set), bad.as_ptr(), 0, 1),
status::ERR_VALUE
);
assert_eq!(
(s.get_int)(handle(&set), bad.as_ptr(), 0, &mut iv),
status::ERR_VALUE
);
}
}
/// 未定义属性 get → ErrUnknown;define 后空数组维度 0 且越界读
/// → BadIndex;负索引 → BadIndex。
#[test]
fn get_missing_empty_and_bounds() {
let s = suite_v1();
let set = PropertySet::new();
let n = cs("a");
let mut iv = 0;
unsafe {
assert_eq!(
(s.get_int)(handle(&set), n.as_ptr(), 0, &mut iv),
status::ERR_UNKNOWN
);
assert_eq!(
(s.get_int)(handle(&set), n.as_ptr(), -1, &mut iv),
status::ERR_UNKNOWN,
"未定义属性先报 Unknown(HS 先查类型)"
);
}
set.define("a", vec![]);
unsafe {
assert_eq!(
(s.get_int)(handle(&set), n.as_ptr(), 0, &mut iv),
status::ERR_BAD_INDEX
);
}
set.define("a", vec![Value::Int(3)]);
unsafe {
assert_eq!(
(s.get_int)(handle(&set), n.as_ptr(), -1, &mut iv),
status::ERR_BAD_INDEX
);
assert_eq!(
(s.get_int)(handle(&set), n.as_ptr(), 1, &mut iv),
status::ERR_BAD_INDEX
);
}
}
/// propSet 隐式创建:index 2 建出维度 3 的属性且前导槽位同值;
/// 已定义数组 index == 维度追加、越界 → BadIndex。
#[test]
fn set_creates_and_appends() {
let s = suite_v1();
let set = PropertySet::new();
let n = cs("a");
unsafe {
assert_eq!((s.set_int)(handle(&set), n.as_ptr(), 2, 7), 0);
}
assert_eq!(set.dimension("a"), 3);
assert_value(&set, "a", 0, &Value::Int(7));
assert_value(&set, "a", 2, &Value::Int(7));
// index == 维度 → 追加(协商属性逐位增长语义)。
unsafe {
assert_eq!((s.set_int)(handle(&set), n.as_ptr(), 3, 9), 0);
}
assert_eq!(set.dimension("a"), 4);
assert_value(&set, "a", 3, &Value::Int(9));
// 越界(跳过追加位)→ BadIndex。
unsafe {
assert_eq!(
(s.set_int)(handle(&set), n.as_ptr(), 9, 1),
status::ERR_BAD_INDEX
);
}
assert_eq!(set.dimension("a"), 4);
}
/// 空预定义数组:index 0 推入;index > 0 → BadIndex。
#[test]
fn set_into_empty_predefined_array() {
let s = suite_v1();
let set = PropertySet::new();
set.define("empty", vec![]);
let n = cs("empty");
unsafe {
assert_eq!(
(s.set_int)(handle(&set), n.as_ptr(), 1, 1),
status::ERR_BAD_INDEX
);
assert_eq!((s.set_int)(handle(&set), n.as_ptr(), 0, 5), 0);
}
assert_value(&set, "empty", 0, &Value::Int(5));
}
/// propSet 类型不符 → Unknown;数值 Int/Double 不互转(set 严格)。
#[test]
fn set_type_mismatch() {
let s = suite_v1();
let set = PropertySet::new();
set.define("i", vec![Value::Int(0)]);
set.define("d", vec![Value::Double(0.0)]);
let ni = cs("i");
let nd = cs("d");
unsafe {
assert_eq!(
(s.set_double)(handle(&set), ni.as_ptr(), 0, 1.0),
status::ERR_UNKNOWN
);
assert_eq!(
(s.set_int)(handle(&set), nd.as_ptr(), 0, 1),
status::ERR_UNKNOWN
);
}
}
/// propGet:Int ↔ Double 数值协随;混合数组元素类型不符 → Failed。
#[test]
fn get_numeric_coercion_and_mixed_arrays() {
let s = suite_v1();
let set = PropertySet::new();
set.define("i", vec![Value::Int(3)]);
set.define("d", vec![Value::Double(1.5)]);
let ni = cs("i");
let nd = cs("d");
let mut dv = 0.0;
let mut iv = 0;
unsafe {
assert_eq!((s.get_double)(handle(&set), ni.as_ptr(), 0, &mut dv), 0);
}
assert_eq!(dv, 3.0);
unsafe {
assert_eq!((s.get_int)(handle(&set), nd.as_ptr(), 0, &mut iv), 0);
}
assert_eq!(iv, 1);
// 混合数组(首元素定类型,后续元素类型不符)→ 单元素读 Failed。
let mix = PropertySet::new();
mix.define(
"m",
vec![Value::Double(1.0), Value::String(cs("x"))],
);
let nm = cs("m");
unsafe {
assert_eq!(
(s.get_double)(handle(&mix), nm.as_ptr(), 1, &mut dv),
status::FAILED
);
}
let mix2 = PropertySet::new();
mix2.define("m", vec![Value::Int(1), Value::String(cs("x"))]);
unsafe {
assert_eq!(
(s.get_int)(handle(&mix2), nm.as_ptr(), 1, &mut iv),
status::FAILED
);
}
}
/// 分量不足/超出的类型读:get_string 读数值属性 → Unknown(类型
/// 不符),get_double 读字符串 → Unknown。
#[test]
fn get_kind_mismatch() {
let s = suite_v1();
let set = PropertySet::new();
set.define("i", vec![Value::Int(1)]);
set.define("str", vec![Value::String(cs("s"))]);
let ni = cs("i");
let ns = cs("str");
let mut sv: *mut c_char = std::ptr::null_mut();
let mut dv = 0.0;
unsafe {
assert_eq!(
(s.get_string)(handle(&set), ni.as_ptr(), 0, &mut sv),
status::ERR_UNKNOWN
);
assert_eq!(
(s.get_double)(handle(&set), ns.as_ptr(), 0, &mut dv),
status::ERR_UNKNOWN
);
}
}
/// propGet 空 out 指针 → ErrValue(四种标量读取)。
#[test]
fn get_null_out_pointers() {
let s = suite_v1();
let set = PropertySet::new();
set.define("i", vec![Value::Int(1)]);
set.define("d", vec![Value::Double(1.0)]);
set.define("s", vec![Value::String(cs("x"))]);
set.define("p", vec![Value::Pointer(std::ptr::null_mut())]);
let n = cs("i");
unsafe {
assert_eq!(
(s.get_int)(handle(&set), n.as_ptr(), 0, std::ptr::null_mut()),
status::ERR_VALUE
);
assert_eq!(
(s.get_double)(handle(&set), n.as_ptr(), 0, std::ptr::null_mut()),
status::ERR_VALUE
);
assert_eq!(
(s.get_string)(handle(&set), n.as_ptr(), 0, std::ptr::null_mut()),
status::ERR_VALUE
);
assert_eq!(
(s.get_pointer)(handle(&set), n.as_ptr(), 0, std::ptr::null_mut()),
status::ERR_VALUE
);
}
}
/// propGetPointer:成功写出指针;混合数组类型不符 → Failed。
#[test]
fn get_pointer_paths() {
let s = suite_v1();
let raw = 0x1234usize as *mut c_void;
let set = PropertySet::new();
set.define("p", vec![Value::Pointer(raw)]);
let np = cs("p");
let mut out: *mut c_void = std::ptr::null_mut();
unsafe {
assert_eq!((s.get_pointer)(handle(&set), np.as_ptr(), 0, &mut out), 0);
}
assert_eq!(out, raw);
let mix = PropertySet::new();
mix.define(
"p",
vec![Value::Pointer(raw), Value::Int(1)],
);
unsafe {
assert_eq!(
(s.get_pointer)(handle(&mix), np.as_ptr(), 1, &mut out),
status::FAILED
);
}
}
/// propGetString:写出内驻指针且内容正确;混合数组 → Failed。
#[test]
fn get_string_paths() {
let s = suite_v1();
let set = PropertySet::new();
set.define("s", vec![Value::String(cs("hello"))]);
let ns = cs("s");
let mut out: *mut c_char = std::ptr::null_mut();
unsafe {
assert_eq!((s.get_string)(handle(&set), ns.as_ptr(), 0, &mut out), 0);
assert_eq!(CStr::from_ptr(out).to_bytes(), b"hello");
}
let mix = PropertySet::new();
mix.define(
"s",
vec![Value::String(cs("x")), Value::Int(1)],
);
unsafe {
assert_eq!(
(s.get_string)(handle(&mix), ns.as_ptr(), 1, &mut out),
status::FAILED
);
}
}
/// propSetString:NULL 值 → ErrValue;截断到首个 NUL。
#[test]
fn set_string_null_and_truncation() {
let s = suite_v1();
let set = PropertySet::new();
let n = cs("s");
unsafe {
assert_eq!(
(s.set_string)(handle(&set), n.as_ptr(), 0, std::ptr::null()),
status::ERR_VALUE
);
}
}
/// propSetN 家族:隐式创建、维度替换/逐位写、空数组与 NULL 参数。
#[test]
fn set_n_families() {
let s = suite_v1();
let set = PropertySet::new();
// set_int_n:隐式创建 2 维。
let ni = cs("ints");
let ivals = [1 as c_int, 2];
unsafe {
assert_eq!(
(s.set_int_n)(handle(&set), ni.as_ptr(), 2, ivals.as_ptr()),
0
);
}
assert_eq!(set.dimension("ints"), 2);
assert_value(&set, "ints", 1, &Value::Int(2));
// count 与现有维度相同 → 逐位覆盖。
let ivals2 = [7 as c_int, 8];
unsafe {
assert_eq!(
(s.set_int_n)(handle(&set), ni.as_ptr(), 2, ivals2.as_ptr()),
0
);
}
assert_value(&set, "ints", 0, &Value::Int(7));
// 维度变化 → 整体替换。
let ivals3 = [9 as c_int];
unsafe {
assert_eq!(
(s.set_int_n)(handle(&set), ni.as_ptr(), 1, ivals3.as_ptr()),
0
);
}
assert_eq!(set.dimension("ints"), 1);
// count 0 + NULL:合法(空维度替换)。
unsafe {
assert_eq!(
(s.set_int_n)(handle(&set), ni.as_ptr(), 0, std::ptr::null()),
0
);
}
assert_eq!(set.dimension("ints"), 0);
// count > 0 + NULL → ErrValue;负 count → BadIndex。
unsafe {
assert_eq!(
(s.set_int_n)(handle(&set), ni.as_ptr(), 1, std::ptr::null()),
status::ERR_VALUE
);
assert_eq!(
(s.set_int_n)(handle(&set), ni.as_ptr(), -1, ivals.as_ptr()),
status::ERR_BAD_INDEX
);
}
// set_double_n:隐式创建 + NULL 分支。
let nd = cs("dbls");
let dvals = [1.5 as c_double, 2.5];
unsafe {
assert_eq!(
(s.set_double_n)(handle(&set), nd.as_ptr(), 2, dvals.as_ptr()),
0
);
assert_eq!(
(s.set_double_n)(handle(&set), nd.as_ptr(), 1, std::ptr::null()),
status::ERR_VALUE
);
}
assert_value(&set, "dbls", 1, &Value::Double(2.5));
// set_pointer_n:单元素与 count 0 + NULL。
let np = cs("ptrs");
let pvals = [0x55usize as *mut c_void];
unsafe {
assert_eq!(
(s.set_pointer_n)(handle(&set), np.as_ptr(), 1, pvals.as_ptr()),
0
);
assert_eq!(
(s.set_pointer_n)(handle(&set), np.as_ptr(), 1, std::ptr::null()),
status::ERR_VALUE
);
assert_eq!(
(s.set_pointer_n)(handle(&set), np.as_ptr(), 0, std::ptr::null()),
0
);
}
assert_eq!(set.dimension("ptrs"), 0);
// set_string_n:成功、NULL 数组、数组内 NULL 元素。
let nstr = cs("strs");
let sa = cs("a");
let sb = cs("b");
let svals = [sa.as_ptr(), sb.as_ptr()];
unsafe {
assert_eq!(
(s.set_string_n)(handle(&set), nstr.as_ptr(), 2, svals.as_ptr()),
0
);
assert_eq!(
(s.set_string_n)(handle(&set), nstr.as_ptr(), 1, std::ptr::null()),
status::ERR_VALUE
);
let with_null = [sa.as_ptr(), std::ptr::null()];
assert_eq!(
(s.set_string_n)(handle(&set), nstr.as_ptr(), 2, with_null.as_ptr()),
status::ERR_VALUE
);
}
assert_eq!(set.dimension("strs"), 2);
}
/// set_n 类型不符 → Unknown;已有数组 count 0 替换为空。
#[test]
fn set_n_type_mismatch_and_empty_replace() {
let s = suite_v1();
let set = PropertySet::new();
set.define("i", vec![Value::Int(1)]);
let ni = cs("i");
let dvals = [2.0 as c_double];
unsafe {
assert_eq!(
(s.set_double_n)(handle(&set), ni.as_ptr(), 1, dvals.as_ptr()),
status::ERR_UNKNOWN
);
// values 为空(count 0)→ 无类型探针,整体替换为空。
assert_eq!(
(s.set_double_n)(handle(&set), ni.as_ptr(), 0, dvals.as_ptr()),
0
);
}
assert_eq!(set.dimension("i"), 0);
}
/// propGetN:Int ↔ Double 协随、min(count, dim) 截断、混合数组写
/// 跳过不符元素。
#[test]
fn get_n_families() {
let s = suite_v1();
let set = PropertySet::new();
set.define("ints", vec![Value::Int(1), Value::Int(2)]);
set.define("dbls", vec![Value::Double(1.5), Value::Double(2.5)]);
// get_double_n 读 Int 属性 → 协随为 f64。
let ni = cs("ints");
let mut dout = [0.0 as c_double; 2];
unsafe {
assert_eq!(
(s.get_double_n)(handle(&set), ni.as_ptr(), 2, dout.as_mut_ptr()),
0
);
}
assert_eq!(dout, [1.0, 2.0]);
// get_int_n 读 Double 属性 → 截断为 i32。
let nd = cs("dbls");
let mut iout = [0 as c_int; 2];
unsafe {
assert_eq!(
(s.get_int_n)(handle(&set), nd.as_ptr(), 2, iout.as_mut_ptr()),
0
);
}
assert_eq!(iout, [1, 2]);
// count > 维度:只拷贝 min(count, dim)。
let mut one = [0 as c_int; 1];
unsafe {
assert_eq!(
(s.get_int_n)(handle(&set), ni.as_ptr(), 5, one.as_mut_ptr()),
0
);
}
assert_eq!(one, [1]);
// count 0:不写。
let mut zero = [7 as c_int; 1];
unsafe {
assert_eq!(
(s.get_int_n)(handle(&set), ni.as_ptr(), 0, zero.as_mut_ptr()),
0
);
}
assert_eq!(zero, [7]);
// get_pointer_n / get_string_n 成功路径。
let ptrs = PropertySet::new();
let raw = 0x99usize as *mut c_void;
ptrs.define("p", vec![Value::Pointer(raw)]);
let np = cs("p");
let mut pout = [std::ptr::null_mut(); 1];
unsafe {
assert_eq!(
(s.get_pointer_n)(handle(&ptrs), np.as_ptr(), 1, pout.as_mut_ptr()),
0
);
}
assert_eq!(pout[0], raw);
let strs = PropertySet::new();
strs.define("s", vec![Value::String(cs("hi"))]);
let ns = cs("s");
let mut sout = [std::ptr::null_mut(); 1];
unsafe {
assert_eq!(
(s.get_string_n)(handle(&strs), ns.as_ptr(), 1, sout.as_mut_ptr()),
0
);
assert_eq!(CStr::from_ptr(sout[0]).to_bytes(), b"hi");
}
}
/// propGetN 错误路径:空 out、未定义、空数组、类型不符、负 count。
#[test]
fn get_n_error_paths() {
let s = suite_v1();
let set = PropertySet::new();
set.define("ints", vec![Value::Int(1)]);
set.define("strs", vec![Value::String(cs("s"))]);
set.define("empty", vec![]);
let ni = cs("ints");
let ns = cs("strs");
let ne = cs("empty");
let nmissing = cs("missing");
let mut iout = [0 as c_int; 2];
let mut dout = [0.0 as c_double; 2];
let mut pout = [std::ptr::null_mut(); 2];
let mut sout = [std::ptr::null_mut(); 2];
unsafe {
// 空 out(count > 0)。
assert_eq!(
(s.get_int_n)(handle(&set), ni.as_ptr(), 1, std::ptr::null_mut()),
status::ERR_VALUE
);
assert_eq!(
(s.get_double_n)(handle(&set), ni.as_ptr(), 1, std::ptr::null_mut()),
status::ERR_VALUE
);
assert_eq!(
(s.get_pointer_n)(handle(&set), ni.as_ptr(), 1, std::ptr::null_mut()),
status::ERR_VALUE
);
assert_eq!(
(s.get_string_n)(handle(&set), ni.as_ptr(), 1, std::ptr::null_mut()),
status::ERR_VALUE
);
// 未定义 → Unknown;空数组 → Unknown(无类型探针)。
assert_eq!(
(s.get_int_n)(handle(&set), nmissing.as_ptr(), 1, iout.as_mut_ptr()),
status::ERR_UNKNOWN
);
assert_eq!(
(s.get_int_n)(handle(&set), ne.as_ptr(), 1, iout.as_mut_ptr()),
status::ERR_UNKNOWN
);
// 类型不符 → Unknown(数值族之间协随,其余严格)。
assert_eq!(
(s.get_int_n)(handle(&set), ns.as_ptr(), 1, iout.as_mut_ptr()),
status::ERR_UNKNOWN
);
assert_eq!(
(s.get_double_n)(handle(&set), ns.as_ptr(), 1, dout.as_mut_ptr()),
status::ERR_UNKNOWN
);
assert_eq!(
(s.get_pointer_n)(handle(&set), ni.as_ptr(), 1, pout.as_mut_ptr()),
status::ERR_UNKNOWN
);
assert_eq!(
(s.get_string_n)(handle(&set), ni.as_ptr(), 1, sout.as_mut_ptr()),
status::ERR_UNKNOWN
);
// 负 count → BadIndex(在 min 之前 idx 转换失败)。
assert_eq!(
(s.get_int_n)(handle(&set), ni.as_ptr(), -1, iout.as_mut_ptr()),
status::ERR_BAD_INDEX
);
}
}
/// propReset 明确不支持;propGetDimension 空 out/未定义/空数组。
#[test]
fn reset_and_dimension() {
let s = suite_v1();
let set = PropertySet::new();
set.define("a", vec![Value::Int(1)]);
set.define("empty", vec![]);
let na = cs("a");
let ne = cs("empty");
let nmissing = cs("missing");
let mut dim = -1;
unsafe {
assert_eq!(
(s.reset)(handle(&set), na.as_ptr()),
status::ERR_UNSUPPORTED
);
assert_eq!(
(s.get_dimension)(handle(&set), na.as_ptr(), std::ptr::null_mut()),
status::ERR_VALUE
);
assert_eq!(
(s.get_dimension)(handle(&set), nmissing.as_ptr(), &mut dim),
status::ERR_UNKNOWN
);
assert_eq!((s.get_dimension)(handle(&set), na.as_ptr(), &mut dim), 0);
assert_eq!(dim, 1);
assert_eq!((s.get_dimension)(handle(&set), ne.as_ptr(), &mut dim), 0);
assert_eq!(dim, 0, "已定义空数组是合法维度 0");
}
}
/// OAK_OFX_TRACE 开启时的诊断分支:所有 trace 打印点都被走到
/// (错误路径与成功路径)。测试串行化并还原环境变量。
#[test]
fn trace_diagnostic_branches() {
static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
let _g = LOCK.lock().unwrap_or_else(|e| e.into_inner());
let prev = std::env::var_os("OAK_OFX_TRACE");
// set_var/remove_var 在本 crate 的 2021 edition 中是安全函数。
std::env::set_var("OAK_OFX_TRACE", "1");
let body = std::panic::catch_unwind(|| {
let s = suite_v1();
let set = PropertySet::new();
let ni = cs("i");
let nstr = cs("str");
let nmissing = cs("missing");
set.define("i", vec![Value::Int(1)]);
set.define("str", vec![Value::String(cs("s"))]);
let mut iv = 0;
let mut dv = 0.0;
let mut sv: *mut c_char = std::ptr::null_mut();
let mut pv: *mut c_void = std::ptr::null_mut();
let mut dim = 0;
unsafe {
// caught 的 trace:非 OK 状态码。
assert_eq!(
(s.get_int)(handle(&set), nmissing.as_ptr(), 0, &mut iv),
status::ERR_UNKNOWN
);
// get_value trace:成功 + 失败。
assert_eq!((s.get_int)(handle(&set), ni.as_ptr(), 0, &mut iv), 0);
assert_eq!(
(s.get_int)(handle(&set), nmissing.as_ptr(), 0, &mut iv),
status::ERR_UNKNOWN
);
// 类型不符 trace。
assert_eq!(
(s.get_string)(handle(&set), ni.as_ptr(), 0, &mut sv),
status::ERR_UNKNOWN
);
// set_value:空数组越界 trace;越界 trace。
let empty = PropertySet::new();
empty.define("e", vec![]);
let ne = cs("e");
assert_eq!(
(s.set_int)(handle(&empty), ne.as_ptr(), 1, 1),
status::ERR_BAD_INDEX
);
assert_eq!(
(s.set_int)(handle(&set), ni.as_ptr(), 5, 2),
status::ERR_BAD_INDEX
);
// propGetPointer trace(成功)。
let ptrs = PropertySet::new();
ptrs.define("p", vec![Value::Pointer(0x11usize as *mut c_void)]);
let np = cs("p");
assert_eq!((s.get_pointer)(handle(&ptrs), np.as_ptr(), 0, &mut pv), 0);
// propGetString trace:成功 + 失败。
assert_eq!((s.get_string)(handle(&set), nstr.as_ptr(), 0, &mut sv), 0);
assert_eq!(
(s.get_string)(handle(&set), nmissing.as_ptr(), 0, &mut sv),
status::ERR_UNKNOWN
);
// propGetInt inspect_err trace。
assert_eq!(
(s.get_int)(handle(&set), nmissing.as_ptr(), 0, &mut iv),
status::ERR_UNKNOWN
);
// get_n trace:成功 dump + 未命中 inspect_err。
let mut iout = [0 as c_int; 2];
assert_eq!(
(s.get_int_n)(handle(&set), ni.as_ptr(), 2, iout.as_mut_ptr()),
0
);
assert_eq!(
(s.get_int_n)(handle(&set), nmissing.as_ptr(), 1, iout.as_mut_ptr()),
status::ERR_UNKNOWN
);
// dump 的 Double 与未知元素分支。
let mixed = PropertySet::new();
mixed.define(
"m",
vec![Value::Double(1.5), Value::String(cs("x"))],
);
let nm = cs("m");
let mut dout = [0.0 as c_double; 2];
assert_eq!(
(s.get_double_n)(handle(&mixed), nm.as_ptr(), 2, dout.as_mut_ptr()),
0
);
// 空 out 的 get_double trace 分支(Err 路径渲染)。
assert_eq!(
(s.get_double)(handle(&set), ni.as_ptr(), 0, &mut dv),
0
);
// propGetDimension 未定义 trace。
assert_eq!(
(s.get_dimension)(handle(&set), nmissing.as_ptr(), &mut dim),
status::ERR_UNKNOWN
);
}
});
match prev {
Some(v) => std::env::set_var("OAK_OFX_TRACE", v),
None => std::env::remove_var("OAK_OFX_TRACE"),
}
if let Err(p) = body {
std::panic::resume_unwind(p);
}
}
}