Files
sdk/pkg/dev_compiler/test/expression_compiler/test_compiler.dart
T
Sigmund Cherem 9dad32ce41 [ddc] Library level expression evaluation for the Dart SDK.
Today, support for expression evaluation in DDC uses a lot of
information.  To properly support compiling expressions in arbitrary
frames, we consume source-maps, module metadata, and full kernel
components at various stages of the process.  This data is already
plumbed and available for non-sdk modules in our debugging systems
(frontend-server, webdev, g3), however it is not available the Dart
SDK module itself.  Because of that, the expression compiler today
rejects all expression evaluation requests when the target is a Dart
SDK library.

Until the fix the availability of this data everywhere, we believe
we can support a limited form of expression evaluation: only
evaluation of libary level expressions. Basically, expressions
where scope data is not necessary and kernel outlines are sufficient to
compile the expression.

Here we introduce logic to recognize the first offset location of a
library, which is a clear indicator that no scope data is needed and
a library level expression evaluation is being requested.

This change is an attempt to help resolve
https://github.com/flutter/devtools/issues/7766.  Note: just like
changes were needed here in the worker and test-compiler files, it
is possible that some change may be necessary in dwds and other
parts of the pipeline.  Additional validation will be needed after
this lands.

Change-Id: Iff081a24ecb05092407279a0a7ed3d38c13cf41d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/367981
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Commit-Queue: Sigmund Cherem <sigmund@google.com>
2024-05-24 20:08:37 +00:00

167 lines
6.0 KiB
Dart

// Copyright (c) 2023, 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 'dart:convert';
import 'dart:io' show File;
import 'package:dev_compiler/src/compiler/shared_command.dart'
show SharedCompilerOptions;
import 'package:dev_compiler/src/kernel/command.dart';
import 'package:dev_compiler/src/kernel/compiler.dart' show ProgramCompiler;
import 'package:dev_compiler/src/kernel/expression_compiler.dart'
show ExpressionCompiler;
import 'package:dev_compiler/src/kernel/module_metadata.dart';
import 'package:front_end/src/api_unstable/ddc.dart' as fe;
import 'package:kernel/ast.dart' show Component, Library;
import 'package:path/path.dart' as p;
import 'package:source_maps/source_maps.dart' as source_maps;
import '../shared_test_options.dart';
class TestCompilationResult {
final String? result;
final bool isSuccess;
TestCompilationResult(this.result, this.isSuccess);
}
class TestExpressionCompiler {
final SetupCompilerOptions setup;
final Component component;
final ExpressionCompiler compiler;
final ModuleMetadata? metadata;
final source_maps.SingleMapping sourceMap;
TestExpressionCompiler._(
this.setup, this.component, this.compiler, this.metadata, this.sourceMap);
static Future<TestExpressionCompiler> init(SetupCompilerOptions setup,
{required Uri input,
required Uri output,
Uri? packages,
Map<String, bool> experiments = const {}}) async {
setup.diagnosticMessages.clear();
setup.errors.clear();
// Initialize the incremental compiler and module component.
// TODO: extend this for multi-module compilations by storing separate
// compilers/components/names per module.
setup.options.packagesFileUri = packages;
setup.options.explicitExperimentalFlags.addAll(fe.parseExperimentalFlags(
experiments,
onError: (message) => throw Exception(message)));
var frontend = DevelopmentIncrementalCompiler(setup.options, input);
var compilerResult = await frontend.computeDelta();
var component = compilerResult.component;
component.computeCanonicalNames();
// Initialize DDC.
var moduleName = p.basenameWithoutExtension(output.toFilePath());
var classHierarchy = compilerResult.classHierarchy!;
var compilerOptions = SharedCompilerOptions(
replCompile: true,
moduleName: moduleName,
experiments: experiments,
soundNullSafety: setup.soundNullSafety,
emitDebugMetadata: true,
canaryFeatures: setup.canaryFeatures,
enableAsserts: setup.enableAsserts,
);
var coreTypes = compilerResult.coreTypes;
final importToSummary = Map<Library, Component>.identity();
final summaryToModule = Map<Component, String>.identity();
for (var lib in component.libraries) {
importToSummary[lib] = component;
}
summaryToModule[component] = moduleName;
var kernel2jsCompiler = ProgramCompiler(component, classHierarchy,
compilerOptions, importToSummary, summaryToModule,
coreTypes: coreTypes);
var module = kernel2jsCompiler.emitModule(component);
// Perform a full compile, writing the compiled JS + sourcemap.
var code = jsProgramToCode(
module,
setup.moduleFormat,
inlineSourceMap: compilerOptions.inlineSourceMap,
buildSourceMap: compilerOptions.sourceMap,
emitDebugMetadata: compilerOptions.emitDebugMetadata,
emitDebugSymbols: compilerOptions.emitDebugSymbols,
jsUrl: '$output',
mapUrl: '$output.map',
compiler: kernel2jsCompiler,
component: component,
);
var codeBytes = utf8.encode(code.code);
var sourceMapBytes = utf8.encode(json.encode(code.sourceMap));
File(output.toFilePath()).writeAsBytesSync(codeBytes);
File('${output.toFilePath()}.map').writeAsBytesSync(sourceMapBytes);
// Save the expression compiler for future compilation.
var compiler = ExpressionCompiler(
setup.options,
setup.moduleFormat,
setup.errors,
frontend,
kernel2jsCompiler,
component,
);
if (setup.errors.isNotEmpty) {
throw Exception('Compilation failed with: ${setup.errors}');
}
setup.diagnosticMessages.clear();
setup.errors.clear();
var sourceMap = source_maps.SingleMapping.fromJson(
code.sourceMap!.cast<String, dynamic>());
return TestExpressionCompiler._(
setup, component, compiler, code.metadata, sourceMap);
}
// Line and column are 1-based.
Future<TestCompilationResult> compileExpression(
{required Uri libraryUri,
Uri? scriptUri,
required int line,
required int column,
required Map<String, String> scope,
required String expression}) async {
// clear previous errors
setup.errors.clear();
// At this time, no metadata is stored for the Dart SDK. This is one of the
// reasons debugging and expression evaluation support is so limited within
// the SDK. When requesting evaluation of library level expressions, we can
// ignore the lack of metadata and simply use the URI directly.
String importUri;
if (libraryUri.isScheme('dart')) {
importUri = libraryUri.toString();
} else {
var libraryMetadata = metadataForLibraryUri(libraryUri);
importUri = libraryMetadata.importUri;
}
var jsExpression = await compiler.compileExpressionToJs(
importUri, scriptUri?.toString(), line, column, scope, expression);
if (setup.errors.isNotEmpty) {
jsExpression = setup.errors.toString().replaceAll(
RegExp(
r'org-dartlang-debug:synthetic_debug_expression:[0-9]*:[0-9]*:'),
'');
return TestCompilationResult(jsExpression, false);
}
return TestCompilationResult(jsExpression, true);
}
LibraryMetadata metadataForLibraryUri(Uri libraryUri) =>
metadata!.libraries.entries
.firstWhere((entry) => entry.value.fileUri == '$libraryUri')
.value;
}