@@ -78,6 +78,15 @@ export interface ParseOptions {
7878 */
7979 noLocation ?: boolean ;
8080
81+ /**
82+ * Parser CPU and memory usage is linear to the number of tokens in a document
83+ * however in extreme cases it becomes quadratic due to memory exhaustion.
84+ * Parsing happens before validation so even invalid queries can burn lots of
85+ * CPU time and memory.
86+ * To prevent this you can set a maximum number of tokens allowed within a document.
87+ */
88+ maxTokens ?: number | undefined ;
89+
8190 /**
8291 * @deprecated will be removed in the v17.0.0
8392 *
@@ -179,12 +188,14 @@ export function parseType(
179188export class Parser {
180189 protected _options : ParseOptions ;
181190 protected _lexer : Lexer ;
191+ protected _tokenCounter : number ;
182192
183193 constructor ( source : string | Source , options : ParseOptions = { } ) {
184194 const sourceObj = isSource ( source ) ? source : new Source ( source ) ;
185195
186196 this . _lexer = new Lexer ( sourceObj ) ;
187197 this . _options = options ;
198+ this . _tokenCounter = 0 ;
188199 }
189200
190201 /**
@@ -569,13 +580,13 @@ export class Parser {
569580 case TokenKind . BRACE_L :
570581 return this . parseObject ( isConst ) ;
571582 case TokenKind . INT :
572- this . _lexer . advance ( ) ;
583+ this . advanceLexer ( ) ;
573584 return this . node < IntValueNode > ( token , {
574585 kind : Kind . INT ,
575586 value : token . value ,
576587 } ) ;
577588 case TokenKind . FLOAT :
578- this . _lexer . advance ( ) ;
589+ this . advanceLexer ( ) ;
579590 return this . node < FloatValueNode > ( token , {
580591 kind : Kind . FLOAT ,
581592 value : token . value ,
@@ -584,7 +595,7 @@ export class Parser {
584595 case TokenKind . BLOCK_STRING :
585596 return this . parseStringLiteral ( ) ;
586597 case TokenKind . NAME :
587- this . _lexer . advance ( ) ;
598+ this . advanceLexer ( ) ;
588599 switch ( token . value ) {
589600 case 'true' :
590601 return this . node < BooleanValueNode > ( token , {
@@ -630,7 +641,7 @@ export class Parser {
630641
631642 parseStringLiteral ( ) : StringValueNode {
632643 const token = this . _lexer . token ;
633- this . _lexer . advance ( ) ;
644+ this . advanceLexer ( ) ;
634645 return this . node < StringValueNode > ( token , {
635646 kind : Kind . STRING ,
636647 value : token . value ,
@@ -1411,7 +1422,7 @@ export class Parser {
14111422 expectToken ( kind : TokenKind ) : Token {
14121423 const token = this . _lexer . token ;
14131424 if ( token . kind === kind ) {
1414- this . _lexer . advance ( ) ;
1425+ this . advanceLexer ( ) ;
14151426 return token ;
14161427 }
14171428
@@ -1429,7 +1440,7 @@ export class Parser {
14291440 expectOptionalToken ( kind : TokenKind ) : boolean {
14301441 const token = this . _lexer . token ;
14311442 if ( token . kind === kind ) {
1432- this . _lexer . advance ( ) ;
1443+ this . advanceLexer ( ) ;
14331444 return true ;
14341445 }
14351446 return false ;
@@ -1442,7 +1453,7 @@ export class Parser {
14421453 expectKeyword ( value : string ) : void {
14431454 const token = this . _lexer . token ;
14441455 if ( token . kind === TokenKind . NAME && token . value === value ) {
1445- this . _lexer . advance ( ) ;
1456+ this . advanceLexer ( ) ;
14461457 } else {
14471458 throw syntaxError (
14481459 this . _lexer . source ,
@@ -1459,7 +1470,7 @@ export class Parser {
14591470 expectOptionalKeyword ( value : string ) : boolean {
14601471 const token = this . _lexer . token ;
14611472 if ( token . kind === TokenKind . NAME && token . value === value ) {
1462- this . _lexer . advance ( ) ;
1473+ this . advanceLexer ( ) ;
14631474 return true ;
14641475 }
14651476 return false ;
@@ -1548,6 +1559,22 @@ export class Parser {
15481559 } while ( this . expectOptionalToken ( delimiterKind ) ) ;
15491560 return nodes ;
15501561 }
1562+
1563+ advanceLexer ( ) : void {
1564+ const { maxTokens } = this . _options ;
1565+ const token = this . _lexer . advance ( ) ;
1566+
1567+ if ( maxTokens !== undefined && token . kind !== TokenKind . EOF ) {
1568+ ++ this . _tokenCounter ;
1569+ if ( this . _tokenCounter > maxTokens ) {
1570+ throw syntaxError (
1571+ this . _lexer . source ,
1572+ token . start ,
1573+ `Document contains more that ${ maxTokens } tokens. Parsing aborted.` ,
1574+ ) ;
1575+ }
1576+ }
1577+ }
15511578}
15521579
15531580/**
0 commit comments