json icon indicating copy to clipboard operation
json copied to clipboard

`Value`s blow the stack on drop when deeply nested

Open brson opened this issue 6 years ago • 8 comments

It seems like Value has the default recursive Drop implementation, and if it gets too deep then Drop hits the end of the stack.

I recently had to write a wrapper type that took apart a serde_json::Value to avoid it recursing. Seems like this could be something serde_json might do on its own.

Didn't see an existing issue on this.

brson avatar May 01 '18 22:05 brson

Wow, thanks. Can you share the code for your wrapper or send a PR?

dtolnay avatar May 01 '18 22:05 dtolnay

@dtolnay I took a stab at a patch, but unfortunately it's complicated to do in-tree because implementing Drop makes it impossible to destructure Value.

Here's my branch anyway: https://github.com/brson/json/tree/drop

And the commit: https://github.com/brson/json/commit/25a12c71f14f4b7112ed9f016fed31e966d0c466

This is similar to what I implemented privately in a wrapper but different because I didn't need to handle the case when Array is the root, so IDK if it's quite right. That's the gist anyway.

brson avatar May 01 '18 23:05 brson

Here's a selection of the build errors that happen when adding Drop:

    |     ------------------------------------------ in this macro invocation

error[E0509]: cannot move out of type `value::Value`, which implements the `Drop` trait
   --> src/value/de.rs:234:17
    |
234 |                 Value::Number(n) => n.deserialize_any(visitor),
    |                 ^^^^^^^^^^^^^^-^
    |                 |             |
    |                 |             hint: to prevent move, use `ref n` or `ref mut n`
    |                 cannot move out of here
...
308 |     deserialize_prim_number!(deserialize_i32);
    |     ------------------------------------------ in this macro invocation

error[E0509]: cannot move out of type `value::Value`, which implements the `Drop` trait
   --> src/value/de.rs:234:17
    |
234 |                 Value::Number(n) => n.deserialize_any(visitor),
    |                 ^^^^^^^^^^^^^^-^
    |                 |             |
    |                 |             hint: to prevent move, use `ref n` or `ref mut n`
    |                 cannot move out of here
...
309 |     deserialize_prim_number!(deserialize_i64);
    |     ------------------------------------------ in this macro invocation

error[E0509]: cannot move out of type `value::Value`, which implements the `Drop` trait
   --> src/value/de.rs:234:17
    |
234 |                 Value::Number(n) => n.deserialize_any(visitor),
    |                 ^^^^^^^^^^^^^^-^
    |                 |             |
    |                 |             hint: to prevent move, use `ref n` or `ref mut n`
    |                 cannot move out of here
...
310 |     deserialize_prim_number!(deserialize_u8);
    |     ----------------------------------------- in this macro invocation

error[E0509]: cannot move out of type `value::Value`, which implements the `Drop` trait
   --> src/value/de.rs:234:17
    |
234 |                 Value::Number(n) => n.deserialize_any(visitor),
    |                 ^^^^^^^^^^^^^^-^
    |                 |             |
    |                 |             hint: to prevent move, use `ref n` or `ref mut n`
    |                 cannot move out of here
...
311 |     deserialize_prim_number!(deserialize_u16);
    |     ------------------------------------------ in this macro invocation

error[E0509]: cannot move out of type `value::Value`, which implements the `Drop` trait
   --> src/value/de.rs:234:17
    |
234 |                 Value::Number(n) => n.deserialize_any(visitor),
    |                 ^^^^^^^^^^^^^^-^
    |                 |             |
    |                 |             hint: to prevent move, use `ref n` or `ref mut n`
    |                 cannot move out of here
...
312 |     deserialize_prim_number!(deserialize_u32);
    |     ------------------------------------------ in this macro invocation

error[E0509]: cannot move out of type `value::Value`, which implements the `Drop` trait
   --> src/value/de.rs:234:17
    |
234 |                 Value::Number(n) => n.deserialize_any(visitor),
    |                 ^^^^^^^^^^^^^^-^
    |                 |             |
    |                 |             hint: to prevent move, use `ref n` or `ref mut n`
    |                 cannot move out of here
...
313 |     deserialize_prim_number!(deserialize_u64);
    |     ------------------------------------------ in this macro invocation

error[E0509]: cannot move out of type `value::Value`, which implements the `Drop` trait
   --> src/value/de.rs:234:17
    |
234 |                 Value::Number(n) => n.deserialize_any(visitor),
    |                 ^^^^^^^^^^^^^^-^
    |                 |             |
    |                 |             hint: to prevent move, use `ref n` or `ref mut n`
    |                 cannot move out of here
...
314 |     deserialize_prim_number!(deserialize_f32);
    |     ------------------------------------------ in this macro invocation

error[E0509]: cannot move out of type `value::Value`, which implements the `Drop` trait
   --> src/value/de.rs:234:17
    |
234 |                 Value::Number(n) => n.deserialize_any(visitor),
    |                 ^^^^^^^^^^^^^^-^
    |                 |             |
    |                 |             hint: to prevent move, use `ref n` or `ref mut n`
    |                 cannot move out of here
...
315 |     deserialize_prim_number!(deserialize_f64);
    |     ------------------------------------------ in this macro invocation

error[E0509]: cannot move out of type `value::Value`, which implements the `Drop` trait
   --> src/value/de.rs:299:13
    |
299 |             Value::Number(n) => n.deserialize_any(visitor),
    |             ^^^^^^^^^^^^^^-^
    |             |             |
    |             |             hint: to prevent move, use `ref n` or `ref mut n`
    |             cannot move out of here

brson avatar May 01 '18 23:05 brson

Hmm, that's unfortunate all around. We cannot implement Drop for Value because it breaks existing destructuring code, we cannot implement Drop for Vec<Value> because it overlaps Vec<T>'s impl, we cannot implement Drop for serde_json::Map<String, Value> because implementations of Drop cannot apply to a specialized choice of type parameters, and we cannot implement Drop for serde_json::Map<K, V> because that requires a recursive call to V's drop.

If this were implemented as a method, would that solve your use case or do you really need there to be a wrapper?

impl Value {
    fn drop(self) { /* the clever */ }
}

dtolnay avatar May 02 '18 00:05 dtolnay

@dtolnay Having that code in-tree would be marginally better than not just because it would be upstream. I'd still root my Value in a wrapper type on my end to avoid mistakes though.

It is unfortunate.

brson avatar May 02 '18 00:05 brson

Hm, well actually, having that by-value drop would probably just make it harder to implement the wrapper type downstream, because then the wrapper has to use a RefCell or something to make its own dtor work.

brson avatar May 02 '18 00:05 brson

I wrote up a simple implementation of this; tested with n in 0..(2<<24) on my own machine and it works flawlessly (if a bit slowly); the playground doesn't let me allocate that much memory though.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=1b68b66537ab03b160e443f692ce7af8

EDIT: Should've read the whole issue, ignore me :P

remexre avatar Jan 11 '19 20:01 remexre

Hmm, that's unfortunate all around. We cannot implement Drop for Value because it breaks existing destructuring code, we cannot implement Drop for Vec<Value> because it overlaps Vec<T>'s impl, we cannot implement Drop for serde_json::Map<String, Value> because implementations of Drop cannot apply to a specialized choice of type parameters, and we cannot implement Drop for serde_json::Map<K, V> because that requires a recursive call to V's drop.

Do you actually need to expose a (forall K, V) Map<K, V> in serde-json? If Value defined

pub enum Value {
  ...
  Object(Object)
}

pub struct Object(Map<String, Value>);
impl Drop for Object {
   ...
}

This would already solve the problem in most cases, right?

Yoric avatar Jan 11 '19 21:01 Yoric