6565 * noLocation?: bool,
6666 * allowLegacySDLEmptyFields?: bool,
6767 * allowLegacySDLImplementsInterfaces?: bool,
68- * experimentalFragmentVariables?: bool
68+ * experimentalFragmentVariables?: bool,
69+ * recursionLimit?: int
6970 * }
7071 *
7172 * - **noLocation**:
@@ -317,6 +318,10 @@ public static function __callStatic(string $name, array $arguments)
317318
318319 private Lexer $ lexer ;
319320
321+ private int $ recursionDepth = 0 ;
322+
323+ private int $ recursionLimit ;
324+
320325 /**
321326 * @param Source|string $source
322327 *
@@ -328,6 +333,7 @@ public function __construct($source, array $options = [])
328333 ? $ source
329334 : new Source ($ source );
330335 $ this ->lexer = new Lexer ($ sourceObj , $ options );
336+ $ this ->recursionLimit = $ options ['recursionLimit ' ] ?? 256 ;
331337 }
332338
333339 /**
@@ -343,6 +349,16 @@ private function loc(Token $startToken): ?Location
343349 return null ;
344350 }
345351
352+ /** @throws SyntaxError */
353+ private function increaseRecursionDepth (): void
354+ {
355+ if ($ this ->recursionLimit > 0 && $ this ->recursionDepth >= $ this ->recursionLimit ) {
356+ throw new SyntaxError ($ this ->lexer ->source , $ this ->lexer ->token ->start , "Recursion depth limit of {$ this ->recursionLimit } exceeded " );
357+ }
358+
359+ ++$ this ->recursionDepth ;
360+ }
361+
346362 /** Determines if the next token is of a given kind. */
347363 private function peek (string $ kind ): bool
348364 {
@@ -701,18 +717,24 @@ private function parseVariable(): VariableNode
701717 */
702718 private function parseSelectionSet (): SelectionSetNode
703719 {
704- $ start = $ this ->lexer -> token ;
720+ $ this ->increaseRecursionDepth () ;
705721
706- return new SelectionSetNode (
707- [
708- 'selections ' => $ this ->many (
709- Token::BRACE_L ,
710- fn (): SelectionNode => $ this ->parseSelection (),
711- Token::BRACE_R
712- ),
713- 'loc ' => $ this ->loc ($ start ),
714- ]
715- );
722+ try {
723+ $ start = $ this ->lexer ->token ;
724+
725+ return new SelectionSetNode (
726+ [
727+ 'selections ' => $ this ->many (
728+ Token::BRACE_L ,
729+ fn (): SelectionNode => $ this ->parseSelection (),
730+ Token::BRACE_R
731+ ),
732+ 'loc ' => $ this ->loc ($ start ),
733+ ]
734+ );
735+ } finally {
736+ --$ this ->recursionDepth ;
737+ }
716738 }
717739
718740 /**
@@ -911,67 +933,73 @@ private function parseFragmentName(): NameNode
911933 */
912934 private function parseValueLiteral (bool $ isConst ): ValueNode
913935 {
914- $ token = $ this ->lexer ->token ;
915- switch ($ token ->kind ) {
916- case Token::BRACKET_L :
917- return $ this ->parseArray ($ isConst );
918-
919- case Token::BRACE_L :
920- return $ this ->parseObject ($ isConst );
921-
922- case Token::INT :
923- $ this ->lexer ->advance ();
936+ $ this ->increaseRecursionDepth ();
924937
925- return new IntValueNode ([
926- 'value ' => $ token ->value ,
927- 'loc ' => $ this ->loc ($ token ),
928- ]);
938+ try {
939+ $ token = $ this ->lexer ->token ;
940+ switch ($ token ->kind ) {
941+ case Token::BRACKET_L :
942+ return $ this ->parseArray ($ isConst );
929943
930- case Token::FLOAT :
931- $ this ->lexer -> advance ( );
944+ case Token::BRACE_L :
945+ return $ this ->parseObject ( $ isConst );
932946
933- return new FloatValueNode ([
934- 'value ' => $ token ->value ,
935- 'loc ' => $ this ->loc ($ token ),
936- ]);
947+ case Token::INT :
948+ $ this ->lexer ->advance ();
937949
938- case Token::STRING :
939- case Token::BLOCK_STRING :
940- return $ this ->parseStringLiteral ();
950+ return new IntValueNode ([
951+ 'value ' => $ token ->value ,
952+ 'loc ' => $ this ->loc ($ token ),
953+ ]);
941954
942- case Token::NAME :
943- if ($ token ->value === 'true ' || $ token ->value === 'false ' ) {
955+ case Token::FLOAT :
944956 $ this ->lexer ->advance ();
945957
946- return new BooleanValueNode ([
947- 'value ' => $ token ->value === ' true ' ,
958+ return new FloatValueNode ([
959+ 'value ' => $ token ->value ,
948960 'loc ' => $ this ->loc ($ token ),
949961 ]);
950- }
951962
952- if ($ token ->value === 'null ' ) {
963+ case Token::STRING :
964+ case Token::BLOCK_STRING :
965+ return $ this ->parseStringLiteral ();
966+
967+ case Token::NAME :
968+ if ($ token ->value === 'true ' || $ token ->value === 'false ' ) {
969+ $ this ->lexer ->advance ();
970+
971+ return new BooleanValueNode ([
972+ 'value ' => $ token ->value === 'true ' ,
973+ 'loc ' => $ this ->loc ($ token ),
974+ ]);
975+ }
976+
977+ if ($ token ->value === 'null ' ) {
978+ $ this ->lexer ->advance ();
979+
980+ return new NullValueNode ([
981+ 'loc ' => $ this ->loc ($ token ),
982+ ]);
983+ }
953984 $ this ->lexer ->advance ();
954985
955- return new NullValueNode ([
986+ return new EnumValueNode ([
987+ 'value ' => $ token ->value ,
956988 'loc ' => $ this ->loc ($ token ),
957989 ]);
958- }
959- $ this ->lexer ->advance ();
960990
961- return new EnumValueNode ([
962- ' value ' => $ token -> value ,
963- ' loc ' => $ this ->loc ( $ token ),
964- ]);
991+ case Token:: DOLLAR :
992+ if (! $ isConst ) {
993+ return $ this ->parseVariable ();
994+ }
965995
966- case Token::DOLLAR :
967- if (! $ isConst ) {
968- return $ this ->parseVariable ();
969- }
996+ break ;
997+ }
970998
971- break ;
999+ throw $ this ->unexpected ();
1000+ } finally {
1001+ --$ this ->recursionDepth ;
9721002 }
973-
974- throw $ this ->unexpected ();
9751003 }
9761004
9771005 /**
@@ -1108,27 +1136,33 @@ private function parseDirective(bool $isConst): DirectiveNode
11081136 */
11091137 private function parseTypeReference (): TypeNode
11101138 {
1111- $ start = $ this ->lexer -> token ;
1139+ $ this ->increaseRecursionDepth () ;
11121140
1113- if ($ this ->skip (Token::BRACKET_L )) {
1114- $ type = $ this ->parseTypeReference ();
1115- $ this ->expect (Token::BRACKET_R );
1116- $ type = new ListTypeNode ([
1117- 'type ' => $ type ,
1118- 'loc ' => $ this ->loc ($ start ),
1119- ]);
1120- } else {
1121- $ type = $ this ->parseNamedType ();
1122- }
1141+ try {
1142+ $ start = $ this ->lexer ->token ;
11231143
1124- if ($ this ->skip (Token::BANG )) {
1125- return new NonNullTypeNode ([
1126- 'type ' => $ type ,
1127- 'loc ' => $ this ->loc ($ start ),
1128- ]);
1129- }
1144+ if ($ this ->skip (Token::BRACKET_L )) {
1145+ $ type = $ this ->parseTypeReference ();
1146+ $ this ->expect (Token::BRACKET_R );
1147+ $ type = new ListTypeNode ([
1148+ 'type ' => $ type ,
1149+ 'loc ' => $ this ->loc ($ start ),
1150+ ]);
1151+ } else {
1152+ $ type = $ this ->parseNamedType ();
1153+ }
11301154
1131- return $ type ;
1155+ if ($ this ->skip (Token::BANG )) {
1156+ return new NonNullTypeNode ([
1157+ 'type ' => $ type ,
1158+ 'loc ' => $ this ->loc ($ start ),
1159+ ]);
1160+ }
1161+
1162+ return $ type ;
1163+ } finally {
1164+ --$ this ->recursionDepth ;
1165+ }
11321166 }
11331167
11341168 /**
0 commit comments