| // 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. | |
| library; | |
| import '../../compiler_api.dart' as api show OutputSink; | |
| import 'code_output_listener.dart'; | |
| import 'source_information.dart'; | |
| export 'code_output_listener.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]. | |
| // TODO(48820): We might have a [pushPosition]. | |
| 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 Function(int targetOffset, SourceLocation sourceLocation) f, | |
| ); | |
| /// Applies [f] to every target offset and associated frame entry. This is | |
| /// mostly used to track inlining data. | |
| void forEachFrameMarker( | |
| void Function(int targetOffset, FrameEntry frameEntry) f, | |
| ); | |
| void close(); | |
| } | |
| class SourceLocationsImpl implements SourceLocations { | |
| @override | |
| final String name; | |
| final AbstractCodeOutput codeOutput; | |
| Map<int, List<SourceLocation>> markers = {}; | |
| Map<int, List<FrameEntry>> frameMarkers = {}; | |
| bool _closed = false; | |
| SourceLocationsImpl(this.name, this.codeOutput); | |
| @override | |
| void addSourceLocation(int targetOffset, SourceLocation sourceLocation) { | |
| assert(targetOffset <= codeOutput.length); | |
| if (_closed) throw UnsupportedError('SourceLocations already closed.'); | |
| List<SourceLocation> sourceLocations = markers.putIfAbsent( | |
| targetOffset, | |
| () => [], | |
| ); | |
| sourceLocations.add(sourceLocation); | |
| } | |
| @override | |
| void addPush( | |
| int targetOffset, | |
| SourceLocation? sourceLocation, | |
| String inlinedMethodName, | |
| ) { | |
| assert(targetOffset <= codeOutput.length); | |
| if (_closed) throw UnsupportedError('SourceLocations already closed.'); | |
| List<FrameEntry> frames = frameMarkers[targetOffset] ??= []; | |
| frames.add(FrameEntry.push(sourceLocation, inlinedMethodName)); | |
| } | |
| @override | |
| void addPop(int targetOffset, bool isEmpty) { | |
| assert(targetOffset <= codeOutput.length); | |
| if (_closed) throw UnsupportedError('SourceLocations already closed.'); | |
| List<FrameEntry> frames = frameMarkers[targetOffset] ??= []; | |
| frames.add(FrameEntry.pop(isEmpty)); | |
| } | |
| @override | |
| void forEachSourceLocation( | |
| void Function(int targetOffset, SourceLocation sourceLocation) f, | |
| ) { | |
| if (_closed) throw UnsupportedError('SourceLocations already closed.'); | |
| markers.forEach((int targetOffset, List<SourceLocation> sourceLocations) { | |
| for (SourceLocation sourceLocation in sourceLocations) { | |
| f(targetOffset, sourceLocation); | |
| } | |
| }); | |
| } | |
| @override | |
| void forEachFrameMarker( | |
| void Function(int targetOffset, FrameEntry sourceLocation) f, | |
| ) { | |
| if (_closed) throw UnsupportedError('SourceLocations already closed.'); | |
| frameMarkers.forEach((int targetOffset, List<FrameEntry> frameEntries) { | |
| for (FrameEntry entry in frameEntries) { | |
| f(targetOffset, entry); | |
| } | |
| }); | |
| } | |
| @override | |
| void close() { | |
| if (_closed) throw UnsupportedError('SourceLocations already closed.'); | |
| _closed = true; | |
| frameMarkers.clear(); | |
| markers.clear(); | |
| } | |
| void _merge(SourceLocationsImpl other) { | |
| assert(name == other.name); | |
| if (_closed) throw UnsupportedError('SourceLocations already closed.'); | |
| int length = codeOutput.length; | |
| if (other.markers.isNotEmpty) { | |
| other.markers.forEach(( | |
| int targetOffset, | |
| List<SourceLocation> sourceLocations, | |
| ) { | |
| (markers[length + targetOffset] ??= []).addAll(sourceLocations); | |
| }); | |
| } | |
| if (other.frameMarkers.isNotEmpty) { | |
| 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 = {}; | |
| @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(); | |
| } | |
| class NoopCodeOutput extends AbstractCodeOutput { | |
| @override | |
| void _addInternal(String text) {} | |
| @override | |
| int get length => 0; | |
| } | |
| /// [CodeOutput] using a [StringBuffer] as backend. | |
| class CodeBuffer extends AbstractCodeOutput implements BufferedCodeOutput { | |
| StringBuffer buffer = StringBuffer(); | |
| CodeBuffer([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 api.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(); | |
| } | |
| } |