From 6402dd9db8e53a55a24a4b1656847b3345662037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Mon, 23 Nov 2020 17:03:01 +0100 Subject: [PATCH] Avoid infinite loops in type inference logic (#12390) --- .../src/path/inference/index.js | 27 ++++++++++++++----- packages/babel-traverse/test/inference.js | 16 +++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) 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" }); + }); }); });