|  | // 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>'; |