Skip to content

Commit c2479a2

Browse files
committed
Fix infinite recursion bug
1 parent e3c6ec1 commit c2479a2

File tree

1 file changed

+26
-6
lines changed

1 file changed

+26
-6
lines changed

src/validation/rules/MaxIntrospectionDepthRule.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,34 @@ export function MaxIntrospectionDepthRule(
1515
* Counts the depth of list fields in "__Type" recursively and
1616
* returns `true` if the limit has been reached.
1717
*/
18-
function checkDepth(node: ASTNode, depth: number = 0): boolean {
18+
function checkDepth(
19+
node: ASTNode,
20+
visitedFragments: Record<string, true>,
21+
depth: number = 0,
22+
): boolean {
1923
if (node.kind === Kind.FRAGMENT_SPREAD) {
20-
const fragment = context.getFragment(node.name.value);
24+
const fragmentName = node.name.value;
25+
if (visitedFragments[fragmentName]) {
26+
// Fragment cycles are handled by `NoFragmentCyclesRule`.
27+
return false;
28+
}
29+
const fragment = context.getFragment(fragmentName);
2130
if (!fragment) {
22-
// missing fragments checks are handled by the `KnownFragmentNamesRule`
31+
// Missing fragments checks are handled by `KnownFragmentNamesRule`.
2332
return false;
2433
}
25-
return checkDepth(fragment, depth);
34+
35+
// Rather than following an immutable programming pattern which has
36+
// significant memory and garbage collection overhead, we've opted to
37+
// take a mutable approach for efficiency's sake. Importantly visiting a
38+
// fragment twice is fine, so long as you don't do one visit inside the
39+
// other.
40+
try {
41+
visitedFragments[fragmentName] = true;
42+
return checkDepth(fragment, visitedFragments, depth);
43+
} finally {
44+
delete visitedFragments[fragmentName];
45+
}
2646
}
2747

2848
if (
@@ -43,7 +63,7 @@ export function MaxIntrospectionDepthRule(
4363
// handles fields and inline fragments
4464
if ('selectionSet' in node && node.selectionSet) {
4565
for (const child of node.selectionSet.selections) {
46-
if (checkDepth(child, depth)) {
66+
if (checkDepth(child, visitedFragments, depth)) {
4767
return true;
4868
}
4969
}
@@ -55,7 +75,7 @@ export function MaxIntrospectionDepthRule(
5575
return {
5676
Field(node) {
5777
if (node.name.value === '__schema' || node.name.value === '__type') {
58-
if (checkDepth(node)) {
78+
if (checkDepth(node, Object.create(null))) {
5979
context.reportError(
6080
new GraphQLError('Maximum introspection depth exceeded', {
6181
nodes: [node],

0 commit comments

Comments
 (0)