Files
sdk/pkg/compiler/lib/compiler_api.dart
T
Sigmund Cherem 710c0b3695 [dartj2s] (tech-debt) better error locations for sources from dill files.
This change addresses a technical debt issue in dart2js, where
errors sometimes get reported as a binary offset location, instead
of a line/column position with contextual data from the source file.

The reason for this behavior is that dartj2s reports binary offsets
when it can't recover line/column data and show the contents of the
file. This typically happens if the file cannot be read directly
from disk.

The fix consits of adding an API to record the contents of files
that are not read directly by the compiler. We then use this API to
provide all sources collected by the CFE when parsing components
from .dill files.

Note: this CL also deletes the `autoread` feature in
source-file-provider. That feature was added for the same purpose.
The way it worked was that instead of registering source content, it
tried to read the sources from disk on-demand as errors got
reported. This doesn't always work for three reasons:

* First, we often use custom schemes (like the multi-root scheme) in
  dill files to make the .dill deterministic in distributed build
  systems, the autoload feature had no understanding of how to
  translate those custom URIs to file URIs (that translation is only
  define at the time the .dill is being built).

* Second, sometimes the files are simply not available, for example,
  in hermetic build systems like bazel we have no access to those
  files.

* Third, when files were found, there was no guarantee that the
  contents were consistent. That is, the current version of the file
  on disk could have been modified and have different contents than
  those used when the .dill file was built.

I tested this manually by adding a crash in SSA and observing the
location in multiple scenarios, including running from source,
running from a .dill with file URIs, running from a .dill with
multi-root URIs, crashing in SDK locations in regular files and
patch files.

Change-Id: Ief09be577f4c9c4b345b4e2641918cafbe93c3fc
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/251700
Reviewed-by: Mayank Patke <fishythefish@google.com>
Commit-Queue: Sigmund Cherem <sigmund@google.com>
2022-07-19 17:10:06 +00:00

225 lines
8.0 KiB
Dart

// 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 compiler;
import 'dart:async';
import 'package:front_end/src/api_unstable/dart2js.dart' as fe;
/// Kind of diagnostics that the compiler can report.
class Diagnostic {
/// An error as identified by the "Dart Programming Language
/// Specification" [https://dart.dev/guides/language/spec].
///
/// Note: the compiler may still produce an executable result after
/// reporting a compilation error. The specification says:
///
/// "A compile-time error must be reported by a Dart compiler before
/// the erroneous code is executed." and "If a compile-time error
/// occurs within the code of a running isolate A, A is immediately
/// suspended."
///
/// This means that the compiler can generate code that when executed
/// terminates execution.
static const Diagnostic ERROR = const Diagnostic(1, 'error');
/// A warning as identified by the "Dart Programming Language
/// Specification" [https://dart.dev/guides/language/spec].
static const Diagnostic WARNING = const Diagnostic(2, 'warning');
/// Any other warning that is not covered by [WARNING].
static const Diagnostic HINT = const Diagnostic(4, 'hint');
/// Additional information about the preceding non-info diagnostic from the
/// compiler.
///
/// For example, consider a duplicated definition. The compiler first emits a
/// message about the duplicated definition, then emits an info message about
/// the location of the existing definition.
static const Diagnostic INFO = const Diagnostic(8, 'info');
/// Informational messages that shouldn't be printed unless
/// explicitly requested by the user of a compiler.
static const Diagnostic VERBOSE_INFO = const Diagnostic(16, 'verbose info');
/// An internal error in the compiler.
static const Diagnostic CRASH = const Diagnostic(32, 'crash');
/// An [int] representation of this kind. The ordinals are designed
/// to be used as bitsets.
final int ordinal;
/// The name of this kind.
final String name;
/// This constructor is not private to support user-defined
/// diagnostic kinds.
const Diagnostic(this.ordinal, this.name);
@override
String toString() => name;
}
// Unless explicitly allowed, passing `null` for any argument to the
// methods of library will result in an Error being thrown.
/// Input kinds used by [CompilerInput.readFromUri].
enum InputKind {
/// Data is read as UTF8 either as a [String] or a zero-terminated
/// `List<int>`.
UTF8,
/// Data is read as bytes in a `List<int>`.
binary,
}
/// Interface for data read through [CompilerInput.readFromUri].
abstract class Input<T> {
/// The URI from which data was read.
Uri get uri;
/// The format of the read [data].
InputKind get inputKind;
/// The raw data read from [uri].
T get data;
/// Release any resources held by the input. After releasing, a call to `get
/// data` will fail, and previously returned data may be invalid.
void release();
}
/// Interface for providing the compiler with input. That is, Dart source files,
/// package config files, etc.
abstract class CompilerInput {
/// Returns a future that completes to the source corresponding to [uri].
/// If an exception occurs, the future completes with this exception.
///
/// If [inputKind] is `InputKind.UTF8` the source can be represented either as
/// a zero-terminated `List<int>` of UTF-8 bytes or as a [String]. If
/// [inputKind] is `InputKind.binary` the source is a read a `List<int>`.
///
/// The following text is non-normative:
///
/// It is recommended to return a UTF-8 encoded list of bytes because the
/// scanner is more efficient in this case. In either case, the data structure
/// is expected to hold a zero element at the last position. If this is not
/// the case, the entire data structure is copied before scanning.
Future<Input> readFromUri(Uri uri, {InputKind inputKind = InputKind.UTF8});
/// Register that [uri] should be an `InputKind.UTF8` input with the
/// given [source] as its zero-terminated list of contents.
///
/// If [uri] was read prior to this call, this registration has no effect,
/// otherwise it is expected that a future [readFromUri] will return the
/// contents provided here.
///
/// The main purpose of this API is to assist in error reporting when
/// compiling from kernel binary files. Binary files embed the contents
/// of source files that may not be available on disk. By using these
/// registered contents, dart2js will be able to provide accurate line/column
/// information on an error.
void registerUtf8ContentsForDiagnostics(Uri uri, List<int> source);
}
/// Output types used in `CompilerOutput.createOutputSink`.
enum OutputType {
/// The main JavaScript output.
js,
/// A deferred JavaScript output part.
jsPart,
/// A source map for a JavaScript output.
sourceMap,
/// Dump info output.
dumpInfo,
/// Deferred map output.
deferredMap,
/// Unused libraries output.
dumpUnusedLibraries,
/// Implementation specific output used for debugging the compiler.
debug,
}
/// Sink interface used for generating output from the compiler.
abstract class OutputSink {
/// Adds [text] to the sink.
void add(String text);
/// Closes the sink.
void close();
}
/// Sink interface used for generating binary data from the compiler.
abstract class BinaryOutputSink {
/// Writes indices [start] to [end] of [buffer] to the sink.
void write(List<int> buffer, [int start = 0, int? end]);
/// Closes the sink.
void close();
}
/// Interface for producing output from the compiler. That is, JavaScript target
/// files, source map files, dump info files, etc.
abstract class CompilerOutput {
/// Returns an [OutputSink] that will serve as compiler output for the given
/// component.
///
/// Components are identified by [name], [extension], and [type]. By
/// convention, the empty string `""` will represent the main output of the
/// provided [type]. [name] and [extension] are otherwise suggestive.
// TODO(johnniwinther): Replace [name] and [extension] with something like
// [id] and [uri].
OutputSink createOutputSink(String name, String extension, OutputType type);
/// Returns an [BinaryOutputSink] that will serve as compiler output for the
/// given URI.
BinaryOutputSink createBinarySink(Uri uri);
}
/// Interface for receiving diagnostic message from the compiler. That is,
/// errors, warnings, hints, etc.
abstract class CompilerDiagnostics {
/// Invoked by the compiler to report diagnostics. If [uri] is `null`, so are
/// [begin] and [end]. No other arguments may be `null`. If [uri] is not
/// `null`, neither are [begin] and [end]. [uri] indicates the compilation
/// unit from where the diagnostic originates. [begin] and [end] are
/// zero-based character offsets from the beginning of the compilation unit.
/// [message] is the diagnostic message, and [kind] indicates indicates what
/// kind of diagnostic it is.
///
/// Experimental: [code] gives access to an id for the messages. Currently it
/// is the [Message] used to create the diagnostic, if available, from which
/// the [MessageKind] is accessible.
void report(
var code, Uri? uri, int? begin, int? end, String text, Diagnostic kind);
}
/// Information resulting from the compilation.
class CompilationResult {
/// `true` if the compilation succeeded, that is, compilation didn't fail due
/// to compile-time errors and/or internal errors.
final bool isSuccess;
/// The compiler object used for the compilation.
///
/// Note: The type of [compiler] is implementation dependent and may vary.
/// Use only for debugging and testing.
final compiler;
/// Shared state between compilations.
///
/// This is used to speed up batch mode.
final fe.InitializedCompilerState? kernelInitializedCompilerState;
CompilationResult(this.compiler,
{this.isSuccess = true, this.kernelInitializedCompilerState});
}