def expand_frames(self, frames): last_state = None state = None cache = self.cache sourcemaps = self.sourcemaps all_errors = [] for frame in frames: errors = cache.get_errors(frame.abs_path) if errors: all_errors.extend(errors) source = cache.get(frame.abs_path) if source is None: logger.info('No source found for %s', frame.abs_path) continue sourcemap_url, sourcemap_idx = sourcemaps.get_link(frame.abs_path) if sourcemap_idx and frame.colno is not None: last_state = state state = find_source(sourcemap_idx, frame.lineno, frame.colno) if is_data_uri(sourcemap_url): sourcemap_label = frame.abs_path else: sourcemap_label = sourcemap_url abs_path = urljoin(sourcemap_url, state.src) logger.debug('Mapping compressed source %r to mapping in %r', frame.abs_path, abs_path) source = cache.get(abs_path) if not source: frame.data = { 'sourcemap': sourcemap_label, } errors = cache.get_errors(abs_path) if errors: all_errors.extend(errors) else: all_errors.append({ 'type': EventError.JS_MISSING_SOURCE, 'url': force_bytes(abs_path, errors='replace'), }) # Store original data in annotation frame.data = { 'orig_lineno': frame.lineno, 'orig_colno': frame.colno, 'orig_function': frame.function, 'orig_abs_path': frame.abs_path, 'orig_filename': frame.filename, 'sourcemap': sourcemap_label, } # SourceMap's return zero-indexed lineno's frame.lineno = state.src_line + 1 frame.colno = state.src_col # The offending function is always the previous function in the stack # Honestly, no idea what the bottom most frame is, so we're ignoring that atm if last_state: frame.function = last_state.name or frame.function else: frame.function = state.name or frame.function filename = state.src # special case webpack support # abs_path will always be the full path with webpack:/// prefix. # filename will be relative to that if abs_path.startswith('webpack:'): filename = abs_path # webpack seems to use ~ to imply "relative to resolver root" # which is generally seen for third party deps # (i.e. node_modules) if '/~/' in filename: filename = '~/' + abs_path.split('/~/', 1)[-1] else: filename = filename.split('webpack:///', 1)[-1] # As noted above, '~/' means they're coming from node_modules, # so these are not app dependencies if filename.startswith('~/'): frame.in_app = False # And conversely, local dependencies start with './' elif filename.startswith('./'): frame.in_app = True # We want to explicitly generate a webpack module name frame.module = generate_module(filename) frame.abs_path = abs_path frame.filename = filename if not frame.module and abs_path.startswith(('http:', 'https:', 'webpack:')): frame.module = generate_module(abs_path) elif sourcemap_url: frame.data = { 'sourcemap': sourcemap_url, } # TODO: theoretically a minified source could point to another mapped, minified source frame.pre_context, frame.context_line, frame.post_context = get_source_context( source=source, lineno=frame.lineno, colno=frame.colno or 0) return all_errors
def process_frame(self, processable_frame, processing_task): frame = processable_frame.frame token = None cache = self.cache sourcemaps = self.sourcemaps all_errors = [] sourcemap_applied = False # can't fetch source if there's no filename present or no line if not frame.get('abs_path') or not frame.get('lineno'): return # can't fetch if this is internal node module as well # therefore we only process user-land frames (starting with /) # or those created by bundle/webpack internals if self.data.get('platform') == 'node' and \ not frame.get('abs_path').startswith(('/', 'app:', 'webpack:')): return errors = cache.get_errors(frame['abs_path']) if errors: all_errors.extend(errors) # This might fail but that's okay, we try with a different path a # bit later down the road. source = self.get_sourceview(frame['abs_path']) in_app = None new_frame = dict(frame) raw_frame = dict(frame) sourcemap_url, sourcemap_view = sourcemaps.get_link(frame['abs_path']) self.sourcemaps_touched.add(sourcemap_url) if sourcemap_view and frame.get('colno') is None: all_errors.append( { 'type': EventError.JS_NO_COLUMN, 'url': http.expose_url(frame['abs_path']), } ) elif sourcemap_view: if is_data_uri(sourcemap_url): sourcemap_label = frame['abs_path'] else: sourcemap_label = sourcemap_url sourcemap_label = http.expose_url(sourcemap_label) if frame.get('function'): minified_function_name = frame['function'] minified_source = self.get_sourceview(frame['abs_path']) else: minified_function_name = minified_source = None try: # Errors are 1-indexed in the frames, so we need to -1 to get # zero-indexed value from tokens. assert frame['lineno'] > 0, "line numbers are 1-indexed" token = sourcemap_view.lookup(frame['lineno'] - 1, frame['colno'] - 1, minified_function_name, minified_source) except Exception: token = None all_errors.append( { 'type': EventError.JS_INVALID_SOURCEMAP_LOCATION, 'column': frame.get('colno'), 'row': frame.get('lineno'), 'source': frame['abs_path'], 'sourcemap': sourcemap_label, } ) # persist the token so that we can find it later processable_frame.data['token'] = token # Store original data in annotation new_frame['data'] = dict(frame.get('data') or {}, sourcemap=sourcemap_label) sourcemap_applied = True if token is not None: abs_path = urljoin(sourcemap_url, token.src) logger.debug( 'Mapping compressed source %r to mapping in %r', frame['abs_path'], abs_path ) source = self.get_sourceview(abs_path) if source is None: errors = cache.get_errors(abs_path) if errors: all_errors.extend(errors) else: all_errors.append( { 'type': EventError.JS_MISSING_SOURCE, 'url': http.expose_url(abs_path), } ) if token is not None: # the tokens are zero indexed, so offset correctly new_frame['lineno'] = token.src_line + 1 new_frame['colno'] = token.src_col + 1 # Try to use the function name we got from symbolic original_function_name = token.function_name # In the ideal case we can use the function name from the # frame and the location to resolve the original name # through the heuristics in our sourcemap library. if original_function_name is None: last_token = None # Find the previous token for function name handling as a # fallback. if processable_frame.previous_frame and \ processable_frame.previous_frame.processor is self: last_token = processable_frame.previous_frame.data.get('token') if last_token: original_function_name = last_token.name if original_function_name is not None: new_frame['function'] = original_function_name filename = token.src # special case webpack support # abs_path will always be the full path with webpack:/// prefix. # filename will be relative to that if abs_path.startswith('webpack:'): filename = abs_path # webpack seems to use ~ to imply "relative to resolver root" # which is generally seen for third party deps # (i.e. node_modules) if '/~/' in filename: filename = '~/' + abs_path.split('/~/', 1)[-1] else: filename = filename.split('webpack:///', 1)[-1] # As noted above: # * [js/node] '~/' means they're coming from node_modules, so these are not app dependencies # * [node] sames goes for `./node_modules/` and '../node_modules/', which is used when bundling node apps # * [node] and webpack, which includes it's own code to bootstrap all modules and its internals # eg. webpack:///webpack/bootstrap, webpack:///external if filename.startswith('~/') or \ '/node_modules/' in filename or \ not filename.startswith('./'): in_app = False # And conversely, local dependencies start with './' elif filename.startswith('./'): in_app = True # We want to explicitly generate a webpack module name new_frame['module'] = generate_module(filename) # while you could technically use a subpath of 'node_modules' for your libraries, # it would be an extremely complicated decision and we've not seen anyone do it # so instead we assume if node_modules is in the path its part of the vendored code elif '/node_modules/' in abs_path: in_app = False if abs_path.startswith('app:'): if filename and NODE_MODULES_RE.search(filename): in_app = False else: in_app = True new_frame['abs_path'] = abs_path new_frame['filename'] = filename if not frame.get('module') and abs_path.startswith( ('http:', 'https:', 'webpack:', 'app:') ): new_frame['module'] = generate_module(abs_path) elif sourcemap_url: new_frame['data'] = dict( new_frame.get('data') or {}, sourcemap=http.expose_url(sourcemap_url) ) # TODO: theoretically a minified source could point to # another mapped, minified source changed_frame = self.expand_frame(new_frame, source=source) # If we did not manage to match but we do have a line or column # we want to report an error here. if not new_frame.get('context_line') \ and source and \ new_frame.get('colno') is not None: all_errors.append( { 'type': EventError.JS_INVALID_SOURCEMAP_LOCATION, 'column': new_frame['colno'], 'row': new_frame['lineno'], 'source': new_frame['abs_path'], } ) changed_raw = sourcemap_applied and self.expand_frame(raw_frame) if sourcemap_applied or all_errors or changed_frame or \ changed_raw: if in_app is not None: new_frame['in_app'] = in_app raw_frame['in_app'] = in_app return [new_frame], [raw_frame] if changed_raw else None, all_errors
def expand_frames(self, frames): last_state = None state = None has_changes = False cache = self.cache sourcemaps = self.sourcemaps for frame in frames: errors = cache.get_errors(frame.abs_path) if errors: has_changes = True frame.errors = errors source = cache.get(frame.abs_path) if source is None: logger.info('No source found for %s', frame.abs_path) continue sourcemap_url, sourcemap_idx = sourcemaps.get_link(frame.abs_path) if sourcemap_idx and frame.colno is not None: last_state = state state = find_source(sourcemap_idx, frame.lineno, frame.colno) abs_path = urljoin(sourcemap_url, state.src) logger.debug('Mapping compressed source %r to mapping in %r', frame.abs_path, abs_path) source = cache.get(abs_path) if not source: frame.data = { 'sourcemap': sourcemap_url, } errors = cache.get_errors(abs_path) if errors: frame.errors.extend(errors) else: frame.errors.append(ERR_MISSING_SOURCE.format( filename=abs_path.encode('utf-8'), )) # Store original data in annotation frame.data = { 'orig_lineno': frame.lineno, 'orig_colno': frame.colno, 'orig_function': frame.function, 'orig_abs_path': frame.abs_path, 'orig_filename': frame.filename, 'sourcemap': sourcemap_url, } # SourceMap's return zero-indexed lineno's frame.lineno = state.src_line + 1 frame.colno = state.src_col # The offending function is always the previous function in the stack # Honestly, no idea what the bottom most frame is, so we're ignoring that atm if last_state: frame.function = last_state.name or frame.function else: frame.function = state.name or frame.function frame.abs_path = abs_path frame.filename = state.src frame.module = generate_module(state.src) elif sourcemap_url: frame.data = { 'sourcemap': sourcemap_url, } # TODO: theoretically a minified source could point to another mapped, minified source frame.pre_context, frame.context_line, frame.post_context = get_source_context( source=source, lineno=frame.lineno, colno=frame.colno or 0)
def expand_frames(self, frames, release): last_state = None state = None cache = self.cache sourcemaps = self.sourcemaps all_errors = [] sourcemap_applied = False for frame in frames: errors = cache.get_errors(frame.abs_path) if errors: all_errors.extend(errors) # can't fetch source if there's no filename present if not frame.abs_path: continue source = self.get_source(frame.abs_path, release) if source is None: logger.debug('No source found for %s', frame.abs_path) continue sourcemap_url, sourcemap_idx = sourcemaps.get_link(frame.abs_path) if sourcemap_idx and frame.colno is None: all_errors.append({ 'type': EventError.JS_NO_COLUMN, 'url': expose_url(frame.abs_path), }) elif sourcemap_idx: last_state = state if is_data_uri(sourcemap_url): sourcemap_label = frame.abs_path else: sourcemap_label = sourcemap_url sourcemap_label = expose_url(sourcemap_label) try: state = find_source(sourcemap_idx, frame.lineno, frame.colno) except Exception: state = None all_errors.append({ 'type': EventError.JS_INVALID_SOURCEMAP_LOCATION, 'column': frame.colno, 'row': frame.lineno, 'source': frame.abs_path, 'sourcemap': sourcemap_label, }) # Store original data in annotation # HACK(dcramer): we stuff things into raw which gets popped off # later when adding the raw_stacktrace attribute. raw_frame = frame.to_json() frame.data = { 'raw': raw_frame, 'sourcemap': sourcemap_label, } sourcemap_applied = True if state is not None: abs_path = urljoin(sourcemap_url, state.src) logger.debug('Mapping compressed source %r to mapping in %r', frame.abs_path, abs_path) source = self.get_source(abs_path, release) if not source: errors = cache.get_errors(abs_path) if errors: all_errors.extend(errors) else: all_errors.append({ 'type': EventError.JS_MISSING_SOURCE, 'url': expose_url(abs_path), }) if state is not None: # SourceMap's return zero-indexed lineno's frame.lineno = state.src_line + 1 frame.colno = state.src_col # The offending function is always the previous function in the stack # Honestly, no idea what the bottom most frame is, so we're ignoring that atm if last_state: frame.function = last_state.name or frame.function else: frame.function = state.name or frame.function filename = state.src # special case webpack support # abs_path will always be the full path with webpack:/// prefix. # filename will be relative to that if abs_path.startswith('webpack:'): filename = abs_path # webpack seems to use ~ to imply "relative to resolver root" # which is generally seen for third party deps # (i.e. node_modules) if '/~/' in filename: filename = '~/' + abs_path.split('/~/', 1)[-1] else: filename = filename.split('webpack:///', 1)[-1] # As noted above, '~/' means they're coming from node_modules, # so these are not app dependencies if filename.startswith('~/'): frame.in_app = False # And conversely, local dependencies start with './' elif filename.startswith('./'): frame.in_app = True # Update 'raw' copy to have same in_app status raw_frame['in_app'] = frame.in_app # We want to explicitly generate a webpack module name frame.module = generate_module(filename) frame.abs_path = abs_path frame.filename = filename if not frame.module and abs_path.startswith(('http:', 'https:', 'webpack:')): frame.module = generate_module(abs_path) elif sourcemap_url: frame.data = { 'sourcemap': expose_url(sourcemap_url), } # TODO: theoretically a minified source could point to another mapped, minified source frame.pre_context, frame.context_line, frame.post_context = get_source_context( source=source, lineno=frame.lineno, colno=frame.colno or 0) if not frame.context_line and source: all_errors.append({ 'type': EventError.JS_INVALID_SOURCEMAP_LOCATION, 'column': frame.colno, 'row': frame.lineno, 'source': frame.abs_path, }) return all_errors, sourcemap_applied
def process_frame(self, processable_frame, processing_task): frame = processable_frame.frame last_token = None token = None cache = self.cache sourcemaps = self.sourcemaps all_errors = [] sourcemap_applied = False # can't fetch source if there's no filename present if not frame.get('abs_path'): return errors = cache.get_errors(frame['abs_path']) if errors: all_errors.extend(errors) # This might fail but that's okay, we try with a different path a # bit later down the road. source = self.get_source(frame['abs_path']) in_app = None new_frame = dict(frame) raw_frame = dict(frame) sourcemap_url, sourcemap_view = sourcemaps.get_link(frame['abs_path']) if sourcemap_view and frame.get('colno') is None: all_errors.append({ 'type': EventError.JS_NO_COLUMN, 'url': expose_url(frame['abs_path']), }) elif sourcemap_view: last_token = token if is_data_uri(sourcemap_url): sourcemap_label = frame['abs_path'] else: sourcemap_label = sourcemap_url sourcemap_label = expose_url(sourcemap_label) try: # Errors are 1-indexed in the frames, so we need to -1 to get # zero-indexed value from tokens. assert frame['lineno'] > 0, "line numbers are 1-indexed" token = sourcemap_view.lookup_token( frame['lineno'] - 1, frame['colno']) except Exception: token = None all_errors.append({ 'type': EventError.JS_INVALID_SOURCEMAP_LOCATION, 'column': frame.get('colno'), 'row': frame.get('lineno'), 'source': frame['abs_path'], 'sourcemap': sourcemap_label, }) # Store original data in annotation new_frame['data'] = dict(frame.get('data') or {}, sourcemap=sourcemap_label) sourcemap_applied = True if token is not None: abs_path = urljoin(sourcemap_url, token.src) logger.debug('Mapping compressed source %r to mapping in %r', frame['abs_path'], abs_path) source = self.get_source(abs_path) if not source: errors = cache.get_errors(abs_path) if errors: all_errors.extend(errors) else: all_errors.append({ 'type': EventError.JS_MISSING_SOURCE, 'url': expose_url(abs_path), }) if token is not None: # Token's return zero-indexed lineno's new_frame['lineno'] = token.src_line + 1 new_frame['colno'] = token.src_col # The offending function is always the previous function in the stack # Honestly, no idea what the bottom most frame is, so we're ignoring that atm if last_token: new_frame['function'] = last_token.name or frame.get('function') else: new_frame['function'] = token.name or frame.get('function') filename = token.src # special case webpack support # abs_path will always be the full path with webpack:/// prefix. # filename will be relative to that if abs_path.startswith('webpack:'): filename = abs_path # webpack seems to use ~ to imply "relative to resolver root" # which is generally seen for third party deps # (i.e. node_modules) if '/~/' in filename: filename = '~/' + abs_path.split('/~/', 1)[-1] else: filename = filename.split('webpack:///', 1)[-1] # As noted above, '~/' means they're coming from node_modules, # so these are not app dependencies if filename.startswith('~/'): in_app = False # And conversely, local dependencies start with './' elif filename.startswith('./'): in_app = True # We want to explicitly generate a webpack module name new_frame['module'] = generate_module(filename) if abs_path.startswith('app:'): if NODE_MODULES_RE.search(filename): in_app = False else: in_app = True new_frame['abs_path'] = abs_path new_frame['filename'] = filename if not frame.get('module') and abs_path.startswith( ('http:', 'https:', 'webpack:', 'app:')): new_frame['module'] = generate_module(abs_path) elif sourcemap_url: new_frame['data'] = dict(new_frame.get('data') or {}, sourcemap=expose_url(sourcemap_url)) # TODO: theoretically a minified source could point to # another mapped, minified source changed_frame = self.expand_frame(new_frame, source=source) if not new_frame.get('context_line') and source: all_errors.append({ 'type': EventError.JS_INVALID_SOURCEMAP_LOCATION, # Column might be missing here 'column': new_frame.get('colno'), # Line might be missing here 'row': new_frame.get('lineno'), 'source': new_frame['abs_path'], }) changed_raw = sourcemap_applied and self.expand_frame(raw_frame) if sourcemap_applied or all_errors or changed_frame or \ changed_raw: if in_app is not None: new_frame['in_app'] = in_app raw_frame['in_app'] = in_app return [new_frame], [raw_frame] if changed_raw else None, all_errors
def process_frame(self, processable_frame, processing_task): frame = processable_frame.frame token = None cache = self.cache sourcemaps = self.sourcemaps all_errors = [] sourcemap_applied = False # can't fetch source if there's no filename present or no line if not frame.get('abs_path') or not frame.get('lineno'): return # can't fetch if this is internal node module as well # therefore we only process user-land frames (starting with /) # or those created by bundle/webpack internals if self.data.get('platform') == 'node' and \ not frame.get('abs_path').startswith(('/', 'app:', 'webpack:')): return errors = cache.get_errors(frame['abs_path']) if errors: all_errors.extend(errors) # This might fail but that's okay, we try with a different path a # bit later down the road. source = self.get_sourceview(frame['abs_path']) in_app = None new_frame = dict(frame) raw_frame = dict(frame) sourcemap_url, sourcemap_view = sourcemaps.get_link(frame['abs_path']) self.sourcemaps_touched.add(sourcemap_url) if sourcemap_view and frame.get('colno') is None: all_errors.append({ 'type': EventError.JS_NO_COLUMN, 'url': http.expose_url(frame['abs_path']), }) elif sourcemap_view: if is_data_uri(sourcemap_url): sourcemap_label = frame['abs_path'] else: sourcemap_label = sourcemap_url sourcemap_label = http.expose_url(sourcemap_label) if frame.get('function'): minified_function_name = frame['function'] minified_source = self.get_sourceview(frame['abs_path']) else: minified_function_name = minified_source = None try: # Errors are 1-indexed in the frames, so we need to -1 to get # zero-indexed value from tokens. assert frame['lineno'] > 0, "line numbers are 1-indexed" token = sourcemap_view.lookup(frame['lineno'] - 1, frame['colno'] - 1, minified_function_name, minified_source) except Exception: token = None all_errors.append({ 'type': EventError.JS_INVALID_SOURCEMAP_LOCATION, 'column': frame.get('colno'), 'row': frame.get('lineno'), 'source': frame['abs_path'], 'sourcemap': sourcemap_label, }) # persist the token so that we can find it later processable_frame.data['token'] = token # Store original data in annotation new_frame['data'] = dict(frame.get('data') or {}, sourcemap=sourcemap_label) sourcemap_applied = True if token is not None: abs_path = urljoin(sourcemap_url, token.src) logger.debug('Mapping compressed source %r to mapping in %r', frame['abs_path'], abs_path) source = self.get_sourceview(abs_path) if source is None: errors = cache.get_errors(abs_path) if errors: all_errors.extend(errors) else: all_errors.append({ 'type': EventError.JS_MISSING_SOURCE, 'url': http.expose_url(abs_path), }) if token is not None: # the tokens are zero indexed, so offset correctly new_frame['lineno'] = token.src_line + 1 new_frame['colno'] = token.src_col + 1 # Try to use the function name we got from symbolic original_function_name = token.function_name # In the ideal case we can use the function name from the # frame and the location to resolve the original name # through the heuristics in our sourcemap library. if original_function_name is None: last_token = None # Find the previous token for function name handling as a # fallback. if processable_frame.previous_frame and \ processable_frame.previous_frame.processor is self: last_token = processable_frame.previous_frame.data.get( 'token') if last_token: original_function_name = last_token.name if original_function_name is not None: new_frame['function'] = original_function_name filename = token.src # special case webpack support # abs_path will always be the full path with webpack:/// prefix. # filename will be relative to that if abs_path.startswith('webpack:'): filename = abs_path # webpack seems to use ~ to imply "relative to resolver root" # which is generally seen for third party deps # (i.e. node_modules) if '/~/' in filename: filename = '~/' + abs_path.split('/~/', 1)[-1] else: filename = filename.split('webpack:///', 1)[-1] # As noted above: # * [js/node] '~/' means they're coming from node_modules, so these are not app dependencies # * [node] sames goes for `./node_modules/`, which is used when bundling node apps # * [node] and webpack, which includes it's own code to bootstrap all modules and its internals # eg. webpack:///webpack/bootstrap, webpack:///external if filename.startswith('~/') or \ filename.startswith('./node_modules/') or \ not filename.startswith('./'): in_app = False # And conversely, local dependencies start with './' elif filename.startswith('./'): in_app = True # We want to explicitly generate a webpack module name new_frame['module'] = generate_module(filename) if abs_path.startswith('app:'): if filename and NODE_MODULES_RE.search(filename): in_app = False else: in_app = True new_frame['abs_path'] = abs_path new_frame['filename'] = filename if not frame.get('module') and abs_path.startswith( ('http:', 'https:', 'webpack:', 'app:')): new_frame['module'] = generate_module(abs_path) elif sourcemap_url: new_frame['data'] = dict(new_frame.get('data') or {}, sourcemap=http.expose_url(sourcemap_url)) # TODO: theoretically a minified source could point to # another mapped, minified source changed_frame = self.expand_frame(new_frame, source=source) # If we did not manage to match but we do have a line or column # we want to report an error here. if not new_frame.get('context_line') \ and source and \ new_frame.get('colno') is not None: all_errors.append({ 'type': EventError.JS_INVALID_SOURCEMAP_LOCATION, 'column': new_frame['colno'], 'row': new_frame['lineno'], 'source': new_frame['abs_path'], }) changed_raw = sourcemap_applied and self.expand_frame(raw_frame) if sourcemap_applied or all_errors or changed_frame or \ changed_raw: if in_app is not None: new_frame['in_app'] = in_app raw_frame['in_app'] = in_app return [new_frame ], [raw_frame] if changed_raw else None, all_errors