Skip to content

Conversation

@seanlinsley
Copy link

@seanlinsley seanlinsley commented Dec 15, 2022

This PR allows users to set a custom recursion limit for specific protobuf structures at build time:

let mut config = prost_build::Config::new();
config.recursion_limit("my_messages.MyMessageType", 1000);

@seanlinsley seanlinsley force-pushed the recursion-limit-macro branch 3 times, most recently from b2bb84c to 9a50224 Compare December 15, 2022 22:58
@seanlinsley
Copy link
Author

thoughts @LucioFranco @nrc @danburkert?

src/lib.rs Outdated
// 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;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it sounds like this will never change, I opted to inline it everywhere it's used. That allows documentation to say explicitly that the default recursion limit is 100 instead of requiring that users look up this constant.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually kinda like having this as a constant. I wonder if we could just make a constants module that contains just this one. And we can then have all the deep dive docs on the recursion implementation there and then we just need to link to there from the lib doc page. I feel like that would make it easier to maintain down the line and centralize it a bit.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷 I'm not sure if a constants module adds much. If you still feel a constant is necessary, I'd lean towards simply adding back the constant here in lib.rs

@seanlinsley
Copy link
Author

Here's a downstream PR using this feature: pganalyze/pg_query.rs#17

Note that to prevent a stack overflow from the recursion, I had to increase the stack size in a separate thread. I wonder if that should be documented here.

Copy link
Member

@LucioFranco LucioFranco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM! Some small suggestions and we can get this merged. Thanks for the patience I went on vacation in between :)

@seanlinsley
Copy link
Author

@LucioFranco this should be ready for re-review

@seanlinsley
Copy link
Author

@LucioFranco ping. happy to make any other changes if needed

Copy link
Member

@LucioFranco LucioFranco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the super long delay on the review here and thank you for the ping (I generally don't mind them if you're waiting for a review). I left a few comments that I would like to see changed but they are pretty minor and I think once those are good we are good to merge. Thank you for pushing through on this!

let recursion_limit: u32 = if let Some(attr) = input
.attrs
.iter()
.find(|attr| attr.path.is_ident("RecursionLimit"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should make this one camel case like serde does https://serde.rs/container-attrs.html

so #[prost(recursion_limit = 5)] etc

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having trouble getting a bespoke attribute parser working. Would it be okay to pull in darling as a dependency? https://crates.io/crates/darling

Another option is to skip proper attribute parsing and only handle the single attribute we have for now.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two years later and I finally got around to this. The code now uses #[prost(recursion_limit = 5)]

src/encoding.rs Outdated
/// 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.
/// It defaults to 100 and can be changed using `prost_build::recursion_limit`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So because this is hidden this doc won't actually be readable. So we need to make sure this is documented at the lib level of prost.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a mention to the README

src/lib.rs Outdated
// 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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually kinda like having this as a constant. I wonder if we could just make a constants module that contains just this one. And we can then have all the deep dive docs on the recursion implementation there and then we just need to link to there from the lib doc page. I feel like that would make it easier to maintain down the line and centralize it a bit.

@seanlinsley seanlinsley force-pushed the recursion-limit-macro branch from 08caf43 to 7fc6689 Compare May 29, 2025 17:44
@seanlinsley seanlinsley requested a review from LucioFranco May 29, 2025 18:13
@LucioFranco LucioFranco requested a review from caspermeijn June 9, 2025 20:47
Copy link
Contributor

@caspermeijn caspermeijn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for coming back to this after two years. I see why this is useful and this is a smart solution.

I am a bit concerned whether this could be a breaking change. I would like to see some prove that it is not.

README.md Outdated
- `derive`: Enable integration with `prost-derive`. Disable this feature to reduce compile times. This feature is enabled by default.
- `prost-derive`: Deprecated. Alias for `derive` feature.
- `no-recursion-limit`: Disable the recursion limit. The recursion limit is 100 and cannot be customized.
- `no-recursion-limit`: Disable the recursion limit. The recursion limit is 100, and can be changed with `prost_build::recursion_limit`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should be prost_build::Config::recursion_limit, right?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch


/// Get the items belonging to the 'prost' list attribute, e.g. `#[prost(foo, bar="baz")]`.
fn prost_attrs(attrs: Vec<Attribute>) -> Result<Vec<Meta>, Error> {
pub(super) fn prost_attrs(attrs: Vec<Attribute>) -> Result<Vec<Meta>, Error> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to verify: this doesn't make the function visible outside this crate, right?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#(#clear;)*
}

fn recursion_limit() -> u32 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a breaking change for messages with a field named recursion_limit?

I think for enum fields, an accessor is created. However, this function is in a trait, so not directly accessible.

I would like to see a test case for that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I follow: how could this possibly affect a field named recursion_limit? This is implemented on the Message trait, not on the struct itself.

/// or it can be disabled entirely using the `no-recursion-limit` feature.
#[cfg(not(feature = "no-recursion-limit"))]
recurse_count: u32,
#[doc(hidden)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this marked as doc hidden?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LucioFranco previously suggested that it should be doc(hidden) because the field was changed to public. I assume a previous version of this PR needed it to be public but it seems like it's no longer needed. I'll revert this field back to private.

/// The recursion limit for decoding protobuf messages.
///
/// Defaults to 100. Can be customized in your build.rs or by using the no-recursion-limit crate feature.
fn recursion_limit() -> u32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a breaking change? I feel like it is not, because you add a default implementation. However rust rules for breaking change are not always logical. Could you verify this with cargo-semver-checks?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that it's not a breaking change because it has a default implementation.

I tried running cargo-semver-checks locally and it highlighted #1247 as being a breaking change, but doesn't mention anything about this PR:

--- failure feature_missing: package feature removed or renamed ---

Description:
A feature has been removed from this package's Cargo.toml. This will break downstream crates which enable that feature.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#cargo-feature-remove
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.41.0/src/lints/feature_missing.ron

Failed in:
  feature prost-derive in the package's Cargo.toml

     Summary semver requires new major version: 1 major and 0 minor checks failed

Maybe this check should run on CI?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants