Repository: apollographql/apollo-rs
Pull request #430 (2023-01-17 17:47:53)

feat(smith): add DocumentBuilder::input_exhausted
This indicates we might have run into https://github.com/rust-fuzz/arbitrary/issues/139

SimonSapin: My current attempt at working around https://github.com/rust-fuzz/arbitrary/issues/139 is to double the size of the input until it’s large enough to not be exhausted, but currently this causes `DocumentBuilder::operation_definition` to overflow the stack, I suspect through generating very deeply nested selection sets.

Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 9133b2a..ba2a698 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -90,7 +90,7 @@ jobs:
           platform: << parameters.platform >>
       - run:
           name: Run cargo test
-          command: cargo test
+          command: cargo test --all-features
 
 # The folowing are reusable command snippets can be referred to in any `steps`.
 # Commands we currently have: install_system_deps, install_rust_toolchain.
diff --git a/crates/apollo-smith/src/document.rs b/crates/apollo-smith/src/document.rs
index 91c7400..dd1ab0e 100644
--- a/crates/apollo-smith/src/document.rs
+++ b/crates/apollo-smith/src/document.rs
@@ -20,7 +20,7 @@ use crate::{
 ///     DirectiveDefinition*
 ///
 /// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-Document).
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct Document {
     pub(crate) operation_definitions: Vec<OperationDef>,
     pub(crate) fragment_definitions: Vec<FragmentDef>,
diff --git a/crates/apollo-smith/src/enum_.rs b/crates/apollo-smith/src/enum_.rs
index 4a29f1e..12b6a6c 100644
--- a/crates/apollo-smith/src/enum_.rs
+++ b/crates/apollo-smith/src/enum_.rs
@@ -54,7 +54,9 @@ impl From<EnumTypeDef> for EnumDefinition {
 impl TryFrom<apollo_parser::ast::EnumTypeDefinition> for EnumTypeDef {
     type Error = crate::FromError;
 
-    fn try_from(enum_def: apollo_parser::ast::EnumTypeDefinition) -> Result<Self, Self::Error> {
+    fn try_from(
+        enum_def: apollo_parser::ast::EnumTypeDefinition,
+    ) -> std::result::Result<Self, Self::Error> {
         Ok(Self {
             description: enum_def
                 .description()
@@ -71,7 +73,7 @@ impl TryFrom<apollo_parser::ast::EnumTypeDefinition> for EnumTypeDef {
                 .expect("must have enum values definition")
                 .enum_value_definitions()
                 .map(EnumValueDefinition::try_from)
-                .collect::<Result<_, _>>()?,
+                .collect::<std::result::Result<_, _>>()?,
             extend: false,
         })
     }
@@ -81,7 +83,9 @@ impl TryFrom<apollo_parser::ast::EnumTypeDefinition> for EnumTypeDef {
 impl TryFrom<apollo_parser::ast::EnumTypeExtension> for EnumTypeDef {
     type Error = crate::FromError;
 
-    fn try_from(enum_def: apollo_parser::ast::EnumTypeExtension) -> Result<Self, Self::Error> {
+    fn try_from(
+        enum_def: apollo_parser::ast::EnumTypeExtension,
+    ) -> std::result::Result<Self, Self::Error> {
         Ok(Self {
             description: None,
             name: enum_def.name().unwrap().into(),
@@ -95,7 +99,7 @@ impl TryFrom<apollo_parser::ast::EnumTypeExtension> for EnumTypeDef {
                 .expect("must have enum values definition")
                 .enum_value_definitions()
                 .map(EnumValueDefinition::try_from)
-                .collect::<Result<_, _>>()?,
+                .collect::<std::result::Result<_, _>>()?,
             extend: true,
         })
     }
@@ -135,7 +139,7 @@ impl TryFrom<apollo_parser::ast::EnumValueDefinition> for EnumValueDefinition {
 
     fn try_from(
         enum_value_def: apollo_parser::ast::EnumValueDefinition,
-    ) -> Result<Self, Self::Error> {
+    ) -> std::result::Result<Self, Self::Error> {
         Ok(Self {
             description: enum_value_def.description().map(Description::from),
             value: enum_value_def
diff --git a/crates/apollo-smith/src/field.rs b/crates/apollo-smith/src/field.rs
index 5b7b3fc..d42524c 100644
--- a/crates/apollo-smith/src/field.rs
+++ b/crates/apollo-smith/src/field.rs
@@ -77,7 +77,7 @@ impl TryFrom<apollo_parser::ast::FieldDefinition> for FieldDef {
 ///     Alias? Name Arguments? Directives? SelectionSet?
 ///
 /// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-Language.Fields).
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct Field {
     pub(crate) alias: Option<Name>,
     pub(crate) name: Name,
diff --git a/crates/apollo-smith/src/fragment.rs b/crates/apollo-smith/src/fragment.rs
index 36a3cf6..a659a02 100644
--- a/crates/apollo-smith/src/fragment.rs
+++ b/crates/apollo-smith/src/fragment.rs
@@ -16,7 +16,7 @@ use crate::{
 ///     fragment FragmentName TypeCondition Directives? SelectionSet
 ///
 /// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#FragmentDefinition).
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct FragmentDef {
     pub(crate) name: Name,
     pub(crate) type_condition: TypeCondition,
@@ -64,7 +64,7 @@ impl TryFrom<apollo_parser::ast::FragmentDefinition> for FragmentDef {
 ///     ... FragmentName Directives?
 ///
 /// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#FragmentSpread).
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct FragmentSpread {
     pub(crate) name: Name,
     pub(crate) directives: HashMap<Name, Directive>,
@@ -109,7 +109,7 @@ impl TryFrom<apollo_parser::ast::FragmentSpread> for FragmentSpread {
 ///     ... TypeCondition? Directives? SelectionSet
 ///
 /// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-Inline-Fragments).
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct InlineFragment {
     pub(crate) type_condition: Option<TypeCondition>,
     pub(crate) directives: HashMap<Name, Directive>,
@@ -152,7 +152,7 @@ impl TryFrom<apollo_parser::ast::InlineFragment> for InlineFragment {
 ///     on NamedType
 ///
 /// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#TypeCondition).
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct TypeCondition {
     name: Name,
 }
diff --git a/crates/apollo-smith/src/input_value.rs b/crates/apollo-smith/src/input_value.rs
index e991cce..9b33f08 100644
--- a/crates/apollo-smith/src/input_value.rs
+++ b/crates/apollo-smith/src/input_value.rs
@@ -451,7 +451,7 @@ mod tests {
 
         assert_eq!(
             input_val_str.as_str(),
-            "[{ first: [{ value: \"womkigecaYWUSQOMKIGECA86420zxvtcaYWUSQOMKIGECA86420zxvtrpnljhfdbKIGECA86420zxvtrpnljhfdbZXVTRPN9420zxvtrpnljhfdbZXVTRPNLJHFDB97rpnljhfdbZXVTRPNLJHFDB97531_ywugbZXVTRPNLJHFDB97531_ywusqomkigeNLJHFDB97531_ywusqomkigecaYWUSQOM531_ywusqomkigecaYWUSQOMKIGECA8vqomkigecaYWUSQOMKIGECA86420zxvtcaYWUSQOMKIGECA86420zxvtrpnljhfdbKIGECA86420zxvtrpnljhfdbZXVTRPN9420zxvtrpnljhfdbZXVTRPNLJHFDB97rpnljhfdbZXVTRPNLJHFDB97531_ywugbZXVTRPNLJHFDB97531_ywusqomkigeNLJHFDB97531_ywusqomkigecaYWUSQOM531_ywusqomkigecaYWUSQOMKIGECA8vqomki\" }, { value: \"a86420zxvtrpnljhfdbZXVTRPN9420zxvtrpnljhfdbZXVTRPNLJHFDB97rpnljhfdbZXVTRPNLJHFDB97531_ywugbZXVTRPNLJHFDB97531kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\" }] }]"
+            "[{ first: [{ value: \"womkigecaYWUSQOMKIGECA86420zxvtcaYWUSQOMKIGECA86420zxvtrpnljhfdbKIGECA86420zxvtrpnljhfdbZXVTRPN9420zxvtrpnljhfdbZXVTRPNLJHFDB97rpnljhfdbZXVTRPNLJHFDB97531_ywugbZXVTRPNLJHFDB97531_ywusqomkigeNLJHFDB97531_ywusqomkigecaYWUSQOM531_ywusqomkigecaYWUSQOMKIGECA8vqomkigecaYWUSQOMKIGECA86420zxvtcaYWUSQOMKIGECA86420zxvtrpnljhfdbKIGECA86420zxvtrpnljhfdbZXVTRPN9420zxvtrpnljhfdbZXVTRPNLJHFDB97rpnljhfdbZXVTRPNLJHFDB97531_ywugbZXVTRPNLJHFDB97531_ywusqomkigeNLJHFDB97531_ywusqomkigecaYWUSQOM531_ywusqomkigecaYWUSQOMKIGECA8vqomkig\" }, { value: \"C6420zxvtrpnljhfdbZXVTRPN9420zxvtrpnljhfdbZXVTRPNLJHFDB97rpnljhfdbZXVTRPNLJHFDB97531_ywugbZXVTRPNLJHFDB97531kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\" }] }]"
         );
     }
 }
diff --git a/crates/apollo-smith/src/lib.rs b/crates/apollo-smith/src/lib.rs
index e9cdbbe..e31d067 100644
--- a/crates/apollo-smith/src/lib.rs
+++ b/crates/apollo-smith/src/lib.rs
@@ -207,6 +207,11 @@ impl<'a> DocumentBuilder<'a> {
         Ok(builder)
     }
 
+    /// Returns whether the provided `Unstructured` is now empty
+    pub fn input_exhausted(&self) -> bool {
+        self.u.is_empty()
+    }
+
     /// Convert a `DocumentBuilder` into a GraphQL `Document`
     pub fn finish(self) -> Document {
         Document {
diff --git a/crates/apollo-smith/src/name.rs b/crates/apollo-smith/src/name.rs
index e6fe4bd..6289304 100644
--- a/crates/apollo-smith/src/name.rs
+++ b/crates/apollo-smith/src/name.rs
@@ -90,7 +90,7 @@ impl<'a> DocumentBuilder<'a> {
     // Mirror what happens in `Arbitrary for String`, but do so with a clamped size.
     pub(crate) fn limited_string(&mut self, max_size: usize) -> ArbitraryResult<String> {
         loop {
-            let size = self.u.int_in_range(0..=max_size)?;
+            let size = self.u.int_in_range(1..=max_size)?;
 
             let gen_str = String::from_utf8(
                 (0..size)
diff --git a/crates/apollo-smith/src/operation.rs b/crates/apollo-smith/src/operation.rs
index 2cfa885..96f13e3 100644
--- a/crates/apollo-smith/src/operation.rs
+++ b/crates/apollo-smith/src/operation.rs
@@ -16,7 +16,7 @@ use crate::{
 ///     OperationType Name? VariableDefinitions? Directives? SelectionSet
 ///
 /// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-Language.Operations).
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct OperationDef {
     pub(crate) operation_type: OperationType,
     pub(crate) name: Option<Name>,
diff --git a/crates/apollo-smith/src/scalar.rs b/crates/apollo-smith/src/scalar.rs
index c559f8c..f0f2f70 100644
--- a/crates/apollo-smith/src/scalar.rs
+++ b/crates/apollo-smith/src/scalar.rs
@@ -16,7 +16,7 @@ use crate::{
 ///     Description? **scalar** Name Directives?
 ///
 /// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-Scalar).
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct ScalarTypeDef {
     pub(crate) name: Name,
     pub(crate) description: Option<Description>,
diff --git a/crates/apollo-smith/src/selection_set.rs b/crates/apollo-smith/src/selection_set.rs
index f335c9d..c9507d0 100644
--- a/crates/apollo-smith/src/selection_set.rs
+++ b/crates/apollo-smith/src/selection_set.rs
@@ -13,7 +13,7 @@ use crate::{
 ///     Selection*
 ///
 /// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-Selection-Sets).
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct SelectionSet {
     selections: Vec<Selection>,
 }
@@ -49,7 +49,7 @@ impl TryFrom<apollo_parser::ast::SelectionSet> for SelectionSet {
 ///     Field | FragmentSpread | InlineFragment
 ///
 /// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#Selection).
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub enum Selection {
     /// Represents a field
     Field(Field),
diff --git a/crates/apollo-smith/src/union.rs b/crates/apollo-smith/src/union.rs
index 8f5429f..1699aa0 100644
--- a/crates/apollo-smith/src/union.rs
+++ b/crates/apollo-smith/src/union.rs
@@ -17,7 +17,7 @@ use crate::{
 ///     Description? **union** Name Directives? UnionDefMemberTypes?
 ///
 /// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-UnionDef).
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct UnionTypeDef {
     pub(crate) name: Name,
     pub(crate) description: Option<Description>,
diff --git a/crates/apollo-smith/src/variable.rs b/crates/apollo-smith/src/variable.rs
index 20e9623..ac9aaf5 100644
--- a/crates/apollo-smith/src/variable.rs
+++ b/crates/apollo-smith/src/variable.rs
@@ -16,7 +16,7 @@ use crate::{
 ///     VariableName : Type DefaultValue? Directives?
 ///
 /// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-Language.Variables).
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct VariableDef {
     name: Name,
     ty: Ty,


Test patch:
diff --git a/crates/apollo-smith/tests/with_document.rs b/crates/apollo-smith/tests/with_document.rs
new file mode 100644
index 0000000..9ab3dd1
--- /dev/null
+++ b/crates/apollo-smith/tests/with_document.rs
@@ -0,0 +1,16 @@
+/// Non-regression for https://github.com/apollographql/apollo-rs/issues/426
+#[cfg(feature = "parser-impl")]
+#[test]
+fn test_with_document() {
+    let mut u = arbitrary::Unstructured::new(&[1]);
+    let schema = "schema { query: Query } type Query { id: ID! }";
+    let schema = apollo_parser::Parser::new(schema)
+        .parse()
+        .document()
+        .try_into()
+        .unwrap();
+    apollo_smith::DocumentBuilder::with_document(&mut u, schema)
+        .unwrap()
+        .operation_definition()
+        .unwrap();
+}