From fbfe37ee4010d36dad795d9144bb88fbeb8e1419 Mon Sep 17 00:00:00 2001 From: mofeiZ <34200447+mofeiZ@users.noreply.github.com> Date: Thu, 10 Oct 2024 12:23:44 -0400 Subject: [PATCH] [compiler] Test fixture: non-reactive phi creates 'dangling ref' scope (#31103) --- .../bug-invalid-phi-as-dependency.expect.md | 91 +++++++++++++++++++ .../bug-invalid-phi-as-dependency.tsx | 30 ++++++ .../packages/snap/src/SproutTodoFilter.ts | 1 + 3 files changed, 122 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md new file mode 100644 index 0000000000000..32e2c9fd6483e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md @@ -0,0 +1,91 @@ + +## Input + +```javascript +import {CONST_TRUE, Stringify, mutate, useIdentity} from 'shared-runtime'; + +/** + * Fixture showing an edge case for ReactiveScope variable propagation. + * + * Found differences in evaluator results + * Non-forget (expected): + *
{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ *
{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ * Forget: + *
{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ * [[ (exception in render) Error: invariant broken ]] + * + */ +function Component() { + const obj = CONST_TRUE ? {inner: {value: 'hello'}} : null; + const boxedInner = [obj?.inner]; + useIdentity(null); + mutate(obj); + if (boxedInner[0] !== obj?.inner) { + throw new Error('invariant broken'); + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{arg: 0}], + sequentialRenders: [{arg: 0}, {arg: 1}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { CONST_TRUE, Stringify, mutate, useIdentity } from "shared-runtime"; + +/** + * Fixture showing an edge case for ReactiveScope variable propagation. + * + * Found differences in evaluator results + * Non-forget (expected): + *
{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ *
{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ * Forget: + *
{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ * [[ (exception in render) Error: invariant broken ]] + * + */ +function Component() { + const $ = _c(4); + const obj = CONST_TRUE ? { inner: { value: "hello" } } : null; + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [obj?.inner]; + $[0] = t0; + } else { + t0 = $[0]; + } + const boxedInner = t0; + useIdentity(null); + mutate(obj); + if (boxedInner[0] !== obj?.inner) { + throw new Error("invariant broken"); + } + let t1; + if ($[1] !== obj || $[2] !== boxedInner) { + t1 = ; + $[1] = obj; + $[2] = boxedInner; + $[3] = t1; + } else { + t1 = $[3]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ arg: 0 }], + sequentialRenders: [{ arg: 0 }, { arg: 1 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.tsx new file mode 100644 index 0000000000000..a1a78bfa7e6b1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.tsx @@ -0,0 +1,30 @@ +import {CONST_TRUE, Stringify, mutate, useIdentity} from 'shared-runtime'; + +/** + * Fixture showing an edge case for ReactiveScope variable propagation. + * + * Found differences in evaluator results + * Non-forget (expected): + *
{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ *
{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ * Forget: + *
{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ * [[ (exception in render) Error: invariant broken ]] + * + */ +function Component() { + const obj = CONST_TRUE ? {inner: {value: 'hello'}} : null; + const boxedInner = [obj?.inner]; + useIdentity(null); + mutate(obj); + if (boxedInner[0] !== obj?.inner) { + throw new Error('invariant broken'); + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{arg: 0}], + sequentialRenders: [{arg: 0}, {arg: 1}], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index dc90c9e29e2e7..351f242e40820 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -481,6 +481,7 @@ const skipFilter = new Set([ 'bug-invalid-hoisting-functionexpr', 'bug-try-catch-maybe-null-dependency', 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted', + 'bug-invalid-phi-as-dependency', 'reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond', 'original-reactive-scopes-fork/bug-nonmutating-capture-in-unsplittable-memo-block', 'original-reactive-scopes-fork/bug-hoisted-declaration-with-scope',