1/* 2 * Copyright (c) Meta Platforms, Inc. and affiliates. 3 * 4 * This source code is licensed under the MIT license found in the 5 * LICENSE file in the root directory of this source tree. 6 */ 7 8#import "ABI48_0_0RCTJSStackFrame.h" 9 10#import "ABI48_0_0RCTLog.h" 11#import "ABI48_0_0RCTUtils.h" 12 13/** 14 * The RegEx used to parse Error.stack. 15 * 16 * JavaScriptCore has the following format: 17 * 18 * Exception: Error: argh 19 * func1@/path/to/file.js:2:18 20 * func2@/path/to/file.js:6:8 21 * eval@[native code] 22 * global code@/path/to/file.js:13:5 23 * 24 * Another supported format: 25 * 26 * Error: argh 27 * at func1 (/path/to/file.js:2:18) 28 * at func2 (/path/to/file.js:6:8) 29 * at eval (native) 30 * at global (/path/to/file.js:13:5) 31 */ 32static NSRegularExpression *ABI48_0_0RCTJSStackFrameRegex() 33{ 34 static dispatch_once_t onceToken; 35 static NSRegularExpression *_regex; 36 dispatch_once(&onceToken, ^{ 37 NSString *pattern = 38 @"\\s*(?:at)?\\s*" // Skip leading "at" and whitespace, noncapturing 39 @"(.+?)" // Capture the function name (group 1) 40 @"\\s*[@(]" // Skip whitespace, then @ or ( 41 @"(.*):" // Capture the file name (group 2), then colon 42 @"(\\d+):(\\d+)" // Line and column number (groups 3 and 4) 43 @"\\)?$" // Optional closing paren and EOL 44 ; 45 NSError *regexError; 46 _regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:®exError]; 47 if (regexError) { 48 ABI48_0_0RCTLogError(@"Failed to build regex: %@", [regexError localizedDescription]); 49 } 50 }); 51 return _regex; 52} 53 54@implementation ABI48_0_0RCTJSStackFrame 55 56- (instancetype)initWithMethodName:(NSString *)methodName 57 file:(NSString *)file 58 lineNumber:(NSInteger)lineNumber 59 column:(NSInteger)column 60 collapse:(BOOL)collapse 61{ 62 if (self = [super init]) { 63 _methodName = methodName; 64 _file = file; 65 _lineNumber = lineNumber; 66 _column = column; 67 _collapse = collapse; 68 } 69 return self; 70} 71 72- (NSDictionary *)toDictionary 73{ 74 return @{ 75 @"methodName" : ABI48_0_0RCTNullIfNil(self.methodName), 76 @"file" : ABI48_0_0RCTNullIfNil(self.file), 77 @"lineNumber" : @(self.lineNumber), 78 @"column" : @(self.column), 79 @"collapse" : @(self.collapse) 80 }; 81} 82 83+ (instancetype)stackFrameWithLine:(NSString *)line 84{ 85 NSTextCheckingResult *match = [ABI48_0_0RCTJSStackFrameRegex() firstMatchInString:line 86 options:0 87 range:NSMakeRange(0, line.length)]; 88 if (!match) { 89 return nil; 90 } 91 92 // methodName may not be present for e.g. anonymous functions 93 const NSRange methodNameRange = [match rangeAtIndex:1]; 94 NSString *methodName = methodNameRange.location == NSNotFound ? nil : [line substringWithRange:methodNameRange]; 95 NSString *file = [line substringWithRange:[match rangeAtIndex:2]]; 96 NSString *lineNumber = [line substringWithRange:[match rangeAtIndex:3]]; 97 NSString *column = [line substringWithRange:[match rangeAtIndex:4]]; 98 99 return [[self alloc] initWithMethodName:methodName 100 file:file 101 lineNumber:[lineNumber integerValue] 102 column:[column integerValue] 103 collapse:NO]; 104} 105 106+ (instancetype)stackFrameWithDictionary:(NSDictionary *)dict 107{ 108 return [[self alloc] initWithMethodName:ABI48_0_0RCTNilIfNull(dict[@"methodName"]) 109 file:dict[@"file"] 110 lineNumber:[ABI48_0_0RCTNilIfNull(dict[@"lineNumber"]) integerValue] 111 column:[ABI48_0_0RCTNilIfNull(dict[@"column"]) integerValue] 112 collapse:[ABI48_0_0RCTNilIfNull(dict[@"collapse"]) boolValue]]; 113} 114 115+ (NSArray<ABI48_0_0RCTJSStackFrame *> *)stackFramesWithLines:(NSString *)lines 116{ 117 NSMutableArray *stack = [NSMutableArray new]; 118 for (NSString *line in [lines componentsSeparatedByString:@"\n"]) { 119 ABI48_0_0RCTJSStackFrame *frame = [self stackFrameWithLine:line]; 120 if (frame) { 121 [stack addObject:frame]; 122 } 123 } 124 return stack; 125} 126 127+ (NSArray<ABI48_0_0RCTJSStackFrame *> *)stackFramesWithDictionaries:(NSArray<NSDictionary *> *)dicts 128{ 129 NSMutableArray *stack = [NSMutableArray new]; 130 for (NSDictionary *dict in dicts) { 131 ABI48_0_0RCTJSStackFrame *frame = [self stackFrameWithDictionary:dict]; 132 if (frame) { 133 [stack addObject:frame]; 134 } 135 } 136 return stack; 137} 138 139- (NSString *)description 140{ 141 return [NSString stringWithFormat:@"<%@: %p method name: %@; file name: %@; line: %ld; column: %ld>", 142 self.class, 143 self, 144 self.methodName, 145 self.file, 146 (long)self.lineNumber, 147 (long)self.column]; 148} 149 150@end 151