Skip to content

Commit 0c83407

Browse files
javachefacebook-github-bot-5
authored andcommitted
Simplify logging exceptions from JS to native
Reviewed By: vjeux Differential Revision: D2615559 fb-gh-sync-id: ee931b3691251c8b6276699c6f927e47d8e8fd97
1 parent d9b4c57 commit 0c83407

3 files changed

Lines changed: 65 additions & 104 deletions

File tree

Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js

Lines changed: 29 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ var sourceMapPromise;
2121

2222
var exceptionID = 0;
2323

24-
function reportException(e: Error, isFatal: bool, stack?: any) {
24+
/**
25+
* Handles the developer-visible aspect of errors and exceptions
26+
*/
27+
function reportException(e: Error, isFatal: bool) {
2528
var currentExceptionID = ++exceptionID;
2629
if (RCTExceptionsManager) {
27-
if (!stack) {
28-
stack = parseErrorStack(e);
29-
}
30+
var stack = parseErrorStack(e);
3031
if (isFatal) {
3132
RCTExceptionsManager.reportFatalException(e.message, stack, currentExceptionID);
3233
} else {
@@ -47,6 +48,9 @@ function reportException(e: Error, isFatal: bool, stack?: any) {
4748
}
4849
}
4950

51+
/**
52+
* Logs exceptions to the (native) console and displays them
53+
*/
5054
function handleException(e: Error, isFatal: boolean) {
5155
// Workaround for reporting errors caused by `throw 'some string'`
5256
// Unfortunately there is no way to figure out the stacktrace in this
@@ -55,74 +59,45 @@ function handleException(e: Error, isFatal: boolean) {
5559
if (!e.message) {
5660
e = new Error(e);
5761
}
58-
var stack = parseErrorStack(e);
59-
var msg =
60-
'Error: ' + e.message +
61-
'\n stack: \n' + stackToString(stack) +
62-
'\n URL: ' + (e: any).sourceURL +
63-
'\n line: ' + (e: any).line +
64-
'\n message: ' + e.message;
65-
if (console.errorOriginal) {
66-
console.errorOriginal(msg);
67-
} else {
68-
console.error(msg);
69-
}
70-
reportException(e, isFatal, stack);
62+
63+
(console._errorOriginal || console.error)(e.message);
64+
reportException(e, isFatal);
7165
}
7266

7367
/**
7468
* Shows a redbox with stacktrace for all console.error messages. Disable by
7569
* setting `console.reportErrorsAsExceptions = false;` in your app.
7670
*/
7771
function installConsoleErrorReporter() {
78-
if (console.reportException) {
72+
// Enable reportErrorsAsExceptions
73+
if (console._errorOriginal) {
7974
return; // already installed
8075
}
81-
console.reportException = reportException;
82-
console.errorOriginal = console.error.bind(console);
76+
console._errorOriginal = console.error.bind(console);
8377
console.error = function reactConsoleError() {
84-
// Note that when using the built-in context executor on iOS (i.e., not
85-
// Chrome debugging), console.error is already stubbed out to cause a
86-
// redbox via RCTNativeLoggingHook.
87-
console.errorOriginal.apply(null, arguments);
78+
console._errorOriginal.apply(null, arguments);
8879
if (!console.reportErrorsAsExceptions) {
8980
return;
9081
}
91-
var str = Array.prototype.map.call(arguments, stringifySafe).join(', ');
92-
if (str.slice(0, 10) === '"Warning: ') {
93-
// React warnings use console.error so that a stack trace is shown, but
94-
// we don't (currently) want these to show a redbox
95-
// (Note: Logic duplicated in polyfills/console.js.)
96-
return;
82+
83+
if (arguments[0] && arguments[0].stack) {
84+
reportException(arguments[0], /* isFatal */ false);
85+
} else {
86+
var str = Array.prototype.map.call(arguments, stringifySafe).join(', ');
87+
if (str.slice(0, 10) === '"Warning: ') {
88+
// React warnings use console.error so that a stack trace is shown, but
89+
// we don't (currently) want these to show a redbox
90+
// (Note: Logic duplicated in polyfills/console.js.)
91+
return;
92+
}
93+
var error : any = new Error('console.error: ' + str);
94+
error.framesToPop = 1;
95+
reportException(error, /* isFatal */ false);
9796
}
98-
var error: any = new Error('console.error: ' + str);
99-
error.framesToPop = 1;
100-
reportException(error, /* isFatal */ false);
10197
};
10298
if (console.reportErrorsAsExceptions === undefined) {
10399
console.reportErrorsAsExceptions = true; // Individual apps can disable this
104100
}
105101
}
106102

107-
function stackToString(stack) {
108-
var maxLength = Math.max.apply(null, stack.map(frame => frame.methodName.length));
109-
return stack.map(frame => stackFrameToString(frame, maxLength)).join('\n');
110-
}
111-
112-
function stackFrameToString(stackFrame, maxLength) {
113-
var fileNameParts = stackFrame.file.split('/');
114-
var fileName = fileNameParts[fileNameParts.length - 1];
115-
116-
if (fileName.length > 18) {
117-
fileName = fileName.substr(0, 17) + '\u2026'; /* ... */
118-
}
119-
120-
var spaces = fillSpaces(maxLength - stackFrame.methodName.length);
121-
return ' ' + stackFrame.methodName + spaces + ' ' + fileName + ':' + stackFrame.lineNumber;
122-
}
123-
124-
function fillSpaces(n) {
125-
return new Array(n + 1).join(' ');
126-
}
127-
128103
module.exports = { handleException, installConsoleErrorReporter };

Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,10 @@ if (typeof window === 'undefined') {
3232
window = GLOBAL;
3333
}
3434

35-
function handleError(e, isFatal) {
36-
try {
37-
require('ExceptionsManager').handleException(e, isFatal);
38-
} catch(ee) {
39-
console.log('Failed to print error: ', ee.message);
40-
}
35+
function setUpConsole() {
36+
// ExceptionsManager transitively requires Promise so we install it after
37+
var ExceptionsManager = require('ExceptionsManager');
38+
ExceptionsManager.installConsoleErrorReporter();
4139
}
4240

4341
/**
@@ -72,21 +70,19 @@ function polyfillGlobal(name, newValue, scope=GLOBAL) {
7270
Object.defineProperty(scope, name, {...descriptor, value: newValue});
7371
}
7472

75-
function setUpRedBoxErrorHandler() {
73+
function setUpErrorHandler() {
74+
function handleError(e, isFatal) {
75+
try {
76+
require('ExceptionsManager').handleException(e, isFatal);
77+
} catch(ee) {
78+
console.log('Failed to print error: ', ee.message);
79+
}
80+
}
81+
7682
var ErrorUtils = require('ErrorUtils');
7783
ErrorUtils.setGlobalHandler(handleError);
7884
}
7985

80-
function setUpRedBoxConsoleErrorHandler() {
81-
// ExceptionsManager transitively requires Promise so we install it after
82-
var ExceptionsManager = require('ExceptionsManager');
83-
var Platform = require('Platform');
84-
// TODO (#6925182): Enable console.error redbox on Android
85-
if (__DEV__ && Platform.OS === 'ios') {
86-
ExceptionsManager.installConsoleErrorReporter();
87-
}
88-
}
89-
9086
function setUpFlowChecker() {
9187
if (__DEV__) {
9288
var checkFlowAtRuntime = require('checkFlowAtRuntime');
@@ -187,12 +183,12 @@ function setUpDevTools() {
187183
}
188184

189185
setUpProcessEnv();
190-
setUpRedBoxErrorHandler();
186+
setUpConsole();
191187
setUpTimers();
192188
setUpAlert();
193189
setUpPromise();
190+
setUpErrorHandler();
194191
setUpXHR();
195-
setUpRedBoxConsoleErrorHandler();
196192
setUpGeolocation();
197193
setUpWebSockets();
198194
setUpProfile();

React/Executors/RCTContextExecutor.m

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
#if RCT_JSC_PROFILER
3434
#include <dlfcn.h>
3535

36-
static NSString * const RCTJSCProfilerEnabledDefaultsKey = @"RCTJSCProfilerEnabled";
36+
static NSString *const RCTJSCProfilerEnabledDefaultsKey = @"RCTJSCProfilerEnabled";
3737

3838
#ifndef RCT_JSC_PROFILER_DYLIB
3939
#define RCT_JSC_PROFILER_DYLIB [[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"RCTJSCProfiler.ios%zd", [[[UIDevice currentDevice] systemVersion] integerValue]] ofType:@"dylib" inDirectory:@"RCTJSCProfiler"] UTF8String]
@@ -119,26 +119,14 @@ @implementation RCTContextExecutor
119119
static JSValueRef RCTNativeLoggingHook(JSContextRef context, __unused JSObjectRef object, __unused JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception)
120120
{
121121
if (argumentCount > 0) {
122-
JSStringRef messageRef = JSValueToStringCopy(context, arguments[0], exception);
123-
if (!messageRef) {
124-
return JSValueMakeUndefined(context);
125-
}
126-
NSString *message = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, messageRef);
127-
JSStringRelease(messageRef);
128-
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:
129-
@"( stack: )?([_a-z0-9]*)@?(http://|file:///)[a-z.0-9:/_-]+/([a-z0-9_]+).bundle(:[0-9]+:[0-9]+)"
130-
options:NSRegularExpressionCaseInsensitive
131-
error:NULL];
132-
message = [regex stringByReplacingMatchesInString:message
133-
options:0
134-
range:(NSRange){0, message.length}
135-
withTemplate:@"[$4$5] \t$2"];
122+
NSString *message = RCTJSValueToNSString(context, arguments[0], exception);
136123

137124
RCTLogLevel level = RCTLogLevelInfo;
138125
if (argumentCount > 1) {
139-
level = MAX(level, JSValueToNumber(context, arguments[1], exception) - 1);
126+
level = MAX(level, JSValueToNumber(context, arguments[1], exception));
140127
}
141-
RCTGetLogFunction()(level, nil, nil, message);
128+
129+
_RCTLog(level, @"%@", message);
142130
}
143131

144132
return JSValueMakeUndefined(context);
@@ -152,18 +140,22 @@ static JSValueRef RCTNoop(JSContextRef context, __unused JSObjectRef object, __u
152140
return JSValueMakeUndefined(context);
153141
}
154142

155-
static NSString *RCTJSValueToNSString(JSContextRef context, JSValueRef value)
143+
static NSString *RCTJSValueToNSString(JSContextRef context, JSValueRef value, JSValueRef *exception)
156144
{
157-
JSStringRef JSString = JSValueToStringCopy(context, value, NULL);
145+
JSStringRef JSString = JSValueToStringCopy(context, value, exception);
146+
if (!JSString) {
147+
return nil;
148+
}
149+
158150
CFStringRef string = JSStringCopyCFString(kCFAllocatorDefault, JSString);
159151
JSStringRelease(JSString);
160152

161153
return (__bridge_transfer NSString *)string;
162154
}
163155

164-
static NSString *RCTJSValueToJSONString(JSContextRef context, JSValueRef value, unsigned indent)
156+
static NSString *RCTJSValueToJSONString(JSContextRef context, JSValueRef value, JSValueRef *exception, unsigned indent)
165157
{
166-
JSStringRef JSString = JSValueCreateJSONString(context, value, indent, NULL);
158+
JSStringRef JSString = JSValueCreateJSONString(context, value, indent, exception);
167159
CFStringRef string = JSStringCopyCFString(kCFAllocatorDefault, JSString);
168160
JSStringRelease(JSString);
169161

@@ -172,14 +164,14 @@ static JSValueRef RCTNoop(JSContextRef context, __unused JSObjectRef object, __u
172164

173165
static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
174166
{
175-
NSString *errorMessage = jsError ? RCTJSValueToNSString(context, jsError) : @"unknown JS error";
176-
NSString *details = jsError ? RCTJSValueToJSONString(context, jsError, 2) : @"no details";
167+
NSString *errorMessage = jsError ? RCTJSValueToNSString(context, jsError, NULL) : @"unknown JS error";
168+
NSString *details = jsError ? RCTJSValueToJSONString(context, jsError, NULL, 2) : @"no details";
177169
return [NSError errorWithDomain:@"JS" code:1 userInfo:@{NSLocalizedDescriptionKey: errorMessage, NSLocalizedFailureReasonErrorKey: details}];
178170
}
179171

180172
#if RCT_DEV
181173

182-
static JSValueRef RCTNativeTraceBeginSection(JSContextRef context, __unused JSObjectRef object, __unused JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], __unused JSValueRef *exception)
174+
static JSValueRef RCTNativeTraceBeginSection(JSContextRef context, __unused JSObjectRef object, __unused JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception)
183175
{
184176
static int profileCounter = 1;
185177
NSString *profileName;
@@ -189,14 +181,14 @@ static JSValueRef RCTNativeTraceBeginSection(JSContextRef context, __unused JSOb
189181
if (JSValueIsNumber(context, arguments[0])) {
190182
tag = JSValueToNumber(context, arguments[0], NULL);
191183
} else {
192-
profileName = RCTJSValueToNSString(context, arguments[0]);
184+
profileName = RCTJSValueToNSString(context, arguments[0], exception);
193185
}
194186
} else {
195187
profileName = [NSString stringWithFormat:@"Profile %d", profileCounter++];
196188
}
197189

198190
if (argumentCount > 1 && JSValueIsString(context, arguments[1])) {
199-
profileName = RCTJSValueToNSString(context, arguments[1]);
191+
profileName = RCTJSValueToNSString(context, arguments[1], exception);
200192
}
201193

202194
if (profileName) {
@@ -206,13 +198,11 @@ static JSValueRef RCTNativeTraceBeginSection(JSContextRef context, __unused JSOb
206198
return JSValueMakeUndefined(context);
207199
}
208200

209-
static JSValueRef RCTNativeTraceEndSection(JSContextRef context, __unused JSObjectRef object, __unused JSObjectRef thisObject, __unused size_t argumentCount, __unused const JSValueRef arguments[], __unused JSValueRef *exception)
201+
static JSValueRef RCTNativeTraceEndSection(JSContextRef context, __unused JSObjectRef object, __unused JSObjectRef thisObject, __unused size_t argumentCount, __unused const JSValueRef arguments[], JSValueRef *exception)
210202
{
211203
if (argumentCount > 0) {
212-
JSValueRef *error = NULL;
213-
double tag = JSValueToNumber(context, arguments[0], error);
214-
215-
if (error == NULL) {
204+
double tag = JSValueToNumber(context, arguments[0], exception);
205+
if (exception == NULL) {
216206
RCTProfileEndEvent((uint64_t)tag, @"console", nil);
217207
}
218208
}

0 commit comments

Comments
 (0)