blob: 869cfa77c342fbf0fb1cbaaab2578f72ae1481a5 [file] [log] [blame]
// 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_api.dart' as api show OutputSink;
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 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();
}
}