Skip to content

Commit 4d4ac3f

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 2d72d39 commit 4d4ac3f

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
@@ -398,7 +398,10 @@ def read_escaped_unicode_fixed_width(self, position: int) -> EscapeSequence:
398398
def read_escaped_character(self, position: int) -> EscapeSequence:
399399
"""Read escaped character sequence"""
400400
body = self.source.body
401-
value = _ESCAPED_CHARS.get(body[position + 1])
401+
try:
402+
value = _ESCAPED_CHARS.get(body[position + 1])
403+
except IndexError: # backslash at the end of the body
404+
value = None
402405
if value:
403406
return EscapeSequence(value, 2)
404407
raise GraphQLSyntaxError(
@@ -553,13 +556,18 @@ def read_16_bit_hex_code(body: str, position: int) -> int:
553556
Returns a negative number if any char was not a valid hexadecimal digit.
554557
"""
555558
# read_hex_digit() returns -1 on error. ORing a negative value with any other
556-
# value always produces a negative value.
557-
return (
558-
read_hex_digit(body[position]) << 12
559-
| read_hex_digit(body[position + 1]) << 8
560-
| read_hex_digit(body[position + 2]) << 4
561-
| read_hex_digit(body[position + 3])
562-
)
559+
# value always produces a negative value. An escape sequence truncated at the end
560+
# of the body raises an IndexError in Python (in GraphQL.js it reads NaN); it is
561+
# reported with the same negative value, keeping the normal path free of checks.
562+
try:
563+
return (
564+
read_hex_digit(body[position]) << 12
565+
| read_hex_digit(body[position + 1]) << 8
566+
| read_hex_digit(body[position + 2]) << 4
567+
| read_hex_digit(body[position + 3])
568+
)
569+
except IndexError:
570+
return -1
563571

564572

565573
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
@@ -339,6 +339,34 @@ def lex_reports_useful_string_errors():
339339
"Invalid Unicode escape sequence: '\\uD83D'.",
340340
(1, 6),
341341
)
342+
# escape sequences truncated at the end of the source; these cases are
343+
# specific to GraphQL-core, since in GraphQL.js reading beyond the end of
344+
# the source yields NaN instead of raising an IndexError
345+
assert_syntax_error(
346+
'"bad esc \\', "Invalid character escape sequence: '\\'.", (1, 10)
347+
)
348+
assert_syntax_error(
349+
'"bad esc \\u', "Invalid Unicode escape sequence: '\\u'.", (1, 10)
350+
)
351+
assert_syntax_error(
352+
'"bad esc \\u0', "Invalid Unicode escape sequence: '\\u0'.", (1, 10)
353+
)
354+
assert_syntax_error(
355+
'"bad esc \\u00', "Invalid Unicode escape sequence: '\\u00'.", (1, 10)
356+
)
357+
assert_syntax_error(
358+
'"bad esc \\u000', "Invalid Unicode escape sequence: '\\u000'.", (1, 10)
359+
)
360+
assert_syntax_error(
361+
'"bad surrogate pair \\uD83D\\u',
362+
"Invalid Unicode escape sequence: '\\uD83D'.",
363+
(1, 21),
364+
)
365+
assert_syntax_error(
366+
'"bad surrogate pair \\uD83D\\uDE',
367+
"Invalid Unicode escape sequence: '\\uD83D'.",
368+
(1, 21),
369+
)
342370

343371
def lexes_block_strings():
344372
assert lex_one('""""""') == Token(TokenKind.BLOCK_STRING, 0, 6, 1, 1, "")

0 commit comments

Comments
 (0)