This is an automated email from the ASF dual-hosted git repository.
tlopex pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git
The following commit(s) were added to refs/heads/main by this push:
new 1e50100d [FIX][Rust] Align traversal permissions and error context
with C++ (#802)
1e50100d is described below
commit 1e50100d010e1d9cbd9ef162e57af07cc7e22eaf
Author: Shushi Hong <[email protected]>
AuthorDate: Sat Sep 19 15:31:41 2026 -0400
[FIX][Rust] Align traversal permissions and error context with C++ (#802)
This PR fixes in-place permission handling and error context in Rust
structural traversal.
When a pre-order `structural_map` callback returns a new object, the
engine keeps the input's `Disallow` setting when processing its
children. With `Allow`, the replacement must still be uniquely owned
before it can be modified in place.
Traversal errors record the actual visited objects, including callback
replacements. Copying an error preserves its cause chain and existing
context. Adding traversal records creates a new context and leaves
retained errors and contexts unchanged.
The existing tests cover these changes across visit, walk, map, and
mutate, including object reuse, replacement identities, metadata
retention, and access-path resolution. No new test files or test
functions are added.
---
rust/tvm-ffi-sys/src/c_api.rs | 10 +
rust/tvm-ffi/src/error.rs | 62 ++++-
rust/tvm-ffi/src/extra/structural_common.rs | 77 +++++-
rust/tvm-ffi/src/extra/structural_mutate.rs | 32 ++-
rust/tvm-ffi/src/extra/structural_mutate/policy.rs | 2 +
rust/tvm-ffi/src/extra/structural_visit.rs | 12 +-
rust/tvm-ffi/tests/test_error.rs | 24 ++
rust/tvm-ffi/tests/test_structural_mutate.rs | 303 +++++++++++++++++----
8 files changed, 444 insertions(+), 78 deletions(-)
diff --git a/rust/tvm-ffi-sys/src/c_api.rs b/rust/tvm-ffi-sys/src/c_api.rs
index 9d828c3d..454956ac 100644
--- a/rust/tvm-ffi-sys/src/c_api.rs
+++ b/rust/tvm-ffi-sys/src/c_api.rs
@@ -301,6 +301,8 @@ pub struct TVMFFIErrorCell {
backtrace: *const TVMFFIByteArray,
update_mode: i32,
),
+ pub cause_chain: TVMFFIObjectHandle,
+ pub extra_context: TVMFFIObjectHandle,
}
/// Shape cell used in shape object following header.
@@ -503,6 +505,14 @@ unsafe extern "C" {
backtrace: *const TVMFFIByteArray,
out: *mut TVMFFIObjectHandle,
) -> i32;
+ pub fn TVMFFIErrorCreateWithCauseAndExtraContext(
+ kind: *const TVMFFIByteArray,
+ message: *const TVMFFIByteArray,
+ backtrace: *const TVMFFIByteArray,
+ cause_chain: TVMFFIObjectHandle,
+ extra_context: TVMFFIObjectHandle,
+ out: *mut TVMFFIObjectHandle,
+ ) -> i32;
pub fn TVMFFITensorFromDLPack(
from: *mut c_void,
require_alignment: i32,
diff --git a/rust/tvm-ffi/src/error.rs b/rust/tvm-ffi/src/error.rs
index cb6dabb1..f8b7e524 100644
--- a/rust/tvm-ffi/src/error.rs
+++ b/rust/tvm-ffi/src/error.rs
@@ -17,12 +17,12 @@
* under the License.
*/
use crate::derive::{Object, ObjectRef};
-use crate::object::{Object, ObjectArc};
+use crate::object::{self, Object, ObjectArc, ObjectRefCore};
use std::ffi::c_void;
use tvm_ffi_sys::TVMFFIBacktraceUpdateMode::kTVMFFIBacktraceUpdateModeAppend;
use tvm_ffi_sys::{
- TVMFFIByteArray, TVMFFIErrorCell, TVMFFIErrorCreate,
TVMFFIErrorMoveFromRaised,
- TVMFFIErrorSetRaised, TVMFFIObjectHandle, TVMFFITypeIndex,
+ TVMFFIByteArray, TVMFFIErrorCell,
TVMFFIErrorCreateWithCauseAndExtraContext,
+ TVMFFIErrorMoveFromRaised, TVMFFIErrorSetRaised, TVMFFIObjectHandle,
TVMFFITypeIndex,
};
/// Error kind, wraps in a struct to be explicit
@@ -69,15 +69,32 @@ pub type Result<T, E = Error> = std::result::Result<T, E>;
impl Error {
pub fn new(kind: ErrorKind<'_>, message: &str, traceback: &str) -> Self {
+ Self::new_with_cause_and_extra_context(kind, message, traceback, None,
None)
+ }
+
+ /// Create an error retaining its cause and any application-specific
context.
+ pub fn new_with_cause_and_extra_context(
+ kind: ErrorKind<'_>,
+ message: &str,
+ traceback: &str,
+ cause_chain: Option<&Error>,
+ extra_context: Option<&object::ObjectRef>,
+ ) -> Self {
unsafe {
let kind_data = TVMFFIByteArray::from_str(kind.as_str());
let message_data = TVMFFIByteArray::from_str(message);
let traceback_data = TVMFFIByteArray::from_str(traceback);
let mut error_handle: TVMFFIObjectHandle = std::ptr::null_mut();
- let ret = TVMFFIErrorCreate(
+ let ret = TVMFFIErrorCreateWithCauseAndExtraContext(
&kind_data,
&message_data,
&traceback_data,
+ cause_chain.map_or(std::ptr::null_mut(), |cause| {
+ ObjectArc::as_raw(&cause.data) as TVMFFIObjectHandle
+ }),
+ extra_context.map_or(std::ptr::null_mut(), |context| {
+ ObjectArc::as_raw(object::ObjectRef::data(context)) as
TVMFFIObjectHandle
+ }),
&mut error_handle,
);
assert_eq!(ret, 0, "Failed to create error object");
@@ -137,6 +154,35 @@ impl Error {
self.data.cell.backtrace.as_str()
}
+ /// Return the cause, if one was attached to this error.
+ pub fn cause_chain(&self) -> Option<Error> {
+ let handle = self.data.cell.cause_chain;
+ if handle.is_null() {
+ return None;
+ }
+ // The cell owns the handle; acquire a reference for the returned
wrapper.
+ unsafe {
+ object::unsafe_::inc_ref(handle.cast());
+ Some(Self {
+ data: ObjectArc::from_raw(handle.cast()),
+ })
+ }
+ }
+
+ /// Return the application-specific context, if present.
+ pub fn extra_context(&self) -> Option<object::ObjectRef> {
+ let handle = self.data.cell.extra_context;
+ if handle.is_null() {
+ return None;
+ }
+ unsafe {
+ object::unsafe_::inc_ref(handle.cast());
+ Some(object::ObjectRef::from_data(ObjectArc::from_raw(
+ handle.cast(),
+ )))
+ }
+ }
+
/// Get the traceback of the error in the order of most recent call last
///
/// # Returns
@@ -179,7 +225,13 @@ impl Error {
let mut new_backtrace = String::new();
new_backtrace.push_str(this.backtrace());
new_backtrace.push_str(backtrace);
- return Error::new(this.kind(), this.message(), &new_backtrace);
+ Self::new_with_cause_and_extra_context(
+ this.kind(),
+ this.message(),
+ &new_backtrace,
+ this.cause_chain().as_ref(),
+ this.extra_context().as_ref(),
+ )
}
}
}
diff --git a/rust/tvm-ffi/src/extra/structural_common.rs
b/rust/tvm-ffi/src/extra/structural_common.rs
index 3447692f..5fb9bfd9 100644
--- a/rust/tvm-ffi/src/extra/structural_common.rs
+++ b/rust/tvm-ffi/src/extra/structural_common.rs
@@ -19,14 +19,87 @@
use crate::any::{Any, AnyView};
use crate::error::Error;
-use crate::object::{self, ObjectCore};
-use crate::tvm_ffi_sys::{TVMFFIAny, TVMFFIGetTypeInfo, TVMFFITypeIndex};
+use crate::function::Function;
+use crate::object::{self, ObjectCore, ObjectRefCore};
+use crate::reflection::FieldGetter;
+use crate::tvm_ffi_sys::{
+ TVMFFIAny, TVMFFIByteArray, TVMFFIGetTypeInfo, TVMFFITypeIndex,
TVMFFITypeKeyToIndex,
+};
/// Add one structural traversal frame to an error's backtrace.
pub(crate) fn with_structural_error_context(error: Error, operation: &str,
frame: &str) -> Error {
Error::with_appended_backtrace(error, &format!("[native structural
{operation}] {frame}\n"))
}
+#[cold]
+pub(crate) fn with_visit_error_context(error: Error, raw: TVMFFIAny) -> Error {
+ if raw.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
+ return error;
+ }
+ let context = (|| {
+ let mut context_type = 0;
+ unsafe {
+ crate::check_safe_call!(TVMFFITypeKeyToIndex(
+ &TVMFFIByteArray::from_str("ffi.VisitErrorContext"),
+ &mut context_type,
+ ))
+ .ok()?;
+ }
+ let mut previous = error.extra_context();
+ let mut nodes = Vec::new();
+ if let Some(prior) = previous.as_ref() {
+ if AnyView::from(prior).type_index() == context_type {
+ let object = &**object::ObjectRef::data(prior);
+ let records = FieldGetter::new(context_type,
"reverse_visit_pattern")
+ .ok()?
+ .get_any(object)
+ .ok()?;
+ let size = Function::get_global("ffi.ListSize")
+ .ok()?
+ .call_tuple((records.clone(),))
+ .ok()?
+ .try_as::<i64>()?;
+ let get_item = Function::get_global("ffi.ListGetItem").ok()?;
+ for i in 0..size {
+ nodes.push(get_item.call_tuple((records.clone(),
i)).ok()?);
+ }
+ previous = FieldGetter::new(context_type, "prev_error_context")
+ .ok()?
+ .get::<_, Option<object::ObjectRef>>(object)
+ .ok()?;
+ }
+ }
+ let node = StructuralView::from_raw(raw).cast::<object::ObjectRef>()?;
+ nodes.push(Any::from(node));
+ let records = Function::get_global("ffi.List")
+ .ok()?
+ .call_packed(&nodes.iter().map(AnyView::from).collect::<Vec<_>>())
+ .ok()?;
+ Function::get_global("ffi.MakeObjectFromPackedArgs")
+ .ok()?
+ .call_tuple((
+ context_type,
+ crate::String::from("reverse_visit_pattern"),
+ records,
+ crate::String::from("prev_error_context"),
+ previous,
+ ))
+ .ok()?
+ .try_as::<object::ObjectRef>()
+ })();
+ // Diagnostic enrichment must not replace the original error on failure.
+ match context {
+ Some(context) => Error::new_with_cause_and_extra_context(
+ error.kind(),
+ error.message(),
+ error.backtrace(),
+ error.cause_chain().as_ref(),
+ Some(&context),
+ ),
+ None => error,
+ }
+}
+
// Generate the tuple arities supported by the standard library (1 through 12).
macro_rules! impl_callback_chain_tuple_arities {
($impl_chain:ident) => {
diff --git a/rust/tvm-ffi/src/extra/structural_mutate.rs
b/rust/tvm-ffi/src/extra/structural_mutate.rs
index 1f656142..366d4924 100644
--- a/rust/tvm-ffi/src/extra/structural_mutate.rs
+++ b/rust/tvm-ffi/src/extra/structural_mutate.rs
@@ -50,7 +50,7 @@ use crate::tvm_ffi_sys::{TVMFFIObjectHandle,
TVMFFISEqHashKind};
use super::structural_common::{
impl_callback_chain_tuple_arities, is_plain_inline, same_shallow,
- try_to_owned_without_normalization, with_structural_error_context,
+ try_to_owned_without_normalization, with_structural_error_context,
with_visit_error_context,
};
use super::structural_visit::{
field_def_region, for_each_field_info, free_var_child_region,
type_attr_column, type_key_of,
@@ -2014,7 +2014,7 @@ impl<D: MapDispatch, Policy: MutContextPolicy<D>>
NativeMapper<'_, D, Policy> {
// A pre-order callback may replace an inline leaf with a
subtree.
if self.order == WalkOrder::PreOrder &&
!is_plain_inline(mapped.type_index()) {
let descended =
- self.map_default_root(&mapped, def_region_kind,
Permit::MaybeInPlace)?;
+ self.map_default_root(&mapped, def_region_kind,
Permit::Copy)?;
Ok(if is_unchanged(&descended) {
mapped
} else {
@@ -2033,7 +2033,6 @@ impl<D: MapDispatch, Policy: MutContextPolicy<D>>
NativeMapper<'_, D, Policy> {
}
self.map_current_raw(raw, def_region_kind, permit)
- .map_err(|error| with_value_context(error, raw))
}
fn map_current_raw(
@@ -2044,12 +2043,15 @@ impl<D: MapDispatch, Policy: MutContextPolicy<D>>
NativeMapper<'_, D, Policy> {
) -> Result<Any> {
match self.order {
WalkOrder::PreOrder => {
+ // A replacement inherits the input's permission, established
+ // before the callback can acquire or release ownership.
+ let permit = permit.inplace_mode(raw).permit();
let value = StructuralView::from_raw(raw);
let Some(callback_result) = self.dispatch.dispatch_map(&value,
def_region_kind)
else {
return self.default_map_current_raw(raw, def_region_kind,
permit);
};
- let mapped = callback_result?;
+ let mapped = callback_result.map_err(|error|
with_value_context(error, raw))?;
let mapped_raw = *mapped.as_raw_ffi_any();
if is_unchanged(&mapped) || same_shallow(raw, mapped_raw) {
// Release the callback's temporary ownership before the
@@ -2057,8 +2059,7 @@ impl<D: MapDispatch, Policy: MutContextPolicy<D>>
NativeMapper<'_, D, Policy> {
drop(mapped);
self.default_map_current_raw(raw, def_region_kind, permit)
} else {
- let descended =
- self.map_default_root(&mapped, def_region_kind,
Permit::MaybeInPlace)?;
+ let descended = self.map_default_root(&mapped,
def_region_kind, permit)?;
Ok(if is_unchanged(&descended) {
mapped
} else {
@@ -2077,7 +2078,7 @@ impl<D: MapDispatch, Policy: MutContextPolicy<D>>
NativeMapper<'_, D, Policy> {
// Keep child rewrites when the callback leaves its input
unchanged.
match self.dispatch.dispatch_map(&value, def_region_kind) {
Some(Ok(result)) if is_unchanged(&result) => Ok(mapped),
- Some(result) => result,
+ Some(result) => result.map_err(|error|
with_value_context(error, mapped_raw)),
None => Ok(mapped),
}
}
@@ -2093,7 +2094,6 @@ impl<D: MapDispatch, Policy: MutContextPolicy<D>>
NativeMapper<'_, D, Policy> {
) -> Result<Any> {
let raw = *mapped.as_raw_ffi_any();
self.default_map_current_raw(raw, def_region_kind, permit)
- .map_err(|error| with_value_context(error, raw))
}
}
@@ -2978,6 +2978,7 @@ fn user_default_mutate<U: StructuralMutator>(
let value = StructuralView::from_raw(raw);
mutator.on_default_mutate(MutateValue::new(&value,
permit.inplace_mode(raw)), kind)
})
+ .map_err(|error| with_value_context(error, raw))
}
fn default_mutate_driver<D: MutationDriver>(
@@ -2985,6 +2986,16 @@ fn default_mutate_driver<D: MutationDriver>(
raw: TVMFFIAny,
def_region_kind: DefRegionKind,
permit: Permit,
+) -> Result<Any> {
+ default_mutate_driver_impl(driver, raw, def_region_kind, permit)
+ .map_err(|error| with_value_context(error, raw))
+}
+
+fn default_mutate_driver_impl<D: MutationDriver>(
+ driver: &mut D,
+ raw: TVMFFIAny,
+ def_region_kind: DefRegionKind,
+ permit: Permit,
) -> Result<Any> {
// Match C++ DefaultMutateExpected: a registered type hook owns any
// identity-remap policy for that type. Automatic remapping applies only
@@ -3213,7 +3224,10 @@ fn with_value_context(error: Error, raw: TVMFFIAny) ->
Error {
if raw.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
error
} else {
- with_error_context(error, &format!("object `{}`",
type_key_of(raw.type_index)))
+ with_error_context(
+ with_visit_error_context(error, raw),
+ &format!("object `{}`", type_key_of(raw.type_index)),
+ )
}
}
diff --git a/rust/tvm-ffi/src/extra/structural_mutate/policy.rs
b/rust/tvm-ffi/src/extra/structural_mutate/policy.rs
index 1d9cdd97..4dc2e092 100644
--- a/rust/tvm-ffi/src/extra/structural_mutate/policy.rs
+++ b/rust/tvm-ffi/src/extra/structural_mutate/policy.rs
@@ -78,6 +78,7 @@ pub(super) fn mutate_with_policy<State>(
value: MutateValue<'_>,
kind: DefRegionKind,
) -> Result<Any> {
+ let raw = value.value.raw();
with_mutation_region(kind, |kind| {
let mut ctx = MutateContext {
driver,
@@ -87,6 +88,7 @@ pub(super) fn mutate_with_policy<State>(
};
policy.default_mutate(value, &mut ctx).map(Any::from)
})
+ .map_err(|error| with_value_context(error, raw))
}
struct NextPolicy<'a, State, Policy> {
diff --git a/rust/tvm-ffi/src/extra/structural_visit.rs
b/rust/tvm-ffi/src/extra/structural_visit.rs
index 1ac54421..15109a32 100644
--- a/rust/tvm-ffi/src/extra/structural_visit.rs
+++ b/rust/tvm-ffi/src/extra/structural_visit.rs
@@ -65,7 +65,9 @@ use crate::tvm_ffi_sys::{
TVMFFIObject, TVMFFISEqHashKind, TVMFFITypeAttrColumn, TVMFFITypeIndex,
TVMFFITypeKeyToIndex,
};
-use super::structural_common::{impl_callback_chain_tuple_arities,
with_structural_error_context};
+use super::structural_common::{
+ impl_callback_chain_tuple_arities, with_structural_error_context,
with_visit_error_context,
+};
const STRUCTURAL_VISIT_ATTR: &str = "__s_visit__";
const FLAG_SEQ_HASH_IGNORE: i64 = kTVMFFIFieldFlagBitMaskSEqHashIgnore as i64;
@@ -1223,7 +1225,7 @@ impl<V: StructuralVisitor> ChildVisit for
UserChildren<'_, V> {
{
Ok(None) => Ok(()),
Ok(Some(interrupt)) => Err(NativeHalt::Interrupt(interrupt.value)),
- Err(error) => Err(NativeHalt::Error(error)),
+ Err(error) => Err(with_value_context(NativeHalt::Error(error),
child)),
}
}
}
@@ -1722,7 +1724,7 @@ unsafe fn runtime_user_visit<V: StructuralVisitor>(
match (&mut *context.cast::<V>()).visit(&StructuralView::from_raw(raw),
def_region_kind) {
Ok(None) => Ok(()),
Ok(Some(interrupt)) => Err(NativeHalt::Interrupt(interrupt.value)),
- Err(error) => Err(NativeHalt::Error(error)),
+ Err(error) => Err(with_value_context(NativeHalt::Error(error), raw)),
}
}
@@ -1918,6 +1920,10 @@ fn with_value_context(halt: NativeHalt, value:
TVMFFIAny) -> NativeHalt {
if value.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
halt
} else {
+ let halt = match halt {
+ NativeHalt::Error(error) =>
NativeHalt::Error(with_visit_error_context(error, value)),
+ interrupt => interrupt,
+ };
with_error_context(halt, &format!("object `{}`",
type_key_of(value.type_index)))
}
}
diff --git a/rust/tvm-ffi/tests/test_error.rs b/rust/tvm-ffi/tests/test_error.rs
index ba48dc90..1413340d 100644
--- a/rust/tvm-ffi/tests/test_error.rs
+++ b/rust/tvm-ffi/tests/test_error.rs
@@ -59,6 +59,30 @@ fn test_error_with_context() {
assert!(error0.backtrace().contains("test_error_with_context"));
let result = error_fn1(false).unwrap();
assert_eq!(result, 1);
+
+ let payload =
object::ObjectRef::try_from(Any::from(Array::new(vec![7i64]))).unwrap();
+ for shared in [false, true] {
+ let error = Error::new_with_cause_and_extra_context(
+ RUNTIME_ERROR,
+ "outer",
+ "origin",
+ Some(&error0),
+ Some(&payload),
+ );
+ let retained = shared.then(|| error.clone());
+ let output = Error::with_appended_backtrace(error, " frame");
+ assert_eq!(output.backtrace(), "origin frame");
+ assert!(output.cause_chain().unwrap().same_as(&error0));
+ assert!(output.extra_context().unwrap().same_as(&payload));
+ if let Some(retained) = retained {
+ assert_eq!(retained.backtrace(), "origin");
+ }
+ }
+ assert_eq!(ObjectArc::strong_count(Error::data(&error0)), 1);
+ assert_eq!(
+ ObjectArc::strong_count(object::ObjectRef::data(&payload)),
+ 1
+ );
}
#[test]
diff --git a/rust/tvm-ffi/tests/test_structural_mutate.rs
b/rust/tvm-ffi/tests/test_structural_mutate.rs
index fe1d0f81..15d36c17 100644
--- a/rust/tvm-ffi/tests/test_structural_mutate.rs
+++ b/rust/tvm-ffi/tests/test_structural_mutate.rs
@@ -22,12 +22,12 @@ use tvm_ffi::collections::map::MapObj;
use tvm_ffi::function::FunctionObj;
use tvm_ffi::object::ObjectRef;
use tvm_ffi::{
- dispatch, structural_map, structural_mutate, Any, AnyView, Array,
DefRegionKind,
- DefaultMutContextPolicy, Error, FieldGetter, Function, InplaceMode,
InplaceValue, IntoMapper,
- Map, MapDispatch, MapWithContextPolicy, MutContextPolicy, MutateCallbacks,
MutateContext,
- MutateValue, Mutator, Object, ObjectArc, ObjectRefCore, Result, String as
FfiString,
- StructuralMutator, StructuralVarRemap, StructuralView, TypeIndex,
Unchanged, UnchangedOr,
- WalkOrder, RUNTIME_ERROR,
+ dispatch, structural_map, structural_mutate, structural_visit,
structural_walk, Any, AnyView,
+ Array, DefRegionKind, DefaultMutContextPolicy, Error, FieldGetter,
Function, InplaceMode,
+ InplaceValue, IntoMapper, Map, MapDispatch, MapWithContextPolicy,
MutContextPolicy,
+ MutateCallbacks, MutateContext, MutateValue, Mutator, Object, ObjectArc,
ObjectRefCore, Result,
+ String as FfiString, StructuralMutator, StructuralVarRemap,
StructuralView, TypeIndex,
+ Unchanged, UnchangedOr, VisitContext, WalkOrder, WalkResult, RUNTIME_ERROR,
};
struct IncrementIntegers;
@@ -743,37 +743,168 @@ fn
reflected_object_without_shallow_copy_is_rejected_even_when_unchanged() {
#[test]
fn callback_errors_preserve_message_and_add_object_context() {
- let error = match structural_map(
- Array::new(vec![1i64]),
- |_integer: i64| -> Result<i64> {
- Err(Error::new(RUNTIME_ERROR, "mapper failed", "origin"))
- },
- WalkOrder::PostOrder,
- ) {
- Ok(_) => panic!("fallible structural mapper unexpectedly succeeded"),
- Err(error) => error,
- };
-
- assert_eq!(error.message(), "mapper failed");
- assert!(error.backtrace().contains("origin"));
- assert!(error.backtrace().contains("object `ffi.Array`"));
+ let child = reflected_object();
+ let root = call_global(
+ "ffi.MakeObjectFromPackedArgs",
+ &[
+ FfiString::from("testing.TestObjectPtrHolder").into(),
+ FfiString::from("value").into(),
+ child.clone(),
+ ],
+ );
+ let payload =
ObjectRef::try_from(Any::from(Array::new(vec![7i64]))).unwrap();
+ let records = call_global("ffi.List", &[child.clone()]);
+ let context = call_global(
+ "ffi.MakeObjectFromPackedArgs",
+ &[
+ FfiString::from("ffi.VisitErrorContext").into(),
+ FfiString::from("reverse_visit_pattern").into(),
+ records,
+ FfiString::from("prev_error_context").into(),
+ Any::from(payload.clone()),
+ ],
+ );
+ let cause = Error::new(RUNTIME_ERROR, "cause", "");
+ let source = Error::new_with_cause_and_extra_context(
+ RUNTIME_ERROR,
+ "callback failed",
+ "origin",
+ Some(&cause),
+ Some(&ObjectRef::try_from(context.clone()).unwrap()),
+ );
+ let mut errors = Vec::new();
+ for order in [WalkOrder::PreOrder, WalkOrder::PostOrder] {
+ let leaf_error = structural_map(
+ 1i64,
+ |_value: i64| -> Result<i64> { Err(source.clone()) },
+ order,
+ )
+ .err()
+ .unwrap();
+ assert!(leaf_error.same_as(&source));
+ errors.push(
+ structural_map(
+ root.clone(),
+ |_value: i64| -> Result<i64> { Err(source.clone()) },
+ order,
+ )
+ .err()
+ .unwrap(),
+ );
+ errors.push(
+ structural_walk(
+ &root,
+ |_value: i64| -> Result<WalkResult> { Err(source.clone()) },
+ order,
+ )
+ .err()
+ .unwrap(),
+ );
+ }
+ errors.push(
+ structural_mutate(
+ root.clone(),
+ |_value: i64, _: &mut MutateContext| -> Result<i64> {
Err(source.clone()) },
+ )
+ .err()
+ .unwrap(),
+ );
+ errors.push(
+ structural_visit(
+ &root,
+ |_value: i64, _: &mut VisitContext<'_, ()>| -> Result<()> {
Err(source.clone()) },
+ )
+ .err()
+ .unwrap(),
+ );
+ for error in errors {
+ assert_eq!(error.message(), "callback failed");
+ assert!(error.backtrace().contains("origin"));
+ assert!(error
+ .backtrace()
+ .contains("object `testing.TestObjectBase`"));
+ assert!(error.cause_chain().unwrap().same_as(&cause));
+ let context = Any::from(error.extra_context().unwrap());
+ assert!(reflected_field::<ObjectRef>(&context,
"prev_error_context").same_as(&payload));
+ let records = Any::from(reflected_field::<ObjectRef>(
+ &context,
+ "reverse_visit_pattern",
+ ));
+ let size = i64::try_from(call_global("ffi.ListSize",
&[records.clone()])).unwrap();
+ let outermost = call_global("ffi.ListGetItem", &[records, (size -
1).into()]);
+ assert_eq!(any_object_pointer(&outermost), any_object_pointer(&root));
+ let paths = call_global(
+ "ffi.VisitErrorContext.FindAccessPaths",
+ &[root.clone(), context, false.into()],
+ );
+ let paths = paths.try_as::<Array<Any>>().unwrap();
+ assert_eq!(paths.len(), 1);
+ assert_eq!(
+ call_global("ffi.ReprPrint", &[paths.get(0).unwrap()])
+ .try_as::<FfiString>()
+ .unwrap()
+ .as_str(),
+ "<root>.value"
+ );
+ }
+ let records = Any::from(reflected_field::<ObjectRef>(
+ &context,
+ "reverse_visit_pattern",
+ ));
+ assert_eq!(
+ i64::try_from(call_global("ffi.ListSize", &[records])).unwrap(),
+ 1
+ );
+ assert_eq!(source.backtrace(), "origin");
- let error = match structural_mutate(
- Array::new(vec![1i64]),
- |_integer: i64, _mutator: &mut MutateContext<'_>| -> Result<i64> {
- Err(Error::new(
- RUNTIME_ERROR,
- "callback mutator failed",
- "callback origin",
- ))
- },
- ) {
- Ok(_) => panic!("fallible callback mutator unexpectedly succeeded"),
- Err(error) => error,
- };
- assert_eq!(error.message(), "callback mutator failed");
- assert!(error.backtrace().contains("callback origin"));
- assert!(error.backtrace().contains("object `ffi.Array`"));
+ // The failing node may be a pre-order replacement or a post-order rebuilt
parent.
+ for order in [WalkOrder::PreOrder, WalkOrder::PostOrder] {
+ let root = Array::new(vec![0i64]);
+ let mut failed_node = None;
+ let error = structural_map(
+ root.clone(),
+ (
+ |value: Array<i64>| -> Result<Any> {
+ let value = if order == WalkOrder::PreOrder {
+ Array::new(vec![1i64])
+ } else {
+ value
+ };
+ failed_node = Some(value.clone());
+ if order == WalkOrder::PreOrder {
+ Ok(value.into())
+ } else {
+ Err(Error::new(RUNTIME_ERROR, "failed", ""))
+ }
+ },
+ |value: i64| -> Result<i64> {
+ if order == WalkOrder::PreOrder {
+ Err(Error::new(RUNTIME_ERROR, "failed", ""))
+ } else {
+ Ok(value + 1)
+ }
+ },
+ ),
+ order,
+ )
+ .err()
+ .unwrap();
+ let context = Any::from(error.extra_context().unwrap());
+ let records = Any::from(reflected_field::<ObjectRef>(
+ &context,
+ "reverse_visit_pattern",
+ ));
+ let node = call_global("ffi.ListGetItem", &[records.clone(),
0i64.into()]);
+ assert!(node
+ .try_as::<ObjectRef>()
+ .unwrap()
+ .same_as(&failed_node.unwrap()));
+ assert_eq!(
+ i64::try_from(call_global("ffi.ListSize", &[records])).unwrap(),
+ 1
+ );
+ assert_eq!(root.get(0).unwrap(), 0);
+ }
}
#[test]
@@ -1076,31 +1207,85 @@ fn
generated_mutate_dispatch_can_default_recurse_from_a_typed_handler() {
}
#[test]
-fn pre_order_retained_alias_disables_in_place_mutation() {
- let root = call_global("ffi.List", &[Any::from(1i64)]);
- let root_pointer = any_object_pointer(&root);
- let mut retained = None;
- let mapped = structural_map(
- root,
- |value: &StructuralView| {
- if value.type_index() == TypeIndex::kTVMFFIList as i32 {
- retained = Some(value.to_owned());
- value.to_owned()
- } else if let Some(integer) = value.cast::<i64>() {
- Any::from(integer + 1)
+fn pre_order_mapping_preserves_inplace_permission() {
+ struct Observe<'a>(&'a Cell<Option<InplaceMode>>);
+ impl<State> MutContextPolicy<State> for Observe<'_> {
+ fn default_mutate(
+ &self,
+ value: MutateValue<'_>,
+ ctx: &mut tvm_ffi::MutateContext<'_, State>,
+ ) -> Result<UnchangedOr<Any>> {
+ if value.type_index() >= TypeIndex::kTVMFFIStaticObjectBegin as
i32 {
+ self.0.set(Some(value.inplace_mode()));
+ }
+ ctx.default_maybe_inplace_mutate_result(value)
+ }
+ }
+ for with_policy in [false, true] {
+ for case in ["inline", "unique", "shared", "retained"] {
+ let root = match case {
+ "inline" => Any::from(true),
+ "retained" => call_global("ffi.List", &[Any::from(1_i64)]),
+ _ => Any::from(Array::new(vec![true])),
+ };
+ let root_pointer = (case == "retained").then(||
any_object_pointer(&root));
+ let alias = (case == "shared").then(|| root.clone());
+ let mut retained = None;
+ let mut pointer = std::ptr::null();
+ let mode = Cell::new(None);
+ let mut mapper = (|value: &StructuralView| {
+ if let Some(integer) = value.cast::<i64>() {
+ return Any::from(integer + 1);
+ }
+ let mapped = if case == "retained" {
+ retained = Some(value.to_owned());
+ value.to_owned()
+ } else {
+ Array::new(vec![1_i64]).into()
+ };
+ pointer = any_object_pointer(&mapped);
+ mapped
+ })
+ .into_mapper();
+ let output = if with_policy {
+ structural_map(
+ root,
+ MapWithContextPolicy::new(&mut mapper, Observe(&mode)),
+ WalkOrder::PreOrder,
+ )
+ .unwrap()
+ } else {
+ structural_map(root, &mut mapper, WalkOrder::PreOrder).unwrap()
+ };
+ let reuse = case == "unique";
+ assert_eq!(
+ any_object_pointer(&output) == pointer,
+ reuse,
+ "{case}, policy={with_policy}"
+ );
+ if with_policy {
+ assert_eq!(
+ mode.get(),
+ Some(if reuse {
+ InplaceMode::Allow
+ } else {
+ InplaceMode::Disallow
+ })
+ );
+ }
+ if case == "retained" {
+ let retained = retained.unwrap();
+ assert_eq!(any_object_pointer(&retained),
root_pointer.unwrap());
+ assert_eq!(list_item(&retained, 0), 1);
+ assert_eq!(list_item(&output, 0), 2);
} else {
- value.to_owned()
+
assert_eq!(Array::<i64>::try_from(output).unwrap().get(0).unwrap(), 2);
}
- },
- WalkOrder::PreOrder,
- )
- .unwrap();
- let retained = retained.unwrap();
-
- assert_eq!(any_object_pointer(&retained), root_pointer);
- assert_ne!(any_object_pointer(&mapped), root_pointer);
- assert_eq!(list_item(&retained, 0), 1);
- assert_eq!(list_item(&mapped, 0), 2);
+ if let Some(alias) = alias {
+
assert!(Array::<bool>::try_from(alias).unwrap().get(0).unwrap());
+ }
+ }
+ }
}
#[test]