| // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| /// Logic to expand and deobuscate stack traces. |
| |
| import 'package:stack_trace/stack_trace.dart'; |
| import 'package:source_span/source_span.dart'; |
| import 'package:source_maps/src/utils.dart'; |
| import 'sourcemap_helper.dart'; |
| import 'dart2js_mapping.dart'; |
| import 'util.dart'; |
| |
| /// Provides the result of deobfuscating a stack trace. |
| class StackDeobfuscationResult { |
| /// Representation of the obfuscated stack trace. |
| final Trace original; |
| |
| /// Representation of the deobfsucated stack trace. |
| final Trace deobfuscated; |
| |
| /// Details about how one original frame maps to deobfuscated frames. A single |
| /// frame might map to many frames (in the case of inlining), or to a null |
| /// value (when we were unabled to deobfuscate it). |
| final Map<Frame, List<Frame>> frameMap; |
| |
| StackDeobfuscationResult(this.original, this.deobfuscated, this.frameMap); |
| } |
| |
| /// Parse [stackTrace] and deobfuscate it using source-map data available from |
| /// [provider]. |
| StackDeobfuscationResult deobfuscateStack( |
| String stackTrace, FileProvider provider) { |
| var trace = Trace.parse(stackTrace.trim()); |
| var deobfuscatedFrames = <Frame>[]; |
| var frameMap = <Frame, List<Frame>>{}; |
| for (var frame in trace.frames) { |
| // If there's no line information, there's no way to translate this frame. |
| // We could return it as-is, but these lines are usually not useful anyways. |
| if (frame.line == null) { |
| continue; |
| } |
| |
| // If there's no column, try using the first column of the line. |
| var column = frame.column ?? 0; |
| |
| Dart2jsMapping mapping = provider.mappingFor(frame.uri); |
| if (mapping == null) continue; |
| |
| // Subtract 1 because stack traces use 1-indexed lines and columns and |
| // source maps uses 0-indexed. |
| SourceSpan span = mapping.sourceMap |
| .spanFor(frame.line - 1, column - 1, uri: frame.uri?.toString()); |
| |
| // If we can't find a source span, ignore the frame. It's probably something |
| // internal that the user doesn't care about. |
| if (span == null) continue; |
| |
| List<Frame> mappedFrames = frameMap[frame] = []; |
| |
| SourceFile jsFile = provider.fileFor(frame.uri); |
| int offset = jsFile.getOffset(frame.line - 1, column - 1); |
| String nameOf(id) => |
| _normalizeName(id >= 0 ? mapping.sourceMap.names[id] : null); |
| |
| Uri fileName = span.sourceUrl; |
| int targetLine = span.start.line + 1; |
| int targetColumn = span.start.column + 1; |
| |
| // Expand inlining data. When present, the fileName, line and column above |
| // correspond to the deepest inlined function, as we expand each frame we |
| // consume the location information, and retrieve the location information |
| // of the caller frame until we reach the actual function that dart2js |
| // inlined all the code into. |
| Map<int, List<FrameEntry>> frames = mapping.frames; |
| List<int> index = mapping.frameIndex; |
| int key = binarySearch(index, (i) => i > offset) - 1; |
| int depth = 0; |
| outer: |
| while (key >= 0) { |
| for (var frame in frames[index[key]].reversed) { |
| if (frame.isEmpty) break outer; |
| if (frame.isPush) { |
| if (depth <= 0) { |
| mappedFrames.add(new Frame(fileName, targetLine, targetColumn, |
| _normalizeName(frame.inlinedMethodName) + "(inlined)")); |
| fileName = Uri.parse(frame.callUri); |
| targetLine = (frame.callLine ?? 0) + 1; |
| targetColumn = (frame.callColumn ?? 0) + 1; |
| } else { |
| depth--; |
| } |
| } |
| if (frame.isPop) { |
| depth++; |
| } |
| } |
| key--; |
| } |
| |
| var functionEntry = findEnclosingFunction(provider, frame.uri, offset); |
| String methodName = nameOf(functionEntry?.sourceNameId ?? -1); |
| mappedFrames.add(new Frame(fileName, targetLine, targetColumn, methodName)); |
| deobfuscatedFrames.addAll(mappedFrames); |
| } |
| return new StackDeobfuscationResult( |
| trace, new Trace(deobfuscatedFrames), frameMap); |
| } |
| |
| /// Ensure we don't use spaces in method names. At this time, they are only |
| /// introduced by `<anonymous function>`. |
| _normalizeName(String methodName) => |
| methodName?.replaceAll("<anonymous function>", "<anonymous>") ?? |
| '<unknown>'; |