Skip to content

Commit e950515

Browse files
committed
fix: raise a syntax error for truncated string escape sequences
Reading a character past the end of the source yields NaN in GraphQL.js, but raises an IndexError in Python. A string escape sequence truncated at the end of the input therefore made the lexer raise an unhandled IndexError instead of a GraphQLSyntaxError, and that exception propagated out of parse() and graphql_sync(). Catch that IndexError and fall back to the same sentinels that any other invalid escape produces, so such an escape is reported as a syntax error with the same message as in GraphQL.js, while the normal path stays unchanged.
1 parent af6addb commit e950515

2 files changed

Lines changed: 44 additions & 8 deletions

File tree

src/graphql/language/lexer.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,10 @@ def read_escaped_unicode_fixed_width(self, position: int) -> EscapeSequence:
367367

368368
def read_escaped_character(self, position: int) -> EscapeSequence:
369369
body = self.source.body
370-
value = _ESCAPED_CHARS.get(body[position + 1])
370+
try:
371+
value = _ESCAPED_CHARS.get(body[position + 1])
372+
except IndexError: # backslash at the end of the body
373+
value = None
371374
if value:
372375
return EscapeSequence(value, 2)
373376
raise GraphQLSyntaxError(
@@ -522,13 +525,18 @@ def read_16_bit_hex_code(body: str, position: int) -> int:
522525
Returns a negative number if any char was not a valid hexadecimal digit.
523526
"""
524527
# read_hex_digit() returns -1 on error. ORing a negative value with any other
525-
# value always produces a negative value.
526-
return (
527-
read_hex_digit(body[position]) << 12
528-
| read_hex_digit(body[position + 1]) << 8
529-
| read_hex_digit(body[position + 2]) << 4
530-
| read_hex_digit(body[position + 3])
531-
)
528+
# value always produces a negative value. An escape sequence truncated at the end
529+
# of the body raises an IndexError in Python (in GraphQL.js it reads NaN); it is
530+
# reported with the same negative value, keeping the normal path free of checks.
531+
try:
532+
return (
533+
read_hex_digit(body[position]) << 12
534+
| read_hex_digit(body[position + 1]) << 8
535+
| read_hex_digit(body[position + 2]) << 4
536+
| read_hex_digit(body[position + 3])
537+
)
538+
except IndexError:
539+
return -1
532540

533541

534542
def read_hex_digit(char: str) -> int:

tests/language/test_lexer.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,34 @@ def lex_reports_useful_string_errors():
330330
"Invalid Unicode escape sequence: '\\uD83D'.",
331331
(1, 6),
332332
)
333+
# escape sequences truncated at the end of the source; these cases are
334+
# specific to GraphQL-core, since in GraphQL.js reading beyond the end of
335+
# the source yields NaN instead of raising an IndexError
336+
assert_syntax_error(
337+
'"bad esc \\', "Invalid character escape sequence: '\\'.", (1, 10)
338+
)
339+
assert_syntax_error(
340+
'"bad esc \\u', "Invalid Unicode escape sequence: '\\u'.", (1, 10)
341+
)
342+
assert_syntax_error(
343+
'"bad esc \\u0', "Invalid Unicode escape sequence: '\\u0'.", (1, 10)
344+
)
345+
assert_syntax_error(
346+
'"bad esc \\u00', "Invalid Unicode escape sequence: '\\u00'.", (1, 10)
347+
)
348+
assert_syntax_error(
349+
'"bad esc \\u000', "Invalid Unicode escape sequence: '\\u000'.", (1, 10)
350+
)
351+
assert_syntax_error(
352+
'"bad surrogate pair \\uD83D\\u',
353+
"Invalid Unicode escape sequence: '\\uD83D'.",
354+
(1, 21),
355+
)
356+
assert_syntax_error(
357+
'"bad surrogate pair \\uD83D\\uDE',
358+
"Invalid Unicode escape sequence: '\\uD83D'.",
359+
(1, 21),
360+
)
333361

334362
# noinspection PyArgumentEqualDefault
335363
def lexes_block_strings():

0 commit comments

Comments
 (0)