| // Copyright (c) 2012, 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. |
| |
| // @dart = 2.10 |
| |
| library dart2js.code_output; |
| |
| import '../../compiler.dart'; |
| import 'code_output_listener.dart'; |
| export 'code_output_listener.dart'; |
| import 'source_information.dart'; |
| |
| /// Interface for a mapping of target offsets to source locations and for |
| /// tracking inlining frame data. |
| /// |
| /// Source-location mapping is used to build standard source-maps files. |
| /// Inlining frames is used to attach an extension to source-map files to |
| /// improve deobfuscation of production stack traces. |
| abstract class SourceLocations { |
| /// The name identifying this source mapping. |
| String get name; |
| |
| /// Adds a [sourceLocation] at the specified [targetOffset]. |
| void addSourceLocation(int targetOffset, SourceLocation sourcePosition); |
| |
| /// Record an inlining call at the [targetOffset]. |
| /// |
| /// The inlining call-site was made from [pushLocation] and calls |
| /// [inlinedMethodName]. |
| void addPush( |
| int targetOffset, SourceLocation pushPosition, String inlinedMethodName); |
| |
| /// Record a return of an inlining call at the [targetOffset]. |
| /// |
| /// [isEmpty] indicates that this return also makes the inlining stack empty. |
| void addPop(int targetOffset, bool isEmpty); |
| |
| /// Applies [f] to every target offset and associated source location. |
| void forEachSourceLocation( |
| void f(int targetOffset, SourceLocation sourceLocation)); |
| |
| /// Recorded inlining data per target-offset. |
| Map<int, List<FrameEntry>> get frameMarkers; |
| } |
| |
| class _SourceLocationsImpl implements SourceLocations { |
| @override |
| final String name; |
| final AbstractCodeOutput codeOutput; |
| Map<int, List<SourceLocation>> markers = {}; |
| @override |
| Map<int, List<FrameEntry>> frameMarkers = {}; |
| |
| _SourceLocationsImpl(this.name, this.codeOutput); |
| |
| @override |
| void addSourceLocation(int targetOffset, SourceLocation sourceLocation) { |
| assert(targetOffset <= codeOutput.length); |
| List<SourceLocation> sourceLocations = |
| markers.putIfAbsent(targetOffset, () => []); |
| sourceLocations.add(sourceLocation); |
| } |
| |
| @override |
| void addPush(int targetOffset, SourceLocation sourceLocation, |
| String inlinedMethodName) { |
| assert(targetOffset <= codeOutput.length); |
| List<FrameEntry> frames = frameMarkers[targetOffset] ??= []; |
| frames.add(FrameEntry.push(sourceLocation, inlinedMethodName)); |
| } |
| |
| @override |
| void addPop(int targetOffset, bool isEmpty) { |
| assert(targetOffset <= codeOutput.length); |
| List<FrameEntry> frames = frameMarkers[targetOffset] ??= []; |
| frames.add(FrameEntry.pop(isEmpty)); |
| } |
| |
| @override |
| void forEachSourceLocation( |
| void f(int targetOffset, SourceLocation sourceLocation)) { |
| markers.forEach((int targetOffset, List<SourceLocation> sourceLocations) { |
| for (SourceLocation sourceLocation in sourceLocations) { |
| f(targetOffset, sourceLocation); |
| } |
| }); |
| } |
| |
| void _merge(_SourceLocationsImpl other) { |
| assert(name == other.name); |
| int length = codeOutput.length; |
| if (other.markers.length > 0) { |
| other.markers |
| .forEach((int targetOffset, List<SourceLocation> sourceLocations) { |
| (markers[length + targetOffset] ??= []).addAll(sourceLocations); |
| }); |
| } |
| |
| if (other.frameMarkers.length > 0) { |
| other.frameMarkers.forEach((int targetOffset, List<FrameEntry> frames) { |
| (frameMarkers[length + targetOffset] ??= []).addAll(frames); |
| }); |
| } |
| } |
| } |
| |
| abstract class SourceLocationsProvider { |
| /// Creates a [SourceLocations] mapping identified by [name] and associates |
| /// it with this code output. |
| SourceLocations createSourceLocations(String name); |
| |
| /// Returns the source location mappings associated with this code output. |
| Iterable<SourceLocations> get sourceLocations; |
| } |
| |
| abstract class CodeOutput implements SourceLocationsProvider { |
| /// Write [text] to this output. |
| /// |
| /// If the output is closed, a [StateError] is thrown. |
| void add(String text); |
| |
| /// Adds the content of [buffer] to the output and adds its markers to |
| /// [markers]. |
| /// |
| /// If the output is closed, a [StateError] is thrown. |
| void addBuffer(CodeBuffer buffer); |
| |
| /// Returns the number of characters currently written to this output. |
| int get length; |
| |
| /// Returns `true` if this output has been closed. |
| bool get isClosed; |
| |
| /// Closes the output. Further writes will cause a [StateError]. |
| void close(); |
| } |
| |
| abstract class AbstractCodeOutput extends CodeOutput { |
| final List<CodeOutputListener> _listeners; |
| |
| AbstractCodeOutput([this._listeners]); |
| |
| Map<String, _SourceLocationsImpl> sourceLocationsMap = |
| <String, _SourceLocationsImpl>{}; |
| @override |
| bool isClosed = false; |
| |
| void _addInternal(String text); |
| |
| void _add(String text) { |
| _addInternal(text); |
| _listeners?.forEach((listener) => listener.onText(text)); |
| } |
| |
| @override |
| void add(String text) { |
| if (isClosed) { |
| throw StateError("Code output is closed. Trying to write '$text'."); |
| } |
| _add(text); |
| } |
| |
| @override |
| void addBuffer(CodeBuffer other) { |
| other.sourceLocationsMap.forEach((String name, _SourceLocationsImpl other) { |
| createSourceLocations(name)._merge(other); |
| }); |
| if (!other.isClosed) { |
| other.close(); |
| } |
| _add(other.getText()); |
| } |
| |
| @override |
| void close() { |
| if (isClosed) { |
| throw StateError("Code output is already closed."); |
| } |
| isClosed = true; |
| _listeners?.forEach((listener) => listener.onDone(length)); |
| } |
| |
| @override |
| Iterable<SourceLocations> get sourceLocations => sourceLocationsMap.values; |
| |
| @override |
| _SourceLocationsImpl createSourceLocations(String name) { |
| return sourceLocationsMap[name] ??= _SourceLocationsImpl(name, this); |
| } |
| } |
| |
| abstract class BufferedCodeOutput { |
| String getText(); |
| } |
| |
| /// [CodeOutput] using a [StringBuffer] as backend. |
| class CodeBuffer extends AbstractCodeOutput implements BufferedCodeOutput { |
| StringBuffer buffer = StringBuffer(); |
| |
| CodeBuffer([List<CodeOutputListener> listeners]) : super(listeners); |
| |
| @override |
| void _addInternal(String text) { |
| buffer.write(text); |
| } |
| |
| @override |
| int get length => buffer.length; |
| |
| @override |
| String getText() { |
| return buffer.toString(); |
| } |
| |
| @override |
| String toString() { |
| throw "Don't use CodeBuffer.toString() since it drops sourcemap data."; |
| } |
| } |
| |
| /// [CodeOutput] using a [CompilationOutput] as backend. |
| class StreamCodeOutput extends AbstractCodeOutput { |
| @override |
| int length = 0; |
| final OutputSink output; |
| |
| StreamCodeOutput(this.output, [List<CodeOutputListener> listeners]) |
| : super(listeners); |
| |
| @override |
| void _addInternal(String text) { |
| output.add(text); |
| length += text.length; |
| } |
| |
| @override |
| void close() { |
| output.close(); |
| super.close(); |
| } |
| } |