Stack overflow in serde + erased_serde

The following minimal example causes a stack overflow:

use serde::Serialize;
use erased_serde::serialize_trait_object;

serialize_trait_object!(MyTrait);
pub trait MyTrait {}

#[derive(Serialize)]
pub struct MyType { l: Box<dyn MyTrait> }

struct MyTraitImplementor;
impl MyTrait for MyTraitImplementor {}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let m = MyType { l: Box::new(MyTraitImplementor) };
    let json_file = std::fs::File::create("/tmp/serde.json")?;
    println!("   About to overflow the stack .....");
    serde_json::to_writer(&json_file, &m)?;
    Ok(())
}

Unfortunately it doesn't work in the playground, because erased_serde is not available there.

With the help of https://crates.io/crates/backtrace-on-stack-overflow, I can get a backtrace by applying the following change to the original example:

-    println!("   About to overflow the stack .....");
-    serde_json::to_writer(&json_file, &m)?;
+    let overflow_the_stack = || serde_json::to_writer(&json_file, &m);
+    unsafe { backtrace_on_stack_overflow::enable(overflow_the_stack).unwrap(); }
The most relevant portion of the backtrace

The top two groups (43386 and 43387) are repeated until the stack blows

43386: erased_serde::ser::serialize
             at /home/jacg/.cargo/registry/src/github.com-1ecc6299db9ec823/erased-serde-0.3.18/src/ser.rs:615:9
      <dyn mfe::MyTrait as serde::ser::Serialize>::serialize
             at /home/jacg/.cargo/registry/src/github.com-1ecc6299db9ec823/erased-serde-0.3.18/src/macros.rs:92:17
      <T as erased_serde::ser::Serialize>::erased_serialize
             at /home/jacg/.cargo/registry/src/github.com-1ecc6299db9ec823/erased-serde-0.3.18/src/ser.rs:243:9
43387: erased_serde::ser::serialize
             at /home/jacg/.cargo/registry/src/github.com-1ecc6299db9ec823/erased-serde-0.3.18/src/ser.rs:615:9
      <dyn mfe::MyTrait as serde::ser::Serialize>::serialize
             at /home/jacg/.cargo/registry/src/github.com-1ecc6299db9ec823/erased-serde-0.3.18/src/macros.rs:92:17
      <T as erased_serde::ser::Serialize>::erased_serialize
             at /home/jacg/.cargo/registry/src/github.com-1ecc6299db9ec823/erased-serde-0.3.18/src/ser.rs:243:9
43388: erased_serde::ser::serialize
             at /home/jacg/.cargo/registry/src/github.com-1ecc6299db9ec823/erased-serde-0.3.18/src/ser.rs:615:9
43389: <dyn mfe::MyTrait as serde::ser::Serialize>::serialize
             at /home/jacg/.cargo/registry/src/github.com-1ecc6299db9ec823/erased-serde-0.3.18/src/macros.rs:92:17
      <T as erased_serde::ser::Serialize>::erased_serialize
             at /home/jacg/.cargo/registry/src/github.com-1ecc6299db9ec823/erased-serde-0.3.18/src/ser.rs:243:9
      erased_serde::ser::serialize
             at /home/jacg/.cargo/registry/src/github.com-1ecc6299db9ec823/erased-serde-0.3.18/src/ser.rs:615:9
      <dyn mfe::MyTrait as serde::ser::Serialize>::serialize
             at /home/jacg/.cargo/registry/src/github.com-1ecc6299db9ec823/erased-serde-0.3.18/src/macros.rs:92:17
      serde::ser::impls::<impl serde::ser::Serialize for alloc::boxed::Box<T>>::serialize
             at /home/jacg/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.136/src/ser/impls.rs:390:17
      <serde_json::ser::Compound<W,F> as serde::ser::SerializeMap>::serialize_value
             at /home/jacg/.cargo/registry/src/github.com-1ecc6299db9ec823/serde_json-1.0.79/src/ser.rs:711:22
      serde::ser::SerializeMap::serialize_entry
             at /home/jacg/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.136/src/ser/mod.rs:1845:9
43390: <serde_json::ser::Compound<W,F> as serde::ser::SerializeStruct>::serialize_field
             at /home/jacg/.cargo/registry/src/github.com-1ecc6299db9ec823/serde_json-1.0.79/src/ser.rs:757:37
      mfe::_::<impl serde::ser::Serialize for mfe::MyType>::serialize
             at src/bin/mfe.rs:7:10
      serde_json::ser::to_writer
             at /home/jacg/.cargo/registry/src/github.com-1ecc6299db9ec823/serde_json-1.0.79/src/ser.rs:2161:10

Can you shed any light on what the problem is?

Is it possible to do what I am trying to do (use serde to serialize an object which contains a boxed trait object)?

If I understand correctly, the problem is in the incorrect usage of serialize_trait_object. Quoting the documentation:

Implement serde::Serialize for a trait object that has erased_serde::Serialize as a supertrait.

In other words, it's possible to serialize trait object only if it's guaranteed that you're able to serialize every specific sized implementation of the trait. In your code, this is not the case.

Indeed.

The problem is fixed with these changes:

-pub trait MyTrait {}
+pub trait MyTrait: erased_serde::Serialize {}
+#[derive(Serialize)]
 struct MyTraitImplementor;

In the real-world case from which this example was derived, MyTraitImplementor comes from an external crate and does implement Serialize (and the trait did have the erased_serde constraint, at least at some stage), but these details got lost somewhere in the process of simplification.

I do find it rather surprising to get infinite recursion in this case, rather than a type error. However convoluted the type error might have been, I'm sure it would have been more informative than the stack overflow.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.