Skip to content

Commit 1d16923

Browse files
pakoitofacebook-github-bot
authored andcommitted
Add remote API to uninstall the global error handler in RN
Reviewed By: fromcelticpark Differential Revision: D6426209 fbshipit-source-id: 804e73e0dc4e4b85b336e3627c00840d2ff3c9d6
1 parent ed2bfcb commit 1d16923

File tree

3 files changed

+118
-11
lines changed

3 files changed

+118
-11
lines changed

Libraries/BatchedBridge/BatchedBridge.js

+15-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,21 @@
1212
'use strict';
1313

1414
const MessageQueue = require('MessageQueue');
15-
const BatchedBridge = new MessageQueue();
15+
16+
// MessageQueue can install a global handler to catch all exceptions where JS users can register their own behavior
17+
// This handler makes all exceptions to be handled inside MessageQueue rather than by the VM at its origin
18+
// This makes stacktraces to be placed at MessageQueue rather than at where they were launched
19+
// The parameter __fbUninstallRNGlobalErrorHandler is passed to MessageQueue to prevent the handler from being installed
20+
//
21+
// __fbUninstallRNGlobalErrorHandler is conditionally set by the Inspector while the VM is paused for intialization
22+
// If the Inspector isn't present it defaults to undefined and the global error handler is installed
23+
// The Inspector can still call MessageQueue#uninstallGlobalErrorHandler to uninstalled on attach
24+
25+
const BatchedBridge = new MessageQueue(
26+
// $FlowFixMe
27+
typeof __fbUninstallRNGlobalErrorHandler !== 'undefined' &&
28+
__fbUninstallRNGlobalErrorHandler === true, // eslint-disable-line no-undef
29+
);
1630

1731
// Wire up the batched bridge on the global object so that we can call into it.
1832
// Ideally, this would be the inverse relationship. I.e. the native environment

Libraries/BatchedBridge/MessageQueue.js

+24-2
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,21 @@ class MessageQueue {
5959

6060
__spy: ?(data: SpyData) => void;
6161

62-
constructor() {
62+
__guard: (() => void) => void;
63+
64+
constructor(shouldUninstallGlobalErrorHandler: boolean = false) {
6365
this._lazyCallableModules = {};
6466
this._queue = [[], [], [], 0];
6567
this._successCallbacks = [];
6668
this._failureCallbacks = [];
6769
this._callID = 0;
6870
this._lastFlush = 0;
6971
this._eventLoopStartTime = new Date().getTime();
72+
if (shouldUninstallGlobalErrorHandler) {
73+
this.uninstallGlobalErrorHandler();
74+
} else {
75+
this.installGlobalErrorHandler();
76+
}
7077

7178
if (__DEV__) {
7279
this._debugInfo = {};
@@ -252,11 +259,26 @@ class MessageQueue {
252259
}
253260
}
254261

262+
uninstallGlobalErrorHandler() {
263+
this.__guard = this.__guardUnsafe;
264+
}
265+
266+
installGlobalErrorHandler() {
267+
this.__guard = this.__guardSafe;
268+
}
269+
255270
/**
256271
* Private methods
257272
*/
258273

259-
__guard(fn: () => void) {
274+
// Lets exceptions propagate to be handled by the VM at the origin
275+
__guardUnsafe(fn: () => void) {
276+
this._inCall++;
277+
fn();
278+
this._inCall--;
279+
}
280+
281+
__guardSafe(fn: () => void) {
260282
this._inCall++;
261283
try {
262284
fn();

Libraries/BatchedBridge/__tests__/MessageQueue-test.js

+79-8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* of patent rights can be found in the PATENTS file in the same directory.
88
*
99
* @emails oncall+react_native
10+
* @format
1011
*/
1112
'use strict';
1213

@@ -38,10 +39,12 @@ describe('MessageQueue', function() {
3839
queue = new MessageQueue();
3940
queue.registerCallableModule(
4041
'MessageQueueTestModule',
41-
MessageQueueTestModule
42+
MessageQueueTestModule,
4243
);
43-
queue.createDebugLookup(0, 'MessageQueueTestModule',
44-
['testHook1', 'testHook2']);
44+
queue.createDebugLookup(0, 'MessageQueueTestModule', [
45+
'testHook1',
46+
'testHook2',
47+
]);
4548
});
4649

4750
it('should enqueue native calls', () => {
@@ -65,7 +68,15 @@ describe('MessageQueue', function() {
6568

6669
it('should call the stored callback', () => {
6770
let done = false;
68-
queue.enqueueNativeCall(0, 1, [], () => {}, () => { done = true; });
71+
queue.enqueueNativeCall(
72+
0,
73+
1,
74+
[],
75+
() => {},
76+
() => {
77+
done = true;
78+
},
79+
);
6980
queue.__invokeCallback(1, []);
7081
expect(done).toEqual(true);
7182
});
@@ -83,32 +94,92 @@ describe('MessageQueue', function() {
8394
});
8495

8596
it('should throw when calling with unknown module', () => {
86-
const unknownModule = 'UnknownModule', unknownMethod = 'UnknownMethod';
97+
const unknownModule = 'UnknownModule',
98+
unknownMethod = 'UnknownMethod';
8799
expect(() => queue.__callFunction(unknownModule, unknownMethod)).toThrow(
88100
`Module ${unknownModule} is not a registered callable module (calling ${unknownMethod})`,
89101
);
90102
});
91103

92104
it('should return lazily registered module', () => {
93-
const dummyModule = {}, name = 'modulesName';
105+
const dummyModule = {},
106+
name = 'modulesName';
94107
queue.registerLazyCallableModule(name, () => dummyModule);
95108

96109
expect(queue.getCallableModule(name)).toEqual(dummyModule);
97110
});
98111

99112
it('should not initialize lazily registered module before it was used for the first time', () => {
100-
const dummyModule = {}, name = 'modulesName';
113+
const dummyModule = {},
114+
name = 'modulesName';
101115
const factory = jest.fn(() => dummyModule);
102116
queue.registerLazyCallableModule(name, factory);
103117
expect(factory).not.toHaveBeenCalled();
104118
});
105119

106120
it('should initialize lazily registered module only once', () => {
107-
const dummyModule = {}, name = 'modulesName';
121+
const dummyModule = {},
122+
name = 'modulesName';
108123
const factory = jest.fn(() => dummyModule);
109124
queue.registerLazyCallableModule(name, factory);
110125
queue.getCallableModule(name);
111126
queue.getCallableModule(name);
112127
expect(factory).toHaveBeenCalledTimes(1);
113128
});
129+
130+
it('should catch all exceptions if the global error handler is installed', () => {
131+
const errorMessage = 'intentional error';
132+
const errorModule = {
133+
explode: function() {
134+
throw new Error(errorMessage);
135+
},
136+
};
137+
const name = 'errorModuleName';
138+
const factory = jest.fn(() => errorModule);
139+
queue.__guardSafe = jest.fn(() => {});
140+
queue.__guardUnsafe = jest.fn(() => {});
141+
queue.installGlobalErrorHandler();
142+
queue.registerLazyCallableModule(name, factory);
143+
queue.callFunctionReturnFlushedQueue(name, 'explode', []);
144+
expect(queue.__guardUnsafe).toHaveBeenCalledTimes(0);
145+
expect(queue.__guardSafe).toHaveBeenCalledTimes(2);
146+
});
147+
148+
it('should propagate exceptions if the global error handler is uninstalled', () => {
149+
queue.uninstallGlobalErrorHandler();
150+
const errorMessage = 'intentional error';
151+
const errorModule = {
152+
explode: function() {
153+
throw new Error(errorMessage);
154+
},
155+
};
156+
const name = 'errorModuleName';
157+
const factory = jest.fn(() => errorModule);
158+
queue.__guardUnsafe = jest.fn(() => {});
159+
queue.__guardSafe = jest.fn(() => {});
160+
queue.registerLazyCallableModule(name, factory);
161+
queue.uninstallGlobalErrorHandler();
162+
queue.callFunctionReturnFlushedQueue(name, 'explode');
163+
expect(queue.__guardUnsafe).toHaveBeenCalledTimes(2);
164+
expect(queue.__guardSafe).toHaveBeenCalledTimes(0);
165+
});
166+
167+
it('should catch all exceptions if the global error handler is re-installed', () => {
168+
const errorMessage = 'intentional error';
169+
const errorModule = {
170+
explode: function() {
171+
throw new Error(errorMessage);
172+
},
173+
};
174+
const name = 'errorModuleName';
175+
const factory = jest.fn(() => errorModule);
176+
queue.__guardUnsafe = jest.fn(() => {});
177+
queue.__guardSafe = jest.fn(() => {});
178+
queue.registerLazyCallableModule(name, factory);
179+
queue.uninstallGlobalErrorHandler();
180+
queue.installGlobalErrorHandler();
181+
queue.callFunctionReturnFlushedQueue(name, 'explode');
182+
expect(queue.__guardUnsafe).toHaveBeenCalledTimes(0);
183+
expect(queue.__guardSafe).toHaveBeenCalledTimes(2);
184+
});
114185
});

0 commit comments

Comments
 (0)