Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport 9_2_X] fix(ios): format js errors in cli output #12147

Merged
merged 2 commits into from
Oct 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion iphone/TitaniumKit/TitaniumKit/Sources/API/KrollBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -876,7 +876,7 @@ - (KrollWrapper *)loadJavascriptText:(NSString *)data fromFile:(NSString *)filen
KrollWrapper *module = [self loadCommonJSModule:data withSourceURL:url_];

if (![module respondsToSelector:@selector(replaceValue:forKey:notification:)]) {
@throw [NSException exceptionWithName:@"org.appcelerator.kroll"
@throw [NSException exceptionWithName:@"org.appcelerator.kroll.invalidmodule"
reason:[NSString stringWithFormat:@"Module \"%@\" failed to leave a valid exports object", filename]
userInfo:nil];
}
Expand Down Expand Up @@ -1327,6 +1327,11 @@ - (id)require:(KrollContext *)kroll path:(NSString *)path
break;
}
}
@catch (NSException *exception) {
if ([exception.name isEqualToString:@"org.appcelerator.kroll.invalidmodule"]) {
return nil;
}
}
@finally {
[self setCurrentURL:oldURL];
// Cache the resolved path for this request if we got a module
Expand Down
10 changes: 10 additions & 0 deletions iphone/TitaniumKit/TitaniumKit/Sources/API/TiExceptionHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
*/
@property (nonatomic, readonly) NSString *sourceURL;

/**
* Returns the actual source code line as a string where the error happened.
*/
@property (nonatomic, readonly) NSString *sourceLine;

/**
* Returns line number where error happened.
*/
Expand Down Expand Up @@ -54,6 +59,11 @@
*/
@property (nonatomic, readonly) NSArray<NSString *> *nativeStack;

/**
* Returns the pre-formated and cleaned native stack trace.
*/
@property (nonatomic, readonly) NSArray<NSString *> *formattedNativeStack;

- (id)initWithMessage:(NSString *)message sourceURL:(NSString *)sourceURL lineNo:(NSInteger)lineNo;
- (id)initWithDictionary:(NSDictionary *)dictionary;

Expand Down
149 changes: 96 additions & 53 deletions iphone/TitaniumKit/TitaniumKit/Sources/API/TiExceptionHandler.m
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ - (void)reportException:(NSException *)exception

- (void)reportScriptError:(TiScriptError *)scriptError
{
DebugLog(@"[ERROR] Script Error %@", [scriptError detailedDescription]);
DebugLog(@"[ERROR] %@", scriptError);

id<TiExceptionHandlerDelegate> currentDelegate = _delegate;
if (currentDelegate == nil) {
Expand All @@ -94,45 +94,15 @@ - (void)reportScriptError:(JSValue *)error inJSContext:(JSContext *)context

- (void)showScriptError:(TiScriptError *)error
{
NSArray<NSString *> *exceptionStackTrace = [error valueForKey:@"nativeStack"];
if (exceptionStackTrace == nil) {
exceptionStackTrace = [NSThread callStackSymbols];
}

NSMutableDictionary *errorDict = [error.dictionaryValue mutableCopy];
[errorDict setObject:[NSNumber numberWithLong:error.column] forKey:@"column"];
[errorDict setObject:[NSNumber numberWithLong:error.lineNo] forKey:@"line"];
if (exceptionStackTrace == nil) {
[[TiApp app] showModalError:[error description]];
} else {
NSMutableArray<NSString *> *formattedStackTrace = [[[NSMutableArray alloc] init] autorelease];
NSUInteger exceptionStackTraceLength = [exceptionStackTrace count];

// re-size stack trace and format results. Starting at index = 1 to not include showScriptError call
for (NSInteger i = 1; i < (exceptionStackTraceLength >= 20 ? 20 : exceptionStackTraceLength); i++) {
NSString *line = [self removeWhitespace:[exceptionStackTrace objectAtIndex:i]];

// remove stack index
line = [line substringFromIndex:[line rangeOfString:@" "].location + 1];

[formattedStackTrace addObject:line];
}
NSString *stackTrace = [formattedStackTrace componentsJoinedByString:@"\n"];
[errorDict setObject:stackTrace forKey:@"nativeStack"];

[[TiApp app] showModalError:[NSString stringWithFormat:@"%@\n\n%@", [error description], stackTrace]];
}
NSString *stackTrace = [error.formattedNativeStack componentsJoinedByString:@"\n"];
[errorDict setObject:stackTrace forKey:@"nativeStack"];
[[TiApp app] showModalError:[error description]];
[[NSNotificationCenter defaultCenter] postNotificationName:kTiErrorNotification object:self userInfo:errorDict];
}

- (NSString *)removeWhitespace:(NSString *)line
{
while ([line rangeOfString:@" "].length > 0) {
line = [line stringByReplacingOccurrencesOfString:@" " withString:@" "];
}
return line;
}

#pragma mark - TiExceptionHandlerDelegate

- (void)handleUncaughtException:(NSException *)exception
Expand All @@ -151,11 +121,13 @@ @implementation TiScriptError

@synthesize message = _message;
@synthesize sourceURL = _sourceURL;
@synthesize sourceLine = _sourceLine;
@synthesize lineNo = _lineNo;
@synthesize column = _column;
@synthesize dictionaryValue = _dictionaryValue;
@synthesize backtrace = _backtrace;
@synthesize nativeStack = _nativeStack;
@synthesize formattedNativeStack = _formattedNativeStack;

- (id)initWithMessage:(NSString *)message sourceURL:(NSString *)sourceURL lineNo:(NSInteger)lineNo
{
Expand Down Expand Up @@ -194,43 +166,114 @@ - (void)dealloc
{
RELEASE_TO_NIL(_message);
RELEASE_TO_NIL(_sourceURL);
RELEASE_TO_NIL(_sourceLine);
RELEASE_TO_NIL(_backtrace);
RELEASE_TO_NIL(_dictionaryValue);
RELEASE_TO_NIL(_nativeStack);
RELEASE_TO_NIL(_formattedNativeStack);
[super dealloc];
}

- (NSString *)description
{
if (self.sourceURL != nil) {
// attempt to load encrypted source code
NSURL *sourceURL = [NSURL URLWithString:self.sourceURL];
NSData *data = [TiUtils loadAppResource:sourceURL];
NSString *source = nil;
if (data == nil) {
source = [NSString stringWithContentsOfFile:[sourceURL path] encoding:NSUTF8StringEncoding error:NULL];
} else {
source = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
NSMutableString *message = [[NSMutableString new] autorelease];
NSString *encodedBundlePath = [NSString stringWithFormat:@"file://%@", [[NSBundle mainBundle].bundlePath stringByReplacingOccurrencesOfString:@" " withString:@"%20"]];

if (self.sourceURL) {
[message appendFormat:@"%@:%ld\n", [self.sourceURL stringByReplacingOccurrencesOfString:encodedBundlePath withString:@""], (long)self.lineNo];
[message appendFormat:@"%@\n", self.sourceLine];
NSString *columnIndicatorPadding = [@"" stringByPaddingToLength:self.column withString:@" " startingAtIndex:0];
[message appendFormat:@"%@^\n", columnIndicatorPadding];
}

NSString *type = self.dictionaryValue[@"type"] != nil ? self.dictionaryValue[@"type"] : @"Error";
[message appendFormat:@"%@: %@", type, self.message];

NSString *jsStack = [self.backtrace stringByReplacingOccurrencesOfString:encodedBundlePath withString:@""];
NSArray *jsStackLines = [jsStack componentsSeparatedByCharactersInSet:NSCharacterSet.newlineCharacterSet];
NSMutableString *formattedJsStack = [[NSMutableString new] autorelease];
for (NSString *line in jsStackLines) {
NSRange atSymbolRange = [line rangeOfString:@"@"];
NSInteger atSymbolIndex = atSymbolRange.location == NSNotFound ? -1 : atSymbolRange.location;
NSString *source = [line substringFromIndex:atSymbolIndex + 1];
NSString *symbolName = @"Object.<anonymous>";
if (atSymbolIndex != -1) {
symbolName = [line substringWithRange:NSMakeRange(0, atSymbolIndex)];
}
NSArray *lines = [source componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSString *line = [lines objectAtIndex:self.lineNo - 1];
NSString *linePointer = [@"" stringByPaddingToLength:self.column withString:@" " startingAtIndex:0];
// global code is our module wrapper code which can be ignored
if ([symbolName isEqualToString:@"global code"]) {
continue;
}
[formattedJsStack appendFormat:@"\n at %@ (%@)", symbolName, source];
}
[message appendString:formattedJsStack];

// remove bundle path from source paths
NSString *encodedBundlePath = [NSString stringWithFormat:@"file://%@", [[NSBundle mainBundle].bundlePath stringByReplacingOccurrencesOfString:@" " withString:@"%20"]];
NSString *jsStack = [self.backtrace stringByReplacingOccurrencesOfString:encodedBundlePath withString:@""];
[message appendFormat:@"\n\n %@", [self.formattedNativeStack componentsJoinedByString:@"\n "]];

return [NSString stringWithFormat:@"/%@:%ld\n%@\n%@^\n%@\n%@", [self.sourceURL lastPathComponent], (long)self.lineNo, line, linePointer, self.message, jsStack];
} else {
return [NSString stringWithFormat:@"%@", self.message];
}
return message;
}

- (NSString *)detailedDescription
{
return _dictionaryValue != nil ? [_dictionaryValue description] : [self description];
}

- (NSArray<NSString *> *)formattedNativeStack
{
if (_formattedNativeStack != nil) {
return _formattedNativeStack;
}

NSArray<NSString *> *stackTrace = self.nativeStack;
if (stackTrace == nil) {
stackTrace = [NSThread callStackSymbols];
}
NSMutableArray<NSString *> *formattedStackTrace = [[[NSMutableArray alloc] init] autorelease];
NSUInteger stackTraceLength = [stackTrace count];
// re-size stack trace and format results. starting at index = 2 to not include this method and callee
for (NSInteger i = 2; i < (stackTraceLength >= 20 ? 20 : stackTraceLength); i++) {
NSString *line = [self removeWhitespace:stackTrace[i]];
// remove stack index
line = [line substringFromIndex:[line rangeOfString:@" "].location + 1];
[formattedStackTrace addObject:line];
}
_formattedNativeStack = [formattedStackTrace copy];

return _formattedNativeStack;
}

- (NSString *)sourceLine
{
if (_sourceURL == nil) {
return nil;
}

if (_sourceLine != nil) {
return _sourceLine;
}

NSURL *sourceURL = [NSURL URLWithString:self.sourceURL];
NSData *data = [TiUtils loadAppResource:sourceURL];
NSString *source = nil;
if (data == nil) {
source = [NSString stringWithContentsOfFile:[sourceURL path] encoding:NSUTF8StringEncoding error:NULL];
} else {
source = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
}
NSArray<NSString *> *lines = [source componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
_sourceLine = [[lines objectAtIndex:self.lineNo - 1] retain];

return _sourceLine;
}

- (NSString *)removeWhitespace:(NSString *)line
{
while ([line rangeOfString:@" "].length > 0) {
line = [line stringByReplacingOccurrencesOfString:@" " withString:@" "];
}
return line;
}

@end

//
Expand Down