-
Notifications
You must be signed in to change notification settings - Fork 47.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[compiler] validation against calling impure functions (#31960)
For now we just reject all calls of impure functions, and the validation is off by default. Going forward we can make this more precise and only reject impure functions called during render. Note that I was intentionally imprecise in the return type of these functions in order to avoid changing output of existing code. We lie to the compiler and say that Date.now, performance.now, and Math.random return unknown mutable objects rather than primitives. Once the validation is complete and vetted we can switch this to be more precise.
- Loading branch information
1 parent
313c8c5
commit 7f3826e
Showing
8 changed files
with
161 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
52 changes: 52 additions & 0 deletions
52
...r/packages/babel-plugin-react-compiler/src/Validation/ValiateNoImpureFunctionsInRender.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/** | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
import {CompilerError, ErrorSeverity} from '..'; | ||
import {HIRFunction} from '../HIR'; | ||
import {getFunctionCallSignature} from '../Inference/InferReferenceEffects'; | ||
|
||
/** | ||
* Checks that known-impure functions are not called during render. Examples of invalid functions to | ||
* call during render are `Math.random()` and `Date.now()`. Users may extend this set of | ||
* impure functions via a module type provider and specifying functions with `impure: true`. | ||
* | ||
* TODO: add best-effort analysis of functions which are called during render. We have variations of | ||
* this in several of our validation passes and should unify those analyses into a reusable helper | ||
* and use it here. | ||
*/ | ||
export function validateNoImpureFunctionsInRender(fn: HIRFunction): void { | ||
const errors = new CompilerError(); | ||
for (const [, block] of fn.body.blocks) { | ||
for (const instr of block.instructions) { | ||
const value = instr.value; | ||
if (value.kind === 'MethodCall' || value.kind == 'CallExpression') { | ||
const callee = | ||
value.kind === 'MethodCall' ? value.property : value.callee; | ||
const signature = getFunctionCallSignature( | ||
fn.env, | ||
callee.identifier.type, | ||
); | ||
if (signature != null && signature.impure === true) { | ||
errors.push({ | ||
reason: | ||
'Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)', | ||
description: | ||
signature.canonicalName != null | ||
? `\`${signature.canonicalName}\` is an impure function whose results may change on every call` | ||
: null, | ||
severity: ErrorSeverity.InvalidReact, | ||
loc: callee.loc, | ||
suggestions: null, | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
if (errors.hasErrors()) { | ||
throw errors; | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
.../__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
|
||
## Input | ||
|
||
```javascript | ||
// @validateNoImpureFunctionsInRender | ||
|
||
function Component() { | ||
const date = Date.now(); | ||
const now = performance.now(); | ||
const rand = Math.random(); | ||
return <Foo date={date} now={now} rand={rand} />; | ||
} | ||
|
||
``` | ||
|
||
|
||
## Error | ||
|
||
``` | ||
2 | | ||
3 | function Component() { | ||
> 4 | const date = Date.now(); | ||
| ^^^^^^^^ InvalidReact: Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). `Date.now` is an impure function whose results may change on every call (4:4) | ||
InvalidReact: Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). `performance.now` is an impure function whose results may change on every call (5:5) | ||
InvalidReact: Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). `Math.random` is an impure function whose results may change on every call (6:6) | ||
5 | const now = performance.now(); | ||
6 | const rand = Math.random(); | ||
7 | return <Foo date={date} now={now} rand={rand} />; | ||
``` | ||
8 changes: 8 additions & 0 deletions
8
...eact-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
// @validateNoImpureFunctionsInRender | ||
|
||
function Component() { | ||
const date = Date.now(); | ||
const now = performance.now(); | ||
const rand = Math.random(); | ||
return <Foo date={date} now={now} rand={rand} />; | ||
} |