@@ -78,6 +78,15 @@ export interface ParseOptions {
78
78
*/
79
79
noLocation ?: boolean ;
80
80
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
+
81
90
/**
82
91
* @deprecated will be removed in the v17.0.0
83
92
*
@@ -179,12 +188,14 @@ export function parseType(
179
188
export class Parser {
180
189
protected _options : ParseOptions ;
181
190
protected _lexer : Lexer ;
191
+ protected _tokenCounter : number ;
182
192
183
193
constructor ( source : string | Source , options : ParseOptions = { } ) {
184
194
const sourceObj = isSource ( source ) ? source : new Source ( source ) ;
185
195
186
196
this . _lexer = new Lexer ( sourceObj ) ;
187
197
this . _options = options ;
198
+ this . _tokenCounter = 0 ;
188
199
}
189
200
190
201
/**
@@ -569,13 +580,13 @@ export class Parser {
569
580
case TokenKind . BRACE_L :
570
581
return this . parseObject ( isConst ) ;
571
582
case TokenKind . INT :
572
- this . _lexer . advance ( ) ;
583
+ this . advanceLexer ( ) ;
573
584
return this . node < IntValueNode > ( token , {
574
585
kind : Kind . INT ,
575
586
value : token . value ,
576
587
} ) ;
577
588
case TokenKind . FLOAT :
578
- this . _lexer . advance ( ) ;
589
+ this . advanceLexer ( ) ;
579
590
return this . node < FloatValueNode > ( token , {
580
591
kind : Kind . FLOAT ,
581
592
value : token . value ,
@@ -584,7 +595,7 @@ export class Parser {
584
595
case TokenKind . BLOCK_STRING :
585
596
return this . parseStringLiteral ( ) ;
586
597
case TokenKind . NAME :
587
- this . _lexer . advance ( ) ;
598
+ this . advanceLexer ( ) ;
588
599
switch ( token . value ) {
589
600
case 'true' :
590
601
return this . node < BooleanValueNode > ( token , {
@@ -630,7 +641,7 @@ export class Parser {
630
641
631
642
parseStringLiteral ( ) : StringValueNode {
632
643
const token = this . _lexer . token ;
633
- this . _lexer . advance ( ) ;
644
+ this . advanceLexer ( ) ;
634
645
return this . node < StringValueNode > ( token , {
635
646
kind : Kind . STRING ,
636
647
value : token . value ,
@@ -1411,7 +1422,7 @@ export class Parser {
1411
1422
expectToken ( kind : TokenKind ) : Token {
1412
1423
const token = this . _lexer . token ;
1413
1424
if ( token . kind === kind ) {
1414
- this . _lexer . advance ( ) ;
1425
+ this . advanceLexer ( ) ;
1415
1426
return token ;
1416
1427
}
1417
1428
@@ -1429,7 +1440,7 @@ export class Parser {
1429
1440
expectOptionalToken ( kind : TokenKind ) : boolean {
1430
1441
const token = this . _lexer . token ;
1431
1442
if ( token . kind === kind ) {
1432
- this . _lexer . advance ( ) ;
1443
+ this . advanceLexer ( ) ;
1433
1444
return true ;
1434
1445
}
1435
1446
return false ;
@@ -1442,7 +1453,7 @@ export class Parser {
1442
1453
expectKeyword ( value : string ) : void {
1443
1454
const token = this . _lexer . token ;
1444
1455
if ( token . kind === TokenKind . NAME && token . value === value ) {
1445
- this . _lexer . advance ( ) ;
1456
+ this . advanceLexer ( ) ;
1446
1457
} else {
1447
1458
throw syntaxError (
1448
1459
this . _lexer . source ,
@@ -1459,7 +1470,7 @@ export class Parser {
1459
1470
expectOptionalKeyword ( value : string ) : boolean {
1460
1471
const token = this . _lexer . token ;
1461
1472
if ( token . kind === TokenKind . NAME && token . value === value ) {
1462
- this . _lexer . advance ( ) ;
1473
+ this . advanceLexer ( ) ;
1463
1474
return true ;
1464
1475
}
1465
1476
return false ;
@@ -1548,6 +1559,22 @@ export class Parser {
1548
1559
} while ( this . expectOptionalToken ( delimiterKind ) ) ;
1549
1560
return nodes ;
1550
1561
}
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
+ }
1551
1578
}
1552
1579
1553
1580
/**
0 commit comments