RCTJSStackFrame.m 4.8 KB

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