Audit try/finally around console patching (#31286)
Otherwise if something errors they can be left patched. [Review without whitespace](https://github.com/facebook/react/pull/31286/files?w=1)
Sebastian Markbåge committed
Oct 18, 2024 at 12:05 UTC
b8ae38f88b70f8a0ea96421a4355266aafefee7f
4 files changed
+197
-184
packages/react-devtools-shared/src/backend/shared/DevToolsComponentStackFrame.js
+83
-84
@@ -82,105 +82,104 @@ export function describeNativeComponentFrame(
82
const previousDispatcher = currentDispatcherRef.H;
83
currentDispatcherRef.H = null;
84
disableLogs();
85
+ try {
86
+ // NOTE: keep in sync with the implementation in ReactComponentStackFrame
87
86
- // NOTE: keep in sync with the implementation in ReactComponentStackFrame
87
-
88
- /**
89
- * Finding a common stack frame between sample and control errors can be
90
- * tricky given the different types and levels of stack trace truncation from
91
- * different JS VMs. So instead we'll attempt to control what that common
92
- * frame should be through this object method:
93
- * Having both the sample and control errors be in the function under the
94
- * `DescribeNativeComponentFrameRoot` property, + setting the `name` and
95
- * `displayName` properties of the function ensures that a stack
96
- * frame exists that has the method name `DescribeNativeComponentFrameRoot` in
97
- * it for both control and sample stacks.
98
- */
99
- const RunInRootFrame = {
100
- DetermineComponentFrameRoot(): [?string, ?string] {
101
- let control;
102
- try {
103
- // This should throw.
104
- if (construct) {
105
- // Something should be setting the props in the constructor.
106
- const Fake = function () {
107
- throw Error();
108
- };
109
- // $FlowFixMe[prop-missing]
110
- Object.defineProperty(Fake.prototype, 'props', {
111
- set: function () {
112
- // We use a throwing setter instead of frozen or non-writable props
113
- // because that won't throw in a non-strict mode function.
88
+ /**
89
+ * Finding a common stack frame between sample and control errors can be
90
+ * tricky given the different types and levels of stack trace truncation from
91
+ * different JS VMs. So instead we'll attempt to control what that common
92
+ * frame should be through this object method:
93
+ * Having both the sample and control errors be in the function under the
94
+ * `DescribeNativeComponentFrameRoot` property, + setting the `name` and
95
+ * `displayName` properties of the function ensures that a stack
96
+ * frame exists that has the method name `DescribeNativeComponentFrameRoot` in
97
+ * it for both control and sample stacks.
98
+ */
99
+ const RunInRootFrame = {
100
+ DetermineComponentFrameRoot(): [?string, ?string] {
101
+ let control;
102
+ try {
103
+ // This should throw.
104
+ if (construct) {
105
+ // Something should be setting the props in the constructor.
106
+ const Fake = function () {
107
throw Error();
115
- },
116
- });
117
- if (typeof Reflect === 'object' && Reflect.construct) {
118
- // We construct a different control for this case to include any extra
119
- // frames added by the construct call.
120
- try {
121
- Reflect.construct(Fake, []);
122
- } catch (x) {
123
- control = x;
108
+ };
109
+ // $FlowFixMe[prop-missing]
110
+ Object.defineProperty(Fake.prototype, 'props', {
111
+ set: function () {
112
+ // We use a throwing setter instead of frozen or non-writable props
113
+ // because that won't throw in a non-strict mode function.
114
+ throw Error();
115
+ },
116
+ });
117
+ if (typeof Reflect === 'object' && Reflect.construct) {
118
+ // We construct a different control for this case to include any extra
119
+ // frames added by the construct call.
120
+ try {
121
+ Reflect.construct(Fake, []);
122
+ } catch (x) {
123
+ control = x;
124
+ }
125
+ Reflect.construct(fn, [], Fake);
126
+ } else {
127
+ try {
128
+ Fake.call();
129
+ } catch (x) {
130
+ control = x;
131
+ }
132
+ // $FlowFixMe[prop-missing] found when upgrading Flow
133
+ fn.call(Fake.prototype);
134
}
125
- Reflect.construct(fn, [], Fake);
135
} else {
136
try {
128
- Fake.call();
137
+ throw Error();
138
} catch (x) {
139
control = x;
140
}
132
- // $FlowFixMe[prop-missing] found when upgrading Flow
133
- fn.call(Fake.prototype);
134
- }
135
- } else {
136
- try {
137
- throw Error();
138
- } catch (x) {
139
- control = x;
140
- }
141
- // TODO(luna): This will currently only throw if the function component
142
- // tries to access React/ReactDOM/props. We should probably make this throw
143
- // in simple components too
144
- const maybePromise = fn();
141
+ // TODO(luna): This will currently only throw if the function component
142
+ // tries to access React/ReactDOM/props. We should probably make this throw
143
+ // in simple components too
144
+ const maybePromise = fn();
145
146
- // If the function component returns a promise, it's likely an async
147
- // component, which we don't yet support. Attach a noop catch handler to
148
- // silence the error.
149
- // TODO: Implement component stacks for async client components?
150
- if (maybePromise && typeof maybePromise.catch === 'function') {
151
- maybePromise.catch(() => {});
146
+ // If the function component returns a promise, it's likely an async
147
+ // component, which we don't yet support. Attach a noop catch handler to
148
+ // silence the error.
149
+ // TODO: Implement component stacks for async client components?
150
+ if (maybePromise && typeof maybePromise.catch === 'function') {
151
+ maybePromise.catch(() => {});
152
+ }
153
+ }
154
+ } catch (sample) {
155
+ // This is inlined manually because closure doesn't do it for us.
156
+ if (sample && control && typeof sample.stack === 'string') {
157
+ return [sample.stack, control.stack];
158
}
159
}
154
- } catch (sample) {
155
- // This is inlined manually because closure doesn't do it for us.
156
- if (sample && control && typeof sample.stack === 'string') {
157
- return [sample.stack, control.stack];
158
- }
159
- }
160
- return [null, null];
161
- },
162
- };
163
- // $FlowFixMe[prop-missing]
164
- RunInRootFrame.DetermineComponentFrameRoot.displayName =
165
- 'DetermineComponentFrameRoot';
166
- const namePropDescriptor = Object.getOwnPropertyDescriptor(
167
- RunInRootFrame.DetermineComponentFrameRoot,
168
- 'name',
169
- );
170
- // Before ES6, the `name` property was not configurable.
171
- if (namePropDescriptor && namePropDescriptor.configurable) {
172
- // V8 utilizes a function's `name` property when generating a stack trace.
173
- Object.defineProperty(
160
+ return [null, null];
161
+ },
162
+ };
163
+ // $FlowFixMe[prop-missing]
164
+ RunInRootFrame.DetermineComponentFrameRoot.displayName =
165
+ 'DetermineComponentFrameRoot';
166
+ const namePropDescriptor = Object.getOwnPropertyDescriptor(
167
RunInRootFrame.DetermineComponentFrameRoot,
175
- // Configurable properties can be updated even if its writable descriptor
176
- // is set to `false`.
177
- // $FlowFixMe[cannot-write]
168
'name',
179
- {value: 'DetermineComponentFrameRoot'},
169
);
181
- }
170
+ // Before ES6, the `name` property was not configurable.
171
+ if (namePropDescriptor && namePropDescriptor.configurable) {
172
+ // V8 utilizes a function's `name` property when generating a stack trace.
173
+ Object.defineProperty(
174
+ RunInRootFrame.DetermineComponentFrameRoot,
175
+ // Configurable properties can be updated even if its writable descriptor
176
+ // is set to `false`.
177
+ // $FlowFixMe[cannot-write]
178
+ 'name',
179
+ {value: 'DetermineComponentFrameRoot'},
180
+ );
181
+ }
182
183
- try {
183
const [sampleStack, controlStack] =
184
RunInRootFrame.DetermineComponentFrameRoot();
185
if (sampleStack && controlStack) {
packages/react-reconciler/src/ReactFiberHooks.js
+21
-9
@@ -1299,8 +1299,11 @@ function mountReducer<S, I, A>(
1299
initialState = init(initialArg);
1300
if (shouldDoubleInvokeUserFnsInHooksDEV) {
1301
setIsStrictModeForDevtools(true);
1302
- init(initialArg);
1303
- setIsStrictModeForDevtools(false);
1302
+ try {
1303
+ init(initialArg);
1304
+ } finally {
1305
+ setIsStrictModeForDevtools(false);
1306
+ }
1307
}
1308
} else {
1309
initialState = ((initialArg: any): S);
@@ -1900,9 +1903,12 @@ function mountStateImpl<S>(initialState: (() => S) | S): Hook {
1903
initialState = initialStateInitializer();
1904
if (shouldDoubleInvokeUserFnsInHooksDEV) {
1905
setIsStrictModeForDevtools(true);
1903
- // $FlowFixMe[incompatible-use]: Flow doesn't like mixed types
1904
- initialStateInitializer();
1905
- setIsStrictModeForDevtools(false);
1906
+ try {
1907
+ // $FlowFixMe[incompatible-use]: Flow doesn't like mixed types
1908
+ initialStateInitializer();
1909
+ } finally {
1910
+ setIsStrictModeForDevtools(false);
1911
+ }
1912
}
1913
}
1914
hook.memoizedState = hook.baseState = initialState;
@@ -2856,8 +2862,11 @@ function mountMemo<T>(
2862
const nextValue = nextCreate();
2863
if (shouldDoubleInvokeUserFnsInHooksDEV) {
2864
setIsStrictModeForDevtools(true);
2859
- nextCreate();
2860
- setIsStrictModeForDevtools(false);
2865
+ try {
2866
+ nextCreate();
2867
+ } finally {
2868
+ setIsStrictModeForDevtools(false);
2869
+ }
2870
}
2871
hook.memoizedState = [nextValue, nextDeps];
2872
return nextValue;
@@ -2880,8 +2889,11 @@ function updateMemo<T>(
2889
const nextValue = nextCreate();
2890
if (shouldDoubleInvokeUserFnsInHooksDEV) {
2891
setIsStrictModeForDevtools(true);
2883
- nextCreate();
2884
- setIsStrictModeForDevtools(false);
2892
+ try {
2893
+ nextCreate();
2894
+ } finally {
2895
+ setIsStrictModeForDevtools(false);
2896
+ }
2897
}
2898
hook.memoizedState = [nextValue, nextDeps];
2899
return nextValue;
packages/react-reconciler/src/ReactFiberWorkLoop.js
+11
-8
@@ -3987,15 +3987,18 @@ function doubleInvokeEffectsOnFiber(
3987
shouldDoubleInvokePassiveEffects: boolean = true,
3988
) {
3989
setIsStrictModeForDevtools(true);
3990
- disappearLayoutEffects(fiber);
3991
- if (shouldDoubleInvokePassiveEffects) {
3992
- disconnectPassiveEffect(fiber);
3993
- }
3994
- reappearLayoutEffects(root, fiber.alternate, fiber, false);
3995
- if (shouldDoubleInvokePassiveEffects) {
3996
- reconnectPassiveEffects(root, fiber, NoLanes, null, false);
3990
+ try {
3991
+ disappearLayoutEffects(fiber);
3992
+ if (shouldDoubleInvokePassiveEffects) {
3993
+ disconnectPassiveEffect(fiber);
3994
+ }
3995
+ reappearLayoutEffects(root, fiber.alternate, fiber, false);
3996
+ if (shouldDoubleInvokePassiveEffects) {
3997
+ reconnectPassiveEffects(root, fiber, NoLanes, null, false);
3998
+ }
3999
+ } finally {
4000
+ setIsStrictModeForDevtools(false);
4001
}
3998
- setIsStrictModeForDevtools(false);
4002
}
4003
4004
function doubleInvokeEffectsInDEVIfNecessary(
packages/shared/ReactComponentStackFrame.js
+82
-83
@@ -103,103 +103,102 @@ export function describeNativeComponentFrame(
103
ReactSharedInternals.H = null;
104
disableLogs();
105
}
106
-
107
- /**
108
- * Finding a common stack frame between sample and control errors can be
109
- * tricky given the different types and levels of stack trace truncation from
110
- * different JS VMs. So instead we'll attempt to control what that common
111
- * frame should be through this object method:
112
- * Having both the sample and control errors be in the function under the
113
- * `DescribeNativeComponentFrameRoot` property, + setting the `name` and
114
- * `displayName` properties of the function ensures that a stack
115
- * frame exists that has the method name `DescribeNativeComponentFrameRoot` in
116
- * it for both control and sample stacks.
117
- */
118
- const RunInRootFrame = {
119
- DetermineComponentFrameRoot(): [?string, ?string] {
120
- let control;
121
- try {
122
- // This should throw.
123
- if (construct) {
124
- // Something should be setting the props in the constructor.
125
- const Fake = function () {
126
- throw Error();
127
- };
128
- // $FlowFixMe[prop-missing]
129
- Object.defineProperty(Fake.prototype, 'props', {
130
- set: function () {
131
- // We use a throwing setter instead of frozen or non-writable props
132
- // because that won't throw in a non-strict mode function.
106
+ try {
107
+ /**
108
+ * Finding a common stack frame between sample and control errors can be
109
+ * tricky given the different types and levels of stack trace truncation from
110
+ * different JS VMs. So instead we'll attempt to control what that common
111
+ * frame should be through this object method:
112
+ * Having both the sample and control errors be in the function under the
113
+ * `DescribeNativeComponentFrameRoot` property, + setting the `name` and
114
+ * `displayName` properties of the function ensures that a stack
115
+ * frame exists that has the method name `DescribeNativeComponentFrameRoot` in
116
+ * it for both control and sample stacks.
117
+ */
118
+ const RunInRootFrame = {
119
+ DetermineComponentFrameRoot(): [?string, ?string] {
120
+ let control;
121
+ try {
122
+ // This should throw.
123
+ if (construct) {
124
+ // Something should be setting the props in the constructor.
125
+ const Fake = function () {
126
throw Error();
134
- },
135
- });
136
- if (typeof Reflect === 'object' && Reflect.construct) {
137
- // We construct a different control for this case to include any extra
138
- // frames added by the construct call.
139
- try {
140
- Reflect.construct(Fake, []);
141
- } catch (x) {
142
- control = x;
127
+ };
128
+ // $FlowFixMe[prop-missing]
129
+ Object.defineProperty(Fake.prototype, 'props', {
130
+ set: function () {
131
+ // We use a throwing setter instead of frozen or non-writable props
132
+ // because that won't throw in a non-strict mode function.
133
+ throw Error();
134
+ },
135
+ });
136
+ if (typeof Reflect === 'object' && Reflect.construct) {
137
+ // We construct a different control for this case to include any extra
138
+ // frames added by the construct call.
139
+ try {
140
+ Reflect.construct(Fake, []);
141
+ } catch (x) {
142
+ control = x;
143
+ }
144
+ Reflect.construct(fn, [], Fake);
145
+ } else {
146
+ try {
147
+ Fake.call();
148
+ } catch (x) {
149
+ control = x;
150
+ }
151
+ // $FlowFixMe[prop-missing] found when upgrading Flow
152
+ fn.call(Fake.prototype);
153
}
144
- Reflect.construct(fn, [], Fake);
154
} else {
155
try {
147
- Fake.call();
156
+ throw Error();
157
} catch (x) {
158
control = x;
159
}
151
- // $FlowFixMe[prop-missing] found when upgrading Flow
152
- fn.call(Fake.prototype);
153
- }
154
- } else {
155
- try {
156
- throw Error();
157
- } catch (x) {
158
- control = x;
159
- }
160
- // TODO(luna): This will currently only throw if the function component
161
- // tries to access React/ReactDOM/props. We should probably make this throw
162
- // in simple components too
163
- const maybePromise = fn();
160
+ // TODO(luna): This will currently only throw if the function component
161
+ // tries to access React/ReactDOM/props. We should probably make this throw
162
+ // in simple components too
163
+ const maybePromise = fn();
164
165
- // If the function component returns a promise, it's likely an async
166
- // component, which we don't yet support. Attach a noop catch handler to
167
- // silence the error.
168
- // TODO: Implement component stacks for async client components?
169
- if (maybePromise && typeof maybePromise.catch === 'function') {
170
- maybePromise.catch(() => {});
165
+ // If the function component returns a promise, it's likely an async
166
+ // component, which we don't yet support. Attach a noop catch handler to
167
+ // silence the error.
168
+ // TODO: Implement component stacks for async client components?
169
+ if (maybePromise && typeof maybePromise.catch === 'function') {
170
+ maybePromise.catch(() => {});
171
+ }
172
+ }
173
+ } catch (sample) {
174
+ // This is inlined manually because closure doesn't do it for us.
175
+ if (sample && control && typeof sample.stack === 'string') {
176
+ return [sample.stack, control.stack];
177
}
178
}
173
- } catch (sample) {
174
- // This is inlined manually because closure doesn't do it for us.
175
- if (sample && control && typeof sample.stack === 'string') {
176
- return [sample.stack, control.stack];
177
- }
178
- }
179
- return [null, null];
180
- },
181
- };
182
- // $FlowFixMe[prop-missing]
183
- RunInRootFrame.DetermineComponentFrameRoot.displayName =
184
- 'DetermineComponentFrameRoot';
185
- const namePropDescriptor = Object.getOwnPropertyDescriptor(
186
- RunInRootFrame.DetermineComponentFrameRoot,
187
- 'name',
188
- );
189
- // Before ES6, the `name` property was not configurable.
190
- if (namePropDescriptor && namePropDescriptor.configurable) {
191
- // V8 utilizes a function's `name` property when generating a stack trace.
192
- Object.defineProperty(
179
+ return [null, null];
180
+ },
181
+ };
182
+ // $FlowFixMe[prop-missing]
183
+ RunInRootFrame.DetermineComponentFrameRoot.displayName =
184
+ 'DetermineComponentFrameRoot';
185
+ const namePropDescriptor = Object.getOwnPropertyDescriptor(
186
RunInRootFrame.DetermineComponentFrameRoot,
194
- // Configurable properties can be updated even if its writable descriptor
195
- // is set to `false`.
196
- // $FlowFixMe[cannot-write]
187
'name',
198
- {value: 'DetermineComponentFrameRoot'},
188
);
200
- }
189
+ // Before ES6, the `name` property was not configurable.
190
+ if (namePropDescriptor && namePropDescriptor.configurable) {
191
+ // V8 utilizes a function's `name` property when generating a stack trace.
192
+ Object.defineProperty(
193
+ RunInRootFrame.DetermineComponentFrameRoot,
194
+ // Configurable properties can be updated even if its writable descriptor
195
+ // is set to `false`.
196
+ // $FlowFixMe[cannot-write]
197
+ 'name',
198
+ {value: 'DetermineComponentFrameRoot'},
199
+ );
200
+ }
201
202
- try {
202
const [sampleStack, controlStack] =
203
RunInRootFrame.DetermineComponentFrameRoot();
204
if (sampleStack && controlStack) {