Skip to content

Commit

Permalink
fix(ios): format js errors in cli output (#12147)
Browse files Browse the repository at this point in the history
Fixes TIMOB-27812
  • Loading branch information
build authored Oct 1, 2020
1 parent 9b516e4 commit bc32947
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 54 deletions.
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

0 comments on commit bc32947

Please sign in to comment.