| // Copyright (c) 2015, 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. |
| |
| import 'dart:async'; |
| |
| import 'package:stack_trace/stack_trace.dart'; |
| |
| import 'class.dart'; |
| import 'frame.dart'; |
| import 'function.dart'; |
| import 'object.dart'; |
| import 'script.dart'; |
| |
| /// A [Map] between whitespace characters and their escape sequences. |
| const _escapeMap = const { |
| '\n': r'\n', |
| '\r': r'\r', |
| '\f': r'\f', |
| '\b': r'\b', |
| '\t': r'\t', |
| '\v': r'\v', |
| '\x7F': r'\x7F', // delete |
| }; |
| |
| /// A regular expression matching the Dart VM stack frame method name for an |
| /// asynchronous body. |
| final _asyncBody = new RegExp(r'<(<anonymous closure>|[^>]+)_async_body>'); |
| |
| /// Transforms [stream] with a [StreamTransformer] that transforms data events |
| /// using [handleData]. |
| Stream<T> transform<S, T>( |
| Stream<S> stream, void handleData(S data, EventSink<T> sink)) => |
| stream |
| .transform(new StreamTransformer.fromHandlers(handleData: handleData)); |
| |
| /// Loads a `stack_trace` [Frame] for [frame]. |
| /// |
| /// If [scripts] is passed, it should be a map of [VMScript]s that have already |
| /// been loaded, indexed by [name]. This function modifys the map so that it can |
| /// be passed in to future invocations to avoid unnecessary loads. |
| Future<Frame> frameToFrame(VMFrame frame, |
| [Map<String, VMScript> scripts]) async { |
| var scopes = []; |
| VMObjectRef scope = frame.function; |
| while (scope is VMFunctionRef) { |
| var function = scope as VMFunctionRef; |
| scopes.add(function.name.replaceAll(_asyncBody, "<fn>")); |
| scope = function.owner; |
| } |
| if (scope is VMClassRef) scopes.add(scope.name); |
| var member = scopes.reversed.join("."); |
| |
| var uri = frame.location.script.uri; |
| var script = scripts == null ? null : scripts[uri.toString()]; |
| if (script == null) { |
| script = await frame.location.script.load(); |
| |
| // The special "evaluate" scheme is used for evaluating code with the VM |
| // service. Different scripts can have the same "evalute" scheme, so we |
| // don't record them. |
| if (scripts != null && uri.scheme != 'evaluate') |
| scripts[uri.toString()] = script; |
| } |
| var location = await script.sourceLocation(frame.location.token); |
| |
| return new Frame(script.uri, location.line, location.column, member); |
| } |
| |
| /// A [RegExp] that matches whitespace characters that should be escaped. |
| final _escapeRegExp = new RegExp( |
| "[\\x00-\\x07\\x0E-\\x1F${_escapeMap.keys.map(_getHexLiteral).join()}]"); |
| |
| /// Returns [str] with all whitespace characters represented as their escape |
| /// sequences. |
| /// |
| /// Backslash characters are escaped as `\\` |
| String escapeString(String str) { |
| str = str.replaceAll('\\', r'\\'); |
| return str.replaceAllMapped(_escapeRegExp, (match) { |
| var mapped = _escapeMap[match[0]]; |
| if (mapped != null) return mapped; |
| return _getHexLiteral(match[0]); |
| }); |
| } |
| |
| /// Given single-character string, return the hex-escaped equivalent. |
| String _getHexLiteral(String input) { |
| int rune = input.runes.single; |
| return r'\x' + rune.toRadixString(16).toUpperCase().padLeft(2, '0'); |
| } |