Repository: tokio-rs/prost (language: rust)

Issue:
Add a recursion limit for decoding
Rust-protobuf and protobuf implementations in other languages support a recursion limit for decoding. Prost decoding is also recursive, and I believe supporting a recursion limit is necessary if messages can be defined recursively (as they can be for TiKV). Could we add this limit to Prost? I can implement this if it would be accepted, but I'm not sure where to put the option - currently Prost does not support any options for decoding/encoding (as far as I can tell). Rust-protobuf adds the recursion limit to the buffer, but because Prost uses a trait for the buffer that seems not the best solution (I think it would work, we would have a recursion-limited buffer which wrapped any implementation of buffer like a smart pointer, but it would be pretty hacky and I think might be inefficient).

The Rust-protobuf solution was added in https://github.com/stepancheg/rust-protobuf/pull/248


Discussion / hints:
Ping @danburkert do you have thoughts on how/if to implement this?
Do you have any examples from other libraries Nick? Maybe their implementations can help inform the discussion.
I'm +1 on the idea, but you're right it needs a bit of design.  I'm curious if and how serde solves this issue.

It'd take a bit of work, but in theory a recursion counter could be threaded through the deserialization stack.  I'd  start by adding a `usize` param to `{message, group}::merge` in encoding.rs.

I don't think serialization/encoding needs a similar limit, since the recursion is naturally limited by how nested the message is.

The biggest question is if/how to make this configurable.  I'd rather not create new variants of `Message::{decode, decode_length_delimited, merge, merge_length_delimited}` with an extra param, but I'm not sure how else to do it.

spitballing: what if we just capped it at something pretty high like 100, then added cargo features which allowed bumping it up?

(I'd also like to preempt one potential path - I wouldn't want to introduce TLS usage for this).
> I'm curious if and how serde solves this issue.

For serde_json we have a limit of 128 levels on deserialization which is not configurable but can be disabled via [`serde_json::Deserializer::disable_recursion_limit`](https://docs.rs/serde_json/1.0.39/serde_json/struct.Deserializer.html#method.disable_recursion_limit). Once disabled, the user might want to use a Deserializer adapter like [`serde_stacker`](https://docs.rs/serde_stacker/0.1.0/serde_stacker/) to grow the stack dynamically, or some other Deserializer adapter to introduce their own recursion limit.
@dtolnay thanks for that background, that's really helpful.  Do you have any thoughts on how common it is to remove the recursion limit?  When using protobuf in other languages I've never needed to increase the limit.

Maybe it's sufficient to cap at ~128, and then have a cargo feature to remove the cap.  `prost` doesn't have the equivalent of a `Deserializer` (but perhaps it should).
Not common. Here were the request for it: https://github.com/serde-rs/json/issues/334.
> Maybe it's sufficient to cap at ~128, and then have a cargo feature to remove the cap.

This sounds like a good idea to me. I couldn't find anywhere to keep a configurable value (rust-protobuf keeps it in their buffer, but Prost doesn't have its own buffer type, it uses the bytes crate).

@danburkert are you happy for me to implement this?
> @danburkert are you happy for me to implement this?

Yep, sounds good, thanks!

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 9adca3dde..0c4344b98 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -34,6 +34,7 @@ exclude = [
 
 [features]
 default = ["prost-derive"]
+no-recursion-limit = []
 
 [dependencies]
 byteorder = "1"
diff --git a/benches/benchmark.rs b/benches/benchmark.rs
index 73521bd13..365a9f24b 100644
--- a/benches/benchmark.rs
+++ b/benches/benchmark.rs
@@ -4,6 +4,7 @@ use std::result;
 
 use criterion::{Benchmark, Criterion, Throughput};
 use failure::bail;
+use prost::encoding::DecodeContext;
 use prost::Message;
 use protobuf::benchmarks::{google_message3, google_message4, proto2, proto3, BenchmarkDataset};
 
diff --git a/prost-derive/src/field/group.rs b/prost-derive/src/field/group.rs
index e69dbabf6..0197e753e 100644
--- a/prost-derive/src/field/group.rs
+++ b/prost-derive/src/field/group.rs
@@ -91,13 +91,14 @@ impl Field {
             Label::Optional => quote! {
                 _prost::encoding::group::merge(tag, wire_type,
                                                  #ident.get_or_insert_with(Default::default),
-                                                 buf)
+                                                 buf,
+                                                 ctx)
             },
             Label::Required => quote! {
-                _prost::encoding::group::merge(tag, wire_type, &mut #ident, buf)
+                _prost::encoding::group::merge(tag, wire_type, &mut #ident, buf, ctx)
             },
             Label::Repeated => quote! {
-                _prost::encoding::group::merge_repeated(tag, wire_type, &mut #ident, buf)
+                _prost::encoding::group::merge_repeated(tag, wire_type, &mut #ident, buf, ctx)
             },
         }
     }
diff --git a/prost-derive/src/field/map.rs b/prost-derive/src/field/map.rs
index 40899c8f0..07f3cf9d0 100644
--- a/prost-derive/src/field/map.rs
+++ b/prost-derive/src/field/map.rs
@@ -162,17 +162,17 @@ impl Field {
                 let default = quote!(#ty::default() as i32);
                 quote! {
                     _prost::encoding::#module::merge_with_default(#km, _prost::encoding::int32::merge,
-                                                                  #default, &mut #ident, buf)
+                                                                  #default, &mut #ident, buf, ctx)
                 }
             }
             ValueTy::Scalar(ref value_ty) => {
                 let val_mod = value_ty.module();
                 let vm = quote!(_prost::encoding::#val_mod::merge);
-                quote!(_prost::encoding::#module::merge(#km, #vm, &mut #ident, buf))
+                quote!(_prost::encoding::#module::merge(#km, #vm, &mut #ident, buf, ctx))
             }
             ValueTy::Message => {
                 quote!(_prost::encoding::#module::merge(#km, _prost::encoding::message::merge,
-                                                        &mut #ident, buf))
+                                                        &mut #ident, buf, ctx))
             }
         }
     }
diff --git a/prost-derive/src/field/message.rs b/prost-derive/src/field/message.rs
index d3629593d..6d6ecd46f 100644
--- a/prost-derive/src/field/message.rs
+++ b/prost-derive/src/field/message.rs
@@ -94,13 +94,14 @@ impl Field {
             Label::Optional => quote! {
                 _prost::encoding::message::merge(wire_type,
                                                  #ident.get_or_insert_with(Default::default),
-                                                 buf)
+                                                 buf,
+                                                 ctx)
             },
             Label::Required => quote! {
-                _prost::encoding::message::merge(wire_type, &mut #ident, buf)
+                _prost::encoding::message::merge(wire_type, &mut #ident, buf, ctx)
             },
             Label::Repeated => quote! {
-                _prost::encoding::message::merge_repeated(wire_type, &mut #ident, buf)
+                _prost::encoding::message::merge_repeated(wire_type, &mut #ident, buf, ctx)
             },
         }
     }
diff --git a/prost-derive/src/field/oneof.rs b/prost-derive/src/field/oneof.rs
index e9863f5e1..893a1f4ee 100644
--- a/prost-derive/src/field/oneof.rs
+++ b/prost-derive/src/field/oneof.rs
@@ -77,7 +77,7 @@ impl Field {
     pub fn merge(&self, ident: TokenStream) -> TokenStream {
         let ty = &self.ty;
         quote! {
-            #ty::merge(&mut #ident, tag, wire_type, buf)
+            #ty::merge(&mut #ident, tag, wire_type, buf, ctx)
         }
     }
 
diff --git a/prost-derive/src/field/scalar.rs b/prost-derive/src/field/scalar.rs
index a4533ff47..e59bf2df6 100644
--- a/prost-derive/src/field/scalar.rs
+++ b/prost-derive/src/field/scalar.rs
@@ -153,12 +153,13 @@ impl Field {
 
         match self.kind {
             Kind::Plain(..) | Kind::Required(..) | Kind::Repeated | Kind::Packed => quote! {
-                #merge_fn(wire_type, &mut #ident, buf)
+                #merge_fn(wire_type, &mut #ident, buf, ctx)
             },
             Kind::Optional(..) => quote! {
                 #merge_fn(wire_type,
                           #ident.get_or_insert_with(Default::default),
-                          buf)
+                          buf,
+                          ctx)
             },
         }
     }
diff --git a/prost-derive/src/lib.rs b/prost-derive/src/lib.rs
index e37b263ac..6fe97aa11 100644
--- a/prost-derive/src/lib.rs
+++ b/prost-derive/src/lib.rs
@@ -189,7 +189,9 @@ fn try_message(input: TokenStream) -> Result<TokenStream, Error> {
                 fn merge_field<B>(&mut self,
                                   tag: u32,
                                   wire_type: _prost::encoding::WireType,
-                                  buf: &mut B) -> ::std::result::Result<(), _prost::DecodeError>
+                                  buf: &mut B,
+                                  ctx: _prost::encoding::DecodeContext,
+                ) -> ::std::result::Result<(), _prost::DecodeError>
                 where B: _bytes::Buf {
                     #struct_name
                     match tag {
@@ -446,8 +448,9 @@ fn try_oneof(input: TokenStream) -> Result<TokenStream, Error> {
                 pub fn merge<B>(field: &mut ::std::option::Option<#ident>,
                                 tag: u32,
                                 wire_type: _prost::encoding::WireType,
-                                buf: &mut B)
-                                -> ::std::result::Result<(), _prost::DecodeError>
+                                buf: &mut B,
+                                ctx: _prost::encoding::DecodeContext,
+                ) -> ::std::result::Result<(), _prost::DecodeError>
                 where B: _bytes::Buf {
                     match tag {
                         #(#merge,)*
diff --git a/src/encoding.rs b/src/encoding.rs
index 9d458780d..a98192783 100644
--- a/src/encoding.rs
+++ b/src/encoding.rs
@@ -178,6 +178,80 @@ where
     Err(DecodeError::new("invalid varint"))
 }
 
+/// Additional information passed to every decode/merge function.
+///
+/// The context should be passed by value and can be freely cloned. When passing
+/// to a function which is decoding a nested object, then use `enter_recursion`.
+#[derive(Clone, Debug)]
+pub struct DecodeContext {
+    /// How many times we can recurse in the current decode stack before we hit
+    /// the recursion limit.
+    ///
+    /// The recursion limit is defined by `RECURSION_LIMIT` and cannot be
+    /// customized. The recursion limit can be ignored by building the Prost
+    /// crate with the `no-recursion-limit` feature.
+    #[cfg(not(feature = "no-recursion-limit"))]
+    recurse_count: u32,
+}
+
+impl Default for DecodeContext {
+    #[cfg(not(feature = "no-recursion-limit"))]
+    #[inline]
+    fn default() -> DecodeContext {
+        DecodeContext {
+            recurse_count: crate::RECURSION_LIMIT,
+        }
+    }
+
+    #[cfg(feature = "no-recursion-limit")]
+    #[inline]
+    fn default() -> DecodeContext {
+        DecodeContext {}
+    }
+}
+
+impl DecodeContext {
+    /// Call this function before recursively decoding.
+    ///
+    /// There is no `exit` function since this function creates a new `DecodeContext`
+    /// to be used at the next level of recursion. Continue to use the old context
+    // at the previous level of recursion.
+    #[cfg(not(feature = "no-recursion-limit"))]
+    #[inline]
+    pub(crate) fn enter_recursion(&self) -> DecodeContext {
+        DecodeContext {
+            recurse_count: self.recurse_count - 1,
+        }
+    }
+
+    #[cfg(feature = "no-recursion-limit")]
+    #[inline]
+    pub(crate) fn enter_recursion(&self) -> DecodeContext {
+        DecodeContext {}
+    }
+
+    /// Checks whether the recursion limit has been reached in the stack of
+    /// decodes described by the `DecodeContext` at `self.ctx`.
+    ///
+    /// Returns `Ok<()>` if it is ok to continue recursing.
+    /// Returns `Err<DecodeError>` if the recursion limit has been reached.
+    #[cfg(not(feature = "no-recursion-limit"))]
+    #[inline]
+    pub(crate) fn limit_reached(&self) -> Result<(), DecodeError> {
+        if self.recurse_count == 0 {
+            Err(DecodeError::new("recursion limit reached"))
+        } else {
+            Ok(())
+        }
+    }
+
+    #[cfg(feature = "no-recursion-limit")]
+    #[inline]
+    pub(crate) fn limit_reached(&self) -> Result<(), DecodeError> {
+        Ok(())
+    }
+}
+
 /// Returns the encoded length of the value in LEB128 variable length format.
 /// The returned value will be between 1 and 10, inclusive.
 #[inline]
@@ -275,9 +349,14 @@ pub fn check_wire_type(expected: WireType, actual: WireType) -> Result<(), Decod
 
 /// Helper function which abstracts reading a length delimiter prefix followed
 /// by decoding values until the length of bytes is exhausted.
-pub fn merge_loop<T, M, B>(value: &mut T, buf: &mut B, mut merge: M) -> Result<(), DecodeError>
+pub fn merge_loop<T, M, B>(
+    value: &mut T,
+    buf: &mut B,
+    ctx: DecodeContext,
+    mut merge: M,
+) -> Result<(), DecodeError>
 where
-    M: FnMut(&mut T, &mut B) -> Result<(), DecodeError>,
+    M: FnMut(&mut T, &mut B, DecodeContext) -> Result<(), DecodeError>,
     B: Buf,
 {
     let len = decode_varint(buf)?;
@@ -288,7 +367,7 @@ where
 
     let limit = remaining - len as usize;
     while buf.remaining() > limit {
-        merge(value, buf)?;
+        merge(value, buf, ctx.clone())?;
     }
 
     if buf.remaining() != limit {
@@ -353,15 +432,16 @@ macro_rules! merge_repeated_numeric {
             wire_type: WireType,
             values: &mut Vec<$ty>,
             buf: &mut B,
+            ctx: DecodeContext,
         ) -> Result<(), DecodeError>
         where
             B: Buf,
         {
             if wire_type == WireType::LengthDelimited {
                 // Packed.
-                merge_loop(values, buf, |values, buf| {
+                merge_loop(values, buf, ctx, |values, buf, ctx| {
                     let mut value = Default::default();
-                    $merge($wire_type, &mut value, buf)?;
+                    $merge($wire_type, &mut value, buf, ctx)?;
                     values.push(value);
                     Ok(())
                 })
@@ -369,7 +449,7 @@ macro_rules! merge_repeated_numeric {
                 // Unpacked.
                 check_wire_type($wire_type, wire_type)?;
                 let mut value = Default::default();
-                $merge(wire_type, &mut value, buf)?;
+                $merge(wire_type, &mut value, buf, ctx)?;
                 values.push(value);
                 Ok(())
             }
@@ -401,7 +481,7 @@ macro_rules! varint {
                 encode_varint($to_uint64, buf);
             }
 
-            pub fn merge<B>(wire_type: WireType, value: &mut $ty, buf: &mut B) -> Result<(), DecodeError> where B: Buf {
+            pub fn merge<B>(wire_type: WireType, value: &mut $ty, buf: &mut B, _ctx: DecodeContext) -> Result<(), DecodeError> where B: Buf {
                 check_wire_type(WireType::Varint, wire_type)?;
                 let $from_uint64_value = decode_varint(buf)?;
                 *value = $from_uint64;
@@ -528,6 +608,7 @@ macro_rules! fixed_width {
                 wire_type: WireType,
                 value: &mut $ty,
                 buf: &mut B,
+                _ctx: DecodeContext,
             ) -> Result<(), DecodeError>
             where
                 B: Buf,
@@ -666,13 +747,14 @@ macro_rules! length_delimited {
             wire_type: WireType,
             values: &mut Vec<$ty>,
             buf: &mut B,
+            ctx: DecodeContext,
         ) -> Result<(), DecodeError>
         where
             B: Buf,
         {
             check_wire_type(WireType::LengthDelimited, wire_type)?;
             let mut value = Default::default();
-            merge(wire_type, &mut value, buf)?;
+            merge(wire_type, &mut value, buf, ctx)?;
             values.push(value);
             Ok(())
         }
@@ -724,12 +806,17 @@ pub mod string {
         encode_varint(value.len() as u64, buf);
         buf.put_slice(value.as_bytes());
     }
-    pub fn merge<B>(wire_type: WireType, value: &mut String, buf: &mut B) -> Result<(), DecodeError>
+    pub fn merge<B>(
+        wire_type: WireType,
+        value: &mut String,
+        buf: &mut B,
+        ctx: DecodeContext,
+    ) -> Result<(), DecodeError>
     where
         B: Buf,
     {
         let mut value_bytes = mem::replace(value, String::new()).into_bytes();
-        bytes::merge(wire_type, &mut value_bytes, buf)?;
+        bytes::merge(wire_type, &mut value_bytes, buf, ctx)?;
         *value = String::from_utf8(value_bytes)
             .map_err(|_| DecodeError::new("invalid string value: data is not UTF-8 encoded"))?;
         Ok(())
@@ -754,6 +841,7 @@ pub mod bytes {
         wire_type: WireType,
         value: &mut Vec<u8>,
         buf: &mut B,
+        _ctx: DecodeContext,
     ) -> Result<(), DecodeError>
     where
         B: Buf,
@@ -796,16 +884,27 @@ pub mod message {
         msg.encode_raw(buf);
     }
 
-    pub fn merge<M, B>(wire_type: WireType, msg: &mut M, buf: &mut B) -> Result<(), DecodeError>
+    pub fn merge<M, B>(
+        wire_type: WireType,
+        msg: &mut M,
+        buf: &mut B,
+        ctx: DecodeContext,
+    ) -> Result<(), DecodeError>
     where
         M: Message,
         B: Buf,
     {
         check_wire_type(WireType::LengthDelimited, wire_type)?;
-        merge_loop(msg, buf, |msg: &mut M, buf: &mut B| {
-            let (tag, wire_type) = decode_key(buf)?;
-            msg.merge_field(tag, wire_type, buf)
-        })
+        ctx.limit_reached()?;
+        merge_loop(
+            msg,
+            buf,
+            ctx.enter_recursion(),
+            |msg: &mut M, buf: &mut B, ctx| {
+                let (tag, wire_type) = decode_key(buf)?;
+                msg.merge_field(tag, wire_type, buf, ctx)
+            },
+        )
     }
 
     pub fn encode_repeated<M, B>(tag: u32, messages: &[M], buf: &mut B)
@@ -822,6 +921,7 @@ pub mod message {
         wire_type: WireType,
         messages: &mut Vec<M>,
         buf: &mut B,
+        ctx: DecodeContext,
     ) -> Result<(), DecodeError>
     where
         M: Message + Default,
@@ -829,7 +929,7 @@ pub mod message {
     {
         check_wire_type(WireType::LengthDelimited, wire_type)?;
         let mut msg = M::default();
-        merge(WireType::LengthDelimited, &mut msg, buf)?;
+        merge(WireType::LengthDelimited, &mut msg, buf, ctx)?;
         messages.push(msg);
         Ok(())
     }
@@ -840,7 +940,7 @@ pub mod message {
         M: Message,
     {
         let len = msg.encoded_len();
-        key_len(tag) + encoded_len_varint(len as u64) + msg.encoded_len()
+        key_len(tag) + encoded_len_varint(len as u64) + len
     }
 
     #[inline]
@@ -875,6 +975,7 @@ pub mod group {
         wire_type: WireType,
         msg: &mut M,
         buf: &mut B,
+        ctx: DecodeContext,
     ) -> Result<(), DecodeError>
     where
         M: Message,
@@ -882,6 +983,7 @@ pub mod group {
     {
         check_wire_type(WireType::StartGroup, wire_type)?;
 
+        ctx.limit_reached()?;
         loop {
             let (field_tag, field_wire_type) = decode_key(buf)?;
             if field_wire_type == WireType::EndGroup {
@@ -891,7 +993,7 @@ pub mod group {
                 return Ok(());
             }
 
-            M::merge_field(msg, field_tag, field_wire_type, buf)?;
+            M::merge_field(msg, field_tag, field_wire_type, buf, ctx.enter_recursion())?;
         }
     }
 
@@ -910,6 +1012,7 @@ pub mod group {
         wire_type: WireType,
         messages: &mut Vec<M>,
         buf: &mut B,
+        ctx: DecodeContext,
     ) -> Result<(), DecodeError>
     where
         M: Message + Default,
@@ -917,7 +1020,7 @@ pub mod group {
     {
         check_wire_type(WireType::StartGroup, wire_type)?;
         let mut msg = M::default();
-        merge(tag, WireType::StartGroup, &mut msg, buf)?;
+        merge(tag, WireType::StartGroup, &mut msg, buf, ctx)?;
         messages.push(msg);
         Ok(())
     }
@@ -984,15 +1087,16 @@ macro_rules! map {
             val_merge: VM,
             values: &mut $map_ty<K, V>,
             buf: &mut B,
+            ctx: DecodeContext,
         ) -> Result<(), DecodeError>
         where
             K: Default + Eq + Hash + Ord,
             V: Default,
             B: Buf,
-            KM: Fn(WireType, &mut K, &mut B) -> Result<(), DecodeError>,
-            VM: Fn(WireType, &mut V, &mut B) -> Result<(), DecodeError>,
+            KM: Fn(WireType, &mut K, &mut B, DecodeContext) -> Result<(), DecodeError>,
+            VM: Fn(WireType, &mut V, &mut B, DecodeContext) -> Result<(), DecodeError>,
         {
-            merge_with_default(key_merge, val_merge, V::default(), values, buf)
+            merge_with_default(key_merge, val_merge, V::default(), values, buf, ctx)
         }
 
         /// Generic protobuf map encode function.
@@ -1061,23 +1165,26 @@ macro_rules! map {
             val_default: V,
             values: &mut $map_ty<K, V>,
             buf: &mut B,
+            ctx: DecodeContext,
         ) -> Result<(), DecodeError>
         where
             K: Default + Eq + Hash + Ord,
             B: Buf,
-            KM: Fn(WireType, &mut K, &mut B) -> Result<(), DecodeError>,
-            VM: Fn(WireType, &mut V, &mut B) -> Result<(), DecodeError>,
+            KM: Fn(WireType, &mut K, &mut B, DecodeContext) -> Result<(), DecodeError>,
+            VM: Fn(WireType, &mut V, &mut B, DecodeContext) -> Result<(), DecodeError>,
         {
             let mut key = Default::default();
             let mut val = val_default;
+            ctx.limit_reached()?;
             merge_loop(
                 &mut (&mut key, &mut val),
                 buf,
-                |&mut (ref mut key, ref mut val), buf| {
+                ctx.enter_recursion(),
+                |&mut (ref mut key, ref mut val), buf, ctx| {
                     let (tag, wire_type) = decode_key(buf)?;
                     match tag {
-                        1 => key_merge(wire_type, key, buf),
-                        2 => val_merge(wire_type, val, buf),
+                        1 => key_merge(wire_type, key, buf, ctx),
+                        2 => val_merge(wire_type, val, buf, ctx),
                         _ => skip_field(wire_type, tag, buf),
                     }
                 },
@@ -1149,7 +1256,7 @@ mod test {
         tag: u32,
         wire_type: WireType,
         encode: fn(u32, &B, &mut BytesMut),
-        merge: fn(WireType, &mut T, &mut Cursor<Bytes>) -> Result<(), DecodeError>,
+        merge: fn(WireType, &mut T, &mut Cursor<Bytes>, DecodeContext) -> Result<(), DecodeError>,
         encoded_len: fn(u32, &B) -> usize,
     ) -> TestResult
     where
@@ -1218,7 +1325,12 @@ mod test {
         }
 
         let mut roundtrip_value = T::default();
-        if let Err(error) = merge(wire_type, &mut roundtrip_value, &mut buf) {
+        if let Err(error) = merge(
+            wire_type,
+            &mut roundtrip_value,
+            &mut buf,
+            DecodeContext::default(),
+        ) {
             return TestResult::error(error.to_string());
         };
 
@@ -1248,7 +1360,7 @@ mod test {
         T: Debug + Default + PartialEq + Borrow<B>,
         B: ?Sized,
         E: FnOnce(u32, &B, &mut BytesMut),
-        M: FnMut(WireType, &mut T, &mut Cursor<Bytes>) -> Result<(), DecodeError>,
+        M: FnMut(WireType, &mut T, &mut Cursor<Bytes>, DecodeContext) -> Result<(), DecodeError>,
         L: FnOnce(u32, &B) -> usize,
     {
         if tag > MAX_TAG || tag < MIN_TAG {
@@ -1291,7 +1403,12 @@ mod test {
                 ));
             }
 
-            if let Err(error) = merge(wire_type, &mut roundtrip_value, &mut buf) {
+            if let Err(error) = merge(
+                wire_type,
+                &mut roundtrip_value,
+                &mut buf,
+                DecodeContext::default(),
+            ) {
                 return TestResult::error(error.to_string());
             };
         }
@@ -1307,7 +1424,12 @@ mod test {
     fn string_merge_failure() {
         let mut s = String::new();
         let mut buf = Cursor::new(b"\x80\x80");
-        let r = string::merge(WireType::LengthDelimited, &mut s, &mut buf);
+        let r = string::merge(
+            WireType::LengthDelimited,
+            &mut s,
+            &mut buf,
+            DecodeContext::default(),
+        );
         r.expect_err("must be an error");
         assert!(s.is_empty());
     }
@@ -1438,12 +1560,13 @@ mod test {
                                                                     values,
                                                                     buf)
                                               },
-                                              |wire_type, values, buf| {
+                                              |wire_type, values, buf, ctx| {
                                                   check_wire_type(WireType::LengthDelimited, wire_type)?;
                                                   $mod_name::merge($key_proto::merge,
                                                                    $val_proto::merge,
                                                                    values,
-                                                                   buf)
+                                                                   buf,
+                                                                   ctx)
                                               },
                                               |tag, values| {
                                                   $mod_name::encoded_len($key_proto::encoded_len,
diff --git a/src/lib.rs b/src/lib.rs
index 8602c2eb3..cfc3d2e7b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -14,6 +14,11 @@ use bytes::{BufMut, IntoBuf};
 
 use crate::encoding::{decode_varint, encode_varint, encoded_len_varint};
 
+// See `encoding::DecodeContext` for more info.
+// 100 is the default recursion limit in the C++ implementation.
+#[cfg(not(feature = "no-recursion-limit"))]
+const RECURSION_LIMIT: u32 = 100;
+
 /// Encodes a length delimiter to the buffer.
 ///
 /// See [Message.encode_length_delimited] for more info.
diff --git a/src/message.rs b/src/message.rs
index 2b27b4276..a4acfd2b4 100644
--- a/src/message.rs
+++ b/src/message.rs
@@ -29,6 +29,7 @@ pub trait Message: Debug + Send + Sync {
         tag: u32,
         wire_type: WireType,
         buf: &mut B,
+        ctx: DecodeContext,
     ) -> Result<(), DecodeError>
     where
         B: Buf,
@@ -106,9 +107,10 @@ pub trait Message: Debug + Send + Sync {
         Self: Sized,
     {
         let mut buf = buf.into_buf();
+        let ctx = DecodeContext::default();
         while buf.has_remaining() {
             let (tag, wire_type) = decode_key(&mut buf)?;
-            self.merge_field(tag, wire_type, &mut buf)?;
+            self.merge_field(tag, wire_type, &mut buf, ctx.clone())?;
         }
         Ok(())
     }
@@ -120,7 +122,12 @@ pub trait Message: Debug + Send + Sync {
         B: IntoBuf,
         Self: Sized,
     {
-        message::merge(WireType::LengthDelimited, self, &mut buf.into_buf())
+        message::merge(
+            WireType::LengthDelimited,
+            self,
+            &mut buf.into_buf(),
+            DecodeContext::default(),
+        )
     }
 
     /// Clears the message, resetting all fields to their default.
@@ -142,11 +149,12 @@ where
         tag: u32,
         wire_type: WireType,
         buf: &mut B,
+        ctx: DecodeContext,
     ) -> Result<(), DecodeError>
     where
         B: Buf,
     {
-        (**self).merge_field(tag, wire_type, buf)
+        (**self).merge_field(tag, wire_type, buf, ctx)
     }
     fn encoded_len(&self) -> usize {
         (**self).encoded_len()
diff --git a/src/types.rs b/src/types.rs
index 5a675f300..6156e297f 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -26,12 +26,13 @@ impl Message for bool {
         tag: u32,
         wire_type: WireType,
         buf: &mut B,
+        ctx: DecodeContext,
     ) -> Result<(), DecodeError>
     where
         B: Buf,
     {
         if tag == 1 {
-            bool::merge(wire_type, self, buf)
+            bool::merge(wire_type, self, buf, ctx)
         } else {
             skip_field(wire_type, tag, buf)
         }
@@ -63,12 +64,13 @@ impl Message for u32 {
         tag: u32,
         wire_type: WireType,
         buf: &mut B,
+        ctx: DecodeContext,
     ) -> Result<(), DecodeError>
     where
         B: Buf,
     {
         if tag == 1 {
-            uint32::merge(wire_type, self, buf)
+            uint32::merge(wire_type, self, buf, ctx)
         } else {
             skip_field(wire_type, tag, buf)
         }
@@ -100,12 +102,13 @@ impl Message for u64 {
         tag: u32,
         wire_type: WireType,
         buf: &mut B,
+        ctx: DecodeContext,
     ) -> Result<(), DecodeError>
     where
         B: Buf,
     {
         if tag == 1 {
-            uint64::merge(wire_type, self, buf)
+            uint64::merge(wire_type, self, buf, ctx)
         } else {
             skip_field(wire_type, tag, buf)
         }
@@ -137,12 +140,13 @@ impl Message for i32 {
         tag: u32,
         wire_type: WireType,
         buf: &mut B,
+        ctx: DecodeContext,
     ) -> Result<(), DecodeError>
     where
         B: Buf,
     {
         if tag == 1 {
-            int32::merge(wire_type, self, buf)
+            int32::merge(wire_type, self, buf, ctx)
         } else {
             skip_field(wire_type, tag, buf)
         }
@@ -174,12 +178,13 @@ impl Message for i64 {
         tag: u32,
         wire_type: WireType,
         buf: &mut B,
+        ctx: DecodeContext,
     ) -> Result<(), DecodeError>
     where
         B: Buf,
     {
         if tag == 1 {
-            int64::merge(wire_type, self, buf)
+            int64::merge(wire_type, self, buf, ctx)
         } else {
             skip_field(wire_type, tag, buf)
         }
@@ -211,12 +216,13 @@ impl Message for f32 {
         tag: u32,
         wire_type: WireType,
         buf: &mut B,
+        ctx: DecodeContext,
     ) -> Result<(), DecodeError>
     where
         B: Buf,
     {
         if tag == 1 {
-            float::merge(wire_type, self, buf)
+            float::merge(wire_type, self, buf, ctx)
         } else {
             skip_field(wire_type, tag, buf)
         }
@@ -248,12 +254,13 @@ impl Message for f64 {
         tag: u32,
         wire_type: WireType,
         buf: &mut B,
+        ctx: DecodeContext,
     ) -> Result<(), DecodeError>
     where
         B: Buf,
     {
         if tag == 1 {
-            double::merge(wire_type, self, buf)
+            double::merge(wire_type, self, buf, ctx)
         } else {
             skip_field(wire_type, tag, buf)
         }
@@ -285,12 +292,13 @@ impl Message for String {
         tag: u32,
         wire_type: WireType,
         buf: &mut B,
+        ctx: DecodeContext,
     ) -> Result<(), DecodeError>
     where
         B: Buf,
     {
         if tag == 1 {
-            string::merge(wire_type, self, buf)
+            string::merge(wire_type, self, buf, ctx)
         } else {
             skip_field(wire_type, tag, buf)
         }
@@ -322,12 +330,13 @@ impl Message for Vec<u8> {
         tag: u32,
         wire_type: WireType,
         buf: &mut B,
+        ctx: DecodeContext,
     ) -> Result<(), DecodeError>
     where
         B: Buf,
     {
         if tag == 1 {
-            bytes::merge(wire_type, self, buf)
+            bytes::merge(wire_type, self, buf, ctx)
         } else {
             skip_field(wire_type, tag, buf)
         }
@@ -356,6 +365,7 @@ impl Message for () {
         tag: u32,
         wire_type: WireType,
         buf: &mut B,
+        _ctx: DecodeContext,
     ) -> Result<(), DecodeError>
     where
         B: Buf,


Test patch:
diff --git a/tests/src/groups.proto b/tests/src/groups.proto
index 51ba4bf1e..8d3fc546c 100644
--- a/tests/src/groups.proto
+++ b/tests/src/groups.proto
@@ -49,3 +49,9 @@ message NestedGroup {
         }
     }
 }
+
+message NestedGroup2 {
+    optional group OptionalGroup = 1 {
+        optional NestedGroup2 nested_group = 1;
+    }
+}
\ No newline at end of file
diff --git a/tests/src/lib.rs b/tests/src/lib.rs
index a66d50c27..ca2b83332 100644
--- a/tests/src/lib.rs
+++ b/tests/src/lib.rs
@@ -341,6 +341,115 @@ mod tests {
         };
     }
 
+    #[test]
+    fn test_deep_nesting() {
+        fn build_and_roundtrip(depth: usize) -> Result<(), prost::DecodeError> {
+            use crate::nesting::A;
+
+            let mut a = Box::new(A::default());
+            for _ in 0..depth {
+                let mut next = Box::new(A::default());
+                next.a = Some(a);
+                a = next;
+            }
+
+            let mut buf = Vec::new();
+            a.encode(&mut buf).unwrap();
+            A::decode(buf).map(|_| ())
+        }
+
+        assert!(build_and_roundtrip(100).is_ok());
+        assert!(build_and_roundtrip(101).is_err());
+    }
+
+    #[test]
+    fn test_deep_nesting_oneof() {
+        fn build_and_roundtrip(depth: usize) -> Result<(), prost::DecodeError> {
+            use crate::recursive_oneof::{a, A, C};
+
+            let mut a = Box::new(A {
+                kind: Some(a::Kind::C(C {})),
+            });
+            for _ in 0..depth {
+                a = Box::new(A {
+                    kind: Some(a::Kind::A(a)),
+                });
+            }
+
+            let mut buf = Vec::new();
+            a.encode(&mut buf).unwrap();
+            A::decode(buf).map(|_| ())
+        }
+
+        assert!(build_and_roundtrip(99).is_ok());
+        assert!(build_and_roundtrip(100).is_err());
+    }
+
+    #[test]
+    fn test_deep_nesting_group() {
+        fn build_and_roundtrip(depth: usize) -> Result<(), prost::DecodeError> {
+            use crate::groups::{nested_group2::OptionalGroup, NestedGroup2};
+
+            let mut a = NestedGroup2::default();
+            for _ in 0..depth {
+                a = NestedGroup2 {
+                    optionalgroup: Some(Box::new(OptionalGroup {
+                        nested_group: Some(a),
+                    })),
+                };
+            }
+
+            let mut buf = Vec::new();
+            a.encode(&mut buf).unwrap();
+            NestedGroup2::decode(buf).map(|_| ())
+        }
+
+        assert!(build_and_roundtrip(50).is_ok());
+        assert!(build_and_roundtrip(51).is_err());
+    }
+
+    #[test]
+    fn test_deep_nesting_repeated() {
+        fn build_and_roundtrip(depth: usize) -> Result<(), prost::DecodeError> {
+            use crate::nesting::C;
+
+            let mut c = C::default();
+            for _ in 0..depth {
+                let mut next = C::default();
+                next.r.push(c);
+                c = next;
+            }
+
+            let mut buf = Vec::new();
+            c.encode(&mut buf).unwrap();
+            C::decode(buf).map(|_| ())
+        }
+
+        assert!(build_and_roundtrip(100).is_ok());
+        assert!(build_and_roundtrip(101).is_err());
+    }
+
+    #[test]
+    fn test_deep_nesting_map() {
+        fn build_and_roundtrip(depth: usize) -> Result<(), prost::DecodeError> {
+            use crate::nesting::D;
+
+            let mut d = D::default();
+            for _ in 0..depth {
+                let mut next = D::default();
+                next.m.insert("foo".to_owned(), d);
+                d = next;
+            }
+
+            let mut buf = Vec::new();
+            d.encode(&mut buf).unwrap();
+            D::decode(buf).map(|_| ())
+        }
+
+        assert!(build_and_roundtrip(50).is_ok());
+        assert!(build_and_roundtrip(51).is_err());
+    }
+
     #[test]
     fn test_recursive_oneof() {
         use crate::recursive_oneof::{a, A, B, C};
diff --git a/tests/src/nesting.proto b/tests/src/nesting.proto
index ea7bf8a1e..2c61979df 100644
--- a/tests/src/nesting.proto
+++ b/tests/src/nesting.proto
@@ -15,3 +15,11 @@ message A {
 message B {
     A a = 1;
 }
+
+message C {
+    repeated C r = 1;
+}
+
+message D {
+    map<string, D> m = 1;
+}