graphql / graphql-spec

GraphQL is a query language and execution engine tied to any backend service.
https://spec.graphql.org
14.31k stars 1.13k forks source link

Fix CoerceArgumentValues() hasValue #1056

Open benjie opened 1 year ago

benjie commented 1 year ago

Fixes a bug discovered whilst carefully evaluating CoerceArgumentValues() that leads to "undefined value leakage" and potential null pointer exception if strictly following the spec. GraphQL.js does not suffer this, so this is a spec bug rather than an implementation bug.

Consider the following schema:

type Query {
  field(arg: String! = "defaultValue"): String
}

And the following GraphQL query:

query ($var: String) {
  field(arg: $var)
}

Imagine that we send an empty object ({}) as the variable values.

Coercing the variableValues according to https://spec.graphql.org/draft/#CoerceVariableValues() we get an empty object ({}).

Fast-forward to https://spec.graphql.org/draft/#CoerceArgumentValues():

Expectation: coercedValues = { arg: "defaultValue" } Actual result: coercedValues = { arg: undefined }

arg is non-null string -> NPE! :boom:

Essentially the phrase "Let {hasValue} be {true} if {argumentValues} provides a value for the name {argumentName}" is at best ambiguous and at worst plain wrong, since the next two lines get the "value" for {argumentName} and then check to see if this {value} is a variable.

This PR fixes this issue by only setting hasValue to true when the value is explicitly resolved via the two branches: variable and non-variable.

There is no need for a GraphQL.js PR for this since GraphQL.js already follows the expected behavior; reproduction:

import { GraphQLNonNull, GraphQLObjectType, GraphQLSchema, GraphQLString, graphqlSync, printSchema, validateSchema } from "graphql";

const Query = new GraphQLObjectType({
  name: "Query",
  fields: {
    field: {
      args: {
        arg: {
          type: new GraphQLNonNull(GraphQLString),
          defaultValue: "defaultValue",
        },
      },
      type: GraphQLString,
      resolve(_, { arg }) {
        return arg;
      },
    },
  },
});

const schema = new GraphQLSchema({
  query: Query,
});

const result = graphqlSync({
  schema,
  source: /* GraphQL */ `
    query ($var: String) {
      field(arg: $var)
    }
  `,
  variables: {
    /* EXPLICITLY DO NOT PASS "var" */
  },
});

const errors = validateSchema(schema);
if (errors.length) {
  console.dir(errors);
  process.exit(1);
}

console.log(printSchema(schema));
console.log(JSON.stringify(result, null, 2));
netlify[bot] commented 1 year ago

Deploy Preview for graphql-spec-draft ready!

Name Link
Latest commit 47e49041ebcadf43392100ee2c9a8b6ed4ac51e4
Latest deploy log https://app.netlify.com/sites/graphql-spec-draft/deploys/6748af62ae812f0008d324d8
Deploy Preview https://deploy-preview-1056--graphql-spec-draft.netlify.app
Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Shane32 commented 1 year ago

GraphQL.js does not suffer this, so this is a spec bug rather than an implementation bug.

The latest build of GraphQL.NET does not suffer from this issue either. Test added in https://github.com/graphql-dotnet/graphql-dotnet/pull/3762 to be sure.