diff --git a/packages/babel-traverse/src/path/inference/index.js b/packages/babel-traverse/src/path/inference/index.js index eb099abc85f6..69cd126a68c3 100644 --- a/packages/babel-traverse/src/path/inference/index.js +++ b/packages/babel-traverse/src/path/inference/index.js @@ -14,6 +14,11 @@ export function getTypeAnnotation(): Object { return (this.typeAnnotation = type); } +// Used to avoid infinite recursion in cases like +// var b, c; if (0) { c = 1; b = c; } c = b; +// It also works with indirect recursion. +const typeAnnotationInferringNodes = new WeakSet(); + /** * todo: split up this method */ @@ -47,14 +52,24 @@ export function _getTypeAnnotation(): ?Object { return node.typeAnnotation; } - let inferer = inferers[node.type]; - if (inferer) { - return inferer.call(this, node); + if (typeAnnotationInferringNodes.has(node)) { + // Bail out from type inference to avoid infinite loops + return; } + typeAnnotationInferringNodes.add(node); + + try { + let inferer = inferers[node.type]; + if (inferer) { + return inferer.call(this, node); + } - inferer = inferers[this.parentPath.type]; - if (inferer?.validParent) { - return this.parentPath.getTypeAnnotation(); + inferer = inferers[this.parentPath.type]; + if (inferer?.validParent) { + return this.parentPath.getTypeAnnotation(); + } + } finally { + typeAnnotationInferringNodes.delete(node); } } diff --git a/packages/babel-traverse/test/inference.js b/packages/babel-traverse/test/inference.js index 111475df4262..dde8bd22746e 100644 --- a/packages/babel-traverse/test/inference.js +++ b/packages/babel-traverse/test/inference.js @@ -289,5 +289,21 @@ describe("inference", function () { const type = path.getTypeAnnotation(); expect(t.isAnyTypeAnnotation(type)).toBeTruthy(); }); + it("should not cause a stack overflow when two variable depend on eachother", function () { + const path = getPath(` + var b, c; + while (0) { + c = 1; + b = c; + } + c = b; + `).get("body.2.expression"); + + expect(path.toString()).toBe("c = b"); + + // Note: this could technically be "number | void", but the cycle detection + // logic just bails out to "any" to avoid infinite loops. + expect(path.getTypeAnnotation()).toEqual({ type: "AnyTypeAnnotation" }); + }); }); });